private static string GetTagName(Block tagBlock) { var child = tagBlock.Children[0]; if (tagBlock.Type != BlockType.Tag || tagBlock.Children.Count == 0 || !(child is Span)) { return(null); } var childSpan = (Span)child; HtmlSymbol textSymbol = null; for (var i = 0; i < childSpan.Symbols.Count; i++) { var symbol = childSpan.Symbols[i] as HtmlSymbol; if (symbol != null && (symbol.Type & (HtmlSymbolType.WhiteSpace | HtmlSymbolType.Text)) == symbol.Type) { textSymbol = symbol; break; } } if (textSymbol == null) { return(null); } return(textSymbol.Type == HtmlSymbolType.WhiteSpace ? null : textSymbol.Content); }
public void IsCommentContentEndingInvalid_ReturnsFalseForAllowedContent() { // Arrange var expectedSymbol1 = new HtmlSymbol("a", HtmlSymbolType.Text); var sequence = Enumerable.Range((int)'a', 26).Select(item => new HtmlSymbol(((char)item).ToString(), HtmlSymbolType.Text)); // Act & Assert Assert.False(HtmlMarkupParser.IsCommentContentEndingInvalid(sequence)); }
private static bool IsSymbolWhiteSpaceOrNewLine(ISymbol symbol, out HtmlSymbol outputHtmlSymbol) { if (symbol is HtmlSymbol htmlSymbol) { if (htmlSymbol.Type == HtmlSymbolType.WhiteSpace || htmlSymbol.Type == HtmlSymbolType.NewLine) { outputHtmlSymbol = htmlSymbol; return(true); } } outputHtmlSymbol = null; return(false); }
private static HtmlSymbol GetReplacementSymbol(HtmlSymbol htmlSymbol) { switch (htmlSymbol.Type) { case HtmlSymbolType.WhiteSpace: // any amount of whitespace is replaced with a single space return(new HtmlSymbol(htmlSymbol.Start, " ", HtmlSymbolType.WhiteSpace, htmlSymbol.Errors)); case HtmlSymbolType.NewLine: // newline is replaced with just \n return(new HtmlSymbol(htmlSymbol.Start, "\n", HtmlSymbolType.NewLine, htmlSymbol.Errors)); default: throw new ArgumentException($"Expected either {HtmlSymbolType.WhiteSpace} or {HtmlSymbolType.NewLine} symbol, {htmlSymbol.Type} given."); } }
private bool EndTag(SourceLocation tagStart, Stack <Tuple <HtmlSymbol, SourceLocation> > tags) { // Accept "/" and move next Assert(HtmlSymbolType.Solidus); HtmlSymbol solidus = CurrentSymbol; if (!NextToken()) { Accept(_bufferedOpenAngle); Accept(solidus); return(false); } else { string tagName = String.Empty; if (At(HtmlSymbolType.Text)) { tagName = CurrentSymbol.Content; } bool matched = RemoveTag(tags, tagName, tagStart); if ( tags.Count == 0 && String.Equals( tagName, SyntaxConstants.TextTagName, StringComparison.OrdinalIgnoreCase ) && matched ) { Output(SpanKind.Markup); return(EndTextTag(solidus)); } Accept(_bufferedOpenAngle); Accept(solidus); AcceptUntil(HtmlSymbolType.CloseAngle); // Accept the ">" return(Optional(HtmlSymbolType.CloseAngle)); } }
internal static bool IsValidAttributeNameSymbol(HtmlSymbol symbol) { if (symbol == null) { return(false); } // These restrictions cover most of the spec defined: http://www.w3.org/TR/html5/syntax.html#attributes-0 // However, it's not all of it. For instance we don't special case control characters or allow OpenAngle. // It also doesn't try to exclude Razor specific features such as the @ transition. This is based on the // expectation that the parser handles such scenarios prior to falling through to name resolution. var symbolType = symbol.Type; return(symbolType != HtmlSymbolType.WhiteSpace && symbolType != HtmlSymbolType.NewLine && symbolType != HtmlSymbolType.CloseAngle && symbolType != HtmlSymbolType.OpenAngle && symbolType != HtmlSymbolType.ForwardSlash && symbolType != HtmlSymbolType.DoubleQuote && symbolType != HtmlSymbolType.SingleQuote && symbolType != HtmlSymbolType.Equals && symbolType != HtmlSymbolType.Unknown); }
private bool IsUnquotedEndOfAttributeValue(HtmlSymbol sym) { // If unquoted, we have a larger set of terminating characters: // http://dev.w3.org/html5/spec/tokenization.html#attribute-value-unquoted-state // Also we need to detect "/" and ">" return sym.Type == HtmlSymbolType.DoubleQuote || sym.Type == HtmlSymbolType.SingleQuote || sym.Type == HtmlSymbolType.OpenAngle || sym.Type == HtmlSymbolType.Equals || (sym.Type == HtmlSymbolType.ForwardSlash && NextIs(HtmlSymbolType.CloseAngle)) || sym.Type == HtmlSymbolType.CloseAngle || sym.Type == HtmlSymbolType.WhiteSpace || sym.Type == HtmlSymbolType.NewLine; }
private bool IsEndOfAttributeValue(HtmlSymbolType quote, HtmlSymbol sym) { return EndOfFile || sym == null || (quote != HtmlSymbolType.Unknown ? sym.Type == quote // If quoted, just wait for the quote : IsUnquotedEndOfAttributeValue(sym)); }
private bool IsTagRecoveryStopPoint(HtmlSymbol sym) { return sym.Type == HtmlSymbolType.CloseAngle || sym.Type == HtmlSymbolType.ForwardSlash || sym.Type == HtmlSymbolType.OpenAngle || sym.Type == HtmlSymbolType.SingleQuote || sym.Type == HtmlSymbolType.DoubleQuote; }
private bool EndTextTag(HtmlSymbol solidus, IDisposable tagBlockWrapper) { Accept(_bufferedOpenAngle); Accept(solidus); var textLocation = CurrentLocation; Assert(HtmlSymbolType.Text); AcceptAndMoveNext(); var seenCloseAngle = Optional(HtmlSymbolType.CloseAngle); if (!seenCloseAngle) { Context.OnError( textLocation, RazorResources.ParseError_TextTagCannotContainAttributes, length: 4 /* text */); Span.EditHandler.AcceptedCharacters = AcceptedCharacters.Any; RecoverTextTag(); } else { Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None; } Span.ChunkGenerator = SpanChunkGenerator.Null; CompleteTagBlockWithSpan(tagBlockWrapper, Span.EditHandler.AcceptedCharacters, SpanKind.Transition); return seenCloseAngle; }
private void TagBlock(Stack<Tuple<HtmlSymbol, SourceLocation>> tags) { // Skip Whitespace and Text var complete = false; do { SkipToAndParseCode(HtmlSymbolType.OpenAngle); // 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 { var atSpecialTag = AtSpecialTag; if (!EndOfFile && !atSpecialTag) { // Start a Block tag. This is used to wrap things like <p> or <a class="btn"> etc. tagBlockWrapper = Context.StartBlock(BlockType.Tag); } if (EndOfFile) { EndTagBlock(tags, complete: true); } else { _bufferedOpenAngle = null; _lastTagStart = CurrentLocation; Assert(HtmlSymbolType.OpenAngle); _bufferedOpenAngle = CurrentSymbol; var tagStart = CurrentLocation; if (!NextToken()) { Accept(_bufferedOpenAngle); EndTagBlock(tags, complete: false); } else { complete = AfterTagStart(tagStart, tags, atSpecialTag, 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(); } } } while (tags.Count > 0); EndTagBlock(tags, complete); }
internal static bool IsValidAttributeNameSymbol(HtmlSymbol symbol) { if (symbol == null) { return false; } // These restrictions cover most of the spec defined: http://www.w3.org/TR/html5/syntax.html#attributes-0 // However, it's not all of it. For instance we don't special case control characters or allow OpenAngle. // It also doesn't try to exclude Razor specific features such as the @ transition. This is based on the // expectation that the parser handles such scenarios prior to falling through to name resolution. var symbolType = symbol.Type; return symbolType != HtmlSymbolType.WhiteSpace && symbolType != HtmlSymbolType.NewLine && symbolType != HtmlSymbolType.CloseAngle && symbolType != HtmlSymbolType.OpenAngle && symbolType != HtmlSymbolType.ForwardSlash && symbolType != HtmlSymbolType.DoubleQuote && symbolType != HtmlSymbolType.SingleQuote && symbolType != HtmlSymbolType.Equals && symbolType != HtmlSymbolType.Unknown; }
public static SpanConstructor EmptyHtml(this SpanFactory self) { var symbol = new HtmlSymbol(self.LocationTracker.CurrentLocation, string.Empty, HtmlSymbolType.Unknown); return self.Span(SpanKind.Markup, symbol) .With(new MarkupChunkGenerator()); }
private bool StartTag(Stack<Tuple<HtmlSymbol, SourceLocation>> tags, IDisposable tagBlockWrapper) { HtmlSymbol bangSymbol = null; HtmlSymbol potentialTagNameSymbol; if (At(HtmlSymbolType.Bang)) { bangSymbol = CurrentSymbol; potentialTagNameSymbol = Lookahead(count: 1); } else { potentialTagNameSymbol = CurrentSymbol; } HtmlSymbol tagName; if (potentialTagNameSymbol == null || potentialTagNameSymbol.Type != HtmlSymbolType.Text) { tagName = new HtmlSymbol(potentialTagNameSymbol.Start, string.Empty, HtmlSymbolType.Unknown); } else if (bangSymbol != null) { tagName = new HtmlSymbol(bangSymbol.Start, "!" + potentialTagNameSymbol.Content, HtmlSymbolType.Text); } else { tagName = potentialTagNameSymbol; } Tuple<HtmlSymbol, SourceLocation> tag = Tuple.Create(tagName, _lastTagStart); if (tags.Count == 0 && // Note tagName may contain a '!' escape character. This ensures <!text> doesn't match here. // <!text> tags are treated like any other escaped HTML start tag. string.Equals(tag.Item1.Content, SyntaxConstants.TextTagName, StringComparison.OrdinalIgnoreCase)) { Output(SpanKind.Markup); Span.ChunkGenerator = SpanChunkGenerator.Null; Accept(_bufferedOpenAngle); var textLocation = CurrentLocation; Assert(HtmlSymbolType.Text); AcceptAndMoveNext(); var bookmark = CurrentLocation.AbsoluteIndex; IEnumerable<HtmlSymbol> tokens = ReadWhile(IsSpacingToken(includeNewLines: true)); var empty = At(HtmlSymbolType.ForwardSlash); if (empty) { Accept(tokens); Assert(HtmlSymbolType.ForwardSlash); AcceptAndMoveNext(); bookmark = CurrentLocation.AbsoluteIndex; tokens = ReadWhile(IsSpacingToken(includeNewLines: true)); } if (!Optional(HtmlSymbolType.CloseAngle)) { Context.Source.Position = bookmark; NextToken(); Context.OnError( textLocation, RazorResources.ParseError_TextTagCannotContainAttributes, length: 4 /* text */); RecoverTextTag(); } else { Accept(tokens); Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None; } if (!empty) { tags.Push(tag); } CompleteTagBlockWithSpan(tagBlockWrapper, Span.EditHandler.AcceptedCharacters, SpanKind.Transition); return true; } Accept(_bufferedOpenAngle); OptionalBangEscape(); Optional(HtmlSymbolType.Text); return RestOfTag(tag, tags, tagBlockWrapper); }
private bool StartTag(Stack<Tuple<HtmlSymbol, SourceLocation>> tags) { // If we're at text, it's the name, otherwise the name is "" HtmlSymbol tagName; if (At(HtmlSymbolType.Text)) { tagName = CurrentSymbol; } else { tagName = new HtmlSymbol(CurrentLocation, String.Empty, HtmlSymbolType.Unknown); } Tuple<HtmlSymbol, SourceLocation> tag = Tuple.Create(tagName, _lastTagStart); if (tags.Count == 0 && String.Equals(tag.Item1.Content, SyntaxConstants.TextTagName, StringComparison.OrdinalIgnoreCase)) { Output(SpanKind.Markup); Span.CodeGenerator = SpanCodeGenerator.Null; Accept(_bufferedOpenAngle); Assert(HtmlSymbolType.Text); AcceptAndMoveNext(); int bookmark = CurrentLocation.AbsoluteIndex; IEnumerable<HtmlSymbol> tokens = ReadWhile(IsSpacingToken(includeNewLines: true)); bool empty = At(HtmlSymbolType.Solidus); if (empty) { Accept(tokens); Assert(HtmlSymbolType.Solidus); AcceptAndMoveNext(); bookmark = CurrentLocation.AbsoluteIndex; tokens = ReadWhile(IsSpacingToken(includeNewLines: true)); } if (!Optional(HtmlSymbolType.CloseAngle)) { Context.Source.Position = bookmark; NextToken(); Context.OnError(tag.Item2, RazorResources.ParseError_TextTagCannotContainAttributes); } else { Accept(tokens); Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None; } if (!empty) { tags.Push(tag); } Output(SpanKind.Transition); return true; } Accept(_bufferedOpenAngle); Optional(HtmlSymbolType.Text); return RestOfTag(tag, tags); }
private bool EndTextTag(HtmlSymbol solidus) { SourceLocation start = _bufferedOpenAngle.Start; Accept(_bufferedOpenAngle); Accept(solidus); Assert(HtmlSymbolType.Text); AcceptAndMoveNext(); bool seenCloseAngle = Optional(HtmlSymbolType.CloseAngle); if (!seenCloseAngle) { Context.OnError(start, RazorResources.ParseError_TextTagCannotContainAttributes); } else { Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None; } Span.CodeGenerator = SpanCodeGenerator.Null; Output(SpanKind.Transition); return seenCloseAngle; }
private void TagBlock(Stack<Tuple<HtmlSymbol, SourceLocation>> tags) { // Skip Whitespace and Text bool complete = false; do { SkipToAndParseCode(HtmlSymbolType.OpenAngle); if (EndOfFile) { EndTagBlock(tags, complete: true); } else { _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); } } } while (tags.Count > 0); EndTagBlock(tags, complete); }
private static bool IsQuote(HtmlSymbol htmlSymbol) { return htmlSymbol.Type == HtmlSymbolType.DoubleQuote || htmlSymbol.Type == HtmlSymbolType.SingleQuote; }
private void VisitHtmlSymbol(HtmlSymbol symbol) { bool ommitTextCharacter = false; switch (symbol.Type) { case HtmlSymbolType.OpenAngle when this.parseState == ParseState.Default: this.parseState = ParseState.InsideTag; break; case HtmlSymbolType.CloseAngle when this.parseState == ParseState.InsideTag: this.parseState = ParseState.Default; break; case HtmlSymbolType.SingleQuote when this.parseState == ParseState.InsideTag: case HtmlSymbolType.DoubleQuote when this.parseState == ParseState.InsideTag: if (this.symbolsWithoutWhitespace.Count >= 3) { var a = this.symbolsWithoutWhitespace[this.symbolsWithoutWhitespace.Count - 2]; var b = this.symbolsWithoutWhitespace[this.symbolsWithoutWhitespace.Count - 1]; if (a.Type == HtmlSymbolType.Text && b.Type == HtmlSymbolType.Equals) { this.lastVisitedAttribute = a.Content; this.parseState = symbol.Type == HtmlSymbolType.SingleQuote ? ParseState.InsideSingleQuoteAttribute : ParseState.InsideDoubleQuoteAttribute; ommitTextCharacter = true; BeginText(); } } break; case HtmlSymbolType.SingleQuote when this.parseState == ParseState.InsideSingleQuoteAttribute: case HtmlSymbolType.DoubleQuote when this.parseState == ParseState.InsideDoubleQuoteAttribute: this.parseState = ParseState.InsideTag; break; case HtmlSymbolType.Text when this.parseState == ParseState.Default: BeginText(); break; } if (this.parseState == this.textState) { if (this.textSymbols != null && !ommitTextCharacter) { this.textSymbols.Add(symbol); } } else { EndText(); } bool isWhitespace = symbol.Type == HtmlSymbolType.WhiteSpace || symbol.Type == HtmlSymbolType.NewLine; if (!isWhitespace) { this.symbolsWithoutWhitespace.Add(symbol); } }