xsl:sort: sorting double digit by numeric value - sorting

UPDATE : example and add some information.
I have to sort out the number below in numerical order in XSLT 1.0. The "code" follow the pattern below :
for example,
<?xml version="1.0" encoding="ISO-8859-1"?>
<DATA>
<CTX>
<VA>
<code>25896_1_1_1</code>
</VA>
<VA>
<code>25896_10_1_1</code>
</VA>
<VA>
<code>25896_2_1_2</code>
</VA>
<VA>
<code>25896_3_1_1</code>
</VA>
<VA>
<code>25896_4_1_1</code>
</VA>
<VA>
<code>25897_1_1_1</code>
</VA>
<VA>
<code>25897_2_1_1</code>
</VA>
<VA>
<code>25897_2_1_2</code>
</VA>
</CTX>
</DATA>
when I just do <xsl:sort select="code" order="ascending" /> it displays above result.
However, I want that to be in order like this :
25896_1_1_1
25896_2_1_2
25896_3_1_1
25896_4_1_1
25896_10_1_1
25897_1_1_1
25897_2_1_1
25897_2_1_2
How do I do this?
JAXP XSLT APIs (javax.xml.transform) to transform xml file. With XSL Transform, the engine is Saxon 6.5.5 // https://xml.apache.org/xalan-j/trax.html

To do this in pure XSLT 1.0, you will need to do it the pedestrian way:
<xsl:sort select="substring-before(code, '_')" data-type="number"/>
<xsl:sort select="substring-before(substring-after(code, '_'), '_')" data-type="number"/>
<xsl:sort select="substring-before(substring-after(substring-after(code, '_'), '_'), '_')" data-type="number"/>
<xsl:sort select="substring-after(substring-after(substring-after(code, '_'), '_'), '_')" data-type="number"/>
If your processor happens to support the EXSLT str:tokenize() extension function, you could make it a bit more elegant:
<xsl:sort select="str:tokenize(code, '_')[1]" data-type="number"/>
<xsl:sort select="str:tokenize(code, '_')[2]" data-type="number"/>
<xsl:sort select="str:tokenize(code, '_')[3]" data-type="number"/>
<xsl:sort select="str:tokenize(code, '_')[4]" data-type="number"/>
Untested, because no code was provided.

Related

How to sort the 'joined' xsl nodeset

I have this (simplified) XML
<?xml version="1.0" encoding="iso-8859-1"?>
<LENEX version="3.0">
<MEETS>
<MEET name="British Gas Champs 2012">
<SESSIONS>
<SESSION number="1" name="Session 1" course="LCM" date="2012-07-22">
<EVENTS>
<EVENT eventid="104" number="104" gender="M" round="PRE" order="4">
<SWIMSTYLE distance="100" stroke="BACK" name="Boys 14 Yrs 100m Backstroke" />
<AGEGROUPS>
<AGEGROUP agegroupid="1" name="14 Yrs Age Group">
<RANKINGS>
<RANKING place="3" resultid="1" />
</RANKINGS>
</AGEGROUP>
</AGEGROUPS>
</EVENT>
</EVENTS>
</SESSION>
<SESSION number="2" name="Session 2" course="LCM" date="2012-07-22">
<EVENTS>
<EVENT eventid="207" number="207" gender="M" round="PRE" order="7">
<SWIMSTYLE distance="100" stroke="FREE" name="Boys 14 Yrs 100m Freestyle"/>
<AGEGROUPS>
<AGEGROUP agegroupid="1" name="14 Yrs Age Group">
<RANKINGS>
<RANKING place="1" resultid="2"/>
</RANKINGS>
</AGEGROUP>
</AGEGROUPS>
</EVENT>
</EVENTS>
</SESSION>
</SESSIONS>
<CLUBS>
<CLUB name="Aberdeen ASC" region="X" type="CLUB">
<ATHLETES>
<ATHLETE athleteid="1169" lastname="Butt" firstname="Suleman">
<RESULTS>
<RESULT resultid="1" eventid="104" swimtime="00:01:01.18"/>
<RESULT resultid="2" eventid="207" swimtime="00:00:53.06"/>
</RESULTS>
</ATHLETE>
</ATHLETES>
</CLUB>
</CLUBS>
</MEET>
</MEETS>
</LENEX>
and this stylesheet
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:php="http://php.net/xsl">
<xsl:variable name="session" select="/LENEX/MEETS/MEET/SESSIONS/SESSION" />
<xsl:variable name="athlete" select="/LENEX/MEETS/MEET/CLUBS/CLUB/ATHLETES/ATHLETE" />
<!-- variable for testing/debugging-->
<xsl:variable name="athlno" select="1169" />
<!--<xsl:param name="evno" />-->
<xsl:template match="/">
<html>
<head>
</head>
<body>
<xsl:apply-templates select="$athlete" />
</body>
</html>
</xsl:template>
<!-- find the results (#resultid) for athleteid in $athlno -->
<xsl:template match="ATHLETE[#athleteid]">
<xsl:choose>
<xsl:when test="self::ATHLETE[#athleteid = $athlno]/RESULTS">
<xsl:apply-templates select="child::RESULTS/RESULT">
<!-- these 'sorts' works -->
<!-- <xsl:sort select="#resultid" data-type="text" order="descending"/>
<xsl:sort select="#eventid" order="descending"/> -->
</xsl:apply-templates>
</xsl:when>
</xsl:choose>
</xsl:template>
<!-- create $res1 $stim1 variables to pass to 'SESSIONS' node -->
<xsl:template match="RESULT">
<xsl:variable name="res1" ><xsl:value-of select="#resultid"/></xsl:variable>
<xsl:variable name="stim1" ><xsl:value-of select="#swimtime"/></xsl:variable>
<tr>
<!--join to 'SESSIONS' node -->
<xsl:apply-templates select="$session/EVENTS/EVENT">
<!-- this xsl:sort doesn't work
<xsl:sort select="#number" data-type="text" order="descending"/>-->
<xsl:with-param name="res2" select="$res1"/>
<xsl:with-param name="stim2" select="$stim1"/>
</xsl:apply-templates>
</tr>
</xsl:template>
<xsl:template match="EVENT">
<xsl:param name="res2" />
<xsl:param name="stim2" />
<xsl:for-each select="child::AGEGROUPS/AGEGROUP/RANKINGS/RANKING[#resultid = $res2]">
<!-- this xsl:sort doesn't work -->
<xsl:sort select="#place" data-type="text" order="ascending"/>
<xsl:value-of select="#place"/>
<xsl:choose>
<xsl:when test="../../../../#round = 'TIM'">
<xsl:text> </xsl:text>HDW
</xsl:when>
<xsl:when test="../../../../#round = 'PRE'">
<xsl:text> </xsl:text>Heat
</xsl:when>
<xsl:when test="../../../../#round = 'SEM'">
<xsl:text> </xsl:text>Semi-F
</xsl:when>
<xsl:when test="../../../../#round = 'FIN'">
<xsl:text> </xsl:text>Final
</xsl:when>
<xsl:otherwise>
<xsl:text> </xsl:text>n/a
</xsl:otherwise>
</xsl:choose>
<xsl:text> </xsl:text><xsl:value-of select="../../../../SWIMSTYLE/#name" />
<xsl:text> </xsl:text><xsl:value-of select="substring($stim2,4)"/>
<xsl:text> </xsl:text><xsl:value-of select="../../../../#number" /><br/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
I only seem to be able to sort by the main nodeset ie RESULTS/RESULT/#resultid or RESULTS/RESULT/eventid. I would like to sort the results of a 'join' by EVENT/SWIMSTYLE/#stroke, EVENT/SWIMSTYLE/#distance and EVENT/#round. I am limited to XSLT 1.0. What I've tried is commented in the XSLT above.
The output I'm looking for is ...
3 Final Boys 14 Yrs 100m Backstroke 01:00.83 269
3 Heat Boys 14 Yrs 100m Backstroke 01:01.18 104
3 Semi-F Boys 14 Yrs 100m Backstroke 01:00.66 156
1 Final Boys 14 Yrs 100m Freestyle 00:52.61 363
1 Heat Boys 14 Yrs 100m Freestyle 00:53.06 207
1 Semi-F Boys 14 Yrs 100m Freestyle 00:53.56 259
1 Final Boys 14 Yrs 200m Freestyle 01:53.34 555
1 Heat Boys 14 Yrs 200m Freestyle 01:57.29 402
1 Semi-F Boys 14 Yrs 200m Freestyle 01:55.23 454
1 Final Boys 14 Yrs 400m Freestyle 04:03.46 351
1 Heat Boys 11/14 Yrs 400m Freestyle 04:09.46 301
A few keys will help you build paths to your sort attributes:
<!-- Map from #resultid to EVENT -->
<xsl:key name="result-event" match="//EVENT" use="descendant::RANKING/#resultid"/>
<!-- Map from #resultid to RANKING -->
<xsl:key name="ranking" match="//RANKING" use="#resultid"/>
Then, when templating the RESULT elements for an ATHLETE, you can reference the sort attributes:
<xsl:template match="/">
<xsl:apply-templates select="//ATHLETE"/>
</xsl:template>
<xsl:template match="ATHLETE">
<xsl:apply-templates select="RESULTS/RESULT">
<xsl:sort select="key('result-event', #resultid)/SWIMSTYLE/#stroke"/>
<xsl:sort select="key('result-event', #resultid)/SWIMSTYLE/#distance" data-type="number"/>
<xsl:sort select="key('result-event', #resultid)/#round"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="RESULT">
<xsl:value-of select="key('ranking', #resultid)/#place"/>
<xsl:apply-templates select="key('result-event', #resultid)"/>
<xsl:value-of select="#swimtime"/>
<br/>
</xsl:template>
<xsl:template match="EVENT">
<xsl:text> </xsl:text><xsl:value-of select="#round"/>
<xsl:text> </xsl:text><xsl:value-of select="SWIMSTYLE/#name" />
<xsl:text> </xsl:text><xsl:value-of select="../../#number" />
</xsl:template>
You can adjust the sort order for rounds by using a lookup table to map the #round key to an arbitrary surrogate key.

Sorting xslt nodes based on sibling

I am having some trouble getting this to work and I could really need some help.
I want to store the smallest variable that has a sibling which is not null.
I have a XML which looks like this:
<Root>
<Item>
<ValueOne>5</ValueOne>
<Item>bababa</Item>
</Item>
<Item>
<ValueOne>3</ValueOne>
<Item>ababa</Item>
</Item>
<Item>
<ValueOne>1</ValueOne>
<Item/>
</Item>
</Root>
I want the smallest ValueOne who's sibling Item has a value.
I was thinking something like:
<xsl:variable name="var">
<xsl:for-each select="Root/Item">
<xsl:if test="not(/Item = '')">
<xsl:sort data-type="text"/>
<xsl:if test="position()=1">
<xsl:value-of select="/Item"/>
</xsl:if>
</xsl:if>
</xsl:for-each>
</xsl:variable>
But this doesn't work and I am not sure why. As you might tell I'm not the best at xslt. I cannot rename anything in the xml or change the structure of it.
Assuming those spaces and returns are non-essential, you could use:
<xsl:variable name="var">
<xsl:for-each select="Root/Item[normalize-space(Item)]">
<xsl:sort select="ValueOne" data-type="number" order="ascending"/>
<xsl:if test="position()=1">
<xsl:value-of select="normalize-space(ValueOne)"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
With all the redundant white space removed, this can be simplified to:
<xsl:variable name="var">
<xsl:for-each select="Root/Item[Item/text()]">
<xsl:sort select="ValueOne" data-type="number" order="ascending"/>
<xsl:if test="position()=1">
<xsl:value-of select="ValueOne"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>

sort in xslt when before creating table of data

I am trying to take the data and put in a table and sort it before I output to the table and when I do it sorts the first columun and rest of the column are not sorted at all. I am trying to sort by streetNuame and only works on the first table column
<fo:table>
<fo:table-column width="82mm"/>
<fo:table-column width="82mm"/>
<fo:table-column width="82mm"/>
<fo:table-body>
<xsl:for-each select="route/address[position() mod 3 = 1]">
<xsl:sort select="streetName"/>
<fo:table-row>
<xsl:apply-templates select=". | following-sibling::*[3 > position()]"/>
<xsl:variable name="vPos" select="position()"/>
<xsl:variable name="vUnfilled"
select=" 2 - count(following-sibling::*)"/>
<xsl:if test="position() = last()">
<xsl:for-each select="../*[not(position() > $vUnfilled)]">
<fo:table-cell>
<fo:block>
<xsl:value-of select="' '"/>
</fo:block>
</fo:table-cell>
</xsl:for-each>
</xsl:if>
</fo:table-row>
</xsl:for-each>
</fo:table-body>
</fo:table>
XML below:
<route>
<name>blah</name>
<address>
<fullStreet>blah</fullStreet>
<streetNumber>blah</streetNumber>
<streetName>blah</streetName>
</address>

xslt 1.0 to replace xpath references with the actual data in the same xml

i have some requirement, where i need to replace the references in the same xml file.
limitation is to use xslt 1.0 only.
below is my sample input xml.
<org>
<depts>
<dept>
<deptId>1009</deptId>
<deptName>IT</deptName>
<deptAccessCode>IT-1009</deptAccessCode>
</dept>
<dept>
<deptId>2344</deptId>
<deptName>BPO</deptName>
<deptAccessCode>BP-2344</deptAccessCode>
</dept>
</depts>
<employees>
<employee>
<name>abc</name>
<dept>
<REFERENCE>
<LocationXPath>/org/depts/dept[2]</LocationXPath>
</REFERENCE>
</dept>
<employee>
</employees>
</org>
now i want to replace the node REFERENCE with actual data at the XPath /org/depts/dept[2].
so the output xml should be like below.
<org>
<depts>
<dept>
<deptId>1009</deptId>
<deptName>IT</deptName>
<deptAccessCode>IT-1009</deptAccessCode>
</dept>
<dept>
<deptId>2344</deptId>
<deptName>BPO</deptName>
<deptAccessCode>BP-2344</deptAccessCode>
</dept>
</depts>
<employees>
<employee>
<name>abc</name>
<dept>
<deptId>2344</deptId>
<deptName>BPO</deptName>
<deptAccessCode>BP-2344</deptAccessCode>
</dept>
<employee>
</employees>
</org>
i have several REFERENCE nodes in different elements referencing to different xpaths across the xml tree, which i need to replace them with actual data.
<someWhereInTheXmlTree>
<sometag>
<REFERENCE>
<LocationXPath>some/reference[1]/to/a/node[3]/in/the[4]/same/xml</LocationXPath>
</REFERENCE>
</sometag>
<someWhereInTheXmlTree>
...
<ffff>
<bbbb>
<REFERENCE>
<LocationXPath>abc/xyz[1]/node[4]/element</LocationXPath>
</REFERENCE>
</bbbb>
<ffff>
please help me on this.
Thanks in advance for the help.
So far i have implemented one XSLT to replace the references but now i am facing unwanted empty name spaces.
Here is my XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:tib="http://www.tibco.com/bw/xslt/custom-functions"
xmlns="http://www.realestate.org/residential/2010/schemas" >
<xsl:output omit-xml-declaration="no" indent="yes" method = "xml" />
<xsl:strip-space elements="*"/>
<xsl:param name="myxml" />
<xsl:template match="node()|#*">
<xsl:param name="isNodeToReplace"><xsl:call-template name="ReferenceCheck" /></xsl:param>
<xsl:choose>
<xsl:when test="$isNodeToReplace='true'">
<xsl:call-template name="replaceWithData">
<xsl:with-param name="ref"><xsl:value-of select="." /></xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="ReferenceCheck">
<xsl:choose>
<xsl:when test="name(child::*[1])='REFERENCE' and name(child::*[1]//child::*[1])='LocationXPath'">true</xsl:when>
<xsl:otherwise>false</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="replaceWithData">
<xsl:param name="ref" />
<xsl:copy-of select="tib:evaluate($myxml,$ref)" />
</xsl:template>
</xsl:stylesheet>
in the above XSLT i am passing the entire xml (same xml, which is being processed) as a parameter $myxml
Below is my sample input XML --this is just a snippet of xml,The actual xml file which i am dealing with is too large and contains so complex tree structure.How ever this sample xml is suffice enough to produce my problem.
Input file
<?xml version="1.0" encoding="UTF-8"?>
<org xmlns="http://www.realestate.org/residential/2010/schemas">
<depts>
<dept>
<deptId>1</deptId>
<deptName>health</deptName>
<deptAccessCode>HL007845</deptAccessCode>
</dept>
</depts>
<employees>
<employee>
<name>TOM</name>
<dept>
<REFERENCE>
<LocationXPath>/org/depts/dept[1]</LocationXPath>
</REFERENCE>
</dept>
</employee>
</employees>
</org>
my output file
<?xml version="1.0" encoding="UTF-8"?>
<org xmlns="http://www.realestate.org/residential/2010/schemas">
<depts>
<dept>
<deptId>1</deptId>
<deptName>health</deptName>
<deptAccessCode>HL007845</deptAccessCode>
</dept>
</depts>
<employees>
<employee>
<name>TOM</name>
<dept xmlns="">
<deptId>1</deptId>
<deptName>health</deptName>
<deptAccessCode>HL007845</deptAccessCode>
</dept>
</employee>
</employees>
</org>
Where as Expected output
<?xml version="1.0" encoding="UTF-8"?>
<org xmlns="http://www.realestate.org/residential/2010/schemas">
<depts>
<dept>
<deptId>1</deptId>
<deptName>health</deptName>
<deptAccessCode>HL007845</deptAccessCode>
</dept>
</depts>
<employees>
<employee>
<name>TOM</name>
<dept>
<deptId>1</deptId>
<deptName>health</deptName>
<deptAccessCode>HL007845</deptAccessCode>
</dept>
</employee>
</employees>
</org>
so i am getting unwanted empty name space in << dept xmlns="">> in the replaced root element.
Hope this could clearly explain my problem
Thanks in Advance
ultimately i have found the solution at the link below to remove the unwanted empty name spaces.
http://social.msdn.microsoft.com/forums/en-US/xmlandnetfx/thread/0de59291-ef3a-4a4c-9ca5-17923b16a504
Here is the new XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:tib="http://www.tibco.com/bw/xslt/custom-functions"
xmlns="http://www.realestate.org/residential/2010/schemas" >
<xsl:output omit-xml-declaration="no" indent="yes" method = "xml" />
<xsl:strip-space elements="*"/>
<xsl:param name="myxml" />
<xsl:template match="node()|#*">
<xsl:param name="isNodeToReplace"><xsl:call-template name="ReferenceCheck" /></xsl:param>
<xsl:choose>
<xsl:when test="$isNodeToReplace='true'">
<xsl:call-template name="replaceWithData">
<xsl:with-param name="ref"><xsl:value-of select="." /></xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="ReferenceCheck">
<xsl:choose>
<xsl:when test="name(child::*[1])='REFERENCE' and name(child::*[1]//child::*[1])='LocationXPath'">true</xsl:when>
<xsl:otherwise>false</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="replaceWithData">
<xsl:param name="ref" />
<xsl:apply-templates select="tib:evaluate($myxml,$ref)" mode="move-to-namespace">
<xsl:with-param name="namespace" select="'http://www.realestate.org/residential/2010/schemas'" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="*" mode="move-to-namespace">
<xsl:param name="namespace" />
<xsl:element name="{local-name()}" namespace="{$namespace}">
<xsl:copy-of select="#*" />
<xsl:apply-templates select="node()" mode="move-to-namespace">
<xsl:with-param name="namespace" select="$namespace"/>
</xsl:apply-templates>
</xsl:element>
</xsl:template>
<xsl:template match="text() | comment() | processing-instruction()" mode="move-to-namespace">
<xsl:copy/>
</xsl:template>
</xsl:stylesheet>
if any xslt expert refines it further to avoid any unnecessary instruction with proper explanation its very glad.
Thanks in advance
In general:
Not posiible in pure XSLT 1.0.
Not possible in pure XSLT 2.0
May be possible in pure XSLT 3.0 -- read about the
<xsl:evaluate> instruction.
In XSLT 1.0 you may be lucky if your XSLT processor implements the EXSLT dyn:evaluate() extension function (a few do).
Otherwize, you will have to write an extension function to select the
nodes and return them back.
If there are restrictions on the syntax of the XPath expressions, then it may be possible to implement a pure XSLT 1.0 solution.
This is not possible in pure XSLT 1.0. You have to use extension function, e.g. Xalan evaluate expression

Informatica B2B Data Transformation

I am trying to handle a xsd:choice in the B2B transformation engine.
(xsd examples here)
That is I have a
And I am only intrested in getting the "one" part
And I only want extract the "one" element not caring from which underlying type it comes from, i.e. I want to put the result into "one" below regardless if it comes from the thingyone, thingytwo or thingythree.
In XSLT terms I would like to do:
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.differentthingies.com/20111119/thingy" xsi:schemaLocation="http://xmlns.differentthingies.com/20111119/thingy differentthinggies.xsd">
<differentthingies>
<thingythree>
<one>thisisthingythree1</one>
<three2>thisisthingythree2</three2>
<three3>thisisthingythree3</three3>
</thingythree>
</differentthingies>
</root>
Transformed by
<xsl:template match="/">
<xsl:variable name="var1_root" select="ns0:root"/>
<root xmlns="http://xmlns.differentthingies.com/20111119/anotherthingy">
<xsl:attribute name="xsi:schemaLocation" namespace="http://www.w3.org/2001/XMLSchema-instance">http://xmlns.differentthingies.com/20111119/anotherthingy thingy.xsd</xsl:attribute>
<thingyone>
<xsl:for-each select="$var1_root/ns0:differentthingies/ns0:thingyone/ns0:one">
<one>
<xsl:value-of select="string(.)"/>
</one>
</xsl:for-each>
<xsl:for-each select="$var1_root/ns0:differentthingies/ns0:thingythree/ns0:one">
<one>
<xsl:value-of select="string(.)"/>
</one>
</xsl:for-each>
<xsl:for-each select="$var1_root/ns0:differentthingies/ns0:thingytwo/ns0:one">
<one>
<xsl:value-of select="string(.)"/>
</one>
</xsl:for-each>
</thingyone>
</root>
</xsl:template>
Would become
<root xmlns="http://xmlns.differentthingies.com/20111119/anotherthingy" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.differentthingies.com/20111119/anotherthingy thingy.xsd">
<thingyone>
<one>thisisthingythree1</one>
</thingyone>
</root>

Resources