Subject: Re: [xsl] How to properly use Key elements From: Emmanuel Bégué <medusis@xxxxxxxxx> Date: Wed, 16 Oct 2013 08:25:10 +0200 |
Ok, there was an error in the previous solution, in that I hadn't noticed that the first port was in the first tr (the one that contains the ship name); so this line: <xsl:apply-templates select="following-sibling::tr" mode="port"/> should in fact read: <xsl:apply-templates select=".|following-sibling::tr" mode="port"/> in order to also process that first tr for the port name. Now, there's also probably more than one ship. If each ship is in its own table, the previous solution should work (substituting the first match="/table" with the new root element). If each ship is in the same table, a key can indeed be useful in order to associate each "port tr" with a ship; the problem is to identify the "ship tr". If we consider a "ship tr" is every tr that has more than 3 rows, this updated solution works: <?xml version="1.0" encoding="ISO-8859-1" ?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" exclude-result-prefixes="xsl"> <xsl:output method="xml" indent="yes" encoding="UTF-8"/> <xsl:key name="portsByShip" match="tr" use="preceding-sibling::tr[count(td) > 3][1]/td[1]"/> <xsl:template match="/root"> <xsl:apply-templates/> </xsl:template> <xsl:template match="*"> <xsl:apply-templates/> </xsl:template> <xsl:template match="thead|text()"/> <xsl:template match="tr[count(td) > 3]"> <!-- beware, the ship name should be a valid element name (no spaces, etc.) --> <xsl:variable name="shipName" select="td[1]"/> <xsl:element name="{$shipName}"> <xsl:apply-templates select=".|key('portsByShip', $shipName)" mode="port"/> </xsl:element> </xsl:template> <xsl:template match="tr" mode="port"> <xsl:variable name="numRows" select="count(td)"/> <xsl:for-each select="td[$numRows - 1]"> <port name="{.}"> <xsl:value-of select="following-sibling::td[1]"/> </port> </xsl:for-each> </xsl:template> </xsl:stylesheet> Applied on this input: <?xml version="1.0" encoding="ISO-8859-1"?> <root> <table> <thead> <tr> <th>Ship</th> <th>Route</th> <th>Port</th> <th>Date</th> </tr> </thead> <tbody> <tr> <td rowspan="3">Titanic</td> <td rowspan="2">Pacific South</td> <td>San Francisco</td> <td>dd/mm/yyyy</td> </tr> <tr> <td>San Diego</td> <td>dd/mm/yyyy</td> </tr> <tr> <td>Acapulco</td> <td>dd/mm/yyyy</td> </tr> <tr> <td rowspan="2">Pacific Central</td> <td>Acapulco</td> <td>dd/mm/yyyy</td> </tr> <tr> <td>Punteras CantC3n</td> <td>dd/mm/yyyy</td> </tr> <tr> <td>PanamC!</td> <td>dd/mm/yyyy</td> </tr> <tr> <td rowspan="3">Titanic2</td> <td rowspan="2">Pacific South</td> <td>San Francisco</td> <td>dd/mm/yyyy</td> </tr> <tr> <td>San Diego</td> <td>dd/mm/yyyy</td> </tr> <tr> <td>Acapulco</td> <td>dd/mm/yyyy</td> </tr> <tr> <td rowspan="2">Pacific Central</td> <td>Acapulco</td> <td>dd/mm/yyyy</td> </tr> <tr> <td>Punteras CantC3n</td> <td>dd/mm/yyyy</td> </tr> <tr> <td>PanamC!</td> <td>dd/mm/yyyy</td> </tr> </tbody> </table> </root> produces this output: <?xml version="1.0" encoding="UTF-8"?> <Titanic> <port name="San Francisco">dd/mm/yyyy</port> <port name="San Diego">dd/mm/yyyy</port> <port name="Acapulco">dd/mm/yyyy</port> <port name="Acapulco">dd/mm/yyyy</port> <port name="Punteras CantCB3n">dd/mm/yyyy</port> <port name="PanamCB!">dd/mm/yyyy</port> <port name="San Francisco">dd/mm/yyyy</port> </Titanic> <Titanic2> <port name="San Francisco">dd/mm/yyyy</port> <port name="San Diego">dd/mm/yyyy</port> <port name="Acapulco">dd/mm/yyyy</port> <port name="Acapulco">dd/mm/yyyy</port> <port name="Punteras CantCB3n">dd/mm/yyyy</port> <port name="PanamCB!">dd/mm/yyyy</port> </Titanic2> What the "portsByShip" does, is associate every tr with the first preceding tr that has more than 3 td child elements, and use the content of the first td child element as the key. Then <xsl:template match="tr[count(td) > 3]"> matches each ship and processes itself (.) and the following ones with the key. The rule "tr[count(td) > 3]" should be altered, both in the key and the template, in order to correspond to the actual input and recognize each ship without error. HTH Regards, EB On Wed, Oct 16, 2013 at 7:46 AM, Emmanuel BC)guC) <medusis@xxxxxxxxx> wrote: > Hello, > > I don't understand what keys are supposed to achieve in this case? > > The following solution produces the desired output without keys: > > <?xml version="1.0" ?> > <xsl:stylesheet version="1.0" > xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> > > <xsl:output method="xml" indent="yes"/> > > <xsl:template match="/table"> > <xsl:apply-templates/> > </xsl:template> > > <xsl:template match="*"> > <xsl:apply-templates/> > </xsl:template> > > <xsl:template match="thead|text()"/> > > <xsl:template match="tr[1]"> > <!-- beware, the ship name should be a valid element name (no > spaces, etc.) --> > <xsl:variable name="shipName" select="td[1]"/> > <xsl:element name="{$shipName}"> > <xsl:apply-templates select="following-sibling::tr" mode="port"/> > </xsl:element> > </xsl:template> > > <xsl:template match="tr" mode="port"> > <xsl:variable name="numRows" select="count(td)"/> > <xsl:for-each select="td[$numRows - 1]"> > <port name="{.}"> > <xsl:value-of select="following-sibling::td[1]"/> > </port> > </xsl:for-each> > </xsl:template> > > </xsl:stylesheet> > > This may be the solution you're refering to in your last paragraph, > and maybe the input is really more complex than the example given; but > if the structure is similar, this should be fast enough even on a long > input file...? > > Regards, > EB > > On Wed, Oct 16, 2013 at 1:35 AM, G. T. Stresen-Reuter > <tedmasterweb@xxxxxxxxx> wrote:
Current Thread |
---|
|
<- Previous | Index | Next -> |
---|---|---|
Re: [xsl] How to properly use Key e, Emmanuel Bégué | Thread | Re: [xsl] How to properly use Key e, Michael Kay |
Re: [xsl] How to properly use Key e, Emmanuel Bégué | Date | Re: [xsl] How to properly use Key e, Michael Kay |
Month |