i would like to get distinct nodes from my xml on multiple levels. Can anyone please give me some hints how to do this? The methods i googled (Muenchian method, for-each-group) were explained with single grouping keys and plain hierarchy.
Here's an example of my xml:
<persons>
<person>
<name>Tom</name>
<age>20</age>
<mails>
<mail>x#test.com</mail>
<mail>y#test.com</mail>
</mails>
</person>
<person>
<name>Tom</name>
<age>20</age>
<mails>
<mail>y#test.com</mail>
<mail>z#test.com</mail>
</mails>
</person>
</persons>
I would like to have distinct person nodes based on name and age, and also a distinct set of mail-nodes. So for the example the desired output would be:
<persons>
<person>
<name>Tom</name>
<age>20</age>
<mails>
<mail>x#test.com</mail>
<mail>y#test.com</mail>
<mail>z#test.com</mail>
</mails>
</person>
</persons>
Is there a way to do this? Thanks a lot in advance.
I. XSLT 1.0 solution:
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:strip-space elements="*"/>
<xsl:key name="kPersByNameAndAge" match="person"
use="concat(name, '+', age)"/>
<xsl:key name="kmailByNameAndAge" match="mail"
use="concat(../../name, '+', ../../age)"/>
<xsl:key name="kmailByNameAndAgeAndVal" match="mail"
use="concat(../../name, '+', ../../age, '+', .)"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<persons>
<xsl:apply-templates select=
"person[generate-id()
=
generate-id(key('kPersByNameAndAge',
concat(name, '+', age)
)
[1]
)
]
"/>
</persons>
</xsl:template>
<xsl:template match="mails">
<mails>
<xsl:apply-templates select=
"key('kmailByNameAndAge', concat(../name, '+', ../age))
[generate-id()
=
generate-id(key('kmailByNameAndAgeAndVal',
concat(../../name, '+', ../../age, '+', .)
)
[1]
)
]
"/>
</mails>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<persons>
<person>
<name>Tom</name>
<age>20</age>
<mails>
<mail>x#test.com</mail>
<mail>y#test.com</mail>
</mails>
</person>
<person>
<name>Tom</name>
<age>20</age>
<mails>
<mail>y#test.com</mail>
<mail>z#test.com</mail>
</mails>
</person>
</persons>
produces the wanted, correct result:
<persons>
<person>
<name>Tom</name>
<age>20</age>
<mails>
<mail>x#test.com</mail>
<mail>y#test.com</mail>
<mail>z#test.com</mail>
</mails>
</person>
</persons>
II. XSLT 2.0 solution
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kmailByNameAndAge" match="mail"
use="concat(../../name, '+', ../../age)"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<persons>
<xsl:for-each-group select="person" group-by="concat(name, '+', age)">
<xsl:apply-templates select="."/>
</xsl:for-each-group>
</persons>
</xsl:template>
<xsl:template match="mails">
<mails>
<xsl:for-each-group select=
"key('kmailByNameAndAge', concat(../name, '+', ../age))"
group-by="concat(../../name, '+', ../../age, '+', .)"
>
<xsl:apply-templates select="."/>
</xsl:for-each-group>
</mails>
</xsl:template>
</xsl:stylesheet>
Related
When I'm in : <xsl:template match="listOfPerson/person">
for person of id "A", is it possible to retrieve his information that is stored in another element here it's inside the element data
xml :
<root>
<data>
<person id="A">
<name> Anna </name>
<age> 1 </age>
</person>
<person id="B">
<name> Banana </name>
<age> 1 </age>
</person>
</data>
<listOfPerson>
<person>
<id>A</id>
</person>
<person>
<id>B</id>
</person>
</listOfPerson>
</root>
my current xsl :
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" indent="yes" />
<xsl:template match="root">
<xsl:apply-templates select="listOfPerson/person"/>
</xsl:template>
<xsl:template match="listOfPerson/person">
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>
current output :
A
B
desired output :
Anna 1
Banana 1
XSLT has a built-in key mechanism for resolving cross-references. Consider the following example:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:key name="person" match="data/person" use="#id" />
<xsl:template match="/root">
<xsl:for-each select="listOfPerson/person">
<xsl:variable name="data" select="key('person', id)" />
<xsl:value-of select="$data/name" />
<xsl:text> </xsl:text>
<xsl:value-of select="$data/age" />
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Applied to your input example, the result will be:
Anna 1
Banana 1
I have this XML
<participants>
<event>Seminar</event>
<location>City somewhere</location>
<first_name>Carl</first_name>
<last_name>Smith</last_name>
<first_name>John</first_name>
<last_name>Somebody</last_name>
<first_name>Lisa</first_name>
<last_name>Lint</last_name>
<first_name>Gabriella</first_name>
<last_name>Whowho</last_name>
</participants>
Which I would need to be transformed into this:
<participants>
<event>Seminar</event>
<location>City somewhere</location>
<persons>
<person>
<given_name>Carl</given_name>
<surname>Smith</surname>
</person>
<person>
<given_name>John</given_name>
<surname>Somebody</surname>
</person>
<person>
<given_name>Lisa</given_name>
<surname>Lint</surname>
</person>
<person>
<given_name>Gabriella</given_name>
<surname>Whowho</surname>
</person>
</persons>
</participants>
The number of persons can be any number, sometimes there might be empty elements (if both first and last names would be empty, then the person would not be created.
I have hard time getting started with this transformation.
If you simply process the first_name elements and use XPath navigation to select its sibling last_name you get
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="participants">
<xsl:copy>
<xsl:apply-templates select="*[not(self::first_name | self::last_name)]"/>
<xsl:apply-templates select="first_name" mode="person"/>
</xsl:copy>
</xsl:template>
<xsl:template match="first_name" mode="person">
<xsl:variable name="surname" select="following-sibling::last_name[1]"/>
<xsl:if test="normalize-space() and normalize-space($surname)">
<person>
<xsl:apply-templates select=". | $surname"/>
</person>
</xsl:if>
</xsl:template>
<xsl:template match="first_name">
<given_name>
<xsl:apply-templates/>
</given_name>
</xsl:template>
<xsl:template match="last_name">
<surname>
<xsl:apply-templates/>
</surname>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/94AbWAW/1
The below is my input xml. I'm trying group by using current-group() function but it is not meeting my requirement, below I have provided the details.
<UsrTimeCardEntry>
<Code>1<Code>
<Name>TC1</Name>
<Person>
<Code>074</Code>
</Person>
</UsrTimeCardEntry>
<UsrTimeCardEntry>
<Code>2<Code>
<Name>TC2</Name>
<Person>
<Code>074</Code>
</Person>
</UsrTimeCardEntry>
I want to group it by Person/Code so that it looks like this
<Person Code="074">
<UsrTimeCardEntry>
<Code>1</Code>
<Name>TC1</Name>
</UsrTimeCardEntry>
<UsrTimeCardEntry>
<Code>2</Code>
<Name>TC2</Name>
</UsrTimeCardEntry>
</Person>
For which I'm using the below xslt, but it is again copying the Person which I don't want, what it that I'm missing here, I tried using current-group() except and not[child::Person] but that too did not work.
<xsl:template match="businessobjects">
<xsl:for-each-group select="UsrTimeCardEntry" group-by="Person/Code">
<Person Code="{current-grouping-key()}">
<xsl:copy-of select="current-group()"></xsl:copy-of>
</Person>
</xsl:for-each-group>
</xsl:template>
Instead of using xsl:copy-of here, use xsl:apply-templates, then you can add a template to ignore the Person node
<xsl:template match="Person" />
This assumes you are also using the identity template to copy all other nodes normally.
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="businessobjects">
<xsl:for-each-group select="UsrTimeCardEntry" group-by="Person/Code">
<Person Code="{current-grouping-key()}">
<xsl:apply-templates select="current-group()" />
</Person>
</xsl:for-each-group>
</xsl:template>
<xsl:template match="Person" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Suggest for grouping the info from two XMLs. In my post, based on employees shift and Route number other information are to be grouped. See the required OutPut for quick review. (XSLT 2)
Input XML (EmpShift.xml is input to the XSLT):
<MPS>
<emp>
<name>Rudramuni TP</name>
<ID>MAC000424</ID>
<Shift>Second</Shift>
</emp>
<emp>
<name>Mohan</name>
<ID>MAC000425</ID>
<Shift>Second</Shift>
</emp>
<emp>
<name>Vijay</name>
<ID>MAC000426</ID>
<Shift>First</Shift>
</emp>
<emp>
<name>Shankar</name>
<ID>MAC000427</ID>
<Shift>First</Shift>
</emp>
<emp>
<name>Prasad</name>
<ID>MAC000428</ID>
<Shift>Second</Shift>
</emp>
</MPS>
Second Input (Called XML EmpAddressInfo.xml):
<Addess_Info>
<emp>
<name>Rudramuni TP</name>
<ID>MAC000424</ID>
<address>Nandini Layout, B-96</address>
<Route_No>10</Route_No>
</emp>
<emp>
<name>Mohan</name>
<ID>MAC000425</ID>
<address>Banashankari</address>
<Route_No>11</Route_No>
</emp>
<emp>
<name>Vijay</name>
<ID>MAC000426</ID>
<address>Marathahalli</address>
<Route_No>10</Route_No>
</emp>
<emp>
<name>Shankar</name>
<ID>MAC000427</ID>
<address>Yelahanka</address>
<Route_No>11</Route_No>
</emp>
<emp>
<name>Prasad</name>
<ID>MAC000428</ID>
<address>Marathahalli</address>
<Route_No>10</Route_No>
</emp>
</Addess_Info>
XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:variable name="varDocAdress" select="document('EmpAddressInfo.xml')"/>
<xsl:template match="MPS">
<xsl:for-each-group select="//emp" group-by="Shift">
<!--xsl:for-each-group select="current-group()" group-by="$varDocAdress/Route_No"-->
<Shift>
<Shift-Name><xsl:value-of select="current-grouping-key()"/></Shift-Name>
<empDetails><xsl:apply-templates select="current-group()/name|current-group()/ID"/></empDetails>
</Shift>
<!--/xsl:for-each-group-->
</xsl:for-each-group>
</xsl:template>
<xsl:template match="name">
<name1><xsl:apply-templates/></name1>
</xsl:template>
<xsl:template match="ID">
<ID><xsl:apply-templates/></ID>
</xsl:template>
</xsl:stylesheet>
Required Result:
<MPS>
<Shift>
<Shift-Name>Second</Shift-Name>
<Route_No><title>10</title>
<empDetails>
<name1>Rudramuni TP</name1><ID>MAC000424</ID><address>Nandini Layout, B-96</address>
</empDetails>
<empDetails>
<name1>Prasad</name1><ID>MAC000428</ID><address>Marathahalli</address>
</empDetails>
</Route_No>
<Route_No><title>11</title>
<empDetails>
<name1>Mohan</name1><ID>MAC000425</ID><address>Banashankari</address>
</empDetails>
</Route_No>
</Shift>
<Shift>
<Shift-Name>First</Shift-Name>
<Route_No><title>10</title>
<empDetails>
<name1>Vijay</name1><ID>MAC000426</ID><address>Marathahalli</address>
</empDetails>
</Route_No>
<Route_No><title>11</title>
<empDetails>
<name1>Shankar</name1><ID>MAC000427</ID><address>Yelahanka</address>
</empDetails>
</Route_No>
</Shift>
</MPS>
Try this XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:variable name="varDocAdress" select="document('EmpAddressInfo.xml')"/>
<xsl:template match="MPS">
<xsl:copy>
<xsl:for-each-group select="//emp" group-by="Shift">
<Shift>
<Shift-Name>
<xsl:value-of select="current-grouping-key()"/>
</Shift-Name>
<xsl:for-each-group select="$varDocAdress/Addess_Info/emp[ID = current-group()/ID]" group-by="Route_No">
<Route_No>
<title><xsl:value-of select="current-grouping-key()"/></title>
<xsl:for-each select="current-group()">
<empDetails>
<xsl:apply-templates select="current()"/>
</empDetails>
</xsl:for-each>
</Route_No>
</xsl:for-each-group>
</Shift>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match="emp">
<name1>
<xsl:value-of select="name"/>
</name1>
<xsl:copy-of select="ID | address"/>
</xsl:template>
</xsl:stylesheet>
I would use a key for the cross-reference:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="xs mf"
version="2.0">
<xsl:param name="address-url" select="'test2014112603.xml'"/>
<xsl:variable name="address-doc" select="doc($address-url)"/>
<xsl:output indent="yes"/>
<xsl:key name="emp-by-id" match="emp" use="ID"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="MPS">
<xsl:copy>
<xsl:for-each-group select="emp" group-by="Shift">
<Shift>
<Shift-Name><xsl:value-of select="current-grouping-key()"/></Shift-Name>
<xsl:for-each-group select="current-group()" group-by="key('emp-by-id', ID, $address-doc)/Route_No">
<Route_No>
<title><xsl:value-of select="current-grouping-key()"/></title>
<xsl:apply-templates select="current-group()"/>
</Route_No>
</xsl:for-each-group>
</Shift>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
<xsl:template match="emp">
<empDetails>
<xsl:apply-templates select="name, ID, key('emp-by-id', ID, $address-doc)/address"/>
</empDetails>
</xsl:template>
<xsl:template match="name">
<name1><xsl:apply-templates/></name1>
</xsl:template>
</xsl:stylesheet>
I would suggest you simplify this by using a key:
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:variable name="varDocAdress" select="document('EmpAddressInfo.xml')"/>
<xsl:key name="emp-by-id" match="emp" use="ID" />
<xsl:template match="/MPS">
<xsl:for-each-group select="emp" group-by="Shift">
<Shift>
<Shift-Name><xsl:value-of select="current-grouping-key()"/></Shift-Name>
<xsl:for-each-group select="current-group()" group-by="key('emp-by-id', ID, $varDocAdress)/Route_No">
<Route_No>
<title><xsl:value-of select="current-grouping-key()"/></title>
<xsl:apply-templates select="current-group()"/>
</Route_No>
</xsl:for-each-group>
</Shift>
</xsl:for-each-group>
</xsl:template>
<xsl:template match="emp">
<empDetails>
<xsl:copy-of select="name | ID | key('emp-by-id', ID, $varDocAdress)/address "/>
</empDetails>
</xsl:template>
</xsl:stylesheet>
input
<person>
<address>
<city>NY</city>
<state></state>
<country>US</country>
</address>
<other>
<gender></gender>
<age>22</age>
<weight/>
</other>
</person>
i only want to remove empty elements from the 'other' node, also the tags under 'other' are not fixed.
output
<person>
<address>
<city>NY</city>
<state></state>
<country>US</country>
</address>
<other>
<age>22</age>
</other>
</person>
I'm new to xslt so pls help..
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:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="other/*[not(node())]"/>
</xsl:stylesheet>
when applied on the provided XML document:
<person>
<address>
<city>NY</city>
<state></state>
<country>US</country>
</address>
<other>
<gender></gender>
<age>22</age>
<weight/>
</other>
</person>
produces the wanted, correct result:
<person>
<address>
<city>NY</city>
<state/>
<country>US</country>
</address>
<other>
<age>22</age>
</other>
</person>
Explanation:
The identity rule copies "as-is" every matched node, for which it is selected for execution.
The only template that overrides the identity templates matches any element that is a child of other and has no children nodes (is empty). As this template has no body, this effectively "deletes" the matched element.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>
<xsl:template match="/">
<xsl:apply-templates select="person"/>
</xsl:template>
<xsl:template match="person">
<person>
<xsl:copy-of select="address"/>
<xsl:apply-templates select="other"/>
</person>
</xsl:template>
<xsl:template match="other">
<xsl:for-each select="child::*">
<xsl:if test=".!=''">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>