Changed TagHelper attributes to be SpanKind.Code if not string typed.
- Also added tests to validate that non string TagHelper attributes inherit the SpanKind.COde behavior. - Removed a block wrapping around single markup spans.
This commit is contained in:
parent
0d60da296d
commit
c947e9ffaa
|
|
@ -44,7 +44,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers
|
|||
TagName = tagName;
|
||||
CodeGenerator = new TagHelperCodeGenerator(descriptors);
|
||||
Type = startTag.Type;
|
||||
Attributes = GetTagAttributes(startTag);
|
||||
Attributes = GetTagAttributes(startTag, descriptors);
|
||||
|
||||
// There will always be at least one child for the '<'.
|
||||
Start = startTag.Children.First().Start;
|
||||
|
|
@ -107,11 +107,19 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers
|
|||
/// </summary>
|
||||
public SourceLocation Start { get; private set; }
|
||||
|
||||
private static IDictionary<string, SyntaxTreeNode> GetTagAttributes(Block tagBlock)
|
||||
private static IDictionary<string, SyntaxTreeNode> GetTagAttributes(
|
||||
Block tagBlock,
|
||||
IEnumerable<TagHelperDescriptor> descriptors)
|
||||
{
|
||||
var attributes = new Dictionary<string, SyntaxTreeNode>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// TODO: Handle malformed tags: https://github.com/aspnet/razor/issues/104
|
||||
// Build a dictionary so we can easily lookup expected attribute value lookups
|
||||
IReadOnlyDictionary<string, string> attributeValueTypes =
|
||||
descriptors.SelectMany(descriptor => descriptor.Attributes)
|
||||
.Distinct(TagHelperAttributeDescriptorComparer.Default)
|
||||
.ToDictionary(descriptor => descriptor.Name,
|
||||
descriptor => descriptor.TypeName,
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// We skip the first child "<tagname" and take everything up to the "ending" portion of the tag ">" or "/>".
|
||||
// The -2 accounts for both the start and end tags.
|
||||
|
|
@ -123,11 +131,11 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers
|
|||
|
||||
if (child.IsBlock)
|
||||
{
|
||||
attribute = ParseBlock((Block)child);
|
||||
attribute = ParseBlock((Block)child, attributeValueTypes);
|
||||
}
|
||||
else
|
||||
{
|
||||
attribute = ParseSpan((Span)child);
|
||||
attribute = ParseSpan((Span)child, attributeValueTypes);
|
||||
}
|
||||
|
||||
attributes.Add(attribute.Key, attribute.Value);
|
||||
|
|
@ -139,7 +147,9 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers
|
|||
// This method handles cases when the attribute is a simple span attribute such as
|
||||
// class="something moresomething". This does not handle complex attributes such as
|
||||
// class="@myclass". Therefore the span.Content is equivalent to the entire attribute.
|
||||
private static KeyValuePair<string, SyntaxTreeNode> ParseSpan(Span span)
|
||||
private static KeyValuePair<string, SyntaxTreeNode> ParseSpan(
|
||||
Span span,
|
||||
IReadOnlyDictionary<string, string> attributeValueTypes)
|
||||
{
|
||||
var afterEquals = false;
|
||||
var builder = new SpanBuilder
|
||||
|
|
@ -192,10 +202,12 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers
|
|||
}
|
||||
}
|
||||
|
||||
return new KeyValuePair<string, SyntaxTreeNode>(name, builder.Build());
|
||||
return CreateMarkupAttribute(name, builder, attributeValueTypes);
|
||||
}
|
||||
|
||||
private static KeyValuePair<string, SyntaxTreeNode> ParseBlock(Block block)
|
||||
private static KeyValuePair<string, SyntaxTreeNode> ParseBlock(
|
||||
Block block,
|
||||
IReadOnlyDictionary<string, string> attributeValueTypes)
|
||||
{
|
||||
// TODO: Accept more than just spans: https://github.com/aspnet/Razor/issues/96.
|
||||
// The first child will only ever NOT be a Span if a user is doing something like:
|
||||
|
|
@ -214,7 +226,7 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers
|
|||
// i.e. <div class="plain text in attribute">
|
||||
if (builder.Children.Count == 1)
|
||||
{
|
||||
return ParseSpan(childSpan);
|
||||
return ParseSpan(childSpan, attributeValueTypes);
|
||||
}
|
||||
|
||||
var textSymbol = childSpan.Symbols.FirstHtmlSymbolAs(HtmlSymbolType.Text);
|
||||
|
|
@ -246,6 +258,22 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers
|
|||
// ensure we don't do special attribute code generation since this is a tag helper).
|
||||
block = RebuildCodeGenerators(builder.Build());
|
||||
|
||||
// If there's only 1 child at this point its value could be a simple markup span (treated differently than
|
||||
// block level elements for attributes).
|
||||
if (block.Children.Count() == 1)
|
||||
{
|
||||
var child = block.Children.First() as Span;
|
||||
|
||||
if (child != null)
|
||||
{
|
||||
// After pulling apart the block we just have a value span.
|
||||
|
||||
var spanBuilder = new SpanBuilder(child);
|
||||
|
||||
return CreateMarkupAttribute(name, spanBuilder, attributeValueTypes);
|
||||
}
|
||||
}
|
||||
|
||||
return new KeyValuePair<string, SyntaxTreeNode>(name, block);
|
||||
}
|
||||
|
||||
|
|
@ -312,10 +340,46 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers
|
|||
return builder.Build();
|
||||
}
|
||||
|
||||
private static KeyValuePair<string, SyntaxTreeNode> CreateMarkupAttribute(
|
||||
string name,
|
||||
SpanBuilder builder,
|
||||
IReadOnlyDictionary<string, string> attributeValueTypes)
|
||||
{
|
||||
string attributeTypeName;
|
||||
|
||||
// If the attribute was requested by the tag helper and doesn't happen to be a string then we need to treat
|
||||
// its value as code. Any non-string value can be any C# value so we need to ensure the SyntaxTreeNode
|
||||
// reflects that.
|
||||
if (attributeValueTypes.TryGetValue(name, out attributeTypeName) &&
|
||||
!string.Equals(attributeTypeName, typeof(string).FullName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
builder.Kind = SpanKind.Code;
|
||||
}
|
||||
|
||||
return new KeyValuePair<string, SyntaxTreeNode>(name, builder.Build());
|
||||
}
|
||||
|
||||
private static bool IsQuote(HtmlSymbol htmlSymbol)
|
||||
{
|
||||
return htmlSymbol.Type == HtmlSymbolType.DoubleQuote ||
|
||||
htmlSymbol.Type == HtmlSymbolType.SingleQuote;
|
||||
}
|
||||
|
||||
// This class is used to compare tag helper attributes by comparing only the HTML attribute name.
|
||||
private class TagHelperAttributeDescriptorComparer : IEqualityComparer<TagHelperAttributeDescriptor>
|
||||
{
|
||||
public static readonly TagHelperAttributeDescriptorComparer Default =
|
||||
new TagHelperAttributeDescriptorComparer();
|
||||
|
||||
public bool Equals(TagHelperAttributeDescriptor descriptorX, TagHelperAttributeDescriptor descriptorY)
|
||||
{
|
||||
return string.Equals(descriptorX.Name, descriptorY.Name, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public int GetHashCode(TagHelperAttributeDescriptor descriptor)
|
||||
{
|
||||
return StringComparer.OrdinalIgnoreCase.GetHashCode(descriptor.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -114,6 +114,11 @@ namespace Microsoft.AspNet.Razor.Test.Framework
|
|||
return self.Span(SpanKind.Markup, content, markup: true).With(new MarkupCodeGenerator());
|
||||
}
|
||||
|
||||
public static SpanConstructor CodeMarkup(this SpanFactory self, params string[] content)
|
||||
{
|
||||
return self.Span(SpanKind.Code, content, markup: true).With(new MarkupCodeGenerator());
|
||||
}
|
||||
|
||||
public static SourceLocation GetLocationAndAdvance(this SourceLocationTracker self, string content)
|
||||
{
|
||||
var ret = self.CurrentLocation;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
|
@ -18,6 +19,98 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
|
|||
{
|
||||
public class TagHelperParseTreeRewriterTest : CsHtmlMarkupParserTestBase
|
||||
{
|
||||
public static TheoryData CodeTagHelperAttributesData
|
||||
{
|
||||
get
|
||||
{
|
||||
var factory = CreateDefaultSpanFactory();
|
||||
var dateTimeNow = new MarkupBlock(
|
||||
factory.Markup(" "),
|
||||
new ExpressionBlock(
|
||||
factory.CodeTransition(),
|
||||
factory.Code("DateTime.Now")
|
||||
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
|
||||
.Accepts(AcceptedCharacters.NonWhiteSpace)));
|
||||
|
||||
return new TheoryData<string, Block>
|
||||
{
|
||||
{
|
||||
"<person age=\"12\" />",
|
||||
new MarkupBlock(
|
||||
new MarkupTagHelperBlock("person",
|
||||
new Dictionary<string, SyntaxTreeNode>
|
||||
{
|
||||
{ "age", factory.CodeMarkup("12") }
|
||||
}))
|
||||
},
|
||||
{
|
||||
"<person birthday=\"DateTime.Now\" />",
|
||||
new MarkupBlock(
|
||||
new MarkupTagHelperBlock("person",
|
||||
new Dictionary<string, SyntaxTreeNode>
|
||||
{
|
||||
{ "birthday", factory.CodeMarkup("DateTime.Now") }
|
||||
}))
|
||||
},
|
||||
{
|
||||
"<person name=\"John\" />",
|
||||
new MarkupBlock(
|
||||
new MarkupTagHelperBlock("person",
|
||||
new Dictionary<string, SyntaxTreeNode>
|
||||
{
|
||||
{ "name", factory.Markup("John") }
|
||||
}))
|
||||
},
|
||||
{
|
||||
"<person name=\"Time: @DateTime.Now\" />",
|
||||
new MarkupBlock(
|
||||
new MarkupTagHelperBlock("person",
|
||||
new Dictionary<string, SyntaxTreeNode>
|
||||
{
|
||||
{ "name", new MarkupBlock(factory.Markup("Time:"), dateTimeNow) }
|
||||
}))
|
||||
},
|
||||
{
|
||||
"<person age=\"12\" birthday=\"DateTime.Now\" name=\"Time: @DateTime.Now\" />",
|
||||
new MarkupBlock(
|
||||
new MarkupTagHelperBlock("person",
|
||||
new Dictionary<string, SyntaxTreeNode>
|
||||
{
|
||||
{ "age", factory.CodeMarkup("12") },
|
||||
{ "birthday", factory.CodeMarkup("DateTime.Now") },
|
||||
{ "name", new MarkupBlock(factory.Markup("Time:"), dateTimeNow) }
|
||||
}))
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(CodeTagHelperAttributesData))]
|
||||
public void TagHelperParseTreeRewriter_CreatesMarkupCodeSpansForNonStringTagHelperAttributes(
|
||||
string documentContent,
|
||||
MarkupBlock expectedOutput)
|
||||
{
|
||||
// Arrange
|
||||
var descriptors = new TagHelperDescriptor[]
|
||||
{
|
||||
new TagHelperDescriptor("person", "PersonTagHelper", "personAssembly", ContentBehavior.None,
|
||||
attributes: new[]
|
||||
{
|
||||
new TagHelperAttributeDescriptor("age", "Age", typeof(int).FullName),
|
||||
new TagHelperAttributeDescriptor("birthday", "BirthDay", typeof(DateTime).FullName),
|
||||
new TagHelperAttributeDescriptor("name", "Name", typeof(string).FullName),
|
||||
})
|
||||
};
|
||||
var providerContext = new TagHelperDescriptorProvider(descriptors);
|
||||
|
||||
// Act & Assert
|
||||
EvaluateData(providerContext,
|
||||
documentContent,
|
||||
expectedOutput,
|
||||
expectedErrors: Enumerable.Empty<RazorError>());
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> IncompleteHelperBlockData
|
||||
{
|
||||
get
|
||||
|
|
@ -39,9 +132,9 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
|
|||
new MarkupTagHelperBlock("p",
|
||||
new Dictionary<string, SyntaxTreeNode>
|
||||
{
|
||||
{ "class", new MarkupBlock(factory.Markup("foo")) },
|
||||
{ "class", factory.Markup("foo") },
|
||||
{ "dynamic", new MarkupBlock(dateTimeNow) },
|
||||
{ "style", new MarkupBlock(factory.Markup("color:red;")) }
|
||||
{ "style", factory.Markup("color:red;") }
|
||||
},
|
||||
new MarkupTagHelperBlock("strong",
|
||||
blockFactory.MarkupTagBlock("</p>")))),
|
||||
|
|
@ -78,13 +171,13 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
|
|||
new MarkupTagHelperBlock("p",
|
||||
new Dictionary<string, SyntaxTreeNode>
|
||||
{
|
||||
{ "class", new MarkupBlock(factory.Markup("foo")) }
|
||||
{ "class", factory.Markup("foo") }
|
||||
},
|
||||
factory.Markup("Hello "),
|
||||
new MarkupTagHelperBlock("p",
|
||||
new Dictionary<string, SyntaxTreeNode>
|
||||
{
|
||||
{ "style", new MarkupBlock(factory.Markup("color:red;")) }
|
||||
{ "style", factory.Markup("color:red;") }
|
||||
},
|
||||
factory.Markup("World")))),
|
||||
new RazorError(string.Format(CultureInfo.InvariantCulture, errorFormat, "p"),
|
||||
|
|
@ -426,7 +519,7 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
|
|||
new MarkupTagHelperBlock("p",
|
||||
new Dictionary<string, SyntaxTreeNode>
|
||||
{
|
||||
{ "class", new MarkupBlock(factory.Markup(" foo")) },
|
||||
{ "class", factory.Markup(" foo") },
|
||||
{ "style",
|
||||
new MarkupBlock(
|
||||
factory.Markup(" color"),
|
||||
|
|
@ -443,7 +536,7 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
|
|||
new MarkupTagHelperBlock("p",
|
||||
new Dictionary<string, SyntaxTreeNode>
|
||||
{
|
||||
{ "class", new MarkupBlock(factory.Markup(" foo")) },
|
||||
{ "class", factory.Markup(" foo") },
|
||||
{ "style",
|
||||
new MarkupBlock(
|
||||
factory.Markup(" color"),
|
||||
|
|
@ -782,8 +875,8 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
|
|||
new MarkupTagHelperBlock("script",
|
||||
new Dictionary<string, SyntaxTreeNode>
|
||||
{
|
||||
{ "class", new MarkupBlock(factory.Markup("foo")) },
|
||||
{ "style", new MarkupBlock(factory.Markup("color:red;")) }
|
||||
{ "class", factory.Markup("foo") },
|
||||
{ "style", factory.Markup("color:red;") }
|
||||
}))
|
||||
};
|
||||
yield return new object[] {
|
||||
|
|
@ -794,8 +887,8 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
|
|||
new MarkupTagHelperBlock("script",
|
||||
new Dictionary<string, SyntaxTreeNode>
|
||||
{
|
||||
{ "class", new MarkupBlock(factory.Markup("foo")) },
|
||||
{ "style", new MarkupBlock(factory.Markup("color:red;")) }
|
||||
{ "class", factory.Markup("foo") },
|
||||
{ "style", factory.Markup("color:red;") }
|
||||
}),
|
||||
factory.Markup(" World")))
|
||||
};
|
||||
|
|
@ -823,8 +916,8 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
|
|||
new MarkupTagHelperBlock("p",
|
||||
new Dictionary<string, SyntaxTreeNode>
|
||||
{
|
||||
{ "class", new MarkupBlock(factory.Markup("foo")) },
|
||||
{ "style", new MarkupBlock(factory.Markup("color:red;")) }
|
||||
{ "class", factory.Markup("foo") },
|
||||
{ "style", factory.Markup("color:red;") }
|
||||
}))
|
||||
};
|
||||
yield return new object[] {
|
||||
|
|
@ -835,8 +928,8 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
|
|||
new MarkupTagHelperBlock("p",
|
||||
new Dictionary<string, SyntaxTreeNode>
|
||||
{
|
||||
{ "class", new MarkupBlock(factory.Markup("foo")) },
|
||||
{ "style", new MarkupBlock(factory.Markup("color:red;")) }
|
||||
{ "class", factory.Markup("foo") },
|
||||
{ "style", factory.Markup("color:red;") }
|
||||
}),
|
||||
factory.Markup(" World")))
|
||||
};
|
||||
|
|
@ -847,13 +940,13 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
|
|||
new MarkupTagHelperBlock("p",
|
||||
new Dictionary<string, SyntaxTreeNode>
|
||||
{
|
||||
{ "class", new MarkupBlock(factory.Markup("foo")) }
|
||||
{ "class", factory.Markup("foo") }
|
||||
}),
|
||||
factory.Markup(" "),
|
||||
new MarkupTagHelperBlock("p",
|
||||
new Dictionary<string, SyntaxTreeNode>
|
||||
{
|
||||
{ "style", new MarkupBlock(factory.Markup("color:red;")) }
|
||||
{ "style", factory.Markup("color:red;") }
|
||||
}),
|
||||
factory.Markup("World"))
|
||||
};
|
||||
|
|
@ -888,9 +981,9 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
|
|||
new MarkupTagHelperBlock("p",
|
||||
new Dictionary<string, SyntaxTreeNode>
|
||||
{
|
||||
{ "class", new MarkupBlock(factory.Markup("foo")) },
|
||||
{ "class", factory.Markup("foo") },
|
||||
{ "dynamic", new MarkupBlock(dateTimeNow) },
|
||||
{ "style", new MarkupBlock(factory.Markup("color:red;")) }
|
||||
{ "style", factory.Markup("color:red;") }
|
||||
}))
|
||||
};
|
||||
yield return new object[] {
|
||||
|
|
@ -899,9 +992,9 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
|
|||
new MarkupTagHelperBlock("p",
|
||||
new Dictionary<string, SyntaxTreeNode>
|
||||
{
|
||||
{ "class", new MarkupBlock(factory.Markup("foo")) },
|
||||
{ "class", factory.Markup("foo") },
|
||||
{ "dynamic", new MarkupBlock(dateTimeNow) },
|
||||
{ "style", new MarkupBlock(factory.Markup("color:red;")) }
|
||||
{ "style", factory.Markup("color:red;") }
|
||||
},
|
||||
factory.Markup("Hello World")))
|
||||
};
|
||||
|
|
@ -911,7 +1004,7 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
|
|||
new MarkupTagHelperBlock("p",
|
||||
new Dictionary<string, SyntaxTreeNode>
|
||||
{
|
||||
{ "class", new MarkupBlock(factory.Markup("foo")) },
|
||||
{ "class", factory.Markup("foo") },
|
||||
{ "dynamic", new MarkupBlock(dateTimeNow) }
|
||||
},
|
||||
factory.Markup("Hello")),
|
||||
|
|
@ -919,7 +1012,7 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
|
|||
new MarkupTagHelperBlock("p",
|
||||
new Dictionary<string, SyntaxTreeNode>
|
||||
{
|
||||
{ "style", new MarkupBlock(factory.Markup("color:red;")) },
|
||||
{ "style", factory.Markup("color:red;") },
|
||||
{ "dynamic", new MarkupBlock(dateTimeNow) }
|
||||
},
|
||||
factory.Markup("World")))
|
||||
|
|
@ -930,9 +1023,9 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
|
|||
new MarkupTagHelperBlock("p",
|
||||
new Dictionary<string, SyntaxTreeNode>
|
||||
{
|
||||
{ "class", new MarkupBlock(factory.Markup("foo")) },
|
||||
{ "class", factory.Markup("foo") },
|
||||
{ "dynamic", new MarkupBlock(dateTimeNow) },
|
||||
{ "style", new MarkupBlock(factory.Markup("color:red;")) }
|
||||
{ "style", factory.Markup("color:red;") }
|
||||
},
|
||||
factory.Markup("Hello World "),
|
||||
new MarkupTagBlock(
|
||||
|
|
@ -973,8 +1066,8 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
|
|||
new MarkupTagHelperBlock("p",
|
||||
new Dictionary<string, SyntaxTreeNode>
|
||||
{
|
||||
{ "class", new MarkupBlock(factory.Markup("foo")) },
|
||||
{ "style", new MarkupBlock(factory.Markup("color:red;")) }
|
||||
{ "class", factory.Markup("foo") },
|
||||
{ "style", factory.Markup("color:red;") }
|
||||
}))
|
||||
};
|
||||
yield return new object[] {
|
||||
|
|
@ -983,8 +1076,8 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
|
|||
new MarkupTagHelperBlock("p",
|
||||
new Dictionary<string, SyntaxTreeNode>
|
||||
{
|
||||
{ "class", new MarkupBlock(factory.Markup("foo")) },
|
||||
{ "style", new MarkupBlock(factory.Markup("color:red;")) }
|
||||
{ "class", factory.Markup("foo") },
|
||||
{ "style", factory.Markup("color:red;") }
|
||||
},
|
||||
factory.Markup("Hello World")))
|
||||
};
|
||||
|
|
@ -994,14 +1087,14 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
|
|||
new MarkupTagHelperBlock("p",
|
||||
new Dictionary<string, SyntaxTreeNode>
|
||||
{
|
||||
{ "class", new MarkupBlock(factory.Markup("foo")) }
|
||||
{ "class", factory.Markup("foo") }
|
||||
},
|
||||
factory.Markup("Hello")),
|
||||
factory.Markup(" "),
|
||||
new MarkupTagHelperBlock("p",
|
||||
new Dictionary<string, SyntaxTreeNode>
|
||||
{
|
||||
{ "style", new MarkupBlock(factory.Markup("color:red;")) }
|
||||
{ "style", factory.Markup("color:red;") }
|
||||
},
|
||||
factory.Markup("World")))
|
||||
};
|
||||
|
|
@ -1011,8 +1104,8 @@ namespace Microsoft.AspNet.Razor.Test.TagHelpers
|
|||
new MarkupTagHelperBlock("p",
|
||||
new Dictionary<string, SyntaxTreeNode>
|
||||
{
|
||||
{ "class", new MarkupBlock(factory.Markup("foo")) },
|
||||
{ "style", new MarkupBlock(factory.Markup("color:red;")) }
|
||||
{ "class", factory.Markup("foo") },
|
||||
{ "style", factory.Markup("color:red;") }
|
||||
},
|
||||
factory.Markup("Hello World "),
|
||||
new MarkupTagBlock(
|
||||
|
|
|
|||
Loading…
Reference in New Issue