This conversion is :
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ext="http://exslt.org/common"> <xsl:output omit-xml-declaration="yes" indent="yes"/> <xsl:strip-space elements="*"/> <xsl:key name="kSameNameAdj" match="*" use="concat(generate-id(..), '+', generate-id(preceding-sibling::*[not(name()=name(current()))][1]) )"/> <xsl:template match="node()|@*"> <xsl:copy> <xsl:apply-templates select="node()|@*"/> </xsl:copy> </xsl:template> <xsl:template match="/"> <xsl:variable name="vrtfPass1"> <xsl:apply-templates select="node()"/> </xsl:variable> <xsl:apply-templates select="ext:node-set($vrtfPass1)" mode="compress"/> </xsl:template> <xsl:template name="explode" match= "*[contains(name(),'_') and not(substring(name(),1,1)='_') and not(substring(name(), string-length(name()))='_') ]"> <xsl:param name="pName" select="name()"/> <xsl:param name="pText" select="text()"/> <xsl:choose> <xsl:when test="not($pName)"> <xsl:value-of select="$pText"/> </xsl:when> <xsl:otherwise> <xsl:element name="{substring-before(concat($pName, '_'), '_')}"> <xsl:call-template name="explode"> <xsl:with-param name="pName" select="substring-after($pName, '_')"/> <xsl:with-param name="pText" select="$pText"/> </xsl:call-template> </xsl:element> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template match="/" name="fold" mode="compress"> <xsl:param name="pDoc" select="/"/> <xsl:choose> <xsl:when test="not($pDoc//*[name()=name(following-sibling::*[1])])"> <xsl:copy-of select="$pDoc"/> </xsl:when> <xsl:otherwise> <xsl:variable name="vrtfThisPass"> <xsl:apply-templates select="$pDoc/*" mode="compress"/> </xsl:variable> <xsl:call-template name="fold"> <xsl:with-param name="pDoc" select="ext:node-set($vrtfThisPass)"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template match="node()|@*" mode="compress"> <xsl:copy> <xsl:apply-templates select="@*|node()[1]" mode="compress"/> </xsl:copy> <xsl:apply-templates select="following-sibling::node()[1]" mode="compress"/> </xsl:template> <xsl:template match="*[name()=name(following-sibling::*[1])]" mode="compress"> <xsl:element name="{name()}"> <xsl:apply-templates mode="compress" select= "key('kSameNameAdj', concat(generate-id(..), '+',generate-id(preceding-sibling::*) ) )/node()"/> </xsl:element> <xsl:apply-templates mode="compress" select= "key('kSameNameAdj', concat(generate-id(..), '+',generate-id(preceding-sibling::*) ) ) [last()]/following-sibling::node()[1] "/> </xsl:template> </xsl:stylesheet>
when applied to the following XML document (provided, extended to be more complex):
<root> <x>This is:</x> <a_b_c>hello</a_b_c> <a_b_c_d>my</a_b_c_d> <a_b_c1>wonderful</a_b_c1> <a_b_c>world</a_b_c> <a_b>!</a_b> <y>The End</y> </root>
creates the desired, correct result :
<root> <x>This is:</x> <a> <b> <c>hello<d>my</d> </c> <c1>wonderful</c1> <c>world</c>!</b> </a> <y>The End</y> </root>
Explanation
0.1. This is a multi-pass conversion . The first pass converts the XML document to:
<root> <x>This is:</x> <a> <b> <c>hello</c> </b> </a> <a> <b> <c> <d>my</d> </c> </b> </a> <a> <b> <c1>wonderful</c1> </b> </a> <a> <b> <c>world</c> </b> </a> <a> <b>!</b> </a> <y>The End</y> </root>
0.2. Successive passes, each compresses any group of neighboring elements with the same name into one element with this name - then the result is processed recursively until there are no more groups of several adjacent siblings with the same name.
0.3. The first pass uses the rule .
0.4. The following passages use a small-sized personality in compression mode.