In RazorCompiler, support self-closing and void elements

This commit is contained in:
Steve Sanderson 2018-01-16 11:50:40 +00:00
parent a64ece0319
commit 1c1fd69bf2
2 changed files with 61 additions and 19 deletions

View File

@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Microsoft.AspNetCore.Razor.Language.Intermediate; using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Microsoft.Blazor.RenderTree; using Microsoft.Blazor.RenderTree;
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@ -20,6 +21,13 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine
{ {
private const string builderVarName = "builder"; private const string builderVarName = "builder";
// Per the HTML spec, the following elements are inherently self-closing
// For example, <img> is the same as <img /> (and therefore it cannot contain descendants)
private static HashSet<string> htmlVoidElementsLookup
= new HashSet<string>(
new[] { "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr" },
StringComparer.OrdinalIgnoreCase);
public override void BeginWriterScope(CodeRenderingContext context, string writer) public override void BeginWriterScope(CodeRenderingContext context, string writer)
{ {
throw new System.NotImplementedException(nameof(BeginWriterScope)); throw new System.NotImplementedException(nameof(BeginWriterScope));
@ -119,26 +127,37 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine
switch (nextToken.Type) switch (nextToken.Type)
{ {
case HtmlTokenType.Character: case HtmlTokenType.Character:
// Text node {
context.CodeWriter // Text node
.WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.AddText)}") context.CodeWriter
.WriteStringLiteral(nextToken.Data) .WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.AddText)}")
.WriteEndMethodInvocation(); .WriteStringLiteral(nextToken.Data)
break; .WriteEndMethodInvocation();
break;
}
case HtmlTokenType.StartTag: case HtmlTokenType.StartTag:
var nextTag = nextToken.AsTag();
context.CodeWriter
.WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.OpenElement)}")
.WriteStringLiteral(nextTag.Data)
.WriteEndMethodInvocation();
break;
case HtmlTokenType.EndTag: case HtmlTokenType.EndTag:
context.CodeWriter {
.WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.CloseElement)}") var nextTag = nextToken.AsTag();
.WriteEndMethodInvocation(); if (nextToken.Type == HtmlTokenType.StartTag)
break; {
context.CodeWriter
.WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.OpenElement)}")
.WriteStringLiteral(nextTag.Data)
.WriteEndMethodInvocation();
}
if (nextToken.Type == HtmlTokenType.EndTag
|| nextTag.IsSelfClosing
|| htmlVoidElementsLookup.Contains(nextTag.Data))
{
context.CodeWriter
.WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.CloseElement)}")
.WriteEndMethodInvocation();
}
break;
}
default: default:
throw new InvalidCastException($"Unsupported token type: {nextToken.Type.ToString()}"); throw new InvalidCastException($"Unsupported token type: {nextToken.Type.ToString()}");

View File

@ -128,12 +128,35 @@ namespace Microsoft.Blazor.Build.Test
var component = CompileToComponent("<myelem>Hello</myelem>"); var component = CompileToComponent("<myelem>Hello</myelem>");
// Assert // Assert
var nodes = GetRenderTree(component).Where(NotWhitespace); Assert.Collection(GetRenderTree(component),
Assert.Collection(nodes,
node => AssertNode.Element(node, "myelem", 1), node => AssertNode.Element(node, "myelem", 1),
node => AssertNode.Text(node, "Hello")); node => AssertNode.Text(node, "Hello"));
} }
[Fact]
public void CanRenderSelfClosingElements()
{
// Arrange/Act
var component = CompileToComponent("Some text so elem isn't at position 0 <myelem />");
// Assert
Assert.Collection(GetRenderTree(component),
node => AssertNode.Text(node, "Some text so elem isn't at position 0 "),
node => AssertNode.Element(node, "myelem", 1));
}
[Fact]
public void CanRenderVoidHtmlElements()
{
// Arrange/Act
var component = CompileToComponent("Some text so elem isn't at position 0 <img>");
// Assert
Assert.Collection(GetRenderTree(component),
node => AssertNode.Text(node, "Some text so elem isn't at position 0 "),
node => AssertNode.Element(node, "img", 1));
}
private static bool NotWhitespace(RenderTreeNode node) private static bool NotWhitespace(RenderTreeNode node)
=> node.NodeType != RenderTreeNodeType.Text => node.NodeType != RenderTreeNodeType.Text
|| !string.IsNullOrWhiteSpace(node.TextContent); || !string.IsNullOrWhiteSpace(node.TextContent);