Greatest number from the tags in XML - xslt

I have an xml with different name tags at different places. What I would need to do is, sort the needed one and get the max value of them.
Input XML:
<SUBSCRIBER>
<OnPeakAccountID>10</OnPeakAccountID>
<OnPeakSmsExpDate>**20640217172520**</OnPeakSmsExpDate>
<UnliSmsOnCtl>20140204173322</UnliSmsOnCtl>
<BucketMocOn>840</BucketMocOn>
<BucketMocOnExp>20140204173322</BucketMocOnExp>
<BucketMocTri>10000</BucketMocTri>
<BucketMocTriExp>**20140210235959**</BucketMocTriExp>
<UnliNxbFbcCtl>**20140210235959**</UnliNxbFbcCtl>
<BucketIM6VolFbc>10000</BucketIM6VolFbc>
<BucketIM6VolFbcExp>**20140210235959**</BucketIM6VolFbcExp>
<UnliEmail1FbcCtl>**20140210235959**</UnliEmail1FbcCtl>
<UnliIM2FbcCtl>**20140210235959**</UnliIM2FbcCtl>
<UnliPhoto1FbcCtl>**20140210235959**</UnliPhoto1FbcCtl>
<UnliSns1FbcCtl>**20140210235959**</UnliSns1FbcCtl>
<UnliBoost1FbcCtl>**20140210235959**</UnliBoost1FbcCtl>
<UnliBrws1FbcCtl>**20140210235959**</UnliBrws1FbcCtl>
</SUBSCRIBER>
For example, In the above XML, I need to find the greatest value from the elements marked with **<>**. To be honest, I am still fighting for the logic and nothing is working at my end. Any help/suggestions would be appreciated. Thanks for your support.

I think you want this template:
<xsl:stylesheet version='1.0' xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="SUBSCRIBER">
<xsl:for-each select="OnPeakSmsExpDate|UnliSmsOnCtl|BucketMocOnExp|BucketMocTriExp|UnliNxbFbcCtl|BucketIM6VolFbcExp|UnliEmail1FbcCtl|UnliIM2FbcCtl|UnliPhoto1FbcCtl|UnliSns1FbcCtl|UnliBoost1FbcCtl|UnliBrws1FbcCtl">
<xsl:sort order="descending"/>
<xsl:if test="position() = 1">
<greatest><xsl:value-of select="."/></greatest>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

XSLT 2.0
<xsl:template match="/SUBSCRIBER">
<max><xsl:value-of select="max((BucketMocTriExp, UnliNxbFbcCtl, BucketIM6VolFbcExp, UnliEmail1FbcCtl, UnliIM2FbcCtl, UnliPhoto1FbcCtl, UnliSns1FbcCtl, UnliBoost1FbcCtl, UnliBrws1FbcCtl))"/></max>
</xsl:template>
EDIT:
To include a node conditionally, use the following construct:
<xsl:value-of select="max((a, b, c, if(d > 0) then e else ())) "/>

This XSLT sorts elements with value (containing 2 asterisk at start and at end) in descending order and extrats the first one:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:template match="SUBSCRIBER">
<xsl:for-each select="*[starts-with(.,'**') and substring(.,string-length(.) - 1) = '**']">
<xsl:sort select="number(substring(substring(.,3),1,string-length(substring(.,3)) - 2))" order="descending"/>
<xsl:if test="position() = 1">
<xsl:value-of select="substring(substring(.,3),1,string-length(substring(.,3)) - 2)"/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Related

XSLT Need to Limit Return of Multiple Instances in XML File to 18 Characters

I currently have the following code to combine multiple instances of Ustrd into one returned value:
<Ustrd>
<xsl:value-of select="a:RmtInf/a:Ustrd"/>
</Ustrd>
This returns:
<Ustrd>Item-1 Item-2 Item-3</Ustrd>
The problem is that I need to limit this to 18 characters, and the substring function does not work with a sequence of items.
Tried:
<Ustrd>
<xsl:value-of select="substring(a:RmtInf/a:Ustrd, 1, 18"/>
</Ustrd>
Expected Result:
<Ustrd>Item-1 Item-2 Item</Ustrd>
Use string-join first e.g. substring(string-join(a:RmtInf/a:Ustrd, ' '), 1, 18). In XPath 3.1 you can also write that as a:RmtInf/a:Ustrd => string-join(' ') => substring(1, 18).
Here's a way this could be done in XSLT 1.0.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<Ustrd>
<xsl:variable name="temp">
<xsl:for-each select="RmtInf/Ustrd">
<xsl:value-of select="."/>
<xsl:if test="position()!=last()">
<xsl:value-of select="' '"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="substring($temp,1,18)"/>
</Ustrd>
</xsl:template>
</xsl:stylesheet>
(Only need to add your namespace.)
See it working here: https://xsltfiddle.liberty-development.net/pPgzCL4

XSLT: How to imitate a flag or break statement in xslt:foreach whiling iterating a List of variable [duplicate]

This question already has an answer here:
How to break out of xsl:for-each loop in XSLT?
(1 answer)
Closed 5 years ago.
I'm new to xslt, pardon me for any mistakes.
In the xsl program I've got a list of values with variable named as "foo:vars", which contains a list of color values.
There is a variable declared as matchWith, which can contain any values (not necessarily present in foor:var list)
The Program Should output as:
if the variable matchWith contains a value which is present in the list "foo:vars" then the value should appear in the tag
<color_found> with the matching value.
else, the value present in the variable matchWith should appear in another tag named as <color_not_found>
Below is the program, that able to give the correct output for case 1, but I fail condition any flag for case 2.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:foo="http://foo.com" exclude-result-prefixes="foo">
<xsl:output indent="yes" method="xml" omit-xml-declaration="yes"/>
<foo:vars>
<foo:var name="a1">Yellow</foo:var>
<foo:var name="b1">red</foo:var>
<foo:var name="c1">green</foo:var>
<foo:var name="d1">blue</foo:var>
</foo:vars>
<xsl:variable name="matchWith">Yellow</xsl:variable>
<xsl:template match="/">
<xsl:for-each select="document('')/xsl:stylesheet/foo:vars/foo:var">
<xsl:variable name="temp">
<xsl:value-of select="."/>
</xsl:variable>
<xsl:choose>
<xsl:when test="$temp=$matchWith">
<color_found>
<xsl:value-of select="$matchWith"/>
</color_found>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The problem is that you are using for-each when you should just check directly whether there is a matching value.
Like this:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:foo="http://foo.com" exclude-result-prefixes="foo">
<xsl:output indent="yes" method="xml" omit-xml-declaration="yes"/>
<foo:vars>
<foo:var name="a1">Yellow</foo:var>
<foo:var name="b1">red</foo:var>
<foo:var name="c1">green</foo:var>
<foo:var name="d1">blue</foo:var>
</foo:vars>
<xsl:variable name="matchWith">Yellow</xsl:variable>
<xsl:template match="/">
<xsl:variable name="options"
select="document('')/xsl:stylesheet/foo:vars/foo:var" />
<xsl:variable name="isMatch" select="$matchWith = $options" />
<xsl:element name="color_{ substring('not_', 1, 4 * not($isMatch)) }found">
<xsl:value-of select="$matchWith" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>

Otherwise-statement in XSLT-choose is not applied/working

I have an XML-document with a type-node whose value is either "1" or "2":
<MyDoc>
<foo>
<bar>
<type>2</type>
</bar>
</foo>
</MyDoc>
I want to set a variable typeBool depending on the value of the type-node, if it is "1" it should be set to false, if it's "2" to true.
With the XSLT-choose-Element it should be possible to test for the current value and set typeBool according to the outcome.
I'm trying to do this with the following construct in XSLT 2.0, but I'm puzzled that the "otherwise"-path is not applied and I get an error that typeBool is not created:
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:variable name="type" select="/MyDoc/foo/bar/type/text()"/>
<xsl:choose>
<xsl:when test="$type = '2'">
<xsl:variable name="typeBool">true</xsl:variable>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="typeBool">false</xsl:variable>
</xsl:otherwise>
</xsl:choose>
<h1><b><xsl:value-of select="$typeBool"/></b></h1>
</xsl:transform>
This is the transformation error I get:
error during xslt transformation:
Source location: line 0, col 0 Description:
No variable with name typeBool exists
As you currently present your problem, an xsl:choose is not needed and it unnecessarily complicates your XSLT code. Your actual problem might be more intricate though.
You can write a template that matches the element you are interested in (for instance, type elements) and then simply select the value of a comparison that will evaluate to either true or false.
XSLT Stylesheet
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="html" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:template match="type">
<h1>
<b>
<xsl:value-of select=". = '2'"/>
</b>
</h1>
</xsl:template>
<xsl:template match="text()"/>
</xsl:transform>
HTML Output
<h1><b>true</b></h1>
Try it online here.
With choose-element
The choose-clause has to be defined inside of the variable-declaration:
<xsl:variable name="type">
<xsl:value-of select="/MyDoc/foo/bar/type/text()"/>
</xsl:variable>
<xsl:variable name="typeBool">
<xsl:choose>
<xsl:when test="$type = '2'">true</xsl:when>
<xsl:otherwise>false</xsl:otherwise>
</xsl:choose>
</xsl:variable>
The conditional also looks cleaner this way.
With XSLT 2.0
#MichaelKay pointed out that in XSLT 2.0 a xpath-conditional can be used, which is even simpler:
<xsl:variable name="type">
<xsl:value-of select="/MyDoc/foo/bar/type/text()"/>
</xsl:variable>
<h1>
<b>
<xsl:value-of select="select="if($type=2) then 'true' else 'false'"/>
</b>
</h1>

XSLT 1.0 - how to check when condition for string

I am trying to conditional check on the input xml file and place the value.
input xml:
<workorder>
<newwo>1</newwo>
</workorder>
If newwo is 1, then I have to set in my output as "NEW" else "OLD"
Expected output is:
newwo: "NEW"
my xslt is:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="2.0">
<xsl:template match="/">
<xsl:apply-templates select="NEWWO" />
</xsl:template>
<xsl:template match="/NEWWO">
<xsl:text>{
newwo:"
</xsl:text>
<xsl:choose>
<xsl:when test="NEWWO != '0'">NEW</xsl:when>
<xsl:otherwise>OLD</xsl:otherwise>
</xsl:choose>
<xsl:text>"
}</xsl:text>
</xsl:template>
Please help me. Thanks in advance!
I see a number of reasons you aren't getting output.
The xpaths are case sensitive. NEWWO is not going to match newwo.
You match / and then apply-templates to newwo (case fixed), but newwo doesn't exist at that context. You'll either have to add */ or workorder/ to the apply-templates (like select="*/newwo") or change / to /* or /workorder in the match.
You match /newwo (case fixed again), but newwo is not the root element. Remove the /.
You do the following test: test="newwo != '0'", but newwo is already the current context. Use . or normalize-space() instead. (If you use normalize-space(), be sure to test against a string. (Quote the 1.))
Here's an updated example.
XML Input
<workorder>
<newwo>1</newwo>
</workorder>
XSLT 1.0
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:template match="/*">
<xsl:apply-templates select="newwo" />
</xsl:template>
<xsl:template match="newwo">
<xsl:text>{
newwo: "</xsl:text>
<xsl:choose>
<xsl:when test=".=1">NEW</xsl:when>
<xsl:otherwise>OLD</xsl:otherwise>
</xsl:choose>
<xsl:text>"
}</xsl:text>
</xsl:template>
</xsl:stylesheet>
Output
{
newwo: "NEW"
}
You try it as below
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="1.0">
<xsl:template match="/">
<xsl:choose>
<xsl:when test="/workorder/newwo = 1">
<xsl:text disable-output-escaping="no"> newwo:New</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text disable-output-escaping="no"> newwo:Old</xsl:text> </xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

Convert short form days of the week to day names in xslt

I have some short form day names like so:
M -> Monday
T -> Tuesday
W -> Wednesday
R -> Thursday
F -> Friday
S -> Saturday
U -> Sunday
How can I convert an xml element like <days>MRF</days> into the long version <long-days>Monday,Thursday,Friday</long-days> using xslt?
Update from comments
Days will not be repeated
This stylesheet
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:d="day"
exclude-result-prefixes="d">
<d:d l="M" n="Monday"/>
<d:d l="T" n="Tuesday"/>
<d:d l="W" n="Wednesday"/>
<d:d l="R" n="Thursday"/>
<d:d l="F" n="Friday"/>
<d:d l="S" n="Saturday"/>
<d:d l="U" n="Sunday"/>
<xsl:variable name="vDays" select="document('')/*/d:d"/>
<xsl:template match="days">
<long-days>
<xsl:apply-templates
select="$vDays[contains(current(),#l)]"/>
</long-days>
</xsl:template>
<xsl:template match="d:d">
<xsl:value-of select="#n"/>
<xsl:if test="position()!=last()">,</xsl:if>
</xsl:template>
</xsl:stylesheet>
With this input:
<days>MRF</days>
Output:
<long-days>Monday,Thursday,Friday</long-days>
Edit: For those who wander, retaining the sequence order:
<xsl:variable name="vCurrent" select="current()"/>
<xsl:apply-templates
select="$vDays[contains($vCurrent,#l)]">
<xsl:sort select="substring-before($vCurrent,#l)"/>
</xsl:apply-templates>
Note: Because days wouldn't be repeated, this is the same as looking up for item existence in sequence with empty string separator.
That should do it... (There might be more elegant solutions though... ;-)
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/days">
<long-days>
<xsl:if test="contains(.,'M')">Monday<xsl:if test="string-length(substring-before(.,'M'))=string-length(.)-1">,</xsl:if></xsl:if>
<xsl:if test="contains(.,'T')">Tuesday<xsl:if test="string-length(substring-before(.,'T'))=string-length(.)-1">,</xsl:if></xsl:if>
<xsl:if test="contains(.,'W')">Wednesday<xsl:if test="string-length(substring-before(.,'W'))=string-length(.)-1">,</xsl:if></xsl:if>
<xsl:if test="contains(.,'R')">Thursday<xsl:if test="string-length(substring-before(.,'R'))=string-length(.)-1">,</xsl:if></xsl:if>
<xsl:if test="contains(.,'F')">Friday<xsl:if test="string-length(substring-before(.,'F'))=string-length(.)-1">,</xsl:if></xsl:if>
<xsl:if test="contains(.,'S')">Saturday<xsl:if test="string-length(substring-before(.,'S'))=string-length(.)-1">,</xsl:if></xsl:if>
<xsl:if test="contains(.,'U')">Sunday<xsl:if test="string-length(substring-before(.,'U'))=string-length(.)-1">,</xsl:if></xsl:if>
</long-days>
</xsl:template>
The currently accepted solution always displays the long days names in chronological order and in addition, it doesn't display repeating (with same code) days.
Suppose we have the following XML document:
<days>STMSU</days>
I. This XSLT 1.0 transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my" exclude-result-prefixes="my" >
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<my:days>
<M>Monday</M>
<T>Tuesday</T>
<W>Wednesday</W>
<R>Thursday</R>
<F>Friday</F>
<S>Saturday</S>
<U>Sunday</U>
</my:days>
<xsl:key name="kLongByShort" match="my:days/*"
use="name()"/>
<xsl:variable name="vstylesheet"
select="document('')"/>
<xsl:template match="days">
<long-days>
<xsl:call-template name="expand"/>
</long-days>
</xsl:template>
<xsl:template name="expand">
<xsl:param name="pcodeString" select="."/>
<xsl:if test="$pcodeString">
<xsl:variable name="vchar" select=
"substring($pcodeString,1,1)"/>
<xsl:for-each select="$vstylesheet">
<xsl:value-of select=
"concat(key('kLongByShort',$vchar),
substring(',',1,string-length($pcodeString)-1)
)
"/>
</xsl:for-each>
<xsl:call-template name="expand">
<xsl:with-param name="pcodeString" select=
"substring($pcodeString,2)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on the above document, produces the wanted, correct result:
<long-days>Saturday,Tuesday,Monday,Saturday,Sunday</long-days>
II. This XSLT 2.0 transformation:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vshortCodes" as="xs:integer+"
select="string-to-codepoints('MTWRFSU')"/>
<xsl:variable name="vlongDays" as="xs:string+"
select="'Monday','Tuesday','Wenesday','Thursday',
'Friday','Saturday','Sunday'
"/>
<xsl:template match="days">
<long-days>
<xsl:for-each select="string-to-codepoints(.)">
<xsl:value-of separator="" select=
"for $pos in position() ne last()
return
($vlongDays[index-of($vshortCodes,current())],
','[$pos])
"/>
</xsl:for-each>
</long-days>
</xsl:template>
</xsl:stylesheet>
when applied on the same XML document:
<days>STMSU</days>
produce the wanted, correct result:
<long-days>Saturday,Tuesday,Monday,Saturday,Sunday</long-days>