Compare date-periods using XSLT - xslt

I have some experience with XSLT but now i've got myself a problem:
I need to check if a period between a begin- and enddate completely covers an other period.
Here's a part of the xml:
<Parent ID="1">
<StartDate>20050101</StartDate>
<EndDate>20060131</EndDate>
<Child ID="1">
<StartDate>20050101</StartDate>
<EndDate>20081231</EndDate>
</Child>
</Parent>
<Parent ID="2">
<StartDate>20060201</StartDate>
<EndDate>20071231</EndDate>
<Child ID="1">
<StartDate>20050101</StartDate>
<EndDate>20081231</EndDate>
</Child>
</Parent>
<Parent ID="3">
<StartDate>20080101</StartDate>
<EndDate>20081231<EndDate>
<Child ID="1">
<StartDate>20050101</StartDate>
<EndDate>20081231</EndDate>
</Child>
</Parent>
So i need to check if the period between start and end of the Parent is fully covered by the period between start and end of the Child in XSLT and write the Parent and Child ID's to xml for fails.
Can someone give me a head start how to manage this in XSLT...?
I have full control over the structure of the XML so when it's easier with an other XML structure (with the same data) i can change it.
Thanks a lot!

Here is an example to do it directly in xslt 2.0 and should work with most date delimeters:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
exclude-result-prefixes="functx xs"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:functx="http://www.functx.com"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:template match="/">
<dates>
<dateBetween>
<xsl:choose>
<xsl:when test="functx:dateBetween('02-01-2009','01-01-2009','03-01-2009')=true()">
<xsl:text>date lays between given dates</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>date is not between given dates</xsl:text>
</xsl:otherwise>
</xsl:choose>
</dateBetween>
<currentDateBetween>
<xsl:choose>
<xsl:when test="functx:currentDateBetween('01-01-2000','01-01-2019')=true()">
<xsl:text>current date lays between given dates</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>current date is not between given dates</xsl:text>
</xsl:otherwise>
</xsl:choose>
</currentDateBetween>
</dates>
</xsl:template>
<!--
Function: dateBetween
Description: This function will check if a dates is between given dates
Input: Input date, Input start date, Input end date
Output: Boolean true if beween param 2 and 3
Created: 21-09-2012 by Raymond Meester
-->
<xsl:function name="functx:dateBetween">
<xsl:param name="param1"/>
<xsl:param name="param2"/>
<xsl:param name="param3"/>
<xsl:variable name ="dateToCheck" select="functx:mmddyyyy-to-date($param1)"/>
<xsl:variable name ="startDate" select="functx:mmddyyyy-to-date($param2)"/>
<xsl:variable name ="endDate" select="functx:mmddyyyy-to-date($param3)"/>
<xsl:variable name ="true" as="xs:boolean" select="true()"/>
<xsl:variable name ="false" as="xs:boolean" select="false()"/>
<xsl:choose>
<xsl:when test="$startDate < $dateToCheck and $dateToCheck < $endDate"><xsl:value-of select="$true"/></xsl:when>
<xsl:otherwise><xsl:value-of select="$false"/></xsl:otherwise>
</xsl:choose>
</xsl:function>
<!--
Function: currentDateBetween
Description: This function will check if a dates is between given dates
Input: Input date, Input start date, Input end date
Output: Boolean true if beween param 2 and 3
Created: 21-09-2012 by Raymond Meester
-->
<xsl:function name="functx:currentDateBetween">
<xsl:param name="param1"/>
<xsl:param name="param2"/>
<xsl:variable name ="startDate" select="functx:mmddyyyy-to-date($param1)"/>
<xsl:variable name ="endDate" select="functx:mmddyyyy-to-date($param2)"/>
<xsl:variable name ="true" as="xs:boolean" select="true()"/>
<xsl:variable name ="false" as="xs:boolean" select="false()"/>
<xsl:choose>
<xsl:when test="$startDate < current-date() and current-date() < $endDate"><xsl:value-of select="$true"/></xsl:when>
<xsl:otherwise><xsl:value-of select="$false"/></xsl:otherwise>
</xsl:choose>
</xsl:function>
<!--
Function: mmddyyyy-to-date
Description: The functx:mmddyyyy-to-date function converts $dateString into a valid xs:date value. The order of the digits in $dateString must be MMDDYYYY, but it can contain any (or no) delimiters between the digits.
Input: Input string
Output: Return date
Created: 2007-02-26 http://www.xsltfunctions.com/xsl/functx_mmddyyyy-to-date.html
-->
<xsl:function name="functx:mmddyyyy-to-date" as="xs:date?"
xmlns:functx="http://www.functx.com">
<xsl:param name="dateString" as="xs:string?"/>
<xsl:sequence select="if (empty($dateString)) then () else if (not(matches($dateString,'^\D*(\d{2})\D*(\d{2})\D*(\d{4})\D*$'))) then error(xs:QName('functx:Invalid_Date_Format')) else xs:date(replace($dateString,'^\D*(\d{2})\D*(\d{2})\D*(\d{4})\D*$','$3-$1-$2'))"/>
</xsl:function>
</xsl:stylesheet>

Using simple string comparison this is easy, because your date format is big-endian. Here's a quick XSLT document I wrote up to test it out:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" indent="yes"/>
<xsl:template match="/">
<root>
<xsl:for-each select="//Parent">
<parent>
<xsl:attribute name="id">
<xsl:value-of select="#ID"/>
</xsl:attribute>
<xsl:choose>
<xsl:when test="(Child/StartDate <= StartDate) and
(Child/EndDate >= EndDate)">
<xsl:text>OK</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>Not OK</xsl:text>
</xsl:otherwise>
</xsl:choose>
</parent>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
Obviously you'll need your own checks to make sure that StartDate is before EndDate for both parent and child.

What's wrong with number(Child/StartDate) <= number(Parent/StartDate) and number(Child/EndDate) >= number(Parent/EndDate)?

This is not going to a complete solution, but you may want to check out the EXSLT extensions for date manipulation, here.
I would however consider creating a couple of XSLT extension functions, using Joda time's Interval abstractions. Probably way easier and faster than trying to do it from XSLT directly.

Related

Retrieving nodes having (or not) a child to apply a conditional template

I read lot of articles but did not find a conclusive help to my problem.
I have an XML document to which I apply an xslt to get a csv file as output.
I send a parameter to my xsl transformation to filter the target nodes to apply the templates.
The xml document looks like that (I removed some unuseful nodes for comprehension):
<GetMOTransactionsResponse xmlns="http://www.exane.com/pott" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.exane.com/pott PoTTMOTransaction.xsd">
<MOTransaction>
<Transaction VersionNumber="2" TradeDate="2013-11-20">
<TransactionId Type="Risque">32164597</TransactionId>
<InternalTransaction Type="Switch">
<BookCounterparty>
<Id Type="Risque">94</Id>
</BookCounterparty>
</InternalTransaction>
<SalesPerson>
<Id Type="Risque">-1</Id>
</SalesPerson>
</Transaction>
<GrossPrice>58.92</GrossPrice>
<MOAccount Account="TO1E" />
<Entity>0021</Entity>
</MOTransaction>
<MOTransaction>
<Transaction VersionNumber="1" TradeDate="2013-11-20">
<TransactionId Type="Risque">32164598</TransactionId>
<SalesPerson>
<Id Type="Risque">-1</Id>
</SalesPerson>
</Transaction>
<GrossPrice>58.92</GrossPrice>
<MOAccount Account="TO3E" />
<Entity>0021</Entity>
</MOTransaction>
</GetMOTransactionsResponse>
My xslt is below (sorry it's quite long, and I write it more simple than it really is):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:pott="http://www.exane.com/pott">
<xsl:output method="text" omit-xml-declaration="no" indent="no" />
<xsl:param name="instrumentalSystem"></xsl:param>
<xsl:template name="abs">
<xsl:param name="n" />
<xsl:choose>
<xsl:when test="$n = 0">
<xsl:text>0</xsl:text>
</xsl:when>
<xsl:when test="$n > 0">
<xsl:value-of select="format-number($n, '#')" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="format-number(0 - $n, '#')" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="outputFormat">
<!--Declaration of variables-->
<xsl:variable name="GrossPrice" select="pott:GrossPrice" />
<xsl:variable name="TransactionId" select="pott:Transaction/pott:TransactionId[#Type='Risque']" />
<xsl:variable name="VersionNumber" select="pott:Transaction/#VersionNumber" />
<!--Set tags values-->
<xsl:value-of select="$Entity" />
<xsl:text>;</xsl:text>
<xsl:value-of select="concat('0000000', pott:MOAccount/#Account) "/>
<xsl:text>;</xsl:text>
<xsl:text>;</xsl:text>
<xsl:value-of select="$TransactionId" />
<xsl:text>;</xsl:text>
<xsl:value-of select="$VersionNumber" />
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="/">
<xsl:choose>
<!-- BB -->
<xsl:when test="$instrumentalSystem = 'BB'">
<!--xsl:for-each select="pott:GetMOTransactionsResponse/pott:MOTransaction/pott:Transaction[pott:InternalTransaction]"-->
<xsl:for-each select="pott:GetMOTransactionsResponse/pott:MOTransaction/pott:Transaction[pott:InternalTransaction]">
<xsl:call-template name="outputFormat"></xsl:call-template>
</xsl:for-each>
</xsl:when>
<!-- CP -->
<xsl:when test="$instrumentalSystem = 'CP'">
<xsl:for-each select="pott:GetMOTransactionsResponse/pott:MOTransaction/pott:Transaction[not(pott:InternalTransaction)]">
<xsl:call-template name="outputFormat"></xsl:call-template>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
If parameter = BB, I want to select MOTransaction nodes that have a child Transaction that contains a InternalTransaction node.
If parameter = CP, I want to select MOTransaction nodes that don't have a child Transaction that contains a InternalTransaction node
When I write
pott:GetMOTransactionsResponse/pott:MOTransaction/pott:Transaction[pott:InternalTransaction], I get the Transaction nodes and not the MOTransaction nodes
I think I am not very far from the expected result, but despite all my attempts, I fail.
If anyone can help me.
I hope being clear, otherwise I can give more information.
Looking at one of xsl:for-each statements, you are doing this
<xsl:for-each select="pott:GetMOTransactionsResponse/pott:MOTransaction/pott:Transaction[pott:InternalTransaction]">
You say you want to select MOTransaction elements, but it is actually selecting the child Transaction elements. To match the logic you require, it should be this
<xsl:for-each select="pott:GetMOTransactionsResponse/pott:MOTransaction[pott:Transaction[pott:InternalTransaction]]">
In fact, this should also work
<xsl:for-each select="pott:GetMOTransactionsResponse/pott:MOTransaction[pott:Transaction/pott:InternalTransaction]">
Similarly, for the second statement (in the case of the parameter being "CP"), it could look like this
<xsl:for-each select="pott:GetMOTransactionsResponse/pott:MOTransaction[pott:Transaction[not(pott:InternalTransaction)]]">
Alternatively, it could look like this
<xsl:for-each select="pott:GetMOTransactionsResponse/pott:MOTransaction[not(pott:Transaction/pott:InternalTransaction)]">
They are not quite the same though, as the first will only include MOTransaction elements that have Transaction child elements, whereas the second will include MOTransaction that don't have any Transaction childs at all.
As a slight aside, you don't really need to use an xsl:for-each and xsl:call-template here. It might be better to use template matching.
Firstly, try changing the named template <xsl:template name="outputFormat"> to this
<xsl:template match="pott:MOTransaction">
Then, you can re-write you merge the xsl:for-each and xsl:call-template into a single xsl:apply-templates call.
<xsl:apply-template select="pott:GetMOTransactionsResponse/pott:MOTransaction[pott:Transaction/pott:InternalTransaction]" />

check for successively numbered attributes

I have a situation where I need to check for attribute values that may be successively numbered and input a dash between the start and end values.
<root>
<ref id="value00008 value00009 value00010 value00011 value00020"/>
</root>
The ideal output would be...
8-11, 20
I can tokenize the attribute into separate values, but I'm unsure how to check if the number at the end of "valueXXXXX" is successive to the previous value.
I'm using XSLT 2.0
You can use xsl:for-each-group with #group-adjacent testing for the number() value subtracting the position().
This trick was apparently invented by David Carlisle, according to Michael Kay.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output indent="yes"/>
<xsl:template match="/">
<xsl:variable name="vals"
select="tokenize(root/ref/#id, '\s?value0*')[normalize-space()]"/>
<xsl:variable name="condensed-values" as="item()*">
<xsl:for-each-group select="$vals"
group-adjacent="number(.) - position()">
<xsl:choose>
<xsl:when test="count(current-group()) > 1">
<!--a sequence of successive numbers,
grab the first and last one and join with '-' -->
<xsl:sequence select="
string-join(current-group()[position()=1
or position()=last()]
,'-')"/>
</xsl:when>
<xsl:otherwise>
<!--single value group-->
<xsl:sequence select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:variable>
<xsl:value-of select="string-join($condensed-values, ',')"/>
</xsl:template>
</xsl:stylesheet>

XPath/XSLT nested predicates: how to get the context of outer predicate?

It seems that this question was not discussed on stackoverflow before, save for Working With Nested XPath Predicates ... Refined where the solution not involving nested predicates was offered.
So I tried to write the oversimplified sample of what I'd like to get:
Input:
<root>
<shortOfSupply>
<food animal="doggie"/>
<food animal="horse"/>
</shortOfSupply>
<animalsDictionary>
<cage name="A" animal="kittie"/>
<cage name="B" animal="dog"/>
<cage name="C" animal="cow"/>
<cage name="D" animal="zebra"/>
</animals>
</root>
Output:
<root>
<hungryAnimals>
<cage name="B"/>
<cage name="D"/>
</hungryAnimals>
</root>
or, alternatively, if there is no intersections,
<root>
<everythingIsFine/>
</root>
And i want to get it using a nested predicates:
<xsl:template match="cage">
<cage>
<xsl:attribute name="name">
<xsl:value-of select="#name"/>
</xsl:attribute>
</cage>
</xsl:template>
<xsl:template match="/root/animalsDictionary">
<xsl:choose>
<!-- in <food> in <cage> -->
<xsl:when test="cage[/root/shortOfSupply/food[ext:isEqualAnimals(./#animal, ?????/#animal)]]">
<hungryAnimals>
<xsl:apply-templates select="cage[/root/shortOfSupply/food[ext:isEqualAnimals(#animal, ?????/#animal)]]"/>
</hungryAnimals>
</xsl:when>
<xsl:otherwise>
<everythingIsFine/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
So what should i write in place of that ??????
I know i could rewrite the entire stylesheet using one more template and extensive usage of variables/params, but it makes even this stylesheet significantly more complex, let alone the real stylesheet i have for real problem.
It is written in XPath reference that the dot . sign means the current context node, but it doesn't tell whether there is any possibility to get the node of context before that; and i just can't believe XPath is missing this obvious feature.
XPath 2.0 one-liner:
for $a in /*/animalsDictionary/cage
return
if(/*/shortOfSupply/*[my:isA($a/#animal, #animal)])
then $a
else ()
When applied on the provided XML document selects:
<cage name="B"/>
<cage name="D"/>
One cannot use a single XPath 1.0 expression to find that a given cage contains a hungry animal.
Here is an XSLT solution (XSLT 2.0 is used only to avoid using an extension function for the comparison -- in an XSLT 1.0 solution one will use an extension function for the comparison and the xxx:node-set() extension to test if the RTF produced by applying templates in the body of the variable contains any child element):
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:my="my:my" exclude-result-prefixes="xs my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<my:Dict>
<a genName="doggie">
<name>dog</name>
<name>bulldog</name>
<name>puppy</name>
</a>
<a genName="horse">
<name>horse</name>
<name>zebra</name>
<name>pony</name>
</a>
<a genName="cat">
<name>kittie</name>
<name>kitten</name>
</a>
</my:Dict>
<xsl:variable name="vDict" select=
"document('')/*/my:Dict/a"/>
<xsl:template match="/">
<root>
<xsl:variable name="vhungryCages">
<xsl:apply-templates select=
"/*/animalsDictionary/cage"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="$vhungryCages/*">
<hungryAnimals>
<xsl:copy-of select="$vhungryCages"/>
</hungryAnimals>
</xsl:when>
<xsl:otherwise>
<everythingIsFine/>
</xsl:otherwise>
</xsl:choose>
</root>
</xsl:template>
<xsl:template match="cage">
<xsl:if test="
/*/shortOfSupply/*[my:isA(current()/#animal,#animal)]">
<cage name="{#name}"/>
</xsl:if>
</xsl:template>
<xsl:function name="my:isA" as="xs:boolean">
<xsl:param name="pSpecName" as="xs:string"/>
<xsl:param name="pGenName" as="xs:string"/>
<xsl:sequence select=
"$pSpecName = $vDict[#genName = $pGenName]/name"/>
</xsl:function>
</xsl:stylesheet>
When this transformation is applied on the provided XML document (corrected to be well-formed):
<root>
<shortOfSupply>
<food animal="doggie"/>
<food animal="horse"/>
</shortOfSupply>
<animalsDictionary>
<cage name="A" animal="kittie"/>
<cage name="B" animal="dogs"/>
<cage name="C" animal="cow"/>
<cage name="D" animal="zebras"/>
</animalsDictionary>
</root>
the wanted, correct result is produced:
<root>
<hungryAnimals>
<cage name="B"/>
<cage name="D"/>
</hungryAnimals>
</root>
Explanation: Do note the use of the XSLT current() function.
XPath 1.0 is not "relationally complete" - it can't do arbitrary joins. If you're in XSLT, you can always get round the limitations by binding variables to intermediate nodesets, or (sometimes) by using the current() function.
XPath 2.0 introduces range variables, which makes it relationally complete, so this limitation has gone.
Doesn't <xsl:when test="cage[#animal = /root/shortOfSupply/food/#animal]"> suffice to express your test condition?
Notice The dot operator in XPath is related to the current context. In XSLT the current template context_ is given by the function current(), which most of the time (not always) coincides with the ..
You can perform the test (and the apply templates as well), using the parent axis abbreviation (../):
cage[#animal=../../shortOfSupply/food/#animal]
Moreover the match pattern in the the first template is wrong, it should be relative to the root:
/root/animalsDictionary
#Martin suggestion is also obviously correct.
Your final template slightly modified:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="root/animalsDictionary">
<xsl:choose>
<xsl:when test="cage[#animal=../../shortOfSupply/food/#animal]">
<hungryAnimals>
<xsl:apply-templates select="cage[#animal
=../../shortOfSupply/food/#animal]"/>
</hungryAnimals>
</xsl:when>
<xsl:otherwise>
<everythingIsFine/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="cage">
<cage name="{#name}"/>
</xsl:template>
</xsl:stylesheet>

replacing text in xml using xslt

I have an XML file which has some values in child Element aswell in attributes.
If i want to replace some text when specific value is matched how can i achieve it?
I tried using xlst:translate() function. But i cant use this function for each element or attribute in xml.
So is there anyway to replace/translate value at one shot?
<?xml version="1.0" encoding="UTF-8"?>
<Employee>
<Name>Emp1</Name>
<Age>40</Age>
<sex>M</sex>
<Address>Canada</Address>
<PersonalInformation>
<Country>Canada</country>
<Street1>KO 92</Street1>
</PersonalInformation>
</Employee>
Output :
<?xml version="1.0" encoding="UTF-8"?>
<Employee>
<Name>Emp1</Name>
<Age>40</Age>
<sex>M</sex>
<Address>UnitedStates</Address>
<PersonalInformation>
<Country>UnitedStates</country>
<Street1>KO 92</Street1>
</PersonalInformation>
</Employee>
in the output, replaced text from Canada to UnitedStates.
so, without using xslt:transform() functions on any element , i should be able to replace text Canada to UnitedStates irrespective of level nodes.
Where ever i find 'Canada' i should be able to replace to 'UnitedStates' in entire xml.
So how can i achieve this.?
I. XSLT 1.0 solution:
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my" >
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<my:Reps>
<rep>
<old>replace this</old>
<new>replaced</new>
</rep>
<rep>
<old>cat</old>
<new>tiger</new>
</rep>
</my:Reps>
<xsl:variable name="vReps" select=
"document('')/*/my:Reps/*"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*">
<xsl:attribute name="{name()}">
<xsl:call-template name="replace">
<xsl:with-param name="pText" select="."/>
</xsl:call-template>
</xsl:attribute>
</xsl:template>
<xsl:template match="text()" name="replace">
<xsl:param name="pText" select="."/>
<xsl:if test="string-length($pText)">
<xsl:choose>
<xsl:when test=
"not($vReps/old[contains($pText, .)])">
<xsl:copy-of select="$pText"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="vthisRep" select=
"$vReps/old[contains($pText, .)][1]
"/>
<xsl:variable name="vNewText">
<xsl:value-of
select="substring-before($pText, $vthisRep)"/>
<xsl:value-of select="$vthisRep/../new"/>
<xsl:value-of select=
"substring-after($pText, $vthisRep)"/>
</xsl:variable>
<xsl:call-template name="replace">
<xsl:with-param name="pText"
select="$vNewText"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document:
<t>
<a attr1="X replace this Y">
<b>cat mouse replace this cat dog</b>
</a>
<c/>
</t>
produces the wanted, correct result:
<t>
<a attr1="X replaced Y">
<b>tiger mouse replaced tiger dog</b>
</a>
<c/>
</t>
Explanation:
The identity rule is used to copy "as-is" some nodes.
We perform multiple replacements, parameterized in my:Reps
If a text node or an attribute doesn't contain any rep-target, it is copied as-is.
If a text node or an attribute contains text to be replaced (rep target), then the replacements are done in the order specified in my:Reps
If the string contains more than one string target, then all targets are replaced: first all occurences of the first rep target, then all occurences of the second rep target, ..., last all occurences of the last rep target.
II. XSLT 2.0 solution:
In XSLT 2.0 one can simply use the standard XPath 2.0 function replace(). However, for multiple replacements the solution would be still very similar to the XSLT 1.0 solution specified above.

what is correct way to test for xs:decimal in XSL?

I'm trying to display different information depending on incoming data. If it's an integer, I want to display just the number, if it's a decimal, I want to use 0.00# pattern. Ya, I know, a bit mixed up, but that's the development spec. :>
I have the following XSL for this specific section but I can't see to get past the xsl:when error message of
"Expected end of expression, found
'castable'. number(SAVG) -->castable
<-- as xs:decimal"
<xsl:choose>
<xsl:when test="number(SAVG) > 0">
<xsl:choose>
<xsl:when test="number(SAVG) castable as xs:decimal">
<xsl:value-of select="format-number(SAVG, '###,###,##0.00#')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="format-number(SAVG, '###,###,##0.###')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:when test="number(SAVG) = 0">
<xsl:text disable-output-escaping="yes">&lt;</xsl:text>1
</xsl:when>
<xsl:otherwise>N/A</xsl:otherwise>
</xsl:choose>
I tried looking/poking around for answers and I have tried "instance of", I've tried using xsl:if, etc but I can't seem to get this to work. Any help would be greatly appreciated.
Thanks.
From comments:
Yes, we are using 1.0. I'm sorry I'm
new to the XSL processing, how do I
glue your XSL and input to generate
the html?
I. XSLT 1.0:
There are no xs:integer and xs:decimal in the XPath 1.0 data model used by XSLT 1.0.
Here is a code snippet that you may use:
<xsl:choose>
<xsl:when test="not(floor(SAVG) = SAVG)">
<xsl:value-of select="format-number(SAVG, '###,###,##0.00#')"/>
</xsl:when>
<xsl:otherwise> <!-- Integer value -->
<xsl:value-of select="SAVG"/>
</xsl:otherwise>
</xsl:choose>
Do note: To test if a numeric value is an integer, we use the following test:
floor($someNum) = $someNum
Here is one way to do this:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:sequence select=
"for $num in (3, 3.14)
return
if($num instance of xs:integer)
then ($num, ' is xs:integer', '
')
else if($num instance of xs:decimal)
then ($num, ' is xs:decimal', '
')
else ($num, ' is something else', '
')
"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on any XML document (not used), the wanted, correct result is produced:
3 is xs:integer
3.14 is xs:decimal
Or, using the format-number() function as per your example:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:sequence select=
"for $num in (3, 3.14)
return
if($num instance of xs:integer)
then (format-number($num, '###,###,##0.###'), '
')
else if($num instance of xs:decimal)
then (format-number($num, '###,###,##0.00#'), '
')
else ()
"/>
</xsl:template>
</xsl:stylesheet>
produces:
3
3.14
This XSLT 1.0 stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="test">
<xsl:choose>
<!-- Not number or number less than zero -->
<xsl:when test="0 > SVGA or number(SVGA) != SVGA">
<xsl:text>N/A</xsl:text>
</xsl:when>
<!-- Number zero -->
<xsl:when test="SVGA = 0">
<xsl:text><1</xsl:text>
</xsl:when>
<!-- Integer number -->
<xsl:when test="floor(SVGA) = SVGA">
<xsl:value-of select="format-number(SVGA,'###,###,##0.###')"/>
</xsl:when>
<!-- Double number -->
<xsl:otherwise>
<xsl:value-of select="format-number(SVGA,'###,###,##0.00#')"/>
</xsl:otherwise>
</xsl:choose>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
With this input:
<root>
<test>
<SVGA>0</SVGA>
</test>
<test>
<SVGA>12131</SVGA>
</test>
<test>
<SVGA>123.5654</SVGA>
</test>
<test>
<SVGA>-12.1</SVGA>
</test>
<test>
<SVGA>-7528</SVGA>
</test>
<test>
<SVGA>zero</SVGA>
</test>
</root>
Output:
<1
12,131
123.565
N/A
N/A
N/A
Edit 3: Better test order (plus Dimitre's expression), better test case, closer input sample to questioner.