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

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"

Related

How to add all comma separated values in 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>

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, '*', ''))))" />

Replacing string in xslt

I am using xslt1.0. My input is
<xsl:variable name="string">width:12pt;border-width:13pt</xsl:variable>
I want to replace the -width: into some other string, but not the width:(width starting with - alone should replaced).How to do it in xslt 1.0.
There is no replace function in XSLT 1.0. You can find here (String.Replace() in XSLT) a template that can do that.
You can use it like this:
<xsl:variable name="string">
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="width:12pt;border-width:13pt" />
<xsl:with-param name="replace" select="-width" />
<xsl:with-param name="by" select="other-string" />
</xsl:call-template>
</xsl:variable>

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" />

Replacing XML value in XSLT

I can't edit XML, I just want to change XML data in an XSLT file.
<xsl:value-of select="Name" disable-output-escaping="yes"/>
The value of XML data is "Northfield Bancorp Inc.(MHC)" and I want to replace it with "Northfield Bancorp Inc." (remove "MHC").
Is there any function available in XSLT which can search and replace the this?
If it is just the "(MHC)" at the end of the string you want to remove, this would do:
<xsl:value-of select="
substring-before(
concat(Name, '(MHC)'),
'(MHC)'
)
" />
If you want to replace dynamically, you could write a function like this:
<xsl:template name="string-replace">
<xsl:param name="subject" select="''" />
<xsl:param name="search" select="''" />
<xsl:param name="replacement" select="''" />
<xsl:param name="global" select="false()" />
<xsl:choose>
<xsl:when test="contains($subject, $search)">
<xsl:value-of select="substring-before($subject, $search)" />
<xsl:value-of select="$replacement" />
<xsl:variable name="rest" select="substring-after($subject, $search)" />
<xsl:choose>
<xsl:when test="$global">
<xsl:call-template name="string-replace">
<xsl:with-param name="subject" select="$rest" />
<xsl:with-param name="search" select="$search" />
<xsl:with-param name="replacement" select="$replacement" />
<xsl:with-param name="global" select="$global" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$rest" />
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$subject" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Which would be callable as:
<xsl:call-template name="string-replace">
<xsl:with-param name="subject" select="Name" />
<xsl:with-param name="search" select="'(MHC)'" />
<xsl:with-param name="replacement" select="''" />
<xsl:with-param name="global" select="true()" />
</xsl:call-template>
XSLT 2.0 has a replace() function.
If you're stuck with 1.0, there is a template in the standard library that can stand in for the lack of a native function:
http://prdownloads.sourceforge.net/xsltsl/xsltsl-1.2.1.zip
http://xsltsl.sourceforge.net/string.html#template.str:subst
another xslt 1.0 template from exslt.org
http://www.exslt.org/str/functions/replace/str.replace.template.xsl