Skip to content
This repository was archived by the owner on Dec 19, 2018. It is now read-only.

Commit

Permalink
Modify parser to group html begin/end elements.
Browse files Browse the repository at this point in the history
- Added a "Tag" block type.
- Wrapped all begin/end elements in a "Tag" Markup block.

#75
  • Loading branch information
NTaylorMullen committed Aug 14, 2014
1 parent 8aef9ff commit d89dedc
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 60 deletions.
187 changes: 153 additions & 34 deletions src/Microsoft.AspNet.Razor/Parser/HtmlMarkupParser.Block.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,18 @@ private void TagBlock(Stack<Tuple<HtmlSymbol, SourceLocation>> tags)
do
{
SkipToAndParseCode(HtmlSymbolType.OpenAngle);

// Output everything prior to the OpenAngle into a markup span
Output(SpanKind.Markup);

IDisposable tagBlock = null;
// Do not want to start a new tag block if we're at the end of the file.
if (!EndOfFile && !AtSpecialTag)
{
// Start a Block tag. This is used to wrap things like <p> or <a class="btn"> etc.
tagBlock = Context.StartBlock(BlockType.Tag);
}

if (EndOfFile)
{
EndTagBlock(tags, complete: true);
Expand All @@ -143,24 +155,42 @@ private void TagBlock(Stack<Tuple<HtmlSymbol, SourceLocation>> tags)
}
else
{
complete = AfterTagStart(tagStart, tags);
complete = AfterTagStart(tagStart, tags, tagBlock);
}
}

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);

// Will be null if we were at end of file or special tag when initially created.
if (tagBlock != null)
{
// End tag block
tagBlock.Dispose();
}
}
while (tags.Count > 0);

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)
{
switch (CurrentSymbol.Type)
{
case HtmlSymbolType.Solidus:
// End Tag
return EndTag(tagStart, tags);
return EndTag(tagStart, tags, tagBlockWrapper);
case HtmlSymbolType.Bang:
// Comment
Accept(_bufferedOpenAngle);
Expand All @@ -171,7 +201,7 @@ private bool AfterTagStart(SourceLocation tagStart, Stack<Tuple<HtmlSymbol, Sour
return XmlPI();
default:
// Start Tag
return StartTag(tags);
return StartTag(tags, tagBlockWrapper);
}
}
if (tags.Count == 0)
Expand Down Expand Up @@ -234,7 +264,9 @@ private bool CData()
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
Assert(HtmlSymbolType.Solidus);
Expand All @@ -258,8 +290,7 @@ private bool EndTag(SourceLocation tagStart, Stack<Tuple<HtmlSymbol, SourceLocat
String.Equals(tagName, SyntaxConstants.TextTagName, StringComparison.OrdinalIgnoreCase) &&
matched)
{
Output(SpanKind.Markup);
return EndTextTag(solidus);
return EndTextTag(solidus, tagBlockWrapper);
}
Accept(_bufferedOpenAngle);
Accept(solidus);
Expand All @@ -271,7 +302,16 @@ private bool EndTag(SourceLocation tagStart, Stack<Tuple<HtmlSymbol, SourceLocat
}
}

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;

Expand All @@ -286,17 +326,32 @@ private bool EndTextTag(HtmlSymbol solidus)
if (!seenCloseAngle)
{
Context.OnError(start, RazorResources.ParseError_TextTagCannotContainAttributes);

Span.EditHandler.AcceptedCharacters = AcceptedCharacters.Any;
RecoverTextTag();
}
else
{
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
}

Span.CodeGenerator = SpanCodeGenerator.Null;
Output(SpanKind.Transition);

CompleteTagBlockWithSpan(tagBlockWrapper, Span.EditHandler.AcceptedCharacters, SpanKind.Transition);

return seenCloseAngle;
}

private bool AtSpecialTag
{
get
{
return (At(HtmlSymbolType.OpenAngle) &&
(NextIs(HtmlSymbolType.Bang) ||
NextIs(HtmlSymbolType.QuestionMark)));
}
}

private bool IsTagRecoveryStopPoint(HtmlSymbol sym)
{
return sym.Type == HtmlSymbolType.CloseAngle ||
Expand Down Expand Up @@ -489,11 +544,11 @@ private void AttributeValue(HtmlSymbolType quote)
// Literal value
// 'quote' should be "Unknown" if not quoted and symbols coming from the tokenizer should never have "Unknown" type.
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.NewLine &&
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));
Accept(value);
Span.CodeGenerator = new LiteralAttributeCodeGenerator(prefix.GetContent(prefixStart), value.GetContent(prefixStart));
Expand Down Expand Up @@ -583,7 +638,7 @@ private void ParseQuoted(HtmlSymbolType type)
}
}

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 ""
HtmlSymbol tagName;
Expand Down Expand Up @@ -625,6 +680,8 @@ private bool StartTag(Stack<Tuple<HtmlSymbol, SourceLocation>> tags)
Context.Source.Position = bookmark;
NextToken();
Context.OnError(tag.Item2, RazorResources.ParseError_TextTagCannotContainAttributes);

RecoverTextTag();
}
else
{
Expand All @@ -636,15 +693,19 @@ private bool StartTag(Stack<Tuple<HtmlSymbol, SourceLocation>> tags)
{
tags.Push(tag);
}
Output(SpanKind.Transition);

CompleteTagBlockWithSpan(tagBlockWrapper, Span.EditHandler.AcceptedCharacters, SpanKind.Transition);

return true;
}
Accept(_bufferedOpenAngle);
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();

Expand Down Expand Up @@ -676,6 +737,8 @@ private bool RestOfTag(Tuple<HtmlSymbol, SourceLocation> tag, Stack<Tuple<HtmlSy
string tagName = tag.Item1.Content.Trim();
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,
// 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
Expand All @@ -692,28 +755,47 @@ private bool RestOfTag(Tuple<HtmlSymbol, SourceLocation> tag, Stack<Tuple<HtmlSy
Assert(HtmlSymbolType.Solidus);
HtmlSymbol solidus = CurrentSymbol;
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(ws);
Accept(openAngle);
Accept(solidus);
AcceptAndMoveNext();
Output(SpanKind.Markup); // Output the whitespace

// Accept to '>', '<' or EOF
AcceptUntil(HtmlSymbolType.CloseAngle, HtmlSymbolType.OpenAngle);
// Accept the '>' if we saw it. And if we do see it, we're complete
return Optional(HtmlSymbolType.CloseAngle);
} // At(HtmlSymbolType.Text) && String.Equals(CurrentSymbol.Content, tagName, StringComparison.OrdinalIgnoreCase)
} // At(HtmlSymbolType.OpenAngle) && NextIs(HtmlSymbolType.Solidus)
bool complete;

using (Context.StartBlock(BlockType.Tag))
{
Accept(openAngle);
Accept(solidus);
AcceptAndMoveNext();

// Accept to '>', '<' or EOF
AcceptUntil(HtmlSymbolType.CloseAngle, HtmlSymbolType.OpenAngle);
// Accept the '>' if we saw it. And if we do see it, we're complete
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
Context.Source.Position = bookmark;
NextToken();
}
else if (String.Equals(tagName, "script", StringComparison.OrdinalIgnoreCase))
{
SkipToEndScriptAndParseCode();
CompleteTagBlockWithSpan(tagBlockWrapper, AcceptedCharacters.None, SpanKind.Markup);

SkipToEndScriptAndParseCode(endTagAcceptedCharacters: AcceptedCharacters.None);
}
else
{
Expand All @@ -725,33 +807,70 @@ private bool RestOfTag(Tuple<HtmlSymbol, SourceLocation> tag, Stack<Tuple<HtmlSy
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
bool seenEndScript = false;

while (!seenEndScript && !EndOfFile)
{
SkipToAndParseCode(HtmlSymbolType.OpenAngle);
SourceLocation tagStart = CurrentLocation;
AcceptAndMoveNext();
AcceptWhile(HtmlSymbolType.WhiteSpace);
if (Optional(HtmlSymbolType.Solidus))

if (NextIs(HtmlSymbolType.Solidus))
{
AcceptWhile(HtmlSymbolType.WhiteSpace);
if (At(HtmlSymbolType.Text) && String.Equals(CurrentSymbol.Content, "script", StringComparison.OrdinalIgnoreCase))
var openAngle = CurrentSymbol;
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;
}

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);
if (!Optional(HtmlSymbolType.CloseAngle))
{
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)
{
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)
{
while (!EndOfFile)
Expand Down
Loading

0 comments on commit d89dedc

Please sign in to comment.