XSLT: How to reverse output without sorting by content - xslt

I have a list of items:
<item>a</item>
<item>x</item>
<item>c</item>
<item>z</item>
and I want as output
z
c
x
a
I have no order information in the file and I just want to reverse the lines. The last line in the source file should be first line in the output. How can I solve this problem with XSLT without sorting by the content of the items, which would give the wrong result?

I will present two XSLT solutions:
I. XSLT 1.0 with recursion Note that this solution works for any node-set, not only in the case when the nodes are siblings:
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/*">
<xsl:call-template name="reverse">
<xsl:with-param name="pList" select="*"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="reverse">
<xsl:param name="pList"/>
<xsl:if test="$pList">
<xsl:value-of
select="concat($pList[last()], '
')"/>
<xsl:call-template name="reverse">
<xsl:with-param name="pList"
select="$pList[not(position() = last())]"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document:
<t>
<item>a</item>
<item>x</item>
<item>c</item>
<item>z</item>
</t>
produces the wanted result:
z
c
x
a
II. XSLT 2.0 solution :
<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:value-of select="reverse(*)/string(.)"
separator="
"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the same XML document, the same correct result is produced.

XML CODE:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!-- Edited by XMLSpy® -->
<device>
<element>a</element>
<element>x</element>
<element>c</element>
<element>z</element>
</device>
XSLT CODE:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!-- Edited by XMLSpy® -->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="//device">
<xsl:for-each select="element">
<xsl:sort select="position()" data-type="number" order="descending"/>
<xsl:text> </xsl:text>
<xsl:value-of select="."/>
<xsl:text> </xsl:text>
</xsl:for-each>
</xsl:template>
note: if you're using data-type="number", and any of the values aren't numbers, those non-numeric values will sort before the numeric values. That means if you're using order="ascending", the non-numeric values appear first; if you use order="descending", the non-numeric values appear last.
Notice that the non-numeric values were not sorted; they simply appear in the output document in the order in which they were encountered.
also, you may find usefull to read this:
http://docstore.mik.ua/orelly/xml/xslt/ch06_01.htm

Not sure what the full XML looks like, so I wrapped in a <doc> element to make it well formed:
<doc>
<item>a</item>
<item>x</item>
<item>c</item>
<item>z</item>
</doc>
Running that example XML against this stylesheet:
<?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" encoding="UTF-8" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:call-template name="reverse">
<xsl:with-param name="item" select="doc/item[position()=last()]" />
</xsl:call-template>
</xsl:template>
<xsl:template name="reverse">
<xsl:param name="item" />
<xsl:value-of select="$item" />
<!--Adds a line feed-->
<xsl:text>
</xsl:text>
<!--Move on to the next item, if we aren't at the first-->
<xsl:if test="$item/preceding-sibling::item">
<xsl:call-template name="reverse">
<xsl:with-param name="item" select="$item/preceding-sibling::item[1]" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Produces the requested output:
z
c
x
a
You may need to adjust the xpath to match your actual XML.

Consider this XML input:
<?xml version="1.0" encoding="utf-8" ?>
<items>
<item>a</item>
<item>x</item>
<item>c</item>
<item>z</item>
</items>
The XSLT:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/items[1]">
<xsl:variable name="items-list" select="." />
<xsl:variable name="items-count" select="count($items-list/*)" />
<xsl:for-each select="item">
<xsl:variable name="index" select="$items-count+1 - position()"/>
<xsl:value-of select="$items-list/item[$index]"/>
<xsl:value-of select="'
'"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
And the result:
z
c
x
a

Related

how to i get the tokenize spliting function content order instead of xml order

When i use XSLT 2.0 key and tokenize function, it's return items order getting changed based on key value. in our output we required retain the same order of tokenize sequence.
Input File
<?xml version="1.0" encoding="UTF-8"?> <a> <bd id="a">a</bd> <bd id="b">b</bd> <bd id="e">e</bd> <bd id="d">d</bd> </a>
XSLT
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:key name="idcollect" match="*[#id]" use="#id" />
<xsl:variable name="name" select="'d,b,e,a'"/>
<xsl:template match="/">
<xsl:for-each select="key('idcollect',tokenize($name,','))" >
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
current Output
<?xml version="1.0" encoding="UTF-8"?><bd id="a">a</bd><bd id="b">b</bd><bd id="e">e</bd><bd id="d">d</bd>
Expected output
<?xml version="1.0" encoding="UTF-8"?><bd id="d">d</bd><bd id="b">b</bd><bd id="e">e</bd><bd id="a">a</bd>
I think you want e.g.
<xsl:variable name="main-doc" select="/"/>
<xsl:for-each select="for $token in tokenize($name,',') return key('idcollect', $token, $main-doc)">
<xsl:copy-of select="."/>
</xsl:for-each>
or in XSLT 3
<xsl:variable name="main-doc" select="/"/>
<xsl:for-each select="tokenize($name,',') ! key('idcollect', ., $main-doc)">
<xsl:copy-of select="."/>
</xsl:for-each>
Of course in both cases the for-each/copy-of nesting is not needed and e.g.
<xsl:copy-of select="let $main-doc := / return tokenize($name,',') ! key('idcollect', ., $main-doc)"/>
or
<xsl:variable name="main-doc" select="/"/>
<xsl:copy-of select="for $token in tokenize($name,',') return key('idcollect', $token, $main-doc)"/>
would suffice.
Try:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="idcollect" match="*[#id]" use="#id" />
<xsl:variable name="name" select="'d,b,e,a'"/>
<xsl:template match="/">
<xsl:variable name="ids" select="tokenize($name,',')"/>
<xsl:for-each select="key('idcollect', $ids)" >
<xsl:sort select="index-of($ids, .)"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Or, if you prefer:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="idcollect" match="*[#id]" use="#id" />
<xsl:variable name="name" select="'d,b,e,a'"/>
<xsl:template match="/">
<xsl:variable name="xml" select="/"/>
<xsl:for-each select="tokenize($name, ',')" >
<xsl:copy-of select="key('idcollect', ., $xml)"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

XSLT 2.0 XPATH expression with variable

I'm working on a Java based xsl-transformation (XSLT 2.0, could also be XSLT 3.0 if there is a free processor for java) with different input xml files. one input file could look like this:
<?xml version="1.0" encoding="UTF-8"?>
<TEST>
<MyElement>
<CHILD>A</CHILD>
<CHILDBEZ>ABEZ</CHILDBEZ>
<NotInteresting></NotInteresting>
</MyElement>
<MyElement>
<CHILD>B</CHILD>
<CHILDBEZ>BBEZ</CHILDBEZ>
<NotInteresting2></NotInteresting2>
</MyElement>
</TEST>
I want to copy all elements but "NotInteresting" and rename the two nodes CHILD and CHILDBEZ based on two parameters that I get from a mapping file:
the xpath expression that tells me where the text of interest is placed (in this case: TEST/MyFirstElement/CHILD and TEST/MyFirstElement/CHILDBEZ)
and the names of the elements what they should have in the output file (in this case: childName and childBez)
the mapping file:
<?xml version="1.0" encoding="UTF-8"?>
<element root="TEST">
<childName>TEST/MyElement/CHILD</childName>
<childBez>TEST/MyElement/CHILDBEZ</childBez>
</element>
desired output:
<TEST>
<MyElement>
<childName>A</childName>
<childBez>ABEZ</childBez>
</MyElement>
<MyElement>
<childName>B</childName>
<childBez>BBEZ</childBez>
</MyElement>
</TEST>
what I have so far:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version="2.0 "
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.apoverlag.at"
xmlns:apo="http://www.apoverlag.at">
<xsl:variable name="vars" select="document('mapping.xml')"/>
<xsl:param name="src" />
<xsl:variable name="path" xpath-default-namespace="" select="$src/path"/> <!-- = TEST/*-->
<xsl:template match="/">
<xsl:for-each select="$path">
<xsl:call-template name="buildNode">
<xsl:with-param name="currentNode" select="current()"></xsl:with-param>
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template name="buildNode">
<xsl:param name="currentNode" />
<xsl:element name="test">
<xsl:for-each select="$vars/element/#root">
<xsl:for-each select="$vars/element/*">
<xsl:element name="{name(.)}"> <xsl:value-of select="concat($currentNode,'/',current())" />
</xsl:element>
</xsl:for-each>
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:transform>
My problem is that:
<xsl:value-of select="concat($currentNode,'/',current())" />
gives me "/TEST/MyFirstElement/CHILD" when I try it hardcoded with:
<xsl:value-of select="$currentNode/CHILD" />
I receive my desired output. Can anyone give me a hint how to solve this problem?
I would suggest a radically different approach. To simplify, I have used a mapping document with full paths (starting with the / root node):
mapping xml
<element root="TEST">
<childName>/TEST/MyElement/CHILD</childName>
<childBez>/TEST/MyElement/CHILDBEZ</childBez>
</element>
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="mapping" select="document('mapping.xml')"/>
<xsl:key name="map" match="*" use="." />
<xsl:template match="/">
<xsl:variable name="first-pass">
<xsl:apply-templates mode="first-pass"/>
</xsl:variable>
<xsl:apply-templates select="$first-pass/*"/>
</xsl:template>
<xsl:template match="*" mode="first-pass">
<xsl:param name="parent-path"/>
<xsl:variable name="path" select="concat($parent-path, '/', name())" />
<xsl:variable name="replacement" select="key('map', $path, $mapping)" />
<xsl:element name="{if ($replacement) then name($replacement) else name()}">
<xsl:attribute name="original" select="not($replacement)"/>
<xsl:apply-templates mode="first-pass">
<xsl:with-param name="parent-path" select="$path"/>
</xsl:apply-templates>
</xsl:element>
</xsl:template>
<xsl:template match="*">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[#original='true' and not(descendant::*/#original='false')]"/>
</xsl:stylesheet>
Result, when applied to the provided input:
<?xml version="1.0" encoding="UTF-8"?>
<TEST>
<MyElement>
<childName>A</childName>
<childBez>ABEZ</childBez>
</MyElement>
<MyElement>
<childName>B</childName>
<childBez>BBEZ</childBez>
</MyElement>
</TEST>

Reference namespace in same XSLT

Trying to reference namespace to the same xslt with document('') but I get :
SystemID: file:/c:/intersystems/cache/mgr/samples/; Line#: 1; Column#: 1
net.sf.saxon.trans.XPathException: Error reported by XML parser
....
Caused by: org.xml.sax.SAXParseException; systemId: file:/c:/intersystems/cache/mgr/samples/; lineNumber: 1; columnNumber: 1; Content is not allowed in prolog.
at com.sun.org.apache.xerces.internal.util.ErrorHandlerWrapper.createSAXParseException(Unknown Source)
So it seems can not reference the same xslt? Is there some way to do this
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
xmlns:xdt="http://www.w3.org/2005/xpath-datatypes" xmlns:csv="csv:csv">
<!-- <xsl:output method="text" version="4.0" encoding="iso-8859-1" indent="yes"/> -->
<xsl:strip-space elements="*" />
<xsl:output method="text" encoding="utf-8" />
<xsl:variable name="delimiter" select="','"/>
<csv:columns>
<column>GlobalID</column>
<column>ServicePointName</column>
</csv:columns>
<xsl:template match="/Report">
<!-- Output the CSV header -->
****<xsl:for-each select="document('')/*/csv:columns/*">****
<xsl:value-of select="."/>
<xsl:if test="position() != last()">
<xsl:value-of select="$delimiter"/>
</xsl:if>
</xsl:for-each>
<xsl:text>
</xsl:text>
<!-- Output rows for each matched Report -->
<xsl:apply-templates select="*" />
</xsl:template>
<xsl:template match="/Report/CLI">
<xsl:for-each select="//CLI">
<xsl:value-of select="GlobalID"/>
<xsl:value-of select="$delimiter"/>
<xsl:value-of select="ServicePointName"/>
<!-- Add a newline at the end of the record -->
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Saxon 9 is an XSLT 2.0 processor - so you can easily do:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:variable name="delimiter" select="','"/>
<xsl:variable name="columns">
<column>GlobalID</column>
<column>ServicePointName</column>
</xsl:variable>
<xsl:template match="/">
<xsl:for-each select="$columns/*">
<xsl:value-of select="."/>
<xsl:if test="position() != last()">
<xsl:value-of select="$delimiter"/>
</xsl:if>
</xsl:for-each>
<xsl:text>
</xsl:text>
<!-- the rest -->
</xsl:template>
</xsl:stylesheet>
I don't see that you're using the columns further on in your stylesheet. If so, why don't you simplify the whole thing to:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:template match="/">
<xsl:text>GlobalID,ServicePointName
</xsl:text>
<!-- the rest -->
</xsl:template>
</xsl:stylesheet>
(which is also XSLT 1.0 compatible).
None of this explains why your attempt to use an internal node wouldn't work.
I've never tried to do this, but just looking at the documentation for the document() function it says this:
document() Used to access the nodes in an external XML document
Edit: I jumped the gun as pointed out by comments below. There is another question facing the same problems:
XSLT document('') function doesn't work

How to assign formated value to other variable in xslt

<xsl:value-of select="substring-before($temp1,';')" disable-output-escaping="yes"/>
where temp1="fassdf sdf; asdf &dfsdfsdf;fsdfsf;"
The above code I am using to split value using ";". The problem is temp1 having &, so it splits this value by the escaped sequence character ;. So i am getting wrong output. But if I use the disable-output-escaping="yes" then the "&" is converted to &.
How to get the formatted value from the string? So if i split the string i will not get any issue. Because I will get string with & instead of &
Lets assume a sample XML for your/our convenience ..
<?xml version="1.0" encoding="utf-8"?>
<root>
<child>sharepoint; R&D;Department;</child>
</root>
XSLT code to output the desired one:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="text" indent="no"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:apply-templates select="node()"/>
</xsl:template>
<xsl:template match="child">
<xsl:call-template name="SplitString">
<xsl:with-param name="StringVal" select="concat(.,';')"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="SplitString">
<xsl:param name="StringVal"/>
<xsl:variable name="first" select="substring-before($StringVal, ';')" />
<xsl:variable name="remaining" select="substring-after($StringVal, ';')" />
<xsl:value-of select="normalize-space($first)" disable-output-escaping="yes" />
<xsl:if test="$remaining">
<xsl:value-of select="'
'"/>
<xsl:call-template name="SplitString">
<xsl:with-param name="StringVal" select="$remaining" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
This is the Output you get:
sharepoint
R&D
Department

how to get required name from a string in xslt?

e.g i have following strings:
xoc.coe.hw.ZSBALAJI
hw.cor.exp.nt.ZSSHIVA
i have to get only last string (i.e. ZSBALAJI from first and ZSSHIVA from second). How can I do it in xslt.
Thanks in advance.
Here is an XSLT-1.0 solution to your problem:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="//string">
<xsl:call-template name="skipper">
<xsl:with-param name="source" select="."/>
<xsl:with-param name="delimiter" select="'.'"/>
</xsl:call-template>
</xsl:template>
<!-- returns the substring after the last delimiter -->
<xsl:template name="skipper">
<xsl:param name="source"/>
<xsl:param name="delimiter"/>
<xsl:choose>
<xsl:when test="contains($source,$delimiter)">
<xsl:call-template name="skipper">
<xsl:with-param name="source" select="substring-after($source,$delimiter)"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$source"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
When applied to this document:
<?xml version="1.0" encoding="UTF-8"?>
<strings>
<string>xoc.coe.hw.ZSBALAJI</string>
<string>hw.cor.exp.nt.ZSSHIVA</string>
</strings>
It produces the following result:
ZSBALAJI
ZSSHIVA
Let's assume that you have the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<a>xoc.coe.hw.ZSBALAJI</a>
<a>hw.cor.exp.nt.ZSSHIVA</a>
</root>
Then the following XSLT
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="text"/>
<xsl:template match="//a">
<xsl:variable name="parts" select="tokenize(node(), '\.')"/>
<xsl:variable name="count" select="count($parts)"/>
<xsl:for-each select="$parts">
<xsl:if test="position() = $count">
<xsl:value-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
will ouput
ZSBALAJI
ZSSHIVA
Essentially, you can use XPath tokenize function and then take the last token.
You can try and use EXSLT tokenize(string, string?) function to split by '.' on the relevant node, see this for additional info.