Legacy XML wrapper implementation

Note:

The legacy XML wrapper is deprecated. FreeMarker 2.3 has introduced support for a new XML processing model. To support this, a new XML wrapper package was introduced, freemarker.ext.dom. For new usage, we encourage you to use that. It is documented in the part XML Processing Guide.

The class freemarker.ext.xml.NodeListModel provides a template model for wrapping XML documents represented as node trees. Every node list can contain zero or more XML nodes (documents, elements, texts, processing instructions, comments, entity references, CDATA sections, etc.). The node list implements the following template model interfaces with the following semantics:

TemplateScalarModel

When used as a scalar, the node list will render the XML fragment that represents its contained nodes. This makes it handy for use in XML-to-XML transforming templates.

TemplateCollectionModel

When used as a collection with list directive, it will simply enumerate its nodes. Every node will be returned as a new node list consisting of a single node.

TemplateSequenceModel

When used as a sequence, it will return the i-th node as a new node list consisting of the single requested node. I.e. to return the 3rd <chapter> element of the <book> element, you'd use the following (note indexes are zero-based):

Template
<#assign thirdChapter = xmldoc.book.chapter[2]>

TemplateHashModel

When used as a hash, it is basically used to traverse children. That is, if you have a node list named book that wraps an element node with several chapters, then the book.chapter will yield a node list with all chapter elements of that book element. The at sign is used to refer to attributes: book.@title yields a node list with a single attribute node, that is the title attribute of the book element.

It is important to realize the consequence that, for example, if book has no chapter-s then book.chapter is an empty sequence, so xmldoc.book.chapter?? will not be false, it will be always true! Similarly, xmldoc.book.somethingTotallyNonsense?? will not be false either. To check if there was no children found, use xmldoc.book.chapter?size == 0.

The hash defines several "magic keys" as well. All these keys start with an underscore. The most notable is the _text key which retrieves the text of the node: ${book.@title._text} will render the value of the attribute into the template. Similarly, _name will retrieve the name of the element or attribute. * or _allChildren returns all direct children elements of all elements in the node list, while @* or _allAttributes returns all attributes of the elements in the node list. There are many more such keys; here's a detailed summary of all the hash keys:

Key name Evaluates to
* or _children all direct element children of current nodes (non-recursive). Applicable to element and document nodes.
@* or _attributes all attributes of current nodes. Applicable to elements only.
@attributeName named attributes of current nodes. Applicable to elements, doctypes and processing instructions. On doctypes it supports attributes publicId, systemId and elementName. On processing instructions, it supports attributes target and data, as well as any other attribute name specified in data as name="value" pair. The attribute nodes for doctype and processing instruction are synthetic, and as such have no parent. Note, however that @* does NOT operate on doctypes or processing instructions.
_ancestor all ancestors up to root element (recursive) of current nodes. Applicable to same node types as _parent.
_ancestorOrSelf all ancestors of current nodes plus current nodes. Applicable to same node types as _parent.
_content the complete content of current nodes, including children elements, text, entity references, and processing instructions (non-recursive). Applicable to elements and documents.
_descendant all recursive descendant element children of current nodes. Applicable to document and element nodes.
_descendantOrSelf all recursive descendant element children of current nodes plus current nodes. Applicable to document and element nodes.
_document all documents the current nodes belong to. Applicable to all nodes except text.
_doctype doctypes of the current nodes. Applicable to document nodes only.
_filterType is a filter-by-type template method model. When called, it will yield a node list that contains only those current nodes whose type matches one of types passed as argument. You should pass arbitrary number of strings to this method containing the names of types to keep. Valid type names are: "attribute", "cdata", "comment", "document", "documentType", "element", "entity", "entityReference", "processingInstruction", "text".
_name the names of current nodes, one string per node (non-recursive). Applicable to elements and attributes (returns their local names), entities, processing instructions (returns its target), doctypes (returns its public ID)
_nsprefix the namespace prefixes of current nodes, one string per node (non-recursive). Applicable to elements and attributes
_nsuri the namespace URIs of current nodes, one string per node (non-recursive). Applicable to elements and attributes
_parent parent elements of current nodes. Applicable to element, attribute, comment, entity, processing instruction.
_qname the qualified names of current nodes in [namespacePrefix:]localName form, one string per node (non-recursive). Applicable to elements and attributes
_registerNamespace(prefix, uri) register a XML namespace with the specified prefix and URI for the current node list and all node lists that are derived from the current node list. After registering, you can use the nodelist["prefix:localname"], or nodelist["@prefix:localname"] syntax (or nodelist.prefix\:localname, or nodelist.@prefix\:localname) to reach elements, and attributes whose names are namespace-scoped. Note that the namespace prefix need not match the actual prefix used by the XML document itself since namespaces are compared solely by their URI. Also note that if you do doc.elem1._registerNamespace(...), and then later you use doc.elem1 again, it will not have the prefix registered, because each time you use doc.elem1, it gives a completely new object. In this example, you certainly should have used doc._registerNamespace(...).
_text the text of current nodes, one string per node (non-recursive). Applicable to elements, attributes, comments, processing instructions (returns its data) and CDATA sections. The reserved XML characters ('<' and '&') are not escaped.
_type Returns a node list containing one string per node describing the type of the node. Possible node type names are: "attribute", "cdata", "comment", "document", "documentType", "element", "entity", "entityReference", "processingInstruction", "text". If the type of the node is unknown, returns "unknown".
_unique a copy of the current nodes that keeps only the first occurrence of every node, eliminating duplicates. Duplicates can occur in the node list by applying uptree-traversals _parent, _ancestor, _ancestorOrSelf, and _document. I.e. foo._children._parent will return a node list that has duplicates of nodes in foo - each node will have the number of occurrences equal to the number of its children. In these cases, use foo._children._parent._unique to eliminate duplicates. Applicable to all node types.
any other key element children of current nodes with name matching the key. This allows for convenience child traversal in book.chapter.title style syntax. Note that nodeset.childname is technically equivalent to nodeset("childname"), but is both shorter to write and evaluates faster. Applicable to document and element nodes.

TemplateMethodModel

When used as a method model, it returns a node list that is the result of evaluating an XPath expression on the current contents of the node list. For this feature to work, you must have the Jaxen library in your classpath. For example:

Template
<#assign firstChapter=xmldoc("//chapter[first()]")>

Namespace handling

For purposes of traversal of children elements that have namespace-scoped names, you can register namespace prefixes with the node list. You can do it either in Java, calling the

public void registerNamespace(String prefix, String uri);

method, or inside a template using the

Template
${nodelist._registerNamespace(prefix, uri)}

syntax. From there on, you can refer to children elements in the namespace denoted by the particular URI through the syntax

nodelist["prefix:localName"]

and

nodelist["@prefix:localName"]

as well as use these namespace prefixes in XPath expressions. Namespaces registered with a node list are propagated to all node lists that are derived from the original node list. Note also that namespaces are matched by their URI only, so you can safely use a prefix for a namespace inside your template that differs from the prefix in the actual XML document - a prefix is just a local alias for the URI both in the template and in the XML document.