示例#1
0
        /// <summary>
        /// Renders the HTML buildable object
        /// </summary>
        /// <returns>HTML encoded string</returns>
        /// <remarks>
        /// This method should render the start tag, and the nested content of the HTML buildable,
        /// optionally may render the closing tag.
        /// </remarks>
        public override List <RenderedSegment> Render()
        {
            // --- Initialize the start tag of the element
            var segments = new List <RenderedSegment>();

            if (!string.IsNullOrEmpty(Tag))
            {
                var builder = new TagBuilder(Tag);
                builder.MergeAttributes(HtmlAttributes);
                var mode = VoidElements.Contains(Tag)
                    ? TagRenderMode.SelfClosing
                    : TagRenderMode.StartTag;
                segments.Add(new RenderedSegment(builder.ToString(mode), Depth));
            }

            // --- Build all nested elements
            foreach (var child in _nestedElements)
            {
                segments.AddRange(child.Render());
                segments.AddRange(child.Complete());
            }

            // --- Now, we're ready
            return(segments);
        }
示例#2
0
 /// <summary>
 /// Renders the (optional) closing tag of the buildable object
 /// </summary>
 /// <returns>HTML encoded string</returns>
 /// <remarks>
 /// This tag should render the closing tag if and only if that Build() have not
 /// already done it.
 /// </remarks>
 public override List <RenderedSegment> Complete()
 {
     if (string.IsNullOrEmpty(Tag) || VoidElements.Contains(Tag))
     {
         return(new List <RenderedSegment>());
     }
     return(new List <RenderedSegment>
     {
         new RenderedSegment(string.Format("</{0}>", Tag), Depth)
     });
 }
            private void RewriteChildren(RazorSourceDocument source, IntermediateNode node)
            {
                // We expect all of the immediate children of a node (together) to comprise
                // a well-formed tree of elements and components.
                var stack = new Stack <IntermediateNode>();

                stack.Push(node);

                // Make a copy, we will clear and rebuild the child collection of this node.
                var children = node.Children.ToArray();

                node.Children.Clear();

                // Due to the way Anglesharp parses HTML (tags at a time) we need to keep track of some state.
                // This handles cases like:
                //
                //  <foo bar="17" baz="@baz" />
                //
                // This will lower like:
                //
                //  HtmlContent <foo bar="17"
                //  HtmlAttribute baz=" - "
                //      CSharpAttributeValue baz
                //  HtmlContent  />
                //
                // We need to consume HTML until we see the 'end tag' for <foo /> and then we can
                // the attributes from the parsed HTML and the CSharpAttribute value.
                var parser     = new HtmlParser(source);
                var attributes = new List <HtmlAttributeIntermediateNode>();

                for (var i = 0; i < children.Length; i++)
                {
                    if (children[i] is HtmlContentIntermediateNode htmlNode)
                    {
                        parser.Push(htmlNode);
                        var tokens = parser.Get();
                        foreach (var token in tokens)
                        {
                            // We have to call this before get. Anglesharp doesn't return the start position
                            // of tokens.
                            var start = parser.GetCurrentLocation();

                            // We have to set the Location explicitly otherwise we would need to include
                            // the token in every call to the parser.
                            parser.SetLocation(token);

                            var end = parser.GetCurrentLocation();

                            if (token.Type == HtmlTokenType.EndOfFile)
                            {
                                break;
                            }

                            switch (token.Type)
                            {
                            case HtmlTokenType.Doctype:
                            {
                                // DocType isn't meaningful in Blazor. We don't process them in the runtime
                                // it wouldn't really mean much anyway since we build a DOM directly rather
                                // than letting the user-agent parse the document.
                                //
                                // For now, <!DOCTYPE html> and similar things will just be skipped by the compiler
                                // unless we come up with something more meaningful to do.
                                break;
                            }

                            case HtmlTokenType.Character:
                            {
                                // Text content
                                var span = new SourceSpan(start, end.AbsoluteIndex - start.AbsoluteIndex);
                                stack.Peek().Children.Add(new HtmlContentIntermediateNode()
                                    {
                                        Children =
                                        {
                                            new IntermediateToken()
                                            {
                                                Content = token.Data,
                                                Kind    = TokenKind.Html,
                                                Source  = span,
                                            }
                                        },
                                        Source = span,
                                    });
                                break;
                            }

                            case HtmlTokenType.StartTag:
                            case HtmlTokenType.EndTag:
                            {
                                var tag = token.AsTag();

                                if (token.Type == HtmlTokenType.StartTag)
                                {
                                    var elementNode = new HtmlElementIntermediateNode()
                                    {
                                        TagName = parser.GetTagNameOriginalCasing(tag),
                                        Source  = new SourceSpan(start, end.AbsoluteIndex - start.AbsoluteIndex),
                                    };

                                    stack.Peek().Children.Add(elementNode);
                                    stack.Push(elementNode);

                                    for (var j = 0; j < tag.Attributes.Count; j++)
                                    {
                                        // Unfortunately Anglesharp doesn't provide positions for attributes
                                        // so we can't record the spans here.
                                        var attribute = tag.Attributes[j];
                                        stack.Peek().Children.Add(CreateAttributeNode(attribute));
                                    }

                                    for (var j = 0; j < attributes.Count; j++)
                                    {
                                        stack.Peek().Children.Add(attributes[j]);
                                    }
                                    attributes.Clear();
                                }

                                if (tag.IsSelfClosing || VoidElements.Contains(tag.Data))
                                {
                                    // We can't possibly hit an error here since we just added an element node.
                                    stack.Pop();
                                }

                                if (token.Type == HtmlTokenType.EndTag)
                                {
                                    var popped = stack.Pop();
                                    if (stack.Count == 0)
                                    {
                                        // If we managed to 'bottom out' the stack then we have an unbalanced end tag.
                                        // Put back the current node so we don't crash.
                                        stack.Push(popped);

                                        var tagName    = parser.GetTagNameOriginalCasing(token.AsTag());
                                        var span       = new SourceSpan(start, end.AbsoluteIndex - start.AbsoluteIndex);
                                        var diagnostic = BlazorDiagnosticFactory.Create_UnexpectedClosingTag(span, tagName);
                                        popped.Children.Add(new HtmlElementIntermediateNode()
                                            {
                                                Diagnostics =
                                                {
                                                    diagnostic,
                                                },
                                                TagName = tagName,
                                                Source  = span,
                                            });
                                    }
                                    else if (!string.Equals(tag.Name, ((HtmlElementIntermediateNode)popped).TagName, StringComparison.OrdinalIgnoreCase))
                                    {
                                        var span       = new SourceSpan(start, end.AbsoluteIndex - start.AbsoluteIndex);
                                        var diagnostic = BlazorDiagnosticFactory.Create_MismatchedClosingTag(span, ((HtmlElementIntermediateNode)popped).TagName, token.Data);
                                        popped.Diagnostics.Add(diagnostic);
                                    }
                                    else
                                    {
                                        // Happy path.
                                        //
                                        // We need to compute a new source span because when we found the start tag before we knew
                                        // the end poosition of the tag.
                                        var length = end.AbsoluteIndex - popped.Source.Value.AbsoluteIndex;
                                        popped.Source = new SourceSpan(
                                            popped.Source.Value.FilePath,
                                            popped.Source.Value.AbsoluteIndex,
                                            popped.Source.Value.LineIndex,
                                            popped.Source.Value.CharacterIndex,
                                            length);
                                    }
                                }

                                break;
                            }

                            case HtmlTokenType.Comment:
                                break;

                            default:
                                throw new InvalidCastException($"Unsupported token type: {token.Type.ToString()}");
                            }
                        }
                    }
                    else if (children[i] is HtmlAttributeIntermediateNode htmlAttribute)
                    {
                        // Buffer the attribute for now, it will get written out as part of a tag.
                        attributes.Add(htmlAttribute);
                    }
                    else
                    {
                        // not HTML, or already rewritten.
                        stack.Peek().Children.Add(children[i]);
                    }
                }

                var extraContent = parser.GetUnparsedContent();

                if (!string.IsNullOrEmpty(extraContent))
                {
                    // extra HTML - almost certainly invalid because it couldn't be parsed.
                    var start = parser.GetCurrentLocation();
                    var end   = parser.GetCurrentLocation(extraContent.Length);
                    var span  = new SourceSpan(start, end.AbsoluteIndex - start.AbsoluteIndex);
                    stack.Peek().Children.Add(new HtmlContentIntermediateNode()
                    {
                        Children =
                        {
                            new IntermediateToken()
                            {
                                Content = extraContent,
                                Kind    = TokenKind.Html,
                                Source  = span,
                            }
                        },
                        Diagnostics =
                        {
                            BlazorDiagnosticFactory.Create_InvalidHtmlContent(span, extraContent),
                        },
                        Source = span,
                    });
                }

                while (stack.Count > 1)
                {
                    // not balanced
                    var popped     = (HtmlElementIntermediateNode)stack.Pop();
                    var diagnostic = BlazorDiagnosticFactory.Create_UnclosedTag(popped.Source, popped.TagName);
                    popped.Diagnostics.Add(diagnostic);
                }
            }
        private bool RestOfTag(Tuple <HtmlSymbol, SourceLocation> tag, Stack <Tuple <HtmlSymbol, SourceLocation> > tags)
        {
            TagContent();

            // We are now at a possible end of the tag
            // Found '<', so we just abort this tag.
            if (At(HtmlSymbolType.OpenAngle))
            {
                return(false);
            }

            bool isEmpty = At(HtmlSymbolType.Solidus);

            // Found a solidus, so don't accept it but DON'T push the tag to the stack
            if (isEmpty)
            {
                AcceptAndMoveNext();
            }

            // Check for the '>' to determine if the tag is finished
            bool seenClose = Optional(HtmlSymbolType.CloseAngle);

            if (!seenClose)
            {
                Context.OnError(tag.Item2, RazorResources.ParseError_UnfinishedTag, tag.Item1.Content);
            }
            else
            {
                if (!isEmpty)
                {
                    // Is this a void element?
                    string tagName = tag.Item1.Content.Trim();
                    if (VoidElements.Contains(tagName))
                    {
                        // 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
                        int bookmark = CurrentLocation.AbsoluteIndex;

                        // Skip whitespace
                        IEnumerable <HtmlSymbol> ws = ReadWhile(IsSpacingToken(includeNewLines: true));

                        // Open Angle
                        if (At(HtmlSymbolType.OpenAngle) && NextIs(HtmlSymbolType.Solidus))
                        {
                            HtmlSymbol openAngle = CurrentSymbol;
                            NextToken();
                            Assert(HtmlSymbolType.Solidus);
                            HtmlSymbol solidus = CurrentSymbol;
                            NextToken();
                            if (At(HtmlSymbolType.Text) && String.Equals(CurrentSymbol.Content, tagName, StringComparison.OrdinalIgnoreCase))
                            {
                                // Accept up to here
                                Accept(ws);
                                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
                                return(Optional(HtmlSymbolType.CloseAngle));
                            } // At(HtmlSymbolType.Text) && String.Equals(CurrentSymbol.Content, tagName, StringComparison.OrdinalIgnoreCase)
                        }     // At(HtmlSymbolType.OpenAngle) && NextIs(HtmlSymbolType.Solidus)

                        // 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();
                    }
                    else
                    {
                        // Push the tag on to the stack
                        tags.Push(tag);
                    }
                }
            }
            return(seenClose);
        }
示例#5
0
        private bool RestOfTag(Tuple <HtmlSymbol, SourceLocation> tag,
                               Stack <Tuple <HtmlSymbol, SourceLocation> > tags,
                               IDisposable tagBlockWrapper)
        {
            TagContent();

            // We are now at a possible end of the tag
            // Found '<', so we just abort this tag.
            if (At(HtmlSymbolType.OpenAngle))
            {
                return(false);
            }

            var isEmpty = At(HtmlSymbolType.ForwardSlash);

            // Found a solidus, so don't accept it but DON'T push the tag to the stack
            if (isEmpty)
            {
                AcceptAndMoveNext();
            }

            // Check for the '>' to determine if the tag is finished
            var seenClose = Optional(HtmlSymbolType.CloseAngle);

            if (!seenClose)
            {
                Context.OnError(tag.Item2, RazorResources.FormatParseError_UnfinishedTag(tag.Item1.Content));
            }
            else
            {
                if (!isEmpty)
                {
                    // Is this a void element?
                    var 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
                        var bookmark = CurrentLocation.AbsoluteIndex;

                        // Skip whitespace
                        IEnumerable <HtmlSymbol> whiteSpace = ReadWhile(IsSpacingToken(includeNewLines: true));

                        // Open Angle
                        if (At(HtmlSymbolType.OpenAngle) && NextIs(HtmlSymbolType.ForwardSlash))
                        {
                            var openAngle = CurrentSymbol;
                            NextToken();
                            Assert(HtmlSymbolType.ForwardSlash);
                            var solidus = CurrentSymbol;
                            NextToken();
                            if (At(HtmlSymbolType.Text) && string.Equals(CurrentSymbol.Content, tagName, StringComparison.OrdinalIgnoreCase))
                            {
                                // Accept up to here
                                Accept(whiteSpace);
                                Output(SpanKind.Markup); // Output the whitespace

                                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
                                    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
                        Context.Source.Position = bookmark;
                        NextToken();
                    }
                    else if (string.Equals(tagName, "script", StringComparison.OrdinalIgnoreCase))
                    {
                        CompleteTagBlockWithSpan(tagBlockWrapper, AcceptedCharacters.None, SpanKind.Markup);

                        SkipToEndScriptAndParseCode(endTagAcceptedCharacters: AcceptedCharacters.None);
                    }
                    else
                    {
                        // Push the tag on to the stack
                        tags.Push(tag);
                    }
                }
            }
            return(seenClose);
        }