RE: [xsl] Grouping into a table (for vertical alignment)

Subject: RE: [xsl] Grouping into a table (for vertical alignment)
From: "Daniel Joshua" <daniel.joshua@xxxxxxxxxxxx>
Date: Fri, 28 May 2004 11:01:17 +0800
Thanks Wendell... trying to figure it out now :p

>Jeni works through one of these in entry 12 in the FAQ
>page at http://www.dpawson.co.uk/xsl/sect2/N4486.html#d4726e727.

Looking at it too, entry 12 actually allows me to handle my problem without
a <xsl:key> ...

I got a question on entry 17:

> <xsl:apply-templates mode="inGroup"
>   select="following-sibling::*[position() &lt; $vGroupLength]"/>

"vGroupLength" is the number of elements in the group.

How does the "position()" work when used in the above manner,
is it just the "context position" within (1) the node list of only
"following-sibling"
or (2) a node list of all siblings? I guess the first, but I just want to
confirm.

I really appreciate all the help I am getting from this list. Thanks all.


Regards,
Daniel


-----Original Message-----
From: Wendell Piez [mailto:wapiez@xxxxxxxxxxxxxxxx]
Sent: Thursday, 27 May, 2004 11:58 PM
To: xsl-list@xxxxxxxxxxxxxxxxxxxxxx
Subject: RE: [xsl] Grouping into a table (for vertical alignment)


Hi Daniel,

Thanks for the more detailed specification. It makes your problem clearer.

At 02:13 AM 5/27/2004, you wrote:
>Currently the input XML structure has a <form> tag which can have either:
>
>  1) "presentation tags" like <text>, <image>, etc which require to fill
the
>whole width (in the HTML, these are already placed in <div> to get its own
>line WITHOUT any <table>)
>
>  2) "entry tags" like <input>, <password>, <checkbox>, etc which require
to
>be separated into a label and a value (in HTML, I want these to be placed
in
>a <table>, EITHER a new table if the previous tag was a "presentation tag"
>OR a new row in the previous table if the previous tag was an "entry tag")
>
>
>The special considerations for this are:
>
>  A) there can be any sequence of "presentation tags" and "entry tags"
>
>  B) if there are "presentation tags" in-between groups of "entry tags",
then
>each separated group of entry tags will have their own table.
>
>  C) I do _not_ want to put "presentation tags" in a table in anyway (eg.
use
>a single column with "colspan=2")

This appears to be a grouping problem. You want to group sequences of entry
tags into tables, leaving presentation tags, which are arbitrarily mixed
among them, outside the tables.

(Be warned that since grouping problems are up-conversions, they're outside
the domain of problems that are very easy with XSLT 1.0. Yet since they're
so common we've worked out ways of dealing with them. Note that given
appropriate wrapper elements in the input, this up-conversion would be
unnecessary since the grouping would already be there.)

There are a couple of different approaches to this, including using keys to
achieve positional grouping, or using templates in a special mode to "walk"
the input tree node by node testing whether the next element should be in
the table.

Your input, again:

   <form>
     <name>form</name>
     <action>submit.do</action>
     <method>post</method>
     <content>
      <text>
         <value>Please enter your Name and Password.</value>
         <class>instruction</class>
       </text>
      <input>
         <name>username</name>
         <label>Name: </label>
         <value></value>
         <class>mandatory</class>
       </input>
      <password>
         <name>password</name>
         <label>Password :</label>
         <value></value>
         <class>mandatory</class>
       </password>
      <text>
         <value>All attempts are logged.</value>
         <class>warning</class>
       </text>
     </content>
   </form>

Let's look at the key-based solution. The trick here is that elements to be
grouped need to be associated with particular elements -- one for each
group -- as "handles" to allow them to be grouped. Assuming your
"presentation tags" are <text> and your "input tags" are <password> and
<input>, it's convenient in this case to define the handle as "any password
or input not preceded by a text", namely those elements that will become
the first in any particular table. We need to associate all passwords or
inputs with these particular passwords or inputs (the first in their
group). This can be done with a key like this:

<xsl:key name="inputs-by-handle" match="password | input"
   use="generate-id((self::node() | preceding-sibling::password |
preceding-sibling::input)[not(preceding-sibling::*[1][self::password|self::i
nput])])[last()]"/>

I know that's an abominable snowman so let's break down the XPath in the
'use' attribute:

(self::node() | preceding-sibling::password | preceding-sibling::input)

collects the context node (each password or input) in a group with all its
preceding sibling passwords and inputs

[not(preceding-sibling::*[1][self::password|self::input])]

a predicate operates on that group, filtering those whose immediately
preceding sibling element (preceding-sibling::*[1]) is not a password or
input. (Either it's a text, or it doesn't exist since the node is the first
of its siblings.)

[last()]

finally, a predicate selects the last of these, in document order.

Wrapping the XPath in generate-id() returns a unique identifier for this
node.

Consequently, whenever we process a password or input element, we can ask
for all the passwords and inputs that "belong" with it.

Consequently we can have a template to match passwords and inputs like this:

<xsl:template match="password | input">
   <xsl:if test="key('inputs-by-handle',generate-id()">
   <!-- this test is true only if this password or input is a handle
        (that is, is the first in a run of them, even if a run of one only)
        since those that are not, will retrieve empty node sets when the
        key() is called with their unique IDs -->
     <table>
       <xsl:apply-templates select="key('inputs-by-handle',generate-id()"
         mode="make-row"/>
         <!-- match the passwords and inputs again in the 'make-row' mode to
              create our table rows -->
     </table>
   </xsl:if>
</xsl:template>

Note that if we have more element types than just text, password, input,
our XPaths become more complex.

This problem will presumably be much easier in XSLT 2.0, and presumably
Jeni or Mike K or someone will soon show us how. :->

Also, if you think this is too baroque you could try the other technique,
the "forward walk". Jeni works through one of these in entry 12 in the FAQ
page at http://www.dpawson.co.uk/xsl/sect2/N4486.html#d4726e727. Or maybe a
friendly XSLTer with some extra time on their hands will work it out for
you.

Finally, sometimes when grouping gets really complex (your element types
proliferate) it's easiest to work in two passes, one of them to mark the
elements to be grouped according to the grouping criteria, and the second
to group according to the marks. (To do two passes in a client, however,
you need a node-set() extension function not available in all processors.)

I hope this helps,
Wendell


======================================================================
Wendell Piez                            mailto:wapiez@xxxxxxxxxxxxxxxx
Mulberry Technologies, Inc.                http://www.mulberrytech.com
17 West Jefferson Street                    Direct Phone: 301/315-9635
Suite 207                                          Phone: 301/315-9631
Rockville, MD  20850                                 Fax: 301/315-8285
----------------------------------------------------------------------
   Mulberry Technologies: A Consultancy Specializing in SGML and XML
======================================================================

Current Thread