XSLT call template on a string with delimiter - xslt

My input xml is this
<StepSchedule>
<StepRateEffectiveDate>20191204</StepRateEffectiveDate>
<StepRate>3.400000</StepRate>
</StepSchedule>
<StepSchedule>
<StepRateEffectiveDate>20200304</StepRateEffectiveDate>
<StepRate>2.250000</StepRate>
</StepSchedule>
<StepSchedule>
<StepRateEffectiveDate>20200604</StepRateEffectiveDate>
<StepRate>2.000000</StepRate>
</StepSchedule>
<StepSchedule>
<StepRateEffectiveDate>20201204</StepRateEffectiveDate>
<StepRate>1.510000</StepRate>
</StepSchedule>
My xslt where I need to reformat the dates to a different format
<xsl:call-template name="date-formatter">
<xsl:with-param name="date" select="./*:StepSchedule/*:StepRateEffectiveDate" />
</xsl:call-template>
<xsl:template name="date-formatter">
<xsl:param name="date" />
<xsl:variable name="year" select="substring($date, 1, 4)" />
<xsl:variable name="month" select="substring($date, 5, 2)" />
<xsl:variable name="day" select="substring($date, 7, 2)" />
<xsl:value-of select="concat($year,'-',$month,'-',$day,'T00:00:00')" />
</xsl:template>
When I run the transformation I get the following error
Transform terminated: 'Type check error. In call to built-in function 'substring', expected type '{http://www.w3.org/2001/XMLSchema}string?' for argument 1 ('$sourceString' in namespace '') but found more than one item.
When the input only contains one StepSchedule node it works fine, the problem is when there is more than one.

The context is not really clear, but if you replace
<xsl:call-template name="date-formatter">
<xsl:with-param name="date" select="./*:StepSchedule/*:StepRateEffectiveDate" />
</xsl:call-template>
by
<xsl:template match="/">
<xsl:for-each select="//StepRateEffectiveDate">
<xsl:call-template name="date-formatter">
<xsl:with-param name="date" select="."/>
</xsl:call-template>
</xsl:for-each>
</xsl:template>
it seems to work. I'm not sure what your XPath is trying to select.
See it working here: https://xsltfiddle.liberty-development.net/bET2rWP

Related

How to trim result string value?

<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.

Increase y value of SVG drawing using XSLT iteration

I asked a similar question earlier. I have made some changes to the code, but am still stuck.
I have part of an XSLT like this:
<xsl:variable name="y" select="0" />
<xsl:if test="units_display='true'">
<xsl:call-template name="DisplayBox">
<xsl:with-param name="current_y" select="$y" />
<xsl:with-param name="value" select="units" />
<xsl:with-param name="text" select="'Units'" />
</xsl:call-template>
</xsl:if>
<xsl:if test="sensor_display='true'">
<xsl:call-template name="DisplayBox">
<xsl:with-param name="current_y" select="$y" />
<xsl:with-param name="value" select="sensor" />
<xsl:with-param name="text" select="'Type'" />
</xsl:call-template>
</xsl:if>
<xsl:if test="offset_display='true'">
<xsl:call-template name="DisplayBox">
<xsl:with-param name="current_y" select="$y" />
<xsl:with-param name="value" select="offset" />
<xsl:with-param name="text" select="'Offset'" />
</xsl:call-template>
</xsl:if>
My call-template is like this:
<xsl:template name="DisplayBox">
<xsl:param name="current_y" />
<xsl:param name="value" />
<xsl:param name="text" />
<rect x="20" y="{150 + $current_y}" width="220" height="20" fill="#FFFFFF" stroke="black" stroke-width="1" />
<text x="25" y="{168 + $current_y}" font-family="arial" font-size="20px" fill="black">
<xsl:value-of select="$value"/>
</text>
<line x1="90" y1="{150 + $current_y}" x2="90" y2="{170 + $current_y}" stroke="black" stroke-width="1" />
<text x="95" y="{168 + $current_y}" font-family="arial" font-size="20px" fill="black"><xsl:value-of select="$text" /></text>
</xsl:template>
I can't work out how to increase the value of current_y depending on whether the if statements are true or not. E,g, if a statement is true, the y value needs to be increased by 20, but not if the statement is false.
So the output could be empty if all 3 statements are false or could be any of these permutations:
Any help would be greatly appreciated.
You have to move the whole thing into a recursion template. You are passing $y for "current_y" which has the value "0" into each call of the template. That recursive template would take the same arguments and two additional:
(1) The test to perform for that round of recursion
(2) Whether you have any additional tests to perform (or exit the recursion)
Then you do a choose and increment the "y" (or not) depending on the test.
* New Info *
After some more thought based on the comments below, what about using something like this:
<xsl:template match="units_display | sensor_display | offset_display">
<xsl:variable name="y-multiplier">
<xsl:value-of select="count(preceding-sibling::units_display[.='true']) + count(preceding-sibling::sensor_display[.='true']) + count(preceding-sibling::offset_display[.='true'])"/>
</xsl:variable>
<xsl:message>
<xsl:value-of select="$y-multiplier"/>
</xsl:message>
</xsl:template>
This counts all of the elements that meet you criteria as you go through the document. This should get you started toward writing the result without recursion.

unique values of multi-token string using xslt

I have xml like
<programs>
<program name="breaking laws" id="97;#ttt;#98;#tpl;#41;#fel" />
<program name="advanced technology" id="89;#hjk;#95;#uio;#81;#lpk" />
<program name="Emerging companies " id="88;#ple;#98;#tpl;#41;#fel" />
<program name="breakinglaws" id="97;#ttt" />
<program name="breakinglaws" id="97;#ttt;#98;#tpl;#81;#lpk" />
<program name="breakinglaws" id="99;#hklo;#95;#uio" />
</programs>
I would like to find all the unique ids text using xslt 1.0 i.e
ttt
tpl
fel
hjk
uio
lpk
ple
hklo
I was trying to do something using key and output tokens
<xsl:template name="output-tokens">
<xsl:param name="list" />
<xsl:variable name="newlist" select="concat(normalize-space($list), ' ')" />
<xsl:variable name="first" select="substring-before($newlist, ';#')" />
<xsl:variable name="remaining" select="substring-after($newlist, ';#')" />
<id>
<xsl:value-of select="$first" />
</id>
<xsl:if test="$remaining">
<xsl:call-template name="output-tokens">
<xsl:with-param name="list" select="$remaining" />
</xsl:call-template>
</xsl:if>
</xsl:template>
Here is one solution
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/programs">
<!-- Variable containing concateneated list of all program elements**
<xsl:variable name="allprograms">
<xsl:apply-templates select="program"/>
</xsl:variable>
<!-- Call recursive template to split the list -->
<xsl:call-template name="output-tokens">
<xsl:with-param name="list" select="$allprograms" />
</xsl:call-template>
</xsl:template>
<!-- Template used to concatenate all program elements -->
<xsl:template match="program">
<xsl:value-of select="concat(#id, ';#') "/>
</xsl:template>
<!-- Recursive template to split out list into unique elements -->
<xsl:template name="output-tokens">
<!-- List to split -->
<xsl:param name="list"/>
<!-- List of all currently processed elements -->
<xsl:param name="newlist" select="';#'" />
<!-- Get first variable in list, and also the remaining part of the list -->
<xsl:variable name="first" select="substring-before($list, ';#')"/>
<xsl:variable name="remaining" select="substring-after($list, ';#')"/>
<!-- Check if first variable is not a number, and is not contained in currently processed list -->
<xsl:if test="number($first) != number($first) and not(contains($newlist, concat(';#', $first, ';#')))">
<id>
<xsl:value-of select="$first"/>
</id>
</xsl:if>
<!-- If there are still elements left in the list, call the template recursively -->
<xsl:if test="$remaining">
<xsl:call-template name="output-tokens">
<xsl:with-param name="list" select="$remaining"/>
<xsl:with-param name="newlist" select="concat($newlist, $first, ';#')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The idea is you first build up a single string containing all the program elements concatenated together. You then call a recursive template which gets the first element from the list, and then checks it has not already been processed (this is achieved by the template also containing a variable of already processed elements)
When the XSLT is applied to your sample XML, the following is output:
<id>ttt</id>
<id>tpl</id>
<id>fel</id>
<id>hjk</id>
<id>uio</id>
<id>lpk</id>
<id>ple</id>
<id>hklo</id>

XSLT: xsl:function won't work

The following works for me:
<xsl:variable name="core" select="document('CoreMain_v1.4.0.xsd')" />
<xsl:variable name="AcRec" select="document('AcademicRecord_v1.3.0.xsd')" />
<xsl:template match="xs:element">
<xsl:variable name="prefix" select="substring-before(#type, ':')" />
<xsl:variable name="name" select="substring-after(#type, ':')" />
<xsl:choose>
<xsl:when test="$prefix = 'AcRec'">
<xsl:apply-templates select="$AcRec//*[#name=$name]">
<xsl:with-param name="prefix" select="$prefix" />
</xsl:apply-templates>
</xsl:when>
<xsl:when test="$prefix = 'core'">
<xsl:apply-templates select="$core//*[#name=$name]">
<xsl:with-param name="prefix" select="$prefix" />
</xsl:apply-templates>
</xsl:when>
</xsl:choose>
</xsl:template>
But I use the same logic to handle the lookup of elements in the current or other documents based on the prefix, matching the node name in numerous places within the stylesheet. So, after changing the stylesheet version to 2.0, I tried:
<xsl:template match="xs:element">
<xsl:value-of select="my:lookup(#type)" />
</xsl:template>
<xsl:function name="my:lookup">
<xsl:param name="attribute" />
<!-- parse the attribute for the prefix & name values -->
<xsl:variable name="prefix" select="substring-before($attribute, ':')" />
<xsl:variable name="name" select="substring-after($attribute, ':')" />
<!-- Switch statement based on the prefix value -->
<xsl:choose>
<xsl:when test="$prefix = 'AcRec'">
<xsl:apply-templates select="$AcRec//*[#name=$name]">
<xsl:with-param name="prefix" select="$prefix" />
</xsl:apply-templates>
</xsl:when>
<xsl:when test="$prefix = 'core'">
<xsl:apply-templates select="$core//*[#name=$name]">
<xsl:with-param name="prefix" select="$prefix" />
</xsl:apply-templates>
</xsl:when>
</xsl:choose>
</xsl:function>
In my reading, I have only found examples of functions that return text - none call templates. I have the impression that an xsl:function should always return text/output...
After more investigation, it is entering the my:lookup function and the variables (prefix & name) are getting populated. So it does enter the xsl:choose statement, and the hits the appropriate when test. The issue appears to be with the apply-templates - value-of is displaying the child values; copy-of does as well, which I think is odd (shouldn't the output include the xml element declarations?). Why would there be a difference if code that works in a template declaration is moved to an xsl:function?
It's been a while since I did any serious XSLT, but IIRC your problem is not in the function, but in your template:
<xsl:template match="xs:element">
<xsl:value-of select="my:lookup(#type)" />
</xsl:template>
The value-of statement won't inline the result tree returned by your function. Instead, it's going to try and reduce that result tree down into some kind of string, and inline that instead. This is why you're seeing the child values and not the elements themselves.
To inline the result tree returned by your function, you'll need to use some templates to copy the result tree into place.
So, your main template will need to change to this:
<xsl:template match="xs:element">
<xsl:apply-templates select="my:lookup(#type)" />
</xsl:template>
and you'll need some templates to do the recursive call. A quick google found a good discussion of the identity template that should do what you need.
(Please forgive any syntax errors, as I said, it's been a while ...)

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