Razor 模板 <table> 作为基于 IFrame 的所见即所得的有效纯 HTML

Razor template <table> as valid pure HTML for IFrame based WYSIWYG

我有 Razor 模板,客户端可以在基于所见即所得的 IFrame 中设置样式(当前 SCeditor)。

原始构造,如 <p>@Model.Price</p>@(Model.CashOnDelivery ? "cash on delivery" : "transfer") 在 WYSIWYG 中工作正常,但问题出现在 <table> 中。现代浏览器喜欢修复 DOM,而 table 标签之间的 Razor 语法并不是真正有效的 HTML,所以我所拥有的肯定不是我所看到的。

例如

<table border="1">
    <tr>
        <th>Name</th>
        <th>Amount</th>
        <th>Price</th>
        <th>TotalPrice</th>
    </tr>
    @foreach (var service in Model.Services)
    {<tr>
        <td>@service.Name</td>
        <td>@service.Amount</td>
        <td>@service.ItemPrice</td>
        <td>@service.TotalPrice</td>
    </tr>}
</table>

显示时所见即所得的IFrame DOM变为

@foreach (var service in Model.Services)
{}
<table border="1">
    <tr>
        <th>Name</th>
        <th>Amount</th>
        <th>Price</th>
        <th>TotalPrice</th>
    </tr>
    <tr>
        <td>@service.Name</td>
        <td>@service.Amount</td>
        <td>@service.ItemPrice</td>
        <td>@service.TotalPrice</td>
    </tr>
</table>

因此,当用户点击“保存”时,所见即所得会根据固定 DOM 为我提供损坏的代码。 Chrome,Firefox 和 IE 10+ 执行大致相同的更正。

我尝试制作一些 hack,比如将 Razor 隐藏在假属性中,但是

用 JS 填充 table 是行不通的,因为模板通常会直接发送到电子邮件。从 <table> 切换到 <div> 会带走 table 的所见即所得功能,因此这也不是一个好的选择。我可以在一个函数中隐藏 table 生成,但同样,它会阻止它的 WYSIWYGing 样式...

总结一下:我需要继续使用 Razor,<table> 和动态生成行,同时保持所见即所得的功能。最好不要在这个过程中让用户害怕模板。我完全没有想法。

好吧,我想出了一个解决方法。

我将模板更改为

<table border="1">
    <tr>
        <th>Name</th>
        <th>Amount</th>
        <th>Price</th>
        <th>TotalPrice</th>
    </tr>
    @foreach (var service in Model.Services)
    {<tr razor-outer="foreach (var service in Model.Services)">
        <td>@service.Name</td>
        <td>@service.Amount</td>
        <td>@service.ItemPrice</td>
        <td>@service.TotalPrice</td>
    </tr>}
</table>

然后在所见即所得中显示之前,只需删除具有 razor-outer 属性的 <tr> 节点前后的文本节点。通过在属性中隐藏代码过程是完全可逆的并且所见即所得友好。

public static string PackRazor(string razor)
{
    if (string.IsNullOrWhiteSpace(razor))
    {
        return razor;
    }
    HtmlDocument doc = new HtmlDocument();

    doc.LoadHtml(razor);

    string razorOuterXpath = "//*[@razor-outer]";

    var nodes = doc.DocumentNode.SelectNodes(razorOuterXpath);
    if (nodes != null)
    {
        foreach (HtmlNode node in nodes)
        {
            HtmlAttribute razorAttr = node.Attributes["razor-outer"];
            var prev = node.PreviousSibling;
            var next = node.NextSibling;

            if (prev.NodeType == HtmlNodeType.Text && prev.InnerHtml.Contains(razorAttr.Value))
            {
                node.ParentNode.RemoveChild(prev);
            }
            if (next.NodeType == HtmlNodeType.Text && next.InnerHtml.Contains("}"))
            {
                node.ParentNode.RemoveChild(next);
            }
        }
    }

    return doc.DocumentNode.OuterHtml;
}

并且在保存模板剃须刀之前可以从属性中恢复

public static string UnpackRazor(string html)
{
    if (string.IsNullOrWhiteSpace(html))
    {
        return html;
    }
    HtmlDocument doc = new HtmlDocument();

    doc.LoadHtml(html);

    string razorOuterXpath = "//*[@razor-outer]";

    var nodes = doc.DocumentNode.SelectNodes(razorOuterXpath);
    if (nodes != null)
    {
        foreach (HtmlNode node in nodes)
        {
            HtmlAttribute razorAttr = node.Attributes["razor-outer"];
            var newNode = HtmlNode.CreateNode(html);
            node.ParentNode.InsertBefore(HtmlNode.CreateNode("@" + razorAttr.Value + "{"), node);
            node.ParentNode.InsertAfter(HtmlNode.CreateNode("}"), node);
            Console.WriteLine(razorAttr.Value);
        }
    }

    return doc.DocumentNode.OuterHtml;
}