XSLT: remove empty elements under specific node - xslt

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>

Related

Is it possible to match attribute from another element and retrieve its content?

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

How to find node value with longest string length

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>

Insert list of elements inside XML tags

I am trying to obtain a list of all the elements with values that aren't in the (Line 1, Line2), and then insert them into the tags similar to the test.
Right now I can retrieve all the elements, but I'm having trouble restricting this to just my desired values. And then I'm unsure how to match and do a for each on elements outside my match criteria. Any advice would be greatly appreciated!
Given the Following XML:
<?xml version="1.0" encoding="UTF-8"?>
<Request>
<Header>
<Line1>Element1</Line1>
<Line2>Element2</Line2>
</Header>
<ElementControl>
<Update>
<Element>test</Element>
</Update>
</ElementControl>
<Member>
<Identifier>123456789</Identifier>
<Contact>
<Person>
<Gender>MALE</Gender>
<Title>Mr</Title>
<Name>JOHN DOE</Name>
</Person>
<HomePhone/>
<eMailAddress/>
<ContactAddresses>
<Address>
<AddressType>POS</AddressType>
<Line1>100 Fake Street</Line1>
<Line2/>
<Line3/>
<Line4/>
<Suburb>Jupiter</Suburb>
<State>OTH</State>
<PostCode>9999</PostCode>
<Country>AUS</Country>
</Address>
</ContactAddresses>
</Contact>
</Member>
</Request>
Current XSL for getting elements
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()">
<xsl:for-each select="node()[text() != '']">
<xsl:value-of select="local-name()"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
<xsl:apply-templates select="node()"/>
</xsl:template>
</xsl:stylesheet>
My WIP xml for inserting the result xml tags is below. I'm unsure how to insert the results of the above xsl into this,
<xsl:template match="Element">
<xsl:copy-of select="."/>
<Element>Value1</Element>
</xsl:template>
And ultimate desired output:
<?xml version="1.0" encoding="UTF-8"?>
<Request>
<Header>
<Line1>Element1</Line1>
<Line2>Element2</Line2>
</Header>
<ElementControl>
<Update>
<Element>Identifier</Element>
<Element>Gender</Element>
<Element>Title</Element>
<Element>Name</Element>
<Element>AddressType</Element>
<Element>Line1</Element>
<Element>Suburb</Element>
<Element>State</Element>
<Element>PostCode</Element>
<Element>Country</Element>
</Update>
</ElementControl>
<Member>
<Identifier>123456789</Identifier>
<Contact>
<Person>
<Gender>MALE</Gender>
<Title>Mr</Title>
<Name>JOHN DOE</Name>
</Person>
<HomePhone/>
<eMailAddress/>
<ContactAddresses>
<Address>
<AddressType>POS</AddressType>
<Line1>100 Fake Street</Line1>
<Line2/>
<Line3/>
<Line4/>
<Suburb>Jupiter</Suburb>
<State>OTH</State>
<PostCode>9999</PostCode>
<Country>AUS</Country>
</Address>
</ContactAddresses>
</Contact>
</Member>
</Request>
I would change the current template to use mode attribute, so it is only used in specific cases, rather than matching all elements. You should also change it to output elements, not text, like so:
<xsl:template match="node()" mode="copy">
<xsl:for-each select=".//node()[text() != '']">
<Element>
<xsl:value-of select="local-name()"/>
</Element>
</xsl:for-each>
</xsl:template>
Then you can call it like this....
<xsl:template match="ElementControl/Update">
<xsl:apply-templates select="../../Member" mode="copy" />
</xsl:template>
Try this XSLT. Note the use of the identity template to copy all other existing elements unchanged
<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:template match="node()" mode="copy">
<xsl:for-each select=".//node()[text() != '']">
<Element>
<xsl:value-of select="local-name()"/>
</Element>
</xsl:for-each>
</xsl:template>
<xsl:template match="ElementControl/Update">
<xsl:apply-templates select="../../Member" mode="copy" />
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

How to use XSLT to convert a simple piece of XML

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.

XSLT: How to remove the self-closed element

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>