Reputation: 607
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.
Upvotes: 1
Views: 6267
Reputation: 131
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>
Upvotes: 2
Reputation: 70648
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.
Upvotes: 3