RE: [xsl] [XSLT 2.0] Determining the datatype of the value returned from a function?

Subject: RE: [xsl] [XSLT 2.0] Determining the datatype of the value returned from a function?
From: "Roger L. Costello" <costello@xxxxxxxxx>
Date: Fri, 17 Jun 2005 13:59:45 -0400
Many thanks David.  A subtle, but extremely important point.
 
I figure that others may encounter this problem, so below I have summarized
the problem and its solution.  /Roger

Determining the datatype of the value returned by a function

Suppose that you want to create an XSLT function which returns a value that
may be one of any number of different (atomic) datatypes, e.g., xs:integer,
xs:double, xs:decimal, xs:string, etc.  

With the value that the function returns you would like to determine its
datatype.  For example, if the function returns the integer 5 then: 

 "$value instance of xs:integer" 

should yield true (assume that $value is a variable that holds the value
returned from the function).

Here is an example that shows invoking a function and then testing the value
that is
returned to determine its datatype:

<xsl:template match="/">
    <xsl:variable name="test1" select="'A'"/>
    <xsl:variable name="value" select="ex:Test($test1)"/>
    <xsl:choose>
        <xsl:when test="$value instance of xsd:integer">
            <xsl:message>INTEGER</xsl:message>
        </xsl:when>
        <xsl:when test="$value instance of xsd:string">
            <xsl:message>STRING</xsl:message>
        </xsl:when>
        ...
        <xsl:otherwise>
             <xsl:message>OTHER</xsl:message>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

How you design the function is crucial.  The below function gives
undesirable results.
It always returns a text node that contains a string value:

<xsl:function name="ex:Test">
    <xsl:param name="letter"/>
    <xsl:choose>
         <xsl:when test="$letter eq 'A'">
             <xsl:variable name="num" select="5" as="xsd:integer"/>
             <xsl:value-of select="$num"/>
         </xsl:when>
         <xsl:when test="$letter eq 'B'">
             <xsl:variable name="num" select="5.00" as="xsd:decimal"/>
             <xsl:value-of select="$num"/>
         </xsl:when>
         ...
         <xsl:otherwise>
             <xsl:variable name="message" select="'Error'" as="xsd:string"/>
             <xsl:value-of select="$message"/>
         </xsl:otherwise>
    </xsl:choose>
</xsl:function>

Consider this code snippet:

<xsl:variable name="num" select="5" as="xsd:integer"/>
<xsl:value-of select="$num"/>

You might think that the xsl:value-of element will output an integer 5.
In fact, it will not.  The xsl:value-of statement always outputs:
1. A text node, and
2. The datatype of the value in the text node is always string.
That is, with the xsl:value-of element you will always loose 
datatype information.  Beware!

Now consider this code snippet:

<xsl:variable name="num" select="5" as="xsd:integer"/>
<xsl:sequence select="$num"/>

The xsl:sequence element behaves very differently from the xsl:value-of
element.  In the above example, the xsl:sequence element will simply
output the number 5, with the integer datatype intact.
So, if we want to create a function that returns an atomic value, and we
want
the datatype of the atomic value intact, then the function must use
xsl:sequence,
and it must not use xsl:value-of.

Here is the correct way to design the function:

<xsl:function name="ex:Test">
    <xsl:param name="letter"/>
    <xsl:choose>
         <xsl:when test="$letter eq 'A'">
             <xsl:variable name="num" select="5" as="xsd:integer"/>
             <xsl:sequence select="$num"/>
         </xsl:when>
         <xsl:when test="$letter eq 'B'">
             <xsl:variable name="num" select="5.00" as="xsd:decimal"/>
             <xsl:sequence select="$num"/>
         </xsl:when>
         ...
         <xsl:otherwise>
             <xsl:variable name="message" select="'Error'" as="xsd:string"/>
             <xsl:sequence select="$message"/>
         </xsl:otherwise>
    </xsl:choose>
</xsl:function>

Here's a complete example, that works as desired:

<xsl:function name="ex:Test">
    <xsl:param name="letter"/>
    <xsl:choose>
         <xsl:when test="$letter eq 'A'">
             <xsl:variable name="num" select="5" as="xsd:integer"/>
             <xsl:sequence select="$num"/>
         </xsl:when>
         <xsl:otherwise>
             <xsl:variable name="message" select="'Error'" as="xsd:string"/>
             <xsl:sequence select="$message"/>
         </xsl:otherwise>
    </xsl:choose>
</xsl:function>

<xsl:template match="/">
    <xsl:variable name="test1" select="'A'"/>
    <xsl:variable name="result" select="ex:Test($test1)"/>
    <xsl:choose>
        <xsl:when test="$result instance of xsd:integer">
            <xsl:message>INTEGER</xsl:message>
        </xsl:when>
        <xsl:when test="$result instance of xsd:string">
            <xsl:message>STRING</xsl:message>
        </xsl:when>
        <xsl:when test="$result instance of xdt:anyAtomicType">
            <xsl:message>xdt:anyAtomicType</xsl:message>
        </xsl:when>
        <xsl:when test="$result instance of text()">
            <xsl:message>text()</xsl:message>
        </xsl:when>
        <xsl:otherwise>
             <xsl:message>OTHER</xsl:message>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

Current Thread