This is the mail archive of the
xsl-list@mulberrytech.com
mailing list .
SUMMARY: Re: Sorting on a variable
- To: xsl-list at mulberrytech dot com
- Subject: SUMMARY: Re: Sorting on a variable
- From: "John E. Simpson" <simpson at polaris dot net>
- Date: Mon, 16 Oct 2000 19:37:36 -0400
- Reply-To: xsl-list at mulberrytech dot com
Thanks to input from Mike Kay here on the list, and off-list from Jeni
Tennison, I solved my problem of sorting on a "variable." (This isn't quite
what I needed to do, as I'll explain in a moment.) Turns out I wasn't far
off the mark after all, although I had some problems with copying....
The general solution I'm using is to clone the source tree, adding to it
for each product a new child element containing the calculated value
(USD-equivalent currency figures, in this case). This clone (an RTF,
converted to a node-set using your favorite processor's node-set()
extension function) then gets sorted on the created element. Works like a
champ. (Sample code below.)
*However*, I discovered a VERY BIG "GOTCHA" to be aware of if you use this
technique yourself. That is that the extra element created in the RTF is
probably not going to be in the HTML namespace. For instance, I called this
element <usd_equiv>, filled it (so I thought) with the desired results, and
tried to place it in the result tree. This worked only as long as I did not
include the HTML 4.0 namespace declaration for the result tree (i.e.,
xmlns="http://www.w3.org/TR/REC-html40"). With that namespace declaration,
there was no <usd_equiv> element to be displayed (or if there was, it was
always empty), let alone sorted on. This was true with both Saxon 5.51 and
MSXML Sept '00 release.
Bearing that in mind, then, here's a solution which does what I wanted.
Again, the sample source document looks like this (currency values jiggered
a bit to make the sort effect more obvious):
><products>
> <product prodID="A1234">
> <name>First prod</name>
> <price curr="USD">29.95</price>
> </product>
> <product prodID="A5678">
> <name>Second prod</name>
> <price curr="GBP">115.95</price>
> </product>
> <product prodID="A9012">
> <name>Third prod</name>
> <price curr="EU">29.95</price>
> </product>
> <product prodID="A9012">
> <name>Fourth prod</name>
> <price curr="USD">50.00</price>
> </product>
></products>
And here's a stylesheet which sorts the products by their "usd_equiv":
<?xml version="1.0"?>
<!-- Note: Both Saxon and MSXML namespaces declared below.
Use the one appropriate for your case, and change
reference to saxon:node-set() below to msxml:node-set()
if you want to use the MSXML product, e.g. w/IE5.5+.
Including the HTML 4.0 namespace declaration, i.e.
xmlns="http://www.w3.org/TR/REC-html40", makes the
usd_equiv element empty. So it's not included below. :) -->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:saxon="http://icl.com/saxon"
xmlns:msxml="urn:schemas-microsoft-com:xslt">
<xsl:template match="/">
<html>
<head><title>Sorting an RTF</title></head>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<!-- Template rule for root element <products> -->
<xsl:template match="products">
<!-- This variable will hold the RTF, including the
usd_equiv element for each product -->
<xsl:variable name="prods_with_usd">
<xsl:apply-templates select="product" mode="calc_usd" />
</xsl:variable>
<table border="1">
<tr>
<th>Name/Version</th>
<th>Price / Curr</th>
<th>Price (USD)</th>
</tr>
<!-- Note that the apply-templates doesn't select the
<product> children, which would be "conventional," but
the RTF (converted to node-set) created by the above
variable. If you're using the MSXML processor, remember
to change "saxon:" to "msxml:". -->
<xsl:apply-templates select="saxon:node-set($prods_with_usd)/product">
<xsl:sort select="usd_equiv" data-type="number" />
</xsl:apply-templates>
</table>
</xsl:template>
<!-- When the mode is "calc_usd" (as in apply-templates within
the xsl:variable above which creates the prods_with_usd RTF),
copy the product node and its attributes, and add a
<usd_equiv> child for each <product> element -->
<xsl:template match="product" mode="calc_usd">
<xsl:copy>
<xsl:copy-of select="@*" />
<xsl:copy-of select="*" />
<!-- <xsl:element> can be replaced, if you want, with a simple
literal result element, i.e. <usd_equiv>. In either case, the
element so created isn't in the HTML namespace, which is
apparently why adding the HTML 4.0 namespace declaration
makes this <usd_equiv> element "disappear." -->
<xsl:element name="usd_equiv">
<xsl:choose>
<xsl:when test="price/@curr='USD'">
<xsl:value-of select="format-number(price, '#,##0.00')"/>
</xsl:when>
<xsl:when test="price/@curr='GBP'">
<xsl:value-of select="format-number(price * 1.47275, '#,##0.00')"/>
</xsl:when>
<xsl:when test="price/@curr='EU'">
<xsl:value-of select="format-number(price * 0.864379, '#,##0.00')"/>
</xsl:when>
<xsl:otherwise>Unknown Currency</xsl:otherwise>
</xsl:choose>
</xsl:element>
</xsl:copy>
</xsl:template>
<!-- When the mode isn't specified, simply create a table row
for the product in question. -->
<xsl:template match="product">
<tr>
<td valign="top"><xsl:value-of select="name"/></td>
<td align="right">
<xsl:value-of select="price"/> / <xsl:value-of select="price/@curr"/>
</td>
<td align="right"><xsl:value-of select="usd_equiv"/></td>
</tr>
</xsl:template>
</xsl:stylesheet>
===============================================================
John E. Simpson | "He asked me if I knew what
http://www.flixml.org | time it was. I said, 'Yes, but
XML Q&A: http://www.xml.com | not right now.'" (Steven Wright)
XSL-List info and archive: http://www.mulberrytech.com/xsl/xsl-list