1

I'm a newbie at XSL/XSLT. I'm trying to add a new element (datasources) to all users elements of this xml:

    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<users>
    <user id="1">
        <repo>r1</repo>
        <home>h1</home>
    </user>
    <user id="2">
        <repo>r2</repo>
        <home>h2</home>
    </user>
    <user id="3">
        <repo>r3</repo>
        <home>h3</home>
    </user>
    <user id="4">
        <repo>r4</repo>
        <home>h4</home>
    </user>
    <user id="5">
        <repo>r5</repo>
        <home>h5</home>
    </user>
</users>

I'm using this XSL script:

    <?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="xml" indent="yes" cdata-section-elements="configXml"/>
    <!-- Copy everything -->
    <xsl:template match="node()">
        <xsl:copy>
            <xsl:copy-of select="@*"/>
            <xsl:apply-templates/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="/users/user[*]">
        <xsl:copy>
            <xsl:apply-templates/>
            <xsl:element name="datasources"></xsl:element>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

The problem is that in the final result the id of all users disappear:

<users>
<user>
    <repo>r1</repo>
    <home>h1</home>
    <datasources/>
</user>
<user>
    <repo>r2</repo>
    <home>h2</home>
    <datasources/>
</user>
    <user>
    <repo>r3</repo>
    <home>h3</home>
    <datasources/>
</user>
    <user>
    <repo>r4</repo>
    <home>h4</home>
    <datasources/>
</user>
    <user>
    <repo>r5</repo>
    <home>h5</home>
    <datasources/>
</user>

How can I keep the users id in the output?

2 Answers2

2

Your copy everything template needs to be:

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

See How to copy everything as is and only remove a specific element.

What is happening is that your 2nd template is matching the <user> elements but the <apply-templates/> will not use the 1st template on attributes since they do not match node()

As Ian Roberts points out, you also need to explicitly select @*|node() in your <xsl:apply-templates/> of your 2nd template in order for attributes to be processed too - in which case they would then be picked up by the modified 1st template.

So the full solution is:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="xml" indent="yes" cdata-section-elements="configXml"/>
    <!-- Copy everything -->
    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="/users/user[*]">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
            <xsl:element name="datasources"></xsl:element>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>
Community
  • 1
  • 1
Richard
  • 9,740
  • 2
  • 23
  • 15
  • 2
    `` with no `select` is equivalent to ``, which does _not_ include attributes. You need to specifically select `@*|node()` if you want attributes as well as child nodes. – Ian Roberts Feb 10 '15 at 09:40
1

The attributes are not copied because you haven't copied them - following the pattern of the other templates, in your /users/user[*] template you need to add a

<xsl:copy-of select="@*"/>

as the first thing in the <xsl:copy> before applying templates to children.

Ian Roberts
  • 120,891
  • 16
  • 170
  • 183