Velocity is an easy-to-use templating system for the JVM. It’s commonly used to code templates for web pages and email. To use Velocity you pass it a template (a string) and a context, which is a map of Javabeans and collections of Javabeans. The template is coded using the Velocity template language. Here is an example of a template (taken from the Velocity User Guide):
Hello $customer.Name! <table> #foreach( $mud in $mudsOnSpecial ) #if ( $customer.hasPurchased($mud) ) <tr> <td> $flogger.promo( $mud ) </td> </tr> #end #end </table>
What if some of your data is not in Javabean form, but is free form XML (free form meaning you don’t have any control over what the structure is going to be)?
Static languages like Scala and Java are pretty limited for dealing with free form XML. You can parse it into a DOM-like tree or parse it using a SAX-like streaming parser. Then to make the data available to Velocity you would write a Velocity-compatible adapter for the chosen XML API.
Groovy has really nice XML handling capabilities. You can parse XML and then use the results using regular Groovy code, not ugly DOM walking. For example, given this XML:
<records> <car name='HSV Maloo' make='Holden' year='2006> <country>Australia</country> <record type='speed'>Production Pickup Truck with speed of 271kph</record> </car> <car name='P50' make='Peel' year='1962'> <country>Isle of Man</country> <record type='size'>Smallest Street-Legal Car at 99cm wide and 59 kg</record> </car> <car name='Royale' make='Bugatti' year='1931'> <country>France</country> <record type='price'>Most Valuable Car at $15 million</record> </car> </records>
You can parse and use it like this in Groovy:
def records = new XmlSlurper().parseText(xml) def allRecords = records.car assert 3 == allRecords.size() def allNodes = records.depthFirst().collect{ it } assert 10 == allNodes.size() def firstRecord = records.car[0] assert 'car' == firstRecord.name() assert 'Holden' == firstRecord.@make.text() assert 'Australia' == firstRecord.country.text() def carsWith_e_InMake = records.car.findAll{ it.@make.text().contains('e') }
Using the Groovy API you can write a Velocity adapter for free form XML that exposes most of the power of the native Groovy language features. The Groovy API is just another set of classes that you can use in any JVM application. You can use the API without using the Groovy language itself.
Here is the adapter code in Scala. It wraps Groovy GNodes and GNodeLists with objects that are compatible with Velocity:
import groovy.util.{XmlParser, Node => GNode, NodeList => GNodeList} object GNodeWrapper def xmlToGNode(xml: String) = GNodeWrapper(new XmlParser().parseText(xml)) def wrapGNodes(n: Any): AnyRef = n match { case list: GNodeList => GNodeListWrapper(list) case node: GNode => GNodeWrapper(node) case x @ _ => x.asInstanceOf[AnyRef] } } import GNodeWrapper._ case class GNodeWrapper(node: GNode) { def get(key: String) = { val gnode = node.get(key) gnode match { case list: GNodeList if list.size == 1 => val n = list.get(0).asInstanceOf[GNode] if (n.children.size == 1) { n.children.get(0) match { case _: GNode => wrapGNodes(n) case x @ _ => x } } else { wrapGNodes(n) } case x @ _ => wrapGNodes(x) } } override def toString: String = node.text } case class GNodeListWrapper(nodeList: GNodeList) { def get(key: String) = wrapGNodes(nodeList.getAt(key)) def get(index: Int) = wrapGNodes(nodeList.get(index)) def size = nodeList.size def isEmpty = nodeList.isEmpty def iterator = GNodeListIterator(nodeList.iterator) override def toString: String = { if (nodeList.size == 0) "" else nodeList.get(0) match { case node: GNode => node.text case x @ _ => x.toString } } } case class GNodeListIterator(iter: java.util.Iterator[_]) extends java.util.Iterator[AnyRef] { def hasNext = iter.hasNext def next = wrapGNodes(iter.next) def remove() = iter.remove() }
That’s not much code, especially compared to what the Java/DOM equivalent would be.
Using the adapter looks like:
// xml is a string containing the sample XML from above val contextData = Map("title" -> "test title", "content" -> "test content", "meta" -> GNodeWrapper.xmlToGNode(xml)) // Renders template and contextData to stringWriter velocityEngine.evaluate(new VelocityContext(contextData.asJava), stringWriter, "example", template)
So a template that looks like this:
title=$title content=$content country=$meta.records.car[0].country year=$meta.records.car[2].get('@year') numcars=$meta.records.car.size() names=#foreach($c in $meta.records.car)$c.get('@name') #end
Would render like this:
title=test title content=test content country=Australia year=1931 numcars=3 names=HSV Maloo P50 Royale
The same technique could be used to render free form XML in another templating system like JSP.
I have an xml based velocity template,
can i import(include/parse) one similar xml based velocity template(.xml) with in one.
Y imported in X & used?