自动转义和输出格式

Page Contents

这是有关自动转义和相关概念的“详细”教程;最低要求改为阅读此内容

Note:

这里描述的自动转义至少需要 FreeMarker 2.3.24. 如果必须使用早期版本,请改用不推荐使用的escape directive

Output formats

每个模板都有一个关联的输出格式(一个freemarker.core.OutputFormat实例)。输出格式规定了转义规则,该规则适用于不是在字符串 Literals 中的所有${...} -s(和#{...} -s)。它还指定了 MIME 类型(例如"text/HTML")和规范名称(例如"HTML"),嵌入应用程序/框架可以将其用于自己的目的。

将输出格式与模板关联是程序员的责任。此外,建议配置 FreeMarker,以使具有ftlhftlx文件 extensions 的模板分别自动与 HTML 和 XML 输出格式相关联。

sched 义的输出格式为:

Name Description MIME Type 默认实施(freemarker.core.*)
HTML 转义符<>&"'&lt;&gt;&amp;&quot;&#39; text/html HTMLOutputFormat.INSTANCE
XHTML 转义符<>&"'&lt;&gt;&amp;&quot;&#39; application/xhtml+xml XHTMLOutputFormat.INSTANCE
XML 转义符<>&"'&lt;&gt;&amp;&quot;&apos; application/xml XMLOutputFormat.INSTANCE
RTF 转义{}\\{\}\\ application/rtf RTFOutputFormat.INSTANCE
undefined 无法逃脱。照原样打印其他输出格式的标记输出值(概念说明later)。在配置中未明确设置输出格式时使用的默认输出格式。 无(null) UndefinedOutputFormat.INSTANCE
plainText Doesn't escape. text/plain PlainTextOutputFormat.INSTANCE
JavaScript Doesn't escape. application/javascript JavaScriptOutputFormat.INSTANCE
JSON Doesn't escape. application/json JSONOutputFormat.INSTANCE
CSS Doesn't escape. text/css CSSOutputFormat.INSTANCE

程序员可以添加自己的输出格式,因此这可能不是您应用程序中的所有输出格式!

覆盖模板中的输出格式

尤其是在旧版应用程序中,您通常会发现输出格式为undefined(可以使用${.output_format}进行检查),因此不会发生自动转义。在其他情况下,将为所有模板设置通用的输出格式(例如 HTML),但是少数模板需要使用不同的输出格式。无论如何,模板的输出格式都可以在ftlHeaders中强制执行:

<#ftl output_format="XML">
${"'"}  <#-- Prints: &apos; -->

上面,输出格式由先前表*(实际上是通过Configuration.getOutputFormat(String name)查找)*中显示的名称来引用。

Note:

如果在添加上述ftlHeaders 后没有发生转义,则<#ftl output_format="XML" auto_esc=true>可能会有所帮助(这意味着 FreeMarker 被配置为使用“禁用”自动转义* policy *,通常不建议这样做)。

输出格式也可以仅应用于模板的一部分,例如:

<#-- Let's assume we have "HTML" output format by default. -->
${"'"}  <#-- Prints: &#39; -->
<#outputformat "XML">
  ${"'"}  <#-- Prints: &apos; -->
</#outputformat>
${"'"}  <#-- Prints: &#39; -->

基本上,模板中的每个位置都有关联的输出格式,并且如您在上面所看到的,模板中的每个位置可能都不相同。这种关联会坚持到位,并且不会随着模板的执行而改变。因此,例如,如果您从outputformat块内部调用宏,并且在该块外部定义了被调用的宏,则它将无法获取该宏的输出格式。或者,如果您具有在模板中使用 HTML 输出格式定义的宏,则在调用它的地方没有母体,那么该宏将始终以 HTML 输出格式执行。这就像您要在文本编辑器中通过输出格式为模板文件的每个字符上色,然后在以后执行模板时,它仅考虑正在执行的语句的颜色。这使您可以严格控制输出格式,从而避免转义。您不必考虑可能导致问题的执行路径。

禁用自动转义

对于单个插值,您可以使用?no_esc禁用自动转义:

<#-- Let's assume we have "HTML" output format by default. -->
${'<b>test</b>'}  <#-- prints: &lt;b&gt;test&lt;/b&gt; -->
${'<b>test</b>'?no_esc}  <#-- prints: <b>test</b> -->

您还可以使用noautoesc directive禁用整个部分的自动转义:

${'&'}  <#-- prints: &amp; -->
<#noautoesc>
  ${'&'}  <#-- prints: & -->
  ...
  ${'&'}  <#-- prints: & -->
</#noautoesc>
${'&'}  <#-- prints: &amp; -->

就像outputformat一样,这仅适用于字面上位于块内的部分(“着色”逻辑)。

也可以在ftlHeaders 中为整个模板禁用自动转义。然后可以为带有autoesc directive的部分重新启用它:

<#ftl autoesc=false>
${'&'}  <#-- prints: & -->
<#autoesc>
  ${'&'}  <#-- prints: &amp; -->
  ...
  ${'&'}  <#-- prints: &amp; -->
</#autoesc>
${'&'}  <#-- prints: & -->

当禁用转义时,也可以使用?esc强制转义单个插值:

<#ftl autoesc=false>
${'&'}  <#-- prints: & -->
${'&'?esc}  <#-- prints: &amp; -->

当然,autoesc?esc也在noautoesc块内工作。

“标记输出”值

在 FTL 中,值具有类型,例如字符串,数字,布尔值等。一种这样的类型称为“标记输出”。该类型的值是一段已经采用输出格式(例如 HTML)的文本,因此不需要进一步转义。我们之前已经产生了这样的值:

这些在${...}之外也很有用。例如,在这里infoBox宏的调用者可以确定消息是纯文本(因此需要转义)还是 HTML(因此不能转义):

<#-- We assume that we have "HTML" output format by default. -->

<@infoBox "Foo & bar" />
<@infoBox "Foo <b>bar</b>"?no_esc />

<#macro infoBox message>
  <div class="infoBox">
    ${message}
  </div>
</#macro>
<div class="infoBox">
    Foo &amp; bar
  </div>
  <div class="infoBox">
    Foo <b>bar</b>
  </div>

获得标记输出值的另一种情况是输出捕获:

<#-- We assume that we have "HTML" output format by default. -->
<#assign captured><b>Test</b></#assign>
Just a string: ${"<b>Test</b>"}
Captured output: ${captured}
Just a string: &lt;b&gt;Test&lt;/b&gt;
Captured output: <b>Test</b>

由于捕获的输出是标记输出,因此不会自动转义。

标记输出值不是字符串,并且不能自动强制转换为字符串,这一点很重要。因此?upper_case?starts_with等将给它们一个错误。您也将无法将它们传递给String参数的 Java 方法。但是有时您需要将值后面的标记作为字符串,您可以将其作为markupOutput?markup_string获得。确保您知道自己在做什么。在标记上(而不是在纯文本上)应用字符串操作会导致无效的标记。还有存在意外的两次转义的危险。

<#-- We assume that we have "HTML" output format by default. -->

<#assign markupOutput1="<b>Test</b>"?no_esc>
<#assign markupOutput2="Foo & bar"?esc>

As expected:
${markupOutput1}
${markupOutput2}

Possibly unintended double escaping:
${markupOutput1?markup_string}
${markupOutput2?markup_string}
As expected:
<b>Test</b>
Foo &amp; bar

Possibly unintended double escaping:
&lt;b&gt;Test&lt;/b&gt;
Foo &amp;amp; bar

更多详细信息和棘手的案例

非标记输出格式

如果输出格式未定义转义规则,则称其为非标记格式。此类输出格式的示例是undefined格式和plainText格式。

这些格式不会产生标记输出值,因此当它们是当前格式时,您不能使用?esc?no_esc。您可以使用输出捕获(如<#assign captured>...</#assign>),但是结果值将是字符串,而不是标记输出值。

此外,当当前输出格式为非标记格式时,不允许使用autoesc指令或<#ftl auto_esc=true>

使用当前输出格式不支持的构造将得到parse-time error

插入其他标记的标记输出值

每个标记输出值都有一个关联的output format。当使用${...}(或#{...})插入标记输出值时,必须在插入点将其转换为当前输出格式(如果它们不同)。在撰写本文时(2.3.24),仅当要转换的值是通过转义纯文本创建的时,这种输出格式转换才会成功:

<#-- We assume that we have "HTML" output format by default. -->

<#assign mo1 = "Foo's bar {}"?esc>
HTLM: ${mo1}
XML:  <#outputformat 'XML'>${mo1}</#outputformat>
RTF:  <#outputformat 'RTF'>${mo1}</#outputformat>

<#assign mo2><p>Test</#assign>
HTML: ${mo2}
XML:  <#attempt><#outputformat 'XML'>${mo2}</#outputformat><#recover>Failed</#attempt>
RTF:  <#attempt><#outputformat 'RTF'>${mo2}</#outputformat><#recover>Failed</#attempt>
HTLM: Foo&#39;s bar {}
XML:  Foo&apos;s bar {}
RTF:  Foo's bar \{\}

HTML: <p>Test
XML:  Failed
RTF:  Failed

但是,输出格式也可以选择按原样插入其他输出格式,而不进行转换。在标准输出格式中,undefined类似于这样,它是用于模板的输出格式,在配置中未为其指定输出格式:

<#-- We assume that we have "undefined" output format here. -->

<#outputformat "HTML"><#assign htmlMO><p>Test</#assign></#outputformat>
<#outputformat "XML"><#assign xmlMO><p>Test</p></#assign></#outputformat>
<#outputformat "RTF"><#assign rtfMO>\par Test</#assign></#outputformat>
HTML: ${htmlMO}
XML:  ${xmlMO}
RTF:  ${rtfMO}
HTML: <p>Test
XML:  <p>Test</p>
RTF:  \par Test

标记输出值和“”运算符

如您所知,如果+运算符的一面是字符串,则它确实可以串联。如果一侧有标记输出值,则通过转义其字符串值,将另一侧提升为具有相同输出格式的标记输出值(如果尚未输出),最后将两个标记连接起来以形成新的标记输出值。例:

<#-- We assume that we have "HTML" output format by default. -->
${"<h1>"?no_esc + "Foo & bar" + "</h1>"?no_esc}
<h1>Foo &amp; bar</h1>

如果+运算符的两侧是不同输出格式的标记值,则右侧操作数将转换为左侧的输出格式。如果这不可能,则将左侧操作数转换为右侧的输出格式。如果这也不可能,那就是错误。 (请参见这里的转换限制。)

${...}内部字符串 Literals

当在字符串* expressions *中使用${...}时(例如,在<#assign s = "Hello ${name}!">中),这只是使用+运算符(<#assign s = "Hello" + name + "!">)的简写。因此,字符串表达式中的${...} -s 不会自动转义,但是,当最终连接的字符串稍后打印时,它可能会自动转义。

<#-- We assume that we have "HTML" output format by default. -->
<#assign name = "Foo & Bar">

<#assign s = "<p>Hello ${name}!">
${s}
<p>Hello ${name}!

To prove that s didn't contain the value in escaped form:
${s?replace('&'), 'and'}
&lt;p&gt;Hello Foo &amp; Bar!
<p>Hello Foo &amp; Bar!

To prove that "s" didn't contain the value in escaped form:
&lt;p&gt;Hello Foo and Bar!

合并的输出格式

组合输出格式是通过将其他输出格式相互嵌套而临时创建的输出格式,因此可以使用两种输出格式的转义。 他们在这里讨论...

上一章 首页 下一章