部分将代码作为HTML和文本运行

18

介绍

我正在创建一个模板构建器,用户可以使用它来为应用程序构建模板。用户可以拖放多个块,例如文本块和“自定义代码”块。该模板将在应用程序中解析。目前,模板可能看起来像这样:

<section>
    <div class="row">
        <div class="col-sm-12">
            <section data-type="code">
                <#code></#code>
            </section>
        </div>
    </div>
    <div class="row">
        <div class="col-sm-12" data-type="container-content">
            <section data-type="text">
                <u>Lorem</u> ipsum
            </section>
        </div>
    </div>
</section>

因此,该模板包含两个元素(请参阅data-type属性):一个部分是自定义编写的代码。在这里,用户编写了自定义的代码,包括Apache Freemarker代码。第二部分是自定义编写的文本。

情况

上述代码将以两种不同的方式使用。

  • 完全相同的代码将在使用模板的应用程序内使用(因此他们应该能够写入Freemarker代码,因为这将被解析)。
  • 在我的网站上,用户应该可以编辑此模板。因为代码存储在数据库中,如上所述,存在问题:

问题

当我在Web界面中直接呈现模板时,text部分将使用<u></u>标记正确呈现,但code部分也将呈现为HTML,这可能会导致奇怪的行为(例如,freemarker符号</#list>被自动转换为<!--#list-->)。

但是,如果我只呈现全文本模板,则带有<u></u>标记的text部分也将不会呈现。

预期结果

我想使用JavaScript/jQuery读取模板变量,然后将每个data-type作为html解析,并将text作为html,将code作为文本。

我该如何遍历模板并执行此操作?


这很有趣...整个东西是在客户端编译和运行的吗? - Soolie
该死..我真的对此很感兴趣。但是你能提供<#code>codeExample</#code>并给出结果和期望结果吗?(在SO代码块中而不是文本说明)。因为我以前从未使用过apache freemarker。 - plonknimbuzz
我忘记了:你如何转换所有这些?然后将<div class="containerThemeBuilder"><section>restYourExampleAbove</section></div>抛出到ajax中,使用$(".containerThemeBuilder").html() - plonknimbuzz
@plonknimbuzz,这不是关于<#code></#code>内的代码或者它使用Freemarker的问题;而是某些字符的组合不被支持。请参考这个链接:https://jsfiddle.net/akzL6cee/2/。因此,它将`</#code>转换为<!--#code-->`(请查看源代码)。这就是您需要的信息吗? - Jordy
是的,这是可能的。但问题是:如何同时运行部分代码作为 HTML 和部分代码作为文本呢? - Jordy
显示剩余10条评论
5个回答

8
有一种替代语法,使用方括号代替尖括号。
检查它是否解决了您的标签识别问题,而不会影响其他任何功能。 https://freemarker.apache.org/docs/dgui_misc_alternativesyntax.html 编辑1:
为了在解析HTML时显示<#code>标签内的源代码,您可以在数据库中对其进行转义(转义HTML特殊字符,如<、>和&,以&lt;、&gt;和&amp;表示)。这样,在呈现时,代码内容中将不会创建任何HTML标记,文档也不会被破坏。
然后,您可以直接将数据库中的所有内容呈现为HTML:文本将保留标记,代码将作为文本。
要进行此修改,可以使用正则表达式找到被<#code>标签包围的内容,并用HTML转义的等效内容替换。要执行此操作的确切方式取决于您将用于该工作的语言,因为在RegExes和可用转义函数方面存在一些差异。
编辑2:
如果您正在使用AJAX加载内容,则有机会在从服务器获取内容之后在javascript中应用替换,同时保持数据库原样。

是的,非常好的观点!这确实解决了尖括号的问题。有什么想法可以完全防止在<section date-type="code"></section>之间执行代码吗? - Jordy
是否有逃避已经存在于您的数据库中的代码的选项?我发现很难恢复可能已经呈现的HTML代码,最终会破坏以下标记。 - Eduardo Poço
我认为这可能是我唯一的选择。所以我必须那样做,或者找到另一个解决方法。 - Jordy

2

问题回顾

在javascript中解析HTML时,通常使用DOMParser对象(IE10+支持)。

正如您所说,解析在data-type="code"部分失败,因为它不知道如何处理</#...>标签...

const templ = `<section><div class="row"><div class="col-sm-12"><section data-type="code"><#code></#code></section></div></div><div class="row"><div class="col-sm-12" data-type="container-content"><section data-type="text"><u>Lorem</u> ipsum</section></div></div></section>`;
const parser = new DOMParser();
const doc = parser.parseFromString(templ, "text/html");

console.log(
  "Wrongly parsed </#code> tag:\n",
  doc.querySelector("[data-type='code']").innerHTML
);

解决方法

现在,也许尝试对需要转义的字符进行快速的正则表达式查找和替换听起来是个好主意,但我不建议这样做...

据我所知,没有办法“打破”解析过程或传递某些类型元素的策略...

我认为这给你留下了两个选择。要么:

  1. 像用户Eduardo Poço他们的回答中建议的那样,在代码段内不使用无法解析的语法。

或者(我更喜欢的方向):

  1. 修改模板本身,停止完全解析代码段的内容。

使用修改后的模板

HTML中有一个用于“脚本”类内容的标签!它就是,毫不意外的,<script>标签。让我们将其注入到我们的code部分中:

<section data-type="code">
    <script type="text">
        <#code></#code>
    </script>
</section>
DOMParser 不会操作这个标签,它会完全保留原样。

const templ = '<section><div class="row"><div class="col-sm-12"><section data-type="code"><script type="text"><#code></#code></' + 'script></section></div></div><div class="row"><div class="col-sm-12" data-type="container-content"><section data-type="text"><u>Lorem</u> ipsum</section></div></div></section>';

const parser = new DOMParser();
const doc = parser.parseFromString(templ, "text/html");

console.log(
  "Now, there's a <script> tag:\n",
  doc.querySelector("[data-type='code']").innerHTML
);

请注意,我不得不将模板字符串从两个部分连接起来,以确保stackoverflow的代码片段不会出错。他们是否遇到了类似的问题?:-o


现在,我们只需要使用通用的DOM方法,包括innerText(而不是innerHTML),将脚本的内部内容返回到DOM的可见部分中:

var templ = '<section><div class="row"><div class="col-sm-12"><section data-type="code"><script type="text"><#code></#code></' + 'script></section></div></div><div class="row"><div class="col-sm-12" data-type="container-content"><section data-type="text"><u>Lorem</u> ipsum</section></div></div></section>`;'

var parser = new DOMParser();
var doc = parser.parseFromString(templ, "text/html");

Array
  .from(doc.querySelectorAll(
    "[data-type='code'] > script")
  )
  .forEach(script => {
      const codeTag = document.createElement("code");
      codeTag.innerText = script.innerHTML;
      script.replaceWith(codeTag);
  });

document.getElementById("wrapper").appendChild(doc.body.firstChild);
code { background: #efefef; }
<div id="wrapper"></div>


1
这样的东西怎么样?

// https://dev59.com/C3VD5IYBdhLWcg3wWaRh
function FitToContent(id, maxHeight)
{
   var text = id && id.style ? id : document.getElementById(id);
   if (!text)
      return;

   /* Accounts for rows being deleted, pixel value may need adjusting */
   if (text.clientHeight == text.scrollHeight) {
      text.style.height = "30px";
   }

   var adjustedHeight = text.clientHeight;
   if (!maxHeight || maxHeight > adjustedHeight)
   {
      adjustedHeight = Math.max(text.scrollHeight, adjustedHeight);
      if (maxHeight)
         adjustedHeight = Math.min(maxHeight, adjustedHeight);
      if (adjustedHeight > text.clientHeight)
         text.style.height = adjustedHeight + "px";
   }
}

$('textarea').each(function(i,v){
  FitToContent($(v)[0], document.documentElement.clientHeight)
});
textarea {
  background: transparent;
  border: 0;
  font-family: 'Times New Roman';
  font-size: 1em;
  width: 100%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<section>
    <div class="row">
        <div class="col-sm-12">
            <h4>First code block</h4>
            <section data-type="code">
                <textarea class='code'><#code>
    <h2>FreeMarker Spring MVC Hello World</h2>
    <table class="datatable">
        <tr>
            <th>Make</th><th>Model</th>
        </tr>
        <#list model["carList"] as car>
        <tr>
            <td>${car.make}</td>
            <td>${car.model}</td>
        </tr>
        </#list>
    </table>
</#code></textarea>
            </section>
        </div>
    </div>
    <div class="row">
        <div class="col-sm-12" data-type="container-content">
            <h4>Regular HTML section</h4>
            <section data-type="text">
                <u>Lorem</u> ipsum
            </section>
        </div>
    </div>
      <div class="row">
        <div class="col-sm-12">
            <h4>Second code block</h4>
            <section data-type="code">
                <textarea class='code'><#code>
    <table class="datatable">
        <tr>
            <th>Name</th><th>Email</th>
        </tr>
        <#list model["personList"] as person>
        <tr>
            <td>${person.name}</td>
            <td>${person.email}</td>
        </tr>
        </#list>
    </table>
</#code></textarea>
            </section>
        </div>
    </div>
</section>


1
你可以使用字符集编码,以便在输出之前不执行。HTML字符集参考 他们可以编辑它,因为它看起来很正常,并将其发送回给你或服务器。确保在头部包含字符集引用。
<meta charset="UTF-8"> // HTML5 
<meta http-equiv="Content-Type" content="text/html;charset=ISO-8859-1"> // HTML4

<!-- @CHARSET / No execute -->
&#60;section&#62;
    &#60;div class="row"&#62;
        &#60;div class="col-sm-12"&#62;
            &#60;section data-type="code"&#62;
                <#code> <!-- Run this --> </#code>
            &#60;/section&#62;
        &#60;/div&#62;
    &#60;/div&#62;
    &#60;div class="row"&#62;
        &#60;div class="col-sm-12" data-type="container-content"&#62;
            &#60;section data-type="text"&#62;
                &#60;u&#62; <!-- Don't run --> &#60;/u&#62; 
            &#60;/section&#62;
        &#60;/div&#62;
    &#60;/div&#62;
&#60;/section&#62;


但是,一旦我解析它,所有代码都将被执行,所以这没有任何意义。我想要的是执行所有代码,除了<section date-type="code"></section>标签之间的代码。 - Jordy

1
如果我没有误解,你可以使用<plaintext>标签将块渲染为页面上的文本。
<plaintext>
    <#code></#code>
</plaintext>

很遗憾,它已经过时了。而且它没有闭合标签,所以我猜它会将标签后的所有代码都渲染为文本。 - Jordy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接