xslt copy child note and add extra nodes - xslt

For the following xml:
<root>
<employees>
<employee>
<Name>ABC</Name>
<Dept>CS</Dept>
<Designation>sse</Designation>
</employee>
</employees>
</root>
I use this xslt:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<!-- Identity transform -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Name">
<xsl:copy-of select="."/>
<Age>34</Age>
</xsl:template>
<xsl:template match="Dept">
<xsl:copy-of select="."/>
<Domain>Insurance</Domain>
</xsl:template>
</xsl:stylesheet>
I would like to achieve the following output
<employee>
<Name>ABC</Name>
<Age>34</Age>
<Dept>CS</Dept>
<Domain>Insurance</Domain>
<Designation>sse</Designation>
</employee>
I don't know what xpath shall I use to limit the result only to employee node.

Well, processing starts at the document node (root node) denoted by / thus if you write a template
<xsl:template match="/">
<xsl:apply-templates select="//employee"/>
</xsl:template>
you only process any employee descendants of the document with the rest of the templates.
See https://xsltfiddle.liberty-development.net/pPqsHT6 for a working demo of above suggestion integrated into your stylesheet, the whole code is
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<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="Name">
<xsl:copy-of select="."/>
<Age>34</Age>
</xsl:template>
<xsl:template match="Dept">
<xsl:copy-of select="."/>
<Domain>Insurance</Domain>
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates select="//employee"/>
</xsl:template>
</xsl:stylesheet>

Related

XSLT copy without segment name

I have the following XML:
<segment>
<personal_information>
<birth_name>xxx</birth_name>
<created_by>yyy</created_by>
<created_on_timestamp>2018-08-06T06:41:07.000Z</created_on_timestamp>
</personal_information>
<segment>
I want to copy the entire personal_information segment with all elements and sub-segments while adding a new field. I tried this with:
<segment>
<personal_information>
<action>DELETE</action>
<xsl:copy>
<xsl:apply-templates select="child::node()"/>
</xsl:copy>
</personal_information>
</segment>
But this results in the following:
<segment>
<personal_information>
<action>DELETE</action>
<personal_information>
<birth_name>xxx</birth_name>
<created_by>yyy</created_by>
<created_on_timestamp>2018-08-06T06:41:07.000Z</created_on_timestamp>
</personal_information>
</personal_information>
</segment>
Would would be the XSLT code to achieve this as a result:
<segment>
<personal_information>
<action>DELETE</action>
<birth_name>xxx</birth_name>
<created_by>yyy</created_by>
<created_on_timestamp>2018-08-06T06:41:07.000Z</created_on_timestamp>
</personal_information>
</segment>
I do not want to copy all fields one by one.
Instead of using <xsl:copy>, use <xsl:copy-of>, which accepts an XPath expression for the nodes to be included in the copy; in this case only the inner childnodes.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="segment">
<segment>
<personal_information>
<action>DELETE</action>
<xsl:copy-of select="personal_information/*" />
</personal_information>
</segment>
</xsl:template>
</xsl:stylesheet>
You need a identity template and one personal_information with adding one action element:
<?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 indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="personal_information">
<xsl:copy>
<action>DELETE</action>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Updated as per new requirement :
<?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 indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="personal_information">
<action>DELETE</action>
<xsl:apply-templates select="node()"/>
</xsl:template>
</xsl:stylesheet>

XSLT 2.0 - passing a node set as a parameter doesn't seem to work

I have a document that I need to transform such that most elements are copied as-is, with some exceptions: for certain specified nodes, child elements need to be appended, and some of these child elements need to reference back to specific elements in the source document. A separate "model/crosswalk" xml file contains the elements to add. The elements in the crosswalk that need to refer back to the source document have "source" attributes that point them to specific elements. Of course, my actual source docs (& the actual crosswalk) are more complex and varied than these examples.
Here is a source document example:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<album>
<artist>Frank Sinatra</artist>
<title>Greatest Hits</title>
</album>
<album>
<artist>Miles Davis</artist>
<title>Kind Of Blue</title>
</album>
<movie>
<title>ET</title>
<director>Steven Spielberg</director>
</movie>
<movie>
<title>Blues Brothers</title>
<director>John Landis</director>
</movie>
</root>
Here is the "crosswalk" (crswlk.xml):
<?xml version="1.0" encoding="UTF-8"?>
<root>
<album>
<artist-info>
<artist2 source="artist"/>
</artist-info>
</album>
<movie>
<director-info>
<director2 source="director"/>
</director-info>
</movie>
</root>
And here is the desired output:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<album>
<artist>Frank Sinatra</artist>
<title>Greatest Hits</title>
<artist-info>
<artist2>Frank Sinatra</artist2>
</artist-info>
</album>
<album>
<artist>Miles Davis</artist>
<title>Kind Of Blue</title>
<artist-info>
<artist2>Miles Davis</artist2>
</artist-info>
</album>
<movie>
<title>ET</title>
<director>Steven Spielberg</director>
<director-info>
<director2>Steven Spielberg</director2>
</director-info>
</movie>
<movie>
<title>Blues Brothers</title>
<director>John Landis</director>
<director-info>
<director2>John Landis</director2>
</director-info>
</movie>
</root>
Here's my xslt:
<?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" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="crosswalk" select="document('crswlk.xml')"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*/album|movie">
<xsl:variable name="theNode" select="."/>
<xsl:variable name="nodeName" select="name()"/>
<xsl:element name="{$nodeName}">
<xsl:apply-templates select="#* | node()"/>
<xsl:apply-templates select="$crosswalk//*[name()=$nodeName]/*">
<xsl:with-param name="curNode" select="$theNode"/>
</xsl:apply-templates>
</xsl:element>
</xsl:template>
<xsl:template match="*[#source]">
<xsl:param name="curNode" />
<xsl:variable name="sourceNodeName" select="#source"/>
<xsl:element name="{name()}">
<xsl:value-of select="$curNode//*[name()=$sourceNodeName]"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
This stops processing once that last template tries to access $curNode for the first time. When I run the xslt in Eclipse (Xalan 2.7.1) it throws this error: " java.lang.ClassCastException: org.apache.xpath.objects.XString cannot be cast to org.apache.xpath.objects.XNodeSet".
If I pass a similar nodeset as a parameter to a template that matches nodes from the source document, it works as expected - the nodeset is accessible. However, passing the nodeset to the last template above doesn't work. Is it because the template matches nodes from the external document? I sure don't know. Any help much appreciated, it took me a while just to get to this point. Thanks!
Looks like you need to change 2 things:
add tunnel="yes" to xsl:with-param and xsl:param
remove the apostrophes from '$sourceNodeName' in the predicate of the xsl:value-of
Updated XSLT:
<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:strip-space elements="*"/>
<xsl:variable name="crosswalk" select="document('crswlk.xml')"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*/album|movie">
<xsl:variable name="theNode" select="."/>
<xsl:variable name="nodeName" select="name()"/>
<xsl:element name="{$nodeName}">
<xsl:apply-templates select="#* | node()"/>
<xsl:apply-templates select="$crosswalk//*[name()=$nodeName]/*">
<xsl:with-param name="curNode" select="$theNode" tunnel="yes"/>
</xsl:apply-templates>
</xsl:element>
</xsl:template>
<xsl:template match="*[#source]">
<xsl:param name="curNode" tunnel="yes"/>
<xsl:variable name="sourceNodeName" select="#source"/>
<xsl:element name="{name()}">
<xsl:value-of select="$curNode//*[name()=$sourceNodeName]"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Also, you can remove a few of those extra xsl:variables...
<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:strip-space elements="*"/>
<xsl:variable name="crosswalk" select="document('crswlk.xml')"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="album|movie">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
<xsl:apply-templates select="$crosswalk/*/*[name()=current()/name()]/*">
<xsl:with-param name="curNode" select="." tunnel="yes"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="*[#source]">
<xsl:param name="curNode" tunnel="yes"/>
<xsl:copy>
<xsl:value-of select="$curNode//*[name()=current()/#source]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
If you are using XSLT 2.0, then Daniel Haley's answer with tunnelling is surely the way to go. If, however, you are actually using Xalan, and therefore only XSLT 1.0, you need to take a different approach.
The problems start on this line:
<xsl:apply-templates select="$crosswalk//*[name()=$nodeName]/*">
This will select either artist-info or director-info in your cross walk document, but you have no specific template matching these, so the generic identity template you are using will match them
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
But this does not take any parameter, not does it pass any parameters on. Therefore, when your last template <xsl:template match="*[#source]"> is matched, the curNode will be empty (an empty string), which is not a node-set, and that saddens Xalan.
So, to solve this in XSLT1.0, just add the parameter to the identity template, and pass it on:
<xsl:template match="node()|#*">
<xsl:param name="curNode" />
<xsl:copy>
<xsl:apply-templates select="#* | node()">
<xsl:with-param name="curNode" select="$curNode" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
If the template is ever matched when there is no parameter being passed, it will just pass on an empty parameter without any issue.
Here is the full XSLT (also with the correction of apostrophes being removed from the xsl:value-of, as mentioned in Daniel's answer).
<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:strip-space elements="*"/>
<xsl:variable name="crosswalk" select="document('crswlk.xml')"/>
<xsl:template match="node()|#*">
<xsl:param name="curNode" />
<xsl:copy>
<xsl:apply-templates select="#* | node()">
<xsl:with-param name="curNode" select="$curNode" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="*/album|movie">
<xsl:variable name="theNode" select="."/>
<xsl:variable name="nodeName" select="name()"/>
<xsl:element name="{$nodeName}">
<xsl:apply-templates select="#* | node()"/>
<xsl:apply-templates select="$crosswalk//*[name()=$nodeName]/*">
<xsl:with-param name="curNode" select="$theNode"/>
</xsl:apply-templates>
</xsl:element>
</xsl:template>
<xsl:template match="*[#source]">
<xsl:param name="curNode" />
<xsl:variable name="sourceNodeName" select="#source"/>
<xsl:element name="{name()}">
<xsl:value-of select="$curNode//*[name()=$sourceNodeName]"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
When applied to your XML documents, the following is output
<root>
<album>
<artist>Frank Sinatra</artist>
<title>Greatest Hits</title>
<artist-info>
<artist2>Frank Sinatra</artist2>
</artist-info>
</album>
<album>
<artist>Miles Davis</artist>
<title>Kind Of Blue</title>
<artist-info>
<artist2>Miles Davis</artist2>
</artist-info>
</album>
<movie>
<title>ET</title>
<director>Steven Spielberg</director>
<director-info>
<director2>Steven Spielberg</director2>
</director-info>
</movie>
<movie>
<title>Blues Brothers</title>
<director>John Landis</director>
<director-info>
<director2>John Landis</director2>
</director-info>
</movie>
</root>

How to group neighbour-siblings

i have something like this:
<root>
<a>foo</a>
<b>bar</b>
<groupme>foobar</groupme>
<groupme>baz</groupme>
<groupme>42</groupme>
<c>abc</c>
<d>def</d>
<groupme>foo</groupme>
<x>xyz</x>
<groupme>bar</groupme>
<groupme>foo</groupme>
<z>thats it</z>
</root>
now i need all groume's which are direct neighbours to be a single node like:
<root>
<a>foo</a>
<b>bar</b>
<groupme>foobar baz 42</groupme>
<c>abc</c>
<d>def</d>
<groupme>foo</groupme>
<x>xyz</x>
<groupme>bar foo</groupme>
<z>thats it</z>
</root>
also the groupme nodes containing other nodes, i've just leave them to provide a simple example.
the groupme nodes only apear in a specific level, no groupme nodes in others then root.
any help for me?
Such grouping can be achieved with an approach called "sibling recursion", for your problem I would suggest a stylesheet as follows:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="root/groupme[not(preceding-sibling::*[1][self::groupme])]">
<xsl:copy>
<xsl:apply-templates select="node()"/>
<xsl:apply-templates select="following-sibling::*[1][self::groupme][1]" mode="list"/>
</xsl:copy>
</xsl:template>
<xsl:template match="root/groupme[preceding-sibling::*[1][self::groupme]]"/>
<xsl:template match="root/groupme[preceding-sibling::*[1][self::groupme]]" mode="list">
<xsl:text> </xsl:text>
<xsl:apply-templates select="node()"/>
<xsl:apply-templates select="following-sibling::*[1][self::groupme][1]" mode="list"/>
</xsl:template>
</xsl:stylesheet>
When applied to the input
<root>
<a>foo</a>
<b>bar</b>
<groupme>foobar</groupme>
<groupme>baz</groupme>
<groupme>42</groupme>
<c>abc</c>
<d>def</d>
<groupme>foo</groupme>
<x>xyz</x>
<groupme>bar</groupme>
<groupme>foo</groupme>
<z>thats it</z>
</root>
the result is
<root>
<a>foo</a>
<b>bar</b>
<groupme>foobar baz 42</groupme>
<c>abc</c>
<d>def</d>
<groupme>foo</groupme>
<x>xyz</x>
<groupme>bar foo</groupme>
<z>thats it</z>
</root>
As an alternative to the sibling recursion it is also possible to "grab" the following siblings with a key based approach:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:key
name="first"
match="root/groupme[preceding-sibling::*[1][self::groupme]]"
use="generate-id(preceding-sibling::groupme[not(preceding-sibling::*[1][self::groupme])][1])"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="root/groupme[not(preceding-sibling::*[1][self::groupme])]">
<xsl:copy>
<xsl:apply-templates select="node()"/>
<xsl:apply-templates select="key('first', generate-id())" mode="list"/>
</xsl:copy>
</xsl:template>
<xsl:template match="root/groupme[preceding-sibling::*[1][self::groupme]]"/>
<xsl:template match="root/groupme" mode="list">
<xsl:text> </xsl:text>
<xsl:apply-templates select="node()"/>
</xsl:template>
</xsl:stylesheet>

xslt to add default value where element doesn't exist

I'm struggling with trying to test to see if an element exists. If it doesn't, I'd like to add in a default value. Here's my XML
<records>
<record>
<InstanceData>
<instance>
<FirstName>Johhny</FirstName>
<LastName>Jenkins</LastName>
<AlbumCount>3</AlbumCount>
</instance>
</InstanceData>
</record>
<record>
<InstanceData>
<instance>
<FirstName>Art</FirstName>
<LastName>Tatum</LastName>
<AlbumCount>7</AlbumCount>
</instance>
</InstanceData>
</record>
<record>
<InstanceData>
<instance>
<FirstName>Count</FirstName>
<LastName>Basie</LastName>
</instance>
</InstanceData>
</record>
</records>
I'd like to be able to copy over existing values and set any record without the Album Count element to <AlbumCount>0</AlbumCount>. This is the xslt I've been working with but I think I'm some way off the mark.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="Records">
<xsl:for-each select="node()">
<xsl:choose>
<xsl:when test="name()='AlbumCount'">
<xsl:element name="AlbumCount">
<xsl:choose>
<xsl:when test="name()='AlbumCount'">
<xsl:copy-of select=".">
</xsl:copy-of>
</xsl:when>
<xsl:otherwise>
<AlbumCount>0</AlbumCount>
</xsl:otherwise>
</xsl:choose>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select=".">
</xsl:copy-of>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Thanks for looking.
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" version="1.0" encoding="UTF-8" indent="yes" omit-xml-declaration="no"/>
<!-- identity template -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="instance[not(AlbumCount)]">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
<AlbumCount>0</AlbumCount>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Start with the identity transformation, then just handle the exception differently.
You test for the existance of an element simply with the elements name, for example:
<xsl:if test="not(AlbumCount)">
<AlbumCount>0</AlbumCount>
</xsl:if>
The simpler way to do what you want is to use the standard copy template combined with a special rule for places where AlbumCount elements need adding:
<?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"/>
<!-- Standard copy template -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<!-- Special template to add AlbumCount elements where required -->
<xsl:template match="records/record/InstanceData/instance">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
<xsl:if test="not(AlbumCount)">
<AlbumCount>0</AlbumCount>
</xsl:if>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

xslt call template with dynamic match

i am trying to paas the dynamic parameter while calling the template to suppress nodes from the xml.
I would call this template like:
transform employee.xml suppress.xsl ElementsToSuppress=id,fname
employee.xml
<?xml version="1.0" encoding="utf-8" ?>
<Employees>
<Employee>
<id>1</id>
<firstname>xyz</firstname>
<lastname>abc</lastname>
<age>32</age>
<department>xyz</department>
</Employee>
<Employee>
<id>2</id>
<firstname>XY</firstname>
<lastname>Z</lastname>
<age>21</age>
<department>xyz</department>
</Employee>
</Employees>
Suppress.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0" xmlns:elements="http://localhost">
<elements:name abbrev="id">id</elements:name>
<elements:name abbrev="fname">firstname</elements:name>
<xsl:param name="ElementsToSuppress" ></xsl:param>
<xsl:variable name="tokenizedSample" select="tokenize($ElementsToSuppress,',')"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
<xsl:for-each select="$tokenizedSample">
<xsl:call-template name ="Suppress" >
<xsl:with-param name="parElementName">
<xsl:value-of select="."/>
</xsl:with-param>
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template name="Suppress">
<xsl:param name="parElementName" select="''"></xsl:param>
<xsl:variable name="extNode" select="document('')/*/elements:name[#abbrev=$parElementName]"/>
<xsl:call-template name="test" >
<xsl:with-param name="parElementName" >
<xsl:value-of select="$extNode"/>
</xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template name="test" match="*[name() = $parElementName]" >
<xsl:param name="parElementName" select="''"></xsl:param>
<xsl:call-template name="SuppressElement" />
</xsl:template>
<xsl:template name="SuppressElement" />
</xsl:stylesheet>
Can we achieve output by using this or some other way? The ideal way is to pass the comma separated abbreviations of nodes and suppress them in one call.
Any help will be appreciated.
Regards,
AB
You don't need XSLT 2.0 for this processing.
This XSLT 1.0 transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="pNodesToSuppress"
select="'id,fname,pi'"/>
<my:toSuppress>
<name abbrev="id">id</name>
<name abbrev="fname">firstname</name>
<name abbrev="pi">somePI</name>
</my:toSuppress>
<xsl:variable name="vToSuppressTable" select=
"document('')/*/my:toSuppress/*"/>
<xsl:variable name="vNamesToSupress">
<xsl:for-each select=
"$vToSuppressTable
[contains(concat(',',$pNodesToSuppress,','),
concat(',',#abbrev, ',')
)
]">
<xsl:value-of select="concat(',',.)"/>
</xsl:for-each>
<xsl:text>,</xsl:text>
</xsl:variable>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="node()" priority="0.01">
<xsl:if test=
"not(contains($vNamesToSupress,
concat(',',name(),',')
)
)">
<xsl:call-template name="identity"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document (with an added processing instruction to demonstrate how we can also delete PIs -- not only elements):
<Employees>
<Employee>
<id>1</id>
<firstname>xyz</firstname>
<lastname>abc</lastname>
<age>32</age>
<department>xyz</department>
</Employee>
<?somePI This PI will be deleted ?>
<Employee>
<id>2</id>
<firstname>XY</firstname>
<lastname>Z</lastname>
<age>21</age>
<department>xyz</department>
</Employee>
</Employees>
produces the wanted, correct results:
<Employees>
<Employee>
<lastname>abc</lastname>
<age>32</age>
<department>xyz</department>
</Employee>
<Employee>
<lastname>Z</lastname>
<age>21</age>
<department>xyz</department>
</Employee>
</Employees>
Do note:
This is a pure XSLT 1.0 transformation.
2.Not only elements, but also processing instructions are deleted, when their name is specified via the external parameter $pNodesToSuppress.
I don't get why the parameter value "fname" would suppress an element called "firstname" but apart from that you might simply want
<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="ElementsToSuppress" as="xs:string" select="'id,firstname'"/>
<xsl:variable name="names-to-suppress" as="xs:QName*"
select="for $s in tokenize($ElementsToSuppress, ',') return QName('', $s)"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#*, node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[node-name(.) = $names-to-suppress]"/>
</xsl:stylesheet>
[edit]
I missed that your sample stylesheet seemed to contain a mapping from those parameters to the elements names in the sample input. In that case here is an adapted version of the stylesheet to handle that case:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:data="http://example.com/data"
xmlns:elements="http://example.com/elements"
exclude-result-prefixes="xs data elements"
version="2.0">
<data:data>
<elements:name abbrev="id">id</elements:name>
<elements:name abbrev="fname">firstname</elements:name>
</data:data>
<xsl:key name="e-by-abbrev" match="elements:name" use="#abbrev"/>
<xsl:param name="ElementsToSuppress" as="xs:string" select="'id,fname'"/>
<xsl:variable name="names-to-suppress" as="xs:QName*"
select="for $s in tokenize($ElementsToSuppress, ',') return QName('', key('e-by-abbrev', $s, document('')/xsl:stylesheet/data:data))"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#*, node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[node-name(.) = $names-to-suppress]"/>
</xsl:stylesheet>
[second edit to implement further request] Here is a sample that also checks the relationship attribute values:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:data="http://example.com/data"
xmlns:elements="http://example.com/elements"
exclude-result-prefixes="xs data elements"
version="2.0">
<xsl:param name="ElementsToSuppress" as="xs:string" select="'id'"/>
<xsl:param name="parVAObjectRelationship" as="xs:string" select="'a,b'"/>
<xsl:variable name="names-to-suppress" as="xs:QName*"
select="for $s in tokenize($ElementsToSuppress, ',') return QName('', key('e-by-abbrev', $s, document('')/xsl:stylesheet/data:data))"/>
<xsl:variable name="att-values-to-suppress" as="xs:string*"
select="tokenize($parVAObjectRelationship, ',')"/>
<data:data>
<elements:name abbrev="id">id</elements:name>
<elements:name abbrev="fname">firstname</elements:name>
</data:data>
<xsl:key name="e-by-abbrev" match="elements:name" use="#abbrev"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#*, node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[#relationship = $att-values-to-suppress]"/>
<xsl:template match="*[node-name(.) = $names-to-suppress]"/>
</xsl:stylesheet>
When applied to
<Employees>
<Employee>
<id>1</id>
<firstname>xyz</firstname>
<lastname relationship="a">abc</lastname>
<age relationship="b">32</age>
<department>xyz</department>
</Employee>
</Employees>
the output is
<Employees>
<Employee>
<firstname>xyz</firstname>
<department>xyz</department>
</Employee>
</Employees>
You might want to add strip-space and output indent="yes" to prevent the blank lines.
If you are using the old Saxon-B or newer Saxon-PE or Saxon-EE as XSLT processor, you can use a saxon extension to achieve dynamic template calls:
<saxon:call-template name="{$templateName}"/>
Don't forget to declare the saxon-Namespace in xsl-stylesheet element:
<xsl:stylesheet xmlns:saxon="http://saxon.sf.net/" [...] >