RE: [xsl] help with recursive function

Subject: RE: [xsl] help with recursive function
From: "Michael Kay" <mike@xxxxxxxxxxxx>
Date: Sat, 10 May 2008 23:02:53 +0100
Firstly, as Andrew replied: it's really useful to declare the types of
parameters and expected result in xsl:function.

Secondly, I would expect this function to operate on strings. A function
that operates on strings shouldn't usually contain an xsl:value-of
instruction, which creates a text node - there should be no need to have any
nodes involved. Use xsl:sequence in place of xsl:value-of.

Finally, and I think this is the cause of your error, if you write a
function (or indeed a template) that creates multiple strings (or multiple
text nodes) then they are not automatically concatenated: the result of the
function or template is a sequence of strings (or text nodes). Automatic
concatenation of text nodes happens only when they form part of the content
of an element constructor or document constructor.  You need to concatenate
the strings "by hand" using concat() or string-join().

Sometimes I use

<xsl:function name="x" as="xs:string">
  <xsl:value-of>
    <xsl:sequence select="'one string'"/>
    <xsl:sequence select="'another string'"/>
  </xsl:value-of>
</xsl:function>

as a way of achieving the concatenation. However, combining the strings into
a text node which is then atomized to a string is not very clean
conceptually, and you need to watch out for space separators being added.

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

> -----Original Message-----
> From: Mario Madunic [mailto:hajduk@xxxxxxxx] 
> Sent: 06 May 2008 17:14
> To: xsl-list@xxxxxxxxxxxxxxxxxxxxxx
> Subject: [xsl] help with recursive function
> 
> Hi,
> 
> I'm having a problem with a recursive function in 2.0.
> 
> I'm iterating through the value of a node that is tokenized 
> on spaces. I normalize the 'word' to lower-case and remove an 
> 's if it exists. Then check it against a library xml file 
> (spellingBee.xml), if the 'word' isn't listed then just 
> upper-case the first letter and leaving the rest as is; 
> otherwise replace the 'word' with the corrected spelling in 
> the spellingBee.xml. Finish by concatenating new 'word' with 
> the value of g_AposS. That was simple enough to create but I 
> decided to take it one step further and check the current 'word'
> for a simple set of delimiters (-/\); doing this to keep down 
> the number of entries in spellingBee.xml. Then start ripping 
> it apart into two pieces: before the delimiter and after the 
> delimiter. The 'word' before the delimiter would be checked 
> against spellingBee.xml and the rules mentioned above would 
> be applied.
> The bit after the delimiter would be tossed back into the 
> function by calling the function again. That is when I 
> receive the following error message:
> 
> [xslt]
> [xslt]
> [xslt] original headline :: MEET THE BEATLES 
> ABC's-A.B.C./A.b.c.'s-ABC\abc's\nbc/n.b.c.'s
> [xslt]
> [xslt]
> [xslt] e:\XSLT\cleanUp.xsl:259: Fatal Error! A sequence of 
> more than one item is not allowed as the first argument of 
> concat() [xslt] Failed to process null
> 
> 
> input XML node
> 
> <head>MEET THE BEATLES 
> ABC's-A.B.C./A.b.c.'s-ABC\abc's\nbc/n.b.c.'s</head>
> 
> 
> 
> called in cleanUp.xsl
> 
> <xsl:variable name="l_TokenizedString" 
> select="tokenize($l_HeadTemp, ' ')" />
> 
> <xsl:for-each select="$l_TokenizedString">
>   <!-- line 259 mentioned in error message -->
>   <xsl:value-of select="concat(f:f_UpperLowerCaseFix(.), if 
> (not(position() =
> last())) then ' ' else '')" />
> </xsl:for-each>
> 
> 
> 
> spellingBee.xml looks like the following
> 
> <fixCase>
>   <word>
>     <lowerCase>abc</lowerCase>
>     <corrected>ABC</corrected>
>   </word>
>   <word>
>     <lowerCase>a.b.c.</lowerCase>
>     <corrected>A.B.C.</corrected>
>   </word>
> </fixCase>
> 
> 
> 
> The function itself
> 
>   <xsl:function name="f:f_UpperLowerCaseFix">
> 
>     <xsl:param name="p_String" />
> 
>     <!-- this variable actually exists in a global variables 
> file and is only defined here for posting on XSLT mailing list -->
>     <xsl:variable name="g_AposS">'s</xsl:variable>
> 
>     <xsl:variable name="l_Delimiter">
>       <xsl:choose>
>         <xsl:when test="contains($p_String, 
> '-')"><xsl:value-of select="'-'"
> /></xsl:when>
>         <xsl:when test="contains($p_String, 
> '/')"><xsl:value-of select="'/'"
> /></xsl:when>
>         <xsl:when test="contains($p_String, 
> '\')"><xsl:value-of select="'\'"
> /></xsl:when>
>         <xsl:otherwise><xsl:value-of select="'none'" 
> /></xsl:otherwise>
>       </xsl:choose>
>     </xsl:variable>
> 
>     <xsl:choose>
>       <xsl:when test="$l_Delimiter = 'none'">
> 
>         <xsl:variable name="l_String" select="lower-case(if 
> (ends-with($p_String, $g_AposS)) then 
> substring-before($p_String, $g_AposS) else $p_String)" />
>         <xsl:variable name="l_AposS" select="if 
> (ends-with($p_String, $g_AposS)) then $g_AposS else ''" />
> 
>         <xsl:choose>
>           <xsl:when
> test="doc('../../supXML/spellingBee.xml')/fixCase//lowerCase[.
>  = $l_String]">
>             <xsl:value-of
> select="concat(doc('../../supXML/spellingBee.xml')/fixCase/wor
> d//corrected[preceding-sibling::*[.
> = $l_String]], $l_AposS)" />
>           </xsl:when>
>           <xsl:otherwise>
>             <xsl:value-of 
> select="concat(upper-case(substring($l_String, 1, 1)), 
> substring($l_String, 2, string-length($l_String)), $l_AposS)" />
>           </xsl:otherwise>
>         </xsl:choose>
>       </xsl:when>
> 
>       <xsl:otherwise>
> 
>         <xsl:variable name="l_StringBeforeDelimiter"
> select="substring-before(lower-case($p_String), $l_Delimiter)" />
>         <xsl:variable name="l_StringAfterDelimiter"
> select="substring-after(lower-case($p_String), $l_Delimiter)" />
> 
>         <xsl:variable name="l_String" select="if 
> (ends-with($l_StringBeforeDelimiter, $g_AposS)) then 
> substring-before($l_StringBeforeDelimiter, $g_AposS) else 
> $l_StringBeforeDelimiter" />
>         <xsl:variable name="l_AposS" select="if 
> (ends-with($l_StringBeforeDelimiter, $g_AposS)) then $g_AposS 
> else ''" />
> 
>         <xsl:choose>
>           <xsl:when
> test="doc('../../supXML/spellingBee.xml')/fixCase//lowerCase[.
>  = $l_String]">
>             <xsl:value-of
> select="concat(doc('../../supXML/spellingBee.xml')/fixCase/wor
> d//corrected[preceding-sibling::*[.
> = $l_String]], $l_AposS, $l_Delimiter)" />
>             <xsl:value-of 
> select="f:f_UpperLowerCaseFix($l_StringAfterDelimiter)" />
>           </xsl:when>
>           <xsl:otherwise>
>             <xsl:value-of 
> select="concat(upper-case(substring($l_String, 1, 1)), 
> substring($l_String, 2, string-length($l_String)), $l_AposS, 
> $l_Delimiter)" />
>             <xsl:value-of 
> select="f:f_UpperLowerCaseFix($l_StringAfterDelimiter)" />
>           </xsl:otherwise>
>         </xsl:choose>
>       </xsl:otherwise>
>     </xsl:choose>
> 
>   </xsl:function>
> 
> The desired output from the sample node above should look like
> 
> input: MEET THE BEATLES ABC's-A.B.C./A.b.c.'s-ABC\abc's\nbc/n.b.c.'s
> output: Meet The Beatles ABC's-A.B.C./A.B.C.'s-ABC\ABC's\nbc/n.b.c.'s
> 
> 
> Any help will be appreciated.
> 
> Marijan (Mario) Madunic

Current Thread