Subject: Re: [xsl] Seeking help on Grouping distingt sub-elements From: Jeni Tennison <jeni@xxxxxxxxxxxxxxxx> Date: Mon, 6 May 2002 16:51:42 +0100 |
Hi Jim, > Below is the input XML that I'm starting with. The goal is for all > of the chassis in a shipment (in a company, in an order), where the > chassis have the same <commonid> value, combine those chassis into > one chassis element, where that chassis element has the 'common' > elements of each of the matching chassis (<commonid> and > <commonelement>) and to then add a new tag that enumerates each of > the specific <specificelement> elements from the group. Note that > one twist on this is that the <commonelement> element can have a > different value in each <chassis> group member and that the last > <commonelement> encountered for a group of <chassis> elements (in > the same <shipment>) with the same <commonid> is the one that 'wins' > - ie, is written to the output document - also note that the values > don't determine which one 'wins' - I just used 1's and 2's for > illustrative purposes - its the position (ie, last encountered in > the group) that matters. Let's start with the XSLT 2.0 method, because that makes it easier to see what's going on. Within each shipment... <xsl:template match="shipment"> <shipment> <xsl:copy-of select="shiptoid" /> ... </shipment> </xsl:template> You want to group together all the chassis elements by their commonid... <xsl:template match="shipment"> <shipment> <xsl:copy-of select="shiptoid" /> <xsl:for-each-group select="chassis" group-by="commonid"> <chassis> <xsl:copy-of select="commonid" /> ... </chassis> </xsl:for-each-group> </shipment> </xsl:template> You want the commonelement to be copied from the *last* chassis in the group of chassis elements that you're looking at... <xsl:template match="shipment"> <shipment> <xsl:copy-of select="shiptoid" /> <xsl:for-each-group select="chassis" group-by="commonid"> <chassis> <xsl:copy-of select="commonid" /> <xsl:copy-of select="current-group()[last()]/commonelement" /> ... </chassis> </xsl:for-each-group> </shipment> </xsl:template> And then you want to provide a newtag wrapper element, inside which you copy all the specificelement children of the chassis elements in the current group: <xsl:template match="shipment"> <shipment> <xsl:copy-of select="shiptoid" /> <xsl:for-each-group select="chassis" group-by="commonid"> <chassis> <xsl:copy-of select="commonid" /> <xsl:copy-of select="current-group()[last()]/commonelement" /> <newtag> <xsl:copy-of select="current-group()/specificelement" /> </newtag> </chassis> </xsl:for-each-group> </shipment> </xsl:template> You can create the rest of the tree with a simple identity template: <xsl:template match="node()|@*"> <xsl:copy> <xsl:apply-templates select="@*|node()" /> </xsl:copy> </xsl:template> The XSLT 1.0 way is basically the same, except that there's no xsl:for-each-group instruction, so we'll use the Muenchian Method instead. We need to group together the chassis elements by both their commonid *and* (I think you might have missed this bit out) an id for the shipment. We'll generate that id through generate-id(), just because it's a bit shorter than using a combination of the company name and the shiptoid. So the key needs to look like: <xsl:key name="chassis" match="chassis" use="concat(generate-id(parent::shipment), '+', commonid)" /> or just: <xsl:key name="chassis" match="chassis" use="concat(generate-id(..), '+', commonid)" /> Given that we're processing a chassis element, we can then get the 'current group' using the key function as follows: <xsl:variable name="current-group" select="key('chassis', concat(generate-id(..), '+', commonid))" /> To get the *unique* chassis elements within the current shipment, we need to go through them one by one and see if they're the first one with the particular commonid in that shipment, using the familiar expression: chassis[generate-id() = generate-id(key('chassis', concat(generate-id(..), '+', commonid))[1])] or: chassis[count(.|key('chassis', concat(generate-id(..), '+', commonid))[1]) = 1] Putting these into the XSLT 2.0 template we put together about, we get the following: <xsl:template match="shipment"> <shipment> <xsl:copy-of select="shiptoid" /> <xsl:for-each select="chassis[count(.|key('chassis', concat(generate-id(..), '+', commonid))[1]) = 1]"> <xsl:variable name="current-group" select="key('chassis', concat(generate-id(..), '+', commonid))" /> <chassis> <xsl:copy-of select="commonid" /> <xsl:copy-of select="$current-group[last()]/commonelement" /> <newtag> <xsl:copy-of select="$current-group/specificelement" /> </newtag> </chassis> </xsl:for-each-group> </shipment> </xsl:template> In fact you could select the *last* of the nodes returned by the key, rather than the first, if you wanted, while extracting the unique chassis. That way, you wouldn't have to use $current-group[last()] to get the correct commonelement element: <xsl:template match="shipment"> <shipment> <xsl:copy-of select="shiptoid" /> <xsl:for-each select="chassis[count(.|key('chassis', concat(generate-id(..), '+', commonid))[last()]) = 1]"> <xsl:variable name="current-group" select="key('chassis', concat(generate-id(..), '+', commonid))" /> <chassis> <xsl:copy-of select="commonid" /> <xsl:copy-of select="commonelement" /> <newtag> <xsl:copy-of select="$current-group/specificelement" /> </newtag> </chassis> </xsl:for-each-group> </shipment> </xsl:template> > Any help is VERY much appreciated!! In the end, I think its coming > down to a lack of sufficient understanding of the <xsl:key> tag on > my part. The xsl:key element tells the processor to build up a hashtable (or something) of nodes indexed by values. When you use the key() function, you retrieve all the nodes that have a particular value from that hashtable. The point with the Muenchian method is that you only process the *first* (or last, if you like) node that's returned for each value stored by the key in order to create the group-level output (like the commonid/commonelement elements). But there's no way to get at the values that are stored in the key aside from by going through all the nodes that might be stored in the key and trying out their value, which is why you have to use one of those horrible expressions in the select attribute. I don't know if that explains things sufficiently; do ask if you have any questions. Cheers, Jeni --- Jeni Tennison http://www.jenitennison.com/ XSL-List info and archive: http://www.mulberrytech.com/xsl/xsl-list
Current Thread |
---|
|
<- Previous | Index | Next -> |
---|---|---|
[xsl] Seeking help on Grouping dist, jestoll | Thread | Re: [xsl] Seeking help on Grouping , G. Ken Holman |
Re: [xsl] foreign characters in par, Mattias Konradsson | Date | Re: [xsl] Seeking help on Grouping , G. Ken Holman |
Month |