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.Blazor.RenderTree;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@ -20,6 +21,13 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine
{
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)
{
throw new System.NotImplementedException(nameof(BeginWriterScope));
@ -119,26 +127,37 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine
switch (nextToken.Type)
{
case HtmlTokenType.Character:
// Text node
context.CodeWriter
.WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.AddText)}")
.WriteStringLiteral(nextToken.Data)
.WriteEndMethodInvocation();
break;
{
// Text node
context.CodeWriter
.WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.AddText)}")
.WriteStringLiteral(nextToken.Data)
.WriteEndMethodInvocation();
break;
}
case HtmlTokenType.StartTag:
var nextTag = nextToken.AsTag();
context.CodeWriter
.WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.OpenElement)}")
.WriteStringLiteral(nextTag.Data)
.WriteEndMethodInvocation();
break;
case HtmlTokenType.EndTag:
context.CodeWriter
.WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.CloseElement)}")
.WriteEndMethodInvocation();
break;
{
var nextTag = nextToken.AsTag();
if (nextToken.Type == HtmlTokenType.StartTag)
{
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:
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>");
// Assert
var nodes = GetRenderTree(component).Where(NotWhitespace);
Assert.Collection(nodes,
Assert.Collection(GetRenderTree(component),
node => AssertNode.Element(node, "myelem", 1),
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)
=> node.NodeType != RenderTreeNodeType.Text
|| !string.IsNullOrWhiteSpace(node.TextContent);