This is the mail archive of the xsl-list@mulberrytech.com mailing list .


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]

Re: How would you add depth to this flat XML source?


Eric,

>It seems to me that I can process each <firstitem> element and then apply 
>templates to all of the following-siblings that have their first <firstitem> 
>preceding-sibling equal to the current <firstitem>.  But, a) I can't seem
to get 
>the syntax right for this solution, b) this seems like an odd way to solve
the 
>problem, and c) somebody probably knows an easier way.

There are definitely other approaches (like you could use an xsl:for-each
rather than applying templates), but your approach is an eminently
reasonable one, and the biggest problem, the XPath expression, applies in
both, so let's see if we can get the syntax working.  I don't know how far
you've got with it, so forgive me if I go through everything just in case!

First you want to "process each <firstitem> element".  You do that with a
template matching that kind of element, so:

<xsl:template match="firstitem">
  ...
</xsl:template>

Second, and importantly, you *only* want to process those firstitem
elements.  That means that the template that matches whatever element your
firstitems and items are children of has to only be applying templates to
the firstitem.  I'll assume that they're children of an 'items' element:

<xsl:template match="items">
  <xsl:apply-templates select="firstitem" />
</xsl:template>

Third, you want to "apply templates to all of the following-siblings that
have their first <firstitem> preceding-sibling equal to the current
<firstitem>".  Applying templates to a node list is easy:

<xsl:apply-templates select="..." />

The thing that's difficult is generating the node list!  Let's take it a
step at a time:

1. "all of the following-siblings..."

Actually, you're not interested in *all* of the following-sibling nodes,
just in the 'item' elements.  The XPath for that is:

  following-sibling::item

In David Carlisle's human-speak, this is "all items that are following
siblings".

2. "...that have..."

Here you're narrowing the list you've got by putting an extra condition on
it, so you use a predicate:

  following-sibling::item[...]

3. "...their first <firstitem> preceding-sibling..."

or 'the first preceding-sibling that is a firstitem':

  preceding-sibling::firstitem[1]

4. "...the current <firstitem>..."

This could be a little tricky because when we're in the predicate that
we're currently in, the *context node* is an item.  We want the *current
node*, which we know is a firstitem (because that's what this template is
matching on) and that we get using:

  current()

5. "...equal to..."

Now we get into a bit of a murky area that I got confused by myself
recently.  We want to make sure that the *node* that is 'the first
preceding-sibling that is a firstitem' is the same as the *node* that is
'the current node'.  There are two ways of doing that:

a. generate a unique identifier for the two nodes and compare them:

  generate-id(preceding-sibling::firstitem[1]) = generate-id(current())

b. make a set that is the union of the two nodes and see how many elements
are in it - if they're the same node, then the answer is 1:

  count(preceding-sibling::firstitem[1] | current()) = 1

I'm going to use the first because it makes more sense to me.  We have our
XPath expression:

  following-sibling::item[generate-id(preceding-sibling::firstitem[1]) =
generate-id(current())]

So we slot that into our xsl:apply-templates, and put that into our
firstitem-matching template:

<xsl:template match="firstitem">
  ...
  <xsl:apply-templates
select="following-sibling::item[generate-id(preceding-sibling::firstitem[1])
 = generate-id(current())]" />
  ...
</xsl:template>

What have we forgotten?  Oh yes, the output!  For each 'firstitem', we want
to have a 'list', with the first 'item' having the content of the
'firstitem' and the other 'item's being copies of the original ones.

<xsl:template match="firstitem">
  <list>
    <item><xsl:value-of select="." /></item>
    <xsl:apply-templates
select="following-sibling::item[generate-id(preceding-sibling::firstitem[1])
 = generate-id(current())]" />
  </list>
</xsl:template>

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

I've tested the full stylesheet (below) in SAXON and it works.

Hope that helps,

Jeni

----8<----
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:output indent="yes" />

<xsl:template match="items">
  <xsl:apply-templates select="firstitem" />
</xsl:template>

<xsl:template match="firstitem">
  <list>
    <item><xsl:value-of select="." /></item>
    <xsl:apply-templates
select="following-sibling::item[generate-id(preceding-sibling::firstitem[1])
 = generate-id(current())]" />
  </list>
</xsl:template>

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

</xsl:stylesheet>
----8<----



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

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]