Question

I'd like to create a generic XSLT 2.0 function that would operate on xml like this:

<?xml version="1.0"?>
<foo xmlns:my="http://meohmy.org" xmlns:yours="http://meohmy2.org">
    <yours:pet my:type="dog" my:color="red">Cindy</yours:pet>
    <yours:pet my:type="cat">Felix</yours:pet>
    <yours:pet my:type="cat" my:color="green">Pai Mei</yours:pet>
</foo> 

and given a function signature like this:

my:doit('item',/foo/yours:/pet,@my:color)

(Where the first argument is a label, the second is the nodeset I want to report over, and a third for the value I want to output for each node in that second argument's nodeset, whether it has it or not).

I want it return data like this:

info=red;;green

Note that I want an empty placeholder corresponding to the element without the third argument in question, and order matters.

Since I'm doing this a lot in my stylesheet (dozens of times for different reasons), a function seemed a natural way to go. This is what I've come up with so far...

<?xml version="1.0"?>
<xsl:stylesheet version="2.0" 
    xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:my="http://meohmy.org" 
    xmlns:yours="http://meohmy2.org">

   <xsl:template match="/">
       <xsl:value-of select="my:doit('item',/foo/yours:pet,@my:color)"/>
   </xsl:template>

   <xsl:function name="my:doit" as="xs:string?">
      <xsl:param name="item" as="xs:string"/>
      <xsl:param name="value"/>
      <xsl:param name="subvalue"/>
      <xsl:if test="$value">
        <xsl:value-of>
             <xsl:value-of select="$item"/>
             <xsl:text>=</xsl:text>
             <xsl:for-each select="$value">
                  <xsl:value-of select="current()/$subvalue"/>
              <xsl:if test="position() != last()">
                   <xsl:text>;</xsl:text>
              </xsl:if>
             </xsl:for-each>
        </xsl:value-of>
    </xsl:if>
  </xsl:function>
</xsl:stylesheet>

However when I do this, I get the following value from the function doit():

item=;;

That suggests to me that the <xsl:value-of select="current()/$subvalue"/> is not correct in the style sheet. I'm close, I can feel it ;> Any suggestions?

Thanks!

Was it helpful?

Solution

In your example, you think that you are passing an XPath expression as the third argument, but you aren't - you are passing its value.

In XPath 3.0 there is a clean solution to this problem: you can pass a function as the third argument. So the call becomes

select="my:doit('item', /foo/yours:pet, function($e){$e/@my:color})"

and the implementation becomes

      <xsl:function name="my:doit" as="xs:string?">
          <xsl:param name="item" as="xs:string"/>
          <xsl:param name="value" as="element()*/>
          <xsl:param name="subvalue" as="function(element()) as xs:string?"/>
          <xsl:if test="$value">
            <xsl:value-of>
                 <xsl:value-of select="$item"/>
                 <xsl:text>=</xsl:text>
                 <xsl:value-of select="$value ! $subvalue(.)" separator=";"/>
            </xsl:value-of>
        </xsl:if>
      </xsl:function>

If you're not in a position to use XPath 3.0 (the above will work in Saxon-PE 9.5) then alternatives are (a) to use Dimitre Novatchev's FXSL library, which simulates higher-order functions in XSLT 2.0, or (b) instead of passing a function, pass an XPath expression in the form of a string, and use a xx:eval() extension function to evaluate the expression dynamically - many processors offer such an extension, but it's not in the standard language.

(Note, in XPath 3.0, A!B means essentially the same as "for $x in A return $x/B")

OTHER TIPS

Try this stylesheet

    <?xml version="1.0"?>
    <xsl:stylesheet version="2.0" 
        xmlns:xs="http://www.w3.org/2001/XMLSchema" 
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
        xmlns:my="http://meohmy.org" 
        xmlns:yours="http://meohmy2.org">

       <xsl:template match="/">
           <xsl:value-of select="my:doit('item',/foo/yours:pet,'my:color')"/>
       </xsl:template>

       <xsl:function name="my:doit" as="xs:string?">
          <xsl:param name="item" as="xs:string"/>
          <xsl:param name="value"/>
          <xsl:param name="subvalue"/>
          <xsl:if test="$value">
            <xsl:value-of>
                 <xsl:value-of select="$item"/>
                 <xsl:text>=</xsl:text>
                 <xsl:for-each select="$value">
                      <xsl:value-of select="current()/attribute()[name() = $subvalue]"/>
                  <xsl:if test="position() != last()">
                       <xsl:text>;</xsl:text>
                  </xsl:if>
                 </xsl:for-each>
            </xsl:value-of>
        </xsl:if>
      </xsl:function>
    </xsl:stylesheet>

It gave me result

item=red;;green
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top