1

let's say I have this input file

<root>

    <keyword>
        <name>foo</name>
        <value>bar</value>
    </keyword>
    <keyword>
        <name>123</name>
        <value>456</value>
    </keyword>

</root>

and I want this output:

<root>

    <keyword>
        <name>foobar</name>
        <value>bar</value>
    </keyword>
        <keyword>
        <name>123</name>
        <value>456</value>
    </keyword>

</root>

Now, I have this working transformation, but I want to know how to make it more elegant.

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"  >

    <!-- copy all nodes and attributes -->
    <xsl:template match="@*|node()" name = "identity">
        <xsl:copy >
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match = "/root/keyword/name[text() = 'foo']">
        <name>foobar</name>
    </xsl:template>

</xsl:stylesheet>

After matching the desired node, I am repeatedly setting it again instead of simply replacing it. Can I do this more elegantly? My request may sound a little ridiculous, but I want to dig deeper into xslt and understand better.

Thanks very much!

Willi Fischer
  • 455
  • 6
  • 21
  • I would shorten `match = "/root/keyword/name[text() = 'foo']"` to `match = "/root/keyword/name[. = 'foo']"`. You will need to add more details like the namespace declaration for the prefix `calypso` to allow us to improve the code more. – Martin Honnen Dec 02 '14 at 18:06
  • I have removed the namespace, which was declared in one place in the transformation (Error by me, forgot to remove). Apart from that, the input and transformation are verbatim copies of my files, no additional namespaces declared. Thanks! – Willi Fischer Dec 02 '14 at 18:38

2 Answers2

3

Assuming XSLT 2.0, I would do it like this (online at http://xsltransform.net/3NzcBsW):

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"  >

    <xsl:param name="old" select="'foo'"/>
    <xsl:param name="new" select="'foobar'"/>

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

    <xsl:template match="/root/keyword/name[. = $old]">
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
            <xsl:value-of select="$new"/>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

Seems more flexible with the parameters. Other than that, I don't see ways to shorten it, the identity transformation template is the heart of the transformation and the one template specific to the input XML sample makes sure the name element we are looking for gets the new content.

Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
  • You could also change `` to `` to handle possible attributes in `name`. – Daniel Haley Dec 02 '14 at 19:30
  • I agree that attributes should be covered as well in a general solution but in that case I prefer doing `` to integrate with the general approach of allowing an override of the identity transformation template to transform them if needed. – Martin Honnen Dec 03 '14 at 10:11
  • That's also what I prefer but with a question title of "Replace value of one single node" I figured the copy-of was appropriate. – Daniel Haley Dec 03 '14 at 15:05
0

XSLT 2.0 Shorter way:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <!-- copy all nodes and attributes -->
    <xsl:template match="@*|node()" name = "identity">
        <xsl:copy >
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="/root/keyword/name/text()[.='foo']">foobar</xsl:template>

</xsl:stylesheet>
David Abragimov
  • 524
  • 2
  • 6
  • 24