visit, recurse, fallback

概要

<#visit node using namespace><#visit node>
<#recurse node using namespace><#recurse node><#recurse using namespace><#recurse>
<#fallback>

这里:

  • node: 算作 结点变量 的表达式。
  • namespace命名空间,或者是命名空间的序列。 命名空间可以以命名空间哈希表(又称为根哈希表)给定, 或者可以引入一个存储模板路径的字符串。代替命名空间哈希表, 你也可以使用普通哈希表。

描述

visitrecurse 指令是用来递归处理树的。在实践中,这通常被用来 处理XML

Visit

当你调用了 <#visit node>时, 它看上去像用户自定义指令(比如宏)来调用从结点名称 (node?node_name) 和命名空间 (node?node_namesoace) 中有名称扣除的结点。名称扣除的规则:

  • 如果结点不支持结点命名空间(如XML中的文本结点), 那么这个指令名仅仅是结点的名称 (node?node_name)。 如果 getNodeNamespace 方法返回 null 时结点就不支持结点命名空间了。

  • 如果结点支持结点命名空间(如XML中的元素结点), 那么从结点命名空间中的前缀扣除可能在结点名称前和一个做为分隔符 (比如 e:book)的冒号追加上去。前缀,以及是否使用前缀, 依赖于何种前缀 FTL命名空间 中用 ftl 指令的 ns_prefixes 参数注册的, 那里 visit 寻找控制器指令 (visit 调用的相同FTL命名空间不是重要的,后面你将会看到)。 具体来说,如果没有用 ns_prefixes 注册默认的命名空间, 那么对于不属于任何命名空间(getNodeNamespace 返回 "")的结点来说就不使用前缀。 如果使用 ns_prefixes 给不属于任意命名空间的结点注册了默认命名空间, 那么就使用前缀 N,而对于属于默认结点命名空间的结点就不使用前缀了。 否则,这两种情况下,用 ns_prefixes 关联结点命名空间的前缀已经被使用了。 如果没有关联结点命名空间的结点前缀,那么 visit 仅仅就好像没有以合适的名称发现指令。

自定义指令调用的结点对于特殊变量 .node 是可用的。比如:

<#-- Assume that nodeWithNameX?node_name is "x" -->
<#visit nodeWithNameX>
Done.
<#macro x>
   Now I'm handling a node that has the name "x".
   Just to show how to access this node: this node has ${.node?children?size} children.
</#macro>

将会输出:

   Now I'm handling a node that has the name "x".
   Just to show how to access this node: this node has 3 children.
Done.

如果使用可选的 using 从句来指定一个或多个命名空间, 那么 visit 就会在那么命名空间中寻找指令, 和先前列表中指定的命名空间都获得优先级。如果指定 using 从句, 对最后一个未完成的 visit 调用的用 using 从句指定命名空间的命名空间或序列被重用了。如果没有这样挂起的 visit 调用,那么当前的命名空间就被使用。 比如,如果你执行这个模板:

<#import "n1.ftl" as n1>
<#import "n2.ftl" as n2>

<#-- This will call n2.x (because there is no n1.x): -->
<#visit nodeWithNameX using [n1, n2]>

<#-- This will call the x of the current namespace: -->
<#visit nodeWithNameX>

<#macro x>
  Simply x
</#macro>

这是 n1.ftl

<#macro y>
  n1.y
</#macro>

这是 n2.ftl

<#macro x>
  n2.x
  <#-- This callc n1.y as it inherits the "using [n1, n2]" from the pending visit call: -->
  <#visit nodeWithNameY>
  <#-- This will call n2.y: -->
  <#visit nodeWithNameY using .namespace>
</#macro>

<#macro y>
  n2.y
</#macro>

将会输出:

  n2.x
  n1.y
  n2.y

  Simply x
 

如果 visit 既没有在和之前描述规则的名称扣除相同名字的FTL命名空间发现自定义指令, 那么它会尝试用名称 @node_type 查找, 又如果结点不支持结点类型属性 (也就是 node?node_type 返回未定义变量), 那么使用名称 @default。对于查找来说,它使用和之前描述相同的机制。 如果仍然没有找到处理结点的自定义指令,那么 visit 停止模板执行, 并抛出错误。一些XML特定的结点类型在这方面有特殊的处理; 参考:XML处理指南/声明的XML处理/具体细节。比如:

<#-- Assume that nodeWithNameX?node_name is "x" -->
<#visit nodeWithNameX>

<#-- Assume that nodeWithNameY?node_type is "foo" -->
<#visit nodeWithNameY>

<#macro x>
Handling node x
</#macro>

<#macro @foo>
There was no specific handler for node ${node?node_name}
</#macro>

将会输出:

Handling node x
  
There was no specific handler for node y

 

Recurse

<#recurse> 指令是真正纯语义上的指令。 它访问结点的所有子结点(而没有结点本身)。所以来写:

<#recurse someNode using someLib>

和这个是相等的:

<#list someNode?children as child><#visit child using someLib></#list>

而目标结点在 recurse 指令中是可选的。 如果目标结点没有指定,那就仅仅使用 .node。 因此,<#recurse> 这个精炼的指令和下面这个是相同的:

<#list .node?children as child><#visit child></#list>

对于熟悉XSLT的用户的评论,<#recurse> 是和XSLT中 <xsl:apply-templates/> 指令相当类似的。

Fallback

正如前面所学的,在 visit 指令的文档中, 自定义指令控制的结点也许在多个FTL命名空间中被搜索。 fallback 指令可以被用在自定义指令中被调用处理结点。 它指挥 FreeMarker 在更多的命名空间 (也就是,在当前调用列表中自定义指令命名空间之后的命名空间) 中来继续搜索自定义指令。如果结点处理器被发现, 那么就被调用,否则 fallback 不会做任何事情。

这个指令的典型用法是在处理程序库之上写定制层,有时传递控制到定制的库中:

<#import "/lib/docbook.ftl" as docbook>

<#--
  We use the docbook library, but we override some handlers
  in this namespace.
-->
<#visit document using [.namespace, docbook]>

<#--
  Override the "programlisting" handler, but only in the case if
  its "role" attribute is "java"
-->
<#macro programlisting>
  <#if .node.@role[0]!"" == "java">
    <#-- Do something special here... -->
    ...
  <#else>
    <#-- Just use the original (overidden) handler -->
    <#fallback>
  </#if>
</#macro>