calculate a String as an equation in xsl - xslt

I have this simple string stored in a variable :
<xsl:variable name="equation">
15+10+32+98
</xsl:variable>
and i want for xsl to take the string stored in this variable and deal with as a math equation to get the result, is there any suggestions ?

If you only have to sum up numbers, the following XSLT 1.0 template add takes a "plus separated string" as argument and returns the sum.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="text"/>
<xsl:template name="add">
<xsl:param name="plusSeparatedString"/>
<xsl:param name="sumValue" select="0"/>
<xsl:choose>
<xsl:when test="contains($plusSeparatedString,'+')">
<xsl:call-template name="add">
<xsl:with-param name="plusSeparatedString" select="substring-after($plusSeparatedString,'+')"/>
<xsl:with-param name="sumValue" select="$sumValue + substring-before($plusSeparatedString,'+')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$sumValue + $plusSeparatedString"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="/">
<xsl:call-template name="add">
<xsl:with-param name="plusSeparatedString" select="'4 + 6+ 8'"/>
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
Apply this test stylesheet to any document (e.g. itself) to see it works.

Related

How to replace commas with spaces in XSL

I need to replace every other comma with a space in my XML output. Right now, I have latitude and longitude that looks like this:
-0.52437106918239,0.391509433962264,-0.533805031446541,0.430817610062893,0
-0.547955974842767,0.427672955974843,
I need the coordinates in my XML output to look like this:
-0.52437106918239 0.391509433962264, -0.533805031446541 0.430817610062893,0
-0.547955974842767 0.427672955974843
How can I use XSLT to do this? Here is my xsl:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:kml="http://www.opengis.net/kml/2.2">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:apply-templates select="kml:kml/kml:Document/kml:Placemark/kml:Polygon
/kml:outerBoundaryIs/kml:LinearRing"/>
</xsl:template>
<xsl:template match="kml:LinearRing">
"POLYGON((<xsl:value-of select="kml:coordinates"/>))"
</xsl:template>
</xsl:stylesheet>
In XSLT 2.0 it would be trivial. You could use replace().
In XSLT 1.0, you could use template like so. Call the convert-space template on your list that needs every second comma replaced.
<xsl:template name="convert-space">
<xsl:param name="text"/>
<xsl:choose>
<xsl:when test="contains($text,',')">
<xsl:value-of select="substring-before($text,',')"/>
<xsl:value-of select="' '"/>
<xsl:call-template name="convert-comma">
<xsl:with-param name="text" select="substring-after($text,',')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="convert-comma">
<xsl:param name="text"/>
<xsl:choose>
<xsl:when test="contains($text,',')">
<xsl:value-of select="substring-before($text,',')"/>
<xsl:value-of select="','"/>
<xsl:call-template name="convert-space">
<xsl:with-param name="text" select="substring-after($text,',')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

Selecting a last word from a string in xslt

I just want to take the last element from a string which is like this "aaa-bbb-ccc-ddd" in xslt.
Out put should be "ddd" irrecspective of '-'s.
XSLT/Xpath 2.0 - using the tokenize() function to split the string on the "-" and then use a predicate filter to select the last item in the sequence:
<?xml version="1.0"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:value-of select="tokenize('aaa-bbb-ccc-ddd','-')[last()]"/>
</xsl:template>
</xsl:stylesheet>
XSLT/XPath 1.0 - using a recursive template to look for the last occurrence of "-" and selecting the substring following it:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:call-template name="substring-after-last">
<xsl:with-param name="input" select="'aaa-bbb-ccc-ddd'" />
<xsl:with-param name="marker" select="'-'" />
</xsl:call-template>
</xsl:template>
<xsl:template name="substring-after-last">
<xsl:param name="input" />
<xsl:param name="marker" />
<xsl:choose>
<xsl:when test="contains($input,$marker)">
<xsl:call-template name="substring-after-last">
<xsl:with-param name="input"
select="substring-after($input,$marker)" />
<xsl:with-param name="marker" select="$marker" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$input" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

Adding numbers in a string using XSLT

I have a string (in a variable) that has a list of numbers separated by space or comma.
I need to sum the numbers in the string.
example string "1,2,5,12,3"
or "1 2 5 12 3"
Is there a way to add the numbers within the string and return the total?
This much shorter transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="text()" name="sumStringList">
<xsl:param name="pText" select="."/>
<xsl:param name="pSum" select="0"/>
<xsl:param name="pDelim" select="','"/>
<xsl:choose>
<xsl:when test="not(string-length($pText) >0)">
<xsl:value-of select="$pSum"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="vnewList"
select="concat($pText,$pDelim)"/>
<xsl:variable name="vHead" select=
"substring-before($vnewList, $pDelim)"/>
<xsl:call-template name="sumStringList">
<xsl:with-param name="pText" select=
"substring-after($pText, $pDelim)"/>
<xsl:with-param name="pSum" select="$pSum+$vHead"/>
<xsl:with-param name="pDelim" select="$pDelim"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
when applied on the following XML document:
<t>1,2,5,12,3</t>
produces the wanted, correct result:
23
Explanation: Recursively called named template that also matches a text node. A sentinel (appended comma) is added to speed up and streamline processing.
II. XSLT 2.0 solution:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:param name="pDelim" select="','"/>
<xsl:template match="text()">
<xsl:sequence select=
"sum(for $s in tokenize(.,$pDelim)
return number($s)
)
"/>
</xsl:template>
</xsl:stylesheet>
When applied on the same XML document (above), this transformation produces the same wanted, correct answer:
23
Here we use the standard XPath 2.0 function tokenize() and we must convert every resulting token to number (using the number() function) before finally applying the standard XPath function sum().
I don't know XSLT, but generally you would split the string using spaces and commas as separators.
After a quick search I found that you can use tokenize(string, separator) as the split function if you are using XSLT 2.0. This page has an example on how to use tokenize.
Here is an XSLT 1.0 solution
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/">
<xsl:variable name="listOfValues" select="'1,2,5,12,3'" />
<xsl:call-template name="splitAndAdd">
<xsl:with-param name="list" select="$listOfValues"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="splitAndAdd">
<xsl:param name="list" />
<xsl:param name="delimiter" select="','"/>
<xsl:param name="total" select="0" />
<xsl:variable name="newList">
<xsl:choose>
<xsl:when test="contains($list, $delimiter)">
<xsl:value-of select="normalize-space($list)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat(normalize-space($list),$delimiter)" />
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="token"
select="substring-before($newList, $delimiter)" />
<xsl:variable name="remaining"
select="normalize-space(substring-after($newList, $delimiter))" />
<xsl:variable name="newTotal" select="$total + number($token)" />
<xsl:choose>
<xsl:when test="$remaining">
<xsl:call-template name="splitAndAdd">
<xsl:with-param name="delimiter" select="$delimiter"/>
<xsl:with-param name="list" select="$remaining"/>
<xsl:with-param name="total" select="$newTotal" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$newTotal" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

Using max in a variable in XSLT 1.0

can I use max function in a variable in XSLT 1?
I need to find a maximum value inside some nodes and I'll need to call this from more places.
So I tried to create a template:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:essox="urn:essox-scripts">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template name="Field001_max_dluznych_splatek">
<xsl:param name="CrRep"/>
<xsl:variable name="PocetDluznychSplatekSplatky">
<xsl:value-of
select="max($CrRep
/Response
/ContractData
/Installments
/InstDetail
/NrOfDueInstalments)" />
</xsl:variable>
<xsl:variable name="PocetDluznychSplatekKarty">
<xsl:value-of
select="max($CrRep
/Response
/ContractData
/Cards
/CardDetail
/NrOfDueInstalments)" />
</xsl:variable>
<xsl:choose>
<xsl:when test="$PocetDluznychSplatekSplatky
>= $PocetDluznychSplatekKarty">
<xsl:value-of select="$PocetDluznychSplatekSplatky"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$PocetDluznychSplatekKarty"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
In XML Spy I get this error:
Error in XPath expression Unknown
function - Name and number of
arguments do not match any function
signature in the static context -
'max'.
What is wrong?
Thanks a lot,
Peter
Use the well known maximum idiom:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:essox="urn:essox-scripts">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template name="Field001_max_dluznych_splatek">
<xsl:param name="CrRep"/>
<xsl:variable name="PocetDluznychSplatekSplatky">
<xsl:call-template name="maximun">
<xsl:with-param name="pSequence"
select="$CrRep
/Response
/ContractData
/Installments
/InstDetail
/NrOfDueInstalments"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="PocetDluznychSplatekSplatky">
<xsl:call-template name="maximun">
<xsl:with-param name="pSequence"
select="$CrRep
/Response
/ContractData
/Cards
/CardDetail
/NrOfDueInstalments"/>
</xsl:call-template>
</xsl:variable>
<xsl:choose>
<xsl:when test="$PocetDluznychSplatekSplatky
>= $PocetDluznychSplatekKarty">
<xsl:value-of select="$PocetDluznychSplatekSplatky"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$PocetDluznychSplatekKarty"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="maximun">
<xsl:param name="pSequence"/>
<xsl:for-each select="$pSequence">
<xsl:sort select="." data-type="number" order="descending"/>
<xsl:if test="position()=1">
<xsl:value-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Note: In a named template for reuse.
There is no max function in XSLT 1.0. You can work around this by sorting your elements in descending order and then taking the value of the first one.
Here's another (slower) way to do it:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="PocetDluznychSplatekSplatky"
select="/test/PocetDluznychSplatekSplatky/val[not(../val > .)][1]" />
<xsl:variable name="PocetDluznychSplatekKarty"
select="/test/PocetDluznychSplatekKarty/val[not(../val > .)][1]" />
<xsl:template match="/">
<xsl:choose>
<xsl:when
test="$PocetDluznychSplatekSplatky >=
$PocetDluznychSplatekKarty">
<xsl:value-of select="$PocetDluznychSplatekSplatky" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$PocetDluznychSplatekKarty" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Sample source document:
<test>
<PocetDluznychSplatekSplatky>
<val>22</val>
<val>3241</val>
<val>13</val>
</PocetDluznychSplatekSplatky>
<PocetDluznychSplatekKarty>
<val>1</val>
<val>3234341</val>
<val>13</val>
</PocetDluznychSplatekKarty>
</test>
Output:
3234341
The XPath in each variable's select looks like this:
/test/PocetDluznychSplatekSplatky/val[not(../val > .)][1]
Or, select the val element having no val siblings with a greater value (i.e. the max).
(Obviously, you'll need to adjust the XPath to fit your source document.)
Note: The sort solution performs much better (assuming an n*log(n) sort implementation). The second approach needs to compare each val to every one of its siblings and is therefore quadratic.

How to parse a delimited string using the index of the delimiter in xslt?

I have a delimited string which looks like below
FirstName&LastName&Address1&Address2&City&State&country&
I need to split the string based on the index of occurrence of the delimiter.
Say, if i need the first four values in the string, i should get the output in
two different variables, where the first variable should have the string with
first four values and the second variable having the remaining string.
I don't want to use exslt or any other extensions.
Please help me with a simple solution.
Input:
<xsl:variable name="index" select='4' />
<xsl:variable name="delimitedString" select='FirstName&LastName&Address1&Address2&City&State&country&' />
Output:
<xsl:variable name="requiredString">
FirstName&LastName&Address1&Address2&
</xsl:variable>
<xsl:variable name="remainingString">
City&State&country&
</xsl:variable>
This 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:param name="pSplitIndex" select="4"/>
<xsl:template match="text()" name="splitAt">
<xsl:param name="pLeft" select="''"/>
<xsl:param name="pRight" select="."/>
<xsl:param name="pSplitAt" select="$pSplitIndex"/>
<xsl:param name="pDelim" select="'&'"/>
<xsl:choose>
<xsl:when test=
"not(string-length($pRight))
or
not(contains($pRight, $pDelim))
">
<t1>
<xsl:value-of select="concat($pLeft, $pRight)"/>
</t1>
<t2/>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="$pSplitAt = 0">
<t1><xsl:value-of select="$pLeft"/></t1>
<t2><xsl:value-of select="$pRight"/></t2>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="splitAt">
<xsl:with-param name="pLeft" select=
"concat($pLeft, substring-before($pRight, $pDelim), $pDelim)
"/>
<xsl:with-param name="pRight"
select="substring-after($pRight, $pDelim)"/>
<xsl:with-param name="pSplitAt" select="$pSplitAt -1"/>
<xsl:with-param name="pDelim" select="$pDelim"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document:
<t>FirstName&LastName&Address1&Address2&City&State&country&</t>
produces the required two strings that result from splitting /t/text() at the fourth & delimiter:
<t1>FirstName&LastName&Address1&Address2&</t1>
<t2>City&State&country&</t2>
You can define a variable, say vrtfSplitResult whose body is the result of applying templates to the text node containing the string. Then, if you can use the xxx:node-set() extension, the two variables you want to define are:
<xsl:variable name="requiredString"
select="xxx:node-set($vrtfSplitResult)/*/t1">
<xsl:variable name="remainingString"
select="xxx:node-set($vrtfSplitResult)/*/t2">
In case you are not allowed to use even the xxx:node-set() extension function, then you should use a similar, named template: getStartingString. This has pretty much the logic of the splitAt template above:
<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:param name="pSplitIndex" select="4"/>
<xsl:template match="text()">
<xsl:variable name="vrequiredString">
<xsl:call-template name="getStartingString"/>
</xsl:variable>
<xsl:variable name="vremainingString" select=
"substring-after(.,$vrequiredString)"/>
<t>
<t1><xsl:value-of select="$vrequiredString"/></t1>
<t2><xsl:value-of select="$vremainingString"/></t2>
</t>
</xsl:template>
<xsl:template name="getStartingString">
<xsl:param name="pLeft" select="''"/>
<xsl:param name="pRight" select="."/>
<xsl:param name="pSplitAt" select="$pSplitIndex"/>
<xsl:param name="pDelim" select="'&'"/>
<xsl:choose>
<xsl:when test=
"not(string-length($pRight))
or
not(contains($pRight, $pDelim))
">
<xsl:value-of select="$pLeft"/>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="$pSplitAt = 0">
<xsl:value-of select="$pLeft"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="getStartingString">
<xsl:with-param name="pLeft" select=
"concat($pLeft, substring-before($pRight, $pDelim), $pDelim)
"/>
<xsl:with-param name="pRight"
select="substring-after($pRight, $pDelim)"/>
<xsl:with-param name="pSplitAt" select="$pSplitAt -1"/>
<xsl:with-param name="pDelim" select="$pDelim"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the same XML document, the wanted, correct result is produced again:
<t>
<t1>FirstName&LastName&Address1&Address2&</t1>
<t2>City&State&country&</t2>
</t>