Print the same value many times in xslt - 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).

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)

XSLT tokenize nodeset

I'm trying to create a variable that stores the value of an input string (TypeInput) in init cap form. This new variable will be used in different places in my stylesheet. I created a template that I call to convert the input string to init cap form. However, when I run the stylesheet, the resulting variable TypeInputInitCap shows up as NodeSet(1) in the debugger and doesn't output text in my output. Any ideas why? See sample below.
<xsl:variable name="TypeInputInitCap">
<xsl:call-template name="ConvertToInitCapString">
<xsl:with-param name="str" select="$TypeInput"></xsl:with-param>
</xsl:call-template>
</xsl:variable>
<xsl:template name="ConvertToInitCapString">
<xsl:param name="str"></xsl:param>
<!-- Extract each component of the name delimited by . -->
<xsl:variable name="TokenNodeSet">
<xsl:for-each select="tokenize($str, '.')">
<!-- Init cap each component -->
<xsl:value-of select="concat(upper-case(substring(.,1,1)), lower-case(substring(.,2)))"></xsl:value-of>
</xsl:for-each>
</xsl:variable>
<xsl:for-each select="$TokenNodeSet">
<xsl:value-of select="."></xsl:value-of>
<xsl:if test="not(last())">
<xsl:text>.</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
I think that the problem is that the $TokenNodeSet variable contains just a single string, and so the second for-each just loops once.
What about doing this instead:
<xsl:template name="ConvertToInitCapString">
<xsl:param name="str"></xsl:param>
<xsl:for-each select="tokenize($str, '\.')">
<xsl:value-of select="concat(upper-case(substring(.,1,1)), lower-case(substring(.,2)))"/>
<xsl:if test="not(last())">
<xsl:text>.</xsl:text>
</xsl:if>
</xsl:for-each>
EDIT
Fixed the tokenize() call above as suggested by LarsH in the comments
I would replace
<xsl:variable name="TokenNodeSet">
<xsl:for-each select="tokenize($str, '.')">
<!-- Init cap each component -->
<xsl:value-of select="concat(upper-case(substring(.,1,1)), lower-case(substring(.,2)))"></xsl:value-of>
</xsl:for-each>
</xsl:variable>
with
<xsl:variable name="TokenNodeSet" select="for $token in tokenize($str, '\.') return concat(upper-case(substring($token,1,1)), lower-case(substring($token,2)))" />
or better yet with
<xsl:variable name="TokenNodeSet" as="xs:string*" select="for $token in tokenize($str, '\.') return concat(upper-case(substring($token,1,1)), lower-case(substring($token,2)))" />
or finally as it is XSLT 2.0 where there are no nodesets I would rename the variable as e.g.
<xsl:variable name="TokenSequence" as="xs:string*" select="for $token in tokenize($str, '\.') return concat(upper-case(substring($token,1,1)), lower-case(substring($token,2)))" />
Thanks all for your help. The second part in my template was not necessary, so I'm now using this version, which works. It re-adds a '.' character between the tokens. (I didn't use the short version suggested in this thread because I will end up with an extra dot at the end if concatenated.):
<xsl:template name="ConvertToInitCapString">
<xsl:param name="str" select="."></xsl:param>
<!-- Extract each component of the name delimited by . -->
<xsl:for-each select="tokenize($str, '\.')">
<!-- Init cap each component -->
<xsl:value-of select="concat(upper-case(substring(.,1,1)), lower-case(substring(.,2)))"></xsl:value-of>
<xsl:if test="position() != last()">
<xsl:value-of select="'.'"></xsl:value-of>
</xsl:if>
</xsl:for-each>
</xsl:template>

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

String showing as NodeSet in next iteration - 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>

xslt: create new node from mixed content within parent element

I'm trying to write a template that will grab a mixture of text nodes and elements within a parent element and create a new node. I've done a lot of searching and couldn't find what I was looking for...so hopefully I'm not asking to basic a question.
Here is a sample of xml I want to transform:
<?xml version="1.0"?>
<root>
<para>Here is some text that will ask users to enter a <rule-line/> [<emph type="it">date</emph>], and maybe their <rule-line/> [<emph type="it">name</emph>]. The text could come in different [<emph type="it">order</emph>] <rule-line/>, and their could be any number of instances.</para>
</root>
I want to group the bracketed text and the rule into a new element like so:
<entry>[<emph type"it">date</emph>]</entry>
I have a template that can identify the text I want to change, and I can change it, but I don't know how to add the text I want to the result tree and omit the old text.
Here are the relevant templates:
<xsl:template match="para">
<xsl:for-each select="* | text()">
<xsl:choose>
<xsl:when test="self::rule-line and following-sibling::node()[1][starts-with(., ' [')] and string(node-name(following-sibling::node()[2])) = 'emph' and following-sibling::node()[3][starts-with(., ']')]">
<xsl:comment>made match</xsl:comment>
<xsl:call-template name="codeEntry">
<xsl:with-param name="rule" select="."/>
<xsl:with-param name="openBracket" select="following-sibling::node()[1]"/>
<xsl:with-param name="emphTag" select="following-sibling::node()[2]"/>
<xsl:with-param name="closeBracketString" select="following-sibling::node()[3]"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
<xsl:template name="codeEntry">
<xsl:param name="rule"/>
<xsl:param name="openBracket"/>
<xsl:param name="emphTag"/>
<xsl:param name="closeBracketString"/>
<entry>
<xsl:copy-of select="$openBracket"/>
<xsl:copy-of select="$emphTag"/>
<xsl:text>] </xsl:text>
</entry>
<xsl:value-of select="substring-after($closeBracketString, ']')"/>
</xsl:template>
Obviously, the when statement grabs a group of nodes, but when each node goes through the otherwise block it gets copied to the result tree. I'm not really sure how to handle this since the para could have any number of these node groupings in any order, or none. (Once I figure this out I'll add another when block that deals with the bracketed text before the rule)
I think creating a variable that tells the template to ignore the node is the way to go...but I'm a little foggy on the immutable variables and their scope...
I was also trying to think of a way I could try to do this recursively...but that would require adding a start tag at one point, an end tag in another, or no tag if the node being processed is in the middle of the sequence...and I know that can get weird in xslt.
Anyone run into this type of situation before?
thanks,
jason
any ideas
Just for fun (What a mess of a schema!), this stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="rule-line"/>
<xsl:template match="emph">
<entry>
<xsl:text>[</xsl:text>
<xsl:call-template name="identity"/>
<xsl:text>]</xsl:text>
</entry>
</xsl:template>
<xsl:template match="text()[normalize-space()='[']
[following-sibling::*[1][self::emph]] |
text()[normalize-space()=']']
[preceding-sibling::*[1][self::emph]]"
priority="1"/>
<xsl:template match="text()[starts-with(normalize-space(),']')]
[preceding-sibling::*[1][self::emph]]">
<xsl:value-of select="substring-after(.,']')"/>
</xsl:template>
<xsl:template match="text()[substring(normalize-space(),
string-length(normalize-space()),
1) = '[']
[following-sibling::*[1][self::emph]]">
<xsl:call-template name="crop-both">
<xsl:with-param name="pString" select="concat(']',.)"/>
</xsl:call-template>
</xsl:template>
<xsl:template match="text()[starts-with(normalize-space(),']')]
[substring(normalize-space(),
string-length(normalize-space()),
1) = '[']
[preceding-sibling::*[1][self::emph]]
[following-sibling::*[1][self::emph]]"
priority="1" name="crop-both">
<xsl:param name="pString" select="."/>
<xsl:variable name="vReverse">
<xsl:call-template name="reverse">
<xsl:with-param name="pString"
select="substring-after(.,']')"/>
</xsl:call-template>
</xsl:variable>
<xsl:call-template name="reverse">
<xsl:with-param name="pString"
select="substring-after($vReverse,'[')"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="reverse">
<xsl:param name="pString"/>
<xsl:if test="$pString!=''">
<xsl:call-template name="reverse">
<xsl:with-param name="pString"
select="substring($pString,2)"/>
</xsl:call-template>
<xsl:value-of select="substring($pString,1,1)"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Output:
<root>
<para>Here is some text that will ask users to enter a <entry>[<emph type="it">date</emph>]</entry>, and maybe their <entry>[<emph type="it">name</emph>]</entry>. The text could come in different <entry>[<emph type="it">order</emph>]</entry>, and their could be any number of instances.</para>
</root>