I am new in XSLT and looking for help to remove duplicates of <EMP> from an xml document on the basis of their children's combined value. From each group of elements with the same value for this, the one with highest value for AIB_Position/AIB must be output. Below is my sample xml document and the corresponding desired output.
<Row_entry>
<Employees>
<Emp>
<Emp_id>E1</Emp_id>
<Emp_Name>Name1</Emp_Name>
<Country>C1</Country>
<AIB_Position>
<AIB>1500</AIB>
</AIB_Position>
</Emp>
<Emp>
<Emp_id>E2</Emp_id>
<Emp_Name>Name2</Emp_Name>
<Country>C2</Country>
<AIB_Position>
<AIB>1700</AIB>
</AIB_Position>
</Emp>
<Emp>
<Emp_id>E2</Emp_id>
<Emp_Name>Name2</Emp_Name>
<Country>C2</Country>
<AIB_Position>
<AIB>1800</AIB>
</AIB_Position>
</Emp>
</Employees>
</Row_entry>
Desired output(Removed duplicate Emp elements based on the combined <Emp_id>, <Emp_Name>, <Country> value):
<Row_entry>
<Employees>
<Emp>
<Emp_id>E1</Emp_id>
<Emp_Name>Name1</Emp_Name>
<Country>C1</Country>
<AIB_Position>
<AIB>1500</AIB>
</AIB_Position>
</Emp>
<Emp>
<Emp_id>E2</Emp_id>
<Emp_Name>Name2</Emp_Name>
<Country>C2</Country>
<AIB_Position>
<AIB>1800</AIB>
</AIB_Position>
</Emp>
</Employees>
</Row_entry>
I think you want this (directly using the XPath 2.0 max() function):
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output indent="yes"/>
<xsl:template match="Employees">
<xsl:copy>
<xsl:for-each-group select="Emp" group-by="concat(Emp_id, '+', Emp_Name, '+', Country)">
<xsl:copy-of select="current-group()
[AIB_Position/AIB/number() = max(current-group()/AIB_Position/AIB/number())][1]"/>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
And if you suspect your XSLT processor of idiocy, such as calculating the max() more than once, use this more precisely directing transformation:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output indent="yes"/>
<xsl:template match="Employees">
<xsl:copy>
<xsl:for-each-group select="Emp"
group-by="concat(Emp_id, '+', Emp_Name, '+', Country)">
<xsl:copy-of select=
"for $max in max(current-group()/AIB_Position/AIB/number())
return
current-group()[AIB_Position/AIB/number() = $max][1]"/>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
In XSLT 2 or later, use for-each-group, for instance in XSLT 3 with a composite grouping key, then sort each group and output the maximum value:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:output indent="yes"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="Employees">
<xsl:copy>
<xsl:for-each-group select="Emp" composite="yes" group-by="Emp_id, Emp_Name, Country">
<xsl:for-each select="current-group()">
<xsl:sort select="AIB_Position/AIB" order="descending"/>
<xsl:if test="position() = 1">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
With an XSLT 3 processor supporting the higher order sort function you could shorten that code to use
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:output indent="yes"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="Employees">
<xsl:copy>
<xsl:for-each-group select="Emp" composite="yes" group-by="Emp_id, Emp_Name, Country">
<xsl:sequence select="sort(current-group(), (), function($emp) { xs:integer($emp/AIB_Position/AIB) })[last()]"/>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
https://stackoverflow.com/tags/xslt-grouping/info has some details on how to implement a composite grouping key of XSLT 3 in XSLT 2 by string-joining the components of the key, if you are limited to XSLT 2.
Related
How do I multiple nest the following xml using xsl 2.0 based on level1 (outer), level2(middle), level3(inner)?
<rootNode>
<fruits>
<fruit>
<level1>4</level1>
<level2/>
<level3/>
<kind>orange</kind>
<size>big</size>
<origin>california</origin>
</fruit>
<fruit>
<level1>4</level1>
<level2>2</level2>
<level3/>
<kind>lemon</kind>
<size>small</size>
<origin>florida</origin>
</fruit>
<fruit>
<level1>4</level1>
<level2>2</level2>
<level3>1</level3>
<kind>pineapple</kind>
<size>normal</size>
<origin>oregon</origin>
</fruit>
<fruit>
<level1>5</level1>
<level2>2</level2>
<level3/>
<kind>pineapple</kind>
<size>normal</size>
<origin>oregon</origin>
</fruit>
<fruit>
<level1>5</level1>
<level2>1</level2>
<level3/>
<kind>peer</kind>
<size>big</size>
<origin>ohio</origin>
</fruit>
</fruits>
</rootNode>
I can do it for level1 using the following xslt
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/rootNode/fruits">
<xsl:for-each-group select="fruit" group-by="level1">
<level1 name="{current-grouping-key()}">
<xsl:copy-of select="current-group()"/>
</level1>
</xsl:for-each-group>
</xsl:template>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
However, using the output from it and doing it again for lavel2 doesn't work. Recursion function? How to write it?
As your title says, you need to nest two xsl:for-each-group instructions, one inside the other:
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:strip-space elements="*"/>
<xsl:template match="/rootNode">
<xsl:for-each-group select="fruits/fruit" group-by="level1">
<level1 name="{current-grouping-key()}">
<xsl:for-each-group select="current-group()" group-by="level2">
<level2 name="{current-grouping-key()}">
<xsl:copy-of select="current-group()"/>
</level2>
</level1>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
Demo: https://xsltfiddle.liberty-development.net/bdxtre
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>
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>
I am new to xslt. I have a xml file like
<EmpCollection>
<Emp>
<Name>Sai</Name>
<Sal>7000</Sal>
</Emp>
<Emp>
<Name>Nari</Name>
<Sal>7400</Sal>
</Emp>
<Emp>
<Name>Hari</Name>
<Sal>9000</Sal>
</Emp>
<Emp>
<Name>Suri</Name>
<Sal>8900</Sal>
</Emp>
</EmpCollection>
Now I want to add sum of salaries without using sum() function.
I want to learn xslt in a clear way:-)
I. Probably one of the simplest solutions:
<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="Sal">
<xsl:param name="pSum" select="0"/>
<xsl:apply-templates select="following::Sal[1]">
<xsl:with-param name="pSum" select="$pSum + ."/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="Sal[not(following::Sal)]">
<xsl:param name="pSum" select="0"/>
<xsl:value-of select="$pSum + ."/>
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates select="descendant::Sal[1]"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<EmpCollection>
<Emp>
<Name>Sai</Name>
<Sal>7000</Sal>
</Emp>
<Emp>
<Name>Nari</Name>
<Sal>7400</Sal>
</Emp>
<Emp>
<Name>Hari</Name>
<Sal>9000</Sal>
</Emp>
<Emp>
<Name>Suri</Name>
<Sal>8900</Sal>
</Emp>
</EmpCollection>
the wanted, correct result is produced:
32300
II. A more general solution is to use the FXSL 2.x f:foldl() function like this:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://fxsl.sf.net/" exclude-result-prefixes="f">
<xsl:import href="../f/func-foldl.xsl"/>
<xsl:import href="../f/func-Operators.xsl"/>
<xsl:output encoding="UTF-8" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:value-of select="f:foldl(f:add(), 0, /*/*/Sal)"/>
</xsl:template>
</xsl:stylesheet>
When this XSLT 2.0 transformation is applied on the same XML document (above), the same correct and wanted result is produced:
32300
You can pass any two-argument function as argument to f:foldl() and produce the solutions of different problems.
For example, passing f:mult() instead of f:add() (and changing the "zero" argument to 1) produces the product of the salaries:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://fxsl.sf.net/" exclude-result-prefixes="f">
<xsl:import href="../f/func-foldl.xsl"/>
<xsl:import href="../f/func-Operators.xsl"/>
<xsl:output encoding="UTF-8" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:value-of select="f:foldl(f:mult(), 1, /*/*/Sal)"/>
</xsl:template>
</xsl:stylesheet>
The result of applying this transformation to the same XML document is now the product of all Sal elements:
4.14918E15
III. In XSLT 3.0 (XPath 3.0) one can use the standard fold-left() or fold-right() function in exactly the same way as in the previous solution.
There are probably more elegant ways (and I guess sum() :) but the following works, by 'folding' siblings with addition. The termination condition is the last sibling (i.e. with no following nodes)
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:template match="/EmpCollection">
<result>Result Is : <xsl:apply-templates select="Emp[1]"/>
</result>
</xsl:template>
<xsl:template match="Emp[following-sibling::Emp]">
<xsl:variable name="salThis">
<xsl:value-of select="Sal"/>
</xsl:variable>
<xsl:variable name="salOthers">
<xsl:apply-templates select="following-sibling::Emp[position()=1]"/>
</xsl:variable>
<xsl:value-of select="$salThis + $salOthers"/>
</xsl:template>
<!--Termination - the last employee just returns its salary value-->
<xsl:template match="Emp[not(following-sibling::Emp)]">
<xsl:value-of select="Sal"/>
</xsl:template>
</xsl:stylesheet>
Gives the Result:
<result>Result Is : 32300</result>
Edit
After reading your question again, I guess this isn't a code golf question?
The below is a more simple way to do this with hard coded element names, which instructs the processor to add together the salaries of the named employees:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:template match="/EmpCollection">
<result>
Result Is : <xsl:value-of select="Emp[Name='Sai']/Sal + Emp[Name='Nari']/Sal + Emp[Name='Hari']/Sal + Emp[Name='Suri']/Sal"/>
</result>
</xsl:template>
</xsl:stylesheet>
which should then lead easily to understand what sum() is doing:
<xsl:template match="/EmpCollection">
<result>
Result Is : <xsl:value-of select="sum(Emp/Sal)"/>
</result>
</xsl:template>
Edit
Count is simply:
<xsl:template match="/EmpCollection">
<result>
<xsl:text>Count Is : </xsl:text>
<xsl:value-of select="count(Emp)"/>
</result>
</xsl:template>
Doing this without count() isn't as simple. Note that xsl:variables cannot be changed once assigned (immutable). So the following doesn't work at all.
<xsl:template match="/EmpCollection">
<xsl:variable name="count" select="0" />
<xsl:for-each select="Emp">
<!--NB : You can't do this. Variables are immutable - XSLT is functional -->
<xsl:variable name="count" select="$count + 1"></xsl:variable>
</xsl:for-each>
<result>
Result Is : <xsl:value-of select="$count"/>
</result>
</xsl:template>
So you could 'loop' through all the nodes, and then use position() to determine if this is the last node in the sequence. (Note that we've used for-each, but in practice it is better to use use apply-template)
<xsl:template match="/EmpCollection">
<result>
<xsl:text>Count Is : </xsl:text>
<xsl:for-each select="Emp">
<xsl:if test="position() = last()">
<xsl:number value="position()" format="1" />
</xsl:if>
</xsl:for-each>
</result>
</xsl:template>
:(
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/" [...] >