String showing as NodeSet in next iteration - XSLT - xslt

This must be easy, but I am not able to figure it out.
Aim is to replace '95' with '3F' if Substring length before '95' is an even number. If Substring length before '95' is not an even number, take the value of sub-string alongwith the value of '9' in '95' and start looking for occurrence of 95 again recursively to replace the value.
Final string will have value replaced with '3F'
A string value is assigned to 'inputHex' variable. The same is passed to the 'inputHexStr' for invoking a template replace95. In the given string first value of '95' occurs at position 134 and 135. therefore the condition not(string-length(substring-before($inputHexStr,$from)) mod 2 = 0) gets invoked. Problem is... in the next invocation to 'replace95' template with the remaining value of string, the string is not accessible and debug shows as a nodeset.
Why is that ?
Update - for the input xml as <x/> the next iteration is changing the context to the root node of the given xml file and the value of inputHexStr is shown as nodeset instead of string.
<?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 match="/">
<xsl:variable name="inputHex"
select="'3C3F786D6C2076657273696F6E3D22312E302220656E636F64696E673D227574662D3822203F3E3C783E74686973206973206120626164206368617261637465722019520737472696E6720646174613C2F7895'"/>
<xsl:variable name="hexResult">
<xsl:call-template name="replace95">
<xsl:with-param name="inputHexStr" select="$inputHex"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$hexResult"/>
</xsl:template>
<xsl:template name="replace95">
<xsl:param name="inputHexStr" select="."/>
<xsl:param name="from" select="'95'"/>
<xsl:param name="to" select="'3F'"/>
<xsl:choose>
<xsl:when test="not(contains($inputHexStr,$from))">
<xsl:value-of select="$inputHexStr"/>
</xsl:when>
<xsl:otherwise>
<xsl:if test="string-length(substring-before($inputHexStr,$from)) mod 2 = 0">
<xsl:value-of select="substring-before($inputHexStr,$from)"/>
<xsl:value-of select="$to"/>
<xsl:call-template name="replace95">
<xsl:with-param name="inputHexstr"
select="substring-after($inputHexStr,$from)"/>
<xsl:with-param name="from" select="$from"/>
<xsl:with-param name="to" select="$to"/>
</xsl:call-template>
</xsl:if>
<xsl:if test="not(string-length(substring-before($inputHexStr,$from)) mod 2 = 0)">
<xsl:variable name="no95part"
select="substring($inputHexStr,1,string-length(substring-before($inputHexStr,$from))+1)"/>
<xsl:value-of select="$no95part"/>
<xsl:variable name="no95Length" select="string-length($no95part)"/>
<xsl:call-template name="replace95">
<xsl:with-param name="inputHexstr" select="substring($inputHexStr,$no95Length+1)"/>
<xsl:with-param name="from" select="$from"/>
<xsl:with-param name="to" select="$to"/>
</xsl:call-template>
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

I think this is because of a typo when you recursively call your templates with parameters.
<xsl:with-param name="inputHexstr" select=...
You have a lower case s here. XSLT is case-sensitive, so it should be a capital S
<xsl:with-param name="inputHexStr" select=...
Note that, it might be worth combining your two xls:if statements into a single xsl:choose to avoid extra calculations on the remainder
<xsl:choose>
<xsl:when test="not(contains($inputHexStr,$from))">...</xsl:when>
<xsl:when test="string-length(substring-before($inputHexStr,$from)) mod 2 = 0">...</xsl:when>
<xsl:otherwise>...</xsl:otherwise>
</xsl:choose>

Related

Sum value of output of template in Apache FOP

I am using Apache FOP to generate a PDF document, and to display a certain value I have to iterate over a number of nodes to determine a total price value, then sum that value. So far I have a function that iterates over an array and then retrieves the intended value, but the issue occurs when I try to sum the results.
<xsl:function name="foo:buildTotalValue">
<xsl:param name="items" />
<xsl:variable name="totals">
<xsl:for-each select="$items/charge">
<xsl:call-template name="getTotalPriceNode">
<xsl:with-param name="itemParam" select="." />
</xsl:call-template>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="sum(exsl:node-set($totals))" />
</xsl:function>
<xsl:template name="getTotalPriceNode">
<xsl:param name="itemParam" />
<xsl:choose>
<xsl:when test="$itemParam/Recurrance = 'OnceOff'">
<xsl:value-of select="$itemParam/TotalValue" />
</xsl:when>
<xsl:when test="$itemParam/Recurrance = 'Monthly'">
<xsl:value-of select="$itemParam/TotalValue * $itemParam/Months"/>
</xsl:when>
<xsl:otherwise><xsl:value-of select="0" /></xsl:otherwise>
</xsl:choose>
</xsl:template>
I'm hoping that when I pass in foo:buildTotalValue with entries like this:
<Charges>
<Charge>
<Recurrance>OnceOff</Recurrance>
<TotalValue>50.00</TotalValue>
</Charge>
<Charge>
<Recurrance>Monthly</Recurrance>
<TotalValue>10.00</TotalValue>
<Months>6</Months>
</Charge>
</Charges>
would return with the value 110.00, but instead I get the error:
Cannot convert string "50.0060.00" to double
I've tried adding a <value> or something in the templates and then using that as a selector for the exsl:node-set function but it doesn't seem to make a difference.
AFAICT, the problem with your function is that it builds a concatenated string of values returned by the called template, instead of a tree of nodes that can be converted into a node-set and summed.
Try changing:
<xsl:for-each select="$items/charge">
<xsl:call-template name="getTotalPriceNode">
<xsl:with-param name="itemParam" select="." />
</xsl:call-template>
</xsl:for-each>
to:
<xsl:for-each select="$items/charge">
<total>
<xsl:call-template name="getTotalPriceNode">
<xsl:with-param name="itemParam" select="." />
</xsl:call-template>
</total>
</xsl:for-each>
and:
<xsl:value-of select="sum(exsl:node-set($totals))" />
to:
<xsl:value-of select="sum(exsl:node-set($totals)/total)" />
Untested, because (see comment to your question).
I ended up using the suggestion from Martin from the comment - the xpath 2+ expression along the line of:
sum(Charge[Recurrance = 'OnceOff']/TotalValue | Charge[Recurrance = 'Monthly']/(TotalValue * Months))
which was able to achieve what I needed without the use of functions / templates / node-set (And in a lot less code)

Split Attribute using ; as delimiter in XSLT

How to split an elements using ; as delimiter.my requirement is like below.
input:
<Element1>C:KEK39519US; U:085896395195; A:K39519US; B:S2345843</Element1>
output:
<CustItem>KEK39519US</CustItem>
<UNumber>085896395195</UNumber>
<ANumber>K39519US</ANumber>
<BNumber>S2345843</BNumber>
the input is every time not same.some times it comes like C:KEK39519US; U:085896395195; B:S2345843
some time like this C:KEK39519US; A:K39519US; B:S2345843
sometime like this U:085896395195; A:K39519US;
sometime like this C:KEK39519US; U:085896395195; A:K39519US;
To solve this in XSLT 1.0 you may need a named template which recursively calls itself. The template will process of the string before the first semi-colon, and output the element accordingly. It will then recursively call itself with the remaining part of the string after this semi-colon (if there is one)
Here is the full XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="Element1">
<xsl:call-template name="outputElements">
<xsl:with-param name="list" select="." />
</xsl:call-template>
</xsl:template>
<xsl:template name="outputElements">
<xsl:param name="list"/>
<xsl:variable name="first" select="normalize-space(substring-before(concat($list, ';'), ';'))"/>
<xsl:variable name="remaining" select="normalize-space(substring-after($list, ';'))"/>
<xsl:call-template name="createElement">
<xsl:with-param name="element" select="$first" />
</xsl:call-template>
<!-- If there are still elements left in the list, call the template recursively -->
<xsl:if test="$remaining">
<xsl:call-template name="outputElements">
<xsl:with-param name="list" select="$remaining"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="createElement">
<xsl:param name="element"/>
<xsl:variable name="elementName">
<xsl:choose>
<xsl:when test="substring-before($element, ':') = 'C'">CustItem</xsl:when>
<xsl:otherwise><xsl:value-of select="concat(substring-before($element, ':'), 'Number')" /></xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:element name="{$elementName}">
<xsl:value-of select="substring-after($element, ':')" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
When applied to you XML, the following is output
<CustItem>KEK39519US</CustItem>
<UNumber>085896395195</UNumber>
<ANumber>K39519US</ANumber>
<BNumber>S2345843</BNumber>
Note the use of Attribute Value Templates in specifying the name of each new element.

How do i remove multiple numbers based on a search of multiple node values?

I am fairly new to XSLT and have scanned through several posts on the topic but cant seem to get the final peice i need to make this work. I am attempting to remove entries from a known string of data that appear in the node data that i have. I have peiced together a solution that works for single node values but not multiple values.
here is my xml
<root>
<item>2</item>
<item>9</item>
<item>5</item>
</root>
here is my code that works for one node value:
<xsl:template match="item">
<xsl:copy>
<xsl:call-template name="replaceChars">
<xsl:with-param name="original" select="string('1 2 3 4 5 6 7 8 9 10')"/>
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="replaceChars">
<xsl:param name="original"/>
<xsl:choose>
<xsl:when test="contains($original, current())">
<xsl:value-of select="substring-before($original, current())"/>
<xsl:variable name="after" select="substring-after($original, current())"/>
<xsl:variable name="char" select="substring-before($after, current())"/>
<xsl:value-of select="concat($char, $after)"/>
<xsl:call-template name="replaceChars">
<xsl:with-param name="original" select="substring-after($after, current())"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$original"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
my latest of testing i am attempting to use this:
<xsl:template match="item">
<xsl:copy>
<xsl:call-template name="replaceChars">
<xsl:with-param name="original" select="string('1 2 3 4 5 6 7 8 9 10')"/>
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="replaceChars">
<xsl:param name="original"/>
<xsl:choose>
<xsl:when test="contains($original, current())">
<xsl:variable name="before" select="substring-before($original, current())"/>
<xsl:variable name="after" select="substring-after($original, current())"/>
<xsl:variable name="char" select="substring-before($after, current())"/>
<xsl:variable name="new" select="concat($before, $after)"/>
<xsl:call-template name="replaceChars">
<xsl:with-param name="original" select="$new"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$original"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
i contiune to get the value iterated several times in the response. i would like my output to be the following:
1 3 4 6 7 8 10
I have searched on this extensively as you can see my example is based on an altered searched scenario. any help would be appreciated.
You're close.
If you want to produce the string "1 3 4 6 7 8 10" once, then you probably don't want the code you show to be evaluated once for each item element, but once for each root element.
If you want the recursive template replaceChars to knock out the value of each item in the set of sibling item elements, then the current structure won't do it. Why? Because it knocks out the value of current() and then calls itself recursively without doing anything to change the value of current().
One alternative approach would be to say "I want to build a string consisting of the number 1 (unless it appears in the input), followed by the number 2 (unless it appears in the input), followed by ..." and write (n.b. not tested):
<xsl:template match="root">
<survivors>
<xsl:if test="not(./item = '1')">1 </xsl:if>
<xsl:if test="not(./item = '2')">2 </xsl:if>
<xsl:if test="not(./item = '3')">3 </xsl:if>
<xsl:if test="not(./item = '4')">4 </xsl:if>
<xsl:if test="not(./item = '5')">5 </xsl:if>
<xsl:if test="not(./item = '6')">6 </xsl:if>
<xsl:if test="not(./item = '7')">7 </xsl:if>
<xsl:if test="not(./item = '8')">8 </xsl:if>
<xsl:if test="not(./item = '9')">9 </xsl:if>
</survivors>
</xsl:template>
Or if you really want to do the substring-match thing, you need to ensure that your recursion in replaceChars actually iterates over the item elements:
<xsl:template match="root">
<survivors>
<xsl:call-template name="replaceChars2">
<xsl:with-param name="s" select="'1 2 3 4 5 6 7 8 9'"/>
<xsl:with-param name="item" select="./item[1]"/>
</xsl:call-template>
</survivors>
</xsl:template>
<xsl:template name="replaceChars2">
<xsl:param name="s"/>
<xsl:param name="item"/>
<xsl:variable name="s2" select="string($item)"/>
<xsl:choose>
<xsl:when test="$item">
<xsl:call-template name="replaceChars2">
<xsl:with-param name="s"
select="concat(
substring-before($s,$s2,
' ',
substring-after($s,$s2,
)"/>
<xsl:with-param name="item"
select="./following-sibling::item[1]"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$s"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Like the code shown by the OP, this assumes no item element will accidentally match any part of the string that should not be deleted (e.g. the original string will never have a value like '11' if any item will ever have the value '1').
The pattern of iterating over siblings and passing parameters to keep track of what happened with the earlier siblings is an important idiom to learn, for transforms like this one.
This transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my">
<xsl:output method="text"/>
<my:sent/>
<xsl:variable name="vSent" select="document('')/*/my:sent"/>
<xsl:param name="pGiven" select="'1 2 3 4 5 6 7 8 9 10'"/>
<xsl:template match="/*">
<xsl:apply-templates select="item[1]"/>
</xsl:template>
<xsl:template match="item">
<xsl:param name="pText" select="$pGiven"/>
<xsl:apply-templates select=
"following-sibling::item[1]|$vSent[not(current()/following-sibling::item)]">
<xsl:with-param name="pText" select=
"concat(substring-before(concat($pText, .), .),
substring-after($pText,.)
)
"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="my:sent">
<xsl:param name="pText"/>
<xsl:value-of select="$pText"/>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<root>
<item>2</item>
<item>9</item>
<item>5</item>
</root>
produces the wanted, correct result:
1 3 4 6 7 8 10
Do note:
There is no XSLT conditional instruction in the whole transformation -- no xsl:choose, no xsl:when, no xsl:otherwise no xsl:if .
There is no named template and no explicit recursion (although xsl:apply-templates recurses implicitly).
Sentinel programming is used in two different ways to significantly simplify the code and make it more efficient.

Print the same value many times in xslt

I have the variable x which is a number. I have a line. ("<name>James</name>") I need to print this sentence number x times. Can I do it in an easy way? without being complex?
If you are using XSLT 2.0 then you can do this ...
<xsl:for-each select="for $i in 1 to $x return $i">
<name>James</name>
</xsl:for-each>
The following is untested...
<xsl:call-template name="show">
<xsl:with-param name="text"><name>James</name></xsl:with-param>
<xsl:with-param name="count">50</xsl:with-param>
</xsl:call-template>
<xsl:template name="show">
<xsl:param name="text"/>
<xsl:param name="count"/>
<xsl:value-of select="$text"/>
<xsl:if test="number($count)>0">
<xsl:call-template name="show">
<xsl:with-param name="text" select="$text"/>
<xsl:with-param name="count" select="number($count)-1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
Updated to have <name> and </name>.
Here is an XmlPlayground of the above working
You could adding the following somewhere in your stylesheet:
<mydata>
<x/><x/><x/><x/> <!-- to print four times -->
</mydata>
then
<xsl:for-each select="document()//mydata/x">
<name>James</name>
</xsl:for-each>
This takes advantage of the ability to include your own data in an XSLT program, and access it through the document function (no argument indicates the stylesheet itself).

XSLT : Looping from 1 to 60

What is the best way to loop in XSLT from 1 to 60?
I research in net, there are some templates to do this, is there any other way for example like a built-in function?
In XSLT 2.0,
<xsl:for-each select="1 to 60">...</xsl:for-each>
But I guess that you must be using XSLT 1.0, otherwise you wouldn't be asking.
In XSLT 1.0 you should use recursion: a template that calls itself with a counter that's incremented on each call, and the recursion terminates when the required value is reached.
Alternatively there's a workaround in XSLT 1.0: provided your source document contains at least 60 nodes, you can do
<xsl:for-each select="(//node())[60 >= position()]">...</xsl:for-each>
The problem with simple recursion when processing long sequences is that often the space for the call stack becomes insufficient and the processing ends due to stack overflow. This typically happens with sequence length >= 1000.
A general technique to avoid this (implementable with any XSLT processor, even if it doesn't recognize tail-recursion) is DVC (Divide and Conquer) style recursion.
Here is an example of a transformation that successfully prints the numbers from 1 to 1000000 (1M):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:call-template name="displayNumbers">
<xsl:with-param name="pStart" select="1"/>
<xsl:with-param name="pEnd" select="1000000"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="displayNumbers">
<xsl:param name="pStart"/>
<xsl:param name="pEnd"/>
<xsl:if test="not($pStart > $pEnd)">
<xsl:choose>
<xsl:when test="$pStart = $pEnd">
<xsl:value-of select="$pStart"/>
<xsl:text>
</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="vMid" select=
"floor(($pStart + $pEnd) div 2)"/>
<xsl:call-template name="displayNumbers">
<xsl:with-param name="pStart" select="$pStart"/>
<xsl:with-param name="pEnd" select="$vMid"/>
</xsl:call-template>
<xsl:call-template name="displayNumbers">
<xsl:with-param name="pStart" select="$vMid+1"/>
<xsl:with-param name="pEnd" select="$pEnd"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When applied on any XML document (not used) this transformation produces the wanted result -- all the numbers from 1 to 1000000.
You can use/adapt this transformation for any task that needs to "do something N times".
Very simple check inside the foreach-loop
<xsl:if test="$maxItems > position()">
do something
</xsl:if>
Based on Dimitre Novatchev's answer.
Example:
<xsl:variable name="maxItems" select="10" />
<xsl:variable name="sequence" select="any-sequence"/>
<xsl:for-each select="$sequence">
<!-- Maybe sort first -->
<xsl:sort select="#sort-by" order="descending" />
<!-- where the magic happens -->
<xsl:if test="$maxItems > position()">
do something
</xsl:if>
</xsl:for-each>
The basic example for V1.0 using recursion would it be like this:
<xsl:template match="/">
<Root>
<!-- Main Call to MyTemplate -->
<xsl:call-template name="MyTemplate" />
</Root>
</xsl:template>
<xsl:template name="MyTemplate">
<xsl:param name="index" select="1" />
<xsl:param name="maxValue" select="60" />
<MyCodeHere>
<xsl:value-of select="$index"/>
</MyCodeHere>
<!-- < represents "<" for html entities -->
<xsl:if test="$index < $maxValue">
<xsl:call-template name="MyTemplate">
<xsl:with-param name="index" select="$index + 1" />
<xsl:with-param name="total" select="$maxValue" />
</xsl:call-template>
</xsl:if>
</xsl:template>
XSLT works based on templates and you'll need a template do run that loop.
You'll need to build a template receiving start and end values and, inside it, make a recursive call computing with start + 1. When $start equals $end, you do return your template, without another call.
In practice: http://www.ibm.com/developerworks/xml/library/x-tiploop/index.html