I am serching an XPath function that works like the XPath 2.0 fn:max function. A function that returns the maximum of several parameters.
After searching a lot I figured out this way:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:math="http://exslt.org/math"
xmlns:exslt="http://exslt.org/common"
xmlns:func="http://exslt.org/functions"
xmlns:my="http://myns.com"
extension-element-prefixes="math exslt func">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<root>
<xsl:value-of select="my:max(1,2)"/>
</root>
</xsl:template>
<func:function name="my:max">
<xsl:param name="e1"/>
<xsl:param name="e2"/>
<xsl:variable name="x">
<val><xsl:value-of select="$e1"/></val>
<val><xsl:value-of select="$e2"/></val>
</xsl:variable>
<func:result select="math:max(exslt:node-set($x)/val)"/>
</func:function>
</xsl:stylesheet>
Is it possible to do it so that my max function can take more elements?
Cheers
Jan
I don't have my XSLT 1.0 book in front of me, but I think the key here is that you can select 'node sets' and set those equal to your parameter variable, rather than having one-node-per-parameter.
Here's a rough guess:
<xsl:template match="/">
<root>
<xsl:call-template name="max">
<xsl:with-param name="values">
<val>1</val>
<val>2</val>
<val>3</val>
</xsl:with-param>
</xsl:call-template>
</root>
</xsl:template>
<func:function name="my:max">
<xsl:param name="x"/>
<func:result select="math:max($x/val/*)"/>
</func:function>
edit: re-read the question along with some XSLT 1.0 guidance. It should resemble the other answer, simplified only slightly. Keep in mind that if the numbers you want come from the XML data, you can use the select= attribute on xsl:with-param to automatically select the nodes you want to compare.
Presumably you could specify the xml (for the node-set) as the input argument?
I'm not "up" on exslt, but using msxsl (just for the node-set function, which is also in exslt):
<xsl:template name="max">
<xsl:param name="values"/>
<xsl:for-each select="msxsl:node-set($values)/val">
<xsl:sort data-type="number" order="descending"/>
<xsl:if test="position()=1">
<xsl:value-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:template>
...
<xsl:call-template name="max">
<xsl:with-param name="values">
<val>13</val>
<val>123</val>
<val>18</val>
</xsl:with-param>
</xsl:call-template>
Thank you for your ideas.
You helped me to understand everything a bit better. But my original intention was to get a
handy XPath function that works on xsl variables.
<!-- works with XPath 2.0 -->
<xst:template match="img/#height">
<xsl:variable name="$maximageheight" select="200">
<xsl:value-of select="fn:max( $maximageheight , . )"/>
</xsl:template>
<!-- until now the only way I see to do the same in XSL 1.0 -->
<xst:template match="img/#height">
<xsl:variable name="$maximageheight" select="200">
<xsl:call-template name="max">
<xsl:with-param name="values">
<val>$maximageheight</val>
<val><xsl:value-of select="."/></val>
</xsl:with-param>
</xsl:call-template>
</xsl:template>
For a fixed umber of parameters it would be possible to implement an exslt function:
<func:function name="my:max" xmlns:func="http://exslt.org/functions">
<xsl:param name="e1"/>
<xsl:param name="e2"/>
<xsl:variable name="x">
<val><xsl:value-of select="$e1"/></val>
<val><xsl:value-of select="$e2"/></val>
</xsl:variable>
<func:result select="math:max(exslt:node-set($x)/val)"/>
</func:function>
But i do not see a way to implement it vor a variable number of parameters.
Related
I have two variables which contain comma separated values:-
<xsl:variable name="Include-Cities" select="'London, Paris, Washington, Tokyo'"/>
<xsl:variable name="Exclude-Cities" select="'Paris, Tokyo'"/>
I have a requirement to remove the values in $Include-Cities which match those found in $Exclude-Cities, so in a way I am subtracting those values from the $Include-Cities variable and outputting the result.
I have looked around the web and found the following example which provides search and replace functionality and which works if the order of the cities in $Exclude-Cities matches the order in $Include-Cities, but fails if the order of values if different.
I am stuck as the values in both lists can change daily and i will never know what those values are, therefore i don't think performing a sort (if its possible) will work.
The example i found:-
<xsl:template name="replace-string">
<xsl:param name="text"/>
<xsl:param name="replace"/>
<xsl:param name="with"/>
<xsl:choose>
<xsl:when test="contains($text,$replace)">
<xsl:value-of select="substring-before($text,$replace)"/>
<xsl:value-of select="$with"/>
<xsl:call-template name="replace-string">
<xsl:with-param name="text" select="substring-after($text,$replace)"/>
<xsl:with-param name="replace" select="$replace"/>
<xsl:with-param name="with" select="$with"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Then i call the template using:-
<xsl:call-template name="replace-string">
<xsl:with-param name="text" select="$Include-Cities"/>
<xsl:with-param name="replace" select="$Exclude-Cities" />
<xsl:with-param name="with" select="''"/>
</xsl:call-template>
I have also looked at examples of tokenizing the values and comparing that way but have had no joy whatsoever.
I know there are string comparsion functions available in 2.0 but I am restricted to using XSLT 1.0.
I am an XSLT noob so can anyone help please?
Any help would be most appreciated
I have also looked at examples of tokenizing the values and comparing
that way but have had no joy whatsoever.
Tokenizing is the correct approach to take here. If your processor supports the EXSLT str:tokenize extension function, you could do:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:str="http://exslt.org/strings"
extension-element-prefixes="str">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="Include-Cities" select="'London, Paris, Washington, Tokyo'"/>
<xsl:variable name="Exclude-Cities" select="'Paris, Tokyo'"/>
<xsl:template match="/">
<xsl:variable name="incl" select="str:tokenize($Include-Cities, ', ')"/>
<xsl:variable name="excl" select="str:tokenize($Exclude-Cities, ', ')"/>
<output>
<xsl:copy-of select="$incl[not(.=$excl)]"/>
</output>
</xsl:template>
and get:
Result
<?xml version="1.0" encoding="UTF-8"?>
<output>
<token>London</token>
<token>Washington</token>
</output>
Otherwise you would have to use a recursive named template to do tokenizing, convert the results to node-sets and then do the comparison as shown above.
Source:
<Data>
<value>M1,M2,M3,M4,M5,M6</value>
</Data>
Need to Display them as
Output:
<ABCD>
<value1>M1</value1>
<value2>M2</value2>
<value3>M3</value3>
<value4>M4</value4>
<value5>M5</value5>
<value6>M6</value6>
</ABCD>
XSLT:
I actually want to split the value based on "," and place them in different variables.
Using str-split(), Can I load it in different variables.
This XSLT 1.0 transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*">
<ABCD>
<xsl:apply-templates/>
</ABCD>
</xsl:template>
<xsl:template match="value/text()" name="split">
<xsl:param name="pText" select="."/>
<xsl:param name="pOrd" select="1"/>
<xsl:if test="$pText">
<xsl:element name="value{$pOrd}">
<xsl:value-of select=
"substring-before(concat($pText, ','), ',')"/>
</xsl:element>
<xsl:call-template name="split">
<xsl:with-param name="pText" select="substring-after($pText, ',')"/>
<xsl:with-param name="pOrd" select="$pOrd+1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<Data>
<value>M1,M2,M3,M4,M5,M6</value>
</Data>
produces the wanted, correct result:
<ABCD>
<value1>M1</value1>
<value2>M2</value2>
<value3>M3</value3>
<value4>M4</value4>
<value5>M5</value5>
<value6>M6</value6>
</ABCD>
Explanation:
Recursive named template, with stop-condition when the passed text-parameter becomes the empty string.
Proper use of xsl:element and AVT.
Proper use of the standard XPath functions substring-before() and substring-after
Proper use of a sentinel to simplify the code and make it more efficient.
If you have access to EXSLT you can use str:split().
<xsl:apply-templates select='str:split(/Data/value, ",")' />
Runnable example here
I am using a recursive template that searches for some specific elements like this:
<xsl:template name="GetProdDependency">
<xsl:param name="TechProd"></xsl:param>
<xsl:param name="BeatenPath"></xsl:param>
<xsl:variable name="TechProdArch" select="$TechProd/pro:own_slot_value[pro:slot_reference='technology_product_architecture']/pro:value"></xsl:variable>
<xsl:variable name="TechProdArchNode" select="/node()/pro:simple_instance[pro:name=$TechProdArch]"></xsl:variable>
<xsl:variable name="TechProdCompList" select="$TechProdArchNode/pro:own_slot_value[pro:slot_reference='contained_techProd_components']/pro:value"/>
<xsl:for-each select="$TechProdCompList">
<xsl:variable name="TechProdAsRole" select="/node()/pro:simple_instance[pro:name=current()]/pro:own_slot_value[pro:slot_reference='technology_product_as_role']/pro:value"/>
<xsl:variable name="TechProdRole" select="/node()/pro:simple_instance[pro:name=$TechProdAsRole]/pro:own_slot_value[pro:slot_reference='role_for_technology_provider']/pro:value"/>
<xsl:variable name="DepTechProd" select="/node()/pro:simple_instance[pro:name=$TechProdRole]"/>
<!-- Check for beaten Path -->
<!-- if $DepTechProd/pro:own_slot_value[pro:slot_reference='name']/pro:value in BeatenPath -->
<xsl:if test="not($BeatenPath[string(.)=string($DepTechProd/pro:own_slot_value[pro:slot_reference='name']/pro:value)])">
<!-- Do the recursion here! -->
<!--<xsl:value-of select="$DepTechProd/pro:own_slot_value[pro:slot_reference='name']/pro:value"/> (type: <xsl:value-of select="$DepTechProd/pro:type"/> and Class: <xsl:value-of select="$DepTechProd/pro:name"/>)-->
<!--<xsl:value-of select="$DepTechProd/pro:own_slot_value[pro:slot_reference='name']/pro:value"/>-->
<xsl:value-of select="$DepTechProd"/>
<xsl:call-template name="GetProdDependency">
<xsl:with-param name="TechProd" select="$DepTechProd"></xsl:with-param>
<xsl:with-param name="BeatenPath" select="$TechProd|$DepTechProd/pro:own_slot_value[pro:slot_reference='name']/pro:value"></xsl:with-param>
<xsl:with-param name="Rev" select="$Rev + 1"></xsl:with-param>
</xsl:call-template>
</xsl:if>
</xsl:for-each>
</xsl:template>
This is working fine in the searching etc.
But when I get the result in the original caller, I was expecting to get a list of nodes from the calling.
I call it like:
<xsl:variable name="DelPlist">
<xsl:call-template name="GetProdDependency">
<xsl:with-param name="TechProd" select="$TechProd"></xsl:with-param>
<xsl:with-param name="BeatenPath" select="$TechProd/pro:own_slot_value[pro:slot_reference='name']/pro:value"></xsl:with-param>
<xsl:with-param name="Rev" select="1"></xsl:with-param>
</xsl:call-template>
</xsl:variable>
And I was expecting to get a list of nodes that I can iterate through with <xsl:for-each>. But If I check for the count($DelPlist), I get 1 as result and I can not iterate.
Can somebody help?
The answer to your question is: in XSLT 2.0 yes, in XSLT 1.0 no.
In XSLT 2.0 both templates and functions can return any value. The type of the result can be specified using the as attribute (for example as="node()*"), and you can use the xsl:sequence instruction to set the result to be the result of any XPath expression.
In XSLT 1.0, if you capture the result of xsl:call-template in a variable, the value of the variable will always be a result tree fragment.
You must specify the type of the result of the template in its as attribute.
If unspecified, the type is document-node() and then in order to iterate through the result, you need to get the children of the result.
Solution: Specify the return type of the template with an as attribute.
Here is a complete example:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="vNodes" as="element()*">
<xsl:call-template name="genNodes"/>
</xsl:variable>
<xsl:for-each select="$vNodes">
<xsl:value-of select="concat('
', position(), ': ')"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:template>
<xsl:template name="genNodes" as="element()*">
<a/>
<b/>
<c/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on any XML document (not used), the wanted, correct result is produced:
1: <a/>
2: <b/>
3: <c/>
Sorry to bother.
I could achieve what meets my requirement in the following amnner:
<xsl:template name="GetProdDependency">
<xsl:param name="TechProd"></xsl:param>
<xsl:param name="BeatenPath"></xsl:param>
<xsl:variable name="TechProdArch" select="$TechProd/pro:own_slot_value[pro:slot_reference='technology_product_architecture']/pro:value"></xsl:variable>
<xsl:variable name="TechProdArchNode" select="/node()/pro:simple_instance[pro:name=$TechProdArch]"></xsl:variable>
<xsl:variable name="TechProdCompList" select="$TechProdArchNode/pro:own_slot_value[pro:slot_reference='contained_techProd_components']/pro:value"/>
<xsl:for-each select="$TechProdCompList">
<xsl:variable name="TechProdAsRole" select="/node()/pro:simple_instance[pro:name=current()]/pro:own_slot_value[pro:slot_reference='technology_product_as_role']/pro:value"/>
<xsl:variable name="TechProdRole" select="/node()/pro:simple_instance[pro:name=$TechProdAsRole]/pro:own_slot_value[pro:slot_reference='role_for_technology_provider']/pro:value"/>
<xsl:variable name="DepTechProd" select="/node()/pro:simple_instance[pro:name=$TechProdRole]"/>
<!-- Check for beaten Path -->
<!-- if $DepTechProd/pro:own_slot_value[pro:slot_reference='name']/pro:value in BeatenPath -->
<xsl:if test="not($BeatenPath[string(.)=string($DepTechProd/pro:own_slot_value[pro:slot_reference='name']/pro:value)])">
<!-- Do the recursion here! -->
<!--<xsl:value-of select="$DepTechProd/pro:own_slot_value[pro:slot_reference='name']/pro:value"/> (type: <xsl:value-of select="$DepTechProd/pro:type"/> and Class: <xsl:value-of select="$DepTechProd/pro:name"/>)-->
<!--<xsl:value-of select="$DepTechProd/pro:own_slot_value[pro:slot_reference='name']/pro:value"/>-->
<!--<xsl:value-of select="$DepTechProd"/>-->
<xsl:element name="TProd">
<xsl:value-of select="$DepTechProd/pro:own_slot_value[pro:slot_reference='name']/pro:value" />
</xsl:element>
<xsl:call-template name="GetProdDependency">
<xsl:with-param name="TechProd" select="$DepTechProd"></xsl:with-param>
<xsl:with-param name="BeatenPath" select="$TechProd|$DepTechProd/pro:own_slot_value[pro:slot_reference='name']/pro:value"></xsl:with-param>
<xsl:with-param name="Rev" select="$Rev + 1"></xsl:with-param>
</xsl:call-template>
</xsl:if>
</xsl:for-each>
</xsl:template>
And could iterate through the nodelist as:
<xsl:variable name="DelPlist">
<xsl:call-template name="GetProdDependency">
<xsl:with-param name="TechProd" select="$TechProd"></xsl:with-param>
<xsl:with-param name="BeatenPath" select="$TechProd/pro:own_slot_value[pro:slot_reference='name']/pro:value"></xsl:with-param>
<xsl:with-param name="Rev" select="1"></xsl:with-param>
</xsl:call-template>
</xsl:variable>
<xsl:for-each select="$DelPlist/node()">
<xsl:value-of select="current()" />
</xsl:for-each>
It meets my current requirement.
Possible in xsl 1.0 with exslt:node-set (supported by most xslt processors):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exslt="http://exslt.org/common"
xmlns:msxml="urn:schemas-microsoft-com:xslt"
exclude-result-prefixes="exslt msxml"
extension-element-prefixes="exslt msxml"
version="1.0">
<!-- support for exslt prefix for msxml, ignored by other processors which however support exsl natively anyway -->
<msxml:script language="JScript" implements-prefix="exslt">this['node-set']=function(x){return x}</msxml:script>
<xsl:template match="/">
<xsl:variable name="elementsReturnedFromCallTemplate">
<xsl:call-template name="getElements"/>
</xsl:variable>
<xsl:for-each select="exslt:node-set($elementsReturnedFromCallTemplate)/*">
<xsl:copy/>
</xsl:for-each>
</xsl:template>
<xsl:template name="getElements">
<a/>
<b/>
</xsl:template>
</xsl:stylesheet>
Output (tested in libxslt, xalan, saxon, msxml):
<a/>
<b/>
I'm trying to find a solution for how to replace standard 3-line code of type "call-template" - "with-param" with one single line.
For example I have the following piece of code:
<xsl:call-template name="do_job">
<xsl:with-param name="str">data1</xsl:with-param>
</xsl:call-template>
that i want to replace with something like this:
<myNs:tr name="data1"/>
It is not possible to create and use macros in XSLT.
In XSLT 2.0 one can write functions using the <xsl:function> instruction. Then a function is referenced in any XPath expression:
my:do_job(someString)
Here is a full example:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:my="my:my">
<xsl:template match="/">
<xsl:sequence select="my:do_job('Hello, World!')"/>
</xsl:template>
<xsl:function name="my:do_job" as="xs:integer">
<xsl:param name="pText" as="xs:string"/>
<xsl:sequence select="string-length($pText)"/>
</xsl:function>
</xsl:stylesheet>
when this transformation is applied on any XML document (not used), the correct result is produced:
13
As Dimitre already said, macros are not supported. You could however generate your XSLT on the fly from an XML document containing macros and then run the generated XSLT.
A stylesheet that would create your XSLT would look like this:
<xsl:template match="myNs:tr">
<xsl:call-template name="do_job">
<xsl:with-param name="str" select="{#name}" />
</xsl:call-template>
</xsl:template>
Beside Dimitre excellent answer and recomendation, you could something like this stylesheet:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:myNs="myNs"
exclude-result-prefixes="myNs">
<myNs:tr name="data1"/>
<myNs:tr name="data2"/>
<xsl:template match="root">
<root>
<xsl:apply-templates/>
</root>
</xsl:template>
<xsl:template match="data">
<data>
<macro>
<xsl:apply-templates select="document('')/*/myNs:*
[#name=current()]"/>
</macro>
<inline>
<xsl:call-template name="do_job">
<xsl:with-param name="str" select="."/>
</xsl:call-template>
</inline>
</data>
</xsl:template>
<xsl:template match="myNs:tr">
<xsl:call-template name="do_job">
<xsl:with-param name="str" select="#name"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="do_job">
<xsl:param name="str"/>
<xsl:value-of select="translate($str,'data','DATA')"/>
</xsl:template>
</xsl:stylesheet>
With this input:
<root>
<data>data1</data>
<data>data2</data>
<data>data3</data>
</root>
Output:
<root>
<data>
<macro>DATA1</macro>
<inline>DATA1</inline>
</data>
<data>
<macro>DATA2</macro>
<inline>DATA2</inline>
</data>
<data>
<macro></macro>
<inline>DATA3</inline>
</data>
</root>
Note: Also I recomend to you to read Dimitre Novatchev's FXSL in http://fxsl.sf.net/
I have the following XML source structure:
<turnovers>
<turnover repid="1" amount="500" rate="0.1"/>
<turnover repid="5" amount="600" rate="0.5"/>
<turnover repid="4" amount="400" rate="0.2"/>
<turnover repid="1" amount="700" rate="0.05"/>
<turnover repid="2" amount="100" rate="0.15"/>
<turnover repid="1" amount="900" rate="0.25"/>
<turnover repid="2" amount="1000" rate="0.18"/>
<turnover repid="5" amount="200" rate="0.55"/>
<turnover repid="9" amount="700" rate="0.40"/>
</turnovers>
I need an XSL:value-of select statement that will return the sum of the product of the rate attribute and the amount attribute for a given rep ID. So for rep 5 I need ((600 x 0.5) + (200 x 0.55)).
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:template match="/turnovers">
<val>
<!-- call the sum function (with the relevant nodes) -->
<xsl:call-template name="sum">
<xsl:with-param name="nodes" select="turnover[#repid='5']" />
</xsl:call-template>
</val>
</xsl:template>
<xsl:template name="sum">
<xsl:param name="nodes" />
<xsl:param name="sum" select="0" />
<xsl:variable name="curr" select="$nodes[1]" />
<!-- if we have a node, calculate & recurse -->
<xsl:if test="$curr">
<xsl:variable name="runningsum" select="
$sum + $curr/#amount * $curr/#rate
" />
<xsl:call-template name="sum">
<xsl:with-param name="nodes" select="$nodes[position() > 1]" />
<xsl:with-param name="sum" select="$runningsum" />
</xsl:call-template>
</xsl:if>
<!-- if we don't have a node (last recursive step), return sum -->
<xsl:if test="not($curr)">
<xsl:value-of select="$sum" />
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Gives:
<val>410</val>
The two <xsl:if>s can be replaced by a single <xsl:choose>. This would mean one less check during the recursion, but it also means two additional lines of code.
In plain XSLT 1.0 you need a recursive template for this, for example:
<xsl:template match="turnovers">
<xsl:variable name="selectedId" select="5" />
<xsl:call-template name="sum_turnover">
<xsl:with-param name="turnovers" select="turnover[#repid=$selectedId]" />
</xsl:call-template>
</xsl:template>
<xsl:template name="sum_turnover">
<xsl:param name="total" select="0" />
<xsl:param name="turnovers" />
<xsl:variable name="head" select="$turnovers[1]" />
<xsl:variable name="tail" select="$turnovers[position()>1]" />
<xsl:variable name="calc" select="$head/#amount * $head/#rate" />
<xsl:choose>
<xsl:when test="not($tail)">
<xsl:value-of select="$total + $calc" />
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="sum_turnover">
<xsl:with-param name="total" select="$total + $calc" />
<xsl:with-param name="turnovers" select="$tail" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
This should do the trick, you'll need to do some further work to select the distinct repid's
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:variable name="totals">
<product>
<xsl:for-each select="turnovers/turnover">
<repid repid="{#repid}">
<value><xsl:value-of select="#amount * #rate"/></value>
</repid>
</xsl:for-each>
</product>
</xsl:variable>
<totals>
<total repid="5" value="{sum($totals/product/repid[#repid='5']/value)}"/>
</totals>
</xsl:template>
</xsl:stylesheet>
In XSLT 1.0 the use of FXSL makes such problems easy to solve:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://fxsl.sf.net/"
xmlns:ext="http://exslt.org/common"
exclude-result-prefixes="xsl f ext"
>
<xsl:import href="zipWith.xsl"/>
<xsl:output method="text"/>
<xsl:variable name="vMultFun" select="document('')/*/f:mult-func[1]"/>
<xsl:template match="/">
<xsl:call-template name="profitForId"/>
</xsl:template>
<xsl:template name="profitForId">
<xsl:param name="pId" select="1"/>
<xsl:variable name="vrtfProducts">
<xsl:call-template name="zipWith">
<xsl:with-param name="pFun" select="$vMultFun"/>
<xsl:with-param name="pList1" select="/*/*[#repid = $pId]/#amount"/>
<xsl:with-param name="pList2" select="/*/*[#repid = $pId]/#rate"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="sum(ext:node-set($vrtfProducts)/*)"/>
</xsl:template>
<f:mult-func/>
<xsl:template match="f:mult-func" mode="f:FXSL">
<xsl:param name="pArg1"/>
<xsl:param name="pArg2"/>
<xsl:value-of select="$pArg1 * $pArg2"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the originally posted source XML document, the correct result is produced:
310
In XSLT 2.0 the same solution using FXSL 2.0 can be expressed by an XPath one-liner:
sum(f:zipWith(f:multiply(),
/*/*[xs:decimal(#repid) eq 1]/#amount/xs:decimal(.),
/*/*[xs:decimal(#repid) eq 1]/#rate/xs:decimal(.)
)
)
The whole transformation:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:f="http://fxsl.sf.net/"
exclude-result-prefixes="f xs"
>
<xsl:import href="../f/func-zipWithDVC.xsl"/>
<xsl:import href="../f/func-Operators.xsl"/>
<!-- To be applied on testFunc-zipWith4.xml -->
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:value-of select=
"sum(f:zipWith(f:multiply(),
/*/*[xs:decimal(#repid) eq 1]/#amount/xs:decimal(.),
/*/*[xs:decimal(#repid) eq 1]/#rate/xs:decimal(.)
)
)
"/>
</xsl:template>
</xsl:stylesheet>
Again, this transformation produces the correct answer:
310
Note the following:
The f:zipWith() function takes as arguments a function fun() (of two arguments) and two lists of items having the same length. It produces a new list of the same length, whose items are the result of the pair-wise application of fun() on the corresponding k-th items of the two lists.
f:zipWith() as in the expression takes the function f:multiply() and two sequences of corresponding "ammount" and "rate" attributes. The sesult is a sequence, each item of which is the product of the corresponding "ammount" and "rate".
Finally, the sum of this sequence is produced.
There is no need to write an explicit recursion and it is also guaranteed that the behind-the scenes recursion used within f:zipWith() is never going to crash (for all practical cases) with "stack overflow"
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:variable name="repid" select="5" />
<xsl:template match="/">
<xsl:value-of select=
"sum(for $x in /turnovers/turnover[#repid=$repid] return $x/#amount * $x/#rate)"/>
</xsl:template>
</xsl:stylesheet>
You can do this if you just need the value and not xml.
The easiest way to do it in XSLT is probably to use programming language bindings, so that you can define your own XPath functions.