Re: [xsl] Nested sections from flat structure

Subject: Re: [xsl] Nested sections from flat structure
From: Jeni Tennison <mail@xxxxxxxxxxxxxxxx>
Date: Mon, 14 May 2001 11:30:00 +0100
Hi Linda,

> My source documents are a bit troublesome. They have a structure
> like HTML. There are two elements that indicate start of a section,
> <head> and <a name="">. Both appear inside a p element. A source
> document can be:
> - without subsections
> - with subsections indicated by <head>
> - with subsections indicated by <a name="">
> - with subsections indicated by <a name="">, and <head> subsections
> inside the first subsections
> - with subsections indicated by <head>, and <a name=""> subsections
> inside the first subsections

When you have troublesome source then it's sometimes better to take a
two-pass approach on the problem - transform it into something more
approachable, and then transform *that* into the output that you want.

Your source would be a lot more approachable if it was clear what was
a 'top' section and what were sections underneath them.  In the
following stylesheet, I first convert the source into:

<GeneralSection/>
<Paragraph>first some intro text</Paragraph>
<TopSection/>
<SectionTitle>A</SectionTitle>
<Paragraph>goes with A.</Paragraph>
<SubSection ID="A.1"/>
<Paragraph>Text with A.1</Paragraph>
<Paragraph>More text with A.1</Paragraph>
<SubSection ID="A.2"/>
<Paragraph>text with A.2</Paragraph>
<Paragraph>Goes with A.2</Paragraph>
<TopSection/>
<SectionTitle>B</SectionTitle>
<Paragraph>Goes with B.</Paragraph>
<Paragraph>Goes with  B.</Paragraph>
<TopSection/>
<SectionTitle>C</SectionTitle>
<Paragraph>Goes with C</Paragraph>
<Paragraph>Goes with C</Paragraph>

And from there using the Muenchian method to create the hierarchy.

The advantage is that both transformations are relatively
straight-forward; the disadvantage is that you need to either use a
node-set extension in your stylesheet or split the stylesheet in two
and use the second on the output of the first.

If you want to use a one-pass approach, like your stylesheet is at the
moment, the only suggestion I'd make for simplifying it is to split up
all those xsl:for-eaches into separate templates, using modes to
distinguish between the different types of processing you do within
each.

Anyway, here's my approach (unless you're using a processor that
supports exsl:node-set(), you'll need to change the namespace for the
node-set() function):

<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
                xmlns:exsl="http://exslt.org/common";
                extension-element-prefixes="exsl">

<xsl:output method="xml" indent="yes" />

<xsl:strip-space elements="*" />

<xsl:variable name="top" select="topic/text/p[head or a/@name][1]" />

<xsl:template match="/">
   <xsl:variable name="sections">
      <GeneralSection />
      <xsl:apply-templates mode="convert" />
   </xsl:variable>
   <xsl:apply-templates select="exsl:node-set($sections)/GeneralSection"
                        mode="group" />
</xsl:template>

<xsl:template match="p" mode="convert">
   <Paragraph><xsl:apply-templates /></Paragraph>
</xsl:template>

<xsl:template match="p[head]" mode="convert">
   <xsl:choose>
      <xsl:when test="$top/head"><TopSection /></xsl:when>
      <xsl:otherwise><SubSection /></xsl:otherwise>
   </xsl:choose>
   <SectionTitle><xsl:apply-templates select="head" /></SectionTitle>
   <Paragraph>
      <xsl:apply-templates select="node()[not(self::head)]" />
   </Paragraph>
</xsl:template>

<xsl:template match="p[a/@name]" mode="convert">
   <xsl:choose>
      <xsl:when test="$top/a/@name">
         <TopSection ID="{a/@name}" />
      </xsl:when>
      <xsl:otherwise><SubSection ID="{a/@name}" /></xsl:otherwise>
   </xsl:choose>
</xsl:template>

<xsl:key name="children" match="Paragraph | SectionTitle"
         use="generate-id(preceding-sibling::*
                             [self::GeneralSection or self::TopSection or
                              self::SubSection][1])" />
<xsl:key name="children" match="TopSection"
         use="generate-id(preceding-sibling::GeneralSection[1])" />
<xsl:key name="children" match="SubSection"
         use="generate-id(preceding-sibling::TopSection[1])" />

<xsl:template match="GeneralSection | TopSection | SubSection"
              mode="group">
   <GeneralSection>
      <xsl:copy-of select="@*" />
      <xsl:apply-templates select="key('children', generate-id())"
                           mode="group" />
   </GeneralSection>
</xsl:template>

<xsl:template match="Paragraph" mode="group">
   <xsl:copy-of select="." />
</xsl:template>

</xsl:stylesheet>

I hope that helps,

Jeni

---
Jeni Tennison
http://www.jenitennison.com/



 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list


Current Thread