具体细节

默认处理器

对于一些XML结点类型,有默认的处理器, 这会处理你不给这些结点定义处理器的结点 (也就是说,如果没有可用的,和结点名称相同的用户自定义指令)。 这里的结点类型,默认的处理器会做:

  • 文本结点:打印其中的文本。要注意,在很多应用程序中, 这对你来说并不好,因为你应该在你发送它们到输出 (使用 ?html?xml?rtf 等,这基于输出的格式)前转义这些文本。

  • 处理指令结点:如果你定义了自定义指令,可以通过调用处理器调用 @pi,否则将什么都不做(忽略这些结点)。

  •  注释结点,文档类型结点:什么都不做(忽略这些结点)。

  • 文档结点:调用 recurse,也就是说, 访问文档结点的所有子结点。

元素和属性结点通常将会被XML独立机制处理。也就是, @node_type 将会被调用作为处理器, 如果它没有被定义,那么错误会阻止模板的处理。

元素结点的情形,这意味着如果你定义了一个称为 @element 的宏(或其他种类的用户自定义指令),没有其他特定的处理器时, 那么它会捕捉所有元素结点。如果你没有 @element 处理器, 那么 必须 为所有可能的元素定义处理器。

属性结点在 recurse 指令中不可见, 所以不需要为它们编写处理器。

访问单独结点

使用visit 指令 可以访问单独的结点,而不是结点的子结点: <#visit nodeToVisist>。 有时这会很有用。

XML命名空间

我们说过对于一个元素的处理器,用户自定义指令(比如宏)的名字就是元素的名字。 事实上,它是元素的完全限定名: prefix:elementName。 这个关于 prefix 的使用规则和命令式处理是相同的。 因此,用户自定义指令 book 仅仅处理不属于任何XML命名空间 (除非你已经定义了默认的XML命名空间)的 book 元素。 所以示例XML将会使用XML命名空间 http://example.com/ebook

<book xmlns="http://example.com/ebook">
...

那么FTL就会像这样:

<#ftl ns_prefixes={"e":"http://example.com/ebook"}>

<#recurse doc>

<#macro "e:book">
  <html>
    <head>
      <title><#recurse .node["e:title"]></title>
    </head>
    <body>
      <h1><#recurse .node["e:title"]></h1>
      <#recurse>
    </body>
  </html>
</#macro>

<#macro "e:chapter">
  <h2><#recurse .node["e:title"]></h2>
  <#recurse>
</#macro>

<#macro "e:para">
  <p><#recurse>
</#macro>

<#macro "e:title">
  <#--
    We have handled this element imperatively,
    so we do nothing here.
  -->
</#macro>

<#macro @text>${.node?html}</#macro>

或者你可以定义一个默认的XML命名空间, 那后面部分的模板保持和源XML命名空间相同,比如:

<#ftl ns_prefixes={"D":"http://example.com/ebook"}>

<#recurse doc>

<#macro book>
...

但是这种情形下不要忘了在XPath表达式(我们在默认中没有使用)中, 默认的XML命名空间必须通过明确的 D: 来访问, 因为在XPath中没有前缀的名称通常指代没有XML命名空间的结点。 而且注意到命令式的XML处理也是相同的逻辑,如果(当且仅当)没有默认XML命名空间时, 元素处理器的名字没有XML命名空间是 N:elementName。 然而,对于不是元素类型的结点(比如文本结点),你不能在处理器名称中使用前缀 N,因为这些结点在XML命名空间中是没有这些概念的。 所以对于示例,文本结点的处理器通常就是 @text

对于更详细的内容,请阅读 recursevisit 指令的参考文档。