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);
        }
示例#2
0
        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));
        }
示例#3
0
        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);
        }
示例#4
0
        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.");
            }
        }
示例#5
0
        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;
        }
示例#13
0
 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;
 }
示例#19
0
        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);
            }
        }