public override void WriteHtmlElement(CodeRenderingContext context, HtmlElementIntermediateNode node) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (node == null) { throw new ArgumentNullException(nameof(node)); } context.RenderChildren(node); }
private HtmlElementIntermediateNode RewriteAsElement(TagHelperIntermediateNode node) { var result = new HtmlElementIntermediateNode() { Source = node.Source, TagName = node.TagName, }; for (var i = 0; i < node.Diagnostics.Count; i++) { result.Diagnostics.Add(node.Diagnostics[i]); } var visitor = new ElementRewriteVisitor(result.Children); visitor.Visit(node); return(result); }
public abstract void WriteHtmlElement(CodeRenderingContext context, HtmlElementIntermediateNode node);
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: { 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(); } break; } case HtmlTokenType.EndTag: { var tag = token.AsTag(); 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(tag); var span = new SourceSpan(start, end.AbsoluteIndex - start.AbsoluteIndex); var diagnostic = VoidElements.Contains(tagName) ? BlazorDiagnosticFactory.Create_UnexpectedClosingTagForVoidElement(span, tagName) : 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 position 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); } }