Reputation: 13
I need to convert a flat structure like this
<root>
<H>1</H>
<I>1-1</I>
<I>1-2</I>
<I>1-3</I>
<H>2</H>
<I>2-1</I>
<I>2-2</I>
</root>
in one like this
<root>
<H>
1
<I>1-1</I>
<I>1-2</I>
<I>1-3</I>
</H>
<H>
2
<I>2-1</I>
<I>2-2</I>
</H>
</root>
I'm trying the approach for-each on the source structure in this way
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="root">
<root>
<xsl:for-each select="child::node()">
<xsl:if test="self::H">
<H>
<xsl:copy-of select="@* | node()"/>
<xsl:call-template name="copyI"/>
</H>
</xsl:if>
</xsl:for-each>
</root>
</xsl:template>
<xsl:template name="copyI">
<xsl:for-each select="following-sibling::node()">
<xsl:choose>
<xsl:when test="self::H">
<!-- Should be fantastic to exit from the loop! -->
</xsl:when>
<xsl:when test="self::I"
<I>
<xsl:copy-of select="@* | node()"/>
</I>
<xsl:text> </xsl:text>
</xsl:when>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Unfortunately, the best result that I can reach is this
<root>
<H>
1
<I>1-1</I>
<I>1-2</I>
<I>1-3</I>
<I>2-1</I> wrong!
<I>2-2</I> wrong!
</H>
<H>
2
<I>2-1</I>
<I>2-2</I>
</H>
</root>
where <I>2-1</I>
and <I>2-2</I>
under H1 are wrong.
The problem is that I cannot escape from the template copyI when it finds and H. Since the source structure is flat (all siblings), I'm afraid that a recursive copyI doesn't help.
Any suggestion?
Thanks a lot. Nicola
Tomalak, thank you very much for your help.
Really, my needs is a bit more complicated and your solution is not scalable.
In my source is optionally presents a <C>
third level hierarchically dependent to <I>
<root>
<H>1</H>
<I>1-1</I>
<C>1-1-1</C>
<I>1-2</I>
<C>1-2-1</C>
<I>1-3</I>
<H>2</H>
<I>2-1</I>
<I>2-2</I>
<C>2-2-1</C>
</root>
and the expected output is
<root>
<H>1
<I>1-1<C>1-1-1</C></I>
<I>1-2<C>1-2-1</C></I>
<I>1-3</I>
</H>
<H>2
<I>2-1</I>
<I>2-2<C>2-2-1</C></I>
</H>
</root>
Thanks a lot again Nicola
Upvotes: 1
Views: 133
Reputation: 338178
This is simple:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes" />
<xsl:template match="root">
<xsl:copy>
<xsl:apply-templates select="H" />
</xsl:copy>
</xsl:template>
<xsl:template match="H">
<xsl:variable name="myId" select="generate-id()" />
<xsl:copy>
<xsl:copy-of select="text()[normalize-space() != '']" />
<xsl:copy-of select="
following-sibling::I[generate-id(preceding-sibling::H[1]) = $myId]
" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
For each <H>
you need to copy those following <I>
elements whose directly preceding <H>
is identical to the current one (node identity is established in XSLT by comparing the results of generate-id()
).
Output:
<root>
<H>1<I>1-1</I><I>1-2</I><I>1-3</I></H>
<H>2<I>2-1</I><I>2-2</I></H>
</root>
Indentation depends on the XSLT processor you use, but since insignificant whitespace is, well ... insignificant, you shouldn't worry too much about that.
You can achieve the same thing by using an XSL key, if you prefer that:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes" />
<xsl:key name="kI" match="I" use="generate-id(preceding-sibling::H[1])" />
<xsl:template match="root">
<xsl:copy>
<xsl:apply-templates select="H" />
</xsl:copy>
</xsl:template>
<xsl:template match="H">
<xsl:copy>
<xsl:copy-of select="text()[normalize-space() != '']" />
<xsl:copy-of select="key('kI', generate-id())" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The key can improve performance for large input documents.
Upvotes: 1