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.
- FAQ
- XSLT Questions and Answers - FAQ (a wealth of information)
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:
- 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>
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 elementc
<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:
Examples
nodename
|
Select all nodes with name nodename |
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[@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. |
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"><?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: [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(/&/,"\\&",$2); gsub(/</,"\\<",$2); gsub(/>/,"\\>",$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="product"/>
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>
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.
|
Copying tags with value-of and copy-of
<?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
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 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:
<?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(...)
.
TODO - Variable assign template
???
|
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 linexsltproc -param MYPARAM "'myparamvalu'"
), otherwise an error is triggered.