Reputation: 1232
I have an XML file structured like so:
<root>
<a>...</a>
<b>...</b>
<a>...</a>
<a>...</a>
<comment>...</comment>
<a>...</a>
<b>...</b>
<comment>...</comment>
<a>...</a>
<b>...</b>
</root>
I would like to use XSLT to transform it so that each sequence of a
s and b
s is gathered into a single <div>
, like so:
<root>
<div>
<a>...</a>
<b>...</b>
<a>...</a>
<a>...</a>
</div>
<comment>...</comment>
<div>
<a>...</a>
<b>...</b>
</div>
<comment>...</comment>
<div>
<a>...</a>
<b>...</b>
</div>
</root>
My first attempt involved putting a <div>...</div>
inside the root
, and then wrapping each comment
in </div>...<div>
(note the reversal of the tags), but that's not allowed in XSLT. How can I do this?
The following question is related to mine, but it involves counting a fixed number of a
s and b
s, whereas I want to count a
s and b
s until I hit a comment
:
Upvotes: 0
Views: 113
Reputation: 116959
Here's how this can be accomplished in XSLT 1.0:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:key name="group-by-comment" match="root/*" use="count(preceding-sibling::comment)" />
<xsl:template match="/root">
<xsl:copy>
<!-- process children of root using group mode -->
<xsl:apply-templates select="*" mode="group"/>
</xsl:copy>
</xsl:template>
<!-- [group mode] for each child of root that is first in its group: -->
<xsl:template match="*[count(. | key('group-by-comment', count(preceding-sibling::comment))[1]) = 1]" mode="group">
<div>
<!-- process the entire group -->
<xsl:apply-templates select="key('group-by-comment', count(preceding-sibling::comment))"/>
</div>
</xsl:template>
<!-- [group mode] special rule for the dividing comment element -->
<xsl:template match="comment" mode="group">
<xsl:copy-of select="."/>
</xsl:template>
<!-- process group members -->
<xsl:template match="*">
<xsl:copy-of select="."/>
</xsl:template>
<!-- exclude the dividing comment element from the processed group -->
<xsl:template match="comment"/>
</xsl:stylesheet>
When applied to the following test input:
<root>
<alpha id="1"/>
<bravo id="2"/>
<charlie id="3"/>
<alpha id="4"/>
<bravo id="5"/>
<comment id="6"/>
<charlie id="7"/>
<alpha id="8"/>
<bravo id="9"/>
<comment id="10"/>
<alpha id="11"/>
<charlie id="12"/>
</root>
the result is:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<div>
<alpha id="1"/>
<bravo id="2"/>
<charlie id="3"/>
<alpha id="4"/>
<bravo id="5"/>
</div>
<comment id="6"/>
<div>
<charlie id="7"/>
<alpha id="8"/>
<bravo id="9"/>
</div>
<comment id="10"/>
<div>
<alpha id="11"/>
<charlie id="12"/>
</div>
</root>
Upvotes: 3
Reputation: 167401
John, consider to use XSLT 2.0 (exists since 2007) with an XSLT 2.0 processor like Saxon 9 and you can easily use
<xsl:template match="root">
<xsl:copy>
<xsl:for-each-group select="*" group-adjacent="boolean(self::a | self::b)">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<div>
<xsl:copy-of select="current-group()"/>
</div>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
Upvotes: 2