RE: [xsl] max() of three xsl:number results

Subject: RE: [xsl] max() of three xsl:number results
From: "Michael Kay" <mike@xxxxxxxxxxxx>
Date: Fri, 23 Dec 2005 09:20:56 -0000
The data type of $X, $Y, and $Z is in each case document-node(). When you
atomize a document node the result is xdt:untypedAtomic. When you apply
max() to a untypedAtomic value, it is converted to a double, and "1.1.1"
cannot be converted to a double.

Change the variables to strings, and max() will then do a string comparison:

<xsl:variable name="X" as="xs:string">
   <xsl:number count="section" level="multiple" />
</xsl:variable>

This also has the advantage that a string is a much simpler object than a
document, so there is less overhead in constructing it and using it.

I would strongly recommend ALWAYS declaring the types of your variables and
parameters in XSLT 2.0. It really makes a big difference to ease of
debugging, especially when you use polymorphic functions like max().

However you still have a problem: the max of "1.2.1" and "1.10.3" is
"1.2.1". In principle you can define a collation that sorts 1.10.3 after
1.2.1, but that depends on the facilities offered by your XSLT processor
(your error message doesn't look like one from Saxon). A more pragmatic
solution might be to use <xsl:number format="00001"/> so that all components
of the section number are fixed-length.

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

> -----Original Message-----
> From: Trevor Nicholls [mailto:trevor@xxxxxxxxxxxxxxxxxx] 
> Sent: 23 December 2005 05:05
> To: xsl-list@xxxxxxxxxxxxxxxxxxxxxx
> Subject: [xsl] max() of three xsl:number results
> 
> Hi
> 
> I have a <document> which consists of arbitrarily nested 
> <section>s of mixed
> content. Scattered throughout my document I have <target> 
> elements. I am
> trying to generate a listing which maps each <target> to its nearest
> <section>:
> a) if a target immediately precedes a section then I want 
> that section's
> location, otherwise
> b) I want the closest predecessor section, which may be
>  1) the section containing the target, or
>  2) the nearest section which precedes it.
> 
> In the following simple example I have given each section a 
> title which
> reflects its location in the structure, and given each target 
> a label which
> does the same.
> 
> <!-- ===== XML ===== -->
> <document>
>  <section title="1">
>   <section title="1.1">
>    <section title="1.1.1">
>     <section title="1.1.1.1">
>     </section>
>     <target label="1.1.1.1a" />
>    </section>
>    <target label="1.1.1.1b" />
>   </section>
>   <target label="1.2" />
>   <section title="1.2">
>   </section>
>   <section title="1.3">
>    <target label="1.3" />
>   </section>
>   <para />
>  </section>
>  <section title="2">
>   <target label="2" />
>   <para />
>  </section>
>  <section title="3">
>  </section>
>  <target label="3" />
> </document>
> <!-- ========== -->
> 
> The following stylesheet arrives at the numbers I want, viz:
> 
> <!-- ===== XSL 2.0 ===== -->
> <xsl:stylesheet version="2.0"
>    xmlns:xsl="http://www.w3.org/1999/XSL/Transform";>
> <xsl:output method="text" />
> 
> <xsl:template match="/">
> <xsl:apply-templates />
> </xsl:template>
> 
> <xsl:template match="target">
>  <xsl:variable name="X">
>   <xsl:number count="section" level="multiple" />
>  </xsl:variable>
>  <xsl:variable name="Y">
>   <xsl:number select="preceding::section[1]"
>      count="section" level="multiple" />
>  </xsl:variable>
>  <xsl:variable name="Z">
>   <xsl:choose>
>    <xsl:when test="following::*[1][self::section]">
>     <xsl:number select="following::*[1][self::section]"
>        count="section" level="multiple" />
>    </xsl:when>
>    <xsl:otherwise>
>     <xsl:value-of select="0" />
>    </xsl:otherwise>
>   </xsl:choose>
>  </xsl:variable>
> 
>  <xsl:value-of select="@label" />
>  <xsl:text> X</xsl:text><xsl:value-of select="$X">
>  <xsl:text> Y</xsl:text><xsl:value-of select="$Y">
>  <xsl:text> Z</xsl:text><xsl:value-of select="$Z">
>  <xsl:text>&#xa;</xsl:text>
> </xsl:template>
> <!-- ========== -->
> 
> When I apply this to the example XML above I get:
> 
> 1.1.1.1a  X1.1.1  Y1.1.1.1  Z0
> 1.1.1.1b  X1.1    Y1.1.1.1  Z0
> 1.2       X1      Y1.1.1.1  Z1.2
> 1.3       X1.3    Y1.2      Z0
> 2         X2      Y1.3      Z0
> 3         X       Y3        Z0
> 
> Clearly what I want is the (lexically) greatest value out of 
> X, Y and Z.
> But when I replace the target template's output with the 
> following lines:
> 
> <xsl:value-of select="@label" />
> <xsl:text> max </xsl:text>
> <xsl:value-of select="max(($X,$Y,$Z))" />
> <xsl:text>&#xa;</xsl:text>
> 
> I get an error:
> 
> Error in XPath 2.0 expression Invalid lexical value - '1.1.1'
> 
> What am I doing wrong?
> 
> Thanks
> Trevor

Current Thread