RE: [xsl] Sort XML based on Tokenized String of sort by fields

Subject: RE: [xsl] Sort XML based on Tokenized String of sort by fields
From: "Michael Kay" <mike@xxxxxxxxxxxx>
Date: Thu, 22 May 2008 10:24:24 +0100
As David suggests, I think I'd go for the approach of generating a
stylesheet. In effect your <REPORT_FORMAT> element defines a miniature
programming language, and a good way of implementing such languages is often
to translate them to XSLT. I've done similar things with report
specifications entered interactively on the screen. You're already doing
dynamic construction/evaluation of XPath expressions, so dynamic
construction of the entire stylesheet (or of the controlling framework, it
can always include/import a fixed module) isn't a major step from that.

Michael Kay
http://www.saxonica.com/  

> -----Original Message-----
> From: Rebecca Sapir [mailto:rsapir@xxxxxxxxxxxxxxxxxxxx] 
> Sent: 22 May 2008 00:21
> To: xsl-list@xxxxxxxxxxxxxxxxxxxxxx
> Subject: [xsl] Sort XML based on Tokenized String of sort by fields
> 
> I am also using the SAXON parser. 
> XSLT version 2
> 
> I am trying to sort by a tokenized string. I get an ORDER_BY 
> string with field names to sort by which are separated by commas. 
> 
> The number of fields in the order_by string names can vary!!!
> 
> The X and X_ROW names are not constant. They can vary and are 
> therefore passed in the PARENT_NODE and CHILD_NODE fields.
> For example: 
> 
> The XML I get is formatted as  
> 
> <REPORT>
>       <REPORT_HDR>
>       ...
>       </REPORT_HDR>
>             <REPORT_FORMAT>
>                         <ORDER_BY> DATE, ID_1, ID_2, TYPE</ORDER_BY>
>                         <PARENT_NODE>X</PARENT_NODE>
>                         <CHILD_NODE>X_ROW</CHILD_NODE>
>             </REPORT_FORMAT>
>             <X>
>                 <X_ROW>             
> 			      <DATE></DATE>
> 			      <ID_1></ID_1>
> 			      <TYPE ></TYPE>
> 			      ...
> 		    </X_ROW>
>                 <X_ROW>             
> 			      <DATE></DATE>
> 			      <ID_1></ID_1>
> 			      <TYPE ></TYPE>
> 			      ...
> 		    </X_ROW>
>                 <X_ROW>             
> 			      <DATE></DATE>
> 			      <ID_1></ID_1>
> 			      <TYPE ></TYPE>
> 			      ...
> 		    </X_ROW>
>                 <X_ROW>             
> 			      <DATE></DATE>
> 			      <ID_1></ID_1>
> 			      <TYPE ></TYPE>
> 			      ...
> 		    </X_ROW>
> </REPORT>
> 
> In this case I want to sort the X/X_ROW's by DATE then ID_1 
> then ID_2 etc
> 
> This is one solution I have so far...
> (The other one is below which uses recursion instead of tokenize)
> 
> <xsl:stylesheet 
> xmlns:xsl="http://www.w3.org/1999/XSL/Transform"; 
> version="2.0" xmlns:saxon="http://saxon.sf.net/";>
> <xsl:variable name="PATH" 
> select="REPORT/REPORT_FORMAT/PARENT_NODE" /> <xsl:variable 
> name="CHILD" select="REPORT/REPORT_FORMAT/CHILD_NODE" /> 
> <xsl:variable name="ORDER_BY" select="REPORT/REPORT_FORMAT/ORDER_BY"/>
> <xsl:variable name="ORDER_BY_TOKEN" select="tokenize($ORDER_BY,',')"/>
>             
> <xsl:template match="REPORT/*">
> 
> <xsl:choose>
> 
>  <xsl:when test="local-name() = $PATH">
> 
>   <xsl:copy>
> 
>     <xsl:for-each select="*">
>      <xsl:sort 
> select="saxon:evaluate(normalize-space($ORDER_BY_TOKEN[1]))"/>
>          
>      <xsl:sort 
> select="saxon:evaluate(normalize-space($ORDER_BY_TOKEN[2]))"/>
>            
>      <xsl:sort 
> select="saxon:evaluate(normalize-space($ORDER_BY_TOKEN[3]))"/>
>            
>      <xsl:sort 
> select="saxon:evaluate(normalize-space($ORDER_BY_TOKEN[4]))"/>
>            
>      <xsl:copy-of select="."/>                                
>                        
>     </xsl:for-each>
> 
>   </xsl:copy>
> 
>  </xsl:when>
> 
>  <xsl:otherwise>
> 
>   <xsl:copy-of select="."/>                                
> 
>  </xsl:otherwise>
> 
> </xsl:choose>
> 
> </xsl:template>
> 
> <xsl:template match="*">
> 
> <xsl:copy>
>   <xsl:apply-templates/>
> </xsl:copy>
> 
> </xsl:template>
> 
> </xsl:stylesheet>
> 
> My issue of course is that I am hard coding the 
> ORDER_BY_TOKEN item that I want. In truth there are a 
> variable number of fields that can get passed to me. 
> 
> Ideally if I could surround the sorts with a for loop that 
> would be what I am looking for. But this does not work:
>   <xsl:for-each select="*">
> 
>     <xsl:for-each select="1 to count($ORDER_BY_TOKEN)">
>       <xsl:sort 
> select="saxon:evaluate(normalize-space($ORDER_BY_TOKEN[.]))"/> 
>       <xsl:copy-of select="."/>        
>     </xsl:for-each>
> 
>   </xsl:for-each>
> 
> 
> Instead of tokenizing I tried a recursive function. This will 
> perform the sorts but not consecutively. So in effect the 
> last sort is really the only one that gets applied. Ie. it 
> does not have the effect of sort by Date then id_1 then id_2 then...
> 
> <xsl:stylesheet 
> xmlns:xsl="http://www.w3.org/1999/XSL/Transform"; 
> version="2.0" xmlns:saxon="http://saxon.sf.net/";>
> 
> <xsl:variable name="PATH" 
> select="REPORT/REPORT_FORMAT/PARENT_NODE" /> <xsl:variable 
> name="ORDER_BY" select="REPORT/REPORT_FORMAT/ORDER_BY"/>
> 
> 
> <xsl:template match="REPORT/*">
> 
> <xsl:choose>
> 
> <xsl:when test="local-name() = $PATH">
> 
>   <xsl:copy>
> 
>     <xsl:call-template name="tokenize">
>       <xsl:with-param name="string" select="$ORDER_BY" />
>     </xsl:call-template>
> 
>   </xsl:copy>
> 
>   </xsl:when>
> 
>   <xsl:otherwise>
>    <xsl:copy-of select="."/>                                
>   </xsl:otherwise>
> 
> </xsl:choose>
> </xsl:template>
> 
> <xsl:template match="*">
>   <xsl:copy>
>        <xsl:apply-templates/>
>   </xsl:copy>
> </xsl:template>
> 
> <xsl:template name="tokenize">
> 
>  <xsl:param name="string" />
> 
>  <xsl:choose>
> 
>   <xsl:when test="contains($string, ',')">
> 
>    <xsl:apply-templates>
>     <xsl:sort 
> select="saxon:evaluate(normalize-space(substring-before($strin
> g,',')))"/>
>    </xsl:apply-templates>
> 
>   <xsl:call-template name="tokenize">
>     <xsl:with-param name="string" 
> select="substring-after($string, ',')" />
>   </xsl:call-template>
> 
>  </xsl:when>
> 
>  <xsl:otherwise>
>   <xsl:copy>
> 
>    <xsl:apply-templates>
>     <xsl:sort 
> select="saxon:evaluate(normalize-space($string))"/>           
>    </xsl:apply-templates>
> 
>   <xsl:copy-of select="."/>
>  </xsl:copy>
>  </xsl:otherwise>
> 
> </xsl:choose>                        
> 
> </xsl:template>
> 
> </xsl:stylesheet>
>  
> Any help would be greatly appreciated.
> 
> Thanks.
> 
> 
> 
> 
> 
> 
> Merlin Securities - #1 Prime Broker North America and #1 
> Prime Broker Single Strategy Funds - Global Custodian 2007
> #1 Prime Broker for Hedge Funds under $1 Billion - Alpha Survey 2007
> 
> 
> 
>  
> --------------------------------------------------------------
> ------------
> This message contains information from Merlin Securities, 
> LLC, or from one of its affiliates, that may be confidential 
> and privileged. If you are not an intended recipient, please 
> refrain from any disclosure, copying, distribution or use of 
> this information and note that such actions are prohibited. 
> If you have received this transmission in error, please 
> notify the sender immediately by telephone or by replying to 
> this transmission.
>  
> Merlin Securities, LLC is a registered broker-dealer. 
> Services offered through Merlin Securities, LLC are not 
> insured by the FDIC or any other Federal Government Agency, 
> are not deposits of or guaranteed by Merlin Securities, LLC 
> and may lose value. Nothing in this communication shall 
> constitute a solicitation or recommendation to buy or sell a 
> particular security.

Current Thread