/// <summary> /// Returns true if there is a metadata expression (outside of a transform) in the expression. /// </summary> internal static bool ContainsMetadataExpressionOutsideTransform(string expression) { ItemsAndMetadataPair pair = new ItemsAndMetadataPair(null, null); GetReferencedItemNamesAndMetadata(expression, 0, expression.Length, ref pair, ShredderOptions.MetadataOutsideTransforms); bool result = (pair.Metadata?.Count > 0); return(result); }
/// <summary> /// Given a list of expressions that may contain item list expressions, /// returns a pair of tables of all item names found, as K=Name, V=String.Empty; /// and all metadata not in transforms, as K=Metadata key, V=MetadataReference, /// where metadata key is like "itemname.metadataname" or "metadataname". /// PERF: Tables are null if there are no entries, because this is quite a common case. /// </summary> internal static ItemsAndMetadataPair GetReferencedItemNamesAndMetadata(List <string> expressions) { ItemsAndMetadataPair pair = new ItemsAndMetadataPair(null, null); foreach (string expression in expressions) { GetReferencedItemNamesAndMetadata(expression, 0, expression.Length, ref pair, ShredderOptions.All); } return(pair); }
private void VerifyExpression(string test) { List <string> list = new List <string>(); list.Add(test); ItemsAndMetadataPair pair = ExpressionShredder.GetReferencedItemNamesAndMetadata(list); Hashtable actualItems = pair.Items; Dictionary <string, MetadataReference> actualMetadata = pair.Metadata; Hashtable expectedItems = GetConsumedItemReferences_OriginalImplementation(test); Console.WriteLine("verifying item names..."); VerifyAgainstCanonicalResults(test, actualItems, expectedItems); Hashtable expectedMetadata = GetConsumedMetadataReferences_OriginalImplementation(test); Console.WriteLine("verifying metadata ..."); VerifyAgainstCanonicalResults(test, actualMetadata, expectedMetadata); Console.WriteLine("===OK==="); }
internal static List <ItemBucket> PrepareBatchingBuckets ( List <string> batchableObjectParameters, Lookup lookup, string implicitBatchableItemType, ElementLocation elementLocation ) { if (batchableObjectParameters == null) { ErrorUtilities.ThrowInternalError("Need the parameters of the batchable object to determine if it can be batched."); } if (lookup == null) { ErrorUtilities.ThrowInternalError("Need to specify the lookup."); } ItemsAndMetadataPair pair = ExpressionShredder.GetReferencedItemNamesAndMetadata(batchableObjectParameters); // All the @(itemname) item list references in the tag, including transforms, etc. HashSet <string> consumedItemReferences = pair.Items; // All the %(itemname.metadataname) references in the tag (not counting those embedded // inside item transforms), and note that the itemname portion is optional. // The keys in the returned hash table are the qualified metadata names (e.g. "EmbeddedResource.Culture" // or just "Culture"). The values are MetadataReference structs, which simply split out the item // name (possibly null) and the actual metadata name. Dictionary <string, MetadataReference> consumedMetadataReferences = pair.Metadata; List <ItemBucket> buckets = null; if (consumedMetadataReferences?.Count > 0) { // Add any item types that we were explicitly told to assume. if (implicitBatchableItemType != null) { consumedItemReferences ??= new HashSet <string>(MSBuildNameIgnoreCaseComparer.Default); consumedItemReferences.Add(implicitBatchableItemType); } // This method goes through all the item list references and figures out which ones // will be participating in batching, and which ones won't. We get back a hashtable // where the key is the item name that will be participating in batching. The values // are all String.Empty (not used). This method may return additional item names // that weren't represented in "consumedItemReferences"... this would happen if there // were qualified metadata references in the consumedMetadataReferences table, such as // %(EmbeddedResource.Culture). Dictionary <string, ICollection <ProjectItemInstance> > itemListsToBeBatched = GetItemListsToBeBatched(consumedMetadataReferences, consumedItemReferences, lookup, elementLocation); // At this point, if there were any metadata references in the tag, but no item // references to batch on, we've got a problem because we can't figure out which // item lists the user wants us to batch. if (itemListsToBeBatched.Count == 0) { foreach (string unqualifiedMetadataName in consumedMetadataReferences.Keys) { // Of course, since this throws an exception, there's no way we're ever going // to really loop here... it's just that the foreach is the only way I can // figure out how to get data out of the hashtable without knowing any of the // keys! ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "CannotReferenceItemMetadataWithoutItemName", unqualifiedMetadataName); } } else { // If the batchable object consumes item metadata as well as items to be batched, // we need to partition the items consumed by the object. buckets = BucketConsumedItems(lookup, itemListsToBeBatched, consumedMetadataReferences, elementLocation); } } // if the batchable object does not consume any item metadata or items, or if the item lists it consumes are all // empty, then the object does not need to be batched if ((buckets == null) || (buckets.Count == 0)) { // create a default bucket that references the project items and properties -- this way we always have a bucket buckets = new List <ItemBucket>(1); buckets.Add(new ItemBucket(null, null, lookup, buckets.Count)); } return(buckets); }
/// <summary> /// Given a subexpression, finds referenced item names and inserts them into the table /// as K=Name, V=String.Empty. /// </summary> /// <remarks> /// We can ignore any semicolons in the expression, since we're not itemizing it. /// </remarks> private static void GetReferencedItemNamesAndMetadata(string expression, int start, int end, ref ItemsAndMetadataPair pair, ShredderOptions whatToShredFor) { for (int i = start; i < end; i++) { int restartPoint; if (Sink(expression, ref i, end, '@', '(')) { // Start of a possible item list expression // Store the index to backtrack to if this doesn't turn out to be a well // formed metadata expression. (Subtract one for the increment when we loop around.) restartPoint = i - 1; SinkWhitespace(expression, ref i); int startOfName = i; if (!SinkValidName(expression, ref i, end)) { i = restartPoint; continue; } // '-' is a legitimate char in an item name, but we should match '->' as an arrow // in '@(foo->'x')' rather than as the last char of the item name. // The old regex accomplished this by being "greedy" if (end > i && expression[i - 1] == '-' && expression[i] == '>') { i--; } // Grab the name, but continue to verify it's a well-formed expression // before we store it. string name = expression.Substring(startOfName, i - startOfName); SinkWhitespace(expression, ref i); // If there's an '->' eat it and the subsequent quoted expression if (Sink(expression, ref i, end, '-', '>')) { SinkWhitespace(expression, ref i); if (!SinkSingleQuotedExpression(expression, ref i, end)) { i = restartPoint; continue; } } SinkWhitespace(expression, ref i); // If there's a ',', eat it and the subsequent quoted expression if (Sink(expression, ref i, ',')) { SinkWhitespace(expression, ref i); if (!Sink(expression, ref i, '\'')) { i = restartPoint; continue; } int closingQuote = expression.IndexOf('\'', i); if (closingQuote == -1) { i = restartPoint; continue; } // Look for metadata in the separator expression // e.g., @(foo, '%(bar)') contains batchable metadata 'bar' GetReferencedItemNamesAndMetadata(expression, i, closingQuote, ref pair, ShredderOptions.MetadataOutsideTransforms); i = closingQuote + 1; } SinkWhitespace(expression, ref i); if (!Sink(expression, ref i, ')')) { i = restartPoint; continue; } // If we've got this far, we know the item expression was // well formed, so make sure the name's in the table if ((whatToShredFor & ShredderOptions.ItemTypes) != 0) { pair.Items = Utilities.CreateTableIfNecessary(pair.Items); pair.Items[name] = String.Empty; } i--; continue; } if (Sink(expression, ref i, end, '%', '(')) { // Start of a possible metadata expression // Store the index to backtrack to if this doesn't turn out to be a well // formed metadata expression. (Subtract one for the increment when we loop around.) restartPoint = i - 1; SinkWhitespace(expression, ref i); int startOfText = i; if (!SinkValidName(expression, ref i, end)) { i = restartPoint; continue; } // Grab this, but we don't know if it's an item or metadata name yet string firstPart = expression.Substring(startOfText, i - startOfText); string itemName = null; string metadataName; string qualifiedMetadataName; SinkWhitespace(expression, ref i); bool qualified = Sink(expression, ref i, '.'); if (qualified) { SinkWhitespace(expression, ref i); startOfText = i; if (!SinkValidName(expression, ref i, end)) { i = restartPoint; continue; } itemName = firstPart; metadataName = expression.Substring(startOfText, i - startOfText); qualifiedMetadataName = itemName + "." + metadataName; } else { metadataName = firstPart; qualifiedMetadataName = metadataName; } SinkWhitespace(expression, ref i); if (!Sink(expression, ref i, ')')) { i = restartPoint; continue; } if ((whatToShredFor & ShredderOptions.MetadataOutsideTransforms) != 0) { pair.Metadata = Utilities.CreateTableIfNecessary(pair.Metadata); pair.Metadata[qualifiedMetadataName] = new MetadataReference(itemName, metadataName); } i--; } } }