[XSL-LIST Mailing List Archive Home] [By Thread] [By Date] [Recent Entries] [Reply To This Message] RE: 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: 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
|
PURCHASE STYLUS STUDIO ONLINE TODAY!Purchasing Stylus Studio from our online shop is Easy, Secure and Value Priced! Download The World's Best XML IDE!Accelerate XML development with our award-winning XML IDE - Download a free trial today! Subscribe in XML format
|