Javier Mr
Javier Mr

Reputation: 2200

Template recursive structure with apache velocity

I have a recursive data structure, basically a tree, where a node may have child nodes and so on. I'm trying to generate a JSON-like file of that structure. For that thought of using the #parse directive. In the context i store the root node and in templateName the template's name.

{
    "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
}

The apache velocity documentation states that the #parse directive only takes one argument.

In examples I have seen the use of the #set directive before calling another template, but if the depth of the tree is higher than 2 this does not work because the variable used in the #set directive is stored in the same context so when going from depth 1 to 2 the variable would be overwritten.

The reason to use #parse instead of a macro as suggested by @Sergiu Dumitriu is because each node may be rendered in a different way depending on it's property $node.type. I would like to have a template for each type so one person adding a template for a particular type does not have to mess with any other template, I mean, maybe this could be achieved having a switch on the type property; but that implies that all ways of rendering will be defined in the same file.

Is there any way of using Velocity to apply templates to recursive data structures?


Solution Based on both answers from Sergiu Dumitriu the final template looks like:

#macro ( displayNode $node)
{
    #set ( $parseNode = $node )
    #set ( $parseTemplate = $parseNode.type + ".vm" )
    #parse ( $parseTemplate )
    #set ( $parseNode = $node )
    #set ( $parseTemplate = "Common.vm" )
    #parse ( $parseTemplate )
}
#end

The Common.vm has the structure shown in the question.

Upvotes: 3

Views: 9918

Answers (2)

Sergiu Dumitriu
Sergiu Dumitriu

Reputation: 11601

To overcome the fact that by default in Velocity variables are global, you could use local variables in the current scope. Velocity has several options for enabling different local scopes, including a template scope created whenever rendering a template, template.provide.scope.control. The problem is that this is disabled by default, so you have to configure Velocity to have it activated. After you activate this, you'll automatically have a $template variable which you can use to store local variables.

## 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
}

Upvotes: 3

Sergiu Dumitriu
Sergiu Dumitriu

Reputation: 11601

You should not use #parse, since it's a somewhat costly operation. Define a macro and use it recursively instead.

#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

If the template name is also variable, you can use #evaluate to construct the template name dynamically:

        #set ($d = '$')
        #foreach ($child in $node.children)
            #evaluate("#display${child.type}Node(${d}child)")
        #end

Upvotes: 7

Related Questions