Implement dynamic xpath in XSLT: for-each-group - xslt

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

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>

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>

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]"

Related

'tokenize()' is an unknown XSLT function

I am using Visual Studio 2015. I want to find the smallest number in the Comma separated list using XSLT.
<EndUnitizeDate>2020-07-13T15:01:43</EndUnitizeDate>
<InternalRecNum>12,3,44,55,66</InternalRecNum>
<LaunchNum>0</LaunchNum> <LeadingSts>900</LeadingSts>
I used tokenize for splitting but I am getting 'tokenize()' is an unknown XSLT function Error.
<xsl:variable name="smallValue" select="s0:WMWDATA/s0:WMFWUpload/s0:Receipts/s0:Receipt/s0:InternalRecNum/text()" />
<xsl:variable name="tokenizedLine" select="tokenize($smallValue, ',')" />
<xsl:for-each select="$tokenizedLine">
<xsl:sort select="." order="descending" />
<xsl:if test="position() = last()">
Smallest: <xsl:value-of select="." />
</xsl:if>
</xsl:for-each>
enter image description here
The tokenize() function requires an XSLT 2.0 processor.
Some XSLT 1.0 processors support tokenizing via en extension function. In pure XSLT 1.0, you need to use a recursive named template.
Here's an example of a template that will both tokenize the input AND find the smallest token:
<xsl:template name="min-token">
<xsl:param name="input"/>
<xsl:param name="prev-min"/>
<xsl:param name="delimiter" select="','"/>
<xsl:variable name="token" select="substring-before(concat($input, $delimiter), $delimiter)" />
<xsl:variable name="min">
<xsl:choose>
<xsl:when test="not($prev-min) or $token < $prev-min">
<xsl:value-of select="$token"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$prev-min"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:choose>
<xsl:when test="contains($input, $delimiter)">
<!-- recursive call -->
<xsl:call-template name="min-token">
<xsl:with-param name="input" select="substring-after($input, $delimiter)"/>
<xsl:with-param name="prev-min" select="$min"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$min"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Demo: https://xsltfiddle.liberty-development.net/aiynfe
If functions like tokenize() are more important to you than using Visual Studio, then this question describes alternatives you could consider:
https://stackoverflow.com/questions/11205268/how-to-use-xslt-2-0-in-visual-studio-2010`

XSL Using replace and matches to update values

I am trying to update an old form that uses a data model that has changed, so any reference to the old model I want to replace with the new. I currently use the function matches to tell if I should preform the replace on the current string and then use the replace function to replace the value with the new one, the problem is that the regex used in the matches does not work with the regex in the replace.
<xsl:template
match="//*[contains(#*:default,'instance(''document'')/')
mode="pass">
<xsl:variable
name="regex"
as="element()*">
<regex>instance('document')/doc_type/description</regex>
<regex>anotherRegex</regex>
</xsl:variable>
<xsl:variable
name="replacement"
as="element()*">
<replacement>xxf:get-request-parameter('documentDesc')</replacement>
<replacement>replacedRegex</replacement>
</xsl:variable>
<xsl:element name="{name()}">
<xsl:for-each select="#*">
<xsl:choose>
<xsl:when test="name() = ('xxf:default')">
<xsl:attribute name="xxf:default">
<xsl:analyze-string
regex="{concat('(',$regex[1],'|',$regex[2],')')}"
select=".">
<xsl:matching-substring>
<xsl:if test="matches(.,$regex[1])">
<xsl:value-of select="replace(.,$regex[1],$replacement[1])" />
</xsl:if>
<xsl:if test="matches(.,$regex[2])">
<xsl:value-of select="replace(.,$regex[2],$replacement[2])" />
</xsl:if>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="." />
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="{local-name()}"><xsl:value-of select="." /></xsl:attribute>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:element>
</xsl:template>
Current XML:
<xf:bind id="clinic-bind"
name="clinic"
xxf:default="instance('document')/doc_type/description"
type="xf:string"/>
What I want to turn it into:
<xf:bind id="clinic-bind"
name="clinic"
xxf:default="xxf:get-request-parameter('documentDesc')"
type="xf:string"/>
So the Problem was basicaally I had to escape the '()' characters in the regex variable.
So I ended up with
<regex>instance\('document'\)/doc_type/description</regex>
In the regex part.

How XSLT treats multiple nested element?

I want to convert some plain text with special marker into HTML formatted text.
For example,
This is the original value
Th<italic>is is a <under>com<bold>bina</bold>tion</under></italic> text.
The original value as actual value (just for reference)
Th<italic>is is a <under>com<bold>bina</bold>tion</under></italic> text.
HTML format I expect as a result
Th<i>is is a <u>com<b>bina</b>tion</u></i> text.
I tried with below template but it can not be parsed by XSLT parser.
<xsl:template name="decorateValue">
<xsl:param name="originalString" />
<xsl:variable name="preString" select="substring-before($originalString, '<')" />
<xsl:variable name="postString" select="substring-after($originalString, '<')" />
<xsl:value-of select="$preString" />
<xsl:variable name="tagName" select="substring-before($postString, '>')" />
<xsl:choose>
<xsl:when test="$tagName='bold'">
<xsl:element name="b">
</xsl:when>
<xsl:when test="$tagName='/bold'">
</xsl:element>
</xsl:when>
<xsl:when test="$tagName='italic'">
<xsl:element name="i">
</xsl:when>
<xsl:when test="$tagName='/italic'">
</xsl:element>
</xsl:when>
<xsl:when test="$tagName='under'">
<xsl:element name="u">
</xsl:when>
<xsl:when test="$tagName='/under'">
</xsl:element>
</xsl:when>
</xsl:choose>
<xsl:call-template name="decorateValue">
<xsl:with-param name="originalString"
select="substring-after($postString, '>')" />
</xsl:call-template>
</xsl:template>
any idea to solve this?
I would appreciate in advance.
If you can actually keep the original text, your life would be easier. Then you can transform
Th<italic>is is a <under>com<bold>bina</bold>tion</under></italic> text. easily into xHTML
<xsl:template match="italic">
<xsl:element name="i">
<xsl:apply-templates />
</xsl:element>
</xsl:template>
You can't mix your tags in your example. You open an xsl:when and open an xsl:element and then close the xsl:when. That is not valid XML!
So if you want to go with the encoded string you need something like:
<xsl:template name="decorateValue">
<xsl:param name="originalString" />
<xsl:if test="$originalString!=''">
<xsl:variable name="preString" select="substring-before($originalString, '<')" />
<xsl:variable name="postString" select="substring-after($originalString, '<')" />
<xsl:variable name="endString" select="'magic happens here!'" />
<xsl:value-of select="$preString" />
<xsl:variable name="tagName" select="substring-before($postString, '>')" />
<xsl:variable name="restString" select="'magic happens here!'" />
<xsl:choose>
<xsl:when test="$tagName='bold'">
<xsl:element name="b">
<xsl:call-template name="decorateValue">
<xsl:with-param name="originalString"
select="$restString" />
</xsl:call-template>
</xsl:element>
</xsl:when>
<xsl:when test="$tagName='italic'">
<xsl:element name="i">
<xsl:call-template name="decorateValue">
<xsl:with-param name="originalString"
select="$restString" />
</xsl:call-template>
</xsl:element>
</xsl:when>
<xsl:when test="$tagName='under'">
<xsl:element name="u">
<xsl:call-template name="decorateValue">
<xsl:with-param name="originalString"
select="$restString" />
</xsl:call-template>
</xsl:element>
</xsl:when>
</xsl:choose>
<xsl:value-of select="$endString" />
</xsl:if>
</xsl:template>
You see 2 places where 'magic happens here'. This is where you need to apply the string-before-last and string-after-last patterns (which are a PITA in XSLT). The best explanation can be found in the XSLT Cookbook (and you want to have XSLT 2.0 at least.
Hope the pointers help. You might consider breaking the pattern into individual, so you don't fish for > alone, but the full tags. You still need to use the before-last / after-last functions.

embeding od condition in xsl when getting error

below is the xsl tag on which I am getting the error as i have used xsl:when intead of xsl:if , folks please advise how can I re correct it so that i do not get compilation exception while transforming the xsl again I am using xslt 1.0
<xsl:when test="abcid=dec_id">
<xsl:for-each select="$qq_Obj/ert_Period/ytr_Period">
<xsl:variable name="ABC_Rate">
<xsl:value-of select="$Sds/oht_Period/rew_Period/#vgRate" />
</xsl:variable>
<xsl:choose>
<xsl:when test="$iue_first=#vgRate">
<xsl:value-of select="'AAA'" />
</xsl:when>
<xsl:value-of select="'BBB'" />
</xsl:choose>
</xsl:for-each>
</xsl:when>
You need to enclose the "else" part of an xsl:choose in an xsl:otherwise:
<xsl:choose>
<xsl:when test="$iue_first=#vgRate">
<xsl:value-of select="'AAA'" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'BBB'" />
</xsl:otherwise>
</xsl:choose>

XSL Basics: showing value of a boolean?

When I have this in xsl:
<xsl:choose>
<xsl:when test="something > 0">
<xsl:variable name="myVar" select="true()"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="myVar" select="false()"/>
</xsl:otherwise>
</xsl:choose>
How can I then print out the value of "myVar"? Or more importantly, how can I use this boolean in another choose statement?
<xsl:choose>
<xsl:when test="something > 0">
<xsl:variable name="myVar" select="true()"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="myVar" select="false()"/>
</xsl:otherwise>
</xsl:choose>
This is quite wrong and useless, because the variable $myVar goes out of scope immediately.
One correct way to conditionally assign to the variable is:
<xsl:variable name="myVar">
<xsl:choose>
<xsl:when test="something > 0">1</xsl:when>
<xsl:otherwise>0</xsl:otherwise>
</xsl:choose>
</xsl:variable>
However, you really don't need this -- much simpler is:
<xsl:variable name="myVar" select="something > 0"/>
How can I then print out the value of "myVar"?
Use:
<xsl:value-of select="$myVar"/>
Or more importantly, how can I use this boolean in another choose
statement?
Here is a simple example:
<xsl:choose>
<xsl:when test="$myVar">
<!-- Do something -->
</xsl:when>
<xsl:otherwise>
<!-- Do something else -->
</xsl:otherwise>
</xsl:choose>
And here is a complete example:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/*/*">
<xsl:variable name="vNonNegative" select=". >= 0"/>
<xsl:value-of select="name()"/>: <xsl:text/>
<xsl:choose>
<xsl:when test="$vNonNegative">Above zero</xsl:when>
<xsl:otherwise>Below zero</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the following XML document:
<temps>
<Monday>-2</Monday>
<Tuesday>3</Tuesday>
</temps>
the wanted, correct result is produced:
Monday: Below zero
Tuesday: Above zero