xsl-list
[Top] [All Lists]

Re: [xsl] How to properly use Key elements

2013-10-16 10:04:50
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(_at_)gmail(_dot_)com> wrote:
On Oct 14, 2013, at 2:58 PM, Wendell Piez <wapiez(_at_)wendellpiez(_dot_)com> 
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 maybe…

<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 
recent…

... 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_^

--~------------------------------------------------------------------
XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list
To unsubscribe, go to: http://lists.mulberrytech.com/xsl-list/
or e-mail: <mailto:xsl-list-unsubscribe(_at_)lists(_dot_)mulberrytech(_dot_)com>
--~--