[XSL-LIST Mailing List Archive Home] [By Thread] [By Date]

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


Subject: RE: [xsl] Grouping into a table (for vertical alignment)
From: Wendell Piez <wapiez@xxxxxxxxxxxxxxxx>
Date: Thu, 27 May 2004 11:58:20 -0400

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::input])])[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
Keywords