Search code examples
javatemplatesdomforeachvelocity

Velocity #foreach with XmlTool node list with text nodes


I'm trying to write a Velocity template that makes use of a DOM object. In particular my dependencies are Velocity 1.7 and Velocity-tools 2.0.

Following the documentation, this is how I pass the DOM to the template engine:

XmlTool tool = new XmlTool().parse(xmlString);
VelocityContext context = new VelocityContext();
context.put("xml", tool);
...

Let's say that this is my original XML document:

<root>
    <foo>
        <bar>
            <baz>10</baz>
            <qux>NO</qux>
        </bar>
        <bar>
            <baz>20</baz>
            <qux>YES</qux>
        </bar>
        <bar>
            <baz>30</baz>
            <qux>NO</qux>
        </bar>
    </foo>
</root>

And this my template file:

#if($xml.foo)
<document>
    #foreach($bar in $xml.foo)
    <repeatableElement>
        <aaa>$bar.baz.text</aaa>
        <bbb>$bar.qux.text</bbb>
    </repeatableElement>
    #end
</document>

Now my problem is, if I run this, the output looks like:

<document>
    <repeatableElement>
        <aaa>102030</aaa>
        <bbb>NOYESNO</bbb>
    </repeatableElement>
    <repeatableElement>
        <aaa>102030</aaa>
        <bbb>NOYESNO</bbb>
    </repeatableElement>
    <repeatableElement>
        <aaa>102030</aaa>
        <bbb>NOYESNO</bbb>
    </repeatableElement>
</document>

As you can see, the #foreach loop correctly prints one repeatableElement for each $bar object. However the .text method on $bar children concatenates the text nodes of the siblings too!
What I want instead is to access each leaf text node alone:

<repeatableElement>
    <aaa>10</aaa>
    <bbb>NO</bbb>
</repeatableElement>
<repeatableElement>
    <aaa>20</aaa>
    <bbb>YES</bbb>
</repeatableElement>
...

Any tip is appreciated. Thanks!


Solution

  • This is a known problem of the Tools 2.0 XmlTool, whose getters call the JDOM method getPath() instead of getUniquePath(). So $bar.baz returns all nodes.

    To circumvent this problem, you can directly use the underlying JDOM API:

    #if($xml.foo)
    <document>
        #foreach($bar in $xml.foo.children())
        <repeatableElement>
            <aaa>$bar.node().element('baz').text</aaa>
            <bbb>$bar.node().element('qux').text</bbb>
        </repeatableElement>
        #end
    </document>
    #end
    

    (Also note that you should loop on $xml.foo.children()).