使用Apache Velocity模板递归结构

3

我有一个递归数据结构,基本上是一棵树,其中一个节点可能有子节点等等。我正在尝试生成该结构的类似JSON的文件。为此,考虑使用#parse指令。在上下文中,我存储根节点,并在templateName中存储模板的名称。

{
    "name" = "$node.name",
    "value" = "$node.value"

    #if ($node.childrens.size() > 0)
    ,
    "childrens" = {
        #foreach ($child in $node.childrens)
            ## The next statement does not work
            #parse ($child.type + ".vm", $child)
        #end
    }
    #end
}

Apache Velocity文档指出#parse指令只接受一个参数

在一些例子中,我们可以看到在调用另一个模板之前使用了#set指令,但是如果树的深度超过2,这将不起作用,因为在#set指令中使用的变量存储在相同的上下文中,所以当从深度1到2时,该变量将被覆盖。

使用#parse而不是@Sergiu Dumitriu建议的宏的原因是每个节点可能根据其属性$node.type以不同的方式呈现。我希望针对每种类型都有一个模板,这样添加特定类型的模板的人就不必干扰任何其他模板。我的意思是,也许可以通过type属性上的开关来实现这一点;但这意味着所有呈现方式都将在同一个文件中定义。

是否有办法使用Velocity将模板应用于递归数据结构?


解决方案根据Sergiu Dumitriu的两个答案,最终模板如下:

#macro ( displayNode $node)
{
    #set ( $parseNode = $node )
    #set ( $parseTemplate = $parseNode.type + ".vm" )
    #parse ( $parseTemplate )
    #set ( $parseNode = $node )
    #set ( $parseTemplate = "Common.vm" )
    #parse ( $parseTemplate )
}
#end
Common.vm 的结构如问题中所示。
2个回答

7

不建议使用#parse,因为它是一种比较耗费资源的操作。相反,定义一个宏并递归使用它

#macro(displayNode $node)
{
    "name" = "$node.name",
    "value" = "$node.value"##
    #if ($node.childrens.size() > 0),
    "childrens" = {
        #foreach ($child in $node.children)
            #displayNode($child)
        #end
    }
    #end
}
#end

如果模板名称也是变量,您可以使用#evaluate来动态构建模板名称:
        #set ($d = '$')
        #foreach ($child in $node.children)
            #evaluate("#display${child.type}Node(${d}child)")
        #end

我没有想到这一点,但它可能完成工作。我之所以选择解析是因为每个节点的显示方式可能取决于节点的类型属性。这就是为什么我使用了 #parse。这在问题中并没有明确说明,所以我已经更新以反映这一事实。 - Javier Mr
一个递归宏起作用了,宏参数似乎是局部的宏调用。我还在你提供的另一个答案中使用了 #parse 之前的 #set,所以最终的解决方案是两者的混合。 - Javier Mr

3
为了解决 Velocity 中变量默认是全局的问题,您可以在当前作用域中使用局部变量。Velocity 有几种选项来启用不同的本地范围,包括每次渲染模板时创建的模板范围template.provide.scope.control。问题是默认情况下它是禁用的,所以您必须配置 Velocity 来激活它。激活后,您将自动拥有一个$template变量,可以用于存储局部变量。
## The first thing to do is to copy the $node value into a local scope
## that won't be overwritten by nested calls
#set ($template.node = $node)
{
    "name" = "$template.node.name",
    "value" = "$template.node.value"##
    #if ($template.node.childrens.size() > 0),
    "childrens" = {
        #foreach ($child in $template.node.children)
            ## $template.node doesn't change, so now $node can be freely reassigned
            #set ($node = $child)
            #parse("${child.type}.vm")
        #end
    }
    #end
}

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