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: Grouping repeating elements


On Wed, 11 Jul 2001 11:57:07 +0100, Wendell  wrote:

>Hi Till,
>
>At 10:12 AM 7/11/01, Trevor wrote:
>[snip]
>>Have a template matching "name".
>>In the body of this template create the target <person> element,
>>copying the matched <name> element, e.g. using copy-of.
>>To handle elements which always appear, use copy-of with
>>following-sibling to get the *first* element after the name of the
>>given type.  Get this working first, and see that 'phil' ends up with
>>age 28.  Make sure everyone gets just one age!
>>When you have that XPath right, you now have to add a condition that
>>the node you find matches the current <name> node and not a later one.
>>So, add a further condition that the <name> node before the one you
>>have found is the same node as the one matched in your template: use
>>preceding-sibling and generate-id().
>>
>>Have a try at this yourself, if you get stuck ask again.
>
>And after you've got it working, ask again and we'll tell you about the 
>Advanced Technique using keys (the Topic of the Week) to simplify such 
>"levitation" problems (by which I mean making implicit structures explicit)....
>
>Cheers,
>Wendell
>
I think what Wendell is referring to is that instead of writing this
(tested with Saxon 6.0.2) (a) :

<xsl:template match="name">
   <person>
       <xsl:variable name="id" select="generate-id()" />
       <xsl:copy-of select="."/>
       <xsl:copy-of select="following-sibling::firstname[1]"/>
       <xsl:copy-of select="following-sibling::age[1]
            [generate-id(preceding-sibling::name[1])=$id]" />
   </person>
</xsl:template>
<xsl:template match="firstname|age" />

You can write (b):

<xsl:key
    name="following-name"
    match="firstname|age"
    use="generate-id(preceding-sibling::name[1])"
/>
<xsl:template match="name">
   <person>
       <xsl:copy-of select=".|key('following-name', generate-id())" />
   </person>
</xsl:template>
</xsl:stylesheet>
<xsl:template match="firstname|age" />

This is not exactly equivalent:
 - if your input contained <name/><age/><age/><name/>... then the key
method copies both <age/> elements, while (a) copies only the first.
This may or may not be what is needed.
 - the key method copies elements in the order they appear in the
input document, rather than in an order you specify (this can be
changed, but then you lose some of the brevity which is a main
attraction of using keys)
 - if you are dealing with large documents, the performance
characteristic of the first method is better, as long as you have a
processor which takes the obvious short cut when it sees expressions
like following-sibling::x[1].  If it doesn't, the key method is
better, but you are likely in trouble with either approach.

BTW you can get from (a) to (b) with some simple algebra.  The core of
method (a) is the expression:

  following-sibling::age[1]
            [generate-id(preceding-sibling::name[1])=$id]

If there is at most one <age/> element between each <name/> then this
is equivalent to:

   //age[generate-id(preceding-sibling::name[1])=$id]

which is an example of a general form 

    //pattern[f(.)=string-value]

where f is a function returning a string.  Whenever you see this you
can rewrite it as:

   <xsl:key name="k" match="pattern" use="f(.)" />
and then
   key ('k', string-value)

I hope somebody else enjoyed that too ;-)

Regards,
Trevor Nash
--
Traditional training & distance learning,
Consultancy by email

Melvaig Software Engineering Limited
voice:     +44 (0) 1445 771 271 
email:     tcn@melvaig.co.uk

 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]