Working with declarative XML processing

With declarative processing, you define how to handle the different types of nodes, and RPL walks the DOM tree and calls the handlers you defined.

This approach is useful for complex XML schemas, where the same element can occur as the child of many other elements. Examples of such schemas are XHTML and XDocBook.

With declarative approach, you use the recurse directive in most cases. This directive gets a node variable as a parameter, and visits all its children nodes, one after the other, starting with the first child. Visiting a node means that the directive calls a user-defined directive (like a macro) that has the same name as the name of the child node (?node_name). Then, that the user-defined directive handles the node which is available as special variable .node. For example, the following code:

<#recurse doc>
 
<#macro book>
  I'm the book element handler, and the title is: ${.node.title}
</#macro>  

produces this output:

I'm the book element handler, and the title is: Test Book  

If you call recurse without a parameter, then it uses .node. This means that it visits all children nodes of the node being handled. For example, the following code:

<#recurse doc>
 
<#macro book>
  Book element with title ${.node.title}
    <#recurse>
  End book
</#macro>
 
<#macro title>
  Title element
</#macro>
 
<#macro chapter>
  Chapter element with title: ${.node.title}
</#macro>  

produces this output:

Book element with title Test Book
Title element
Chapter element with title: Ch1
Chapter element with title: C 
End book  

The examples above show how to define handlers for element nodes, but not for text nodes. Since the name of the handler is the same as the name of nodes it handles, and the name of all text nodes is @text, you define the handler for text nodes as shown in the following example:

<#macro @text>${.node?html}</#macro>  

?html is necessary because you have to HTML-escape the text, since you generate output of HTML format.

The following example shows a template that transforms the XML to complete HTML:

<#recurse doc>
 
<#macro book>
  <html>
    <head>
      <title><#recurse .node.title></title>
    </head>
    <body>
      <h1><#recurse .node.title></h1>
      <#recurse>
    </body>
  </html>
</#macro>
 
<#macro chapter>
  < ><#recurse .node.title></ >
  <#recurse>
</#macro>
 
<#macro para>
  <p><#recurse>
</#macro>
 
<#macro title>
  <#--
    We have handled this element imperatively,
    so we do nothing here.
  -->
</#macro>
 
<#macro @text>${.node?html}</#macro>  

produce this output:

  <html>
    <head>
      <title>Test Book</title>
    </head>
    <body>
      <h1>Test Book</h1>
 
      < >Ch1</ >
   
      <p>p1.1
 
      <p>p1.2
 
      <p>p1.3
  
    < >C </ >
  
      <p>p2.1
 
      <p>p2.2
  
    </body>
  </html>

Note that you can substantially reduce the amount of superfluous whitespace in the output by using the trim directives such as <#t>.

The declarative approach works well with XML schemas where any element might occur anywhere. For example, you need to changes text color to red in any element such as a title or a paragraph. To do this, add a macro that creates an element called mark as shown in the following example:

<#macro mark><font color=red><#recurse></font></#macro>  

Now, you can use <mark>...</mark> anywhere.

For certain XML schemas, declarative XML processing results in shorter and much clearer RPL than imperative XML processing. It's up to you to decide which approach to use; don't forget that you can mix the two approaches. For example, you can use imperative approach in an element handler to process the contents of that element.

About default handlers

For some XML node types, RPL provides a default handler. This handler will handle the node if you do not define a handler for the node (i.e. if there is no user-defined directive available with the name identical to the node name). The following table lists the node types that the default handler supports and describes how it handles each node type:

Node type

Default Handler Action

Text node

Prints the text as it.

Note that in most cases, this approach does not work well, because you should escape the text before sending it to the output (with ?html?xml or ?rtf, etc. depending on the output format).

Processing instruction node

Calls a handler called @pi if you have created such user-defined directive. Otherwise, ignores the node.

Comment node

Document type node

Ignores the node.

Document node

Calls the recurse directive, that is visits all children of the document node.

Element and attribute nodes are handled according to the usual XML-independent mechanism. That is, @node_type will be called as the handler. If @node_type is not defined, then an error terminates template processing. For element nodes, this means that you must define a macro or another kind of user-defined directive called @element to catch all element nodes. If you do not define an @element handler, you must define a handler for all possible elements.

Attribute nodes are not visited by the recurse directive, so you do not need to write handlers for them.

Visiting a single node

You can visit a single node instead of its children with the visit directive.

About XML namespaces

The name of the handler user-defined directive must be the fully qualified name of the element: prefix:elementName. The rules regarding the usage of prefixes is the same as with imperative processing. For example, the user-defined book directive handles only the book element that does not belong to any XML namespace (unless you specified a default XML namespace). If the example, it uses the XML namespace http://example.com/ebook:

<book xmlns="http://example.com/ebook">
...  

The RPL will be:

<#rpl ns_prefixes={"e":"http://example.com/ebook"}>
 
<#recurse doc>
 
<#macro "e:book">
  <html>
    <head>
      <title><#recurse .node["e:title"]></title>
    </head>
    <body>
      <h1><#recurse .node["e:title"]></h1>
      <#recurse>
    </body>
  </html>
</#macro>
 
<#macro "e:chapter">
  < ><#recurse .node["e:title"]></ >
  <#recurse>
</#macro>
 
<#macro "e:para">
  <p><#recurse>
</#macro>
 
<#macro "e:title">
  <#--
    We have handled this element imperatively,
    so we do nothing here.
  -->
</#macro>
 
<#macro @text>${.node?html}</#macro>  

Alternately, you can define a default XML namespace, then use  recurse and the book macro as shown in the following example:

<#rpl ns_prefixes={"D":"http://example.com/ebook"}>
 
<#recurse doc>
 
<#macro book>
...  

In this case, keep in mind that in XPath expressions, you must access the default XML namespace with an explicit D:. This is because in XPath, names without a prefix always refer to nodes with no XML namespace in XPath. Also note that as with imperative XML processing, the name of handlers for elements that have no XML namespace is N:elementName only if a default XML namespace is defined. However, for nodes that are not of type element (such as text nodes), you never use the N prefix in the handler name, because those nodes do not use XML namespaces. For example, the handler for text nodes is always just @text.

For more detailed information, see the recurse and visit directives in Directive Reference.

Next steps

XSL transformation processing