Question

I have this xml:

<root>

    <first>The first</first>

    <second>and the second</second>
</root>

I would like the output to be:

<root>

     <firstAndSecond>The first and the second</firstAndSecond>

</root>

However I cannot find any articles that demonstrate how to do this in xsl so I would be very greatful if someone could provide me with an example or link me an article that explains how to do this.

Thanks in advance.

Était-ce utile?

La solution

Although probably not entirely necessary in such a simple input XML, it is usually worth starting off with the XSLT identity transform, which on its own copies nodes as-is, meaning you only need to write templates for 'exceptions'

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

In your case, you could see the problem as transforming the first child of the root into a new element, so you would have a template to match the first element

 <xsl:template match="/*/*[1]">

To create a new element with a dynamic name, use the xsl:element command, like so

<xsl:element name="{local-name()}And{local-name(following-sibling::*[1])}">

Or maybe to keep a bit more readable, use a variable in the expression

    <xsl:variable name="second" select="local-name(following-sibling::*[1])" />
    <xsl:element name="{local-name()}And{$second}">

Note the use of Attribute Value Templates here, which indicate an expression to be evaluated, not output literally. So, in this case local-name() is being evaluated to get the name of the element (excluding namespaces).

Within this you would copy the child of the two child elements across using xsl:apply-templates (which would handle the case where there nodes other than text to copy)

        <xsl:apply-templates select="node()" />
        <xsl:text> </xsl:text>
        <xsl:apply-templates select="following-sibling::*[1]/node()"/>

Finally, to stop the identity transform copying it, you would also need a template to exclude the second child of the root

<xsl:template match="/*/*[position() > 1]" />

Trying this XSLT

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

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

    <xsl:template match="/*/*[1]">
        <xsl:variable name="second" select="local-name(following-sibling::*[1])" />
        <xsl:element name="{local-name()}And{$second}">
            <xsl:apply-templates select="node()" />
            <xsl:text> </xsl:text>
            <xsl:apply-templates select="following-sibling::*[1]/node()"/>
        </xsl:element>
    </xsl:template>

    <xsl:template match="/*/*[position() > 1]" />
</xsl:stylesheet>

Note, this doesn't captialise the first letter. To do this (in XSLT 1.0) you will need to use a combination of substring to extract the first letter, and translate to convert it to upper case.

Autres conseils

What about this solution ?

<root>
    <first>The first</first>
    <second>and the second</second>
</root>
<xsl:template match="root">
    <xsl:variable name="first_name" select="name(*[position() = 1])"/>
    <xsl:variable name="second_name" select="name(*[position() = 2])"/>
    <xsl:variable name="combined_names" select="concat($first_name,'And',$second_name)"/>
    <xsl:element name="{$combined_names}">
        <xsl:value-of select="concat(*[position() = 1],' and ',*[position() = 2])"/>
    </xsl:element>
</xsl:template>
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top