[XSL-LIST Mailing List Archive Home] [By Thread] [By Date]

Re: [xsl] Positional Grouping Problem - Add hierarchy to a flat structure


Subject: Re: [xsl] Positional Grouping Problem - Add hierarchy to a flat structure
From: Jeni Tennison <jeni@xxxxxxxxxxxxxxxx>
Date: Wed, 2 Jul 2003 10:29:20 +0100

Hi Tom,

> Basically, I want to replace the <BeginFile> node, with a <File>
> node. And then copy all elements between <BeginFile file="x"> and
> its corresponding <EndFile file="x"> as subelements to this new
> <File> node. Nesting <File> nodes if a second <BeginFile> node is
> found before the corresponding <EndFile> node is found. For each
> <BeginFile file="x"/>, there will always be a corresponding <EndFile
> file="x"/> element. And the LineNum attribute is not that important,
> but are in the original document, are valid, and can be used in the
> solution.

Thankfully, because you have filenames on both the <BeginFile> and
<EndFile> elements, this isn't too hard to do...

There are several approaches, but I find the easiest approach to be a
recursive step-by-step approach. Basically, we're going to step
through the elements in <LogFile> one by one. When we come across a
<BeginFile> element, we'll create a <File> element. For the content of
the <File> element, we'll process its following sibling, and for the
part after the new <File> element, we'll jump on to the element after
the matching <EndFile> element for the <BeginFile>.

Because we need to jump from a <BeginFile> element to its matching
<EndFile> element easily, it's a good idea to set up a key to index
the <EndFile> elements by their fileName:

<xsl:key name="EndFile" match="EndFile" use="@fileName" />

Within a template matching a <BeginFile> element, we can now find the
matching <EndFile> element with:

  key('EndFile', @fileName)

We then need to test whether there is a matching <EndFile> element or
not; if there isn't, we need an empty element:

<xsl:template match="BeginFile">
  <xsl:variable name="EndFile" select="key('EndFile', @fileName)" />
  <xsl:choose>
    <xsl:when test="$EndFile">
      <File lineNum="{@lineNum}" fileName="{@fileName}">
        ...
      </File>
      ...
    </xsl:when>
    <xsl:otherwise>
      <File lineNum="{@lineNum}" fileName="{@fileName}" />
      ...
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

In the case where we're creating a <File> element with content, the
content comes from processing the following siblings of the
<BeginFile> element recursively. The stuff after the <File> element
comes from processing whatever comes after the matching <EndFile>
element, again recursively:

<xsl:template match="BeginFile">
  <xsl:variable name="EndFile" select="key('EndFile', @fileName)" />
  <xsl:choose>
    <xsl:when test="$EndFile">
      <File lineNum="{@lineNum}" fileName="{@fileName}">
        <xsl:apply-templates select="following-sibling::*[1]" />
      </File>
      <xsl:apply-templates select="$EndFile/following-sibling::*[1]" />
    </xsl:when>
    <xsl:otherwise>
      <File lineNum="{@lineNum}" fileName="{@fileName}" />
      ...
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

When we're creating an empty element, the stuff after the empty <File>
element comes from processing the following siblings of the
<BeginFile> element recursively:

<xsl:template match="BeginFile">
  <xsl:variable name="EndFile" select="key('EndFile', @fileName)" />
  <xsl:choose>
    <xsl:when test="$EndFile">
      <File lineNum="{@lineNum}" fileName="{@fileName}">
        <xsl:apply-templates select="following-sibling::*[1]" />
      </File>
      <xsl:apply-templates select="$EndFile/following-sibling::*[1]" />
    </xsl:when>
    <xsl:otherwise>
      <File lineNum="{@lineNum}" fileName="{@fileName}" />
      <xsl:apply-templates select="following-sibling::*[1]" />
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

If you consider the content of a <File> element, most of it is made up
of other elements that you just want copied:

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

But to support the recursion through the siblings, once we've copied
that element, we need to go on to its immediately following sibling:

<xsl:template match="*">
  <xsl:copy-of select="." />
  <xsl:apply-templates select="following-sibling::*[1]" />
</xsl:template>

Currently the above template will process the <EndFile> elements as
well, but we know that as soon as we encounter an <EndFile> element,
we've come to the end of the content of the <File> element that we're
currently building. So we need a template matching <EndFile> elements
that just does nothing:

<xsl:template match="EndFile" />

Finally, to kick the whole process off, we have to apply templates to
the first element within the <LogFile> element:

<xsl:template match="LogFile">
  <xsl:apply-templates select="*[1]" />
</xsl:template>

And there you have it. If you need any help/explanation, let us know.

Cheers,

Jeni

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


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



Current Thread