user4918296
user4918296

Reputation:

XSLT: Add attribute to all descendants of rootnode

I have the following xml document:

$ cat data.xml
<rootnode>
 <section id="1" >
  <outer param="p1">
   <inner />
   <inner />
  </outer>
  <outer param="p1">
   <inner />
  </outer>
  <outer />
  <outer param="p5"/>
 </section>
 <section id="2" >
  <outer >
   <inner anotherparam="ap1"/>
   <inner param="p4"/>
   <inner />
  </outer>
 </section>
</rootnode>

I want to add a new attribute to every element, except for the rootnode. This XSLT transformation does almost what I want:

$ cat add.xsl
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="node()">
        <xsl:copy>
            <xsl:attribute name="newattribute">NEW</xsl:attribute>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

However, it also adds the new attribute to rootnode. How can I ommit the rootnode so the it will be just copied ?

Upvotes: 0

Views: 129

Answers (3)

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243479

I want to add a new attribute to every node, except for the rootnode.

I believe that the only element you want not to add the attribute to is the document (top) element. It happens to be named rootnode in this XML document, but in another document it may have a different name, like topnode and you want the solution to work also in this case.

Also, if you have an element named rootnode that is not the top element, you probably want the new attribute to be added to this element, too.

Thus, the following transformation would do this, regardless of the name of the document element:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes"/>

  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="*/*">
    <xsl:copy>
      <xsl:attribute name="newattribute">NEW</xsl:attribute>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

Do notice the match pattern of the overriding template:

  <xsl:template match="*/*">

This matches every element whose parent node is also an element -- effectively excluding the top (document) element.

When the transformation is applied on the provided XML document:

<rootnode>
    <section id="1" >
        <outer param="p1">
            <inner />
            <inner />
        </outer>
        <outer param="p1">
            <inner />
        </outer>
        <outer />
        <outer param="p5"/>
    </section>
    <section id="2" >
        <outer >
            <inner anotherparam="ap1"/>
            <inner param="p4"/>
            <inner />
        </outer>
    </section>
</rootnode>

the wanted, correct result is produced:

<rootnode>
    <section newattribute="NEW" id="1">
        <outer newattribute="NEW" param="p1">
            <inner newattribute="NEW"/>
            <inner newattribute="NEW"/>
        </outer>
        <outer newattribute="NEW" param="p1">
            <inner newattribute="NEW"/>
        </outer>
        <outer newattribute="NEW"/>
        <outer newattribute="NEW" param="p5"/>
    </section>
    <section newattribute="NEW" id="2">
        <outer newattribute="NEW">
            <inner newattribute="NEW" anotherparam="ap1"/>
            <inner newattribute="NEW" param="p4"/>
            <inner newattribute="NEW"/>
        </outer>
    </section>
</rootnode>

When the transformation -- without any change -- is applied on a similar document (only the document element is renamed to topnode):

<topnode>
    <section id="1" >
        <outer param="p1">
            <inner />
            <inner />
        </outer>
        <outer param="p1">
            <inner />
        </outer>
        <outer />
        <outer param="p5"/>
    </section>
    <section id="2" >
        <outer >
            <inner anotherparam="ap1"/>
            <inner param="p4"/>
            <inner />
        </outer>
    </section>
</topnode>

again the correct result is produced:

<topnode>
    <section newattribute="NEW" id="1">
        <outer newattribute="NEW" param="p1">
            <inner newattribute="NEW"/>
            <inner newattribute="NEW"/>
        </outer>
        <outer newattribute="NEW" param="p1">
            <inner newattribute="NEW"/>
        </outer>
        <outer newattribute="NEW"/>
        <outer newattribute="NEW" param="p5"/>
    </section>
    <section newattribute="NEW" id="2">
        <outer newattribute="NEW">
            <inner newattribute="NEW" anotherparam="ap1"/>
            <inner newattribute="NEW" param="p4"/>
            <inner newattribute="NEW"/>
        </outer>
    </section>
</topnode>

And in the case having this source XML document (a <rootnode> element has been added that is not the top element):

<rootnode>
    <section id="1" >
        <outer param="p1">
            <inner />
            <inner />
        </outer>
        <outer param="p1">
            <inner />
        </outer>
        <outer />
        <outer param="p5"/>
    </section>
    <rootnode/>
    <section id="2" >
        <outer >
            <inner anotherparam="ap1"/>
            <inner param="p4"/>
            <inner />
        </outer>
    </section>
</rootnode>

Again the correct result is produced by the same unchanged transformation:

<rootnode>
    <section newattribute="NEW" id="1">
        <outer newattribute="NEW" param="p1">
            <inner newattribute="NEW"/>
            <inner newattribute="NEW"/>
        </outer>
        <outer newattribute="NEW" param="p1">
            <inner newattribute="NEW"/>
        </outer>
        <outer newattribute="NEW"/>
        <outer newattribute="NEW" param="p5"/>
    </section>
    <rootnode newattribute="NEW"/>
    <section newattribute="NEW" id="2">
        <outer newattribute="NEW">
            <inner newattribute="NEW" anotherparam="ap1"/>
            <inner newattribute="NEW" param="p4"/>
            <inner newattribute="NEW"/>
        </outer>
    </section>
</rootnode>

Lesson to be learned:

Specifying a precise match pattern can save you a lot of work!

Upvotes: 3

michael.hor257k
michael.hor257k

Reputation: 116993

I would suggest you do it this way:

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="*"/>

<!-- identity transform -->
<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="*[not(self::rootnode)]">
    <xsl:copy>
        <xsl:attribute name="newattribute">NEW</xsl:attribute>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

Upvotes: 2

Matt Hogan-Jones
Matt Hogan-Jones

Reputation: 3113

If you add an xsl template that matches the rootnode and just do similar to what you are already doing for the @*|node() match it won't add the attribute.

Here is my version:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="rootnode">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="*">
        <xsl:copy>
            <xsl:attribute name="newattribute">NEW</xsl:attribute>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

When applied to your input XML it produces the following output:

<rootnode>
    <section newattribute="NEW" id="1">
        <outer newattribute="NEW" param="p1">
            <inner newattribute="NEW"/>
            <inner newattribute="NEW"/>
        </outer>
        <outer newattribute="NEW" param="p1">
            <inner newattribute="NEW"/>
        </outer>
        <outer newattribute="NEW"/>
        <outer newattribute="NEW" param="p5"/>
    </section>
    <section newattribute="NEW" id="2">
        <outer newattribute="NEW">
            <inner newattribute="NEW" anotherparam="ap1"/>
            <inner newattribute="NEW" param="p4"/>
            <inner newattribute="NEW"/>
        </outer>
    </section>
</rootnode>

Upvotes: 0

Related Questions