We will now use an XSLT stylesheet (shown in Example 5.9) to produce the compact form of an XML file. The output of this transformation will only be a stream of plain characters without any tags. This shows that XSLT can be used to transform XML input into a plain text file.
Example 5.8. Text compaction of the cellar book of Example 2.2 produced by the stylesheet of Example 5.9.
Some lines and parts of lines indicated by ...
have been omitted here.
cellar-book[@noNamespaceSchemaLocation[CellarBook.xsd] wine-catalog[wine[@name[Prado Rey Roble] @appellation[Ribera-del-duero] @classification[d.o.] @code[C00929026] @format[magnum] properties[color[red] alcoholic-strength[12.5] nature[still]] origin[country[Spain] region[Old Castille] producer[Real Sitio de Ventosilla SA]] price[35.25] year[2002]] wine[...]] owner[name[first[Jude] family[Raisin]] street[1234 rue des Chateaux] city[St-George] province[ON] postal-code[M7W 7S0]] location[street[4587 des Futailles] city[Vallée des crus] province[QC] postal-code[H3C 4J8]] cellar[wine[@code[C00043125] purchaseDate[2005-06-20] quantity[2] comment[bold[Guy Lapalme, Montréal] : should reorder soon]] ... wine[@code[C00929026] purchaseDate[2003-10-15] quantity[1] comment[for bold[big] parties]]]]
The algorithm given Example 5.9 follows the same pattern as the one explained in Section 5.2.3. We recursively follow the structure of the tree and output
a corresponding stream of characters. In our case, only one rule is applied to all element
nodes: we output the name of the element and then output its attributes and children, with
an indentation corresponding to the number of characters in the name of the parent element.
The root node has an indentation of 0. Because we want an output with few blank lines, we
only change line after having written the first attribute or child element. This rule is
implemented with the template starting on line 40‑. The
*
in the match
attribute indicates that this rule applies to
all element nodes not matched by a more specific rule such as the one on line 8‑ that matches only the root node. In this latter rule, we
apply the general rule to all children with
xsl:apply-templates
without any
select
attribute. All templates have a parameter indicating the indentation
given as an xsl:with-param
element and declared in the template with the
xsl:param
element.
Characters are put in the output stream literally by xsl:text
elements. Text
can also be computed with xsl:value-of
elements whose select
attribute is an XPath expression. Conditions can be introduced with an
xsl:if
element whose test
attribute can refer to the current
context; here count(../@*)
counts the number of attributes of the parent and
position()
indicates the rank of the current node among its siblings. With
this information, we can decide if the current line should be ended and insert the
appropriate number of blanks according to the value of the indent
parameter
before outputting the information on this line. After the output of the element name, we
call the templates for all attributes, elements and text nodes of this node (this is
achieved with the select
attribute on line 47‑). In our case, we also update the current
indentation that will be given to all these nodes. The output of all these recursive
template applications will be enclosed in a pair of square brackets.
Because all characters and new lines in the stylesheets are returned as they appear in
the input (including \n
and leading and trailing spaces between elements), it
can be difficult to achieve a specific output format. This is not really a problem if the
output of the transformation is HTML, because in this case these spurious spaces and
newlines are removed before being displayed. In our case, the transformation output is given
as is to the user so it is simpler to only output the content of stylesheet elements without
any \n
and without leading and trailing spaces. This is why, on line 5‑, we declare that all elements (
*
) of this
stylesheet should ignore all spaces in the instance file.
On line 22‑, we define the template to output the value of
an attribute, which is simply the name of the attribute preceded by an
@
and followed by its value in square brackets. If the attribute is
not the first one, the line is ended and a new indentation is produced.
On line 32‑, we also check if we need to end the current
line and then we output the value of the current node with all extraneous
space removed using the
normalize-space
function. This removes
all whitespace at the start and at the end of the value and leaves only one space
between non-space characters.
To output a given number of spaces, we have defined an XPath function
called nl-and-indent
(line 16‑) with one parameter that it
used to create a sequence of spaces, which are joined into one string preceded by a newline.
Example 5.9. [compact.xsl
]: Stylesheet used to transform the cellar book
instance document (Example 2.2) into
Example 5.8
1 <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:gl="http://www.iro.umontreal.ca/lapalme" version="2.0"> 5 <xsl:strip-space elements="*"/><xsl:output omit-xml-declaration="yes" method="text"/>
<xsl:template match="/">
<xsl:apply-templates> 10 <xsl:with-param name="indent" select="0"/> </xsl:apply-templates> <xsl:text> </xsl:text> </xsl:template> 15 <xsl:function name="gl:nl-and-indent">
<xsl:param name="nb"/> <xsl:value-of select="concat('
', string-join(for $i in 1 to $nb return ' ',''))"/> 20 </xsl:function> <xsl:template match="@*">
<xsl:param name="indent"/> <xsl:if test="position()>1"> 25 <xsl:value-of select="gl:nl-and-indent($indent)"/> </xsl:if> <xsl:text>@</xsl:text> <xsl:value-of select="local-name()"/> <xsl:text>[</xsl:text><xsl:value-of select="."/><xsl:text>]</xsl:text> 30 </xsl:template> <xsl:template match="text()">
<xsl:param name="indent"/> <xsl:if test="count(../@*)>0 or position()>1"> 35 <xsl:value-of select="gl:nl-and-indent($indent)"/> </xsl:if> <xsl:value-of select="normalize-space(.)"/>
</xsl:template> 40 <xsl:template match="*">
<xsl:param name="indent"/> <xsl:if test="count(../@*)>0 or position()>1"> <xsl:value-of select="gl:nl-and-indent($indent)"/> </xsl:if> 45 <xsl:value-of select="local-name()"/> <xsl:text>[</xsl:text> <xsl:apply-templates select="@*|*|text()">
<xsl:with-param name="indent" select="$indent + string-length(local-name())+1"/> 50 </xsl:apply-templates> <xsl:text>]</xsl:text> </xsl:template> </xsl:stylesheet> 55
|
The XSLT processor will ignore all whitespace nodes from the input so that the output only contains spaces explicitly inserted by the stylesheet. |
|
Indicates that the output will be plain text, thus we do not want an XML declaration to be emitted. |
|
At the root, starts to apply the compaction algorithm with an indentation of 0. Forces a new line
at the end of the output with the content of an |
|
XPath function for outputting a number of spaces given by the |
|
For an attribute, outputs the name of the attribute preceded by
an |
|
For a text node, outputs its normalized value. If there were attributes or if this text node is not the first child, indent the following line. |
|
Normalizes a text value, i.e. remove the surrounding spaces and line breaks. |
|
For an element, outputs the element name followed by the output of the recursive call to the compaction of attributes, children and text nodes are enclosed in square brackets. If there are any attributes and it this element is not the first one, indent the following line. |
|
Updates the indentation for the children nodes with the length of the element name plus one to take into account the open bracket. |