[xsl] Nested sections from flat structure

Subject: [xsl] Nested sections from flat structure
From: Linda van den Brink <lvdbrink@xxxxxxx>
Date: Thu, 10 May 2001 15:33:36 +0200
Hi all, 

I'm wondering about the best (shortest or most elegant or easiest) way to
transform an XML document with a 'flat' structure (like an HTML document
segmented with h* elements) to an XML document with nested sections. I've
been looking at ways to do this (XSL FAQ, Jeni's pages) and have succeeded
in doing it myself. But now I'm wondering if there are better ways than what
I've come up with. 

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

My solution is below. It's based on for-each. I'm hoping there's a better
way to do this, because this stylesheet is quite long and difficult to
read/understand for others. Is there a solution that *pushes* the elements
from the flat structure into sections, instead of pulling like mine does? 

SOURCE XML (remember this is one of 5 variants)
<?xml version="1.0" encoding="iso-8859-1"?>
<topic>
	<text>
		<p>first some intro text</p>
		<p><head>A</head>
		  goes with A.</p>
		<p><a name="A.1"/></p>
		<p>Text with A.1</p>
		<p>More text with A.1</p>
		<p><a name="A.2"/></p>
		<p>text with A.2</p>
		<p>Goes with A.2</p>
		<p><head>B</head>
		  Goes with B.</p>
		<p>Goes with  B.</p>
		<p><head>C</head>
		  Goes with C</p>
		<p>Goes with C</p>
	</text>
</topic> 

STYLESHEET <?xml version="1.0" encoding="iso-8859-1"?>
Hi all, 

I'm wondering about the best (shortest or most elegant or easiest) way to
transform an XML document with a 'flat' structure (like an HTML document
segmented with h* elements) to an XML document with nested sections. I've
been looking at ways to do this (XSL FAQ, Jeni's pages) and have succeeded
in doing it myself. But now I'm wondering if there are better ways than what
I've come up with. 

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

So, here's my solution. It's based on for-each. I'm hoping there's a better
way to do this, because this stylesheet is quite long and difficult to
read/understand for others. Is there a solution that *pushes* the elements
from the flat structure into sections, instead of pulling like mine does? 

SOURCE XML (remember this is one of 5 variants)
<?xml version="1.0" encoding="iso-8859-1"?>
<topic>
	<text>
		<p>first some intro text</p>
		<p><head>A</head>
		  goes with A.</p>
		<p><a name="A.1"/></p>
		<p>Text with A.1</p>
		<p>More text with A.1</p>
		<p><a name="A.2"/></p>
		<p>text with A.2</p>
		<p>Goes with A.2</p>
		<p><head>B</head>
		  Goes with B.</p>
		<p>Goes with  B.</p>
		<p><head>C</head>
		  Goes with C</p>
		<p>Goes with C</p>
	</text>
</topic> 

STYLESHEET <?xml version="1.0" encoding="is<?xml version="1.0"
encoding="iso-8859-1"?>
<xsl:stylesheet   version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform";>

<xsl:output method="xml" indent="yes"/>
<xsl:template match="text">
 <GeneralSection>
 <xsl:choose>
  <xsl:when test="p[a[@name]][not(preceding-sibling::p[head])]">
   <xsl:for-each
select="p[not(preceding-sibling::p/a[@name])][not(a[@name])]">
    <xsl:call-template name="blockcontent"/>
   </xsl:for-each>
   <xsl:for-each select="p[a[@name]]">
   <xsl:variable name="id" select="generate-id()"/>
   <GeneralSection ID="{a/@name}">
    <xsl:choose>
     <xsl:when test="following-sibling::p[head]">
      <xsl:for-each
select="following-sibling::p[not(preceding-sibling::p[head])][not(self::p[he
ad])]">
       <xsl:call-template name="blockcontent"/>
      </xsl:for-each>
      <xsl:for-each select="following-sibling::p[head]">
      <xsl:variable name="headid" select="generate-id()"/>
       <GeneralSection>
        <SectionTitle><xsl:value-of select="head"/></SectionTitle>
        <Paragraph><xsl:apply-templates
select="node()[not(self::head)]"/></Paragraph>
        <xsl:for-each
select="following-sibling::p[generate-id(preceding-sibling::p[head][1])=$hea
did][not(head)]
 
[generate-id(preceding-sibling::p[a[@name]][1])=$id][not(a[@name])]">
         <xsl:call-template name="blockcontent"/>
        </xsl:for-each>
       </GeneralSection>
      </xsl:for-each>
     </xsl:when>
     <xsl:otherwise>
      <xsl:for-each
select="following-sibling::p[generate-id(preceding-sibling::p[a[@name]][1])=
$id][not(a[@name])]">
       <xsl:call-template name="blockcontent"/>
      </xsl:for-each>
     </xsl:otherwise>
    </xsl:choose>
   </GeneralSection>
   </xsl:for-each>
  </xsl:when>
  <xsl:when test="p[head][not(preceding-sibling::p[a[@name]])]">
   <xsl:for-each select="p[not(preceding-sibling::p[head])][not(head)]">
    <xsl:call-template name="blockcontent"/>
   </xsl:for-each>
   <xsl:for-each select="p[head]">
   <xsl:variable name="id" select="generate-id()"/>
   <GeneralSection>
    <SectionTitle><xsl:value-of select="head"/></SectionTitle>
    <Paragraph><xsl:apply-templates
select="node()[not(self::head)]"/></Paragraph>
    <xsl:choose>
     <xsl:when test="following-sibling::p[a[@name]]">
      <xsl:for-each select="following-sibling::p[a[@name]]">
      <xsl:variable name="headid" select="generate-id()"/>
       <GeneralSection ID="{a/@name}">
         <xsl:for-each
select="following-sibling::p[generate-id(preceding-sibling::p[a[@name]][1])=
$headid][not(a)]
 
[generate-id(preceding-sibling::p[head][1])=$id][not(head)]">
         <xsl:call-template name="blockcontent"/>
        </xsl:for-each>
       </GeneralSection>
      </xsl:for-each>
     </xsl:when>
     <xsl:otherwise>
      <xsl:for-each
select="following-sibling::p[generate-id(preceding-sibling::p[head][1])=$id]
[not(head)]">
       <xsl:call-template name="blockcontent"/>
      </xsl:for-each>
     </xsl:otherwise>
    </xsl:choose>
   </GeneralSection>
   </xsl:for-each>
  </xsl:when>
  <xsl:otherwise>
   <xsl:call-template name="blockcontent"/>
  </xsl:otherwise>
 </xsl:choose>
 </GeneralSection>
</xsl:template>

<xsl:template name="blockcontent">
 <xsl:for-each select="descendant-or-self::p">
  <Paragraph><xsl:apply-templates/></Paragraph>
 </xsl:for-each>
</xsl:template>
</xsl:stylesheet>

OUTPUT XML
<?xml version="1.0" encoding="utf-8"?>
<GeneralSection>
   <Paragraph>first some intro text</Paragraph>
   <GeneralSection>
      <SectionTitle>A</SectionTitle>
      <Paragraph>goes with A.</Paragraph>
      <GeneralSection ID="A.1">
         <Paragraph>Text with A.1</Paragraph>
         <Paragraph>More text with A.1</Paragraph>
      </GeneralSection>
      <GeneralSection ID="A.2">
         <Paragraph>text with A.2</Paragraph>
         <Paragraph>Goes with A.2</Paragraph>
      </GeneralSection>
   </GeneralSection>
   <GeneralSection>
      <SectionTitle>B</SectionTitle>
      <Paragraph>Goes with B.</Paragraph>
      <Paragraph>Goes with B.</Paragraph>
   </GeneralSection>
   <GeneralSection>
      <SectionTitle>C</SectionTitle>
      <Paragraph>Goes with C</Paragraph>
      <Paragraph>Goes with C</Paragraph>
   </GeneralSection>
</GeneralSection>


Suggestions? 

Linda
OUTPUT XML
<?xml version="1.0" encoding="utf-8"?>
<GeneralSection>
   <Paragraph>first some intro text</Paragraph>
   <GeneralSection>
      <SectionTitle>A</SectionTitle>
      <Paragraph>goes with A.</Paragraph>
      <GeneralSection ID="A.1">
         <Paragraph>Text with A.1</Paragraph>
         <Paragraph>More text with A.1</Paragraph>
      </GeneralSection>
      <GeneralSection ID="A.2">
         <Paragraph>text with A.2</Paragraph>
         <Paragraph>Goes with A.2</Paragraph>
      </GeneralSection>
   </GeneralSection>
   <GeneralSection>
      <SectionTitle>B</SectionTitle>
      <Paragraph>Goes with B.</Paragraph>
      <Paragraph>Goes with B.</Paragraph>
   </GeneralSection>
   <GeneralSection>
      <SectionTitle>C</SectionTitle>
      <Paragraph>Goes with C</Paragraph>
      <Paragraph>Goes with C</Paragraph>
   </GeneralSection>
</GeneralSection>


Suggestions? 

Linda

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


Current Thread