How can you convert
<person>
<personFirstName>FirstName</personFirstName>
<personLastName>LastName</personLastName>
<personAge>40</personAge>
</person>
to
<person>
<name>
<first>FirstName</first>
<last>LastName</last>
</name>
<age>40</age>
</person>
using XSLT, moreover, if the input XML is a collection of person nodes, like so:
<persons>
<person>
...
</person>
</persons>
It should be very easy. You can try to:
match person then open name, apply templates, close name, open age, get value from personAge, close age
match personFirstName, open first, get value, close first
same as personFirstName for personLastName
I think 3 templates wihtout loops should be enough. Try it!
The key is the identity transform and overriding it when needed.
Sample XML
<persons>
<person>
<personFirstName>FirstName</personFirstName>
<personLastName>LastName</personLastName>
<personAge>40</personAge>
</person>
<person>
<personFirstName>FirstName2</personFirstName>
<personLastName>LastName2</personLastName>
<personAge>100</personAge>
</person>
</persons>
Sample XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output 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="person">
<person>
<name>
<first><xsl:apply-templates select="personFirstName"/></first>
<last><xsl:apply-templates select="personLastName"/></last>
</name>
<age><xsl:apply-templates select="personAge"/></age>
</person>
</xsl:template>
<xsl:template match="personFirstName|personLastName|personAge">
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>
OUTPUT
<persons>
<person>
<name>
<first>FirstName</first>
<last>LastName</last>
</name>
<age>40</age>
</person>
<person>
<name>
<first>FirstName2</first>
<last>LastName2</last>
</name>
<age>100</age>
</person>
</persons>
A "push-style" solution:
<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="personFirstName">
<name>
<xsl:apply-templates mode="renameWrapped"
select=".|../personLastName"/>
</name>
</xsl:template>
<xsl:template match="personFirstName" mode="renameWrapped">
<first><xsl:apply-templates/></first>
</xsl:template>
<xsl:template match="personLastName" mode="renameWrapped">
<last><xsl:apply-templates/></last>
</xsl:template>
<xsl:template match="personAge">
<age><xsl:apply-templates/></age>
</xsl:template>
<xsl:template match="personLastName"/>
</xsl:stylesheet>
when applied on this XML document:
<persons>
<person>
<personFirstName>FirstName</personFirstName>
<personLastName>LastName</personLastName>
<personAge>40</personAge>
</person>
<person>
<personFirstName>FirstName2</personFirstName>
<personLastName>LastName2</personLastName>
<personAge>100</personAge>
</person>
</persons>
the wanted, correct result is produced:
<persons>
<person>
<name>
<first>FirstName</first>
<last>LastName</last>
</name>
<age>40</age>
</person>
<person>
<name>
<first>FirstName2</first>
<last>LastName2</last>
</name>
<age>100</age>
</person>
</persons>
Explanation:
Using and overriding the identity rule/template for wrapping and renaming of elements.
The elements to be wrapped are renamed in mode renameWrapped.
The personAge element is renamed in a non-moded template that overrides the identity rule for elements named personAge.
Related
Here is my XML:
<persons>
<person>
<name>Jason</name>
</person>
<person>
<name>John</name>
</person>
<person>
<name>Mary</name>
</person>
<person>
<name>Jennifer</name>
</person>
</persons>
Using XSLT 1.0 I need to find the person with the longest name. What is the best way to do this?
Try:
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="/persons">
<xsl:for-each select="person">
<xsl:sort select="string-length(name)" data-type="number" order="ascending"/>
<xsl:if test="position()=last()">
<xsl:copy-of select="name"/>
</xsl:if>
</xsl:for-each>
</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>
Let's assume, you have the xml below. The goal is to group by FirstName and export the Person into different xml files. Each output xml files should only contain up to X different FirstName.
Below is an example of the desired transformation with X = 3
XML input:
<People>
<Person>
<FirstName>John</FirstName>
<LastName>Doe</LastName>
</Person>
<Person>
<FirstName>Jack</FirstName>
<LastName>White</LastName>
</Person>
<Person>
<FirstName>Mark</FirstName>
<LastName>Wall</LastName>
</Person>
<Person>
<FirstName>John</FirstName>
<LastName>Ding</LastName>
</Person>
<Person>
<FirstName>Cyrus</FirstName>
<LastName>Ding</LastName>
</Person>
<Person>
<FirstName>Megan</FirstName>
<LastName>Boing</LastName>
</Person>
</People>
XML output 1 with 3 different FirstName
<People>
<Person>
<FirstName>John</FirstName>
<LastName>Doe</LastName>
</Person>
<Person>
<FirstName>John</FirstName>
<LastName>Ding</LastName>
</Person>
<Person>
<FirstName>Jack</FirstName>
<LastName>White</LastName>
</Person>
<Person>
<FirstName>Mark</FirstName>
<LastName>Wall</LastName>
</Person>
</People>
XML output 2 with the 2 remaining FirstName
<People>
<Person>
<FirstName>Cyrus</FirstName>
<LastName>Ding</LastName>
</Person>
<Person>
<FirstName>Megan</FirstName>
<LastName>Boing</LastName>
</Person>
</People>
It seems to me that the muenchian grouping can be used along with the to produce multiple output files. However, the core question is where we can set a threshold in number of person before exporting to a new file?
Here is an example of doing it in two steps with XSLT 2.0:
<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:param name="n" as="xs:integer" select="3"/>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="People">
<xsl:variable name="groups" as="element(group)*">
<xsl:for-each-group select="Person" group-by="FirstName">
<group>
<xsl:copy-of select="current-group()"/>
</group>
</xsl:for-each-group>
</xsl:variable>
<xsl:for-each-group select="$groups" group-by="(position() - 1) idiv $n">
<xsl:result-document href="group{position()}.xml">
<People>
<xsl:copy-of select="current-group()"/>
</People>
</xsl:result-document>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
I might try to convert to XSLT 1.0 and EXSLT later.
[edit]
Here is an attempt to translate into XSLT 1.0 and EXSLT:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl"
exclude-result-prefixes="exsl"
version="1.0">
<xsl:param name="n" select="3"/>
<xsl:output method="xml" indent="yes"/>
<xsl:key name="person-by-firstname"
match="Person"
use="FirstName"/>
<xsl:template match="People">
<xsl:variable name="groups">
<xsl:for-each select="Person[generate-id() = generate-id(key('person-by-firstname', FirstName)[1])]">
<group>
<xsl:copy-of select="key('person-by-firstname', FirstName)"/>
</group>
</xsl:for-each>
</xsl:variable>
<xsl:for-each select="exsl:node-set($groups)/group[(position() - 1) mod $n = 0]">
<exsl:document href="groupTest{position()}.xml">
<People>
<xsl:copy-of select="Person | following-sibling::group[position() < $n]/Person"/>
</People>
</exsl:document>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
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>
I have a large xml file which contents a lot of self-closed tags. How could remove all them by using XSLT.
eg.
<?xml version="1.0" encoding="utf-8" ?>
<Persons>
<Person>
<Name>user1</Name>
<Tel />
<Mobile>123</Mobile>
</Person>
<Person>
<Name>user2</Name>
<Tel>456</Tel>
<Mobile />
</Person>
<Person>
<Name />
<Tel>123</Tel>
<Mobile />
</Person>
<Person>
<Name>user4</Name>
<Tel />
<Mobile />
</Person>
</Persons>
I'm expecting the result:
<?xml version="1.0" encoding="utf-8" ?>
<Persons>
<Person>
<Name>user1</Name>
<Mobile>123</Mobile>
</Person>
<Person>
<Name>user2</Name>
<Tel>456</Tel>
</Person>
<Person>
<Tel>123</Tel>
</Person>
<Person>
<Name>user4</Name>
</Person>
</Persons>
Note: there are thousands of different elements, how can I programmatically remove all the self-closed tags. Another question is how to remove the empty element such as <name></name> as well.
Can anyone help me on this? Many thanks.
The self-closed tags are equivalent to empty tags. You can remove all empty tags, but you have no way of knowing whether they were self-closed in the input XML or not (<tag/> and <tag></tag> are indistinguishable).
<!-- the identity template copies everything that has no special handler -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<!-- special handler for elements that have no child nodes:
they are removed by this empty template -->
<xsl:template match="*[not(node())]" />
If elements that contain whitespace only are "empty" by your definition as well, then replace the second template with:
<xsl:template match="*[normalize-space() = '']" />
From the XML point of view, there is no difference between "self-closed" element like and empty element like (see spec).
Here is a transformation to strip all empty elements:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" encoding="utf-8" />
<xsl:strip-space elements="*" />
<xsl:template match="#*|node()">
<xsl:if test=".!=''">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
You might want to check if they are required. It should look something like this if they are: use="required". Also check if they are: type="nonEmptyString".
You can remove all empty elements - ones that do not have nested elements and attributes declared. If this solution works for you you can do following:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="*">
<xsl:if test="string(.) != '' or descendant-or-self::*/#*[string(.)]">
<xsl:element name="{name()}" >
<xsl:copy-of select="#*[string(.)]"/>
<xsl:apply-templates select="* | text()" />
</xsl:element>
</xsl:if>
</xsl:template>
<xsl:template match="text()">
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>
The reason to post this answer is,
that you haven't accepted any of the
existing answers yet.
Well. This is very simple XSLT challenge. Just match a node with text data as null and close the template tag, so that, the node will not appear in the output.like this, <xsl:template match=*[.='']/> add it along with your identity template. Similar to the way Tomolak has nailed.
The problem with this approach is, it deletes even your parent node (<Person/> tag for example) if it null.
If this is your xml:
<Persons>
<Person>
<data>text</data>
<data2>text</data2>
<data3/>
</Person>
<Person/>
</Persons>
From the above xml even the tag is removed. So the output xml will be:
<Persons>
<Person>
<data>text</data>
<data2>text</data2>
</Person>
</Persons>
If you want to avoid that, then add an exception.
<xsl:template match="*[name()!='Person' and not(node())]"/>
Add it your identity template. Your XSLT will be:
<?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="xml" indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[name()!='Person' and not(node())]"/>
</xsl:stylesheet>
And the output xml will be:
<Persons>
<Person>
<data>text</data>
<data2>text</data2>
</Person>
<Person/>
</Persons>