xsl-list
[Top] [All Lists]

RE: [xsl] help with recursive function

2008-05-10 15:03:18
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(_at_)imag(_dot_)net] 
Sent: 06 May 2008 17:14
To: xsl-list(_at_)lists(_dot_)mulberrytech(_dot_)com
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


--~------------------------------------------------------------------
XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list
To unsubscribe, go to: http://lists.mulberrytech.com/xsl-list/
or e-mail: 
<mailto:xsl-list-unsubscribe(_at_)lists(_dot_)mulberrytech(_dot_)com>
--~--



--~------------------------------------------------------------------
XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list
To unsubscribe, go to: http://lists.mulberrytech.com/xsl-list/
or e-mail: <mailto:xsl-list-unsubscribe(_at_)lists(_dot_)mulberrytech(_dot_)com>
--~--

<Prev in Thread] Current Thread [Next in Thread>