1

I am trying to use saxon:evaluate to reduce repeated code in an xsl function. However anything I try returns an error.

This is a section of the repetitive code.

<!--Select by outputclass and group by attribute-->
<xsl:when test="$from='oclass-attribute'">
    <xsl:for-each-group select="$compose//*[contains(@outputclass,$sel)]" group-by="@*[name()=$group]">
        <xsl:sort select="current-grouping-key()" />
        <element key="{current-grouping-key()}">
            <xsl:if test="number(current-group()[1])=number(current-group()[1])">
                <xsl:attribute name="max" select="max(current-group())"/>
                <xsl:attribute name="sum" select="sum(current-group())"/>
            </xsl:if>
            <xsl:copy-of select="current-group()[1]"/>
        </element>
    </xsl:for-each-group>
</xsl:when>

<!--Select by node and group by child node-->
<xsl:when test="$from='node-childnode'">
    <xsl:for-each-group select="$compose//*[name()=$sel]" group-by="child::*[name()=$group]">
        <xsl:sort select="current-grouping-key()" />
        <element key="{current-grouping-key()}">
            <xsl:if test="number(current-group()[1])=number(current-group()[1])">
                <xsl:attribute name="max" select="max(current-group())"/>
                <xsl:attribute name="sum" select="sum(current-group())"/>
            </xsl:if> 
            <xsl:copy-of select="current-group()[1]"/>
        </element>
    </xsl:for-each-group>
</xsl:when>     

What I want is to pass a parameter dictating whether it is an element-name or outputclass-attribute that is selected.

Then another parameter dictating what to group by: attribute or parent, child, following or preceding node.

What I have tried is below:

<xsl:variable name="oSel">
        <xsl:choose>
            <xsl:when test="starts-with($from,'oclass-')">
                <xsl:value-of select="$compose//*[contains(@outputclass,$sel)]"/>
            </xsl:when>
            <xsl:when test="starts-with($from,'node-')">
                <xsl:value-of select="$compose//*[name()=$sel]"/>    
            </xsl:when> 
            <xsl:otherwise/>
        </xsl:choose>
    </xsl:variable>

    <xsl:variable name="oGroup">
        <xsl:choose>
            <xsl:when test="ends-with($from,'-attribute')">
                <xsl:value-of select="*/@*[local-name()=$group]"/>
            </xsl:when>
            <xsl:when test="ends-with($from,'-childnode')">
                <xsl:value-of select="*/*[name()=$group]"/>
            </xsl:when>
            <xsl:when test="ends-with($from,'-parentnode')">
                <xsl:value-of select="parent::*[name()=$group]"/>
            </xsl:when>
            <xsl:when test="ends-with($from,'-followingnode')">
                <xsl:value-of select="following-sibling::*[name()=$group][1]"/>
            </xsl:when>
            <xsl:when test="ends-with($from,'-precedingnode')">
                <xsl:value-of select="preceding-sibling::*[name()=$group][1]"/>
            </xsl:when>
            <xsl:otherwise/>
        </xsl:choose>
    </xsl:variable>

    <xsl:variable name="test">
        <!--<xsl:for-each-group select="saxon:evaluate($oSel)" group-by="saxon:evaluate($oGroup)">-->
            <xsl:for-each-group select="saxon:evaluate($oSel)" group-by="saxon:evaluate($oGroup)">
            <element key="{current-grouping-key()}">
                <xsl:if test="number(current-group()[1])=number(current-group()[1])">
                    <xsl:attribute name="max" select="max(current-group())"/>
                    <xsl:attribute name="sum" select="sum(current-group())"/>
                </xsl:if>
                <xsl:copy-of select="current-group()[1]"/>
            </element>
        </xsl:for-each-group>
    </xsl:variable>

I have looked into the saxon documentation and tried all sorts of solutions but none of them are working. Is it possible to do this?

Should have added that I am using Saxon 9.1.0.8 - Sorry, still new to XSLT

Aaron
  • 27
  • 1
  • 4

3 Answers3

2

Firstly, saxon:evaluate() isn't the right tool for the job.

Now, why isn't it working? To declare the value of $oSel, you've done something like this:

<xsl:value-of select="$compose//*[contains(@outputclass,$sel)]"/>

which evaluates the expression in the select attribute and returns its result. But you're then passing $oSel to saxon:evaluate(), which expects a string containing an XPath expression. I think you're trying to bind the variable to the expression "$compose//*[contains(@outputclass,$sel)]", not to the result of evaluating this expression. To do that you would have to write

   <xsl:value-of select="'$compose//*[contains(@outputclass,$sel)]'"/>

Note the extra quotes; but that would now fail because the expression passed to saxon:evaluate() can't explicitly use variables such as $compose (there's a mechanism to pass parameters, but you don't really want to go there).

In XSLT 3.0 saxon:evaluate is superseded by the standard instruction xsl:evaluate; but you don't want that one either.

The right mechanism to be using here is higher order functions.

In XSLT 3.0 you can write

<xsl:for-each-group select="$compose//*[$predicate(.)]" group-by="$grouping-key(.)">

Where $predicate and $grouping-key are variables bound to user-defined functions. You can bind these variables with logic like this:

 <xsl:variable name="predicate" as="function(element()) as xs:boolean">
    <xsl:choose>
        <xsl:when test="starts-with($from,'oclass-')">
            <xsl:sequence select="function($n){contains($n/@outputclass,$sel)}"/>
        </xsl:when>
        <xsl:when test="starts-with($from,'node-')">
            <xsl:sequence select="function($n){name($n)=$sel}"/>    
        </xsl:when> 
    </xsl:choose>
</xsl:variable>
Michael Kay
  • 156,231
  • 11
  • 92
  • 164
  • See also https://stackoverflow.com/questions/27786927/replacing-xpath-expression-with-variable-function/27789366#27789366 – Michael Kay Apr 29 '19 at 16:26
  • Thanks for your help Michael, unfortunately I am currently using an old version of Saxon that does not support XSLT 3.0. I should have stated that I was using version 9.1 - sorry still very new to using Saxon and XSLT. I've chased up my company to see if they can provide a newer version. – Aaron May 01 '19 at 12:14
  • It's always nice to have the opportunity to show how the newer features of the language can deliver a real benefit! – Michael Kay May 01 '19 at 20:58
1

Change the xpath as below.

$compose//*[name()=$sel or contains(@outputclass,$sel)]

Here is the final code looks like

<xsl:when test="$from='oclass-attribute'">
<xsl:for-each-group select="$compose//*[name()=$sel or contains(@outputclass,$sel)]" group-by="@*[name()=$group]">
    <xsl:sort select="current-grouping-key()" />
    <element key="{current-grouping-key()}">
        <xsl:if test="number(current-group()[1])=number(current-group()[1])">
            <xsl:attribute name="max" select="max(current-group())"/>
            <xsl:attribute name="sum" select="sum(current-group())"/>
        </xsl:if>
        <xsl:copy-of select="current-group()[1]"/>
    </element>
</xsl:for-each-group>

supputuri
  • 13,644
  • 2
  • 21
  • 39
1

Try

select="if ($from='oclass-attribute') then $compose//*[contains(@outputclass,$sel)] else $compose//*[name()=$sel]"

and use the same approach for the group-by attribute:

group-by="if ($from='oclass-attribute') then @*[name()=$group] else child::*[name()=$group]"
Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
  • Thanks martin. I will try to use this with 'else-if' as you have suggested. I have also tried Micheal's solution but unless my company will provide me with a newer version of Saxon it will not work - I'm currently using 9.1. – Aaron May 01 '19 at 12:18
  • The open-source Saxon HE edition is available in the latest releases 9.8 or 9.9 to support XSLT 3. HE however does not support higher-order functions though you could at least use `xsl:function` to write predicate or group-by functions in a bit more structured and maintainable way than using `if .. else`. But try whether it works, at least for the first two samples you have shown it should do and for the longer I only think stuffing all into lots of `if () then else if () then else if ()` becomes unreadable but should work at least as well. – Martin Honnen May 01 '19 at 12:34