Practical XSLT
Bob DuCharme
www.snee.com/bob
[email protected]
these slides:
www.snee.com/xml
1.0
©CSW Group Ltd 2005
Outline
• What is XSLT?
• Our first stylesheet: renaming and deletion
• Running stylesheets
• Reordering elements
• Renaming attributes
• Converting elements to attributes and vice
versa
• Outputting plain text and controlling white
space
• Conditional execution
• Selective output of data
• XPath
XSLT
• XSL Transformations (XSLT)
• relationship to XSL
• XSLT 1.0 W3C Recommendation November
16, 1999
• XSLT 1.1 W3C Working Draft December 12,
2000
• (as of 8 June 2005): XPath 2.0, XSLT 2.0,
XQuery 1.0 in “last call Working Draft” status
from Abstract:
"This specification defines the syntax and
semantics of XSLT, which is a language for
transforming XML documents into other XML
documents."
Documents, trees
From section 1, "Introduction" of XSLT spec:
"A transformation expressed in XSLT describes rules for
transforming a source tree into a result tree."
What about documents?
An XSLT stylesheet
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="year">
<vintage>
<xsl:apply-templates/>
</vintage>
</xsl:template>
<xsl:template match="price">
</xsl:template>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
"Literal result elements"
Running it
my saxon shell script:
#! /bin/sh
java net.sf.saxon.Transform $1 $2 $3 $4 $5 $6 $7
saxon.bat:
java net.sf.saxon.Transform %1 %2 %3 %4 %5 %6 %7
Running example:
saxon -o wine1.out wine1.xml wine1.xsl
More processors: Xalan C++, Xalan Java,
libxslt... see
http://www.xmlsoftware.com/xslt.html.
Effect of first example
testxslt -in wine1.xml -xsl wine1.xsl
turns this
<wine grape="chardonnay">
<product>Carneros</product>
<year>1997</year>
<price>10.99</price>
</wine>
into this:
<?xml version="1.0" encoding="UTF-8"?>
<wine grape="chardonnay">
<product>Carneros</product>
<vintage>1997</vintage>
</wine>
Empty xsl:stylesheet element
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"/>
turns this
<winelist date="20000626">
<wine grape="chardonnay">
<product>Carneros</product>
<year>1997</year>
<price>10.99</price>
</wine>
</winelist>
into this:
<?xml version="1.0" encoding="UTF-8"?>
Carneros
1997
10.99
Doing it yourself: sample data
• wine.xml for demonstrations
• orders.xml for exercises
wine.xml (part 1 of 2)
<?xml version="1.0"?>
<winelist>
<wine grape="chardonnay">
<winery>Benziger</winery>
<product>Carneros</product>
<year>1997</year>
<desc>Well-textured flavors, good finish.</desc>
<prices>
<list>10.99</list>
<discounted>9.50</discounted>
<case>114.00</case>
</prices>
</wine>
wine.xml (part 2 of 2)
<wine grape="cabernet">
<winery>Duckpond</winery>
<product>Merit Selection</product>
<year>1996</year>
<desc>Sturdy and generous flavors, long finish.</desc>
<prices>
<list>13.99</list>
<discounted>11.99</discounted>
<case>143.50</case>
</prices>
</wine>
<wine grape="chardonnay">
<winery>Lindeman's</winery>
<product>Bin 65</product>
<year>1998</year>
<desc>Youthful, with a cascade of spicy fig.</desc>
<prices>
<list>6.99</list>
<discounted>5.99</discounted>
<case>71.50</case>
</prices>
</wine>
</winelist>
orders.xml (part 1 of 5)
<!ELEMENT orders (order+)>
<!ELEMENT order (quantity, pricePerItem, orderDate,
warrantee?,billingAddr,shippingAddr?,productPic?)>
orders.xml (part 2 of 5)
<orders>
<order orderNum="o43123" itemNum="i1009">
<quantity>1</quantity>
<pricePerItem>29.99</pricePerItem>
<orderDate>20000623</orderDate>
<warrantee>90 days parts and labor</warrantee>
<billingAddr lastUpdate="20000623">
<recip>Sterling Moss</recip>
<line1>21 Manor Dr.</line1>
<line2></line2>
<city>Milford</city>
<state>CT</state>
<zip>06460</zip>
</billingAddr>
<productPic picent="a124"/>
</order>
orders.xml (part 3 of 5)
<order orderNum="o24987" itemNum="i0874">
<quantity>4</quantity>
<pricePerItem>9.99</pricePerItem>
<orderDate>20000621</orderDate>
<billingAddr lastUpdate="20000621">
<recip>Johnny Herbert</recip>
<line1>37 8th Ave.</line1>
<line2>Apt. 3</line2>
<city>Brooklyn</city>
<state>NY</state>
<zip>11217</zip>
</billingAddr>
<shippingAddr lastUpdate="19990405">
<recip>Martin Blundell</recip>
<line1>37 8th Ave.</line1>
<line2>Apt. 5</line2>
<city>Brooklyn</city>
<state>NY</state>
<zip>11217</zip>
</shippingAddr>
<productPic picent="a123"/>
</order>
orders.xml (part 4 of 5)
<order orderNum="o86734" itemNum="i2118">
<quantity>1</quantity>
<pricePerItem>300.00</pricePerItem>
<orderDate>20000625</orderDate>
<warrantee></warrantee>
<billingAddr lastUpdate="19990422">
<recip>Damon Hill</recip>
<line1>321 Main St.</line1>
<line2></line2>
<city>Delavan</city>
<state>WI</state>
<zip>21353</zip>
</billingAddr>
<shippingAddr lastUpdate="19990405">
<recip>Graham Hill</recip>
<line1>9344 Simon Rd.</line1>
<line2></line2>
<city>Lake Geneva</city>
<state>WI</state>
<zip>21993</zip>
</shippingAddr>
</order>
orders.xml (part 5 of 5)
<order orderNum="o67918" itemNum="i1009">
<quantity>2</quantity>
<pricePerItem>29.99</pricePerItem>
<orderDate>20000622</orderDate>
<warrantee></warrantee>
<billingAddr lastUpdate="19981103">
<recip>Jenson Button</recip>
<line1>38 North Spring Ave.</line1>
<line2>Apt. 32</line2>
<city>Atlanta</city>
<state>GA</state>
<zip>92344</zip>
</billingAddr>
<productPic picent="a124"/>
</order>
</orders>
Exercise 1
In a DOS window:
• Enter "saxon" by itself to see what happens.
• Take a look at empty.xsl and orders.xml with
your text editor.
• Have saxon process orders.xml using
empty.xsl. You can pipe it to "more" or
redirect it to a text file if you like.
• Process the same XML file with identity.xsl.
Reviewing Renaming and Deletion
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="year">
<vintage>
<xsl:apply-templates/>
</vintage>
</xsl:template>
<xsl:template match="price">
</xsl:template>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
"Literal result elements"
Deleting, Part 2
Another way to delete price from the following:
<wine grape="chardonnay">
<product>Carneros</product>
<year>1997</year>
<price>10.99</price>
</wine>
is like this:
<xsl:template match="wine">
<wine>
<!-- <xsl:apply-templates/> -->
<xsl:apply-templates select="product"/>
<xsl:apply-templates select="year"/>
</wine>
</xsl:template>
(Note how it also deletes the grape attribute.)
Reordering Elements
<xsl:template match="wine">
<wine grape="[email protected]}">
<xsl:apply-templates select="price"/>
<xsl:apply-templates select="year"/>
<xsl:apply-templates select="product"/>
</wine>
</xsl:template>
turns this
<wine grape="chardonnay">
<product>Carneros</product>
<year>1997</year>
<price>10.99</price>
</wine>
into this:
<wine grape="chardonnay">
<price>10.99</price>
<year>1997</year>
<product>Carneros</product>
</wine>
skeleton.xsl
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-<xsl:template match="">
<xsl:apply-templates/>
</xsl:template>
-->
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates
select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Exercise 2
Copy skeleton.xsl to create a stylesheet called
ws1.xsl that does the following when
orders.xml is used as input:
•Renames quantity to amount and orderDate to
dateOrdered
•Deletes any warrantee and productPic
elements (use either deletion method)
•Moves the order date to be the first thing in
the order and the shipping address to before
the billing address
Don't worry about the order element's
attributes for now.
Renaming Attributes
<wine grape="[email protected]}">
outputs this:
<wine grape="chardonnay">
And this
<wine varietal="[email protected]}">
will output this:
<wine varietal="chardonnay">
Converting Attributes to Elements & Vice
Versa
<xsl:template match="wine">
<wine vintage="{year}">
<xsl:apply-templates select="product"/>
<category><xsl:value-of select="@grape"/>
</category>
<xsl:apply-templates select="price"/>
</wine>
</xsl:template>
converts
<wine grape="chardonnay">
<product>Carneros</product>
<year>1997</year>
<price>10.99</price>
</wine>
to this:
<wine vintage="1997">
<product>Carneros</product>
<category>chardonnay</category>
<price>10.99</price>
</wine>
Attribute Value Templates
Following WRONG:
<wine functest =
"<xsl:value-of select='string-length(@grape)'>">
Attribute value templates give you much of the
power of xsl:value-of in attribute values.
Correct:
<wine functest="{string-length(@grape)}">
What We've Covered So Far
• deleting elements
• renaming elements
• reordering elements
• deleting attributes
• renaming attributes
• converting elements to attributes
• converting attributes to elements
Exercise 3
Prepare a stylesheet that copies orders.xml,
doing the following to the result:
•Rename the itemNum attribute to be just item.
•Make orderNum a subelement of order instead of
an attribute.
•Make quantity an attribute of order instead of a
subelement.
If time, give order a new child called addresses
that has billingAddr and shippingAddr as its
children. (Hint: you can use a literal result
element with XSLT elements inside of it.)
Controlling White Space, Part 1
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="wine">
test string 1
<xsl:apply-templates select="winery"/>
<xsl:text>test string 2</xsl:text>
<xsl:apply-templates select="winery"/>
</xsl:template>
</xsl:stylesheet>
Controlling White Space, Part 1
Output of previous stylesheet with wine.xml:
test string 1
Benzigertest string 2Benziger
test string 1
Duckpondtest string 2Duckpond
test string 1
Lindeman'stest string 2Lindeman's
Controlling White Space, Part 2
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="wine">
Wine:
<xsl:apply-templates select="winery"/>
<xsl:apply-templates select="product"/>
<xsl:apply-templates select="year"/>
</xsl:template>
</xsl:stylesheet>
Controlling White Space, Part 2
Output of previous stylesheet with wine.xml:
Wine:
BenzigerCarneros1997
Wine:
DuckpondMerit Selection1996
Wine:
Lindeman'sBin 651998
Controlling White Space, Part 2b
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="wine">
Wine:
<xsl:apply-templates select="winery"/><xsl:text>
</xsl:text>
<xsl:apply-templates select="product"/><xsl:text>
</xsl:text>
<xsl:apply-templates select="year"/>
</xsl:template>
</xsl:stylesheet>
Controlling White Space, Part 2b
Output of previous stylesheet with wine.xml:
Wine:
Benziger
Carneros
1997
Wine:
Duckpond
Merit Selection
1996
Wine:
Lindeman's
Bin 65
1998
Exercise 4
Prepare a stylesheet that creates a commaseparated value (CSV) from orders.xml showing
each order's quantity, pricePerItem, and
orderDate on each line.
The output should look like this:
1,29.99,20000623
4,9.99,20000621
1,300.00,20000625
2,29.99,20000622
Don't worry if lines are skipped between the
lines of output, but make sure that each order's
information is all on one line.
Control Structures: if
<xsl:template match="wine">
<wine grape="[email protected]}">
<xsl:if [email protected] = "chardonnay"'>
<emph>Special sale on chardonnays!</emph>
</xsl:if>
<xsl:apply-templates select="*|@*|text()"/>
</wine>
</xsl:template>
xsl:if in Action
Prceding turns this:
<wine grape="chardonnay">
<product>Carneros</product>
<year>1997</year>
<price>10.99</price>
</wine>
<wine grape="cabernet">
<product>Merit Selection</product>
<year>1996</year>
<price>13.99</price>
</wine>
into this:
<wine grape="chardonnay">
<emph>Special sale on chardonnays!</emph>
<product>Carneros</product>
<year>1997</year>
<price>10.99</price>
</wine>
<wine grape="cabernet">
<product>Merit Selection</product>
<year>1996</year>
<price>13.99</price>
</wine>
Control Structures: choose
<xsl:template match="wine">
<wine grape="[email protected]}">
<xsl:choose>
<xsl:when [email protected] = "chardonnay"'>
<emph>Special sale on chardonnays!</emph>
</xsl:when>
<xsl:when [email protected] = "cabernet"'>
<emph>Gotta love them cabernets!</emph>
</xsl:when>
<xsl:otherwise>
<emph>Another fabulous grape!</emph>
</xsl:otherwise>
</xsl:choose>
<xsl:apply-templates select="*|@*|text()"/>
</wine>
</xsl:template>
xsl:choose in Action
Preceding template turns this
<wine grape="chardonnay">
<product>Carneros</product>
<year>1997</year><price>10.99</price>
</wine>
<wine grape="cabernet">
<product>Merit Selection</product>
<year>1996</year><price>13.99</price>
</wine>
into this:
<wine grape="chardonnay">
<emph>Special sale on chardonnays!</emph>
<product>Carneros</product>
<year>1997</year><price>10.99</price>
</wine>
<wine grape="cabernet">
<emph>Gotta love them cabernets!</emph>
<product>Merit Selection</product>
<year>1996</year><price>13.99</price>
</wine>
Selective output based on data values, Part 1
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="wine">
<xsl:if test="@grape = 'chardonnay'">
<xsl:copy>
<xsl:apply-templates
select="@*|node()"/>
</xsl:copy>
</xsl:if>
</xsl:template>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates
select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Selective output based on data values, Part 1
<?xml version="1.0" encoding="utf-8"?><winelist>
<wine grape="chardonnay">
<winery>Benziger</winery>
<product>Carneros</product>
<year>1997</year>
<desc>Well-textured flavors, good finish.</desc>
<prices>
<list>10.99</list>
<discounted>9.50</discounted>
<case>114.00</case>
</prices>
</wine>
<wine grape="chardonnay">
<winery>Lindeman's</winery>
<product>Bin 65</product>
<year>1998</year>
<desc>Youthful, with a cascade of spicy fig.</desc>
<prices>
<list>6.99</list>
<discounted>5.99</discounted>
<case>71.50</case>
</prices>
</wine>
</winelist>
Selective output based on data values, Part 2
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="wine[@grape != 'chardonnay']">
</xsl:template>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates
select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Exercise 5
• Write a stylesheet that only copies
the order elements with a
pricePerItem of more than 50.
• Remember that a < or > character
inside of an attribute value must be
represented as an entity reference
(&lt; or &gt;).
XPath
Abstract of spec: a "language for addressing
parts of an XML document, designed to be
used by both XSLT and XPointer."
Split out from XSLT; became a W3C
Recommendation in November of 1999.
Used by XSLT, XPointer, and by XQuery
Iterating across a set of nodes
<a>
<b>3</b>
<c>10</c>
<b>4</b>
<c>20</c>
<b>5</b>
</a>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="a">
<xsl:for-each select="b">
<xsl:value-of select=". + 5"/>
<xsl:text> </xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
output:
<?xml version="1.0" encoding="UTF-8"?>8 9 10
Addressing Parts of a Document
<poem>
<title>Crossroad Blues</title>
<stanza>
<verse>I went down to the crossroads, fell down on my
knees.</verse>
<verse>I went down to the crossroads, fell down on my
knees.</verse>
<verse>Ask the lord above have mercy, save me if you
please.</verse>
</stanza>
<stanza>
<verse>Standing at the crossroads, tried to flag a ride</verse>
<verse>Standing at the crossroads, tried to flag a ride</verse>
<verse>Nobody seems to know me, everybody pass me by.</verse>
</stanza>
</poem>
Addressing Parts of a Document
/poem/title
/poem/stanza/verse
/
/poem/*
..
../title
XPath Syntax
XPath expression expressed as a location path.
Location path: one or more location steps
separated by "/".
Slash at beginning makes it an absolute
location path.
Compare Windows or Unix directory names:
\windows\cookies vs. windows\cookies vs.
..\cookies, or /usr/bin vs. usr/bin vs. ../bin
For a winelist node, output the first year child
of a wine element:
<xsl:template match="winelist">
<xsl:value-of select="wine/year"/>
</xsl:template>
XPath: Location Steps
From the XPath spec:
"A location step has three parts:
an axis, which specifies the tree relationship between
the nodes selected by the location step and the
context node,
●
a node test, which specifies the node type and
expanded-name of the nodes selected by the location
step, and
●
zero or more predicates, which use arbitrary
expressions to further refine the set of nodes selected
by the location step."
●
For XSLT purposes, the context node is the node
that the XSLT processor is currently dealing with as it
goes through the source tree's nodes.
XPath Location Steps: Axes
axes: child, descendant, parent, ancestor,
following-sibling, preceding-sibling, following,
preceding, attribute, namespace, self,
descendant-or-self, and ancestor-or-self.
child is default.
<xsl:value-of select="child::wine">
same as
<xsl:value-of select="wine">
preceding-sibling axis and wine.xml
<xsl:template match="product">
<name>
<xsl:value-of
select="preceding-sibling::winery"/>
<xsl:text> </xsl:text>
<xsl:value-of select="."/>
</name>
</xsl:template>
turns
<winery>Benziger</winery>
<product>Carneros</product>
into this:
<name>Benziger Carneros</name>
XPath Location Steps: Node Test
<xsl:value-of select="child::wine">
<xsl:value-of select="wine">
Node test: "wine" above; "*" in following:
<xsl:value-of select="*">
XPath Location Steps: Predicate
From spec: "A predicate filters a node-set with
respect to an axis to produce a new node-set."
<xsl:value-of select="child::wine[last()]"/>
<xsl:value-of select="child::wine[1]"/>
<xsl:value-of select="wine[@grape='cabernet']">
<xsl:value-of select="wine[count(*) = 0]">
<xsl:value-of select="wine[@grape]">
Contrast last one with
<xsl:value-of select="@grape">
XPath Abbreviations Part 1
Section 2.5, Abbreviated Syntax of the XPath spec lists
handy abbreviations to use instead of the full
axisname::nodeTest[predicate] syntax
. abbreviates self::node()
// abbreviates descendant-or-self::node() (node()
returns true for all nodes)
Example: .//para is short for
self::node()/descendant-orself::node()/child::para, selecting all para
descendants of the current node.
.. abbreviates parent::node()
XPath Abbreviations Part 2
@ abbreviates attribute::
Example: ..[email protected] selects the parent node's
grape attribute; ../year selects context node's
year sibling (compare UNIX and DOS relative
path notation)
A missing axis abbreviates child::
Example: product abbreviates child::product
Sample Data for Playing with Axes
<!DOCTYPE thing [
<!ELEMENT thing (title,thing*)>
<!ATTLIST thing id ID #IMPLIED>
<!ELEMENT title (#PCDATA)>
]>
<thing id="i1"><title>main thing</title>
<thing id="i1-1"><title>child 1-1</title>
<thing id="i1-1-1"><title>child 1-1-1</title></thing>
<thing id="i1-1-2"><title>child 1-1-2</title>
<thing id="i1-1-2-1"><title>child 1-1-2-1</title>
</thing>
<thing id="i1-1-2-2"><title>child 1-1-2-2</title>
</thing>
</thing>
<thing id="i1-1-3"><title>child 1-1-3</title></thing>
</thing>
<thing id="i1-2"><title>child 1-2</title>
<thing id="i1-2-1"><title>child 1-2-1</title></thing>
<thing id="i1-2-2"><title>child 1-2-2</title></thing>
</thing>
</thing>
Thing n will have children thing n-1, thing n-2, etc.
Xpath sample data: tree structure
i1
i1-2
i1-1
i1-1-1
i1-1-2
i1-1-2-1
i1-1-3
i1-1-2-2
i1-2-1
i1-2-2
"following" axis: Whole Node List, First
Node
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="thing[@id = 'i1-1']">
<xsl:for-each select="following::thing">
<xsl:value-of
select="attribute::id"/><xsl:text> </xsl:text>
</xsl:for-each>
</xsl:template>
<xsl:template match="@*|*">
<xsl:apply-templates select="@*|*"/>
</xsl:template>
</xsl:stylesheet>
Output:
i1-2 i1-2-1 i1-2-2
select value of "following::thing[1]" would only
select i1-2.
"preceding" axis: Whole Node List, "First"
Node
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="thing[@id = 'i1-1-3']">
<xsl:for-each select="preceding::thing">
<xsl:value-of
select="attribute::id"/><xsl:text> </xsl:text>
</xsl:for-each>
</xsl:template>
<xsl:template match="@*|*">
<xsl:apply-templates select="@*|*"/>
</xsl:template>
</xsl:stylesheet>
Output:
i1-1-1 i1-1-2 i1-1-2-1 i1-1-2-2
“first” preceding element
select value of "preceding::thing[1]" would
select i1-1-2-2, not i1-1-1. Same with ancestor
axis.
select value of "preceding::thing[last()] would
select i1-1-1.
Combining Node Sets with XPath
Expression
<xsl:template match="thing[@id = 'i1-1-2']">
<xsl:for-each
select="preceding-sibling::thing | followingsibling::thing">
<xsl:value-of select="attribute::id"/>
<xsl:text> </xsl:text>
</xsl:for-each>
</xsl:template>
Output:
i1-1-1 i1-1-3
Exercise 6
ws2.xsl lists all the nodes that are children of i1-1.
Experiment with the context node and different axes in
the xsl:for-each element's select value to see what
gets selected. Try predicates of [last()] and [n] (where
n is a number like 1 or 2), and try combining node sets
with the OR (|) operator. (A comment in ws2.xsl shows
the structure of the input and lists the axes.)
After exploring for a bit, find the XPath expression that
selects every node in the document that starts before
i1-1-3 . (Hint: note the "|" in the second template rule's
match pattern in ws2.xsl.)
axes: child, descendant, parent, ancestor,
following-sibling, preceding-sibling, following,
preceding, attribute, namespace, self,
descendant-or-self, and ancestor-or-self.
Descargar

Practical XSLT Bob DuCharme www.snee.com/bob …