tags:

views:

341

answers:

3

I have an XML document with several <person> elements, each of which contains the <name> of the person, and several <preferred-number> elements inside a grouping <preferred-numbers> element.

I already found that to obtain the greatest <preferred-number> I have to do an <xsl:apply-template> with a <xsl:sort> inside it and then take the first element, as XSLT v1.0 doesn't have fn:max().

But I guess that this would give me the greatest value from all. So, how could I arrange a stylesheet to extract each person's <name> with their greatest <preferred-number>?

A: 

When it comes to sorting stuff in XSLT 1.0 you have to use a slightly roundabout approach as I recall. If you haven't already you'll want to look into Muenchian grouping to give you some ideas to start with.

I've found this site to be a very useful resource.

glenatron
A: 

Thanks to glenatron.
Very helpful.
Sorry, I can't vote, I'm not registered.

+1  A: 

One can use the "maximum" template as provided by FXSL -- the Functional Programming Library for XSLT. FXSL is written entirely in XSLT itself.

Here is an example of using the "maximum" template:

When this transformation:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://fxsl.sf.net/"
xmlns:maximum-own-compare="maximum-own-compare"
xmlns:Mycompare="Mycompare"
exclude-result-prefixes="xsl f Mycompare"
>
   <xsl:import href="maximum.xsl"/>

   <xsl:output method="text"/>

   <!-- This transformation must be applied to:
        numList.xml 
    -->

     <Mycompare:Mycompare/>

    <xsl:template match="/">
      <xsl:variable name="vCMPFun" 
           select="document('')/*/Mycompare:*[1]"/>

      <xsl:call-template name="maximum">
        <xsl:with-param name="pList" select="/*/*"/>
        <xsl:with-param name="pCMPFun" select="$vCMPFun"/> 

      </xsl:call-template>
    </xsl:template>

    <xsl:template name="MyIsGreater" mode="f:FXSL"
      match="Mycompare:*">
         <xsl:param name="arg1"/>
         <xsl:param name="arg2"/>

         <xsl:choose>
          <xsl:when test="$arg1 > $arg2">1</xsl:when>
          <xsl:otherwise>0</xsl:otherwise>
         </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

is applied on this XML document:

<nums>
  <num>01</num>
  <num>02</num>
  <num>03</num>
  <num>04</num>
  <num>05</num>
  <num>06</num>
  <num>07</num>
  <num>08</num>
  <num>09</num>
  <num>10</num>
</nums>

the wanted result is obtained.

10

Note, that I can pass any needed "compare" operation as parameter! For example, if my compare operation returns 1 whenever $arg1 < $arg2, then the above transformation will produce "01" -- the minimum of all values.

Now, I will show two solutions for the original problem.

Solution1 (using FXSL's "maximum" template):

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://fxsl.sf.net/"
xmlns:Mycompare="Mycompare"
exclude-result-prefixes="xsl f Mycompare"
>
   <xsl:import href="maximum.xsl"/>

   <xsl:output omit-xml-declaration="yes" indent="yes"/>

     <Mycompare:Mycompare/>

      <xsl:variable name="vCMPFun"
           select="document('')/*/Mycompare:*[1]"/>

    <xsl:template match="/">
      <persons>
       <xsl:apply-templates select="*/*"/>
      </persons>
    </xsl:template>
    <xsl:template match="person">
     <person>
      <xsl:call-template name="maximum">
        <xsl:with-param name="pList" select="*/*"/>
        <xsl:with-param name="pCMPFun" select="$vCMPFun"/> 

      </xsl:call-template>
     </person>
    </xsl:template>

    <xsl:template name="MyIsGreater" mode="f:FXSL"
      match="Mycompare:*">
         <xsl:param name="arg1"/>
         <xsl:param name="arg2"/>

         <xsl:choose>
          <xsl:when test="$arg1 > $arg2">1</xsl:when>
          <xsl:otherwise>0</xsl:otherwise>
         </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the following XML document:

<t>
    <person>
     <preferred-number>
      <preferred-number>8</preferred-number>
      <preferred-number>10</preferred-number>
      <preferred-number>4</preferred-number>
      <preferred-number>12</preferred-number></preferred-number>
    </person>
    <person>
     <preferred-number>
      <preferred-number>2</preferred-number>
      <preferred-number>11</preferred-number>
      <preferred-number>15</preferred-number>
      <preferred-number>6</preferred-number></preferred-number>
    </person>
    <person>
     <preferred-number>
      <preferred-number>10</preferred-number>
      <preferred-number>44</preferred-number>
      <preferred-number>9</preferred-number></preferred-number>
    </person>
</t>

the wanted result is produced:

<persons>
   <person>
      <preferred-number>12</preferred-number>
   </person>
   <person>
      <preferred-number>15</preferred-number>
   </person>
   <person>
      <preferred-number>44</preferred-number>
   </person>
</persons>

Solution2 (using a template written by hand):

When this transformation:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

    <xsl:template match="/">
     <persons>
       <xsl:apply-templates select="*/*"/>
     </persons>
    </xsl:template>

    <xsl:template match="person">
     <person>
      <xsl:call-template name="maximum">
        <xsl:with-param name="pList" select="*/*"/>
      </xsl:call-template>
     </person>
    </xsl:template>

    <xsl:template name="maximum">
      <xsl:param name="pList"/>

      <xsl:choose>
       <xsl:when test="not($pList)">
        -99999999999999999999
       </xsl:when>
       <xsl:otherwise>
         <xsl:variable name="vLen" select="count($pList)"/>

         <xsl:choose>
          <xsl:when test="$vLen = 1">
           <xsl:value-of select="$pList[1]"/>
          </xsl:when>
          <xsl:otherwise>
           <xsl:variable name="vHalf"
             select="floor($vLen div 2)"/>

           <xsl:variable name="vMax1">
             <xsl:call-template name="maximum">
              <xsl:with-param name="pList"
                select="$pList[not(position() > $vHalf)]"/>
             </xsl:call-template>
           </xsl:variable>

           <xsl:variable name="vMax2">
             <xsl:call-template name="maximum">
              <xsl:with-param name="pList"
                select="$pList[position() > $vHalf]"/>
             </xsl:call-template>
           </xsl:variable>

           <xsl:value-of
            select="$vMax1*($vMax1 > $vMax2)
                   +
                    $vMax2*($vMax2 >= $vMax1)
            "/>
          </xsl:otherwise>
         </xsl:choose>
       </xsl:otherwise>
      </xsl:choose>
    </xsl:template>
</xsl:stylesheet>
is applied on the same XML document, again the correct result is produced:
<persons>
   <person>12</person>
   <person>15</person>
   <person>44</person>
</persons>
Dimitre Novatchev