XSLT: how to reverse the tree? - xslt

I need to transform a document like this:
<root>
<products>
<ProductInfo>
<ProductID>0</ProductID>
<ProductName>Hello world!</ProductName>
</ProductInfo>
<M>
<ModelInfo>
<ModelID>0</ModelID>
<ModelName>Hello world!</ModelName>
</ModelInfo>
</M>
</products>
</root>
Into this:
<root>
<products>
<M>
<ModelInfo>
<ModelName>Hello world!</ModelName>
<ModelID>0</ModelID>
</ModelInfo>
</M>
<ProductInfo>
<ProductName>Hello world!</ProductName>
<ProductID>0</ProductID>
</ProductInfo>
</products>
</root>
So all the tags in the output should be in reversed order.
I need this for testing: I need to ensure that some external application accepts the tags in any order; and also I need it to test that my XML Schema allows tags in any order.

Not reverse the tree, but reverse the order of sibling branches (at all levels):
XSLT 1.0
<xsl:stylesheet version="1.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:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="node()">
<xsl:sort select="position()" data-type="number" order="descending"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Related

Transform elements with some properties using group by attribute value

I have a requirement to transform below XML
<XML>
<Obj1 attr1="value1" attr2="value2" attr="10"/>
<Test1 tatt1="tvalue1" tatt2="tvalue2" attr="10"/>
<Obj1 attr1="value11" attr2="value21" attr="101"/>
<Test1 tatt1="tvalue11" tatt2="tvalue21" attr="101"/>
<Obj1 attr1="value12" attr2="value22" attr="102"/>
<Test1 tatt1="tvalue12" tatt2="tvalue22" attr="102"/>
</XML>
I want transformed XML like
<XML>
<Obj1 attr1="value1" attr2="value2" attr="10" tatt1="tvalue1"/>
<Obj1 attr1="value11" attr2="value21" attr="101" tatt1="tvalue11"/>
<Obj1 attr1="value12" attr2="value22" attr="102" tatt1="tvalue12"/>
</XML>
I have achieved it through normal pattern matching and finding the matching attribute value in all other elements. I doubt about the performance. So wanted to check if it can be done using group-by attribute name and combining attributes from all such elements into one.
I want to merge contents (all attributes of Obj1 and selected attributes from matching elements) of all matching elements having attr= into transformed XML.
Try this XSLT-1.0 stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="xml" indent="yes"/>
<xsl:key name="tests" match="Test1" use="#attr" />
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()"> <!-- Identity template: copies all nodes to the output - unless a more specific template matches -->
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Obj1"> <!-- Modifies all 'Obj1' elements -->
<xsl:copy>
<xsl:copy-of select="#*|key('tests',#attr)/#tatt1" />
</xsl:copy>
</xsl:template>
<xsl:template match="Test1" /> <!-- Removes all 'Test1' elements from the output -->
</xsl:stylesheet>
The output should be as desired. And the use of the xsl:key will improve the performance.
AFAICT, you could so simply:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/XML">
<xsl:copy>
<xsl:for-each select="Obj1">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:copy-of select="following-sibling::Test1[1]/#tatt1"/>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
If you want do it by matching the value of the attr attribute instead of by position, then it would become:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:key name="test" match="Test1" use="#attr"/>
<xsl:template match="/XML">
<xsl:copy>
<xsl:for-each select="Obj1">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:copy-of select="key('test', #attr)/#tatt1"/>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Xslt copy element to existing or create new if not exists

Pretty new to xml transformations and i'm stuck at (might be for you) pretty easy task.
Let's suggest we have source:
<root>
<someValue>123</someValue>
</root>
It should be transformed into:
<root>
<additional>
<someValue>123</someValue>
</additional>
</root>
But if we have this as a source:
<root>
<additional>
<b>something</b>
</additional>
<someValue>123</someValue>
</root>
we should move someValue to existing additional, i.e.:
<root>
<additional>
<b>something</b>
<someValue>123</someValue>
</additional>
</root>
Keep in mind that there can be other elements at a level with same behavior (moved under additional).
Well, working example is much appreciated, but if it is accompanied by small description of how it works that would be fantastic (i prefer be tought to fish, rather than just being fed with it).
One possible approach would be to add a additional wrapper as a child of root, and remove the existing additional wrapper - so its children move up to become children of root (or rather children of the added additional wrapper):
XSLT 1.0
<xsl:stylesheet version="1.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="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/root">
<xsl:copy>
<additional>
<xsl:apply-templates/>
</additional>
</xsl:copy>
</xsl:template>
<xsl:template match="additional">
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>
Keep in mind that there can be other elements at a level with same
behavior (moved under additional).
This stylesheet:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="root[additional|someValue]">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<additional>
<xsl:apply-templates select="additional/*|someValue"/>
</additional>
<xsl:apply-templates select="node()[not(self::additional|self::someValue)]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
With this input:
<root>
<additional>
<b>something</b>
</additional>
<someValue>123</someValue>
<anotherValue>keep</anotherValue>
</root>
Output:
<root>
<additional>
<b>something</b>
<someValue>123</someValue>
</additional>
<anotherValue>keep</anotherValue>
</root>
Do note: just one rule to override the identity transformation. Only process root meeting the conditions (someValue or additional childs). Copying root, applying templates to attributes (to further process), wrapping with an additional element the result of applying templates to additional's childs and root's someValue childs. Finally, applying templates to root's childs, which are not additional nor someValue.
I ended up with following:
<xsl:stylesheet version="1.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="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/root/additional"/>
<xsl:template match="/root">
<xsl:copy>
<additional>
<xsl:copy-of select="someValue"/>
<xsl:copy-of select="additional/*"/>
</additional>
</xsl:copy>
</xsl:template>
Here we strip original additional and then create it from scratch copying there needed someValue and original content from source (additional/*)
Use for version xslt 2.0
<?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"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output method="xml" omit-xml-declaration="no" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node() except someValue"/>
</xsl:copy>
</xsl:template>
<xsl:template match="additional">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
<xsl:copy-of select="following-sibling::someValue"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Identity Transformation - compare attributes and limit output

I want to compare the attributes of two xml-Files and identity transform the input file in the same step. The output xml should only contain elements whose attributes occur in the comparing xml. As shown in the given example, the last concept node should not be outputted, as there is no matching attribute in the comparing.xml
input.xml
<navigation
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<facets>
<facet id="d1e12000000000000000000000011111">
<title xml:lang="en">sometxt</title>
<title xml:lang="de">eintxt</title>
<concepts>
<concept id="d1e12000000000000000000000000000">
<title xml:lang="en">sometxt</title>
<title xml:lang="de">eintxt</title>
<concepts>
<concept id="d1e19000000000000000000000000000">
<title xml:lang="en">sometxt</title>
<title xml:lang="de">eintxt</title>
<concepts>
</concepts>
</concept>
</concepts>
</concept>
</concepts>
</facet>
</facets>
part of comparing.xml with indefinite heading-levels
<foo>
<heading class="d1e12000000000000000000000011111|d1e12000000000000000000000000000">Myheading</heading>
<chapter>
<heading class="d1e12000000000000000000000011111|d1e12000000000000000000000000000">myheading</heading>
<operation>
<heading class="d1e12000000000000000000000011111|d1e12000000000000000000000000000">another heading</heading>
</operation>
</chapter>
desired output.xml with only applicable id's
<nav:navigation
xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:nav="http://www.nav.de/">
<nav:facets>
<nav:facet id="d1e12000000000000000000000011111">
<nav:title xml:lang="en">sometxt</nav:title>
<nav:title xml:lang="de">eintxt</nav:title>
<nav:concepts>
<nav:concept id="d1e12000000000000000000000000000">
<nav:title xml:lang="en">sometxt</nav:title>
<nav:title xml:lang="de">eintxt</nav:title>
<nav:concepts>
</nav:concepts>
</nav:concept>
</nav:concepts>
</nav:facet>
</nav:facets>
my xsl so far
<xsl:stylesheet 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:nav="http://www.nav.de/"
version="2.0" >
<xsl:output method="xml" indent="yes" encoding="utf-8"/>
<xsl:variable name="docu" select="document(comparing.xml)"/>
<xsl:template match="*">
<xsl:element name="nav:{name()}" namespace="http://www.nav.de/">
<xsl:copy-of select="namespace::*"/>
<xsl:apply-templates select="node()|#*"/>
</xsl:element>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
EDIT: sorry for posting this in the comment-section. I've tried something along those lines, but it didn't work
<xsl:template match="concept | facet">
<xsl:variable name="foo-id" select="#id"/>
<xsl:for-each select="$docu//heading">
<xsl:if test="contains(./#class, $foo-id)">
<xsl:apply-templates/>
</xsl:if>
</xsl:for-each>
</xsl:template>
I would suggest you try it this way:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:nav="http://www.nav.de/">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="comparing-url" select="'comparing.xml'"/>
<xsl:key name="comp" match="#class" use="tokenize(., '\|')" />
<xsl:template match="*">
<xsl:element name="nav:{name()}" >
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="*[#id][not(key('comp', #id, document($comparing-url)))]"/>
</xsl:stylesheet>

Xslt gouping issue

I have an xml like below
<XYZ>
<Name>abc</Name>
<Year>1984</Year>
<Model>qwe</Model>
<Name>abc</Name>
<Year>1987</Year>
<Model>qwe</Model>
<Name>qweqr</Name>
<Year>1977</Year>
<Model>tryet</Model>
</XYZ>
Which needs to be transformed to
<XYZ>
<Name>abc</Name>
<Year>1984</Year>
<Year>1987</Year>
<Model>qwe</Model>
<Name>qweqr</Name>
<Year>1977</Year>
<Model>tryet</Model>
</XYZ>
Here Name can be considered as key
With XSLT 1.0, this can be done using Muenchian Grouping with following XSLT:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:key name="names" match="Name" use="text()"/>
<xsl:key name="models" match="Model" use="text()"/>
<xsl:template match="XYZ">
<XYZ>
<xsl:for-each select="//Name[generate-id()=generate-id(key('names',text())[1])]">
<xsl:variable name="current" select="."/>
<xsl:apply-templates select="."/>
<xsl:apply-templates select="//Name[.=$current]/following-sibling::Year[1]"/>
<xsl:for-each select="//Name[.=$current]/following-sibling::Model[1]
[generate-id()=generate-id(key('models',text())[1])]">
<xsl:apply-templates select="."/>
</xsl:for-each>
</xsl:for-each>
</XYZ>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:transform>
Result:
<XYZ>
<Name>abc</Name>
<Year>1984</Year>
<Year>1987</Year>
<Model>qwe</Model>
<Name>qweqr</Name>
<Year>1977</Year>
<Model>tryet</Model>
</XYZ>
The duplicate nodes for Year and Model are removed as only unique values are selected in the xsl:for-each:
<xsl:for-each select="//Name[generate-id()=generate-id(key('names',text())[1])]">
The unique Name is copied, then all years that have a preceding Name with the value of the current unique Name:
<xsl:apply-templates select="//Name[.=$current]/following-sibling::Year[1]"/>
Then, the corresponding unique Model nodes are copied with a second xsl:for-each selecting only unique following Model nodes:
<xsl:for-each select="//Name[.=$current]/following-sibling::Model[1]
[generate-id()=generate-id(key('models',text())[1])]">
As there are already many answers on Stackoverflow for XSLT grouping using the Muenchian method, I just recommend the detailed explanation in this article by Jeni Tennison http://www.jenitennison.com/xslt/grouping/muenchian.xml.
As additional reference for XSLT grouping you can have a look at http://www.dpawson.co.uk/xsl/sect2/N4486.html
Update: As suggested as comment, this won't work in case of duplicate model values for different name values. Following adjusted XSLT
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" omit-xml-declaration="yes" encoding="UTF-8 indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:key name="names" match="Name" use="text()"/>
<xsl:template match="XYZ">
<XYZ>
<xsl:for-each select="//Name[generate-id()=generate-id(key('names',text())[1])]">
<xsl:variable name="current" select="."/>
<xsl:apply-templates select="."/>
<xsl:apply-templates select="//Name[.=$current]/following-sibling::Year[1]"/>
<xsl:for-each select="//Name[.=$current]/following-sibling::Model[1]
[not(.= preceding-sibling::Name[.=$current]
/preceding-sibling::Name[.=$current]
/following-sibling::Model)]">
<xsl:apply-templates select="."/>
</xsl:for-each>
</xsl:for-each>
</XYZ>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:transform>
works for the example XML provided in OP as well as the example XML suggested in the comment - only unique name values with optional multiple years and unique model values for a name, but the same model can also be listed for a different name.
I think you can use this stylesheet, which uses keys to select Year and Model(groups the Models too based on their Name and then with their value) based on their preceding Name(after grouping Names):
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="Name" match="Name" use="."/>
<xsl:key name="Year" match="Year" use="preceding::Name[1]"/>
<xsl:key name="Model" match="Model" use="preceding::Name[1]"/>
<xsl:template match="XYZ">
<xsl:copy>
<xsl:for-each select="Name[count(. | key('Name', .)[1]) = 1]">
<xsl:copy-of select="."/>
<xsl:copy-of select="key('Year', .)"/>
<xsl:copy-of select="key('Model', .)[not(. = preceding::Model[preceding::Name[1] = current()])]"/>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

xslt extract and sort leaf nodes by name - unexpected result

I wanted to extract leaf nodes and have them sorted.
My XSL gives unexpected results. How can I solve this?
Input
<root>
<b>
<b33 zzz="2" fff="3"></b33>
<b11></b11>
<b22></b22>
</b>
<a>
<a27></a27>
<a65 fff="0" eee="2" zzz="10"></a65>
<a11></a11>
</a>
</root>
Xsl
<?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" omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<root>
<xsl:call-template name="leafnodes"/>
</root>
</xsl:template>
<xsl:template match="*[not(*)]|#*" name="leafnodes">
<xsl:copy>
<xsl:apply-templates select="node()">
<xsl:sort select="name()"/>
</xsl:apply-templates>
<xsl:apply-templates select="#*">
<xsl:sort select="name()"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Output (I would expected it to be sorted, it is not)
<root>
<b33 fff="3" zzz="2" />
<b11 />
<b22 />
<a27 />
<a65 eee="2" fff="0" zzz="10" />
<a11 />
</root>
I would expect the nodes in the order a11, a27, a65, b11, b22, b33.
If I leave out the '[not(*)]', the xsl takes all nodes and sorts them properly.
How can this be solved?
To output all element which have no child sorted by name and the attributes also sorted by name. Try this;
<?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" omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<root>
<xsl:apply-templates select="//*[not(*)]">
<xsl:sort select="name()"/>
</xsl:apply-templates>
</root>
</xsl:template>
<xsl:template match="*|#*">
<xsl:copy>
<xsl:apply-templates select="#*" >
<xsl:sort select="name()"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Which will generate following output:
<root>
<a11/>
<a27/>
<a65 eee="2" fff="0" zzz="10"/>
<b11/>
<b22/>
<b33 fff="3" zzz="2"/>
</root>