private void AddTagHelperAttributes(IList <TagHelperAttributeNode> attributes, TagHelperBinding tagHelperBinding) { var descriptors = tagHelperBinding.Descriptors; var renderedBoundAttributeNames = new HashSet <string>(StringComparer.OrdinalIgnoreCase); foreach (var attribute in attributes) { var attributeValueNode = attribute.Value; var associatedDescriptors = descriptors.Where(descriptor => descriptor.BoundAttributes.Any(attributeDescriptor => TagHelperMatchingConventions.CanSatisfyBoundAttribute(attribute.Name, attributeDescriptor))); if (associatedDescriptors.Any() && renderedBoundAttributeNames.Add(attribute.Name)) { if (attributeValueNode == null) { // Minimized attributes are not valid for bound attributes. TagHelperBlockRewriter has already // logged an error if it was a bound attribute; so we can skip. continue; } foreach (var associatedDescriptor in associatedDescriptors) { var associatedAttributeDescriptor = associatedDescriptor.BoundAttributes.First(a => { return(TagHelperMatchingConventions.CanSatisfyBoundAttribute(attribute.Name, a)); }); var setTagHelperProperty = new TagHelperPropertyIntermediateNode() { AttributeName = attribute.Name, BoundAttribute = associatedAttributeDescriptor, TagHelper = associatedDescriptor, AttributeStructure = attribute.AttributeStructure, Source = BuildSourceSpanFromNode(attributeValueNode), IsIndexerNameMatch = TagHelperMatchingConventions.SatisfiesBoundAttributeIndexer(attribute.Name, associatedAttributeDescriptor), }; _builder.Push(setTagHelperProperty); attributeValueNode.Accept(this); _builder.Pop(); } } else { var addHtmlAttribute = new TagHelperHtmlAttributeIntermediateNode() { AttributeName = attribute.Name, AttributeStructure = attribute.AttributeStructure }; _builder.Push(addHtmlAttribute); if (attributeValueNode != null) { attributeValueNode.Accept(this); } _builder.Pop(); } } }
public override void VisitMarkupMinimizedTagHelperAttribute(MarkupMinimizedTagHelperAttributeSyntax node) { if (!_featureFlags.AllowMinimizedBooleanTagHelperAttributes) { // Minimized attributes are not valid for non-boolean bound attributes. TagHelperBlockRewriter // has already logged an error if it was a non-boolean bound attribute; so we can skip. return; } var element = node.FirstAncestorOrSelf <MarkupTagHelperElementSyntax>(); var descriptors = element.TagHelperInfo.BindingResult.Descriptors; var attributeName = node.Name.GetContent(); var associatedDescriptors = descriptors.Where(descriptor => descriptor.BoundAttributes.Any(attributeDescriptor => TagHelperMatchingConventions.CanSatisfyBoundAttribute(attributeName, attributeDescriptor))); if (associatedDescriptors.Any() && _renderedBoundAttributeNames.Add(attributeName)) { foreach (var associatedDescriptor in associatedDescriptors) { var associatedAttributeDescriptor = associatedDescriptor.BoundAttributes.First(a => { return(TagHelperMatchingConventions.CanSatisfyBoundAttribute(attributeName, a)); }); var expectsBooleanValue = associatedAttributeDescriptor.ExpectsBooleanValue(attributeName); if (!expectsBooleanValue) { // We do not allow minimized non-boolean bound attributes. return; } var setTagHelperProperty = new TagHelperPropertyIntermediateNode() { AttributeName = attributeName, BoundAttribute = associatedAttributeDescriptor, TagHelper = associatedDescriptor, AttributeStructure = node.TagHelperAttributeInfo.AttributeStructure, Source = null, IsIndexerNameMatch = TagHelperMatchingConventions.SatisfiesBoundAttributeIndexer(attributeName, associatedAttributeDescriptor), }; _builder.Add(setTagHelperProperty); } } else { var addHtmlAttribute = new TagHelperHtmlAttributeIntermediateNode() { AttributeName = attributeName, AttributeStructure = node.TagHelperAttributeInfo.AttributeStructure }; _builder.Add(addHtmlAttribute); } }
internal static bool ExpectsBooleanValue(this BoundAttributeDescriptor attribute, string name) { if (attribute.IsBooleanProperty) { return(true); } var isIndexerNameMatch = TagHelperMatchingConventions.SatisfiesBoundAttributeIndexer(name, attribute); return(isIndexerNameMatch && attribute.IsIndexerBooleanProperty); }
public override void VisitMarkupTagHelperAttribute(MarkupTagHelperAttributeSyntax node) { var element = node.FirstAncestorOrSelf <MarkupTagHelperElementSyntax>(); var descriptors = element.TagHelperInfo.BindingResult.Descriptors; var attributeName = node.Name.GetContent(); var attributeValueNode = node.Value; var associatedDescriptors = descriptors.Where(descriptor => descriptor.BoundAttributes.Any(attributeDescriptor => TagHelperMatchingConventions.CanSatisfyBoundAttribute(attributeName, attributeDescriptor))); if (associatedDescriptors.Any() && _renderedBoundAttributeNames.Add(attributeName)) { foreach (var associatedDescriptor in associatedDescriptors) { var associatedAttributeDescriptor = associatedDescriptor.BoundAttributes.First(a => { return(TagHelperMatchingConventions.CanSatisfyBoundAttribute(attributeName, a)); }); var setTagHelperProperty = new TagHelperPropertyIntermediateNode() { AttributeName = attributeName, BoundAttribute = associatedAttributeDescriptor, TagHelper = associatedDescriptor, AttributeStructure = node.TagHelperAttributeInfo.AttributeStructure, Source = BuildSourceSpanFromNode(attributeValueNode), IsIndexerNameMatch = TagHelperMatchingConventions.SatisfiesBoundAttributeIndexer(attributeName, associatedAttributeDescriptor), }; _builder.Push(setTagHelperProperty); VisitAttributeValue(attributeValueNode); _builder.Pop(); } } else { var addHtmlAttribute = new TagHelperHtmlAttributeIntermediateNode() { AttributeName = attributeName, AttributeStructure = node.TagHelperAttributeInfo.AttributeStructure }; _builder.Push(addHtmlAttribute); VisitAttributeValue(attributeValueNode); _builder.Pop(); } }
public void Matches_ReturnsExpectedResult( Action <RequiredAttributeDescriptorBuilder> configure, string attributeName, string attributeValue, bool expectedResult) { // Arrange var builder = new DefaultRequiredAttributeDescriptorBuilder(); configure(builder); var requiredAttibute = builder.Build(); // Act var result = TagHelperMatchingConventions.SatisfiesRequiredAttribute(attributeName, attributeValue, requiredAttibute); // Assert Assert.Equal(expectedResult, result); }
/// <summary> /// Gets all tag helpers that match the given HTML tag criteria. /// </summary> /// <param name="tagName">The name of the HTML tag to match. Providing a '*' tag name /// retrieves catch-all <see cref="TagHelperDescriptor"/>s (descriptors that target every tag).</param> /// <param name="attributes">Attributes on the HTML tag.</param> /// <param name="parentTagName">The parent tag name of the given <paramref name="tagName"/> tag.</param> /// <param name="parentIsTagHelper">Is the parent tag of the given <paramref name="tagName"/> tag a tag helper.</param> /// <returns><see cref="TagHelperDescriptor"/>s that apply to the given HTML tag criteria. /// Will return <c>null</c> if no <see cref="TagHelperDescriptor"/>s are a match.</returns> public TagHelperBinding GetBinding( string tagName, IReadOnlyList <KeyValuePair <string, string> > attributes, string parentTagName, bool parentIsTagHelper) { if (!string.IsNullOrEmpty(_tagHelperPrefix) && (tagName.Length <= _tagHelperPrefix.Length || !tagName.StartsWith(_tagHelperPrefix, StringComparison.OrdinalIgnoreCase))) { // The tagName doesn't have the tag helper prefix, we can short circuit. return(null); } IEnumerable <TagHelperDescriptor> descriptors; // Ensure there's a HashSet to use. if (!_registrations.TryGetValue(TagHelperMatchingConventions.ElementCatchAllName, out HashSet <TagHelperDescriptor> catchAllDescriptors)) { descriptors = new HashSet <TagHelperDescriptor>(TagHelperDescriptorComparer.Default); } else { descriptors = catchAllDescriptors; } // If we have a tag name associated with the requested name, we need to combine matchingDescriptors // with all the catch-all descriptors. if (_registrations.TryGetValue(tagName, out HashSet <TagHelperDescriptor> matchingDescriptors)) { descriptors = matchingDescriptors.Concat(descriptors); } var tagNameWithoutPrefix = _tagHelperPrefix != null ? new StringSegment(tagName, _tagHelperPrefix.Length) : tagName; StringSegment parentTagNameWithoutPrefix = parentTagName; if (_tagHelperPrefix != null && parentIsTagHelper) { parentTagNameWithoutPrefix = new StringSegment(parentTagName, _tagHelperPrefix.Length); } Dictionary <TagHelperDescriptor, IReadOnlyList <TagMatchingRuleDescriptor> > applicableDescriptorMappings = null; foreach (var descriptor in descriptors) { // We're avoiding desccriptor.TagMatchingRules.Where and applicableRules.Any() to avoid // Enumerator allocations on this hotpath List <TagMatchingRuleDescriptor> applicableRules = null; for (var i = 0; i < descriptor.TagMatchingRules.Count; i++) { var rule = descriptor.TagMatchingRules[i]; if (TagHelperMatchingConventions.SatisfiesRule(tagNameWithoutPrefix, parentTagNameWithoutPrefix, attributes, rule)) { if (applicableRules is null) { applicableRules = new List <TagMatchingRuleDescriptor>(); } applicableRules.Add(rule); } } if (applicableRules != null && applicableRules.Count > 0) { if (applicableDescriptorMappings == null) { applicableDescriptorMappings = new Dictionary <TagHelperDescriptor, IReadOnlyList <TagMatchingRuleDescriptor> >(); } applicableDescriptorMappings[descriptor] = applicableRules; } } if (applicableDescriptorMappings == null) { return(null); } var tagHelperBinding = new TagHelperBinding( tagName, attributes, parentTagName, applicableDescriptorMappings, _tagHelperPrefix); return(tagHelperBinding); }
/// <summary> /// Gets all tag helpers that match the given HTML tag criteria. /// </summary> /// <param name="tagName">The name of the HTML tag to match. Providing a '*' tag name /// retrieves catch-all <see cref="TagHelperDescriptor"/>s (descriptors that target every tag).</param> /// <param name="attributes">Attributes on the HTML tag.</param> /// <param name="parentTagName">The parent tag name of the given <paramref name="tagName"/> tag.</param> /// <returns><see cref="TagHelperDescriptor"/>s that apply to the given HTML tag criteria. /// Will return <c>null</c> if no <see cref="TagHelperDescriptor"/>s are a match.</returns> public TagHelperBinding GetBinding( string tagName, IReadOnlyList <KeyValuePair <string, string> > attributes, string parentTagName) { if (!string.IsNullOrEmpty(_tagHelperPrefix) && (tagName.Length <= _tagHelperPrefix.Length || !tagName.StartsWith(_tagHelperPrefix, StringComparison.OrdinalIgnoreCase))) { // The tagName doesn't have the tag helper prefix, we can short circuit. return(null); } IEnumerable <TagHelperDescriptor> descriptors; // Ensure there's a HashSet to use. if (!_registrations.TryGetValue(TagHelperMatchingConventions.ElementCatchAllName, out HashSet <TagHelperDescriptor> catchAllDescriptors)) { descriptors = new HashSet <TagHelperDescriptor>(TagHelperDescriptorComparer.Default); } else { descriptors = catchAllDescriptors; } // If we have a tag name associated with the requested name, we need to combine matchingDescriptors // with all the catch-all descriptors. if (_registrations.TryGetValue(tagName, out HashSet <TagHelperDescriptor> matchingDescriptors)) { descriptors = matchingDescriptors.Concat(descriptors); } var tagNameWithoutPrefix = _tagHelperPrefix != null?tagName.Substring(_tagHelperPrefix.Length) : tagName; Dictionary <TagHelperDescriptor, IReadOnlyList <TagMatchingRuleDescriptor> > applicableDescriptorMappings = null; foreach (var descriptor in descriptors) { var applicableRules = descriptor.TagMatchingRules.Where( rule => TagHelperMatchingConventions.SatisfiesRule(tagNameWithoutPrefix, parentTagName, attributes, rule)); if (applicableRules.Any()) { if (applicableDescriptorMappings == null) { applicableDescriptorMappings = new Dictionary <TagHelperDescriptor, IReadOnlyList <TagMatchingRuleDescriptor> >(); } applicableDescriptorMappings[descriptor] = applicableRules.ToList(); } } if (applicableDescriptorMappings == null) { return(null); } var tagHelperBinding = new TagHelperBinding( tagName, attributes, parentTagName, applicableDescriptorMappings, _tagHelperPrefix); return(tagHelperBinding); }