Xslt gouping issue - xslt

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>

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>

If attribute exists, remove it and put its value as element's value

I am trying to remove specific attributes and put their values as element values surrounded by #.
My knowledge of XSLT is, unfortunately, so elementary that I could not translate any similar question to something that I could use.
Whatever I put inside
<xsl:template match="#Attr">
</xsl:template>
just deletes the attribute.
In short, XML like:
<Parent>
<Elem1 Attr="Something" OtherAttr="Other">ExistingValue</Elem1>
<Elem2 Attr="SomethingElse" />
</Parent>
should become:
<Parent>
<Elem1 OtherAttr="Other">#Something#</Elem1>
<Elem2>#SomethingElse#</Elem2>
</Parent>
If an element already has a value it should be replaced. Attributes other than one named Attr, if they exist, should be left unchanged. Elements that don't have attribute Attr should be left unchanged.
If an element already has a value it should be replaced.
If you want to modify the element, you must operate on the element, not on the attribute.
Try it this way:
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="*[#Attr]">
<xsl:copy>
<xsl:apply-templates select="#*[not(name()='Attr')]"/>
<xsl:value-of select="concat('#', #Attr, '#')"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Use this XSLT
<?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:template match="*">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[#Attr]">
<xsl:copy>
<xsl:copy-of select="#* except #Attr"/>
<xsl:value-of select="#Attr"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Its been a while since I used XSLT but something like this should work:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml" version="1.0">
<xsl:output encoding="UTF-8" indent="yes" method="xml" standalone="no" omit-xml-declaration="no"/>
<xsl:template match="/">
<xsl:apply-templates select="*"/>
</xsl:template>
<xsl:template match="*">
<xsl:element name="{name()}">
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="*"/>
</xsl:element>
</xsl:template>
<xsl:template match="#*">
<xsl:value-of select="."/>
</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>

Applying consecutive transformations in xslt

I am a newbie to xslt and I have a variable "name" which stores a result of a transformation how can we transform the variable "name" using some other template in same xslt file.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="#* | node()" >
<xsl:variable name="name">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:variable>
</xsl:template>
<xsl:template match="ns1:BP7Locations" >
<xsl:copy>
<xsl:apply-templates select="ns1:Entry">
<xsl:sort select="ns4:Location/ns4:LocationNum" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
In XSLT 2.0 the typical pattern for a multi-phase transformation is
<xsl:variable name="temp1">
<xsl:apply-templates mode="phase1"/>
</xsl:variable>
<xsl:variable name="temp2">
<xsl:apply-templates select="$temp1" mode="phase2"/>
</xsl:variable>
<xsl:apply-templates select="$temp2" mode="phase3"/>
In XSLT 1.0 this isn't allowed, because the variable holds a "result tree fragment" which can only be processed in very limited ways. Nearly every XSLT 1.0 processor implements the exslt:node-set() extension function so you can get around this restriction. The code then becomes:
<xsl:variable name="temp1">
<xsl:apply-templates mode="phase1"/>
</xsl:variable>
<xsl:variable name="temp2">
<xsl:apply-templates select="exslt:node-set($temp1)" mode="phase2"/>
</xsl:variable>
<xsl:apply-templates select="exslt:node-set($temp2)" mode="phase3"/>
You will need to add the namespace xmlns:exslt="http://exslt.org/common" to your stylesheet.
You don't have to use different modes for the different phases of processing, but it helps to avoid hard-to-spot bugs: the template rules for each processing phase should have corresponding mode attributes, and it can also be a good idea to put the rules for each mode in a separate stylesheet module.
For an example, you can consider this XML:
<root>
<a>value</a>
</root>
And this XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exslt="http://exslt.org/common" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="root">
<xsl:variable name="a">
<xsl:apply-templates select="a" mode="mode1"/>
</xsl:variable>
<xsl:apply-templates select="exslt:node-set($a)/a" mode="mode2"/>
</xsl:template>
<xsl:template match="a" mode="mode1">
<a><xsl:value-of select="'mode1 called'"/></a>
</xsl:template>
<xsl:template match="a" mode="mode2">
<a><xsl:value-of select="concat(., ' mode2 called')"/></a>
</xsl:template>
</xsl:stylesheet>
This produces the following as output:
<?xml version="1.0" encoding="utf-8"?>
<a xmlns:exslt="http://exslt.org/common">mode1 called mode2 called</a>
The XSLT's first template has a variable a which stores the data after processing element <a> and then the xsl:apply-templates processes the data in the variable a again. Here the #mode on xsl:template differentiates the second and the third templates.

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>