Search code examples
xsltxslt-2.0

adding attribute to the node


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 <countryid> is 32 then it should add attribute countryid="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:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

    <xsl:include href="Common/identity.xsl"/>
    <xsl:output indent="yes" method="xml"/>

    <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>
</xsl:stylesheet>

Any help will be appreciated. Also can we pass countryid as comma seperated values so that i can pass 32,100 and then it should add attribute to all the matching nodes.

Thanks.


Solution

  • 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)