XSLT: Difference between revisions

From miki
Jump to navigation Jump to search
 
(66 intermediate revisions by the same user not shown)
Line 9: Line 9:
;exslt (extensions)
;exslt (extensions)
* http://exslt.org/
* http://exslt.org/
:Extensions supported in libxml (<code>xsltproc</code> processor)
:Extensions supported in libxml (<code>xsltproc</code> processor), including [http://exslt.org/str/index.html<code>str:foo</code>] functions.

;XPath
* https://www.w3.org/TR/xpath/ &mdash; Reference specification, which has several xpath examples at the top.
* https://developer.mozilla.org/en-US/docs/Web/XPath/Functions &dash; XPath functions (<code>translate</code>...).

;FAQ
* [http://www.dpawson.co.uk/xsl/sect2/sect21.html XSLT Questions and Answers - FAQ] (a wealth of information)
: [http://www.dpawson.co.uk/xsl/sect2/nono.html Things XSLT can't do]
* [http://lenzconsulting.com/how-xslt-works/#priority How XSLT works - priority] &mdash; this discuss the priority of selection where several template rules match. The whole page however is worth reading!


== Overview ==
== Overview ==
Line 86: Line 95:
</source>
</source>


When applied to the example XML file above (with <code>xsltproc minimal.xsl example.xml</code>), the minimal stylesheet produces this result:
We use <code>xsltproc</code> to apply the minimal stylesheet on our example:

<source lang="bash">
xsltproc minimal.xsl example.xml
</source>

We get the following result:
<source lang=xml>
<source lang=xml>
<?xml version="1.0"?>
<?xml version="1.0"?>
Line 163: Line 178:
<xsl:param name="filename" />
<xsl:param name="filename" />
<func:result>
<func:result>
<xsl:for-each select="exsl:node-set(str:tokenize(.,'/'))">
<xsl:for-each select="exsl:node-set(str:tokenize($filename,'/'))">
<xsl:if test="(position()!=1) and (position()!=last())">/</xsl:if>
<xsl:if test="(position()!=1) and (position()!=last())">/</xsl:if>
<xsl:if test="position()!=last()"><xsl:value-of select="."/></xsl:if>
<xsl:if test="position()!=last()"><xsl:value-of select="."/></xsl:if>
Line 176: Line 191:
</source>
</source>
|valign=top|
|valign=top|
;Input
<source lang=xml>
<source lang=xml>
<?xml version="1.0"?>
<?xml version="1.0"?>
Line 184: Line 200:
<file></file>
<file></file>
</root>
</root>
</source>|}
</source>
|}
We get the following result (<code>xsltproc style.xsl data.xml</code>):
We get the following result (<code>xsltproc style.xsl data.xml</code>):
<source lang=xml>
<source lang=xml>
Line 195: Line 212:


</source>
</source>

Some remarks:
* <code>extension-element-prefixes="func"</code> is mandatory, or xsltproc will complain with meaning less errors like:
{http://myserver.org/xsl/functions}dirname: called with too many arguments
xsltApplySequenceConstructor: param was not compiled
xsltApplySequenceConstructor: for-each was not compiled

=== Types ===
==== Boolean ====
Boolean are constructed with expression <code>false()</code> and <code>true()</code> [http://stackoverflow.com/questions/346226/how-to-create-a-boolean-value].

<source lang=xslt>
<xsl:variable name="var_false" select="false()"/>
<xsl:variable name="var_true" select="true()"/>
</source>

which is same as

<source lang=xslt>
<xsl:variable name="var_false" select="1 = 0"/>
<xsl:variable name="var_true" select="'true' = 'true'"/>
</source>

==== String ====
;Test for empty
<source lang=xslt>
<!-- Test if a string in variable 'var' is empty -->
<xsl:variable name="is_var_empty" select="$var = ''"/>
</source>

;Test for non-empty
<source lang=xslt>
<!-- Test if a string in variable 'var' is not empty -->
<xsl:variable name="is_var_empty" select="$var != ''"/>
</source>

;Use multi-token <tt>str:replace</tt>

Using <code>node-set</code>:
<source lang=xslt>
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" xmlns:str="http://exslt.org/strings" exclude-result-prefixes="exsl str">

<xsl:template match="/">
<xsl:variable name="from"><a>a</a><a>b</a></xsl:variable>
<xsl:variable name="to"><a>A</a><a>B</a></xsl:variable>
<xsl:value-of select="str:replace('abcde',exsl:node-set($from)/a,exsl:node-set($to)/a)"/>
</xsl:template>
</xsl:stylesheet>
</source>
This changes the string <code>abcde</code> into <code>ABcde</code>. Note the special construct <code>exsl:node-set($from)'''/a'''</code>, where the trailing <code>/a</code> is necessary to tell which elements to use.

Another solution is to use <code>str:split</code> to build a node set in expression context:
<source lang=xslt>
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" xmlns:str="http://exslt.org/strings" exclude-result-prefixes="exsl str">

<xsl:template match="/">
<xsl:value-of select="str:replace('abcde',str:split('ab',''),str:split('AB',''))"/>
</xsl:template>
</xsl:stylesheet>
</source>

=== Variables ===
<source lang=xslt>
<!-- Variable can be defined globally-->
<xsl:variable name="my_other_var">some value</xsl:variable>

<xsl:template match="/">
<!-- ...or in a template -->
<xsl:variable name="my_var" select="sometag/@someattr"/>
<xsl:value-of select="$my_var"/>
<xsl:value-of select="$my_other_var"/>
</xsl:template>
</source>

=== Result Tree Fragment vs Node Tree ===
These are XSLT 1.0 concepts [http://www.dpawson.co.uk/xsl/sect2/identity.html]:
;Node Tree
:These are built using something like
<source lang=xslt>
<xsl:variable name="n" select="---some-path-expression" />
</source>
:Result is a set of nodes, actually a set of '''references''' to nodes. These are original nodes in the source document, and they retain their original position in the source document, which means for example that you can process each node in the set to ask how many ancestors it has.

;Result Tree Fragment
:Result Tree Fragment (RTF) are called ''temporary tree'' in XSLT 2.0. These are built using something like
<source lang=xslt>
<xsl:variable name="n" />
---some-instruction---
</xsl:variable>
</source>
:The result is a new document. The term "fragment" comes from DOM, and means '''a document that isn't constrained to have a single element at the top level'''. The nodes in this tree are newly constructed nodes; they may be copies of nodes in the source tree (or not) but they have separate identity and have lost their relationships to other nodes in the source tree.

=== Expressions and XPath ===
;Test that two element references are the pointing to same element
<source lang=xslt>
<xsl:if test='generate-id(A)=generate-id(B)'> <!-- XPath 1.0 -->
<xsl:if test='A is B'> <!-- XPath 2.0 -->
</source>

;Match all elements <code>y</code> that are not the first element of a parent element <code>c</code>:
<source lang=xslt>
<xsl:template match="y[not(generate-id(.) = generate-id(ancestor::c//y)[1])]" />
</source>

== XSLT Elements ==
Reference: [http://www.w3schools.com/xml/xsl_elementref.asp w3schools - XSLT Elements Reference]

=== <tt><xsl:message></tt> ===
The <xsl:message> element writes a message to the output. This element is primarily used to report errors. It can also be used to log XSLT processing activity [http://www.dpawson.co.uk/xsl/sect2/identity.html].
<source lang=xslt>
<xsl:message terminate="yes|no">
<!-- Content:template -->
</xsl:message>
</source>

=== <tt><xsl:import></tt> ===
Imports the contents of one style sheet into another.
* Has '''lower''' priority than the importing stylesheet
* Must be first child of <code><xsl:stylesheet></code>
<source lang=xslt>
<xsl:import href="URI"/>
</source>

=== <tt><xsl:include></tt> ===
Includes the contents of one style sheet into another.
* Has '''same''' priority than the importing stylesheet
* Must be first child of <code><xsl:stylesheet></code>
<source lang=xslt>
<xsl:include href="URI"/>
</source>

== XPath ==
References:
* https://www.w3schools.com/xml/xpath_intro.asp
* https://www.w3schools.com/xml/xpath_axes.asp (<code>parent</code>, <code>ancestor</code>, <code>child</code>...)

=== Match examples ===
{| class=wikitable
|-
|<code>nodename</code>
|Select all nodes with name ''nodename''
|-
|<code>//nodename</code>
|Select all children or grand-children nodes with name ''nodename''
|-
|<code>nodename[childname]</code>
|Select all nodes with name ''nodename'' that has a child ''childname''
|-
|<code>nodename[@nodeattr]</code>
|Select all nodes ''nodename'' which has an attribute ''nodeattr''.
|-
|<code>nodename[not(@nodeattr)]</code>
|Select all nodes ''nodename'' which doesn't have an attribute ''nodeattr''.
|-
|<code><nowiki>nodename[text() != '']</nowiki></code>
|Select all nodes ''nodename'' that are not empty (ie. have text content).
|-
|<code>nodename[@nodeattr="value"]</code>
|Select all nodes ''nodename'' which has an attribute ''nodeattr'' equal to ''value''.
|-
|<code>parentname/nodename[1]</code>
|Select the first ''nodename'' that is the child of a node ''parentname''.
|-
|<code>parentname/nodename[@nodeattr="value"][1]</code>
|Select the first ''nodename'' that is the child of a node ''parentname'' and that has attribute ''nodeattr'' equal to ''value''.
|-
|<code>nodename[''cond_1''][''cond_2'']</code>
|Match all nodes ''nodename'' that satisfy both conditions.
|-
|
<code>A|B</code>
|Match node A or node B
|-
|<code>/red/*[name() = 'yellow' or name()='orange']/blue</code>
|Match /red/yellow/blue or /red/orange/blue
|-
|<code>//*[book/title='foo']</code>
|Select any node 'book' (from root) that has a child node 'title' containing 'foo'.
|-
|<code>//*[*/title='foo']</code><br/>
<code>//*[title='foo']/parent::*</code><br/>
<code>//*[title='foo']/..</code>
|Select any node that has a child node 'title' containing 'foo'.
|-
|<code>//*[*//title='foo']</code>
|Select any node that has a descendant node 'title' containing 'foo'.
|}

=== Value-of select examples ===
{| class=wikitable
|-
|<code>select="."</code>
|Content of current node
|-
|<code>select="nodename"</code>
|Content of node with name ''nodename''
|-
|<code>select="nodename/@nodeattr"</code>
|Value of attribute ''nodeattr'' in node with name ''nodename''
|}

=== test examples ===
{| class=wikitable
|-
|<code>test="@version"</code>
|Attribute version exists
|-
|<code>test="(@version)"</code>
|Attribute version does NOT exist
|-
|<code>test="@version='101'"</code>
|Attribute version exists and is equal to '101'
|-
|<code>test="position()=1"</code>
|First node in selected nodes (careful of space nodes, make sure to filter nodes first)
|-
|<code>test="position()=last()"</code>
|Last node in selected nodes (careful of space nodes, make sure to filter nodes first)
|-
|<code>test="ancestor::book/@title='foo'"</code>
|has an ancestor node 'book' with an attribute 'title' equal to 'foo'
|}

=== string functions ===
{| class="wikitable"
|-
|<code>starts-with(string1, string2)</code>
|It returns true when first string starts with the second string.
|-
|<code>contains(string1, string2)</code>
|It returns true when the first string contains the second string.
|-
|<code>substring(string, offset, length?)</code>
|It returns a section of the string. The section starts at offset up to the length provided.
|-
|<code>substring-before(string1, string2)</code>
|It returns the part of string1 up before the first occurrence of string2.
|-
|<code>substring-after(string1, string2)</code>
|It returns the part of string1 after the first occurrence of string2.
|-
|<code>string-length(string)</code>
|It returns the length of string in terms of characters.
|-
|<code>normalize-space(string)</code>
|It trims the leading and trailing space from string.
|-
|<code>translate(string1, string2, string3)</code>
|It returns string1 after any matching characters in string2 have been replaced by the characters in string3.
|-
|<code>concat(string1, string2, ...)</code>
|It is used to concatenate all strings.
|-
|<code>format-number(number1, string1, string2)</code>
|It returns a formatted version of number1 after applying string1 as a format string. String2 is an optional locale string.
|}

== Patterns ==
=== Overriding the identity transformation ===
Overriding the identity transformation is one of the most fundamental XSLT design patterns [http://stackoverflow.com/questions/321860/how-to-remove-elements-from-xml-using-xslt-with-stylesheet-and-xsltproc], [http://www.dpawson.co.uk/xsl/sect2/identity.html].

The principle is to start with the identity transformation
<source lang=xslt>
<xsl:template match="node()|@*"> <!-- Whenever you match any node or any attribute -->
<xsl:copy> <!-- Copy the current node --
<xsl:apply-templates select="node()|@*"/> <!-- Including any attributes it has and any child nodes -->
</xsl:copy>
</xsl:template>
</source>

and then override this template with a more specific one that has ''priority'':
<source lang=xslt>
<xsl:template match="Element[@fruit='apple' and @animal='cat']"/>
</source>
Here the template will simply delete any element with attributes <code><... fruit='apple' animal='cat'></code>.

;Variants
There are two variants of the identity template in common use. This version:
<source lang=xslt>
<xsl:template match="*">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</source>
processes all *elements* by copying them, and can be overridden for individual elements.

This version:
<source lang=xslt>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</source>
processes all *nodes* by copying them, and can be overridden for individual elements, attributes, comments, processing instructions, or text nodes.


== Tips ==
== Tips ==
Line 244: Line 560:
</xsl:template>
</xsl:template>
</xsl:transform>
</xsl:transform>
</source>

=== Add an attribute to an existing element ===
We cannot use <code><copy-of></code>, which does not allow to modify an element. Instead we must use <code><copy></code>, which copies only the element name and namespace, and complete as necessary as in code below [http://stackoverflow.com/questions/2972992/xslt-how-to-add-attributes-to-copy-of]:
<source lang=xslt>
<xsl:template match="/root/Algemeen/foto/img">
<xsl:copy>
<xsl:attribute name="width">100</xsl:attribute>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:template>
</source>

=== Use <tt>str:split</tt> or <tt>str:tokenize</tt> to generate node-set in expression context ===
Using <code>str:split</code> and <code>str:tokenize</code> is an effective way to build a node-set in expression context:

<source lang=xslt>
<xsl:template match="/">
<xsl:value-of select="str:replace('abcde',str:split('abcde',''),str:tokenize('one|two,three/four-five','|,/-'))"/>
</xsl:template>
</source>
The example above generates two node-sets to use the multi-token version of <code>str:replace</code>.

=== Sorting elements by value and removing duplicates ===
From [http://stackoverflow.com/questions/5726965/sorting-elements-by-value-and-removing-duplicates stackoverflow.com]:

<source lang=xslt>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="kNewTermByValue" match="newTerm" use="."/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="NewTerms">
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:apply-templates
select="newTerm[count(.|key('kNewTermByValue',.)[1])=1]">
<xsl:sort/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
</source>

=== Replace node with environment variables ===
Say we have an environment variable
<source lang="bash">
export FOO=foo
</source>
and we want to replace element named <code><env-var>FOO</env-var></code>.

Use the following template:
<source lang=xslt>
<xsl:template match="env-var">
<xsl:variable name="env-var-name">
<xsl:value-of select="."/>
</xsl:variable>
<xsl:variable name="env-var-value">
<xsl:value-of select="document('env-var.lst')//EnvVarList/EnvVar[@name=$env-var-name]"/>
</xsl:variable></xsl:template>
<xsl:value-of select="$env-var-value"/>
</source>
Generate {{file|env-var.lst}} with:
<source lang="bash">
env | awk 'BEGIN {print "<?xml version=\"1.0\"?>\n<EnvVarList>"; FS="="} /=/{gsub(/&/,"\\&amp;",$2); gsub(/</,"\\&lt;",$2); gsub(/>/,"\\&gt;",$2); print "\t<EnvVar name=\"" $1 "\">"$2"</EnvVar>"} END {print "</EnvVarList>"}' > env-var.lst
</source>

=== Don't copy namespace attribute with copy-of ===

From [https://stackoverflow.com/questions/19998180/xsl-copy-nodes-without-xmlns StackOverflow]:

<source lang=xslt>
<xsl:template match="*" mode="copy-no-namespaces">
<xsl:element name="{local-name()}">
<xsl:copy-of select="@*"/>
<xsl:apply-templates select="node()" mode="copy-no-namespaces"/>
</xsl:element>
</xsl:template>

<xsl:template match="comment()| processing-instruction()" mode="copy-no-namespaces">
<xsl:copy/>
</xsl:template>
</source>

Replace <code>copy-of</code> with
<source lang="xml">
<xsl:apply-templates select="somenode" mode="copy-no-namespaces"/>
</source>

=== Don't use template when value-of is enough ===

Assuming some source XML with:
<source lang="xml">
<product>MY_PRODUCT</product>
</source>

The complicated XSLT
<source lang="xslt">
PRODUCT:=<xsl:apply-templates select="target/product" mode="product"/>

<xsl:template match="product" mode="product">
<xsl:text/><xsl:value-of select="."/>&cr;<xsl:text/>
&cr;
</xsl:template>
</source>

can be replaced with:
<source lang="xslt">
PRODUCT:=<xsl:value-of select="target/product"/>
</source>

=== Throw an error / exception ===
Using <code><xsl:message terminate="yes"></code> [https://stackoverflow.com/questions/13523750/how-can-i-throw-an-exception-from-xslt]:
<source lang="xml">
<xsl:if test="(your condition)">
<xsl:message terminate="yes">ERROR: Missing attribute XYZ under
<xsl:value-of select="local-name()"/> !</xsl:message>
</xsl:if>
</source>

An alternative is to use XPath function <code>fn:error</code> [https://www.w3.org/TR/xpath-functions/#func-error].

=== Output ANSI Escape Sequences ===
See [https://stackoverflow.com/questions/66304355/how-to-output-ansi-escape-sequences-with-xslt-xsltproc Stack Overflow], maybe somehow answered my question...

=== For-each loop without duplicate (test for node equality) ===
We can use the <code>generate-id</code> tip ([https://www.oreilly.com/library/view/xslt-cookbook/0596003722/ch04s02.html]) to detect whether two nodes are the same.

For instance, say we have a document with a collection of nodes <code><mynode name"somename"></code> that we want to process, but avoiding those with duplicate names, we could do the following.

<source lang="xslt">
<xsl:for-each select="//mynode">
<xsl:variable name="name" select="@name"/>
<xsl:if test="generate-id(.) = generate-id(//mynode[@name=$name][1])">
<!-- process node here -->
</xsl:if>
</xsl:for-each>
</source>

== Example of extension functions ==
Here some handy functions. These requires the extension module <code>http://exslt.org/functions</code>:

<source lang=xslt>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:func="http://exslt.org/functions"
xmlns:exsl="http://exslt.org/common"
xmlns:str="http://exslt.org/strings"
xmlns:efn="https://miki.immie.org/wiki/XSLT"
extension-element-prefixes="func"
exclude-result-prefixes="exsl str func efn">
</source>

=== efn:if ===
<source lang=xslt>
<!-- function: efn:if
| A function to build compact if-then-else expression. Return $then if $test, and $else otherwise.
|
| Example of use:
| <xsl:value-of select="efn:if(@href!='',concat(@href,'/'))" />
| <xsl:value-of select="efn:if(@href!='',concat(@href,'/'),'./')" />
-->
<func:function name="efn:if">
<xsl:param name="test" />
<xsl:param name="then" />
<xsl:param name="else" select="''" />
<func:result>
<xsl:choose>
<xsl:when test="$test"><xsl:value-of select="$then" /></xsl:when>
<xsl:otherwise><xsl:value-of select="$else" /></xsl:otherwise>
</xsl:choose>
</func:result>
</func:function>
</source>

=== efn:escape ===
<source lang=xslt>
<!-- function: efn:escape
| Escape all occurences of '(', ')' and '\' in $content with backslashes ('\').
|
| Example of use:
| <xsl:value-of select="efn:escape(@href)" />
-->
<func:function name="efn:escape">
<xsl:param name="content" />
<func:result>
<xsl:value-of select="str:replace($content,str:split('()\',''),str:split('\(;\);\\',';'))" />
</func:result>
</func:function>
</source>

=== efn:dirname ===
<source lang=xslt>
<!-- function: efn:dirname
| Extract dirname from given filename. Returns '.' if filename has no directory part, except if $dot_if_empty=false().
|
| Example of use:
| <xsl:value-of select="efn:dirname(@filename)" />
| <xsl:value-of select="efn:dirname(@filename,false())" />
-->
<func:function name="efn:dirname">
<xsl:param name="filename" />
<xsl:param name="dot_if_empty" select="true()" />
<xsl:variable name="result">
<xsl:for-each select="exsl:node-set(str:tokenize($filename,'/'))">
<xsl:if test="(position()!=1) and (position()!=last())">/</xsl:if>
<xsl:if test="position()!=last()"><xsl:value-of select="."/></xsl:if>
</xsl:for-each>
</xsl:variable>
<func:result>
<xsl:choose>
<xsl:when test="$dot_if_empty and ($result='')">.</xsl:when>
<xsl:otherwise><xsl:value-of select="$result"/></xsl:otherwise>
</xsl:choose>
</func:result>
</func:function>
</source>
</source>


Line 297: Line 832:
<source lang=xslt>
<source lang=xslt>
<?xml version='1.0' encoding="UTF-8"?>
<?xml version='1.0' encoding="UTF-8"?>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'>

<xsl:output method="text" indent="no" encoding="UTF-8"/>
<xsl:output method="text" indent="no" encoding="UTF-8"/>


Line 543: Line 1,076:
For-each loop with select condition:
For-each loop with select condition:
<xsl:for-each select="target/content[@id='second']">&name;, </xsl:for-each>
<xsl:for-each select="target/content[@id='second']">&name;, </xsl:for-each>
For-each loop with if condition:
<xsl:for-each select="target/*">
<xsl:if test="position() = 1">First dot is </xsl:if>
<xsl:if test="position() &gt; 1">, next dot is </xsl:if>
<xsl:value-of select="."/>
<xsl:if test="position() = last()">.</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:template>


Line 568: Line 1,108:
For-each loop with select condition:
For-each loop with select condition:
double,
double,
For-each loop with if condition:
First dot is content1, next dot is content2, next dot is content3.
</source>
</source>
|}
|}
Line 632: Line 1,174:
</source>
</source>
|}
|}

=== Copying tags with value-of and copy-of ===

{| class=wikitable width=100%
|-
|
{| width=100%
|-
| valign=top width=50% |
;XSLT file
<source lang=xslt>
<?xml version='1.0' encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'>

<xsl:template match="element">
<xsl:text>
----- COPYOF-DOT -----------------------
</xsl:text>
<xsl:apply-templates select="subelement" mode="copyof-dot" />
<xsl:text>
----- COPYOF-STAR ----------------------
</xsl:text>
<xsl:apply-templates select="subelement" mode="copyof-star" />
<xsl:text>
----- COPYOF-NODE ----------------------
</xsl:text>
<xsl:apply-templates select="subelement" mode="copyof-node" />
<xsl:text>
----- COPYOF-DOT -----------------------
</xsl:text>
<xsl:apply-templates select="subelement" mode="valueof-dot" />
<xsl:text>
----- COPYOF-STAR ----------------------
</xsl:text>
<xsl:apply-templates select="subelement" mode="valueof-star" />
<xsl:text>
----- COPYOF-NODE ----------------------
</xsl:text>
<xsl:apply-templates select="subelement" mode="valueof-node" />
</xsl:template>

<xsl:template match="subelement" mode="copyof-dot" ><xsl:copy-of select="." /></xsl:template>
<xsl:template match="subelement" mode="copyof-star"><xsl:copy-of select="*" /></xsl:template>
<xsl:template match="subelement" mode="copyof-node"><xsl:copy-of select="node()" /></xsl:template>
<xsl:template match="subelement" mode="valueof-dot" ><xsl:value-of select="." /></xsl:template>
<xsl:template match="subelement" mode="valueof-star"><xsl:value-of select="*" /></xsl:template>
<xsl:template match="subelement" mode="valueof-node"><xsl:value-of select="node()" /></xsl:template>

</xsl:stylesheet>
</source>
| valign=top |
;Source file
<source lang=xml>
<?xml version="1.0"?>
<element><subelement>Some string with <span class="highlight">highlighted parts</span> and normal parts.</subelement></element>
</source>
|}
;Processed with
:<code>xsltproc -param PARAM "'text'" style.xsl source</code>
;output
<source lang=text>
<?xml version="1.0"?>

----- COPYOF-DOT -----------------------
<subelement>Some string with <span class="highlight">highlighted parts</span> and normal parts.</subelement>
----- COPYOF-STAR ----------------------
<span class="highlight">highlighted parts</span>
----- COPYOF-NODE ----------------------
Some string with <span class="highlight">highlighted parts</span> and normal parts.
----- COPYOF-DOT -----------------------
Some string with highlighted parts and normal parts.
----- COPYOF-STAR ----------------------
highlighted parts
----- COPYOF-NODE ----------------------
Some string with
</source>
|}

See also on [https://stackoverflow.com/questions/6199345/how-to-copy-all-child-nodes-of-any-type-of-a-template-context-element StackOverflow].


=== Template with match rule and dynamic entity ===
=== Template with match rule and dynamic entity ===
Line 962: Line 1,583:


=== Two-stage xml using variables and node-set ===
=== Two-stage xml using variables and node-set ===
References:
* http://www.usingxml.com/Transforms/XslPipelines (see example below)
* http://www.xml.com/pub/a/2003/07/16/nodeset.html (very detailed example).

{| class=wikitable width=100%
{| class=wikitable width=100%
|-
|-
Line 1,061: Line 1,686:
</source>
</source>
|}
|}

=== Example recursive template ===
<source lang=xslt>
<xsl:template name="escape">
<xsl:param name="content"/>
<xsl:if test="string-length($content)>0">
<xsl:choose>
<xsl:when test="starts-with($content,'(')"><xsl:text>\(</xsl:text><xsl:call-template name="escapeForMake"><xsl:with-param name="content" select="substring($content, 2)"/></xsl:call-template></xsl:when>
<xsl:when test="starts-with($content,')')"><xsl:text>\)</xsl:text><xsl:call-template name="escapeForMake"><xsl:with-param name="content" select="substring($content, 2)"/></xsl:call-template></xsl:when>
<xsl:when test="starts-with($content,'\')"><xsl:text>\\</xsl:text><xsl:call-template name="escapeForMake"><xsl:with-param name="content" select="substring($content, 2)"/></xsl:call-template></xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($content,1,1)"/>
<xsl:call-template name="escapeForMake">
<xsl:with-param name="content" select="substring($content, 2)"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
</source>

Call it as follow:
<source lang=xslt>
<xsl:call-template name="escape"><xsl:with-param name="content"> select="@synopsis" /></xsl:call-template>
</source>

Note that the same template could be implemented using string extension:
<source lang=xslt>
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:func="http://exslt.org/functions" xmlns:exsl="http://exslt.org/common" xmlns:str="http://exslt.org/strings" extension-element-prefixes="func" xmlns:my="http://myserver.org/xsl/functions" exclude-result-prefixes="exsl str func my">

<func:function name="my:escape">
<xsl:param name="filename" />
<func:result>
<xsl:value-of select="str:replace(str:replace(str:replace($filename,'\','\\'),'(','\('),')','\)')" />
</func:result>
</func:function>

<xsl:template match="file">
<directory><xsl:value-of select="my:escape()"/></directory>
</xsl:template>
</xsl:stylesheet>
</source>

Or even shorter, using the multi-token version of <code>str:replace</code>:
<source lang=xslt>
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:func="http://exslt.org/functions" xmlns:exsl="http://exslt.org/common" xmlns:str="http://exslt.org/strings" extension-element-prefixes="func" xmlns:my="http://myserver.org/xsl/functions" exclude-result-prefixes="exsl str func my">

<func:function name="my:escape">
<xsl:param name="filename" />
<func:result>
<xsl:value-of select="str:replace($filename,str:split('()\',''),str:split('\(,\),\\',','))" />
</func:result>
</func:function>

<xsl:template match="file">
<directory><xsl:value-of select="my:escape()"/></directory>
</xsl:template>
</xsl:stylesheet>
</source>

=== Example using node-set to compute sum-products ===
From http://www.xml.com/pub/a/2003/07/16/nodeset.html:
{| class=wikitable width=100%
|-
| valign=top width=50%|
;Stylesheet
<source lang=xslt>
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl"
version="1.0">

<xsl:template match="/">
<html>
<head>
<title>Invoice</title>
</head>
<body>
<h1>Invoice</h1>
<!-- Format invoice items as a table -->
<table border="1" style="text-align: center">
<tr>
<th>Description</th>
<th>Quantity</th>
<th>Unit price</th>
<th>Subtotal</th>
</tr>
<xsl:for-each select="invoice/item">
<tr>
<td><xsl:value-of select="description"/></td>
<td><xsl:value-of select="qty"/></td>
<td><xsl:value-of select="unitPrice"/></td>
<td><xsl:value-of select="qty * unitPrice"/></td>
</tr>
</xsl:for-each>
<tr>
<th colspan="3">Total</th>
<th>
<!-- Gather subtotals into variable -->
<xsl:variable name="subTotals">
<xsl:for-each select="invoice/item">
<number>
<xsl:value-of select="qty * unitPrice"/>
</number>
</xsl:for-each>
</xsl:variable>

<!-- Sum subtotals stored as a result tree fragment
in the variable -->
<xsl:value-of
select="sum(exsl:node-set($subTotals)/number)"/>
</th>
</tr>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
</source>
|valign=top|
;source
<source lang=xml>
<?xml version="1.0" encoding="utf-8"?>
<invoice>
<item>
<description>Pilsner Beer</description>
<qty>6</qty>
<unitPrice>1.69</unitPrice>
</item>
<item>
<description>Sausage</description>
<qty>3</qty>
<unitPrice>0.59</unitPrice>
</item>
<item>
<description>Portable Barbecue</description>
<qty>1</qty>
<unitPrice>23.99</unitPrice>
</item>
<item>
<description>Charcoal</description>
<qty>2</qty>
<unitPrice>1.19</unitPrice>
</item>
</invoice>
</source>
|}
In particular, we observe:
* We first construct a set of sub-totals with
<source lang=xslt>
<xsl:variable name="subTotals">
<xsl:for-each select="invoice/item">
<number><xsl:value-of select="qty * unitPrice"/></number>
</xsl:for-each>
</xsl:variable>
</source>
* Then we compute the sum with
<source lang=xslt>
sum(exsl:node-set($subTotals)/number).
</source>
:Note the special construct <code>node-set(...)/number</code>, and not simply <code>node-set(...)</code>.

=== TODO - Variable assign template ===

{| class=wikitable width=100%
|-
|
{| width=100%
|-
| valign=top width=50% |
;XSLT file
<source lang=xslt>
<?xml version='1.0' encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'>
<xsl:template match="template_name">
<xsl:variable name="CHIP">
<xsl:value-of select="."/>
</xsl:variable>
<xsl:choose>
<xsl:when test="starts-with(substring($CHIP, 3, 2), '31')">ARMCM0</xsl:when>
<xsl:otherwise>ARMCM3</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
</source>
| valign=top |
;Source file
<source lang=xml>
<?xml version="1.0"?>
???
</source>
|}
;Processed with
:<code>xsltproc style.xsl source</code>
;output
<source lang=text>
???
</source>
|}

=== Template - test whether an attribute is present ===

Say we have a XML doc with tags like:

<build><command>...</command></build>
<build cmd="..." />

To process the <code>build</code> differently based on the presence of the attribute <code>cmd</code>:

<source lang="xslt">
<xsl:template match="build[@cmd]">
<xsl:value-of select="@cmd" />
</xsl:template>

<xsl:template match="build/command">
<xsl:value-of select="." />
</xsl:template>
</source>

Or using a <code>choose</code> construct:

<source lang="xslt">
<xsl:template match="build">
<xsl:choose>
<xsl:when test="@cmd">
<xsl:value-of select="@cmd" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="command" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</source>


== Miscellaneous ==
== Miscellaneous ==
* <code><xsl:param name="MYPARAM"></code> in <code><xsl:stylesheet></code> node declares a ''global'' parameter.
* <code><xsl:param name="MYPARAM"></code> in <code><xsl:stylesheet></code> node declares a ''global'' parameter.
: After this declaration we can use <code>$MYPARAM</code> to get the value of the parameter. If it is not declared, we can only use <code>$MYPARAM</code> if it is passed as a parameter (e.g. via command line <code>xsltproc -param MYPARAM "'myparamvalu'"</code>), otherwise an error is triggered.
: After this declaration we can use <code>$MYPARAM</code> to get the value of the parameter. If it is not declared, we can only use <code>$MYPARAM</code> if it is passed as a parameter (e.g. via command line <code>xsltproc -param MYPARAM "'myparamvalu'"</code>), otherwise an error is triggered.
* Some string functions:
<source lang="xml">
<xsl:template match="verse">
<xsl:value-of select="."/>
<xsl:value-of select="substring(.,7,6)"/>
<xsl:value-of select="substring(.,12)"/>
<xsl:value-of select="substring-before(.,'dreary')"/>
<xsl:value-of select="substring-after(.,'desolation')"/>
</xsl:template>
</source>

Latest revision as of 14:40, 27 July 2022

XSLT stands for Extensible Stylesheet Language Transformations.

Links

Gnome library
exslt (extensions)
Extensions supported in libxml (xsltproc processor), including str:foo functions.
XPath
FAQ
Things XSLT can't do
  • How XSLT works - priority — this discuss the priority of selection where several template rules match. The whole page however is worth reading!

Overview

Currently there are two versions of the XSLT standards: XSLT 1.0 and XSLT 2.0.

XSLT 1.0
On linux, the standard XSLT 1.0 processor is xsltproc (from package xsltproc, libxml2 library).
In XSLT 1.0, most functions come from XSLT extensions (like http://exslt.org/).


XSLT 2.0
  • On linux, the standard XSLT 2.0 processor is saxonb-xslt (from package libsaxonb-java).
  • XSLT 2.0 introduces XQuery, XPath 2.0, and XSLT functions (with prefix fn:). See here for a detailed list of features.

Extensions

From w3.org [1]:

  • The element extension mechanism allows namespaces to be designated as extension namespaces. When a namespace is designated as an extension namespace and an element with a name from that namespace occurs in a template, then the element is treated as an instruction rather than as a literal result element.

Extensions available in the XSLT processor

Using xsltproc (package xsltproc [2], we can get the list of built-in extensions with:

xsltproc --dumpextensions 
# Registered XSLT Extensions
# --------------------------
# Registered Extension Functions:
# {http://exslt.org/math}atan2
# {http://exslt.org/strings}align
# ...
exslt.org/crypto
  • "Hidden" extensions that provides basic crypto functions (apparently from patch [3], introduced in July 5 2004 [4])
  • See also [5]
  • CAUTION — These extensions are not available if using a custom implementation of Python like Anaconda
xsltproc --dumpextensions|grep crypto
# {http://exslt.org/crypto}rc4_decrypt
# {http://exslt.org/crypto}md4
# {http://exslt.org/crypto}sha1
# {http://exslt.org/crypto}md5
# {http://exslt.org/crypto}rc4_encrypt
Example of use:
<xsl:template match="rootnode">
<xsl:value-of select="crypto:md5('ahahah')"/>
</xsl:template>

Quick reference

Minimal stylesheet

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

</xsl:stylesheet>

This is the minimal stylesheet. Another solution is to use an xsl:transform element, which is a synonym to xsl:stylesheet [6].

<?xml version="1.0"?>
<books>
  <book>
    <title>XML in a Nutshell</title>
    <editor>O'Reilly</editor>
  </book>
  <book>
    <title>XSL in a Nutshell</title>
    <editor>O'Reilly</editor>
  </book>
</book>

We use xsltproc to apply the minimal stylesheet on our example:

xsltproc minimal.xsl example.xml

We get the following result:

<?xml version="1.0"?>

  
    XML in a Nutshell
    O'Reilly
  
  
    XSL in a Nutshell
    O'Reilly

The result is not empty. This is because by default XSLT has a default template for text node that simply copy the content of the node without transform.

xsl:attribute

Input Output
<link site="www.stackoverflow.com"/>
<a href="http://www.stackoverflow.com">Click here</a>

Use

<xsl:template match="link">
    <a>
        <xsl:attribute name="href">
            <xsl:text>http://</xsl:text><xsl:value-of select="@site"/>
        </xsl:attribute>
        <xsl:text>Link</xsl:text>
    </a>
</xsl:template>

Or shorter using curly braces {...} [7]:

<xsl:template match="link">
    <a href="http://{@site}">Click here</a>
</xsl:template>

Also for:

func:functions

References:

Using the exslt:func extension, we can define functions in XSLT 1.0.

Here an example stylesheet:

Stylesheet
<?xml version="1.0"?>
<xsl:stylesheet 
    version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:func="http://exslt.org/functions" 
    xmlns:exsl="http://exslt.org/common" 
    xmlns:str="http://exslt.org/strings" 
    xmlns:my="http://myserver.org/xsl/functions"
    extension-element-prefixes="func"
    exclude-result-prefixes="exsl str func my">

    <func:function name="my:dirname">
        <xsl:param name="filename" />
        <func:result>
            <xsl:for-each select="exsl:node-set(str:tokenize($filename,'/'))">
                <xsl:if test="(position()!=1) and (position()!=last())">/</xsl:if>
                <xsl:if test="position()!=last()"><xsl:value-of select="."/></xsl:if>
            </xsl:for-each>
        </func:result>
    </func:function>

    <xsl:template match="file">
        <directory><xsl:value-of select="my:dirname(.)"/></directory>
    </xsl:template>
</xsl:stylesheet>
Input
<?xml version="1.0"?>
<root>
    <file>your/dir/file.ext</file>
    <file>more/directory/with space/dir/file.ext</file>
    <file>file.ext</file>
    <file></file>
</root>

We get the following result (xsltproc style.xsl data.xml):

<?xml version="1.0"?>

    <directory>your/dir</directory>
    <directory>more/directory/with space/dir</directory>
    <directory/>
    <directory/>

Some remarks:

  • extension-element-prefixes="func" is mandatory, or xsltproc will complain with meaning less errors like:
{http://myserver.org/xsl/functions}dirname: called with too many arguments
xsltApplySequenceConstructor: param was not compiled
xsltApplySequenceConstructor: for-each was not compiled

Types

Boolean

Boolean are constructed with expression false() and true() [8].

<xsl:variable name="var_false" select="false()"/>
<xsl:variable name="var_true" select="true()"/>

which is same as

<xsl:variable name="var_false" select="1 = 0"/>
<xsl:variable name="var_true" select="'true' = 'true'"/>

String

Test for empty
<!-- Test if a string in variable 'var' is empty -->
<xsl:variable name="is_var_empty" select="$var = ''"/>
Test for non-empty
<!-- Test if a string in variable 'var' is not empty -->
<xsl:variable name="is_var_empty" select="$var != ''"/>
Use multi-token str:replace

Using node-set:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" xmlns:str="http://exslt.org/strings" exclude-result-prefixes="exsl str">

    <xsl:template match="/">
        <xsl:variable name="from"><a>a</a><a>b</a></xsl:variable>
        <xsl:variable name="to"><a>A</a><a>B</a></xsl:variable>
        <xsl:value-of select="str:replace('abcde',exsl:node-set($from)/a,exsl:node-set($to)/a)"/>
    </xsl:template>
</xsl:stylesheet>

This changes the string abcde into ABcde. Note the special construct exsl:node-set($from)/a, where the trailing /a is necessary to tell which elements to use.

Another solution is to use str:split to build a node set in expression context:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" xmlns:str="http://exslt.org/strings" exclude-result-prefixes="exsl str">

    <xsl:template match="/">
        <xsl:value-of select="str:replace('abcde',str:split('ab',''),str:split('AB',''))"/>
    </xsl:template>
</xsl:stylesheet>

Variables

<!-- Variable can be defined globally-->
<xsl:variable name="my_other_var">some value</xsl:variable>

<xsl:template match="/">
    <!-- ...or in a template -->
    <xsl:variable name="my_var" select="sometag/@someattr"/>
    <xsl:value-of select="$my_var"/>
    <xsl:value-of select="$my_other_var"/>
</xsl:template>

Result Tree Fragment vs Node Tree

These are XSLT 1.0 concepts [9]:

Node Tree
These are built using something like
<xsl:variable name="n" select="---some-path-expression" />
Result is a set of nodes, actually a set of references to nodes. These are original nodes in the source document, and they retain their original position in the source document, which means for example that you can process each node in the set to ask how many ancestors it has.
Result Tree Fragment
Result Tree Fragment (RTF) are called temporary tree in XSLT 2.0. These are built using something like
<xsl:variable name="n" />
---some-instruction---
</xsl:variable>
The result is a new document. The term "fragment" comes from DOM, and means a document that isn't constrained to have a single element at the top level. The nodes in this tree are newly constructed nodes; they may be copies of nodes in the source tree (or not) but they have separate identity and have lost their relationships to other nodes in the source tree.

Expressions and XPath

Test that two element references are the pointing to same element
<xsl:if test='generate-id(A)=generate-id(B)'>  <!-- XPath 1.0 -->
<xsl:if test='A is B'>                         <!-- XPath 2.0 -->
Match all elements y that are not the first element of a parent element c
<xsl:template match="y[not(generate-id(.) = generate-id(ancestor::c//y)[1])]" />

XSLT Elements

Reference: w3schools - XSLT Elements Reference

<xsl:message>

The <xsl:message> element writes a message to the output. This element is primarily used to report errors. It can also be used to log XSLT processing activity [10].

<xsl:message terminate="yes|no">
<!-- Content:template -->
</xsl:message>

<xsl:import>

Imports the contents of one style sheet into another.

  • Has lower priority than the importing stylesheet
  • Must be first child of <xsl:stylesheet>
<xsl:import href="URI"/>

<xsl:include>

Includes the contents of one style sheet into another.

  • Has same priority than the importing stylesheet
  • Must be first child of <xsl:stylesheet>
<xsl:include href="URI"/>

XPath

References:

Match examples

nodename Select all nodes with name nodename
//nodename Select all children or grand-children nodes with name nodename
nodename[childname] Select all nodes with name nodename that has a child childname
nodename[@nodeattr] Select all nodes nodename which has an attribute nodeattr.
nodename[not(@nodeattr)] Select all nodes nodename which doesn't have an attribute nodeattr.
nodename[text() != ''] Select all nodes nodename that are not empty (ie. have text content).
nodename[@nodeattr="value"] Select all nodes nodename which has an attribute nodeattr equal to value.
parentname/nodename[1] Select the first nodename that is the child of a node parentname.
parentname/nodename[@nodeattr="value"][1] Select the first nodename that is the child of a node parentname and that has attribute nodeattr equal to value.
nodename[cond_1][cond_2] Match all nodes nodename that satisfy both conditions.

A|B

Match node A or node B
/red/*[name() = 'yellow' or name()='orange']/blue Match /red/yellow/blue or /red/orange/blue
//*[book/title='foo'] Select any node 'book' (from root) that has a child node 'title' containing 'foo'.
//*[*/title='foo']

//*[title='foo']/parent::*
//*[title='foo']/..

Select any node that has a child node 'title' containing 'foo'.
//*[*//title='foo'] Select any node that has a descendant node 'title' containing 'foo'.

Value-of select examples

select="." Content of current node
select="nodename" Content of node with name nodename
select="nodename/@nodeattr" Value of attribute nodeattr in node with name nodename

test examples

test="@version" Attribute version exists
test="(@version)" Attribute version does NOT exist
test="@version='101'" Attribute version exists and is equal to '101'
test="position()=1" First node in selected nodes (careful of space nodes, make sure to filter nodes first)
test="position()=last()" Last node in selected nodes (careful of space nodes, make sure to filter nodes first)
test="ancestor::book/@title='foo'" has an ancestor node 'book' with an attribute 'title' equal to 'foo'

string functions

starts-with(string1, string2) It returns true when first string starts with the second string.
contains(string1, string2) It returns true when the first string contains the second string.
substring(string, offset, length?) It returns a section of the string. The section starts at offset up to the length provided.
substring-before(string1, string2) It returns the part of string1 up before the first occurrence of string2.
substring-after(string1, string2) It returns the part of string1 after the first occurrence of string2.
string-length(string) It returns the length of string in terms of characters.
normalize-space(string) It trims the leading and trailing space from string.
translate(string1, string2, string3) It returns string1 after any matching characters in string2 have been replaced by the characters in string3.
concat(string1, string2, ...) It is used to concatenate all strings.
format-number(number1, string1, string2) It returns a formatted version of number1 after applying string1 as a format string. String2 is an optional locale string.

Patterns

Overriding the identity transformation

Overriding the identity transformation is one of the most fundamental XSLT design patterns [11], [12].

The principle is to start with the identity transformation

<xsl:template match="node()|@*">                <!-- Whenever you match any node or any attribute -->
  <xsl:copy>                                    <!-- Copy the current node --
     <xsl:apply-templates select="node()|@*"/>  <!-- Including any attributes it has and any child nodes -->
  </xsl:copy>
</xsl:template>

and then override this template with a more specific one that has priority:

<xsl:template match="Element[@fruit='apple' and @animal='cat']"/>

Here the template will simply delete any element with attributes <... fruit='apple' animal='cat'>.

Variants

There are two variants of the identity template in common use. This version:

<xsl:template match="*">
    <xsl:copy>
        <xsl:copy-of select="@*"/>
        <xsl:apply-templates/>
    </xsl:copy>
</xsl:template>

processes all *elements* by copying them, and can be overridden for individual elements.

This version:

<xsl:template match="node()|@*">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

processes all *nodes* by copying them, and can be overridden for individual elements, attributes, comments, processing instructions, or text nodes.

Tips

Output tags like <?fileVersion 4.0.0 ?>

Use a xsl:text tag with attribute disable-output-escaping="yes": [13]:

<xsl:template match="/">
<xsl:text disable-output-escaping="yes">&lt;?fileVersion 4.0.0?&gt;</xsl:text>
</xsl:template>

This will produce:

<?fileVersion 4.0.0?>

Generate a random UID

<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:data="http://exslt.org/dates-and-times"
    xmlns:math="http://exslt.org/math"
    extension-element-prefixes="date math"
    version='1.0'>
<!-- .... -->
<xsl:variable name="noncels" select="floor(math:random()*800)+100"/>
<xsl:variable name="noncems" select="data:seconds(date:date-time())-date:seconds('1970-01-01T00:00:00')"/>
<!-- .... -->
<xsl:value-of select="concat($noncems,noncels+0)"/>
</xsl:stylesheet>

Beautify / indent an XML file

Use the identity transform: [14]

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="xml"  encoding="UTF-8" indent="yes" />
    <xsl:strip-space elements="*"/>

    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
</xsl:transform>

Add an attribute to an existing element

We cannot use <copy-of>, which does not allow to modify an element. Instead we must use <copy>, which copies only the element name and namespace, and complete as necessary as in code below [15]:

<xsl:template match="/root/Algemeen/foto/img">
    <xsl:copy>
        <xsl:attribute name="width">100</xsl:attribute>
        <xsl:apply-templates select="@*|node()" />
    </xsl:copy>
</xsl:template>

Use str:split or str:tokenize to generate node-set in expression context

Using str:split and str:tokenize is an effective way to build a node-set in expression context:

<xsl:template match="/">
    <xsl:value-of select="str:replace('abcde',str:split('abcde',''),str:tokenize('one|two,three/four-five','|,/-'))"/>
</xsl:template>

The example above generates two node-sets to use the multi-token version of str:replace.

Sorting elements by value and removing duplicates

From stackoverflow.com:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:key name="kNewTermByValue" match="newTerm" use="."/>
    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="NewTerms">
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
            <xsl:apply-templates
             select="newTerm[count(.|key('kNewTermByValue',.)[1])=1]">
                <xsl:sort/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

Replace node with environment variables

Say we have an environment variable

export FOO=foo

and we want to replace element named <env-var>FOO</env-var>.

Use the following template:

<xsl:template match="env-var">
<xsl:variable name="env-var-name">
    <xsl:value-of select="."/>
</xsl:variable>
<xsl:variable name="env-var-value">
    <xsl:value-of select="document('env-var.lst')//EnvVarList/EnvVar[@name=$env-var-name]"/>
</xsl:variable></xsl:template>
<xsl:value-of select="$env-var-value"/>

Generate env-var.lst with:

env | awk 'BEGIN {print "<?xml version=\"1.0\"?>\n<EnvVarList>"; FS="="} /=/{gsub(/&/,"\\&amp;",$2); gsub(/</,"\\&lt;",$2); gsub(/>/,"\\&gt;",$2); print "\t<EnvVar name=\"" $1 "\">"$2"</EnvVar>"} END {print "</EnvVarList>"}' > env-var.lst

Don't copy namespace attribute with copy-of

From StackOverflow:

<xsl:template match="*" mode="copy-no-namespaces">
    <xsl:element name="{local-name()}">
        <xsl:copy-of select="@*"/>
        <xsl:apply-templates select="node()" mode="copy-no-namespaces"/>
    </xsl:element>
</xsl:template>

<xsl:template match="comment()| processing-instruction()" mode="copy-no-namespaces">
    <xsl:copy/>
</xsl:template>

Replace copy-of with

<xsl:apply-templates select="somenode" mode="copy-no-namespaces"/>

Don't use template when value-of is enough

Assuming some source XML with:

<product>MY_PRODUCT</product>

The complicated XSLT

PRODUCT:=<xsl:apply-templates select="target/product" mode="product"/>

<xsl:template match="product" mode="product">
    <xsl:text/><xsl:value-of select="."/>&cr;<xsl:text/>
    &cr;
</xsl:template>

can be replaced with:

PRODUCT:=<xsl:value-of select="target/product"/>

Throw an error / exception

Using <xsl:message terminate="yes"> [16]:

<xsl:if test="(your condition)">
   <xsl:message terminate="yes">ERROR: Missing attribute XYZ under
      <xsl:value-of select="local-name()"/> !</xsl:message>
</xsl:if>

An alternative is to use XPath function fn:error [17].

Output ANSI Escape Sequences

See Stack Overflow, maybe somehow answered my question...

For-each loop without duplicate (test for node equality)

We can use the generate-id tip ([18]) to detect whether two nodes are the same.

For instance, say we have a document with a collection of nodes <mynode name"somename"> that we want to process, but avoiding those with duplicate names, we could do the following.

<xsl:for-each select="//mynode">
    <xsl:variable name="name" select="@name"/>
    <xsl:if test="generate-id(.) = generate-id(//mynode[@name=$name][1])">
        <!-- process node here -->
    </xsl:if>
</xsl:for-each>

Example of extension functions

Here some handy functions. These requires the extension module http://exslt.org/functions:

<xsl:stylesheet 
    version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:func="http://exslt.org/functions" 
    xmlns:exsl="http://exslt.org/common" 
    xmlns:str="http://exslt.org/strings" 
    xmlns:efn="https://miki.immie.org/wiki/XSLT"
    extension-element-prefixes="func"
    exclude-result-prefixes="exsl str func efn">

efn:if

<!-- function: efn:if 
|        A function to build compact if-then-else expression. Return $then if $test, and $else otherwise.
|
|    Example of use: 
|       <xsl:value-of select="efn:if(@href!='',concat(@href,'/'))" />
|       <xsl:value-of select="efn:if(@href!='',concat(@href,'/'),'./')" />
-->
<func:function name="efn:if">
<xsl:param name="test" />
<xsl:param name="then" />
<xsl:param name="else" select="''" />
<func:result>
<xsl:choose>
<xsl:when test="$test"><xsl:value-of select="$then" /></xsl:when>
<xsl:otherwise><xsl:value-of select="$else" /></xsl:otherwise>
</xsl:choose>
</func:result>
</func:function>

efn:escape

<!-- function: efn:escape
|        Escape all occurences of '(', ')' and '\' in $content with backslashes ('\').
|
|    Example of use: 
|       <xsl:value-of select="efn:escape(@href)" />
-->
<func:function name="efn:escape">
<xsl:param name="content" />
<func:result>
<xsl:value-of select="str:replace($content,str:split('()\',''),str:split('\(;\);\\',';'))" />
</func:result>
</func:function>

efn:dirname

<!-- function: efn:dirname 
|        Extract dirname from given filename. Returns '.' if filename has no directory part, except if $dot_if_empty=false().
|
|    Example of use: 
|       <xsl:value-of select="efn:dirname(@filename)" />
|       <xsl:value-of select="efn:dirname(@filename,false())" />
-->
<func:function name="efn:dirname">
    <xsl:param name="filename" />
    <xsl:param name="dot_if_empty" select="true()" />
    <xsl:variable name="result">
        <xsl:for-each select="exsl:node-set(str:tokenize($filename,'/'))">
            <xsl:if test="(position()!=1) and (position()!=last())">/</xsl:if>
            <xsl:if test="position()!=last()"><xsl:value-of select="."/></xsl:if>
        </xsl:for-each>
    </xsl:variable>
    <func:result>
        <xsl:choose>
            <xsl:when test="$dot_if_empty and ($result='')">.</xsl:when>
            <xsl:otherwise><xsl:value-of select="$result"/></xsl:otherwise>
        </xsl:choose>
    </func:result>
</func:function>

Examples

Simple <xsl:template>

XSLT file
<?xml version='1.0' encoding="UTF-8"?>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'>

<xsl:output method="text" indent="no" encoding="UTF-8"/>

<xsl:param name="PARAM"/>

<xsl:template match="project">
<xsl:text/>This template will replace 'project' by a simple <xsl:value-of select="$PARAM"/><xsl:text/>
</xsl:template>

</xsl:stylesheet>
Source file
<?xml version="1.0"?>
<project name="myproject">
        <target name="myname"/>
</project>
Processed with
xsltproc -param PARAM "'text'" style.xsl source
output
This template will replace 'project' by a simple text

Spaces and CR

XSLT file
<?xml version='1.0' encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'>
<xsl:output method="text" indent="no" encoding="UTF-8"/>

<xsl:param name="WORLD"/>

<xsl:template match="project">
    <xsl:text>CR/spaces are preserved...
    inside 'xsl:text'.</xsl:text>

<xsl:text> But spaces/CR between two tags are deleted.</xsl:text>
Except if ...
    it contains non-blanks.
    <xsl:text/>So better use empty text tags<xsl:text/>
    <xsl:text/> to eat spaces/CR, while still using
    ... xslt <xsl:value-of select="$PARAM"/><xsl:text/>
</xsl:template>

</xsl:stylesheet>
Source file
<?xml version="1.0"?>
<project name="myproject">
    <target name="myname"/>
</project>
Processed with
xsltproc -param PARAM "'parameters.'" style.xsl source
output
CR/spaces are preserved...
    inside 'xsl:text'. But spaces/CR between two tags are deleted.
Except if ...
    it contains non-blanks.
    So better use empty text tags to eat spaces/CR, while still using
    ... xslt parameters.

Using entities

XSLT file
<?xml version='1.0' encoding="UTF-8"?>
<!DOCTYPE xsl:stylesheet [
<!ENTITY cr '<xsl:text xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
</xsl:text>'>
<!ENTITY space '<xsl:text xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> </xsl:text>'>
<!ENTITY param '<xsl:value-of xmlns:xsl="http://www.w3.org/1999/XSL/Transform" select="$PARAM"/>'>
<!ENTITY entity '<xsl:value-of xmlns:xsl="http://www.w3.org/1999/XSL/Transform" select="$ENTITY"/>'>
]>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'>

<xsl:output method="text" indent="no" encoding="UTF-8"/>

<xsl:template match="project">
<xsl:text/>Use entities for special characters like<xsl:text/>
<xsl:text/>&cr;&cr;<xsl:text/>
    carriage return
<xsl:text/>&cr;&cr;<xsl:text/>
<xsl:text/>or "<xsl:text/>&space;&space;&param;&space;&space;<xsl:text/>
<xsl:text/>" where normal "<xsl:text/>
<xsl:text/>  &param;  <xsl:text/>" or
<xsl:text/>

<xsl:text/>carriage return<xsl:text/>

<xsl:text/>
<xsl:text/>
does not work.

Using &entity; is also shorter than using 'xsl:value-of' tags.
</xsl:template>

</xsl:stylesheet>
Source file
<?xml version="1.0"?>
<project name="myproject">
    <target name="myname"/>
</project>
Processed with
xsltproc -param PARAM "'space'" -param ENTITY "'entities'" style.xsl source
output
Use entities for special characters like


    carriage return


or "  space  " where normal "space" or
carriage return
does not work.

Using entities is also shorter than using 'xsl:value-of' tags.

Using template parameters

XSLT file
<?xml version='1.0' encoding="UTF-8"?>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'>

<xsl:output method="text" indent="no" encoding="UTF-8"/>

<xsl:template match="project">
<xsl:text/>Calling template by matching, passing a string:
<xsl:apply-templates select="target">
	<xsl:with-param name="PARAM" select="'a string'"/>
</xsl:apply-templates>
Calling template by matching, passing a GLOBALPARAM:
<xsl:apply-templates select="target">
	<xsl:with-param name="PARAM" select="$GLOBALPARAM"/>
</xsl:apply-templates>
Calling template by name
<xsl:call-template name="named">
    <xsl:with-param name="PARAM" select="'a string'"/>
</xsl:call-template>
</xsl:template>

<xsl:template match="target">
    <xsl:param name="PARAM"/>
    <xsl:text/>parameter is <xsl:value-of select="$PARAM"/><xsl:text/>
</xsl:template>

<xsl:template name="named">
    <xsl:param name="PARAM"/>
    <xsl:text/>parameter is <xsl:value-of select="$PARAM"/><xsl:text/>
</xsl:template>

</xsl:stylesheet>
Source file
<?xml version="1.0"?>
<project name="myproject">
    <target name="myname"/>
</project>
Processed with
xsltproc -param GLOBALPARAM "'global param'" style.xsl source
output
Calling template by matching, passing a string:
parameter is a string
Calling template by matching, passing a GLOBALPARAM:
parameter is global param
Calling template by name
parameter is a string

Simple <xsl-apply-templates> and .

XSLT file
<?xml version='1.0' encoding="UTF-8"?>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'>

<xsl:output method="text" indent="no" encoding="UTF-8"/>

<xsl:template match="project">
<xsl:apply-templates select="target/*"/>
</xsl:template>

<xsl:template match="content">
    <xsl:text/>dot is '<xsl:value-of select="."/>', <xsl:text/>
</xsl:template>

</xsl:stylesheet>
Source file
<?xml version="1.0"?>
<project name="myproject">
    <target name="target1">
        <content>content1</content>
        <content>content2</content>
        <content>content3</content>
    </target>
</project>
Processed with
xsltproc style.xsl source
output
dot is 'content1', dot is 'content2', dot is 'content3',

Example of <xsl:for-each>

XSLT file
<?xml version='1.0' encoding="UTF-8"?>
<!DOCTYPE xsl:stylesheet [
<!ENTITY name '<xsl:value-of xmlns:xsl="http://www.w3.org/1999/XSL/Transform" select="@name"/>'>
]>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'>

<xsl:output method="text" indent="no" encoding="UTF-8"/>

<xsl:template match="project">
<xsl:text/>Simple for-each loop:
    <xsl:for-each select="target/*"><xsl:text/>dot is '<xsl:value-of select="."/>', </xsl:for-each>
For-each loop with select condition:
    <xsl:for-each select="target/content[@id='second']">&name;, </xsl:for-each>
For-each loop with if condition:
    <xsl:for-each select="target/*">
        <xsl:if test="position() = 1">First dot is </xsl:if>
        <xsl:if test="position() &gt; 1">, next dot is </xsl:if>
        <xsl:value-of select="."/>
        <xsl:if test="position() = last()">.</xsl:if>
    </xsl:for-each>
</xsl:template>

</xsl:stylesheet>
Source file
<?xml version="1.0"?>
<project name="myproject">
    <target name="target1">
        <content id='first' name="only">content1</content>
        <content id='second' name="double">content2</content>
        <content id='third' name="last">content3</content>
    </target>
</project>
Processed with
xsltproc style.xsl source
output
Simple for-each loop:
    dot is 'content1', dot is 'content2', dot is 'content3', 
For-each loop with select condition:
    double, 
For-each loop with if condition:
    First dot is content1, next dot is content2, next dot is content3.

Example of <xsl:choose>

XSLT file
<?xml version='1.0' encoding="UTF-8"?>
<!DOCTYPE xsl:stylesheet [
<!ENTITY name '<xsl:value-of xmlns:xsl="http://www.w3.org/1999/XSL/Transform" select="@name"/>'>
]>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'>

<xsl:output method="text" indent="no" encoding="UTF-8"/>

<xsl:template match="project">
<xsl:apply-templates select="target/*"/>
</xsl:template>

<xsl:template match="target/*">
<xsl:text/><xsl:variable name="TGTTYPE">
    <xsl:choose>
        <xsl:when test="@type='small'">
            <xsl:text/><xsl:value-of select="small"/><xsl:text/>
        </xsl:when>
        <xsl:otherwise>
            <xsl:text/><xsl:value-of select="big"/><xsl:text/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:variable><xsl:text/>
<xsl:text/>We have <xsl:value-of select="$TGTTYPE"/> for <xsl:value-of select="@type"/>.
</xsl:template>


</xsl:stylesheet>
Source file
<?xml version="1.0"?>
<project name="myproject">
    <target name="target1">
        <content type='small'><small>content1</small><big>CONTENT1</big></content>
        <content type='big'><small>content2</small><big>CONTENT2</big></content>
        <content type='small'><small>content3</small><big>CONTENT3</big></content>
    </target>
</project>
Processed with
xsltproc style.xsl source
output
We have content1 for small.
We have CONTENT2 for big.
We have content3 for small.

Copying tags with value-of and copy-of

XSLT file
<?xml version='1.0' encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'>

  <xsl:template match="element">
<xsl:text>
----- COPYOF-DOT -----------------------
</xsl:text>
    <xsl:apply-templates select="subelement" mode="copyof-dot" />
<xsl:text>
----- COPYOF-STAR ----------------------
</xsl:text>
    <xsl:apply-templates select="subelement" mode="copyof-star" />
<xsl:text>
----- COPYOF-NODE ----------------------
</xsl:text>
    <xsl:apply-templates select="subelement" mode="copyof-node" />
<xsl:text>
----- COPYOF-DOT -----------------------
</xsl:text>
    <xsl:apply-templates select="subelement" mode="valueof-dot" />
<xsl:text>
----- COPYOF-STAR ----------------------
</xsl:text>
    <xsl:apply-templates select="subelement" mode="valueof-star" />
<xsl:text>
----- COPYOF-NODE ----------------------
</xsl:text>
    <xsl:apply-templates select="subelement" mode="valueof-node" />
  </xsl:template>

  <xsl:template match="subelement" mode="copyof-dot" ><xsl:copy-of select="." /></xsl:template>
  <xsl:template match="subelement" mode="copyof-star"><xsl:copy-of select="*" /></xsl:template>
  <xsl:template match="subelement" mode="copyof-node"><xsl:copy-of select="node()" /></xsl:template>
  <xsl:template match="subelement" mode="valueof-dot" ><xsl:value-of select="." /></xsl:template>
  <xsl:template match="subelement" mode="valueof-star"><xsl:value-of select="*" /></xsl:template>
  <xsl:template match="subelement" mode="valueof-node"><xsl:value-of select="node()" /></xsl:template>

</xsl:stylesheet>
Source file
<?xml version="1.0"?>
<element><subelement>Some string with <span class="highlight">highlighted parts</span> and normal parts.</subelement></element>
Processed with
xsltproc -param PARAM "'text'" style.xsl source
output
<?xml version="1.0"?>

----- COPYOF-DOT -----------------------
<subelement>Some string with <span class="highlight">highlighted parts</span> and normal parts.</subelement>
----- COPYOF-STAR ----------------------
<span class="highlight">highlighted parts</span>
----- COPYOF-NODE ----------------------
Some string with <span class="highlight">highlighted parts</span> and normal parts.
----- COPYOF-DOT -----------------------
Some string with highlighted parts and normal parts.
----- COPYOF-STAR ----------------------
highlighted parts
----- COPYOF-NODE ----------------------
Some string with

See also on StackOverflow.

Template with match rule and dynamic entity

XSLT file
<?xml version='1.0' encoding="UTF-8"?>
<!DOCTYPE xsl:stylesheet [
<!ENTITY name '<xsl:value-of xmlns:xsl="http://www.w3.org/1999/XSL/Transform" select="@name"/>'>
]>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'>

<xsl:output method="text" indent="no" encoding="UTF-8"/>

<xsl:template match="project/target/*">
    <xsl:value-of select="@type"/>
</xsl:template>


</xsl:stylesheet>
Source file
<?xml version="1.0"?>
<project name="myproject">
    <target name="target1">
        <content type='small'><small>content1</small><big>CONTENT1</big></content>
        <content type='big'><small>content2</small><big>CONTENT2</big></content>
        <content type='small'><small>content3</small><big>CONTENT3</big></content>
    </target>
</project>
Processed with
xsltproc style.xsl source
output
    
        small
        big
        small

More complex apply-templates, with dynamic entities

XSLT file
<?xml version='1.0' encoding="UTF-8"?>
<!DOCTYPE xsl:stylesheet [
<!ENTITY name '<xsl:value-of xmlns:xsl="http://www.w3.org/1999/XSL/Transform" select="@name"/>'>
]>

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'>

<xsl:output method="text" indent="no" encoding="UTF-8"/>

<xsl:template match="project">
    <xsl:apply-templates select="target/*"/>
</xsl:template>

<xsl:template match="project/target/*">
    <xsl:value-of select="@type"/>
</xsl:template>


</xsl:stylesheet>
Source file
<?xml version="1.0"?>
<project name="myproject">
    <target name="target1">
        <content type='small'><small>content1</small><big>CONTENT1</big></content>
        <content type='big'><small>content2</small><big>CONTENT2</big></content>
        <content type='small'><small>content3</small><big>CONTENT3</big></content>
    </target>
</project>
Processed with
xsltproc style.xsl source
output
smallbigsmall

Using namespace

XSLT file
<?xml version='1.0' encoding="UTF-8"?>
<!DOCTYPE xsl:stylesheet [
<!ENTITY name '<xsl:value-of xmlns:xsl="http://www.w3.org/1999/XSL/Transform" select="@name"/>'>
]>

<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:ns="http://your.server.org/your/xsl/url/MYNS" 
    exclude-result-prefixes="xsl ns"
    version='1.0'>

<xsl:output method="text" indent="no" encoding="UTF-8"/>

<xsl:template match="ns:project">
    <xsl:apply-templates select="ns:target/*"/>
</xsl:template>

<xsl:template match="ns:project/ns:target/*">
    <xsl:if test="@type='small'">SMALL </xsl:if>
    <xsl:if test="@type='big'">BIG </xsl:if>
</xsl:template>


</xsl:stylesheet>
Source file
<?xml version="1.0"?>
<ns:project 
    xmlns:ns="http://your.server.org/your/xsl/url/MYNS" 
    name="myproject">
    <ns:target name="target1">
        <ns:content type='small'>content1</ns:content>
        <ns:content type='big'>content2</ns:content>
        <ns:content type='small'>content3</ns:content>
    </ns:target>
</ns:project>
Processed with
xsltproc style.xsl source
output
SMALL BIG SMALL

Example of expressions

XSLT file
<?xml version='1.0' encoding="UTF-8"?>

<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:math="http://exslt.org/math"
    extension-element-prefixes="math"
    version='1.0'>

    <xsl:output method="xml" indent="yes" encoding="UTF-8"/>

    <xsl:template match="project">
        <xsl:apply-templates select="target/*"/>
    </xsl:template>

    <xsl:template match="project/target/*">
        <p>
            <msg>dot is current tag content, here '.' is '<xsl:value-of select="."/>'</msg>
            <msg attr="{.}">Use braces {...} in attributes</msg>
            <msg random="unique.{floor(math:random()*2147483647)}">{...} can be any expression</msg>
        </p>
    </xsl:template>
</xsl:stylesheet>
Source file
<?xml version="1.0"?>
<project name="myproject">
    <target name="target1">
        <content type='small'>content1</content>
        <content type='big'>content2</content>
        <content type='small'>content3</content>
    </target>
</project>
Processed with
xsltproc style.xsl source
output
<?xml version="1.0" encoding="UTF-8"?>
<p>
  <msg>dot is current tag content, here '.' is 'content1'</msg>
  <msg attr="content1">Use braces {...} in attributes</msg>
  <msg random="unique.799522789">{...} can be any expression</msg>
</p><p>
  <msg>dot is current tag content, here '.' is 'content2'</msg>
  <msg attr="content2">Use braces {...} in attributes</msg>
  <msg random="unique.90206728">{...} can be any expression</msg>
</p><p>
  <msg>dot is current tag content, here '.' is 'content3'</msg>
  <msg attr="content3">Use braces {...} in attributes</msg>
  <msg random="unique.2005052793">{...} can be any expression</msg>
</p>

Generate custom XML tags

XSLT file
<?xml version='1.0' encoding="UTF-8"?>

<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:math="http://exslt.org/math"
    extension-element-prefixes="math"
    version='1.0'>

    <xsl:output method="xml" indent="yes" encoding="UTF-8"/>

    <xsl:template match="project">
        <xsl:text disable-output-escaping="yes">&lt;?fileVersion 4.0.0?&gt;</xsl:text><cproject id="my project"/>
        <p>Use 'disable-output-escaping' to generate xml tags.</p>
    </xsl:template>
</xsl:stylesheet>
Source file
<?xml version="1.0"?>
<project name="myproject">
    <target name="target1">
        <content type='small'>content1</content>
        <content type='big'>content2</content>
        <content type='small'>content3</content>
    </target>
</project>
Processed with
xsltproc style.xsl source
output
<?xml version="1.0" encoding="UTF-8"?>
<?fileVersion 4.0.0?><cproject id="my project"/><p>Use 'disable-output-escaping' to generate xml tags.</p>

Removing white spaces

XSLT file
<?xml version='1.0' encoding="UTF-8"?>

<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version='1.0'>

    <xsl:output method="xml" indent="yes" version="1.0" encoding="UTF-8" standalone="no"/>

    <xsl:template match="project">
        <project>
            <xsl:apply-templates select="*"/>
        </project>
    </xsl:template>

    <xsl:template match="target">
        <cleaned_target><xsl:value-of select="normalize-space(.)"/></cleaned_target>
    </xsl:template>
</xsl:stylesheet>
Source file
<?xml version="1.0"?>
<project name="myproject">
    <target>no_space_at_all</target>
    <target>   with_space_around  </target>
    <target>with
        carriage
        returns
    </target>
    <target>with  spaces  between  the  words</target>
</project>
Processed with
xsltproc style.xsl source
output
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project>
  <cleaned_target>no_space_at_all</cleaned_target>
  <cleaned_target>with_space_around</cleaned_target>
  <cleaned_target>with carriage returns</cleaned_target>
  <cleaned_target>with spaces between the words</cleaned_target>
</project>

Two-stage xml using variables and node-set

References:

XSLT file
<?xml version='1.0' encoding="UTF-8"?>

<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:exsl="http://exslt.org/common"
    exclude-result-prefixes="exsl"
    version='1.0'>

    <xsl:variable name="myvar">
        <xsl:apply-templates select="books" mode="stageone"/>
    </xsl:variable>

    <xsl:template match="book" mode="stageone">
        <title><xsl:value-of select="title"/></title>
    </xsl:template>

    <xsl:template match="/">
        <!-- Must use mode="...", or we have recursion since we match "/" -->
        <xsl:apply-templates select="exsl:node-set($myvar)" mode="stagetwo"/>
    </xsl:template>

    <xsl:template match="title" mode="stagetwo">
        <p><xsl:value-of select="."/></p>
    </xsl:template>
</xsl:stylesheet>
Source file
<?xml version="1.0"?>
<books>
  <book>
    <title>XML in a Nutshell</title>
    <editor>O'Reilly</editor>
  </book>
  <book>
    <title>XSL for the Noobs</title>
    <editor>O'Reilly</editor>
  </book>
</books>
Processed with
xsltproc style.xsl source
output
<?xml version="1.0"?>

  <p>XML in a Nutshell</p>
  <p>XSL for the Noobs</p>

Using call-template

XSLT file
<?xml version='1.0' encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'>
    <xsl:template match="*">
        <xsl:call-template name="wrap_with_p">
            <xsl:with-param name="content" select="concat('abc','def')"/>
        </xsl:call-template>
    </xsl:template>
    <xsl:template name="wrap_with_p">
        <xsl:param name="content"/>
        <p><xsl:value-of select="$content"/></p>
    </xsl:template>
</xsl:stylesheet>
Source file
<?xml version="1.0"?>
<title>XSL for the Noobs</title>
Processed with
xsltproc style.xsl source
output
<?xml version="1.0"?>
<p>abcdef</p>

Example recursive template

<xsl:template name="escape">
    <xsl:param name="content"/>
    <xsl:if test="string-length($content)>0">
    <xsl:choose>
        <xsl:when test="starts-with($content,'(')"><xsl:text>\(</xsl:text><xsl:call-template name="escapeForMake"><xsl:with-param name="content" select="substring($content, 2)"/></xsl:call-template></xsl:when>
        <xsl:when test="starts-with($content,')')"><xsl:text>\)</xsl:text><xsl:call-template name="escapeForMake"><xsl:with-param name="content" select="substring($content, 2)"/></xsl:call-template></xsl:when>
        <xsl:when test="starts-with($content,'\')"><xsl:text>\\</xsl:text><xsl:call-template name="escapeForMake"><xsl:with-param name="content" select="substring($content, 2)"/></xsl:call-template></xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="substring($content,1,1)"/>
            <xsl:call-template name="escapeForMake">
                <xsl:with-param name="content" select="substring($content, 2)"/>
            </xsl:call-template>
        </xsl:otherwise>
    </xsl:choose>
    </xsl:if>
</xsl:template>

Call it as follow:

<xsl:call-template name="escape"><xsl:with-param name="content"> select="@synopsis" /></xsl:call-template>

Note that the same template could be implemented using string extension:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:func="http://exslt.org/functions" xmlns:exsl="http://exslt.org/common" xmlns:str="http://exslt.org/strings" extension-element-prefixes="func" xmlns:my="http://myserver.org/xsl/functions" exclude-result-prefixes="exsl str func my">

    <func:function name="my:escape">
        <xsl:param name="filename" />
        <func:result>
            <xsl:value-of select="str:replace(str:replace(str:replace($filename,'\','\\'),'(','\('),')','\)')" />
        </func:result>
    </func:function>

    <xsl:template match="file">
        <directory><xsl:value-of select="my:escape()"/></directory>
    </xsl:template>
</xsl:stylesheet>

Or even shorter, using the multi-token version of str:replace:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:func="http://exslt.org/functions" xmlns:exsl="http://exslt.org/common" xmlns:str="http://exslt.org/strings" extension-element-prefixes="func" xmlns:my="http://myserver.org/xsl/functions" exclude-result-prefixes="exsl str func my">

    <func:function name="my:escape">
        <xsl:param name="filename" />
        <func:result>
            <xsl:value-of select="str:replace($filename,str:split('()\',''),str:split('\(,\),\\',','))" />
        </func:result>
    </func:function>

    <xsl:template match="file">
        <directory><xsl:value-of select="my:escape()"/></directory>
    </xsl:template>
</xsl:stylesheet>

Example using node-set to compute sum-products

From http://www.xml.com/pub/a/2003/07/16/nodeset.html:

Stylesheet
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:exsl="http://exslt.org/common"
                extension-element-prefixes="exsl"
                version="1.0">

<xsl:template match="/">
  <html>
    <head>
      <title>Invoice</title>
    </head>
    <body>
      <h1>Invoice</h1>
      
      <!-- Format invoice items as a table -->
      <table border="1" style="text-align: center">
        <tr>
          <th>Description</th>
          <th>Quantity</th>
          <th>Unit price</th>
          <th>Subtotal</th>
        </tr>
        <xsl:for-each select="invoice/item">
          <tr>
            <td><xsl:value-of select="description"/></td>
            <td><xsl:value-of select="qty"/></td>
            <td><xsl:value-of select="unitPrice"/></td>
            <td><xsl:value-of select="qty * unitPrice"/></td>
          </tr>
        </xsl:for-each>
        <tr>
          <th colspan="3">Total</th>
          <th>
            <!-- Gather subtotals into variable -->
            <xsl:variable name="subTotals">
              <xsl:for-each select="invoice/item">
                <number>
                  <xsl:value-of select="qty * unitPrice"/>
                </number>
              </xsl:for-each>
            </xsl:variable>

            <!-- Sum subtotals stored as a result tree fragment 
                 in the variable -->
            <xsl:value-of 
              select="sum(exsl:node-set($subTotals)/number)"/>
          </th>
        </tr>
      </table>
    </body>
  </html>
</xsl:template>
  
</xsl:stylesheet>
source
<?xml version="1.0" encoding="utf-8"?>
<invoice>
  <item>
    <description>Pilsner Beer</description>
    <qty>6</qty>
    <unitPrice>1.69</unitPrice>
  </item>
  <item>
    <description>Sausage</description>
    <qty>3</qty>
    <unitPrice>0.59</unitPrice>
  </item>
  <item>
    <description>Portable Barbecue</description>
    <qty>1</qty>
    <unitPrice>23.99</unitPrice>
  </item>
  <item>
    <description>Charcoal</description>
    <qty>2</qty>
    <unitPrice>1.19</unitPrice>
  </item>
</invoice>

In particular, we observe:

  • We first construct a set of sub-totals with
<xsl:variable name="subTotals"> 
  <xsl:for-each  select="invoice/item">
    <number><xsl:value-of select="qty * unitPrice"/></number>
  </xsl:for-each>
</xsl:variable>
  • Then we compute the sum with
sum(exsl:node-set($subTotals)/number).
Note the special construct node-set(...)/number, and not simply node-set(...).

TODO - Variable assign template

XSLT file
<?xml version='1.0' encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'>
  <xsl:template match="template_name">
    <xsl:variable name="CHIP">
      <xsl:value-of select="."/>
    </xsl:variable>
    <xsl:choose>
      <xsl:when test="starts-with(substring($CHIP, 3, 2), '31')">ARMCM0</xsl:when>
      <xsl:otherwise>ARMCM3</xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>
Source file
<?xml version="1.0"?>
???
Processed with
xsltproc style.xsl source
output
???

Template - test whether an attribute is present

Say we have a XML doc with tags like:

<build><command>...</command></build>
<build cmd="..." />

To process the build differently based on the presence of the attribute cmd:

<xsl:template match="build[@cmd]">
  <xsl:value-of select="@cmd" />
</xsl:template>

<xsl:template match="build/command">
  <xsl:value-of select="." />
</xsl:template>

Or using a choose construct:

<xsl:template match="build">
  <xsl:choose>
    <xsl:when test="@cmd">
      <xsl:value-of select="@cmd" />
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="command" />
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

Miscellaneous

  • <xsl:param name="MYPARAM"> in <xsl:stylesheet> node declares a global parameter.
After this declaration we can use $MYPARAM to get the value of the parameter. If it is not declared, we can only use $MYPARAM if it is passed as a parameter (e.g. via command line xsltproc -param MYPARAM "'myparamvalu'"), otherwise an error is triggered.
  • Some string functions:
<xsl:template match="verse">
  <xsl:value-of select="."/>
  <xsl:value-of select="substring(.,7,6)"/>
  <xsl:value-of select="substring(.,12)"/>
  <xsl:value-of select="substring-before(.,'dreary')"/>
  <xsl:value-of select="substring-after(.,'desolation')"/>
</xsl:template>