Modify parser to group html begin/end elements.

- Added a "Tag" block type.
- Wrapped all begin/end elements in a "Tag" Markup block.

#75
This commit is contained in:
N. Taylor Mullen 2014-08-13 17:34:25 -07:00
parent ff944e5948
commit 6114d5d269
8 changed files with 251 additions and 95 deletions

View File

@ -38,7 +38,7 @@ namespace Microsoft.AspNet.Razor.Parser
return "<"; return "<";
case HtmlSymbolType.Bang: case HtmlSymbolType.Bang:
return "!"; return "!";
case HtmlSymbolType.Solidus: case HtmlSymbolType.ForwardSlash:
return "/"; return "/";
case HtmlSymbolType.QuestionMark: case HtmlSymbolType.QuestionMark:
return "?"; return "?";

View File

@ -125,25 +125,58 @@ namespace Microsoft.AspNet.Razor.Parser
do do
{ {
SkipToAndParseCode(HtmlSymbolType.OpenAngle); SkipToAndParseCode(HtmlSymbolType.OpenAngle);
if (EndOfFile)
// Output everything prior to the OpenAngle into a markup span
Output(SpanKind.Markup);
// Do not want to start a new tag block if we're at the end of the file.
IDisposable tagBlockWrapper = null;
try
{ {
EndTagBlock(tags, complete: true); if (!EndOfFile && !AtSpecialTag)
}
else
{
_bufferedOpenAngle = null;
_lastTagStart = CurrentLocation;
Assert(HtmlSymbolType.OpenAngle);
_bufferedOpenAngle = CurrentSymbol;
SourceLocation tagStart = CurrentLocation;
if (!NextToken())
{ {
Accept(_bufferedOpenAngle); // Start a Block tag. This is used to wrap things like <p> or <a class="btn"> etc.
EndTagBlock(tags, complete: false); tagBlockWrapper = Context.StartBlock(BlockType.Tag);
}
if (EndOfFile)
{
EndTagBlock(tags, complete: true);
} }
else else
{ {
complete = AfterTagStart(tagStart, tags); _bufferedOpenAngle = null;
_lastTagStart = CurrentLocation;
Assert(HtmlSymbolType.OpenAngle);
_bufferedOpenAngle = CurrentSymbol;
SourceLocation tagStart = CurrentLocation;
if (!NextToken())
{
Accept(_bufferedOpenAngle);
EndTagBlock(tags, complete: false);
}
else
{
complete = AfterTagStart(tagStart, tags, tagBlockWrapper);
}
}
if (complete)
{
// Completed tags have no accepted characters inside of blocks.
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
}
// Output the contents of the tag into its own markup span.
Output(SpanKind.Markup);
}
finally
{
// Will be null if we were at end of file or special tag when initially created.
if (tagBlockWrapper != null)
{
// End tag block
tagBlockWrapper.Dispose();
} }
} }
} }
@ -152,15 +185,17 @@ namespace Microsoft.AspNet.Razor.Parser
EndTagBlock(tags, complete); EndTagBlock(tags, complete);
} }
private bool AfterTagStart(SourceLocation tagStart, Stack<Tuple<HtmlSymbol, SourceLocation>> tags) private bool AfterTagStart(SourceLocation tagStart,
Stack<Tuple<HtmlSymbol, SourceLocation>> tags,
IDisposable tagBlockWrapper)
{ {
if (!EndOfFile) if (!EndOfFile)
{ {
switch (CurrentSymbol.Type) switch (CurrentSymbol.Type)
{ {
case HtmlSymbolType.Solidus: case HtmlSymbolType.ForwardSlash:
// End Tag // End Tag
return EndTag(tagStart, tags); return EndTag(tagStart, tags, tagBlockWrapper);
case HtmlSymbolType.Bang: case HtmlSymbolType.Bang:
// Comment // Comment
Accept(_bufferedOpenAngle); Accept(_bufferedOpenAngle);
@ -171,7 +206,7 @@ namespace Microsoft.AspNet.Razor.Parser
return XmlPI(); return XmlPI();
default: default:
// Start Tag // Start Tag
return StartTag(tags); return StartTag(tags, tagBlockWrapper);
} }
} }
if (tags.Count == 0) if (tags.Count == 0)
@ -234,10 +269,12 @@ namespace Microsoft.AspNet.Razor.Parser
return false; return false;
} }
private bool EndTag(SourceLocation tagStart, Stack<Tuple<HtmlSymbol, SourceLocation>> tags) private bool EndTag(SourceLocation tagStart,
Stack<Tuple<HtmlSymbol, SourceLocation>> tags,
IDisposable tagBlockWrapper)
{ {
// Accept "/" and move next // Accept "/" and move next
Assert(HtmlSymbolType.Solidus); Assert(HtmlSymbolType.ForwardSlash);
HtmlSymbol solidus = CurrentSymbol; HtmlSymbol solidus = CurrentSymbol;
if (!NextToken()) if (!NextToken())
{ {
@ -258,8 +295,7 @@ namespace Microsoft.AspNet.Razor.Parser
String.Equals(tagName, SyntaxConstants.TextTagName, StringComparison.OrdinalIgnoreCase) && String.Equals(tagName, SyntaxConstants.TextTagName, StringComparison.OrdinalIgnoreCase) &&
matched) matched)
{ {
Output(SpanKind.Markup); return EndTextTag(solidus, tagBlockWrapper);
return EndTextTag(solidus);
} }
Accept(_bufferedOpenAngle); Accept(_bufferedOpenAngle);
Accept(solidus); Accept(solidus);
@ -271,7 +307,16 @@ namespace Microsoft.AspNet.Razor.Parser
} }
} }
private bool EndTextTag(HtmlSymbol solidus) private void RecoverTextTag()
{
// We don't want to skip-to and parse because there shouldn't be anything in the body of text tags.
AcceptUntil(HtmlSymbolType.CloseAngle, HtmlSymbolType.NewLine);
// Include the close angle in the text tag block if it's there, otherwise just move on
Optional(HtmlSymbolType.CloseAngle);
}
private bool EndTextTag(HtmlSymbol solidus, IDisposable tagBlockWrapper)
{ {
SourceLocation start = _bufferedOpenAngle.Start; SourceLocation start = _bufferedOpenAngle.Start;
@ -286,6 +331,9 @@ namespace Microsoft.AspNet.Razor.Parser
if (!seenCloseAngle) if (!seenCloseAngle)
{ {
Context.OnError(start, RazorResources.ParseError_TextTagCannotContainAttributes); Context.OnError(start, RazorResources.ParseError_TextTagCannotContainAttributes);
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.Any;
RecoverTextTag();
} }
else else
{ {
@ -293,14 +341,27 @@ namespace Microsoft.AspNet.Razor.Parser
} }
Span.CodeGenerator = SpanCodeGenerator.Null; Span.CodeGenerator = SpanCodeGenerator.Null;
Output(SpanKind.Transition);
CompleteTagBlockWithSpan(tagBlockWrapper, Span.EditHandler.AcceptedCharacters, SpanKind.Transition);
return seenCloseAngle; return seenCloseAngle;
} }
// Special tags include <! and <? tags
private bool AtSpecialTag
{
get
{
return (At(HtmlSymbolType.OpenAngle) &&
(NextIs(HtmlSymbolType.Bang) ||
NextIs(HtmlSymbolType.QuestionMark)));
}
}
private bool IsTagRecoveryStopPoint(HtmlSymbol sym) private bool IsTagRecoveryStopPoint(HtmlSymbol sym)
{ {
return sym.Type == HtmlSymbolType.CloseAngle || return sym.Type == HtmlSymbolType.CloseAngle ||
sym.Type == HtmlSymbolType.Solidus || sym.Type == HtmlSymbolType.ForwardSlash ||
sym.Type == HtmlSymbolType.OpenAngle || sym.Type == HtmlSymbolType.OpenAngle ||
sym.Type == HtmlSymbolType.SingleQuote || sym.Type == HtmlSymbolType.SingleQuote ||
sym.Type == HtmlSymbolType.DoubleQuote; sym.Type == HtmlSymbolType.DoubleQuote;
@ -325,7 +386,7 @@ namespace Microsoft.AspNet.Razor.Parser
private bool IsEndOfTag() private bool IsEndOfTag()
{ {
if (At(HtmlSymbolType.Solidus)) if (At(HtmlSymbolType.ForwardSlash))
{ {
if (NextIs(HtmlSymbolType.CloseAngle)) if (NextIs(HtmlSymbolType.CloseAngle))
{ {
@ -364,7 +425,7 @@ namespace Microsoft.AspNet.Razor.Parser
sym.Type != HtmlSymbolType.Equals && sym.Type != HtmlSymbolType.Equals &&
sym.Type != HtmlSymbolType.CloseAngle && sym.Type != HtmlSymbolType.CloseAngle &&
sym.Type != HtmlSymbolType.OpenAngle && sym.Type != HtmlSymbolType.OpenAngle &&
(sym.Type != HtmlSymbolType.Solidus || !NextIs(HtmlSymbolType.CloseAngle))); (sym.Type != HtmlSymbolType.ForwardSlash || !NextIs(HtmlSymbolType.CloseAngle)));
} }
else else
{ {
@ -470,12 +531,16 @@ namespace Microsoft.AspNet.Razor.Parser
// Dynamic value, start a new block and set the code generator // Dynamic value, start a new block and set the code generator
using (Context.StartBlock(BlockType.Markup)) using (Context.StartBlock(BlockType.Markup))
{ {
Context.CurrentBlock.CodeGenerator = new DynamicAttributeBlockCodeGenerator(prefix.GetContent(prefixStart), valueStart); Context.CurrentBlock.CodeGenerator =
new DynamicAttributeBlockCodeGenerator(prefix.GetContent(prefixStart), valueStart);
OtherParserBlock(); OtherParserBlock();
} }
} }
else if (At(HtmlSymbolType.Text) && CurrentSymbol.Content.Length > 0 && CurrentSymbol.Content[0] == '~' && NextIs(HtmlSymbolType.Solidus)) else if (At(HtmlSymbolType.Text) &&
CurrentSymbol.Content.Length > 0 &&
CurrentSymbol.Content[0] == '~' &&
NextIs(HtmlSymbolType.ForwardSlash))
{ {
// Virtual Path value // Virtual Path value
SourceLocation valueStart = CurrentLocation; SourceLocation valueStart = CurrentLocation;
@ -489,11 +554,12 @@ namespace Microsoft.AspNet.Razor.Parser
// Literal value // Literal value
// 'quote' should be "Unknown" if not quoted and symbols coming from the tokenizer should never have "Unknown" type. // 'quote' should be "Unknown" if not quoted and symbols coming from the tokenizer should never have "Unknown" type.
var value = ReadWhile(sym => var value = ReadWhile(sym =>
// These three conditions find separators which break the attribute value into portions // These three conditions find separators which break the attribute value into portions
sym.Type != HtmlSymbolType.WhiteSpace && sym.Type != HtmlSymbolType.WhiteSpace &&
sym.Type != HtmlSymbolType.NewLine && sym.Type != HtmlSymbolType.NewLine &&
sym.Type != HtmlSymbolType.Transition && sym.Type != HtmlSymbolType.Transition &&
// This condition checks for the end of the attribute value (it repeats some of the checks above but for now that's ok) // This condition checks for the end of the attribute value (it repeats some of the checks above
// but for now that's ok)
!IsEndOfAttributeValue(quote, sym)); !IsEndOfAttributeValue(quote, sym));
Accept(value); Accept(value);
Span.CodeGenerator = new LiteralAttributeCodeGenerator(prefix.GetContent(prefixStart), value.GetContent(prefixStart)); Span.CodeGenerator = new LiteralAttributeCodeGenerator(prefix.GetContent(prefixStart), value.GetContent(prefixStart));
@ -518,7 +584,7 @@ namespace Microsoft.AspNet.Razor.Parser
sym.Type == HtmlSymbolType.SingleQuote || sym.Type == HtmlSymbolType.SingleQuote ||
sym.Type == HtmlSymbolType.OpenAngle || sym.Type == HtmlSymbolType.OpenAngle ||
sym.Type == HtmlSymbolType.Equals || sym.Type == HtmlSymbolType.Equals ||
(sym.Type == HtmlSymbolType.Solidus && NextIs(HtmlSymbolType.CloseAngle)) || (sym.Type == HtmlSymbolType.ForwardSlash && NextIs(HtmlSymbolType.CloseAngle)) ||
sym.Type == HtmlSymbolType.CloseAngle || sym.Type == HtmlSymbolType.CloseAngle ||
sym.Type == HtmlSymbolType.WhiteSpace || sym.Type == HtmlSymbolType.WhiteSpace ||
sym.Type == HtmlSymbolType.NewLine; sym.Type == HtmlSymbolType.NewLine;
@ -553,7 +619,7 @@ namespace Microsoft.AspNet.Razor.Parser
break; break;
case HtmlSymbolType.OpenAngle: case HtmlSymbolType.OpenAngle:
// Another "<" means this tag is invalid. // Another "<" means this tag is invalid.
case HtmlSymbolType.Solidus: case HtmlSymbolType.ForwardSlash:
// Empty tag // Empty tag
case HtmlSymbolType.CloseAngle: case HtmlSymbolType.CloseAngle:
// End of tag // End of tag
@ -583,7 +649,7 @@ namespace Microsoft.AspNet.Razor.Parser
} }
} }
private bool StartTag(Stack<Tuple<HtmlSymbol, SourceLocation>> tags) private bool StartTag(Stack<Tuple<HtmlSymbol, SourceLocation>> tags, IDisposable tagBlockWrapper)
{ {
// If we're at text, it's the name, otherwise the name is "" // If we're at text, it's the name, otherwise the name is ""
HtmlSymbol tagName; HtmlSymbol tagName;
@ -610,11 +676,11 @@ namespace Microsoft.AspNet.Razor.Parser
int bookmark = CurrentLocation.AbsoluteIndex; int bookmark = CurrentLocation.AbsoluteIndex;
IEnumerable<HtmlSymbol> tokens = ReadWhile(IsSpacingToken(includeNewLines: true)); IEnumerable<HtmlSymbol> tokens = ReadWhile(IsSpacingToken(includeNewLines: true));
bool empty = At(HtmlSymbolType.Solidus); bool empty = At(HtmlSymbolType.ForwardSlash);
if (empty) if (empty)
{ {
Accept(tokens); Accept(tokens);
Assert(HtmlSymbolType.Solidus); Assert(HtmlSymbolType.ForwardSlash);
AcceptAndMoveNext(); AcceptAndMoveNext();
bookmark = CurrentLocation.AbsoluteIndex; bookmark = CurrentLocation.AbsoluteIndex;
tokens = ReadWhile(IsSpacingToken(includeNewLines: true)); tokens = ReadWhile(IsSpacingToken(includeNewLines: true));
@ -625,6 +691,8 @@ namespace Microsoft.AspNet.Razor.Parser
Context.Source.Position = bookmark; Context.Source.Position = bookmark;
NextToken(); NextToken();
Context.OnError(tag.Item2, RazorResources.ParseError_TextTagCannotContainAttributes); Context.OnError(tag.Item2, RazorResources.ParseError_TextTagCannotContainAttributes);
RecoverTextTag();
} }
else else
{ {
@ -636,15 +704,19 @@ namespace Microsoft.AspNet.Razor.Parser
{ {
tags.Push(tag); tags.Push(tag);
} }
Output(SpanKind.Transition);
CompleteTagBlockWithSpan(tagBlockWrapper, Span.EditHandler.AcceptedCharacters, SpanKind.Transition);
return true; return true;
} }
Accept(_bufferedOpenAngle); Accept(_bufferedOpenAngle);
Optional(HtmlSymbolType.Text); Optional(HtmlSymbolType.Text);
return RestOfTag(tag, tags); return RestOfTag(tag, tags, tagBlockWrapper);
} }
private bool RestOfTag(Tuple<HtmlSymbol, SourceLocation> tag, Stack<Tuple<HtmlSymbol, SourceLocation>> tags) private bool RestOfTag(Tuple<HtmlSymbol, SourceLocation> tag,
Stack<Tuple<HtmlSymbol, SourceLocation>> tags,
IDisposable tagBlockWrapper)
{ {
TagContent(); TagContent();
@ -655,7 +727,7 @@ namespace Microsoft.AspNet.Razor.Parser
return false; return false;
} }
bool isEmpty = At(HtmlSymbolType.Solidus); bool isEmpty = At(HtmlSymbolType.ForwardSlash);
// Found a solidus, so don't accept it but DON'T push the tag to the stack // Found a solidus, so don't accept it but DON'T push the tag to the stack
if (isEmpty) if (isEmpty)
{ {
@ -676,44 +748,63 @@ namespace Microsoft.AspNet.Razor.Parser
string tagName = tag.Item1.Content.Trim(); string tagName = tag.Item1.Content.Trim();
if (VoidElements.Contains(tagName)) if (VoidElements.Contains(tagName))
{ {
CompleteTagBlockWithSpan(tagBlockWrapper, AcceptedCharacters.None, SpanKind.Markup);
// Technically, void elements like "meta" are not allowed to have end tags. Just in case they do, // Technically, void elements like "meta" are not allowed to have end tags. Just in case they do,
// we need to look ahead at the next set of tokens. If we see "<", "/", tag name, accept it and the ">" following it // we need to look ahead at the next set of tokens. If we see "<", "/", tag name, accept it and the ">" following it
// Place a bookmark // Place a bookmark
int bookmark = CurrentLocation.AbsoluteIndex; int bookmark = CurrentLocation.AbsoluteIndex;
// Skip whitespace // Skip whitespace
IEnumerable<HtmlSymbol> ws = ReadWhile(IsSpacingToken(includeNewLines: true)); IEnumerable<HtmlSymbol> whiteSpace = ReadWhile(IsSpacingToken(includeNewLines: true));
// Open Angle // Open Angle
if (At(HtmlSymbolType.OpenAngle) && NextIs(HtmlSymbolType.Solidus)) if (At(HtmlSymbolType.OpenAngle) && NextIs(HtmlSymbolType.ForwardSlash))
{ {
HtmlSymbol openAngle = CurrentSymbol; HtmlSymbol openAngle = CurrentSymbol;
NextToken(); NextToken();
Assert(HtmlSymbolType.Solidus); Assert(HtmlSymbolType.ForwardSlash);
HtmlSymbol solidus = CurrentSymbol; HtmlSymbol solidus = CurrentSymbol;
NextToken(); NextToken();
if (At(HtmlSymbolType.Text) && String.Equals(CurrentSymbol.Content, tagName, StringComparison.OrdinalIgnoreCase)) if (At(HtmlSymbolType.Text) && string.Equals(CurrentSymbol.Content, tagName, StringComparison.OrdinalIgnoreCase))
{ {
// Accept up to here // Accept up to here
Accept(ws); Accept(whiteSpace);
Accept(openAngle); Output(SpanKind.Markup); // Output the whitespace
Accept(solidus);
AcceptAndMoveNext();
// Accept to '>', '<' or EOF using (Context.StartBlock(BlockType.Tag))
AcceptUntil(HtmlSymbolType.CloseAngle, HtmlSymbolType.OpenAngle); {
// Accept the '>' if we saw it. And if we do see it, we're complete Accept(openAngle);
return Optional(HtmlSymbolType.CloseAngle); Accept(solidus);
} // At(HtmlSymbolType.Text) && String.Equals(CurrentSymbol.Content, tagName, StringComparison.OrdinalIgnoreCase) AcceptAndMoveNext();
} // At(HtmlSymbolType.OpenAngle) && NextIs(HtmlSymbolType.Solidus)
// Accept to '>', '<' or EOF
AcceptUntil(HtmlSymbolType.CloseAngle, HtmlSymbolType.OpenAngle);
// Accept the '>' if we saw it. And if we do see it, we're complete
var complete = Optional(HtmlSymbolType.CloseAngle);
if (complete)
{
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
}
// Output the closing void element
Output(SpanKind.Markup);
return complete;
}
}
}
// Go back to the bookmark and just finish this tag at the close angle // Go back to the bookmark and just finish this tag at the close angle
Context.Source.Position = bookmark; Context.Source.Position = bookmark;
NextToken(); NextToken();
} }
else if (String.Equals(tagName, "script", StringComparison.OrdinalIgnoreCase)) else if (string.Equals(tagName, "script", StringComparison.OrdinalIgnoreCase))
{ {
SkipToEndScriptAndParseCode(); CompleteTagBlockWithSpan(tagBlockWrapper, AcceptedCharacters.None, SpanKind.Markup);
SkipToEndScriptAndParseCode(endTagAcceptedCharacters: AcceptedCharacters.None);
} }
else else
{ {
@ -725,33 +816,74 @@ namespace Microsoft.AspNet.Razor.Parser
return seenClose; return seenClose;
} }
private void SkipToEndScriptAndParseCode() private void SkipToEndScriptAndParseCode(AcceptedCharacters endTagAcceptedCharacters = AcceptedCharacters.Any)
{ {
// Special case for <script>: Skip to end of script tag and parse code // Special case for <script>: Skip to end of script tag and parse code
bool seenEndScript = false; bool seenEndScript = false;
while (!seenEndScript && !EndOfFile) while (!seenEndScript && !EndOfFile)
{ {
SkipToAndParseCode(HtmlSymbolType.OpenAngle); SkipToAndParseCode(HtmlSymbolType.OpenAngle);
SourceLocation tagStart = CurrentLocation; SourceLocation tagStart = CurrentLocation;
AcceptAndMoveNext();
AcceptWhile(HtmlSymbolType.WhiteSpace); if (NextIs(HtmlSymbolType.ForwardSlash))
if (Optional(HtmlSymbolType.Solidus))
{ {
AcceptWhile(HtmlSymbolType.WhiteSpace); var openAngle = CurrentSymbol;
if (At(HtmlSymbolType.Text) && String.Equals(CurrentSymbol.Content, "script", StringComparison.OrdinalIgnoreCase)) NextToken(); // Skip over '<', current is '/'
var solidus = CurrentSymbol;
NextToken(); // Skip over '/', current should be text
if (At(HtmlSymbolType.Text) && string.Equals(CurrentSymbol.Content, "script", StringComparison.OrdinalIgnoreCase))
{ {
// </script! seenEndScript = true;
}
// We put everything back because we just wanted to look ahead to see if the current end tag that we're parsing is
// the script tag. If so we'll generate correct code to encompass it.
PutCurrentBack(); // Put back whatever was after the solidus
PutBack(solidus); // Put back '/'
PutBack(openAngle); // Put back '<'
}
if (seenEndScript)
{
Output(SpanKind.Markup);
using (Context.StartBlock(BlockType.Tag))
{
Span.EditHandler.AcceptedCharacters = endTagAcceptedCharacters;
AcceptAndMoveNext(); // '<'
AcceptAndMoveNext(); // '/'
SkipToAndParseCode(HtmlSymbolType.CloseAngle); SkipToAndParseCode(HtmlSymbolType.CloseAngle);
if (!Optional(HtmlSymbolType.CloseAngle)) if (!Optional(HtmlSymbolType.CloseAngle))
{ {
Context.OnError(tagStart, RazorResources.FormatParseError_UnfinishedTag("script")); Context.OnError(tagStart, RazorResources.FormatParseError_UnfinishedTag("script"));
} }
seenEndScript = true; Output(SpanKind.Markup);
} }
} }
else
{
AcceptAndMoveNext(); // '<' (not the closing script tags open angle)
}
} }
} }
private void CompleteTagBlockWithSpan(IDisposable tagBlockWrapper,
AcceptedCharacters acceptedCharacters,
SpanKind spanKind)
{
Debug.Assert(tagBlockWrapper != null,
"Tag block wrapper should not be null when attempting to complete a block");
Span.EditHandler.AcceptedCharacters = acceptedCharacters;
// Write out the current span into the block before closing it.
Output(spanKind);
// Finish the tag block
tagBlockWrapper.Dispose();
}
private bool AcceptUntilAll(params HtmlSymbolType[] endSequence) private bool AcceptUntilAll(params HtmlSymbolType[] endSequence)
{ {
while (!EndOfFile) while (!EndOfFile)

View File

@ -36,37 +36,65 @@ namespace Microsoft.AspNet.Razor.Parser
/// Reads the content of a tag (if present) in the MarkupDocument (or MarkupSection) context, /// Reads the content of a tag (if present) in the MarkupDocument (or MarkupSection) context,
/// where we don't care about maintaining a stack of tags. /// where we don't care about maintaining a stack of tags.
/// </summary> /// </summary>
/// <returns>A boolean indicating if we scanned at least one tag.</returns> private void ScanTagInDocumentContext()
private bool ScanTagInDocumentContext()
{ {
if (Optional(HtmlSymbolType.OpenAngle)) if (At(HtmlSymbolType.OpenAngle))
{ {
if (At(HtmlSymbolType.Bang)) if (NextIs(HtmlSymbolType.Bang))
{ {
AcceptAndMoveNext(); // Accept '<'
BangTag(); BangTag();
return true;
} }
else if (At(HtmlSymbolType.QuestionMark)) else if (NextIs(HtmlSymbolType.QuestionMark))
{ {
AcceptAndMoveNext(); // Accept '<'
XmlPI(); XmlPI();
return true;
} }
else if (!At(HtmlSymbolType.Solidus)) else
{ {
bool scriptTag = At(HtmlSymbolType.Text) && Output(SpanKind.Markup);
String.Equals(CurrentSymbol.Content, "script", StringComparison.OrdinalIgnoreCase);
Optional(HtmlSymbolType.Text); // Start tag block
TagContent(); // Parse the tag, don't care about the content var tagBlock = Context.StartBlock(BlockType.Tag);
Optional(HtmlSymbolType.Solidus);
Optional(HtmlSymbolType.CloseAngle); AcceptAndMoveNext(); // Accept '<'
if (scriptTag)
if (!At(HtmlSymbolType.ForwardSlash))
{ {
SkipToEndScriptAndParseCode(); // Parsing a start tag
var scriptTag = At(HtmlSymbolType.Text) &&
string.Equals(CurrentSymbol.Content, "script", StringComparison.OrdinalIgnoreCase);
Optional(HtmlSymbolType.Text);
TagContent(); // Parse the tag, don't care about the content
Optional(HtmlSymbolType.ForwardSlash);
Optional(HtmlSymbolType.CloseAngle);
if (scriptTag)
{
Output(SpanKind.Markup);
tagBlock.Dispose();
SkipToEndScriptAndParseCode();
return;
}
} }
return true; else
{
// Parsing an end tag
// This section can accept things like: '</p >' or '</p>' etc.
Optional(HtmlSymbolType.ForwardSlash);
// Whitespace here is invalid (according to the spec)
Optional(HtmlSymbolType.Text);
AcceptAll(HtmlSymbolType.WhiteSpace);
Optional(HtmlSymbolType.CloseAngle);
}
Output(SpanKind.Markup);
// End tag block
tagBlock.Dispose();
} }
} }
return false;
} }
} }
} }

View File

@ -18,6 +18,7 @@ namespace Microsoft.AspNet.Razor.Parser.SyntaxTree
Template, Template,
// Special // Special
Comment Comment,
Tag
} }
} }

View File

@ -133,7 +133,7 @@ namespace Microsoft.AspNet.Razor.Tokenizer
case '!': case '!':
return EndSymbol(HtmlSymbolType.Bang); return EndSymbol(HtmlSymbolType.Bang);
case '/': case '/':
return EndSymbol(HtmlSymbolType.Solidus); return EndSymbol(HtmlSymbolType.ForwardSlash);
case '?': case '?':
return EndSymbol(HtmlSymbolType.QuestionMark); return EndSymbol(HtmlSymbolType.QuestionMark);
case '[': case '[':

View File

@ -11,7 +11,7 @@ namespace Microsoft.AspNet.Razor.Tokenizer.Symbols
NewLine, // Newline NewLine, // Newline
OpenAngle, // < OpenAngle, // <
Bang, // ! Bang, // !
Solidus, // / ForwardSlash, // /
QuestionMark, // ? QuestionMark, // ?
DoubleHyphen, // -- DoubleHyphen, // --
LeftBracket, // [ LeftBracket, // [

View File

@ -8,6 +8,7 @@ namespace Microsoft.AspNet.Razor.Utils
internal class DisposableAction : IDisposable internal class DisposableAction : IDisposable
{ {
private Action _action; private Action _action;
private bool _invoked;
public DisposableAction(Action action) public DisposableAction(Action action)
{ {
@ -20,16 +21,10 @@ namespace Microsoft.AspNet.Razor.Utils
public void Dispose() public void Dispose()
{ {
Dispose(true); if (!_invoked)
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
// If we were disposed by the finalizer it's because the user didn't use a "using" block, so don't do anything!
if (disposing)
{ {
_action(); _action();
_invoked = true;
} }
} }
} }

View File

@ -66,7 +66,7 @@ namespace Microsoft.AspNet.Razor.Test.Tokenizer
[Fact] [Fact]
public void Solidus_Is_Recognized() public void Solidus_Is_Recognized()
{ {
TestSingleToken("/", HtmlSymbolType.Solidus); TestSingleToken("/", HtmlSymbolType.ForwardSlash);
} }
[Fact] [Fact]