This is the mail archive of the xsl-list@mulberrytech.com mailing list .


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]

SUMMARY: Re: Sorting on a variable


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

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]