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: "Mukul Gandhi" <gandhi.mukul@xxxxxxxxx>
Date: Wed, 28 May 2008 22:16:11 +0530
I think following idea might work:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
                       version="2.0">

<xsl:output method="xml" indent="yes" />

<xsl:template match="REPORT">
  <xsl:variable name="x" select="REPORT_FORMAT/PARENT_NODE" />
  <xsl:variable name="y" select="REPORT_FORMAT/CHILD_NODE" />
  <xsl:variable name="order-by" select="for $i in
tokenize(REPORT_FORMAT/ORDER_BY,',') return normalize-space($i)" />
  <result>
    <xsl:for-each select="*[local-name() = $x]/*[local-name() = $y]">
      <xsl:sort select="string-join(for $j in $order-by return
*[local-name() = $j],':')" />
      <xsl:copy-of select="." />
    </xsl:for-each>
  </result>
</xsl:template>

</xsl:stylesheet>

You must ensure that in this part of the stylesheet .......
*[local-name() = $j],':')" /> the string (here a colon) should be
unusual and does not exist in the data.

I have done limited testing of this stylesheet, and it seems to work fine.

On Thu, May 22, 2008 at 4:50 AM, Rebecca Sapir
<rsapir@xxxxxxxxxxxxxxxxxxxx> wrote:
> 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($string,',')))"/>
>   </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.


-- 
Regards,
Mukul Gandhi

Current Thread