Re: [xsl] How to properly use Key elements

Subject: Re: [xsl] How to properly use Key elements
From: Wendell Piez <wapiez@xxxxxxxxxxxxxxx>
Date: Wed, 16 Oct 2013 11:04:33 -0400
Ted,

I'm responding to this since you addressed it to me, although you also
seem to have this in hand thanks to Emmanuel and Mike.

Mastering keys in XSLT is definitely worth the time and effort invested.

On Tue, Oct 15, 2013 at 7:35 PM, G. T. Stresen-Reuter
<tedmasterweb@xxxxxxxxx> wrote:
> On Oct 14, 2013, at 2:58 PM, Wendell Piez <wapiez@xxxxxxxxxxxxxxx> wrote:
> Indeed, were 2.0 an option, I'd definitely be using it! Unfortunately I'm
stuck with a 1.0 implementation, but I could be mistaken. We're doing the
transformations through PHP (5.3). I've just always assumed  libxslt (or
whatever it's called) was a 1.0-only implementation. Please: prove me wrong
and save me this extra work!

Sorry, I can't quite manage that today. What I offered was only the
obligatory caution, in case you needed it, which you didn't. :-)

...
>> b. use='tr[etc]' will use the values of (certain) 'tr' children of
>> your matched 'td' elements as key values, but 'td' never has 'tr'
>> children, so even if the key matched, you'd have empty string key
>> values.
>
> So, to clarify, the USE attribute must be a child attribute or element of
the MATCH attribute. Is this correct?

Not quite. @use accepts any XPath expression, whose returned values
are cast to strings (in XSLT 1.0) for purposes of the key lookup. But
the XPath is evaluated relative to the node matched by the key. So
your expression "tr[etc]" will be evaluated from the context of 'td'
elements, since the key matches those.

>> To devise a correct solution, I suggest
>>
>> 1. Considering whether you can't use XSLT 2.0 for-each-group.
>
> Can't. :-(

Meh! I can see there's still a market for XSLT 1.0 experts (which is
fortunate for me).

>> 2. If not, consider whether doing this in two passes would simplify
>> the problem. (In the first pass you would label the td elements with
>> their information types, simplifying the declaration of the key for
>> the second pass.)
>
> Ideal but in this particular case I'm doing this for a client and such an
approach *might* imply a change to their base processing system. I do think,
though, that I could probably create a variable (via exslt extensions)
consisting of a fragment marked up as suggested and then operate on the
fragment.

Also meh. I suppose exslt is fair enough, but I also hate
micropipelining in a language not really designed to support it. (Or
designed to support it and then restricted formally from doing so,
which may be closer to the case.)

>> 3. If neither of these, please clarify the logic whereby you know
>> which td is of which type.
>
> It may not have been clear in my sample markup so let me put it this way: we
are processing HTML tables of data. Each table contains "sections". The start
of each section is indicated by the presence of 4 TD elements in the first
row. Other rows only have 2 or 3 TD elements. The first TD element in the
first row has a ROWSPAN attribute running the length of the rows for that
section. This TD element has a value that represents the group name (is what
we'd like to group by).
>
> Given your clarification about how keys work, it sounds like I need
something like this:
>
> <xsl:key
>        name="ports-by-ship"
>        match="tr"
>        use="tr[count(td) = 4]/td[position() = 1]"
> />

I am guessing you need something like

<xsl:key name="ports-by-ship"
       match="tr[count(td) = 4]/td[3]"
       use="../td[1]"/>

<xsl:key name="ports-by-ship"
       match="tr[count(td) != 4]/td[1]"
       use="../preceding-sibling::tr[count(td)=4][1]/td[1]"/>

Note the use of two declarations for the same key. This is permitted,
and very useful in cases like this. Here, the first declaration
catches the ports from the rows with four cells, the second catches
the ports from the others.

Untested!

...
> but I have to think that this would only give me the first row of TD
elements, and not all of those that follow. I suspect that I might need
something like "following-sibling::td" in the MATCH attribute or maybeb&
>
> <xsl:key
>        name="ports-by-ship"
>        match="td"
>        use="td[ancestor::tr[count(td) = 4]][1]"
> />
>
> and even then I suspect I'll get ALL the ancestors instead of just the most
recentb&

... yes ... the key to keys is to match the elements you wish to
retrieve, and find the values you want to use for the retrieval
relative to those elements.

>> At a higher level, I think the essence of the problem here is that you
>> aren't accounting for evaluation context properly in devising your
>> XPaths. Some review of the design and functionality of keys (apart
>> from how to do Muenchian grouping) would be effort well spent. Keys
>> are extremely useful in XSLT 2.0 as well! though no longer so
>> necessary for grouping.
>
> I've read and re-read everything I could find about keys but it's just one
of those things that for me, takes a while to sink in, especially if I haven't
seen it in a while. I fully understand their power and have used them
successfully in the past, but I'm just a bit lost on this one.

They take practice, but gradually things click into place. Plus the
practice is good for XPath in general since it's all about relative
path traversal.

Good luck!
Wendell

_____oo_________o_o___ooooo____ooooooo_^

Current Thread