How to trim result string value? - xslt

<xsl:value-of select="IPADDRESS" />
The above line returns the IP address 192.123.201.21 but I want the output to be 192.123.201. How to split the string at . and remove the last token?

In XSLT 1.0 you need to work a little harder at it:
<xsl:variable name="lastOctet" select="substring-after(substring-after(substring-after(IPADDRESS, '.'), '.'), '.')" />
<xsl:value-of select="substring(IPADDRESS, 1, string-length(IPADDRESS) - string-length($lastOctet) - 1)" />

The XPath 1.0 substring-before and substring-after functions can give you the substring before/after the first occurrence of a given separator, but to find the substring before the last occurrence you need to use a tail-recursive template
<xsl:template name="substring-before-last">
<xsl:param name="str" />
<xsl:param name="separator" />
<xsl:param name="prefix" select="''" /><!-- first segment - no prefix -->
<xsl:variable name="after-first" select="substring-after($str, $separator)" />
<xsl:if test="$after-first">
<xsl:value-of select="concat($prefix, substring-before($str, $separator))" />
<xsl:call-template name="substring-before-last">
<xsl:with-param name="str" select="$after-first" />
<xsl:with-param name="separator" select="$separator" />
<!-- for second and subsequent segments, prepend a $separator -->
<xsl:with-param name="prefix" select="$separator" />
</xsl:call-template>
</xsl:if>
</xsl:template>
This template keeps writing out segments between separators until it reaches a point where there are no more instances of the separator string. You would call it by replacing your xsl:value-of element with
<xsl:call-template name="substring-before-last">
<xsl:with-param name="str" select="IPADDRESS" />
<xsl:with-param name="separator" select="'.'" /><!-- note the single quotes -->
</xsl:call-template>

With XSLT 2.0 you could use <xsl:value-of select="tokenize(IPADDRESS, '\.')[position() lt last()]" separator="."/>.

This should work (referring to your title: "how to trim result string value?"):
<xsl:value-of select="substring(IPADDRESS,1,11)" />
Can you rely on the IPADDRESS element to always have the same structure and content? If so, there is no need to tokenize.

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)

How to replace variable inside curly braces with values in different tags in xslt

I am processing an xml file using xslt.
<ns1:declarationStatements>
<ns1:parameterisedEntity>
<ns2:code>NUTSUPSTATE20</ns2:code>
<ns2:localeData>
<ns1:description>
<![CDATA[** When {s} according to instructions {m}g typically weighs {m}g.]]>
</ns1:description>
<ns1:id>20253</ns1:id>
</ns2:localeData>
<ns2:specType>FOOD</ns2:specType>
<ns2:id>6653</ns2:id>
</ns1:parameterisedEntity>
<ns1:textParameters>
<ns1:value>228</ns1:value>
<ns1:id>68225</ns1:id>
<ns1:sequence>2</ns1:sequence>
</ns1:textParameters>
<ns1:textParameters>
<ns1:value>cooked</ns1:value>
<ns1:id>68233</ns1:id>
<ns1:sequence>0</ns1:sequence>
</ns1:textParameters>
<ns1:textParameters>
<ns1:value>255</ns1:value>
<ns1:id>68229</ns1:id>
<ns1:sequence>1</ns1:sequence>
</ns1:textParameters>
<ns1:id>133421</ns1:id>
</ns1:declarationStatements>
I want to get the text inside <ns1:description> i.e.-
**When {s} according to instructions {m}g typically weighs {m}g
But I want {s}, {m} and {m} to be replaced by the values in <ns1:textParameters>/<ns1:value>. It should look like -
**When cooked according to instructions 255g typically weighs 228g.
I tried doing that by using <xsl:value-of select="ns0:declarationStatements"> and the manipulating string but it is becoming very tedious and complex.
The number of such braces may also vary. So do we have anything like List or Array in XSLT?
Is there any other way or trick I can use to solve this problem?
Thanks
Assuming the parameters are meant to be inserted in order of their ns1:sequence value, I would start by defining a key as:
<xsl:key name="text-param" match="ns1:textParameters" use="ns1:sequence" />
then call the following recursive template with ns1:description as the string param:
<xsl:template name="merge-params">
<xsl:param name="string"/>
<xsl:param name="i" select="0"/>
<xsl:choose>
<xsl:when test="contains($string, '{') and contains(substring-after($string, '{'), '}')">
<xsl:value-of select="substring-before($string, '{')" />
<xsl:value-of select="key('text-param', $i)/ns1:value" />
<!-- recursive call -->
<xsl:call-template name="merge-params">
<xsl:with-param name="string" select="substring-after($string, '}')" />
<xsl:with-param name="i" select="$i + 1" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$string" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>

How to show a character n times in XSLT?

I have a template with a parameter. How can I insert a tab character n times?
n is the value of the parameter.
In XSLT 2.0:
<xsl:for-each select="1 to $count"> </xsl:for-each>
(Sadly though, I suspect that if you were using XSLT 2.0 you wouldn't need to ask the question).
Another technique often used with XSLT 1.0 is the hack:
<xsl:for-each select="//*[position() <= $count]"> </xsl:for-each>
which works provided the number of elements in your source document is greater than the number of tab characters you want to output.
Just call it recursively; output a tab, then call the same template again with n-1 passed in, if n > 1.
<xsl:template name="repeat">
<xsl:param name="output" />
<xsl:param name="count" />
<xsl:if test="$count > 0">
<xsl:value-of select="$output" />
<xsl:call-template name="repeat">
<xsl:with-param name="output" select="$output" />
<xsl:with-param name="count" select="$count - 1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
As has been pointed out, this example will actually output a minimum of one. In my experience where the output is whitespace, it's usually needed. You can adapt the principle of a recursive template like this any way you see fit.
This seems the simplest and most flexible to me.
For XSLT 1.0 (or perhaps 1.1).
<xsl:variable name="count">10</xsl:variable>
<xsl:variable name="repeat"><xsl:text> </xsl:text></xsl:variable>
<xsl:sequence select="string-join((for $i in 1 to $count return $repeat),'')"/>
Of course the count variable is where you assign your n parameter.
I used the variable repeat to hold the tab character, but you could just replace the $repeat with the tab character in single quotes in the sequence element. Note: This variable can be of a length greater than 1, which creates a whole bunch of possibilities.
It does not use recursion, so it won't run into a recursion limit.
I don't know the maximum value you can use for count, but I tested it up to 10,000.
Globally define a long enough array of tabs:
<xsl:variable name="TABS" select="' '" />
Then use like this:
<xsl:value-of select="fn:substring($TABS, 1, fn:number($COUNT))" />
(XSLT 1.0)
<xsl:template name="tabs">
<xsl:param name="n"/>
<xsl:if test="$n > 0"> <!-- When n = 0, output nothing. -->
<xsl:call-template name="tabs"> <!-- Recursive call: call same template... -->
<xsl:with-param name="n" select="$n - 1"/> <!-- ... for writing n - 1 tabs. -->
</xsl:call-template>
<xsl:text> </xsl:text> <!-- Add one tab character. -->
</xsl:if>
</xsl:template>
Example usage:
<xsl:call-template name="tabs">
<xsl:with-param name="n" select="3"/>
</xsl:call-template>
I've discovered an LGPL-licensed library for doing this called functx, as I was sure someone had to have already done this... This is a "standard library" type XSLT library, which contains a function called repeat-string. From the docs:
The functx:repeat-string function returns a string consisting of a given number of copies of $stringToRepeat concatenated together.
Where I use it like this in my code:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:functx="http://www.functx.com">
<xsl:import href="../buildlib/functx-1.0.xsl"/>
<xsl:output omit-xml-declaration="yes" />
<xsl:variable name="INDENT" select="' '" />
....
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="data-pusher-properties">
<xsl:for-each select="property">
<xsl:choose>
...
<xsl:when test="boolean(#value = '${pusher.notifications.server}')">
<xsl:value-of select="functx:repeat-string($INDENT, #indent)" />
<xsl:text>"</xsl:text>
<xsl:value-of select="#name" />
<xsl:text>": </xsl:text>
<xsl:text>"</xsl:text>
<xsl:value-of select="$pusher.notifications.email.server" />
<xsl:text>"\
</xsl:text>
</xsl:when>
...
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
So for printing a tab character n times, call it like this:
<xsl:value-of select="functx:repeat-string(' ', n)" />
I know this question is old, but I hope this can still help someone.
Documentation for the repeat-string function

XSL media:content - Trimming strings in XSLT

Can anyone tell me how to select the URL from this:
<media:content url="http://feedproxy.google.com/~r/TEDTalks_video/~5/aZWq6PY05YE/TimBrown_2009G.mp4" fileSize="57985745" type="video/mp4" />
I want to:
Create a link to this file:
Trim the URL from:
http://feedproxy.google.com/~r/TEDTalks_video/~5/aZWq6PY05YE/TimBrown_2009G.mp4
to:
TimBrown_2009G
and then take: TimBrown_2009G and use it as part of a URL
Selecting the URL. You just need to make sure that you have the correct namespace URI.
<xsl:value-of xmlns:media="http://search.yahoo.com/mrss/"
select="media:content/#url"/>
Trimming down the URL. How to do this best depends on whether you are using XSLT 1 or 2, since the latter has better string handling functions from XPath 2.0.
If you are using XSLT 1, you might want to create a helper template to return the last segment out of a delimited string:
<xsl:template name="last-substring-after">
<xsl:param name="string"/>
<xsl:param name="separator"/>
<xsl:choose>
<xsl:when test="contains($string, $separator)">
<xsl:call-template name="last-substring-after">
<xsl:with-param name="string"
select="substring-after($string, $separator)"/>
<xsl:with-param name="separator"
select="$separator"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$string"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Then you can make use of that to extract the last segment of the URL, and proceed to extract the part before the dot. Assuming the URL is in variable url:
<xsl:variable name="name">
<xsl:call-template name="last-substring-after">
<xsl:with-param name="string" select="$url"/>
<xsl:with-param name="separator" select="'/'"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="substring-before($name, '.')"/>

XSLT "contains" function to check for period

Ok, I'm stumped. I would like test if a parameter sent to an XSLT template contains a period and to print out quotes if it does not. The parameter I would like to test is "value" in the template below. It seems the contains function should work, but for some reason the quotes always get outputted regardless the contents of "value". What am I doing wrong? Thanks
<!-- Add a JSON property -->
<xsl:template name="addProperty">
<xsl:param name="name" />
<xsl:param name="value" />
<xsl:value-of select="$name" />
<xsl:text>:</xsl:text>
<xsl:if test="not(contains($value,'.'))">'</xsl:if>
<xsl:value-of select="$value" />
<xsl:if test="not(contains($value,'.'))">'</xsl:if>
<xsl:text>,</xsl:text>
</xsl:template>
When I call your template, it works fine. How are you calling it? This is what I used:
<xsl:call-template name="addProperty">
<xsl:with-param name="name" select="'abc'"/>
<xsl:with-param name="value" select="'123'"/><!-- quoted number -->
</xsl:call-template>
<xsl:call-template name="addProperty">
<xsl:with-param name="name" select="'abc'"/>
<xsl:with-param name="value" select="123"/><!-- NOT quoted number -->
</xsl:call-template>
<xsl:call-template name="addProperty">
<xsl:with-param name="name" select="'xyz'"/>
<xsl:with-param name="value" select="'456.789'"/><!-- quoted number -->
</xsl:call-template>
<xsl:call-template name="addProperty">
<xsl:with-param name="name" select="'xyz'"/>
<xsl:with-param name="value" select="456.789"/><!-- NOT quoted number -->
</xsl:call-template>
And this is what I got as output:
abc:'123',abc:'123',xyz:456.789,xyz:456.789,
Could you not be passing in the values to the named template that you think you are passing in? What XSLT engine are you using?
A good way to test this is to add something like this to your named template and see what it produces, if you don't have a good debugger handy:
XXX<xsl:value-of select="$value"/>XXX
YYY<xsl:value-of select="contains($value, '.')"/>YYY
ZZZ<xsl:value-of select="not(contains($value, '.'))"/>ZZZ