Pregunta

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.

¿Fue útil?

Solución

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.

Otros consejos

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>
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top