aspnetcore/src/Microsoft.AspNetCore.Razor/Parser/HtmlMarkupParser.cs

258 lines
8.6 KiB
C#

// Copyright (c) .NET Foundation. 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 Microsoft.AspNetCore.Razor.Chunks.Generators;
using Microsoft.AspNetCore.Razor.Parser.SyntaxTree;
using Microsoft.AspNetCore.Razor.Tokenizer;
using Microsoft.AspNetCore.Razor.Tokenizer.Symbols;
namespace Microsoft.AspNetCore.Razor.Parser
{
public partial class HtmlMarkupParser : TokenizerBackedParser<HtmlTokenizer, HtmlSymbol, HtmlSymbolType>
{
//From http://dev.w3.org/html5/spec/Overview.html#elements-0
private ISet<string> _voidElements = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"area",
"base",
"br",
"col",
"command",
"embed",
"hr",
"img",
"input",
"keygen",
"link",
"meta",
"param",
"source",
"track",
"wbr"
};
public ISet<string> VoidElements
{
get { return _voidElements; }
}
protected override ParserBase OtherParser
{
get { return Context.CodeParser; }
}
protected override LanguageCharacteristics<HtmlTokenizer, HtmlSymbol, HtmlSymbolType> Language
{
get { return HtmlLanguageCharacteristics.Instance; }
}
protected override bool SymbolTypeEquals(HtmlSymbolType x, HtmlSymbolType y) => x == y;
public override void BuildSpan(SpanBuilder span, SourceLocation start, string content)
{
span.Kind = SpanKind.Markup;
span.ChunkGenerator = new MarkupChunkGenerator();
base.BuildSpan(span, start, content);
}
protected override void OutputSpanBeforeRazorComment()
{
Output(SpanKind.Markup);
}
protected void SkipToAndParseCode(HtmlSymbolType type)
{
SkipToAndParseCode(sym => sym.Type == type);
}
protected void SkipToAndParseCode(Func<HtmlSymbol, bool> condition)
{
HtmlSymbol last = null;
var startOfLine = false;
while (!EndOfFile && !condition(CurrentSymbol))
{
if (Context.NullGenerateWhitespaceAndNewLine)
{
Context.NullGenerateWhitespaceAndNewLine = false;
Span.ChunkGenerator = SpanChunkGenerator.Null;
AcceptWhile(symbol => symbol.Type == HtmlSymbolType.WhiteSpace);
if (At(HtmlSymbolType.NewLine))
{
AcceptAndMoveNext();
}
Output(SpanKind.Markup);
}
else if (At(HtmlSymbolType.NewLine))
{
if (last != null)
{
Accept(last);
}
// Mark the start of a new line
startOfLine = true;
last = null;
AcceptAndMoveNext();
}
else if (At(HtmlSymbolType.Transition))
{
var transition = CurrentSymbol;
NextToken();
if (At(HtmlSymbolType.Transition))
{
if (last != null)
{
Accept(last);
last = null;
}
Output(SpanKind.Markup);
Accept(transition);
Span.ChunkGenerator = SpanChunkGenerator.Null;
Output(SpanKind.Markup);
AcceptAndMoveNext();
continue; // while
}
else
{
if (!EndOfFile)
{
PutCurrentBack();
}
PutBack(transition);
}
// Handle whitespace rewriting
if (last != null)
{
if (!Context.DesignTimeMode && last.Type == HtmlSymbolType.WhiteSpace && startOfLine)
{
// Put the whitespace back too
startOfLine = false;
PutBack(last);
last = null;
}
else
{
// Accept last
Accept(last);
last = null;
}
}
OtherParserBlock();
}
else if (At(HtmlSymbolType.RazorCommentTransition))
{
if (last != null)
{
// Don't render the whitespace between the start of the line and the razor comment.
if (startOfLine && last.Type == HtmlSymbolType.WhiteSpace)
{
AddMarkerSymbolIfNecessary();
// Output the symbols that may have been accepted prior to the whitespace.
Output(SpanKind.Markup);
Span.ChunkGenerator = SpanChunkGenerator.Null;
}
Accept(last);
last = null;
}
AddMarkerSymbolIfNecessary();
Output(SpanKind.Markup);
RazorComment();
// Handle the whitespace and newline at the end of a razor comment.
if (startOfLine &&
(At(HtmlSymbolType.NewLine) ||
(At(HtmlSymbolType.WhiteSpace) && NextIs(HtmlSymbolType.NewLine))))
{
AcceptWhile(IsSpacingToken(includeNewLines: false));
AcceptAndMoveNext();
Span.ChunkGenerator = SpanChunkGenerator.Null;
Output(SpanKind.Markup);
}
}
else
{
// As long as we see whitespace, we're still at the "start" of the line
startOfLine &= At(HtmlSymbolType.WhiteSpace);
// If there's a last token, accept it
if (last != null)
{
Accept(last);
last = null;
}
// Advance
last = CurrentSymbol;
NextToken();
}
}
if (last != null)
{
Accept(last);
}
}
protected static Func<HtmlSymbol, bool> IsSpacingToken(bool includeNewLines)
{
return sym => sym.Type == HtmlSymbolType.WhiteSpace || (includeNewLines && sym.Type == HtmlSymbolType.NewLine);
}
private void OtherParserBlock()
{
AddMarkerSymbolIfNecessary();
Output(SpanKind.Markup);
using (PushSpanConfig())
{
Context.SwitchActiveParser();
Context.CodeParser.ParseBlock();
Context.SwitchActiveParser();
}
Initialize(Span);
NextToken();
}
private bool IsBangEscape(int lookahead)
{
var potentialBang = Lookahead(lookahead);
if (potentialBang != null &&
potentialBang.Type == HtmlSymbolType.Bang)
{
var afterBang = Lookahead(lookahead + 1);
return afterBang != null &&
afterBang.Type == HtmlSymbolType.Text &&
!string.Equals(afterBang.Content, "DOCTYPE", StringComparison.OrdinalIgnoreCase);
}
return false;
}
private void OptionalBangEscape()
{
if (IsBangEscape(lookahead: 0))
{
Output(SpanKind.Markup);
// Accept the parser escape character '!'.
Assert(HtmlSymbolType.Bang);
AcceptAndMoveNext();
// Setup the metacode span that we will be outputing.
Span.ChunkGenerator = SpanChunkGenerator.Null;
Output(SpanKind.MetaCode, AcceptedCharacters.None);
}
}
}
}