I am trying to add an attribute to the node if the child node value is equal to some string.
I have a main.xml file
<Employees>
<Employee>
<countryid>32</countryid>
<id name="id">1</id>
<firstname >ABC</firstname>
<lastname >XYZ</lastname>
</Employee>
<Employee>
<countryid>100</countryid>
<id name="id">2</id>
<firstname >ddd</firstname>
<lastname >ggg</lastname>
</Employee>
</Employees>
So let's say if the country id is equal to 32 then it should add attribute country=32 to Employee node. The output should be like below :
output.xml
<Employees>
<Employee countryid="32">
<countryid>32</countryid>
<id name="id">1</id>
<firstname >ABC</firstname>
<lastname >XYZ</lastname>
</Employee>
<Employee>
<countryid>100</countryid>
<id name="id">2</id>
<firstname >ddd</firstname>
<lastname >ggg</lastname>
</Employee>
</Employees>
I am using the following script but getting error that An attribute node cannot be create after the children of containing element.:
Transform.xsl
<xsl:template match="/">
<xsl:apply-templates />
</xsl:template>
<xsl:template match="Employees/Employee/countryid[.=32']">
<xsl:attribute name="countryid">32</xsl:attribute>
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
Any help will be appreciated. Also can we pass countryid as comma seprated values so that i can pass 32,100 and then it should add attribute to all the matching nodes.
Thanks.
In addition to Dimitre's good answer, an XSLT 2.0 stylesheet:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="pCountry" select="'32,100'"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Employee[countryid = tokenize($pCountry,',')]">
<Employee countryid="{countryid}">
<xsl:apply-templates select="@*|node()"/>
</Employee>
</xsl:template>
</xsl:stylesheet>
Output:
<Employees>
<Employee countryid="32">
<countryid>32</countryid>
<id name="id">1</id>
<firstname>ABC</firstname>
<lastname>XYZ</lastname>
</Employee>
<Employee countryid="100">
<countryid>100</countryid>
<id name="id">2</id>
<firstname>ddd</firstname>
<lastname>ggg</lastname>
</Employee>
</Employees>
Note: Existencial comparison with sequence, param/variable reference in patterns.
Other approach assuming countryid
is always first child:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:param name="pCountry" select="'32,100'"/>
<xsl:template match="node()|@*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="countryid[. = tokenize($pCountry,',')]">
<xsl:attribute name="countryid">
<xsl:value-of select="."/>
</xsl:attribute>
<xsl:call-template name="identity"/>
</xsl:template>
</xsl:stylesheet>
Note: Now xsl:strip-space
instruction is important (avoids output text node before attribute)
Part 1.
So let's say if the country id is
equal to 32 then it should add
attribute country=32 to Employee node.
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="Employee[countryid=32]">
<Employee countryid="{countryid}">
<xsl:apply-templates select="@*|node()"/>
</Employee>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<Employees>
<Employee>
<countryid>32</countryid>
<id name="id">1</id>
<firstname >ABC</firstname>
<lastname >XYZ</lastname>
</Employee>
<Employee>
<countryid>100</countryid>
<id name="id">2</id>
<firstname >ddd</firstname>
<lastname >ggg</lastname>
</Employee>
</Employees>
produces the wanted, correct result:
<Employees>
<Employee countryid="32">
<countryid>32</countryid>
<id name="id">1</id>
<firstname>ABC</firstname>
<lastname>XYZ</lastname>
</Employee>
<Employee>
<countryid>100</countryid>
<id name="id">2</id>
<firstname>ddd</firstname>
<lastname>ggg</lastname>
</Employee>
</Employees>
Explanation:
The identity rule is used to copy every node as-is. Using and overriding the identity rule (template) is the most fundamental and powerful XSLT design pattern.
There is only one template that overrides the identity rule for specific nodes -- Employee
elements that have a countryid
child with string value (converted to number) 32. This template adds a countryid
attribute to the Employee
element and applies templates to resume the activity of the identity rule and copy everything else as-is.
Part 2.
Also can we pass countryid as comma
seprated values so that i can pass
32,100 and then it should add
attribute to all the matching nodes
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:param name="pIds" select="'32,100'"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Employee">
<Employee>
<xsl:if test=
"contains(concat(',',$pIds,','),
concat(',',countryid,',')
)">
<xsl:attribute name="countryid">
<xsl:value-of select="countryid"/>
</xsl:attribute>
</xsl:if>
<xsl:apply-templates select="@*|node()"/>
</Employee>
</xsl:template>
</xsl:stylesheet>
when applied to the same XML document (above), produces the wanted, correct result:
<Employees>
<Employee countryid="32">
<countryid>32</countryid>
<id name="id">1</id>
<firstname>ABC</firstname>
<lastname>XYZ</lastname>
</Employee>
<Employee countryid="100">
<countryid>100</countryid>
<id name="id">2</id>
<firstname>ddd</firstname>
<lastname>ggg</lastname>
</Employee>
</Employees>