Parse the whitespace surrounding equals in attribute correctly

- #123
 - Handled the corresponding cases in tag helper scenarios
 - Added unit and code generation tests
This commit is contained in:
Ajay Bhargav Baaskaran 2015-08-28 17:26:35 -07:00
parent 6568de38d1
commit 08c8f9f7ba
8 changed files with 339 additions and 66 deletions

View File

@ -463,6 +463,7 @@ namespace Microsoft.AspNet.Razor.Parser
// http://dev.w3.org/html5/spec/tokenization.html#attribute-name-state // http://dev.w3.org/html5/spec/tokenization.html#attribute-name-state
// Read the 'name' (i.e. read until the '=' or whitespace/newline) // Read the 'name' (i.e. read until the '=' or whitespace/newline)
var name = Enumerable.Empty<HtmlSymbol>(); var name = Enumerable.Empty<HtmlSymbol>();
var whitespaceAfterAttributeName = Enumerable.Empty<HtmlSymbol>();
if (At(HtmlSymbolType.Text)) if (At(HtmlSymbolType.Text))
{ {
name = ReadWhile(sym => name = ReadWhile(sym =>
@ -472,6 +473,10 @@ namespace Microsoft.AspNet.Razor.Parser
sym.Type != HtmlSymbolType.CloseAngle && sym.Type != HtmlSymbolType.CloseAngle &&
sym.Type != HtmlSymbolType.OpenAngle && sym.Type != HtmlSymbolType.OpenAngle &&
(sym.Type != HtmlSymbolType.ForwardSlash || !NextIs(HtmlSymbolType.CloseAngle))); (sym.Type != HtmlSymbolType.ForwardSlash || !NextIs(HtmlSymbolType.CloseAngle)));
// capture whitespace after attribute name (if any)
whitespaceAfterAttributeName = ReadWhile(
sym => sym.Type == HtmlSymbolType.WhiteSpace || sym.Type == HtmlSymbolType.NewLine);
} }
else else
{ {
@ -485,6 +490,10 @@ namespace Microsoft.AspNet.Razor.Parser
{ {
// Minimized attribute // Minimized attribute
// We are at the prefix of the next attribute or the end of tag. Put it back so it is parsed later.
PutCurrentBack();
PutBack(whitespaceAfterAttributeName);
// Output anything prior to the attribute, in most cases this will be the tag name: // Output anything prior to the attribute, in most cases this will be the tag name:
// |<input| checked />. If in-between other attributes this will noop or output malformed attribute // |<input| checked />. If in-between other attributes this will noop or output malformed attribute
// content (if the previous attribute was malformed). // content (if the previous attribute was malformed).
@ -507,11 +516,14 @@ namespace Microsoft.AspNet.Razor.Parser
// Start a new markup block for the attribute // Start a new markup block for the attribute
using (Context.StartBlock(BlockType.Markup)) using (Context.StartBlock(BlockType.Markup))
{ {
AttributePrefix(whitespace, name); AttributePrefix(whitespace, name, whitespaceAfterAttributeName);
} }
} }
private void AttributePrefix(IEnumerable<HtmlSymbol> whitespace, IEnumerable<HtmlSymbol> nameSymbols) private void AttributePrefix(
IEnumerable<HtmlSymbol> whitespace,
IEnumerable<HtmlSymbol> nameSymbols,
IEnumerable<HtmlSymbol> whitespaceAfterAttributeName)
{ {
// First, determine if this is a 'data-' attribute (since those can't use conditional attributes) // First, determine if this is a 'data-' attribute (since those can't use conditional attributes)
var name = nameSymbols.GetContent(Span.Start); var name = nameSymbols.GetContent(Span.Start);
@ -520,14 +532,27 @@ namespace Microsoft.AspNet.Razor.Parser
// Accept the whitespace and name // Accept the whitespace and name
Accept(whitespace); Accept(whitespace);
Accept(nameSymbols); Accept(nameSymbols);
// Since this is not a minimized attribute, the whitespace after attribute name belongs to this attribute.
Accept(whitespaceAfterAttributeName);
Assert(HtmlSymbolType.Equals); // We should be at "=" Assert(HtmlSymbolType.Equals); // We should be at "="
AcceptAndMoveNext(); AcceptAndMoveNext();
var whitespaceAfterEquals = ReadWhile(sym => sym.Type == HtmlSymbolType.WhiteSpace || sym.Type == HtmlSymbolType.NewLine);
var quote = HtmlSymbolType.Unknown; var quote = HtmlSymbolType.Unknown;
if (At(HtmlSymbolType.SingleQuote) || At(HtmlSymbolType.DoubleQuote)) if (At(HtmlSymbolType.SingleQuote) || At(HtmlSymbolType.DoubleQuote))
{ {
// Found a quote, the whitespace belongs to this attribute.
Accept(whitespaceAfterEquals);
quote = CurrentSymbol.Type; quote = CurrentSymbol.Type;
AcceptAndMoveNext(); AcceptAndMoveNext();
} }
else if (whitespaceAfterEquals.Any())
{
// No quotes found after the whitespace. Put it back so that it can be parsed later.
PutCurrentBack();
PutBack(whitespaceAfterEquals);
}
// We now have the prefix: (i.e. ' foo="') // We now have the prefix: (i.e. ' foo="')
var prefix = Span.GetContent(); var prefix = Span.GetContent();
@ -537,10 +562,15 @@ namespace Microsoft.AspNet.Razor.Parser
Span.ChunkGenerator = SpanChunkGenerator.Null; // The block chunk generator will render the prefix Span.ChunkGenerator = SpanChunkGenerator.Null; // The block chunk generator will render the prefix
Output(SpanKind.Markup); Output(SpanKind.Markup);
// Read the values // Read the attribute value only if the value is quoted
while (!EndOfFile && !IsEndOfAttributeValue(quote, CurrentSymbol)) // or if there is no whitespace between '=' and the unquoted value.
if (quote != HtmlSymbolType.Unknown || !whitespaceAfterEquals.Any())
{ {
AttributeValue(quote); // Read the attribute value.
while (!EndOfFile && !IsEndOfAttributeValue(quote, CurrentSymbol))
{
AttributeValue(quote);
}
} }
// Capture the suffix // Capture the suffix
@ -567,6 +597,11 @@ namespace Microsoft.AspNet.Razor.Parser
// Output the attribute name, the equals and optional quote. Ex: foo=" // Output the attribute name, the equals and optional quote. Ex: foo="
Output(SpanKind.Markup); Output(SpanKind.Markup);
if (quote == HtmlSymbolType.Unknown && whitespaceAfterEquals.Any())
{
return;
}
// Not a "conditional" attribute, so just read the value // Not a "conditional" attribute, so just read the value
SkipToAndParseCode(sym => IsEndOfAttributeValue(quote, sym)); SkipToAndParseCode(sym => IsEndOfAttributeValue(quote, sym));

View File

@ -206,23 +206,32 @@ namespace Microsoft.AspNet.Razor.Parser.TagHelpers.Internal
// The goal here is to consume the equal sign and the optional single/double-quote. // The goal here is to consume the equal sign and the optional single/double-quote.
// The coming symbols will either be a quote or value (in the case that the value is unquoted). // The coming symbols will either be a quote or value (in the case that the value is unquoted).
// Spaces after/before the equal symbol are not yet supported:
// https://github.com/aspnet/Razor/issues/123
// TODO: Handle malformed tags, if there's an '=' then there MUST be a value. // TODO: Handle malformed tags, if there's an '=' then there MUST be a value.
// https://github.com/aspnet/Razor/issues/104 // https://github.com/aspnet/Razor/issues/104
SourceLocation symbolStartLocation; SourceLocation symbolStartLocation;
// Skip the whitespace preceding the start of the attribute value.
var valueStartIndex = i + 1; // Start from the symbol after '='.
while (valueStartIndex < htmlSymbols.Length &&
(htmlSymbols[valueStartIndex].Type == HtmlSymbolType.WhiteSpace ||
htmlSymbols[valueStartIndex].Type == HtmlSymbolType.NewLine))
{
valueStartIndex++;
}
// Check for attribute start values, aka single or double quote // Check for attribute start values, aka single or double quote
if ((i + 1) < htmlSymbols.Length && IsQuote(htmlSymbols[i + 1])) if (valueStartIndex < htmlSymbols.Length && IsQuote(htmlSymbols[valueStartIndex]))
{ {
// Move past the attribute start so we can accept the true value. // Move past the attribute start so we can accept the true value.
i++; valueStartIndex++;
symbolStartLocation = htmlSymbols[i].Start; symbolStartLocation = htmlSymbols[valueStartIndex].Start;
// If there's a start quote then there must be an end quote to be valid, skip it. // If there's a start quote then there must be an end quote to be valid, skip it.
symbolOffset = 1; symbolOffset = 1;
i = valueStartIndex - 1;
} }
else else
{ {

View File

@ -848,32 +848,36 @@ namespace Microsoft.AspNet.Razor.Test.Generator
DefaultPAndInputTagHelperDescriptors, DefaultPAndInputTagHelperDescriptors,
new List<LineMapping> new List<LineMapping>
{ {
BuildLineMapping(documentAbsoluteIndex: 14, BuildLineMapping(
documentLineIndex: 0, documentAbsoluteIndex: 14,
generatedAbsoluteIndex: 493, documentLineIndex: 0,
generatedLineIndex: 15, generatedAbsoluteIndex: 493,
characterOffsetIndex: 14, generatedLineIndex: 15,
contentLength: 11), characterOffsetIndex: 14,
BuildLineMapping(documentAbsoluteIndex: 62, contentLength: 11),
documentLineIndex: 3, BuildLineMapping(
documentCharacterOffsetIndex: 26, documentAbsoluteIndex: 63,
generatedAbsoluteIndex: 1289, documentLineIndex: 3,
generatedLineIndex: 39, documentCharacterOffsetIndex: 27,
generatedCharacterOffsetIndex: 28, generatedAbsoluteIndex: 1289,
contentLength: 0), generatedLineIndex: 39,
BuildLineMapping(documentAbsoluteIndex: 122, generatedCharacterOffsetIndex: 28,
documentLineIndex: 5, contentLength: 0),
generatedAbsoluteIndex: 1634, BuildLineMapping(
generatedLineIndex: 48, documentAbsoluteIndex: 122,
characterOffsetIndex: 30, documentLineIndex: 5,
contentLength: 0), generatedAbsoluteIndex: 1634,
BuildLineMapping(documentAbsoluteIndex: 88, generatedLineIndex: 48,
documentLineIndex: 4, characterOffsetIndex: 30,
documentCharacterOffsetIndex: 12, contentLength: 0),
generatedAbsoluteIndex: 1789, BuildLineMapping(
generatedLineIndex: 54, documentAbsoluteIndex: 89,
generatedCharacterOffsetIndex: 19, documentLineIndex: 4,
contentLength: 0) documentCharacterOffsetIndex: 13,
generatedAbsoluteIndex: 1789,
generatedLineIndex: 54,
generatedCharacterOffsetIndex: 19,
contentLength: 0),
} }
}, },
{ {
@ -1484,6 +1488,7 @@ namespace Microsoft.AspNet.Razor.Test.Generator
{ {
{ "SingleTagHelper", null, DefaultPAndInputTagHelperDescriptors }, { "SingleTagHelper", null, DefaultPAndInputTagHelperDescriptors },
{ "SingleTagHelperWithNewlineBeforeAttributes", null, DefaultPAndInputTagHelperDescriptors }, { "SingleTagHelperWithNewlineBeforeAttributes", null, DefaultPAndInputTagHelperDescriptors },
{ "TagHelpersWithWeirdlySpacedAttributes", null, DefaultPAndInputTagHelperDescriptors },
{ "BasicTagHelpers", null, DefaultPAndInputTagHelperDescriptors }, { "BasicTagHelpers", null, DefaultPAndInputTagHelperDescriptors },
{ "BasicTagHelpers.RemoveTagHelper", null, DefaultPAndInputTagHelperDescriptors }, { "BasicTagHelpers.RemoveTagHelper", null, DefaultPAndInputTagHelperDescriptors },
{ "BasicTagHelpers.Prefixed", null, PrefixedPAndInputTagHelperDescriptors }, { "BasicTagHelpers.Prefixed", null, PrefixedPAndInputTagHelperDescriptors },

View File

@ -21,9 +21,56 @@ namespace Microsoft.AspNet.Razor.Test.Parser.Html
new MarkupBlock( new MarkupBlock(
new MarkupTagBlock( new MarkupTagBlock(
Factory.Markup("<a"), Factory.Markup("<a"),
new MarkupBlock(new AttributeBlockChunkGenerator(name: "href", prefix: new LocationTagged<string>(" href='", 2, 0, 2), suffix: new LocationTagged<string>("'", 12, 0, 12)), new MarkupBlock(
new AttributeBlockChunkGenerator(
name: "href", prefix: new LocationTagged<string>(" href='", 2, 0, 2), suffix: new LocationTagged<string>("'", 12, 0, 12)),
Factory.Markup(" href='").With(SpanChunkGenerator.Null), Factory.Markup(" href='").With(SpanChunkGenerator.Null),
Factory.Markup("Foo").With(new LiteralAttributeChunkGenerator(prefix: new LocationTagged<string>(string.Empty, 9, 0, 9), value: new LocationTagged<string>("Foo", 9, 0, 9))), Factory.Markup("Foo").With(
new LiteralAttributeChunkGenerator(
prefix: new LocationTagged<string>(string.Empty, 9, 0, 9), value: new LocationTagged<string>("Foo", 9, 0, 9))),
Factory.Markup("'").With(SpanChunkGenerator.Null)),
Factory.Markup(" />").Accepts(AcceptedCharacters.None))));
}
[Fact]
public void SimpleLiteralAttributeWithWhitespaceSurroundingEquals()
{
ParseBlockTest("<a href \f\r\n= \t\n'Foo' />",
new MarkupBlock(
new MarkupTagBlock(
Factory.Markup("<a"),
new MarkupBlock(
new AttributeBlockChunkGenerator(
name: "href",
prefix: new LocationTagged<string>(" href \f\r\n= \t\n'", 2, 0, 2),
suffix: new LocationTagged<string>("'", 19, 2, 4)),
Factory.Markup(" href \f\r\n= \t\n'").With(SpanChunkGenerator.Null),
Factory.Markup("Foo").With(
new LiteralAttributeChunkGenerator(
prefix: new LocationTagged<string>(string.Empty, 16, 2, 1), value: new LocationTagged<string>("Foo", 16, 2, 1))),
Factory.Markup("'").With(SpanChunkGenerator.Null)),
Factory.Markup(" />").Accepts(AcceptedCharacters.None))));
}
[Fact]
public void DynamicAttributeWithWhitespaceSurroundingEquals()
{
ParseBlockTest("<a href \n= \r\n'@Foo' />",
new MarkupBlock(
new MarkupTagBlock(
Factory.Markup("<a"),
new MarkupBlock(
new AttributeBlockChunkGenerator(
name: "href",
prefix: new LocationTagged<string>(" href \n= \r\n'", 2, 0, 2),
suffix: new LocationTagged<string>("'", 18, 2, 5)),
Factory.Markup(" href \n= \r\n'").With(SpanChunkGenerator.Null),
new MarkupBlock(new DynamicAttributeBlockChunkGenerator(new LocationTagged<string>(string.Empty, 14, 2, 1), 14, 2, 1),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("Foo")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace))),
Factory.Markup("'").With(SpanChunkGenerator.Null)), Factory.Markup("'").With(SpanChunkGenerator.Null)),
Factory.Markup(" />").Accepts(AcceptedCharacters.None)))); Factory.Markup(" />").Accepts(AcceptedCharacters.None))));
} }
@ -63,20 +110,20 @@ namespace Microsoft.AspNet.Razor.Test.Parser.Html
[Fact] [Fact]
public void NewLinePrecedingAttribute() public void NewLinePrecedingAttribute()
{ {
ParseBlockTest($"<a{Environment.NewLine}href='Foo' />", ParseBlockTest("<a\r\nhref='Foo' />",
new MarkupBlock( new MarkupBlock(
new MarkupTagBlock( new MarkupTagBlock(
Factory.Markup("<a"), Factory.Markup("<a"),
new MarkupBlock( new MarkupBlock(
new AttributeBlockChunkGenerator( new AttributeBlockChunkGenerator(
name: "href", name: "href",
prefix: new LocationTagged<string>(Environment.NewLine + "href='", 2, 0, 2), prefix: new LocationTagged<string>("\r\nhref='", 2, 0, 2),
suffix: new LocationTagged<string>("'", 11 + Environment.NewLine.Length, 1, 9)), suffix: new LocationTagged<string>("'", 13, 1, 9)),
Factory.Markup(Environment.NewLine + "href='").With(SpanChunkGenerator.Null), Factory.Markup("\r\nhref='").With(SpanChunkGenerator.Null),
Factory.Markup("Foo").With( Factory.Markup("Foo").With(
new LiteralAttributeChunkGenerator( new LiteralAttributeChunkGenerator(
prefix: new LocationTagged<string>(string.Empty, 8 + Environment.NewLine.Length, 1, 6), prefix: new LocationTagged<string>(string.Empty, 10, 1, 6),
value: new LocationTagged<string>("Foo", 8 + Environment.NewLine.Length, 1, 6))), value: new LocationTagged<string>("Foo", 10, 1, 6))),
Factory.Markup("'").With(SpanChunkGenerator.Null)), Factory.Markup("'").With(SpanChunkGenerator.Null)),
Factory.Markup(" />").Accepts(AcceptedCharacters.None)))); Factory.Markup(" />").Accepts(AcceptedCharacters.None))));
} }
@ -84,30 +131,30 @@ namespace Microsoft.AspNet.Razor.Test.Parser.Html
[Fact] [Fact]
public void NewLineBetweenAttributes() public void NewLineBetweenAttributes()
{ {
ParseBlockTest($"<a{Environment.NewLine}href='Foo'{Environment.NewLine}abcd='Bar' />", ParseBlockTest("<a\nhref='Foo'\r\nabcd='Bar' />",
new MarkupBlock( new MarkupBlock(
new MarkupTagBlock( new MarkupTagBlock(
Factory.Markup("<a"), Factory.Markup("<a"),
new MarkupBlock(new AttributeBlockChunkGenerator( new MarkupBlock(new AttributeBlockChunkGenerator(
name: "href", name: "href",
prefix: new LocationTagged<string>(Environment.NewLine + "href='", 2, 0, 2), prefix: new LocationTagged<string>("\nhref='", 2, 0, 2),
suffix: new LocationTagged<string>("'", 11 + Environment.NewLine.Length, 1, 9)), suffix: new LocationTagged<string>("'", 12, 1, 9)),
Factory.Markup(Environment.NewLine + "href='").With(SpanChunkGenerator.Null), Factory.Markup("\nhref='").With(SpanChunkGenerator.Null),
Factory.Markup("Foo").With( Factory.Markup("Foo").With(
new LiteralAttributeChunkGenerator( new LiteralAttributeChunkGenerator(
prefix: new LocationTagged<string>(string.Empty, 8 + Environment.NewLine.Length, 1, 6), prefix: new LocationTagged<string>(string.Empty, 9, 1, 6),
value: new LocationTagged<string>("Foo", 8 + Environment.NewLine.Length, 1, 6))), value: new LocationTagged<string>("Foo", 9, 1, 6))),
Factory.Markup("'").With(SpanChunkGenerator.Null)), Factory.Markup("'").With(SpanChunkGenerator.Null)),
new MarkupBlock( new MarkupBlock(
new AttributeBlockChunkGenerator( new AttributeBlockChunkGenerator(
name: "abcd", name: "abcd",
prefix: new LocationTagged<string>(Environment.NewLine + "abcd='", 12 + Environment.NewLine.Length, 1, 10), prefix: new LocationTagged<string>("\r\nabcd='", 13, 1, 10),
suffix: new LocationTagged<string>("'", 21 + Environment.NewLine.Length * 2, 2, 9)), suffix: new LocationTagged<string>("'", 24, 2, 9)),
Factory.Markup(Environment.NewLine + "abcd='").With(SpanChunkGenerator.Null), Factory.Markup("\r\nabcd='").With(SpanChunkGenerator.Null),
Factory.Markup("Bar").With( Factory.Markup("Bar").With(
new LiteralAttributeChunkGenerator( new LiteralAttributeChunkGenerator(
prefix: new LocationTagged<string>(string.Empty, 18 + Environment.NewLine.Length * 2, 2, 6), prefix: new LocationTagged<string>(string.Empty, 21, 2, 6),
value: new LocationTagged<string>("Bar", 18 + Environment.NewLine.Length * 2, 2, 6))), value: new LocationTagged<string>("Bar", 21, 2, 6))),
Factory.Markup("'").With(SpanChunkGenerator.Null)), Factory.Markup("'").With(SpanChunkGenerator.Null)),
Factory.Markup(" />").Accepts(AcceptedCharacters.None)))); Factory.Markup(" />").Accepts(AcceptedCharacters.None))));
} }
@ -115,20 +162,20 @@ namespace Microsoft.AspNet.Razor.Test.Parser.Html
[Fact] [Fact]
public void WhitespaceAndNewLinePrecedingAttribute() public void WhitespaceAndNewLinePrecedingAttribute()
{ {
ParseBlockTest($"<a {Environment.NewLine}href='Foo' />", ParseBlockTest("<a \t\r\nhref='Foo' />",
new MarkupBlock( new MarkupBlock(
new MarkupTagBlock( new MarkupTagBlock(
Factory.Markup("<a"), Factory.Markup("<a"),
new MarkupBlock( new MarkupBlock(
new AttributeBlockChunkGenerator( new AttributeBlockChunkGenerator(
name: "href", name: "href",
prefix: new LocationTagged<string>(" " + Environment.NewLine + "href='", 2, 0, 2), prefix: new LocationTagged<string>(" \t\r\nhref='", 2, 0, 2),
suffix: new LocationTagged<string>("'", 12 + Environment.NewLine.Length, 1, 9)), suffix: new LocationTagged<string>("'", 15, 1, 9)),
Factory.Markup(" " + Environment.NewLine + "href='").With(SpanChunkGenerator.Null), Factory.Markup(" \t\r\nhref='").With(SpanChunkGenerator.Null),
Factory.Markup("Foo").With( Factory.Markup("Foo").With(
new LiteralAttributeChunkGenerator( new LiteralAttributeChunkGenerator(
prefix: new LocationTagged<string>(string.Empty, 9 + Environment.NewLine.Length, 1, 6), prefix: new LocationTagged<string>(string.Empty, 12, 1, 6),
value: new LocationTagged<string>("Foo", 9 + Environment.NewLine.Length, 1, 6))), value: new LocationTagged<string>("Foo", 12, 1, 6))),
Factory.Markup("'").With(SpanChunkGenerator.Null)), Factory.Markup("'").With(SpanChunkGenerator.Null)),
Factory.Markup(" />").Accepts(AcceptedCharacters.None)))); Factory.Markup(" />").Accepts(AcceptedCharacters.None))));
} }
@ -373,6 +420,26 @@ namespace Microsoft.AspNet.Razor.Test.Parser.Html
Factory.Markup("</span>").Accepts(AcceptedCharacters.None)))); Factory.Markup("</span>").Accepts(AcceptedCharacters.None))));
} }
[Fact]
public void ConditionalAttributesWithWeirdSpacingAreDisabledForDataAttributesInBlock()
{
ParseBlockTest("<span data-foo = '@foo'></span>",
new MarkupBlock(
new MarkupTagBlock(
Factory.Markup("<span"),
new MarkupBlock(
Factory.Markup(" data-foo = '"),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("foo")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace)),
Factory.Markup("'")),
Factory.Markup(">").Accepts(AcceptedCharacters.None)),
new MarkupTagBlock(
Factory.Markup("</span>").Accepts(AcceptedCharacters.None))));
}
[Fact] [Fact]
public void ConditionalAttributesAreDisabledForDataAttributesInDocument() public void ConditionalAttributesAreDisabledForDataAttributesInDocument()
{ {
@ -392,5 +459,24 @@ namespace Microsoft.AspNet.Razor.Test.Parser.Html
new MarkupTagBlock( new MarkupTagBlock(
Factory.Markup("</span>")))); Factory.Markup("</span>"))));
} }
[Fact]
public void ConditionalAttributesWithWeirdSpacingAreDisabledForDataAttributesInDocument()
{
ParseDocumentTest("<span data-foo=@foo ></span>",
new MarkupBlock(
new MarkupTagBlock(
Factory.Markup("<span"),
new MarkupBlock(
Factory.Markup(" data-foo="),
new ExpressionBlock(
Factory.CodeTransition(),
Factory.Code("foo")
.AsImplicitExpression(CSharpCodeParser.DefaultKeywords)
.Accepts(AcceptedCharacters.NonWhiteSpace))),
Factory.Markup(" >")),
new MarkupTagBlock(
Factory.Markup("</span>"))));
}
} }
} }

View File

@ -104,9 +104,11 @@ namespace Microsoft.AspNet.Razor.Test.Parser.Html
BlockFactory.MarkupTagBlock("<div >"), BlockFactory.MarkupTagBlock("<div >"),
new MarkupTagBlock( new MarkupTagBlock(
Factory.Markup("<p"), Factory.Markup("<p"),
new MarkupBlock( new MarkupBlock(new AttributeBlockChunkGenerator(name: "class", prefix: new LocationTagged<string>(" class = '", 8, 0, 8), suffix: new LocationTagged<string>("'", 21, 0, 21)),
Factory.Markup(" class")), Factory.Markup(" class = '").With(SpanChunkGenerator.Null),
Factory.Markup(" = 'bar'>")), Factory.Markup("bar").With(new LiteralAttributeChunkGenerator(prefix: new LocationTagged<string>(string.Empty, 18, 0, 18), value: new LocationTagged<string>("bar", 18, 0, 18))),
Factory.Markup("'").With(SpanChunkGenerator.Null)),
Factory.Markup(">")),
Factory.Markup(" Foo "), Factory.Markup(" Foo "),
BlockFactory.MarkupTagBlock("</p>"), BlockFactory.MarkupTagBlock("</p>"),
BlockFactory.MarkupTagBlock("</div >"))); BlockFactory.MarkupTagBlock("</div >")));

View File

@ -1761,7 +1761,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers
new KeyValuePair<string, SyntaxTreeNode>("class1", new MarkupBlock()), new KeyValuePair<string, SyntaxTreeNode>("class1", new MarkupBlock()),
new KeyValuePair<string, SyntaxTreeNode>( new KeyValuePair<string, SyntaxTreeNode>(
"class2", "class2",
factory.Markup("").With(SpanChunkGenerator.Null)), factory.Markup(string.Empty).With(SpanChunkGenerator.Null)),
new KeyValuePair<string, SyntaxTreeNode>("class3", new MarkupBlock()), new KeyValuePair<string, SyntaxTreeNode>("class3", new MarkupBlock()),
})) }))
}, },
@ -1776,7 +1776,7 @@ namespace Microsoft.AspNet.Razor.TagHelpers
new KeyValuePair<string, SyntaxTreeNode>("class2", new MarkupBlock()), new KeyValuePair<string, SyntaxTreeNode>("class2", new MarkupBlock()),
new KeyValuePair<string, SyntaxTreeNode>( new KeyValuePair<string, SyntaxTreeNode>(
"class3", "class3",
factory.Markup("").With(SpanChunkGenerator.Null)), factory.Markup(string.Empty).With(SpanChunkGenerator.Null)),
})) }))
}, },
}; };

View File

@ -0,0 +1,121 @@
#pragma checksum "TagHelpersWithWeirdlySpacedAttributes.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "61ec0611b60d88357a6c3b6551513ebf6223f6ee"
namespace TestOutput
{
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using System;
using System.Threading.Tasks;
public class TagHelpersWithWeirdlySpacedAttributes
{
#line hidden
#pragma warning disable 0414
private TagHelperContent __tagHelperStringValueBuffer = null;
#pragma warning restore 0414
private TagHelperExecutionContext __tagHelperExecutionContext = null;
private TagHelperRunner __tagHelperRunner = null;
private TagHelperScopeManager __tagHelperScopeManager = new TagHelperScopeManager();
private PTagHelper __PTagHelper = null;
private InputTagHelper __InputTagHelper = null;
private InputTagHelper2 __InputTagHelper2 = null;
#line hidden
public TagHelpersWithWeirdlySpacedAttributes()
{
}
#pragma warning disable 1998
public override async Task ExecuteAsync()
{
__tagHelperRunner = __tagHelperRunner ?? new TagHelperRunner();
Instrumentation.BeginContext(33, 2, true);
WriteLiteral("\r\n");
Instrumentation.EndContext();
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("p", TagMode.StartTagAndEndTag, "test", async() => {
Instrumentation.BeginContext(105, 11, true);
WriteLiteral("Body of Tag");
Instrumentation.EndContext();
}
, StartTagHelperWritingScope, EndTagHelperWritingScope);
__PTagHelper = CreateTagHelper<PTagHelper>();
__tagHelperExecutionContext.Add(__PTagHelper);
__tagHelperExecutionContext.AddHtmlAttribute("class", Html.Raw("Hello World"));
#line 6 "TagHelpersWithWeirdlySpacedAttributes.cshtml"
__PTagHelper.Age = 1337;
#line default
#line hidden
__tagHelperExecutionContext.AddTagHelperAttribute("age", __PTagHelper.Age);
StartTagHelperWritingScope();
#line 7 "TagHelpersWithWeirdlySpacedAttributes.cshtml"
Write(true);
#line default
#line hidden
__tagHelperStringValueBuffer = EndTagHelperWritingScope();
__tagHelperExecutionContext.AddHtmlAttribute("data-content", Html.Raw(__tagHelperStringValueBuffer.ToString()));
__tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
Instrumentation.BeginContext(35, 85, false);
await WriteTagHelperAsync(__tagHelperExecutionContext);
Instrumentation.EndContext();
__tagHelperExecutionContext = __tagHelperScopeManager.End();
Instrumentation.BeginContext(120, 4, true);
WriteLiteral("\r\n\r\n");
Instrumentation.EndContext();
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("input", TagMode.SelfClosing, "test", async() => {
}
, StartTagHelperWritingScope, EndTagHelperWritingScope);
__InputTagHelper = CreateTagHelper<InputTagHelper>();
__tagHelperExecutionContext.Add(__InputTagHelper);
__InputTagHelper2 = CreateTagHelper<InputTagHelper2>();
__tagHelperExecutionContext.Add(__InputTagHelper2);
__InputTagHelper.Type = "text";
__tagHelperExecutionContext.AddTagHelperAttribute("type", __InputTagHelper.Type);
__InputTagHelper2.Type = __InputTagHelper.Type;
__tagHelperExecutionContext.AddHtmlAttribute("data-content", Html.Raw("hello"));
__tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
Instrumentation.BeginContext(124, 47, false);
await WriteTagHelperAsync(__tagHelperExecutionContext);
Instrumentation.EndContext();
__tagHelperExecutionContext = __tagHelperScopeManager.End();
Instrumentation.BeginContext(171, 4, true);
WriteLiteral("\r\n\r\n");
Instrumentation.EndContext();
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("p", TagMode.StartTagAndEndTag, "test", async() => {
}
, StartTagHelperWritingScope, EndTagHelperWritingScope);
__PTagHelper = CreateTagHelper<PTagHelper>();
__tagHelperExecutionContext.Add(__PTagHelper);
#line 11 "TagHelpersWithWeirdlySpacedAttributes.cshtml"
__PTagHelper.Age = 1234;
#line default
#line hidden
__tagHelperExecutionContext.AddTagHelperAttribute("age", __PTagHelper.Age);
__tagHelperExecutionContext.AddHtmlAttribute("data-content", Html.Raw("hello2"));
__tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
Instrumentation.BeginContext(175, 46, false);
await WriteTagHelperAsync(__tagHelperExecutionContext);
Instrumentation.EndContext();
__tagHelperExecutionContext = __tagHelperScopeManager.End();
Instrumentation.BeginContext(221, 4, true);
WriteLiteral("\r\n\r\n");
Instrumentation.EndContext();
__tagHelperExecutionContext = __tagHelperScopeManager.Begin("input", TagMode.SelfClosing, "test", async() => {
}
, StartTagHelperWritingScope, EndTagHelperWritingScope);
__InputTagHelper = CreateTagHelper<InputTagHelper>();
__tagHelperExecutionContext.Add(__InputTagHelper);
__InputTagHelper2 = CreateTagHelper<InputTagHelper2>();
__tagHelperExecutionContext.Add(__InputTagHelper2);
__InputTagHelper.Type = "password";
__tagHelperExecutionContext.AddTagHelperAttribute("type", __InputTagHelper.Type);
__InputTagHelper2.Type = __InputTagHelper.Type;
__tagHelperExecutionContext.AddHtmlAttribute("data-content", Html.Raw("blah"));
__tagHelperExecutionContext.Output = await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
Instrumentation.BeginContext(225, 51, false);
await WriteTagHelperAsync(__tagHelperExecutionContext);
Instrumentation.EndContext();
__tagHelperExecutionContext = __tagHelperScopeManager.End();
}
#pragma warning restore 1998
}
}

View File

@ -0,0 +1,15 @@
@addTagHelper "something, nice"
<p
class
=
"Hello World" age =1337
data-content= "@true">Body of Tag</p>
<input type = 'text' data-content= "hello" />
<p age= "1234" data-content
= 'hello2'></p>
<input type
=password data-content =blah/>