1

Taking the t:mult3 example in the FXSL's testFunc-curry.xsl file, I've changed it slightly so that the first 2 parameters are sequences. These sequences seem to loose any empty items when curried.

<xsl:function name="foo:bar" as="xs:anyAtomicType">
        <xsl:param name="headers" as="xs:string*"/>
        <xsl:param name="row" as="xs:anyAtomicType*"/>
        <xsl:param name="column" as="xs:string"/>
        <xsl:message select="$column"/>
        <xsl:message select="count($row)"/>
        <xsl:value-of select="$row[index-of($headers, $column)]"/>
</xsl:function>

This works fine when called directly, or when the first 2 paramters are curried, providing the row sequence has no empty (string) items. However if one of the row items is empty (eg think of CSV-style input where a column's value like the test_col_two below is optional. Note the headers are mandatory and taken from the the first row of the CSV):

<xsl:variable name="row" select="tokenize(.,',')"/>
<xsl:variable name="rw" select="foo:bar($headers, $row)"/>
<xsl:message select="$rw"/>
<xsl:variable name="a" select="f:apply($rw,'test_col_one')"/>
<xsl:variable name="b" select="f:apply($rw,'test_col_two')"/>
<xsl:variable name="c" select="f:apply($rw,'test_col_three')"/>
<xsl:message select="concat($a,$b,$c)"/>

If test_col_two is empty, variable b will contain the value of test_col_three, not test_col_two.

Have I misunderstood the example, or is this an issue with FXSL?

I can see that the XML output by foo:bar($headers, $row) does include empty elements representing the empty strings, so the information is at least initially captured correctly from what I see.

Full code below.

<xsl:import href="fxsl-xslt2/f/func-curry.xsl"/>
<foo:bar/>
<xsl:function name="foo:bar" as="node()">
    <xsl:sequence select="document('')/*/foo:bar[1]"/>
</xsl:function>
<xsl:function name="foo:bar" as="xs:anyAtomicType">
    <xsl:param name="headers" as="xs:string*"/>
    <xsl:param name="row" as="xs:anyAtomicType*"/>
    <xsl:param name="column" as="xs:string"/>
    <xsl:message select="$column"/>
    <xsl:message select="count($row)"/>
    <xsl:value-of select="$row[index-of($headers, $column)]"/>
</xsl:function>
<xsl:function name="foo:bar" as="node()">
    <xsl:param name="headers" as="xs:string*"/>
    <xsl:sequence select="f:curry(foo:bar(), 3, $headers)"/>
</xsl:function>
<xsl:function name="foo:bar" as="node()">
    <xsl:param name="headers" as="xs:string*"/>
    <xsl:param name="row" as="xs:anyAtomicType*"/>
    <xsl:sequence select="f:curry(foo:bar(), 3, $headers, $row)"/>
</xsl:function>
<xsl:template match="foo:bar" mode="f:FXSL">
    <xsl:param name="arg1" as="xs:string*"/>
    <xsl:param name="arg2" as="xs:anyAtomicType*"/>
    <xsl:param name="arg3" as="xs:string"/>
    <xsl:sequence select="foo:bar($arg1,$arg2,$arg3)"/>
</xsl:template>
Phil
  • 592
  • 6
  • 15
  • Interesting question. I am not familiar enough with fxsl to try to answer it without testing and debugging code so I tried downloading which seems to be the latest release for XSLT 2 from https://sourceforge.net/projects/fxsl/files/FXSL%20for%20XSLT%202/FXSL%202.0/ only to find that it seems not to work with current Saxon versions as it uses some `xdt` types which I think with the XSLT and XPath 2 final spec were moved to the `xs` namespace. Is there any newer release you are using that was updated? It would also help if you provide a minimal but complete XSLT sample with `$headers` defined. – Martin Honnen Mar 19 '18 at 13:55
  • On a different note, XPath 3.1 with higher-order functions allows partial function application without the need to use FXSL so if you can move to XSLT 3 with XPath 3.1 and higher-order function support (which unfortunately Saxon 9.8 HE does not support) you might be able to avoid the cumbersome approach you need to use with FXSL. – Martin Honnen Mar 19 '18 at 14:28
  • @MartinHonnen - you'll need to get the head of CVS - then it works OK with Saxon: cvs -z3 -d:pserver:anonymous@a.cvs.sourceforge.net:/cvsroot/fxsl co -P fxsl-xslt2 – Phil Mar 19 '18 at 14:37
  • @MartinHonnen - yes I've read some of the overviews of XSLT3 that suggest this stuff is now part of the standard. Alas, for now I'm stuck with the version of Saxon on Ubuntu 16.04 which only supports XSLT 2. It might be that I can take a different design approach to mean I don't need fxsl - but given the need for me to partially specify the function for each CSV row, fxsl seemed like a reasonable way of doing this. – Phil Mar 19 '18 at 14:44

1 Answers1

1

Looking at

 <xsl:function name="int:makeArg" as="element()">
    <xsl:param name="arg1"/>
    <arg>
      <xsl:choose>
        <xsl:when test="exists($arg1[2])">
          <xsl:attribute name="s"/>

          <xsl:for-each select="$arg1">
            <e t="{f:type(.)}"><xsl:sequence select="."/></e>
          </xsl:for-each>
        </xsl:when>
        <xsl:otherwise>
          <xsl:attribute name="t" select="f:type($arg1)"/>
          <xsl:sequence select="$arg1"/>
        </xsl:otherwise>
      </xsl:choose>
    </arg>
  </xsl:function>

  <xsl:function name="int:getArg">
    <xsl:param name="pargNode" as="element()*"/>

    <xsl:sequence select=
     "if(not($pargNode/@s))
        then 
           if(not($pargNode/@t) or $pargNode/@t = 'xml:node')
              then $pargNode/node()
              else
                f:apply(f:Constructor($pargNode/@t), $pargNode/node() )
        else
          for $varg in $pargNode/e/node()
            return  
               if(not($varg/../@t) or $varg/../@t = 'xml:node')
                  then $varg
                  else
                     f:apply(f:Constructor($varg/../@t), $varg )
     "
    />
  </xsl:function>

where arguments are created with int:makeArg and processed with int:getArg it seems that makeArg sets up an e element with the value of the original arg as the contents (<e t="{f:type(.)}"><xsl:sequence select="."/></e>) and then getArg expects a child node in e as it does for $varg in $pargNode/e/node(). However, if you have a sequence with empty strings inside then I think this approach swallows the empty strings as xsl:sequence select="''" with not construct any child node in the e element and then the getArg with e/node() obviously fails to find that argument. So it looks that FXSL has some flaw there, perhaps rewriting

          for $varg in $pargNode/e/node()
            return  
               if(not($varg/../@t) or $varg/../@t = 'xml:node')
                  then $varg
                  else
                     f:apply(f:Constructor($varg/../@t), $varg )

as

          for $varg in $pargNode/e
            return  
               if(not($varg/@t) or $varg/@t = 'xml:node')
                  then $varg/node()
                  else
                     f:apply(f:Constructor($varg/@t), $varg )

suffices. Hopefully Dimitre Novatchev @DimitreNovatchev can tell you more about this as the author of the library.

Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
  • Thanks for this - it fits with my observations, I could see `` being used for empty items, rather than `Some Text`. So it seem to explain it, as no child node is matched. – Phil Mar 19 '18 at 15:09