Re: [xsl] empty elements to filled without overlapping hierachies

Subject: Re: [xsl] empty elements to filled without overlapping hierachies
From: Jeni Tennison <jeni@xxxxxxxxxxxxxxxx>
Date: Wed, 12 Nov 2003 13:25:04 +0000
Hi James,

> What I'm wondering is what sort of strategy one could use in
> XSLT (including XSLT2) to change these to filled tags.  e.g.:
>   <Q value="WELBORNE." > NO BOUZE? NOR NO TOBACCO?</Q>

Let's try to find an XSLT 2.0 solution. It will use
<xsl:for-each-group> with the group-starting-with option, which groups
together items such that each group starts with the same thing.

When we match the <body> element, we want to copy it and its <TITLE>
child:

<xsl:template match="body">
  <body>
    <xsl:copy-of select="TITLE" />
    ...
  </body>
</xsl:template>

The first level of grouping is <SN>s. We want to group all the
child nodes of the <body> element, aside from the <TITLE> element,
into groups that start with a particular <SN> element. For each of
these groups, we want to create a <SN> element with the value
attribute from the original <SN>:

<xsl:template match="body">
  <body>
    <xsl:copy-of select="TITLE" />
    <xsl:for-each-group select="node() except TITLE"
                        group-starting-with="SN">
      <SN value="{@value}">
        ...
      </SN>
    </xsl:for-each-group>
  </body>
</xsl:template>

Next, we want to take the elements in that group (aside from the <SN>
element itself) and group them into groups starting with <Q> elements.
Now, there are three possible arrangements of these groups:

  1. The very first group might not start with a <Q> element; in that
     case, we just want to copy all the items in that group.

  2. The group might end with a <SSD> element, in which case we want
     to have a <Q> element that contains all the items in the group
     aside from that final <SSD> element (and the <Q> element itself).

  3. The group might end with something other than a <SSD> element, in
     which case we want to have a <Q> element that contains all the
     items in the group aside from the <Q> element itself

<xsl:template match="body">
  <body>
    <xsl:copy-of select="TITLE" />
    <xsl:for-each-group select="node() except TITLE"
                        group-starting-with="SN">
      <SN value="{@value}">
        <xsl:for-each-group select="current-group() except ."
                            group-starting-with="Q">
          <xsl:choose>
            <xsl:when test="not(self::Q)">
              <xsl:copy-of select="." />
            </xsl:when>
            <xsl:when test="current-group()[last()][self::SSD]">
              <Q value="{@value}">
                <xsl:copy-of select="(current-group() except .)
                                       [position() != last()]" />
              </Q>
              <xsl:copy-of select="current-group()[last()]" />
            </xsl:when>
            <xsl:otherwise>
              <Q value="{@value}">
                <xsl:copy-of select="current-group() except ." />
              </Q>
            </xsl:otherwise>
          </xsl:choose>
        </xsl:for-each-group>
      </SN>
    </xsl:for-each-group>
  </body>
</xsl:template>

This assumes that whitespace-only text nodes have been stripped from
the input tree using:

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

and the output looks best if you then include:

<xsl:output indent="yes" />

An XSLT 1.0 solution would be harder...

Cheers,

Jeni

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


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


Current Thread