I have a following scenario. I have a TEAM-MEMBERS node which has name in the format last name, first name
<TEAM-MEMBER><LONG-NAME>Last Name, First Name</LONG-NAME></TEAM-MEMBER>
I want to transform this to
<CONTACT><FIRSTNAME>First Name</FIRSTNAME><LASTNAME>Last Name</LASTNAME></CONTACT>
Basically i want to split the <LONG-NAME> node's value by ,
How can I achieve this using XSLT 1.0
This XSLT will be consumed by BizTalk Server hence i am looking for some XSLT 1.0 solutions only
Thanks
Karthik
Here is a complete XSLT 1.0 solution that uses a general "split" template that splits a string into multiple substrings, provided a delimiter to designate the boundary between substrings:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="TEAM-MEMBER">
<xsl:variable name="vrtfSplitWords">
<xsl:call-template name="split">
<xsl:with-param name="pText" select="."/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="vSplitWords"
select="ext:node-set($vrtfSplitWords)/*"/>
<CONTACT>
<FIRSTNAME><xsl:value-of select="$vSplitWords[2]"/></FIRSTNAME>
<LASTNAME><xsl:value-of select="$vSplitWords[1]"/></LASTNAME>
</CONTACT>
</xsl:template>
<xsl:template name="split">
<xsl:param name="pText" select="."/>
<xsl:param name="pDelim" select="', '"/>
<xsl:param name="pElemName" select="'word'"/>
<xsl:if test="string-length($pText)">
<xsl:element name="{$pElemName}">
<xsl:value-of select=
"substring-before(concat($pText,$pDelim),
$pDelim
)
"/>
</xsl:element>
<xsl:call-template name="split">
<xsl:with-param name="pText" select=
"substring-after($pText,$pDelim)"/>
<xsl:with-param name="pDelim" select="$pDelim"/>
<xsl:with-param name="pElemName" select="$pElemName"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<TEAM-MEMBER><LONG-NAME>Last Name, First Name</LONG-NAME></TEAM-MEMBER>
the wanted, correct result is produced:
<CONTACT>
<FIRSTNAME>First Name</FIRSTNAME>
<LASTNAME>Last Name</LASTNAME>
</CONTACT>
II. Solution using FXSL
This transformation uses the str-split-to-words template from FXSL:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
exclude-result-prefixes="ext"
>
<xsl:import href="strSplit-to-Words.xsl"/>
<xsl:output indent="yes" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:output indent="yes" omit-xml-declaration="yes"/>
<xsl:param name="pmaxLines" select="10"/>
<xsl:template match="/">
<xsl:variable name="vwordNodes">
<xsl:call-template name="str-split-to-words">
<xsl:with-param name="pStr" select="/"/>
<xsl:with-param name="pDelimiters"
select="',()'"/>
</xsl:call-template>
</xsl:variable>
<xsl:apply-templates select=
"ext:node-set($vwordNodes)/*[normalize-space()]"/>
</xsl:template>
<xsl:template match="word[normalize-space()][1]">
<FIRSTNAME>
<xsl:value-of select="normalize-space()"/>
</FIRSTNAME>
</xsl:template>
<xsl:template match="word[normalize-space()][2]">
<MIDNAME>
<xsl:value-of select="normalize-space()"/>
</MIDNAME>
</xsl:template>
<xsl:template match="word[normalize-space()][last()]">
<LASTNAME>
<xsl:value-of select="normalize-space(.)"/>
</LASTNAME>
</xsl:template>
</xsl:stylesheet>
when applied to this XML document (made quite more complex):
<TEAM-MEMBER><LONG-NAME>First Name, (Jr.), Last Name</LONG-NAME></TEAM-MEMBER>
the wanted, correct result is produced:
<FIRSTNAME>First Name</FIRSTNAME>
<MIDNAME>Jr.</MIDNAME>
<LASTNAME>Last Name</LASTNAME>
Do Note:
The str-split-to-words template accepts multiple delimiters. Thus in this transformation the delimiters used are: ',', '(' and ')'
You need a recursive named template. Fortunately it's already been written: look for str:tokenize at http://www.exslt.org.
used substring-after() and substring-before() to get around the split
Related
Looking to parse out a namespace from a full class name in xml.
Data example:
<results>
<test-case name="Co.Module.Class.X">
</results>
End result (going to csv format):
,Co.Module.Class
Stylesheet:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:output method="text" indent="yes" encoding="ISO-8859-1"/>
<xsl:param name="delim" select="','" />
<xsl:param name="quote" select="'"'" />
<xsl:param name="break" select="'
'" />
<xsl:template match="/">
FullTestName, Namespace
<xsl:apply-templates select="//test-case" />
</xsl:template>
<xsl:template match="test-case">
<xsl:apply-templates />
<xsl:value-of select="#name" />
<xsl:value-of select="$delim" />
<xsl:value-of select="function to go here for nameWithJustNamespace" />
<xsl:value-of select="$break" />
</xsl:template>
I understand the process would need a last index of "." to be called once, yet I'm not finding XSLT to have that function. How to best accomplish this?
To do this in pure XSLT 1.0, you need to call a named recursive template, e.g.:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:template match="/results">
<xsl:call-template name="remove-last-token">
<xsl:with-param name="text" select="test-case/#name"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="remove-last-token">
<xsl:param name="text"/>
<xsl:param name="delimiter" select="'.'"/>
<xsl:value-of select="substring-before($text, $delimiter)"/>
<xsl:if test="contains(substring-after($text, $delimiter), $delimiter)">
<xsl:value-of select="$delimiter"/>
<xsl:call-template name="remove-last-token">
<xsl:with-param name="text" select="substring-after($text, $delimiter)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
This pure XSLT 1.0 transformation (shorter, no conditional XSLT operations, single template):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="test-case[contains(#name, '.')]">
<xsl:param name="pDotIndex" select="0"/>
<xsl:variable name="vNextToken"
select="substring-before(substring(#name, $pDotIndex+1), '.')"/>
<xsl:value-of select="concat(substring('.', 2 - ($pDotIndex > 0)),$vNextToken)"/>
<xsl:variable name="vNewDotIndex" select="$pDotIndex+string-length($vNextToken)+1"/>
<xsl:apply-templates
select="self::node()[contains(substring(#name,$vNewDotIndex+1), '.')]">
<xsl:with-param name="pDotIndex" select="$vNewDotIndex"/>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<results>
<test-case name="Co.Module.Class.X"/>
</results>
produces the wanted, correct result:
Co.Module.Class
Part 2
With a slight modification the following transformation produces the complete CSV:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="test-case[contains(#name, '.')]">
<xsl:param name="pDotIndex" select="0"/>
<xsl:variable name="vNextToken"
select="substring-before(substring(#name, $pDotIndex+1), '.')"/>
<xsl:value-of select="concat(substring(',', 2 - (position() > 1)),
substring('.', 2 - ($pDotIndex > 0)), $vNextToken)"/>
<xsl:variable name="vNewDotIndex" select="$pDotIndex+string-length($vNextToken)+1"/>
<xsl:apply-templates
select="self::node()[contains(substring(#name,$vNewDotIndex+1), '.')]">
<xsl:with-param name="pDotIndex" select="$vNewDotIndex"/>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
When applied on this XML document:
<results>
<test-case name="Co.Module.Class.X"/>
<test-case name="Co2.Module2.Class2.Y"/>
<test-case name="Co3.Module3.Class3.Z"/>
</results>
the wanted, correct (CSV) result is produced:
Co.Module.Class,Co2.Module2.Class2,Co3.Module3.Class3
I've a challenging problem and so far I wasn't able to solve.
Within my xlst I have variable which contains a string.
I need to add the following sequence [eol] to this string.
On a fix position namely every 65 characters
I thought to use a function or template to recursive add this charackter.
The reason is that the string length can variate in length.
<xsl:function name="funct:insert-eol" as="xs:string" >
<xsl:param name="originalString" as="xs:string?"/>
<xsl:variable name="length">
<xsl:value-of select="string-length($originalString)"/>
</xsl:variable>
<xsl:variable name="start" as="xs:integer">
<xsl:value-of select="1"/>
</xsl:variable>
<xsl:variable name="eol" as="xs:integer">
<xsl:value-of select="65"/>
</xsl:variable>
<xsl:variable name="newLines">
<xsl:value-of select="$length idiv number('65')"/>
</xsl:variable>
<xsl:for-each select="1 to $newLines">
<xsl:value-of select="substring($originalString, $start, $eol)" />
</xsl:for-each>
</xsl:function>
The more I write code the more variables I need to introduce. This is still my lack on understanding.
For example we want every 5 chars an [eol]
aaaaaaabbbbbbccccccccc
aaaaa[eol]aabbb[eol]bbbcc[eol]ccccc[eol]cc
Hope someone has a starting point for me..
Regards Dirk
Rather straight-forward and short -- no recursion is necessary (and can even be specified as a single XPath expression):
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:param name="pLLength" select="5"/>
<xsl:template match="/*">
<xsl:variable name="vText" select="string()"/>
<xsl:for-each select="1 to string-length($vText) idiv $pLLength +1">
<xsl:value-of select="substring($vText, $pLLength*(position()-1)+1, $pLLength)"/>
<xsl:if test=
"not(position() eq last()
or position() eq last() and string-length($vText) mod $pLLength)">[eol]</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on this XML document:
<t>aaaaaaabbbbbbccccccccc</t>
the wanted, correct result is produced:
aaaaa[eol]aabbb[eol]bbbcc[eol]ccccc[eol]cc
When this XML document is processed:
<t>aaaaaaabbbbbbcccccccccddd</t>
again the wanted, correct result is produced:
aaaaa[eol]aabbb[eol]bbbcc[eol]ccccc[eol]ccddd[eol]
You can treat it as a grouping problem, using for-each-group:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="xs mf">
<xsl:function name="mf:insert-eol" as="xs:string">
<xsl:param name="input" as="xs:string"/>
<xsl:param name="chunk-size" as="xs:integer"/>
<xsl:value-of>
<xsl:for-each-group select="string-to-codepoints($input)" group-by="(position() - 1) idiv $chunk-size">
<xsl:if test="position() gt 1"><xsl:sequence select="'eol'"/></xsl:if>
<xsl:sequence select="codepoints-to-string(current-group())"/>
</xsl:for-each-group>
</xsl:value-of>
</xsl:function>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* , node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text">
<xsl:copy>
<xsl:sequence select="mf:insert-eol(., 5)"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
That stylesheet transforms
<root>
<text>aaaaaaabbbbbbccccccccc</text>
</root>
into
<root>
<text>aaaaaeolaabbbeolbbbcceolccccceolcc</text>
</root>
Try this one:
<?xml version='1.0' ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:param name="TextToChange" select="'aaaaaaabbbbbbccccccccc'"/>
<xsl:param name="RequiredLength" select="xs:integer(5)"/>
<xsl:template match="/">
<xsl:call-template name="AddText"/>
</xsl:template>
<xsl:template name="AddText">
<xsl:param name="Text" select="$TextToChange"/>
<xsl:param name="TextLength" select="string-length($TextToChange)"/>
<xsl:param name="start" select="xs:integer(1)"/>
<xsl:param name="end" select="$RequiredLength"/>
<xsl:choose>
<xsl:when test="$TextLength gt $RequiredLength">
<xsl:value-of select="substring($Text,$start,$end)"/>
<xsl:text>[eol]</xsl:text>
<xsl:call-template name="AddText">
<xsl:with-param name="Text" select="substring-after($Text, substring($Text,$start,$end))"/>
<xsl:with-param name="TextLength"
select="string-length(substring-after($Text, substring($Text,$start,$end)))"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$Text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
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
Following on from an earlier question, and this is more about xsl syntax. I want to split part of a URL variable into a new variable in xsl.
This code works when the variable is sitting part way along a URL. EG:
http://www.mysite.com/test.aspx?aVar=something&bVar=somethingMore&cVar=yetMoreStill
<xsl:variable name="testVar" select="substring-after($url, 'bVar=')"/>
<xsl:value-of select="substring-before($testVar, '&')" />
The problem is the variable can sometime sit at the end of the URL (I have no control over this) EG:
http://www.mysite.com/test.aspx?aVar=something&bVar=somethingMore
So the above code fails. Is there away I can allow for both occurrences? The end game is I'm just trying to get the value of bVar no matter where it sits within the URL. Thanks.
How about the following workaround?
<xsl:variable name="testVar" select="substring-after($url, 'bVar=')"/>
<xsl:value-of select="substring-before(concat($testVar, '&'), '&')" />
Try this. This is XSLT 1.0:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:template match="/">
<xsl:call-template name="urlResolver">
<xsl:with-param name="input" select="'http://www.mysite.com/test.aspx?aVar=something&bVar=somethingMore'" />
</xsl:call-template>
<xsl:call-template name="urlResolver">
<xsl:with-param name="input" select="'http://www.mysite.com/test.aspx?aVar=something&bVar=somethingMore&cVar=yetMoreStill'" />
</xsl:call-template>
</xsl:template>
<xsl:template name="urlResolver">
<xsl:param name="input" />
<xsl:variable name="testVar" select="substring-after($input, 'bVar=')"/>
<xsl:choose>
<xsl:when test="contains($testVar, '&')"><xsl:value-of select="substring-before($testVar, '&')" /></xsl:when>
<xsl:otherwise><xsl:value-of select="$testVar" /></xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Try to make use of tokenize (available in XSLT 2.0) like the following:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" omit-xml-declaration="yes" method="xml" version="1.0"/>
<xsl:template match="/">
<xsl:variable name="test"><![CDATA[http://www.mysite.com/test.aspx?aVar=something&bVar=somethingMore&cVar=yetMoreStill]]></xsl:variable>
<xsl:variable name="splitURL" select="tokenize($test,'&')"/>
<xsl:variable name="bvar" select="$splitURL[starts-with(.,'bVar')]"/>
<out><xsl:value-of select="substring-after($bvar, 'bVar=')"/></out>
</xsl:template>
</xsl:stylesheet>
The currently accepted answer is generally wrong.
Try it with this URL:
http://www.mysite.com/test.aspx?subVar=something&bVar=somethingMore
and you get the wrong result: something
This question was already answered... In case you read the answer you would just reuse it and get your QString from the produced result:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pUrl" select=
"'http://www.mysite.com/test.aspx?subVar=something&bVar=somethingMore'"/>
<xsl:template match="/">
<xsl:variable name="vrtfQStrings">
<xsl:call-template name="GetQueryStringParams"/>
</xsl:variable>
bVar = "<xsl:value-of select="ext:node-set($vrtfQStrings)/bVar"/>"
</xsl:template>
<xsl:template name="GetQueryStringParams">
<xsl:param name="pUrl" select="$pUrl"/>
<xsl:variable name="vQueryPart" select=
"substring-before(substring-after(concat($pUrl,'?'),
'?'),
'?')"/>
<xsl:variable name="vHeadVar" select=
"substring-before(concat($vQueryPart,'&'), '&')"/>
<xsl:element name="{substring-before($vHeadVar, '=')}">
<xsl:value-of select="substring-after($vHeadVar, '=')"/>
</xsl:element>
<xsl:variable name="vRest" select="substring-after($vQueryPart, '&')"/>
<xsl:if test="string-length($vRest) > 0">
<xsl:call-template name="GetQueryStringParams">
<xsl:with-param name="pUrl" select=
"concat('?', substring(substring-after($vQueryPart, $vHeadVar), 2))"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When applied on any XML document (not used), this transformation produces the wanted, correct result:
bVar = "somethingMore"
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.