How to add all comma separated values in XSLT - xslt

I have a question in XSLT.
I have one variable, suppose :
<xsl:variable name="myVar" select="string('1.3,2.1,3.3,5.1,11.4')">
I want to add all comma separated values.
This is my current code, where I am trying to sum up all csv values which are stored in $sum variable.
<xsl:template name="getCount" >
<xsl:param name="str" /> <!-- $str is having '0.001,0.003' value -->
<xsl:param name="delimiter" />
<xsl:param name="summation" />
<xsl:choose>
<xsl:when test="contains(string($str),string($delimiter))">
<xsl:variable name="beforecomma" select="substring-before(string($str),string($delimiter))" />
<xsl:variable name="aftercomma" select="substring-after(string($str),string($delimiter))" />
<xsl:choose>
<xsl:when test="$aftercomma=''">
<xsl:value-of select="$summation + $beforecomma" />
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="getindexvalue">
<xsl:with-param name="str" select="$aftercomma" />
<xsl:with-param name="delimiter" select="string($delimiter)" />
<xsl:with-param name="summation" select="$summation + beforecomma" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
</xsl:choose>
</xsl:template>

Assuming that the call-template is a typo and should be a recursive call to the same template
<xsl:call-template name="getCount">
then you're actually very very close to a working solution. The error is in
<xsl:with-param name="summation" select="$summation + beforecomma" />
where you're missing a $ in front of beforecomma:
<xsl:with-param name="summation" select="$summation + $beforecomma" />
Without the dollar you're looking for a child element named beforecomma rather than taking the value of the variable.
A few other comments:
you have a lot of redundant string() calls, you could simply say e.g. contains($str,$delimiter) instead of contains(string($str),string($delimiter)), as the functions you're using automatically coerce their arguments to string.
your current code requires at least two comma-separated values, it can't cope with being given just one value (for which the sum would just be the value itself). Structuring your template to cope with this would actually make it simpler:
<xsl:template name="getCount" >
<xsl:param name="str" /> <!-- $str is having '0.001,0.003' value -->
<xsl:param name="delimiter" />
<xsl:param name="summation" select="0" />
<xsl:choose>
<xsl:when test="contains($str,$delimiter)">
<xsl:variable name="beforecomma" select="substring-before($str,$delimiter)" />
<xsl:variable name="aftercomma" select="substring-after($str,$delimiter)" />
<xsl:call-template name="getCount">
<xsl:with-param name="str" select="$aftercomma" />
<xsl:with-param name="delimiter" select="$delimiter" />
<xsl:with-param name="summation" select="$summation + $beforecomma" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$summation + $str" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>

Related

Using 2 XSLT substring function in template

Why cant I use this XSLT string function in a template?
<xsl:with-param name="text" select="substring($text,'2') and substring($text,1,(string-length($text)-1))" />
Here is the template:
<!-- Template to remove double quotes if available in first and last position of any field -->
<xsl:template name="remove-quotes">
<xsl:param name="text"/>
<xsl:param name="quot" select="'"'"/>
<xsl:param name="trim1" select="substring($text,'2')"/>
<xsl:param name="trim2" select="substring($text,1,(string-length($text)-1))"/>
<xsl:choose>
<xsl:when test="starts-with($text,$quot) and ends-with($text,$quot)">
<xsl:call-template name="remove-quotes">
<xsl:with-param name="text" select="$trim1 and $trim2"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Invoked by:
<xsl:call-template name="remove-quotes">
<xsl:with-param name="text" select="XXXXX"/>
</xsl:call-template>
I am not sure what your template is trying to do, but certainly this part makes no sense:
<xsl:call-template name="remove-quotes">
<xsl:with-param name="text" select="$trim1 and $trim2"/>
</xsl:call-template>
and is Boolean operator. An expression that contains and returns a result of either true() or false().
Same thing with:
<xsl:with-param name="text" select="substring($text,'2') and substring($text,1,(string-length($text)-1))" />
Added:
To remove either a leading or a trailing quote or both, you could do simply:
<xsl:variable name="lead" select="number(starts-with($text, '"'))" />
<xsl:variable name="trail" select="number(ends-with($text, '"'))" />
<xsl:value-of select="substring($text, 1 + $lead, string-length($text) - $lead - $trail)" />
XSLT Template:
<!-- Template to remove trailing and leading double quotes from the fields -->
<xsl:template name="remove-quotes">
<xsl:param name="text"/>
<xsl:param name="quot" select="'"'"/>
<xsl:param name="lead" select="number(starts-with($text, '"'))"/>
<xsl:param name="trail" select="number(ends-with($text, '"'))"/>
<xsl:choose>
<xsl:when test="starts-with($text,$quot) and ends-with($text,$quot)">
<xsl:call-template name="remove-quotes">
<xsl:with-param name="text" select="substring($text, 1 + $lead, string-length($text) - $lead - $trail)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Invoke like this:
<xsl:call-template name="remove-quotes">
<xsl:with-param name="text" select="SampleText"/>
</xsl:call-template>

Sum of attributes after truncating a character from the value

I have the following xml.
<xml>
<table>
<cols width="1.00*" />
<cols width="2.00*" />
<cols width="4.00*" />
<row><p>Hello</p></row>
</table>
<p>
Life is good.
</p>
</xml>
Explaination:
I need to read the column width from the above xml and display. But in some cases user specifies the width so less that the table columns overlap on each other.
Hence I thought to do this formula.
col1width=col1width/totalWidth*100;
This will give me the table width in % format so that the columns get distributed properly.
But I am not able to take a total count of all these attributes. My xslt just does not work. Please see the xslt below:
XSLT:
<xsl:template match="node()" mode="table">
<fo:table table-layout="fixed">
<fo:table-header>
<fo:table-row>
<xsl:for-each select="current()/cols">
<xsl:variable name="maxWidth"
select="number(substring-before(current()/table/cols/#width, '*')) + number(substring-before(following-sibling::cols/#width, '*'))" />
</xsl:for-each>
</fo:table-row>
</fo:table>
Solutions tried:
I have tried using sum function. But here, before summing, i have to
truncate the '*' character and convert to number and then add. Does
not work.
Written a recursive template to get the sum. I am getting the sum with this. But I am not able to return the total width from the
template. I guess xslt does not support returning of calculated
values. Below is the recursive xslt.
<xsl:template name="maximumTableWidth">
<xsl:param name="total" select="0" />
<xsl:param name="totalCols" />
<xsl:param name="index" select="1" />
<xsl:if test="$index <= $totalCols">
<xsl:variable name="maxWidth"
select="$total + translate(current()/cols[$index]/#width, '*', '')" />
<xsl:call-template name="maximumTableWidth">
<xsl:with-param name="total" select="$maxWidth" />
<xsl:with-param name="nodes" select="$totalCols" />
<xsl:with-param name="index" select="$index + 1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
XSLT call:
<xsl:template name="main">
<xsl:variable name="maximumWidth">
<xsl:call-template name="maximumTableWidth">
<xsl:with-param name="total" select="0" />
<xsl:with-param name="totalCols"
select="count(current()/cols)" />
</xsl:call-template>
</xsl:variable>
</xsl:template>
Here, the variable is of type string and hence has no value.
Please help me with this problem. Also can suggest any other approach for table column width. I am generating pdf output using xsl fo. And my whole xslt is dynamic. I cannot have a direct path like node1/node2/node3.
Thank you.
You've got a couple of problems with your maximumTableWidth template to start with. Firstly, you should probably wrap the translate function in the number function
<xsl:variable name="maxWidth"
select="$total + number(translate(current()/cols[$index]/#width, '*', ''))" />
Secondly, you need to make sure you call it with the correct parameters. For your recursive call you set a parameter called nodes, when it should be totalCols
<xsl:with-param name="totalCols" select="$totalCols" />
But in terms of returning a value, all you need to do it use xsl:value-of to output the value, and your maximumWidth variable will then be set to that value. All you need to do is change the xsl:if in the template to an xsl:choose and output the value in the xsl:otherwise condition:
<xsl:template name="maximumTableWidth">
<xsl:param name="total" select="0" />
<xsl:param name="totalCols" />
<xsl:param name="index" select="1" />
<xsl:choose>
<xsl:when test="$index <= $totalCols">
<xsl:variable name="maxWidth"
select="$total + number(translate(current()/cols[$index]/#width, '*', ''))" />
<xsl:call-template name="maximumTableWidth">
<xsl:with-param name="total" select="$maxWidth" />
<xsl:with-param name="totalCols" select="$totalCols" />
<xsl:with-param name="index" select="$index + 1" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$total" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
There is another way to write this template recursively. Instead of passing in the index, and incrementing it, pass in the cols element itself, and use following-sibling to iterate over them. Try this template instead
<xsl:template name="maximumTableWidth">
<xsl:param name="col" />
<xsl:param name="total" select="0" />
<xsl:choose>
<xsl:when test="$col">
<xsl:variable name="maxWidth"
select="$total + number(translate($col/#width, '*', ''))" />
<xsl:call-template name="maximumTableWidth">
<xsl:with-param name="total" select="$maxWidth" />
<xsl:with-param name="col" select="$col/following-sibling::cols[1]" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$total" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
You would call this like so:
<xsl:variable name="maximumWidth">
<xsl:call-template name="maximumTableWidth">
<xsl:with-param name="col" select="cols[1]" />
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$maximumWidth" />
EDIT: If you were able to use XSLT 2.0, then you can do away with the named template altogether, and just set the maximumWidth template to this
<xsl:variable name="maximumWidth" select="sum(cols/(number(translate(#width, '*', ''))))" />

How do I do a str:replace in XSLT/XPATH 1.0?

In XPATH 2.0 there is a function that allows me to replace a substring in a string with another string. I'd like to do this using xalan. Unfortunately, it doesn't support the EXSLT method str:replace and it only uses XSLT 1.0 stylesheets. Including the function from exslt.org doesn't seem to work. If I try using the function style, it complains that it can't find str:replace. If I try using the template style, it complains that it can't find node-set, even though it is supported. translate is useless since it's just a character swap. Any ideas?
You can write your own function which can immitate xslt 2.0 replace :
<xsl:template name="replace">
<xsl:param name="text" />
<xsl:param name="replace" />
<xsl:param name="by" />
<xsl:choose>
<xsl:when test="contains($text, $replace)">
<xsl:value-of select="substring-before($text,$replace)" />
<xsl:value-of select="$by" />
<xsl:call-template name="replace">
<xsl:with-param name="text"
select="substring-after($text,$replace)" />
<xsl:with-param name="replace" select="$replace" />
<xsl:with-param name="by" select="$by" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
If you call it like this :
<xsl:variable name="replacedString">
<xsl:call-template name="replace">
<xsl:with-param name="text" select="'This'" />
<xsl:with-param name="replace" select="'This'" />
<xsl:with-param name="by" select="'That'" />
</xsl:call-template>
Your resulting $replacedString will have the value "That"

When test hanging in an infinite loop

I'm tokenising a string with XSLT 1.0 and trying to prevent empty strings from being recognised as tokens. Here's the entire function, based on XSLT Cookbook:
<xsl:template name="tokenize">
<xsl:param name="string" select="''" />
<xsl:param name="delimiters" select="';#'" />
<xsl:param name="tokensplitter" select="','" />
<xsl:choose>
<!-- Nothing to do if empty string -->
<xsl:when test="not($string)" />
<!-- No delimiters signals character level tokenization -->
<xsl:when test="not($delimiters)">
<xsl:call-template name="_tokenize-characters">
<xsl:with-param name="string" select="$string" />
<xsl:with-param name="tokensplitter" select="$tokensplitter" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="_tokenize-delimiters">
<xsl:with-param name="string" select="$string" />
<xsl:with-param name="delimiters" select="$delimiters" />
<xsl:with-param name="tokensplitter" select="$tokensplitter" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="_tokenize-characters">
<xsl:param name="string" />
<xsl:param name="tokensplitter" />
<xsl:if test="$string">
<token><xsl:value-of select="substring($string, 1, 1)"/></token>
<xsl:call-template name="_tokenize-characters">
<xsl:with-param name="string" select="substring($string, 2)" />
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="_tokenize-delimiters">
<xsl:param name="string" />
<xsl:param name="delimiters" />
<xsl:param name="tokensplitter" />
<!-- Extract a delimiter -->
<xsl:variable name="delimiter" select="substring($delimiters, 1, 1)"/>
<xsl:choose>
<!-- If the delimiter is empty we have a token -->
<xsl:when test="not($delimiter) and $string != ''">
<xsl:text>£</xsl:text>
<token><xsl:value-of select="$string"/></token>
<xsl:text>$</xsl:text>
<xsl:value-of select="$tokensplitter"/>
</xsl:when>
<!-- If the string contains at least one delimiter we must split it -->
<xsl:when test="contains($string, $delimiter)">
<!-- If it starts with the delimiter we don't need to handle the before part -->
<xsl:if test="not(starts-with($string, $delimiter))">
<!-- Handle the part that comes before the current delimiter with the next delimiter. -->
<!-- If there is no next the first test in this template will detect the token. -->
<xsl:call-template name="_tokenize-delimiters">
<xsl:with-param name="string" select="substring-before($string, $delimiter)" />
<xsl:with-param name="delimiters" select="substring($delimiters, 2)" />
<xsl:with-param name="tokensplitter" select="$tokensplitter" />
</xsl:call-template>
</xsl:if>
<!-- Handle the part that comes after the delimiter using the current delimiter -->
<xsl:call-template name="_tokenize-delimiters">
<xsl:with-param name="string" select="substring-after($string, $delimiter)" />
<xsl:with-param name="delimiters" select="$delimiters" />
<xsl:with-param name="tokensplitter" select="$tokensplitter" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<!-- No occurrences of current delimiter so move on to next -->
<xsl:call-template name="_tokenize-delimiters">
<xsl:with-param name="string" select="$string" />
<xsl:with-param name="delimiters" select="substring($delimiters, 2)" />
<xsl:with-param name="tokensplitter" select="$tokensplitter" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Value for string that I'm passing in is:
Europe;#6;#Global;#3;#Middle East,
Africa and
Caucasus;2;#Europe;#6;#Global;#3;#Middle
East, Africa and Caucasus
(The £ and $ indicators are just there so I can see no empty strings are output. This is within SharePoint so is difficult to debug.)
This code hangs processing of the XSLT. The line causing the problem is <xsl:when test="not($delimiter) and $string != ''">. As soon as I remove the second and test it works again. I've also tried and string($string) without success.
Anyone know why this is happening and how to resolve it?
I believe my suspicion was correct: you're falling through to your <xsl:otherwise> clause when $string has a value, but $delimiter does not, causing an infinite loop, as you say.
Add the following new <xsl:when> clause after the first one:
<xsl:when test="not($delimiter) and $string = ''" />
That will prevent the execution from entering the <xsl:otherwise> block when it shouldn't.
A more elaborate explanation of what's going on and why it's looping:
There are three branches in the <xsl:choose> block.
<xsl:when test="not($delimiter) and $string != ''">
<xsl:when test="contains($string, $delimiter)">
<xsl:otherwise>
So, when neither $string nor $delimiter contain values, the first condition fails (because $string != '' is false). The second condition passes (because contains(nil,nil) always returns true (confirmed in Visual Studio)), which calls the template again with the same parameters (because the substring-before returns the empty string since it doesn't contain the empty delimiter). Ergo, an infinite loop.
The fix is to add a new, empty condition:
<xsl:when test="not($delimiter) and $string != ''">
<xsl:when test="not($delimiter) and $string = ''" />
<xsl:when test="contains($string, $delimiter)">
<xsl:otherwise>
EDIT: I've poked around and I can't find a reference to the defined behaviour of contains when the second parameter is empty or nil. Tests have shown that Microsoft Visual Studio's XSLT engine returns true when the second parameter is either empty or nil. I'm not sure if that's the defined behaviour or if it's up to the implementor to decide. Does anyone have a conclusive answer to this? Tomalak, I'm looking at you.
Isn't string a reserved word? Can you try to replace that name for anything else?
EDIT: Supplied code ran without problem here: XSLT Tryit Editor v1.0 using:
<xsl:call-template name="tokenize">
<xsl:with-param name="string">Europe;#6;#Global...</xsl:with-param>
</xsl:call-template>

How to replace double quotes with typographer's quotes (aka curly quotes)

I need to programatically replace "regular double quotes" with “typographer's quotes”
My initial thought is something like this:
<xsl:variable name="text">
<xsl:call-template name="replace-string"><!-- FYI: replace-string is a custom method that works like you would expect-->
<xsl:with-param name="text" select="."/>
<xsl:with-param name="replace" select="string(' "')" /><!-- left quote because of space before -->
<xsl:with-param name="with" select="string('“')"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="text2">
<xsl:call-template name="replace-string">
<xsl:with-param name="text" select="$text"/>
<xsl:with-param name="replace" select="string('" ')" /><!-- right quote because of space after -->
<xsl:with-param name="with" select="string('”')"/>
</xsl:call-template>
</xsl:variable>
<xsl:apply-templates select="$text2" />
My worry is situations where there is not a determining space by the quote. Such as these.
They say "this is great".
I like tigers ("big large cats").
Has anyone ever had to do this before that knows some extra rules to apply or a different strategy?
Thanks!
A solution that works without extension functions would be:
<xsl:template name="typograpic-quotes">
<xsl:param name="text" select="''" />
<xsl:param name="quote" select="'"'" />
<xsl:param name="open" select="'“'" />
<xsl:param name="close" select="'”'" />
<xsl:param name="inquote" select="false()" />
<xsl:choose>
<xsl:when test="contains($text, $quote)">
<xsl:value-of select="substring-before($text, $quote)" />
<xsl:choose>
<xsl:when test="$inquote">
<xsl:value-of select="$close" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$open" />
</xsl:otherwise>
</xsl:choose>
<xsl:call-template name="typograpic-quotes">
<xsl:with-param name="text" select="substring-after($text, $quote)" />
<xsl:with-param name="quote" select="$quote" />
<xsl:with-param name="open" select="$open" />
<xsl:with-param name="close" select="$close" />
<xsl:with-param name="inquote" select="not($inquote)" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Needless to say that unbalanced quotes in the input will make this fail.
Regular Expressions.
regex:replace($textVariable, '"([^"]*)"' , 'gi', '“$1”')
Havent tested this, but this is an easy approach IMHO. you match all "(anything not ")zero or more times" and replace that with your other typographical quotes. The $1 is a back-reference to the first match. While there are issues here such as incorrectly nested text, quotes that are not closed etc., I am suggesting something like this. You may rewrite the regex for this one and test it out.
This is assuming that your XSLT processor supports the EXSLT extensions.
Because we have some unbalanced quotes. I am working on something a little more pragmatic. The below seems to work in all cases I have tried.
<!-- Get text. All quotes defaulted to right quote -->
<xsl:variable name="text">
<xsl:call-template name="replace-string">
<xsl:with-param name="text" select="."/>
<xsl:with-param name="replace" select="string('"')" />
<xsl:with-param name="with" select="string('”')"/>
</xsl:call-template>
</xsl:variable>
<!-- Turn quotes preceded by a space into left quote -->
<xsl:variable name="text2">
<xsl:call-template name="replace-string">
<xsl:with-param name="text" select="$text"/>
<xsl:with-param name="replace" select="string(' ”')" />
<!-- right quote because of space after -->
<xsl:with-param name="with" select="string(' “')"/>
</xsl:call-template>
</xsl:variable>
<!-- Turn quotes preceded by a parenthesy into left quote -->
<xsl:variable name="text3">
<xsl:call-template name="replace-string">
<xsl:with-param name="text" select="$text2"/>
<xsl:with-param name="replace" select="string('(”')" />
<!-- right quote because of space after -->
<xsl:with-param name="with" select="string('(“')"/>
</xsl:call-template>
</xsl:variable>
<!-- Turn quotes that are the first character in the text into left quote -->
<!-- Note: this one is still a little funky. For some reason the first character is always whitespace. So I am checking the second character because it is really the first. -->
<xsl:variable name="text4" >
<xsl:choose>
<xsl:when test="normalize-space(substring( $text3, 2, 2 )) = string('”')">
<xsl:value-of select="string('“')"/>
<xsl:value-of select="substring($text3, 3)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text3"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:apply-templates select="$text4" />