<?xml version = "1.0"?>
<inventory>
<product upc = "324">
<type> Cereal </type>
<brand> Kellogs </brand>
<model> Cheerios
</model>
<description>
Breakfast food
</description>
<price per = "box">
4.25 </price>
</product>
<product upc =
"298">
<type> Coffee </type>
<brand> Peets </brand>
<description>
Breakfast beverage
</description>
<price per = "lb">
8.33 </price>
</product>
<product upc =
"324">
<type> Cereal </type>
<brand> Post </brand>
<model> Toasties
</model>
<description>
Breakfast food
</description>
<price per = "box">
4.05 </price>
</product>
<product upc =
"834">
<type> Dog food </type>
<brand> Alpo </brand>
<description>
Dog food
</description>
<price per = "can">
.85 </price>
</product>
<product upc =
"834">
<type> Dog food </type>
<brand>
<description>
Dog food
</description>
<price per = "can">
1.05 </price>
</product>
<product upc =
"922">
<type> Soft drink
</type>
<brand> Coka Cola
</brand>
<model> Diet Coke
</model>
<description>
beverage
</description>
<price per = "6
pack"> 1.85 </price>
</product>
</inventory>
<xsl:template match = "/">
<xsl:variable name =
"nodes" select = "PATH"/>
<html>
<head><title> XSL Demo
</title></head>
<body>
<xsl:for-each select =
"$nodes">
<xsl:value-of select =
"'name = '"/>
<xsl:value-of select =
"name(.)"/> <br />
<xsl:value-of select =
"'value = '"/>
<xsl:value-of select =
"."/> <br/>
</xsl:for-each>
</body>
</html>
</xsl:template>
name = product
value = Cereal Kellogs Cheerios Breakfast food 4.25
name = product
value = Coffee Peets Breakfast beverage 8.33
name = product
value = Cereal Post Toasties Breakfast food 4.05
name = product
value = Dog food Alpo Dog food .85
name = product
value = Dog food Kal Kan Dog food 1.05
name = product
value = Soft drink Coka Cola Diet Coke beverage 1.85
name = upc
value = 324
name = upc
value = 298
name = upc
value = 324
name = upc
value = 834
name = upc
value = 834
name = upc
value = 922
name = upc
value = 324
name = upc
value = 324
name = upc
value = 834
name = upc
value = 834
name = upc
value = 298
name = upc
value = 834
name = upc
value = 834
name = upc
value = 922
<xsl:template match = "/">
<xsl:variable name =
"codes" select = "//@upc"/>
<html>
<head><title> XSL Demo
</title></head>
<body>
<h1 align =
"center"> Today's Inventory </h1>
<xsl:for-each select =
"$codes">
<xsl:sort order =
"ascending"/>
<hr />
<xsl:value-of select =
"position()"/>.
<xsl:value-of select =
"../type"/>
<xsl:for-each select =
".."> <br />
<xsl:if test =
"model">
model = <xsl:value-of
select = "model"/> <br />
</xsl:if>
brand = <xsl:value-of
select = "brand"/> <br />
price = $<xsl:value-of
select = "price"/> per
<xsl:value-of select =
"price/@per"/> <br />
</xsl:for-each>
</xsl:for-each>
</body>
</html>
</xsl:template>
<xsl:template match = "/">
<xsl:variable name =
"nodes" select = "//product"/>
<html>
<head><title> XSL Demo
</title></head>
<body>
<table width="60%"
border = "1">
<caption
align="top">
<xsl:value-of select =
'"Today's Inventory"'/>
</caption>
<tr>
<th width="20%">
Brand </th>
<th width="20%">
Model </th>
<th width="30%">
Description </th>
<th width="30%">
Price </th>
</tr>
<xsl:for-each select =
"$nodes">
<tr>
<td> <xsl:value-of select =
"brand"/> </td>
<td>
<xsl:choose>
<xsl:when test = "model">
<xsl:value-of select =
"model"/>
</xsl:when>
<xsl:otherwise>
n/a
</xsl:otherwise>
</xsl:choose>
</td>
<td> <xsl:value-of select =
"description"/> </td>
<td>
$<xsl:value-of select =
"price"/> per
<xsl:value-of select =
"price/@per"/></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
<order>
<item quant = "2">
<upc> 324 </upc>
<brand> Kellogs </brand>
<model> Cheerios
</model>
</item>
<item quant = "3">
<upc> 298 </upc>
<brand> Peets </brand>
</item>
</order>
<xsl:stylesheet
xmlns:xsl =
"http://www.w3.org/1999/XSL/Transform"
version = "1.1">
<xsl:variable name = "inventory"
select = "document('inventory.xml')"/>
<xsl:template match = "/">
<xsl:variable name =
"total">
<xsl:call-template name =
"getTotal">
<xsl:with-param name =
"items" select = "//item"/>
</xsl:call-template>
</xsl:variable>
<html>
<head><title> XSL Demo
</title></head>
<body>
<table align = "center"
width="75%" border = "1">
<caption
align="top">
<xsl:value-of select =
'"Your Invoice"'/>
</caption>
<tr>
<th width="20%">
Brand </th>
<th width="20%">
Model </th>
<th width="20%">
Quantity </th>
<th width="40%">
Price </th>
</tr>
<xsl:for-each select =
"//item">
<xsl:variable name =
"brand" select = "brand"/>
<xsl:variable name =
"model" select = "model"/>
<xsl:variable name =
"price" select =
"$inventory//product[brand =
$brand and
(not(model) or model =
$model)]/price"/>
<xsl:variable name =
"unit" select =
"$inventory//product[brand =
$brand and
(not(model) or model =
$model)]/price/@per"/>
<tr>
<td> <xsl:value-of select =
"brand"/> </td>
<td>
<xsl:choose>
<xsl:when test = "model">
<xsl:value-of select =
"model"/>
</xsl:when>
<xsl:otherwise> n/a
</xsl:otherwise>
</xsl:choose>
</td>
<td> <xsl:value-of select =
"@quant"/> </td>
<td>
$<xsl:value-of select =
"$price"/> per
<xsl:value-of select =
"$unit"/>
</td>
</tr>
</xsl:for-each>
</table>
<br /> Your total =
$<xsl:value-of select = "$total"/>
</body>
</html>
</xsl:template>
<xsl:template name = "getTotal">
<xsl:param name =
"items"/>
<xsl:choose>
<xsl:when test =
"$items">
<xsl:variable name =
"brand" select = "$items[1]/brand"/>
<xsl:variable name =
"model" select = "$items[1]/model"/>
<xsl:variable name =
"price" select =
"$inventory//product[brand
= $brand and
(not($items[1]/model) or
model = $model)]/price"/>
<xsl:variable name =
"quant" select = "$items[1]/@quant"/>
<xsl:variable name =
"subtotal1" select =
"$quant *
$price"/>
<xsl:message> <!-- for
debugging -->
entering getTotal
price = $<xsl:value-of
select = "$price"/>
quant = <xsl:value-of
select = "$quant"/>
</xsl:message>
<xsl:variable name =
"subtotal2">
<xsl:call-template name =
"getTotal">
<xsl:with-param name =
"items" select =
"$items[position()
!= 1]"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select =
"$subtotal1 + $subtotal2"/>
</xsl:when>
<xsl:otherwise> 0
</xsl:otherwise>
</xsl:choose>
</xsl:template>
entering getTotal
price = $ 4.25
quant = 2
entering getTotal
price = $ 8.33
quant = 3
Here's the format of the polynomial x^3 + 3x^2 + 2x + 5 represented as the XML document poly1.xml:
<polynomial>
<monomial>
<coeff> 1 </coeff>
<var> x </var>
<exp> 3 </exp>
</monomial>
<monomial>
<coeff> 3 </coeff>
<var> x </var>
<exp> 2 </exp>
</monomial>
<monomial>
<coeff> 2 </coeff>
<var> x </var>
<exp> 1 </exp>
</monomial>
<monomial>
<coeff> 5 </coeff>
<var> x </var>
<exp> 0 </exp>
</monomial>
</polynomial>
Here's the structure of our test harness, main.xsl:
<?xml version = "1.0" ?>
<xsl:stylesheet
xmlns:xsl =
"http://www.w3.org/1999/XSL/Transform"
version = "1.1">
<xsl:include href = "polys.xsl"/>
<xsl:output method = "xml" indent = "yes" />
<xsl:template match = "/"> ... </xsl:template>
</xsl:stylesheet>
Our main template matches the root of poly1.xml, then calls templates from polys.xsl:
<xsl:template match = "/">
<results>
<result
of = "polyPrint">
<xsl:call-template name =
"polyPrint">
<xsl:with-param name=
"poly" select = "polynomial"/>
</xsl:call-template>
</result>
<result of =
"polyEval">
<xsl:call-template name =
"polyEval">
<xsl:with-param name=
"poly"
select =
"polynomial/monomial"/>
<xsl:with-param name =
"xval" select = "3.0"/>
</xsl:call-template>
</result>
<result of = "deriv">
<polynomial>
<xsl:call-template name =
"deriv">
<xsl:with-param name=
"poly"
select =
"polynomial/monomial"/>
</xsl:call-template>
</polynomial>
</result>
<result of =
"printDeriv">
<xsl:variable name =
"dp">
<xsl:call-template name =
"deriv">
<xsl:with-param name=
"poly"
select =
"polynomial/monomial"/>
</xsl:call-template>
</xsl:variable>
<xsl:call-template name =
"polyPrint">
<xsl:with-param name =
"poly" select = "$dp"/>
</xsl:call-template>
</result>
</results>
</xsl:template>
Here's the output produced:
<?xml version="1.0" encoding="utf-8"?>
<results>
<result of="polyPrint">
x^3
+ 3 x^2 + 2
x +
5
</result>
<result of="polyEval">
65
</result>
<result of="deriv">
<polynomial>
<monomial
xmlns:Math="java:java.lang.Math">
<coeff>3</coeff>
<var> x </var>
<exp>2</exp>
</monomial>
<monomial
xmlns:Math="java:java.lang.Math">
<coeff>6</coeff>
<var> x </var>
<exp>1</exp>
</monomial>
<monomial
xmlns:Math="java:java.lang.Math">
<coeff>2</coeff>
<var> x </var>
<exp>0</exp>
</monomial>
<monomial
xmlns:Math="java:java.lang.Math">
<coeff>0</coeff>
<var> x </var>
<exp> 0 </exp>
</monomial>
</polynomial>
</result>
<result
of="printDeriv">
3x^2 + 6 x + 2 + 0
</result>
</results>
This sketches how static Java methods can be called from a style sheet.
<?xml version = "1.0" ?>
<xsl:stylesheet
xmlns:xsl =
"http://www.w3.org/1999/XSL/Transform"
version = "1.1"
xmlns:Math="java:java.lang.Math">
<xsl:script implements-prefix = "Math"
xmlns:math =
"java:java.lang.Math"
language = "java"
src =
"java:java.lang.Math"/>
<xsl:template name = "polyPrint"> ... </xsl:template>
<xsl:template name = "monoPrint"> ... </xsl:template>
<xsl:template name = "polyEval"> ... </xsl:template>
<xsl:template name = "monoEval"> ... </xsl:template>
<xsl:template name = "polyDeriv"> ... </xsl:template>
<xsl:template name = "monoDeriv"> ... </xsl:template>
</xsl:stylesheet>
<xsl:template name = "polyEval">
<xsl:param name =
"poly"/>
<xsl:param name =
"xval"/>
<xsl:choose>
<xsl:when test =
"$poly">
<xsl:variable name =
"val1">
<xsl:call-template name
="monoEval">
<xsl:with-param
name = "mono"
select = "$poly[1]"/>
<xsl:with-param name =
"xval" select = "$xval"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name =
"val2">
<xsl:call-template name
="polyEval">
<xsl:with-param name =
"poly"
select =
"$poly[position() != 1]"/>
<xsl:with-param name =
"xval" select = "$xval"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select =
"$val1 + $val2"/>
</xsl:when>
<xsl:otherwise> 0
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Calling Java to evaluate a monomial (only seems to work with Saxon): (???)
<xsl:template name = "monoEval">
<xsl:param name =
"mono"/>
<xsl:param name =
"xval"/>
<xsl:variable name =
"exp"
select =
"Math:pow(number($xval), number($mono/exp))"
xmlns:Math =
"java:java.lang.Math"/>
<xsl:value-of select =
"$mono/coeff * $exp"/>
</xsl:template>
<xsl:template name = "polyPrint">
<xsl:param name =
"poly"/>
<xsl:for-each select =
"$poly/monomial">
<xsl:call-template name =
"monoPrint">
<xsl:with-param name =
"mono" select = "."/>
</xsl:call-template>
<xsl:if test = "position()
!= last()"> + </xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name = "monoPrint">
<xsl:param name =
"mono"/>
<xsl:if test = "$mono/coeff !=
1">
<xsl:value-of select =
"$mono/coeff"/>
</xsl:if>
<xsl:if test = "$mono/exp !=
0">
<xsl:value-of select =
"$mono/var"/>
</xsl:if>
<xsl:if test = "$mono/exp
> 1">
^<xsl:value-of select =
"$mono/exp"/>
</xsl:if>
</xsl:template>
<xsl:template name = "deriv">
<xsl:param name =
"poly"/>
<xsl:for-each select =
"$poly">
<xsl:call-template name =
"derivMono">
<xsl:with-param name =
"mono" select = "."/>
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template name = "derivMono">
<xsl:param name =
"mono"/>
<monomial>
<coeff>
<xsl:value-of select =
"$mono/coeff * $mono/exp"/>
</coeff>
<var>
<xsl:value-of select =
"$mono/var"/>
</var>
<exp>
<xsl:choose>
<xsl:when test =
"$mono/exp > 0">
<xsl:value-of select =
"$mono/exp - 1"/>
</xsl:when>
<xsl:otherwise> 0
</xsl:otherwise>
</xsl:choose>
</exp>
</monomial>
</xsl:template>
Although we can't assume much about the ordering of elements in a node set, it does have some order (often this is the document order) and so we can treat node sets as lists. If we can't predict the size of a node set ahead of time, then we must iterate through the node set, processing each element. This can be done using the for-each element:
<xsl:for-each select = "NODES">
<!-- process each node -->
</xsl:for-each>
In some cases, the result of processing an element of a node set may depend on the results produced by processing the preceding elements. Since variables are not updatable in XSLT, we must use recursion, passing intermediary results as parameters.
Many functions that process lists of values can be defined using list recursion:
f(list)
= init, if list is empty
= combine(process(head(list)), f(tail(list))),
otherwise
This works because the tail of a list is always shorter than the list. After enough calls to f, the tail will eventually be empty and the recursion will halt.
Most list recursions are so routine, that they can be regarded as instances of one of three generic list processing functions: map, filter, and accum.
map(list, amplifer)
= [], if list is empty
= putFirst(amplifer(head(list)), map(tail(list),
amplifer))
filter(list, tester)
= [], if list is empty
= filter(tail(list), tester), if
!tester(head(list))
= putFirst(head(list),
filter(tail(list), tester)), otherwise
accum(list, init, combiner)
= init, if list is empty
= combiner(head(list),
accum(tail(list), init, combiner)),
otherwise
These functions are generic, because the actual processing of individual list members is determined by a function that's passed as a parameter (i.e., the last parameter).
For example, assume we are given a list:
nums = [1, 9, -6, 0, 2]
Assume we would like to compute the sum of squares of the positive elements of this list. We begin by filtering out the non-positive members:
nums1 = filter(nums, isPositive)
In this case we assume isPositive is a function of the form:
isPositive(n) = (0 < n)
The result should look like this:
nums1 = [1, 9, 2]
Next, we use map to create a new list by squaring each member of nums1:
nums2 = map(nums1, square)
We assume square is defined by:
square(n) = n * n
The resulting list should look like this:
nums2 = [1, 81, 4]
Finally, we use accum to add the members of nums2:
sum = accum(nums2, 0, +)
Unfolding this recursion produces the expression:
sum = 1 + (81 + (4 +
0)) = 86
For simplicity, we assume our XML inputs are lists of numbers:
<list>
<item> 1 </item>
<item> 9 </item>
<item> -6 </item>
<item> 0.3 </item>
<item> 2 </item>
<item> 91 </item>
<item> 8.5 </item>
<item> -1 </item>
</list>
After the copy of the input tree, the result file displays the result of applying three templates to the input list: map (square), accum (+), and filter (positive):
<?xml version="1.0" encoding="UTF-8"?>
<results>
<result of="copy">
<list>
<item> 1 </item>
<item> 9 </item>
<item> -6 </item>
<item> 0.3 </item>
<item> 2 </item>
<item> 91 </item>
<item> 8.5 </item>
<item> -1 </item>
</list>
</result>
<result
of="filter">
<list>
<item
xmlns="www.demo.org"> 1 </item>
<item
xmlns="www.demo.org"> 9 </item>
<item
xmlns="www.demo.org"> 0.3 </item>
<item
xmlns="www.demo.org"> 2 </item>
<item
xmlns="www.demo.org"> 91 </item>
<item
xmlns="www.demo.org"> 8.5 </item>
</list>
</result>
<result
of="map">
<list>
<item
xmlns="www.demo.org">1</item>
<item
xmlns="www.demo.org">81</item>
<item
xmlns="www.demo.org">36</item>
<item
xmlns="www.demo.org">0.09</item>
<item
xmlns="www.demo.org">4</item>
<item xmlns="www.demo.org">8281</item>
<item
xmlns="www.demo.org">72.25</item>
<item
xmlns="www.demo.org">1</item>
</list>
</result>
<result
of="accum">104.8</result>
</results>
Our test harness imports rather than includes a file containing our list processing templates. The imported templates will have lower priority than the local templates. This gives us an opportunity to override the imported templates we are unhappy with, and keep the ones we want or don't need.
Our style sheet declares that it generates XML instead of HTML, the default. The main template matches the root nod of any XML document containing a list. The copy-of element is used to write a copy of the list to the result tree. Using value-of instead would only write the text nodes to the result tree.
<?xml version = "1.0" ?>
<xsl:stylesheet
xmlns:xsl =
"http://www.w3.org/1999/XSL/Transform"
version = "1.0">
<xsl:import href = "listprocs.xsl"/>
<xsl:output method = "xml" indent = "yes" />
<!-- override default templates -->
<xsl:template name = "combiner"> ... </xsl:template>
<xsl:template name = "amplifier"> ... </xsl:template>
<xsl:template name = "tester"> ... </xsl:template>
<!-- main template -->
<xsl:template match = "list">
<results>
<result of = "copy">
<xsl:copy-of select =
".."/>
</result>
<result of = "map">
... </result>
<result of =
"filter"> ... </result>
<result of =
"accum"> ... </result>
</results>
</xsl:template>
</xsl:stylesheet>
Here are the template calls. Too keep things as generic as possible, even the tag of the list is passed as a parameter:
<result of =
"filter">
<list>
<xsl:call-template name =
"filter">
<xsl:with-param name =
"vals" select = "*"/>
<xsl:with-param name =
"tag" select = "'item'"/>
</xsl:call-template>
</list>
</result>
<result of = "map">
<list>
<xsl:call-template name =
"map">
<xsl:with-param name =
"vals" select = "*"/>
<xsl:with-param name =
"tag" select = "'item'"/>
</xsl:call-template>
</list>
</result>
<result of =
"accum">
<xsl:call-template name =
"accum">
<xsl:with-param name =
"vals" select = "*"/>
<xsl:with-param name =
"init" select = "0"/>
</xsl:call-template>
</result>
We need to override the inherited combiner, amplifier, and tester templates called by accum, map, and filter, respectively
<xsl:template name = "combiner">
<xsl:param name =
"arg1"/>
<xsl:param name =
"arg2"/>
<xsl:value-of select = "$arg1 +
$arg2"/>
</xsl:template>
<xsl:template name = "amplifier">
<xsl:param name =
"arg"/>
<xsl:value-of select = "$arg *
$arg"/>
</xsl:template>
<xsl:template name = "tester">
<xsl:param name =
"arg"/>
<xsl:value-of select = "$arg >
0"/>
</xsl:template>
Here is the structure of listproc.xsl, our file of reusable list processing templates:
<?xml version = "1.0"?>
<xsl:stylesheet
xmlns = "www.demo.org"
xmlns:xsl =
"http://www.w3.org/1999/XSL/Transform"
version = "1.1">
<!-- accum template -->
<xsl:template name =
"accum"> ... </xsl:template>
<!-- map template -->
<xsl:template name =
"map"> ... </xsl:template>
<!-- filter template -->
<xsl:template name =
"filter"> ... </xsl:template>
<!-- default templates -->
<xsl:template name =
"combiner"> ... </xsl:template>
<xsl:template name =
"amplifier"> ... </xsl:template>
<xsl:template name =
"tester"> ... </xsl:template>
</xsl:stylesheet>
The iterative version of accum only adds the members of a list of numbers:
<xsl:template name = "accum">
<xsl:param name =
"vals"/>
<xsl:value-of select = "sum($vals)"/>
</xsl:template>
To accumulate using an arbitrary element combiner, we need recursion. Recursion is necessary here because we must remember and update the result. This can be done by passing the result to the recursive call (tail recursion) or by using a normal list recursion.
Here's the normal list recursion:
<xsl:template name = "accum">
<xsl:param name =
"vals"/> <!-- node set to be processed -->
<xsl:param name = "init" select
= "0"/> <!--
<xsl:choose>
<!-- when vals not empty
-->
<xsl:when test =
"$vals">
<!-- fetch head -->
<xsl:variable name =
"head">
<xsl:value-of select =
"$vals[1]"/>
</xsl:variable>
<!-- recursively accum tail
-->
<xsl:variable name =
"tail">
<xsl:call-template name =
"accum">
<xsl:with-param name =
"vals"
select = "$vals[position()
!= 1]" />
<xsl:with-param name =
"init" select = "$init"/>
</xsl:call-template>
</xsl:variable>
<!-- combine results -->
<xsl:call-template name =
"combiner">
<xsl:with-param name =
"arg1" select = "$head"/>
<xsl:with-param name =
"arg2" select = "$tail"/>
</xsl:call-template>
</xsl:when>
<!-- when vals is empty -->
<xsl:otherwise>
<xsl:value-of select = "$init"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
The tail recursion is simpler and more efficient:
<xsl:template name = "accum">
<xsl:param name =
"vals"/>
<xsl:param name = "result"
select = "0"/>
<xsl:choose>
<xsl:when test =
"$vals">
<xsl:call-template name =
"accum">
<xsl:with-param
name = "vals"
select =
"$vals[position() != 1]" />
<xsl:with-param
name =
"result"
select = "$vals[1]
+ $result"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select =
"$result"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name = "accum">
<xsl:param name =
"vals"/>
<xsl:param name = "result"
select = "0"/>
<xsl:choose>
<xsl:when test = "$vals">
<xsl:call-template name =
"accum">
<xsl:with-param
name = "vals"
select =
"$vals[position() != 1]" />
<xsl:with-param
name =
"result">
<!-- combine results
-->
<xsl:call-template name
= "combiner">
<xsl:with-param name
= "arg1" select = "$vals[1]"/>
<xsl:with-param name
= "arg2" select = "$result"/>
</xsl:call-template>
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select =
"$result"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Map doesn't need to remember intermediate results, so an iteration works well:
<xsl:template name = "map">
<xsl:param name =
"vals"/>
<xsl:param name = "tag"
select = "'item'"/>
<xsl:for-each select =
"$vals">
<xsl:element name =
"{$tag}">
<xsl:call-template name =
"amplifier">
<xsl:with-param name =
"arg">
<xsl:value-of select =
"."/>
</xsl:with-param>
</xsl:call-template>
</xsl:element>
</xsl:for-each>
</xsl:template>
Like map, filter can also be iterative:
<xsl:template name = "filter">
<xsl:param name =
"vals"/>
<xsl:param name = "tag"
select = "'item'"/>
<xsl:for-each select =
"$vals">
<xsl:variable name =
"pass">
<xsl:call-template name =
"tester">
<xsl:with-param name = "arg"
select = "."/>
</xsl:call-template>
</xsl:variable>
<xsl:if test = "$pass =
'true'">
<xsl:element name =
"{$tag}">
<xsl:value-of select =
"."/>
</xsl:element>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name = "amplifier">
<xsl:param name =
"arg"/>
<xsl:message> Entering default
amplifier </xsl:message>
</xsl:template>
<xsl:template name = "combiner">
<xsl:param name =
"arg1"/>
<xsl:param name = "arg2"/>
<xsl:message> Entering default
combiner </xsl:message>
</xsl:template>
<xsl:template name = "tester">
<xsl:message> Entering default
tester </xsl:message>
<xsl:param name =
"arg"/>
</xsl:template>
<xsl:template name = "filter">
<xsl:param name =
"vals"/>
<xsl:param name = "tag"
select = "'item'"/>
<xsl:choose>
<!-- when vals is not empty
-->
<xsl:when test =
"$vals">
<!-- fetch head -->
<xsl:variable name =
"head">
<xsl:value-of select =
"$vals[1]"/>
</xsl:variable>
<!-- test head -->
<xsl:variable name =
"pass">
<xsl:call-template name =
"tester">
<xsl:with-param name =
"arg" select = "$vals[1]"/>
</xsl:call-template>
</xsl:variable>
<!-- filter tail -->
<xsl:variable name =
"tail">
<xsl:call-template name =
"filter">
<xsl:with-param name =
"vals"
select = "$vals[position()
!= 1]" />
<xsl:with-param name =
"tag" select = "$tag"/>
</xsl:call-template>
</xsl:variable>
<!-- copy elements that
passed -->
<xsl:if test =
"$pass='true'">
<xsl:element name =
"{$tag}">
<xsl:copy-of select =
"$head"/>
</xsl:element>
</xsl:if>
<xsl:copy-of select =
"$tail" />
</xsl:when>
<!-- vals is empty -->
<xsl:otherwise>
<xsl:value-of select = "null"
/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name = "map">
<xsl:param name =
"vals"/>
<xsl:param name = "tag"
select = "'item'"/>
<xsl:choose>
<!-- when vals not empty
-->
<xsl:when test =
"$vals">
<!-- amplify and tag first
val -->
<xsl:variable name =
"head">
<xsl:element name = "{$tag}">
<xsl:call-template name =
"amplifier">
<xsl:with-param name =
"arg">
<xsl:value-of select
= "$vals[1]"/>
</xsl:with-param>
</xsl:call-template>
</xsl:element>
</xsl:variable>
<!-- recursively map tail
-->
<xsl:variable name =
"tail">
<xsl:call-template name =
"map">
<xsl:with-param name =
"vals"
select = "$vals[position()
!= 1]" />
<xsl:with-param name =
"tag" select = "$tag"/>
</xsl:call-template>
</xsl:variable>
<!-- copy head element (not
its value) to result -->
<xsl:copy-of select =
"$head"/>
<!-- copy tail elements (not
their values) to result -->
<xsl:copy-of select =
"$tail" />
</xsl:when>
<!-- vals is empty -->
<xsl:otherwise>
<xsl:value-of select = "null"
/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
A matrix is a rectangular grid of numbers:
|10 20 30|
|14 24
34|
|18 28
38|
Here's how we might represent this matrix as an XML document:
<matrix>
<row>
<col> 10 </col>
<col> 20 </col>
<col> 30 </col>
</row>
<row>
<col> 14 </col>
<col> 24 </col>
<col> 34 </col>
</row>
<row>
<col> 18 </col>
<col> 28 </col>
<col> 38 </col>
</row>
</matrix>
Our main style sheet generates XML output. It includes various reusable matrix manipulation templates from matops.xsl. The output will simply be a collection of matrices:
<matrices> MATRIX MATRIX ... MATRIX </matrices>
The copy-of element is used to copy the input matrix to the output file:
<xsl:output
method = "xml"
indent = "yes" />
<xsl:include href = "matops.xsl"/>
<xsl:template match = "matrix">
<matrices>
the original:
<xsl:copy-of
select = "."/>
<!-- etc. -->
</matrices>
</xsl:template>
Thus, our output file begins with:
<matrices>
the original:
<matrix>
<row>
<col> 10 </col>
<col> 20 </col>
<col> 30 </col>
</row>
<row>
<col> 14 </col>
<col> 24 </col>
<col> 34 </col>
</row>
<row>
<col> 18 </col>
<col> 28 </col>
<col> 38 </col>
</row>
</matrix>
Next, the main style sheet calls the get-col template from matops:
the first column:
<matrix>
<row>
<xsl:call-template name =
"get-col">
<xsl:with-param name =
"rows" select = "row"/>
<xsl:with-param name =
"pos" select = "1"/>
</xsl:call-template>
</row>
</matrix>
This generates the output:
the first column:
<matrix>
<row>
<col> 10 </col>
<col> 14 </col>
<col>
18 </col>
</row>
</matrix>
Here's the get-col template from matops. It requires two parameters: the set of row nodes and the position of the column to be returned:
<xsl:template name = "get-col">
<xsl:param name =
"rows"/>
<xsl:param name = "pos"/>
<xsl:for-each select =
"$rows">
<xsl:copy-of select =
"col[position() = $pos]"/>
</xsl:for-each>
</xsl:template>
A special case of get-col is the get-last-col template:
<xsl:template name = "get-last-col">
<xsl:param name =
"rows"/>
<xsl:call-template name =
"get-col">
<xsl:with-param name =
"rows" select = "$rows"/>
<xsl:with-param name =
"pos" select = "count($rows)"/>
</xsl:call-template>
</xsl:template>
Next, the main style sheet creates a matrix by deleting a column:
but first column:
<matrix>
<xsl:call-template name =
"but-col">
<xsl:with-param name =
"rows" select = "row"/>
<xsl:with-param name =
"pos" select = "1"/>
</xsl:call-template>
</matrix>
Here's the output produced:
but first column:
<matrix>
<row>
<col> 20 </col>
<col> 30 </col>
</row>
<row>
<col> 24 </col>
<col> 34 </col>
</row>
</matrix>
Here's the but-col template from matops.xsl:
<xsl:template name = "but-col">
<xsl:param name =
"rows"/>
<xsl:param name =
"pos"/>
<xsl:for-each select =
"$rows">
<row>
<xsl:copy-of select =
"col[position() != $pos]"/>
</row>
</xsl:for-each>
</xsl:template>
The but-last-col is a special case:
<xsl:template name = "but-last-col">
<xsl:param name =
"rows"/>
<xsl:call-template name =
"but-col">
<xsl:with-param name =
"rows" select = "$rows"/>
<xsl:with-param name =
"pos" select = "count($rows)"/>
</xsl:call-template>
</xsl:template>
Finally, the transpose is computed. (Recall that the transpose of an n x m matrix A is the m x n matrix that results by making the row i column j entry of A the row j column i entry of B):
The transpose:
<xsl:call-template name =
"transpose">
<xsl:with-param name =
"mat" select = "."/>
</xsl:call-template>
Here's the output produced:
The transpose:
<matrix>
<row>
<col> 10 </col>
<col> 14 </col>
<col> 18 </col>
</row>
<row>
<col> 20 </col>
<col> 24 </col>
<col> 28 </col>
</row>
<row>
<col> 30 </col>
<col> 34 </col>
<col> 38 </col>
</row>
</matrix>
Actually, matops has two transpose templates. The first one only works for 3 x 3 matrices:
<xsl:template name = "transpose-3x3">
<matrix>
<row>
<col>
<xsl:value-of select =
"row[1]/col[1]"/>
</col>
<col>
<xsl:value-of select = "row[2]/col[1]"/>
</col>
<col>
<xsl:value-of select =
"row[3]/col[1]"/>
</col>
</row>
<row>
<col>
<xsl:value-of select =
"row[1]/col[2]"/>
</col>
<col>
<xsl:value-of select =
"row[2]/col[2]"/>
</col>
<col>
<xsl:value-of select =
"row[3]/col[2]"/>
</col>
</row>
<row>
<col>
<xsl:value-of select =
"row[1]/col[3]"/>
</col>
<col>
<xsl:value-of select =
"row[2]/col[3]"/>
</col>
<col>
<xsl:value-of select =
"row[3]/col[3]"/>
</col>
</row>
</matrix>
</xsl:template>
We can generalize this by:
<xsl:template name = "transpose">
<xsl:param name =
"mat"/>
<matrix>
<xsl:for-each select =
"$mat/row">
<xsl:variable name =
"row-pos" select = "position()"/>
<row>
<xsl:for-each select =
"col">
<xsl:variable name =
"col-pos" select = "position()"/>
<col>
<xsl:value-of select
=
"$mat/row[$col-pos]/col[$row-pos]"/>
</col>
</xsl:for-each>
</row>
</xsl:for-each>
</matrix>
</xsl:template>
When generating HTML, matrices can be converted into tables:
Here's a typical call:
<xsl:call-template name = "toTable">
<xsl:with-param name =
"mat" select = "."/>
<xsl:with-param name =
"title" select = "'A'"/>
</xsl:call-template>
Here's the template from matops
that generates the template:
<xsl:template name = "toTable">
<xsl:param name =
"mat"/>
<xsl:param name = "title"
select = "'The Matrix'"/>
<table width="60%" border = "1">
<caption align="top">
<xsl:value-of select = "$title" />
</caption>
<xsl:for-each select = "$mat/row">
<tr>
<xsl:for-each select = "col">
<td align = "center"> <xsl:value-of select =
"." /> </td>
</xsl:for-each>
</tr>
</xsl:for-each>
</table>
</xsl:template>