XSL transformation processing

We recommend that you use XSL if you are familiar with its concepts and/or when more advanced processing is needed. Otherwise, imperative and declarative models of RPL suffice for most cases.

XSL transformation is used to to transform XML data into either other XML formats, HTML, or text. 

XSL uses XPath as its matching engine, which is another standard in the industry. XSL is, in its most basic form, a matching engine of XML patterns directed by XPath selectors.  For more information on XPath, visit http://www.w3.org/TR/xpath.

XSL basics

XSL is written in XML format with use and support of namespaces. The style of writing XSL is by entering xsl:template elements that match a given criteria (an XML path, a condition for an XML path, etc.). The flow of code is directed by xsl:apply-templates elements placed within templates, in a parent-child manner. This is similar to the declarative XML processing where the xsl:template is equivalent to <#macro…> elements, while xsl:apply-templates is somewhat equivalent to <#recurse>.  XSL is more powerful than the declarative model because it allows recursion in which the path selection is defined in a more specific way.

The XSL language supports xsl:for-each, xsl:choose and xsl:if as programmatic constructs. These constructs resemble the <#list>, <#if> and <#switch> directives.  Note that XSL functionality are somewhat limited.

XSL processing in RPL

You use the xslt method  for XSL processing. The xslt method receives two XML nodes, which you need to previously initialize with parsexml, and perhaps load, as described in the section “XML Terminology. The first parameter is the node of the XML to be transformed. The second parameter is the node of the XSL transformation (which is, by definition, XML as well).

The following example assumes that the node doc has been initialized:

<#assign transform=parsexml(load("cms://contentlibrary/transforms/transform.xml"))>
<#assign result=xslt(doc, transform)>
${result}

The following transform.xml:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text" />
  
  <xsl:template match="/">
    <xsl:value-of select="book/title"/>
    <xsl:apply-templates select="book/chapter" />
  </xsl:template>
  
  <xsl:template match="chapter">[<xsl:value-of select="title"/>]</xsl:template>
  
</xsl:stylesheet>

produces this output:

Test Book[Ch1][C ]

The first template matches the root of the XML. That template first obtains the title of the book, then applies further templates that match the chapters of the book.  Another template that matches the chapter extracts the title of the chapter.

Working with XML output

Sometimes you need to transform from one XML format into another XML format.  Consider the following example:

<#assign transform=parsexml(load("cms://transforms/transform.xml"))>
<#assign result=xslt(source, transform)>
${result}

with the following transform.xml:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
  
  <xsl:template match="/book">
    <publication>
      <xsl:attribute name="name"><xsl:value-of select="title"/></xsl:attribute>
      <index>
      <xsl:apply-templates select="chapter" />
      </index>
    </publication>
  </xsl:template>
  
  <xsl:template match="chapter">
    <section><xsl:value-of select="title"/></section>
    <xsl:apply-templates select="para" />
  </xsl:template>
  
  <xsl:template match="para">
    <section><xsl:value-of select="."/></section>
  </xsl:template>
</xsl:stylesheet>

produces this output:

<publication name="Test Book">
<index>
<section>Ch1</section>
<section>p1.1</section>
<section>p1.2</section>
<section>p1.3</section>
<section>C </section>
<section>p2.1</section>
<section>p2.2</section>
</index>
</publication>

This example:

  1. Matches the book at the root. 

  2. Encloses the output in a <publication> tag with an attribute called name with the contents of the title element. 

  3. Looks for all chapters and places them inside an <index> tag.

  4. Puts a <section> tag, with the contents of the chapter title into the chapters template.

  5. Rather than enclosing the children elements into another tag, the example places them at the same level as the chapter, and thus simply selects the para elements.

  6. In the para template, creates a new <section> tag that encloses the value of the paragraph by obtaining the value of the para element (using the period syntax).

Note that the example RPL outputs the value of the result variable. 

To process this as a node instead, with the familiar RPL syntax, you can modify the above example as follows:

<#assign transform=parsexml(load("cms://transforms/booktransform.xml"))>
<#assign result=parsexml(xslt(source, transform))>
Publication Title: ${result.publication.@name}
<#list result.publication.index.section as sec>
${sec_index+1}. - ${sec}
</#list>

produces this output:

Publication Title: Test Book
1. - Ch1
2. - p1.1
3. - p1.2
4. - p1.3
5. - C 
6. - p2.1
7. - p2.2

This time, the resulting string is parsed XML with the parsexml method.  After this, we are free to use the imperative processing model on the resulting XML. 

Including additional transformation templates

XSL supports an <xsl:include href="path"/> instruction to add extra transformation templates as defined in an external file. For example, the following file called root.xml:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="text"/>
  
  <xsl:include href="cms://transforms/included.xml"/>
  
</xsl:stylesheet>

includes an additional file called included.xml, which contains the following code:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">
  
  <xsl:template match="/book ">[<xsl:value-of select="title"/>]</xsl:template>
  
</xsl:stylesheet>

This is similar to an RPL include directive.  In this mode, the xsl:include instruction allows full paths to cms://, http://, and https:// sources. 

This case supports only full paths, unlike the RPL include directive which supports both relative and absolute paths.