Nadia's question yesterday set me to thinking about how to turn flat XML
into hierarchical XML by using XSLT 2.0's for-each-group instruction.
To simplify matters, I wrote a solution against a generic data structure
rather than against Nadia's specific structure (from FrameMaker). Nadia:
I'm sure you can translate this solution to your specific source files. If
you do have trouble, though, I'll be happy to help.
I think this will become an increasingly common problem as more and more
companies try to use FrameMaker and Word as front ends to XML-based
single-sourcing document-production systems.
So, without further ado, here's the source file (flat as the proverbial
pancake):
<?xml version="1.0" encoding="UTF-8"?>
<doc>
<heading1>H11</heading1>
<para>P1</para>
<para>P2</para>
<para>P3</para>
<heading2>H21</heading2>
<para>P4</para>
<para>P5</para>
<heading3>H31</heading3>
<para>P6</para>
<heading2>H22</heading2>
<para>P7</para>
<para>P8</para>
<heading1>H12</heading1>
<para>P9</para>
</doc>
Here's the XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="doc">
<doc>
<xsl:for-each-group select="*" group-starting-with="heading1">
<heading1 title="{current-group()[1]}">
<xsl:for-each-group select="current-group()"
group-starting-with="heading2">
<xsl:choose>
<xsl:when test="current-group()[1]/name() = 'heading2'">
<heading2 title="{current-group()[1]}">
<xsl:for-each-group select="current-group()"
group-starting-with="heading3">
<xsl:choose>
<xsl:when test="current-group()[1]/name() =
'heading3'">
<heading3 title="{current-group()[1]}">
<xsl:apply-templates select="current-group()"/>
</heading3>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</heading2>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</heading1>
</xsl:for-each-group>
</doc>
</xsl:template>
<xsl:template match="heading1|heading2|heading3"/>
<xsl:template match="para">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
And here's the result (now hierarchical):
<?xml version="1.0" encoding="UTF-8"?>
<doc>
<heading1 title="H11">
<para>P1</para>
<para>P2</para>
<para>P3</para>
<heading2 title="H21">
<para>P4</para>
<para>P5</para>
<heading3 title="H31">
<para>P6</para>
</heading3>
</heading2>
<heading2 title="H22">
<para>P7</para>
<para>P8</para>
</heading2>
</heading1>
<heading1 title="H12">
<para>P9</para>
</heading1>
</doc>
The only mystery to me is why I had to use test="current-group()[1]/name()
= 'heading2'" rather than test="current-group()/heading2" or just
test="heading2". I'd love to hear the explanation for that one.
Thanks.
Jay Bryant
Bryant Communication Services
(presently consulting at Synergistic Solution Technologies)
--~------------------------------------------------------------------
XSL-List info and archive: http://www.mulberrytech.com/xsl/xsl-list
To unsubscribe, go to: http://lists.mulberrytech.com/xsl-list/
or e-mail: <mailto:xsl-list-unsubscribe(_at_)lists(_dot_)mulberrytech(_dot_)com>
--~--