[xsl] flattening nested xml

Subject: [xsl] flattening nested xml
From: "Andrew Welch" <awelch@xxxxxxxxxxxxxxx>
Date: Thu, 16 May 2002 15:57:27 +0100
(reasonbly long and hard)

I am having problems with nested xml.  Because it is nested, it makes it
very hard to convert it to html in the structure that I need... I will
explain a bit:

Consider the following xml:

<subpara1>
  <para>first</para>
  <subpara2>
    <para>second</para>
    <subpara3>
      <para>third</para>
    </subpara3>
  </subpara2>
</subpara1>

The output I am looking for is a numbered list, such as:

1	first
2	second
3 	third

This isnt so bad - I could just populate a table... but when tables get
large they get remarkably slow, even fixed-width tables, and its
difficult to with nested xml.  So, I use a combination of spans and
divs:

<div>
<span>1</span><span id="spacer">&#160;
</span><span>first</span>
</div>

(the closing and opening spans must have zero whitespace between them)

'spacer' gets sized to 5em - previousSibling.clientWidth to provide the
correct amount of space between the number and the data.

Ok, so far so good.  But if the data wraps, it will wrap beneath the
number and not indented where it should, so I contain the whole lot in a
div indented by 5em.  Of course, this simply shifts the whole lot to the
right by 5em, but if I include a text-indent of -5em it produces the
required output by reverse-indenting(?:) only the top line by -5em,
giving the impression of a neatly tabbed list.

Great.  But this has brought with it problems.  If the para's contain
any block elements in them it treats the next line as a first line and
indents that by -5em.  Also, it affects the widths of divs if they are
effectively the 'first line' (and caused numerous other issues other
markup, but were all solvable).

So, getting back to xslt, if the data *wasnt* nested, I could simply do:

<div>
  <span height=height of next sibling>1</span>
  <span id=spacer/>
  <span>data goes here</span>
</div>
<div>..next subpara</div>

(currently, the next subpara would be in the 'data goes here' bit
because of the nesting)

This would then make it easy.  So my questions are:

- Can I alter my templates so that the nesting doesnt matter?
- If I need to do a two pass transformation, what would the template
look like?
[the source xml cannot be changed]

Please paste in the snipped xml and xsl into your editor and apply it to
see what Im talking about.  Many thanks for any interest in this.  


Example xml:
============
<?xml version="1.0"?>
<root>
  <subpara1>
  <para>first</para>
  <subpara2>
    <para>second</para>
    <subpara3>
      <para>third</para>
    </subpara3>
  </subpara2>
  <subpara2>
    <para>wrap me to see what happens xxx xxx xxx xxx xxx xxx</para>
  </subpara2>
  <subpara2>
    <para>some text with a <list>block</list> item in the middle</para>
  </subpara2>
</subpara1>
</root>


Example xsl:
(you may need to put the script back on one line)
============
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
                version="1.0">

<xsl:template match="/">
<html>
  <head>
    <script>
   <xsl:comment>
   <![CDATA[
    function doSpacer() {
      try {
       for (var x = 0; x<spacer.length; x++)
        <!--*** here ****-->
        spacer[x].style.width = spacerWidth.scrollWidth -
spacer[x].previousSibling.scrollWidth;
      } catch(e){}
    }
   ]]>
   </xsl:comment>
   </script>
  </head>
  <body onload="doSpacer();">
  <xsl:apply-templates/>
  </body>
</html>
</xsl:template>

<xsl:template match="root">
  <div style="padding-left:5em;text-indent:-5em">
    <xsl:apply-templates/>
  </div>
  <span id="spacerWidth" style="width:5em">&#160;</span>
</xsl:template>

<xsl:template match="subpara1|subpara2|subpara3">
  <div>
  <xsl:apply-templates/>
  </div>
</xsl:template>

<xsl:template match="para">
  <span><xsl:number level="multiple" format="1.1.1"
count="subpara1|subpara2|subpara3"/>
  </span><span id="spacer">&#160;
  </span><span><xsl:apply-templates/></span>
</xsl:template>

<xsl:template match="list">
  <ul><li><xsl:value-of select="."/></li></ul>
</xsl:template>

</xsl:stylesheet>

Output should like:
===================
<html>
<head>
<META http-equiv="Content-Type" content="text/html; charset=UTF-16">
<script><!-- 
    function doSpacer() {
      try {
       for (var x = 0; x<spacer.length; x++)

        spacer[x].style.width = spacerWidth.scrollWidth -
spacer[x].previousSibling.scrollWidth;
      } catch(e){}
    }
    --></script></head>
<body onload="doSpacer();">
<div style="padding-left:5em;text-indent:-5em">
<div><span>1</span><span id="spacer"> 
  </span><span>first</span><div><span>1.1</span><span id="spacer"> 
  </span><span>second</span><div><span>1.1.1</span><span id="spacer"> 
  </span><span>third</span></div>
</div>
<div><span>1.2</span><span id="spacer"> 
  </span><span>wrap me to see what happens xxx xxx xxx xxx xxx
xxx</span></div>
<div><span>1.3</span><span id="spacer"> 
  </span><span>some text with a <ul><li>block</li></ul> item in the
middle</span></div>
</div>
</div><span id="spacerWidth" style="width:5em"> </span></body>
</html>


Cheers!
Andrew

---
Outgoing mail is certified Virus Free.
Checked by AVG anti-virus system (http://www.grisoft.com).
Version: 6.0.350 / Virus Database: 196 - Release Date: 17/04/2002
 

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


Current Thread