9.12. Accessing XML Documents from a Templating Engine

Problem

You need to reference XML nodes from a template.

Solution

Use FreeMarker and parse an XML document with the NodeModel class. A NodeModel is an object that allows access to an XML document as a hierarchy of named elements and attributes from a FreeMarker template. NodeModel has a public static method parse( ), which parses an XML document and returns a NodeModel to be added to your context Map. The following code parses an XML document and passes a NodeModel to a template:

import freemarker.template.Configuration;
import freemarker.cache.ClassTemplateLoader;
import freemarker.template.ObjectWrapper;
import freemarker.template.Template;
import freemarker.ext.dom.NodeModel;

// Create a File Object for our XML data
File composers = new File("composers.xml");
NodeModel nodeModel = NodeModel.parse( composers );

Map root = new HashMap( );
root.put("doc", nodeModel); 

// A template is processed with a Map and output is sent to a Writer.
Template template = cfg.getTemplate("composerTable.ftl");
template.process(root, writer);
System.out.println("output: 
" + writer.toString( ));

A File object refers to an XML document, and NodeModel.parse( ) is used to parse this document to a NodeModel object, which is then placed in the root Map—the context with which the FreeMarker template will be merged. The XML document contains information about the lives of great classical composers, and the structure of this document is shown here:

<?xml version="1.0"?>

<composers>
  <composer>
    <name>Bach, Johann Sebastian</name>
    <born date="3/21/1685">
      <location>Eisenbach</location>
    </born>
    <notes>Bach wrote intense and complex fugues.</notes>
    <link>http://www.bachfaq.org/</link>
  </composer>
  <composer>
    <name>Mozart, Wolfgang Amadeus</name>
    <born date="1/27/1756">
      <location>Salzburg</location>
    </born>
    <notes>Wrote first symphony at age 8.</notes>
    <link>http://www.mozartproject.org/</link>
  </composer>
  <composer>
    <name>Hendrix, Jimi</name>
    <born date="11/27/1942">
      <location>Seattle</location>
    </born>
    <notes>Hendrix set his guitar on fire in Monterey</notes>
    <link>http://www.jimihendrix.com/</link>
  </composer>
</composers>

The NodeModel object is exposed to the template as doc, and the #list directive is used to iterate through each composer element. A reference to a child element link of the composer element is ${composer.link}, and a reference to the date attribute of the born element is preceded by @--${composer.born.@date}. The FreeMarker template, which references elements and attributes through a NodeModel, is:

<#list doc.composers.composer as composer>
  <p>
    <a href="${composer.link}">${composer.name}</a><br/>
    Born on ${composer.born.@date} in ${composer.born.location}<br/>
    Notes: ${composer.notes}
  </p>
</#list>

Discussion

In addition to simple access to elements and attributes, FreeMarker also allows you to use XPath expressions if Apache Xalan is available on the classpath. If you have Xalan, you can use XPath with the same syntax you would use if you were trying to access a map. Instead of someMap["key"], you would use someElement["<XPath>"]. Here is a quick example, which uses an XPath expression to iterate through every composer’s “born” element:

<#list doc["composers/composer/born"] as birth>
  <p>Born: ${birth.date}, ${birth.location}  ${birth?parent.name}</p> 
</#list>

FreeMarker also includes a number of built-ins for NodeModel objects; in the previous template, ?parent returns the parent element of the element represented by the birth node. Table 9-4 lists a number of built-ins for XML nodes; ?children returns all of the child nodes of a given node, and ?ancestors gives every node above this node in an XML document.

Table 9-4. FreeMarker built-ins for NodeModel objects

Expression

Evaluates to

${composers?children}

A sequence of all child nodes. This example would return 3 composer nodes.

${composer?parent}

If called on a composer node, this would return the composers node.

${composer?root}

This would return the doc node, which is the topmost node in this document.

${link?ancestors}

If this corresponded to the link element for Jimi Hendrix, this would return a sequence of [${composers.composer[3]}, ${composers}]. This returns an array of all ancestors starting with ${link?parent} and ending at ${link.root}.

${link?node_name}

This would return “link.” This returns the name of the element or attribute in question.

${link?node_type}

This would return “element.” It could return “attribute,” “element,” “text,” “comment,” “entity,” and a few other types corresponding to Node types in the DOM API.

See Also

For more detail about referencing XML elements through NodeModel and the use of XPath expressions in FreeMarker, see the “Learning by Example” section of Imperative XML Processing (http://www.freemarker.org/docs/xgui_imperative_learn.html).

FreeMarker also offers syntax for declarative processing of XML—assigning macros to handle elements in an XML document. For more information about FreeMarker declarative XML processing, see the FreeMarker online documentation (http://www.freemarker.org/docs/xgui_declarative_basics.html).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.60.249