XSLT
XSLT stands for Extensible Stylesheet Language Transformations.
Links
- Gnome library
- exslt (extensions)
- Extensions supported in libxml (
xsltproc
processor)
- XPath
- https://www.w3.org/TR/xpath/ — Reference specification, which has several xpath examples at the top.
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>
When applied to the example XML file above (with xsltproc minimal.xsl example.xml
), the minimal stylesheet produces this 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:
- Using variables (like in
<a href="http://{$varName}">Click here</a>
)
func:functions
References:
Using the exslt:func extension, we can define functions in XSLT 1.0.
Here an example 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>
|
<?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 = ''"/>
- 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>
Tips
Output tags like <?fileVersion 4.0.0 ?>
Use a xsl:text
tag with attribute disable-output-escaping="yes"
: [9]:
<xsl:template match="/">
<xsl:text disable-output-escaping="yes"><?fileVersion 4.0.0?></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: [10]
<?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 [11]:
<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
.
Examples
Simple <xsl:template>
This template will replace 'project' by a simple text
|
Spaces and CR
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
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
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 .
dot is 'content1', dot is 'content2', dot is 'content3',
|
Example of <xsl:for-each>
Simple for-each loop:
dot is 'content1', dot is 'content2', dot is 'content3',
For-each loop with select condition:
double,
|
Example of <xsl:choose>
We have content1 for small.
We have CONTENT2 for big.
We have content3 for small.
|
Template with match rule and dynamic entity
small
big
small
|
More complex apply-templates, with dynamic entities
smallbigsmall
|
Using namespace
SMALL BIG SMALL
|
Example of expressions
<?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
<?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
<?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:
- http://www.usingxml.com/Transforms/XslPipelines (see example below)
- http://www.xml.com/pub/a/2003/07/16/nodeset.html (very detailed example).
<?xml version="1.0"?>
<p>XML in a Nutshell</p>
<p>XSL for the Noobs</p>
|
Using call-template
<?xml version="1.0"?>
<p>abcdef</p>
|
Example recursive function
<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:
<?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>
|
<?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 simplynode-set(...)
.
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 linexsltproc -param MYPARAM "'myparamvalu'"
), otherwise an error is triggered.