 |
| XSLT General questions and answers about XSLT. For issues strictly specific to the book XSLT 1.1 Programmers Reference, please post to that forum instead. |
Welcome to the p2p.wrox.com Forums.
You are currently viewing the XSLT section of the Wrox Programmer to Programmer discussions. This is a community of software programmers and website developers including Wrox book authors and readers. New member registration was closed in 2019. New posts were shut off and the site was archived into this static format as of October 1, 2020. If you require technical support for a Wrox book please contact http://hub.wiley.com
|
|
|
|

September 30th, 2003, 07:19 AM
|
|
Authorized User
|
|
Join Date: Jun 2003
Posts: 14
Thanks: 0
Thanked 0 Times in 0 Posts
|
|
alternate ordering
I need to order an XML data set based on one node, but rather than ascending or decending, i need to alternate between all the possibilities.
For example, instaed of 1111222233334444 or 4444333322221111, I need the order to be 1234123412341234.
I can only think of very clumsy ways to achieve this. Does anone have a neat way?
|
|

September 30th, 2003, 07:52 AM
|
|
Friend of Wrox
|
|
Join Date: Jun 2003
Posts: 1,212
Thanks: 0
Thanked 1 Time in 1 Post
|
|
Hmm sounds interesting, but for me there is not enough detail here to know precisely what you want. Can you post sample input XML and desired output?
|
|

September 30th, 2003, 09:08 AM
|
|
Authorized User
|
|
Join Date: Jun 2003
Posts: 14
Thanks: 0
Thanked 0 Times in 0 Posts
|
|
OK, for example I want to order the following XML
<persons>
<person><name>Al</name><number>1</number></person>
<person><name>Bill</name><number>1</number></person>
<person><name>Cal</name><number>2</number></person>
<person><name>Dolly</name><number>2</number></person>
<person><name>Elly</name><number>3</number></person>
<person><name>Gill</name><number>3</number></person>
<person><name>Holly</name><number>4</number></person>
</persons>
by the number node, as follows
<persons>
<person><name>Al</name><number>1</number></person>
<person><name>Cal</name><number>2</number></person>
<person><name>Elly</name><number>3</number></person>
<person><name>Holly</name><number>4</number></person>
<person><name>Bill</name><number>1</number></person>
<person><name>Dolly</name><number>2</number></person>
<person><name>Gill</name><number>3</number></person>
</persons>
|
|

September 30th, 2003, 02:10 PM
|
 |
Friend of Wrox
|
|
Join Date: Aug 2003
Posts: 5,407
Thanks: 0
Thanked 16 Times in 16 Posts
|
|
Looks to me like you are going to have to come up with something yourself. I've never heard of a programmatic construct in any language that does that.
You're going to have to do something like create multiple datasets, one for each number, then sort each of those by name, then go thru each dataset and element to get them in the right fashion. Could be very tricky if you don't have a fixed number of "number"s.
Peter
|
|

October 1st, 2003, 03:29 AM
|
|
Friend of Wrox
|
|
Join Date: Jun 2003
Posts: 147
Thanks: 0
Thanked 0 Times in 0 Posts
|
|
I can't exactly know what you mean by "neat", but in any case the algorithm is simple and I don't see here any complexity.
You need to group (or just sort, since you have numbers, but I used grouping which can be helpful when the "number" element will be changed) and then for every group output i-th person. Honestly, I couldn't find simpler solution (I mean some elegant XSLT-specific construct or approach, although in the code below "person[position() = $current-offset]" XPath is very convenient in this situation when the number of persons in each group is not the same)...
Code:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="number-to-person" match="person" use="number"/>
<xsl:template match="/">
<xsl:variable name="groups">
<xsl:for-each select="/persons/person[generate-id() = generate-id(key('number-to-person', number)[1])]">
<xsl:sort select="number"/>
<xsl:variable name="current-group" select="/persons/person[number = current()/number]"/>
<group>
<count>
<xsl:value-of select="count($current-group)"/>
</count>
<xsl:for-each select="$current-group">
<xsl:copy-of select="."/>
</xsl:for-each>
</group>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="max-count">
<xsl:call-template name="get-max">
<xsl:with-param name="set" select="$groups/*"/>
</xsl:call-template>
</xsl:variable>
<persons>
<xsl:call-template name="serialize">
<xsl:with-param name="set" select="$groups/*"/>
<xsl:with-param name="max-group-count" select="$max-count"/>
</xsl:call-template>
</persons>
</xsl:template>
<xsl:template name="serialize">
<xsl:param name="set"/>
<xsl:param name="current-offset" select="1"/>
<xsl:param name="max-group-count"/>
<xsl:choose>
<xsl:when test="$max-group-count < $current-offset"/>
<xsl:otherwise>
<xsl:for-each select="$set">
<xsl:copy-of select="person[position() = $current-offset]"/>
</xsl:for-each>
<xsl:call-template name="serialize">
<xsl:with-param name="set" select="$set"/>
<xsl:with-param name="current-offset" select="$current-offset + 1"/>
<xsl:with-param name="max-group-count" select="$max-group-count"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="get-max">
<xsl:param name="set"/>
<xsl:choose>
<xsl:when test="$set">
<xsl:variable name="max-of-rest">
<xsl:call-template name="get-max">
<xsl:with-param name="set" select="$set[position() > 1]"/>
</xsl:call-template>
</xsl:variable>
<xsl:choose>
<xsl:when test="$set[1]/count < $max-of-rest">
<xsl:value-of select="$max-of-rest"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$set[1]/count"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>0</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
I used Saxon 6.5.2, which performs RTF to nodeset conversion implicitly if the version is set to 1.1; you may need to perform the conversion explicitly by using the EXSLT's node-set function or something similar.
Regards,
Armen
|
|

October 1st, 2003, 03:38 AM
|
|
Friend of Wrox
|
|
Join Date: Jun 2003
Posts: 1,212
Thanks: 0
Thanked 1 Time in 1 Post
|
|
How about a simple recursive template like this one:
Code:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>
<xsl:variable name="numRecs" select="count(/persons/person)"/>
<xsl:key name="nodesByNumber" match="person" use="number"/>
<xsl:template match="/">
<persons>
<xsl:call-template name="genOutput">
<xsl:with-param name="num" select="1"/>
<xsl:with-param name="posn" select="1"/>
<xsl:with-param name="recCount" select="1"/>
</xsl:call-template>
</persons>
</xsl:template>
<xsl:template name="genOutput">
<xsl:param name="num"/>
<xsl:param name="posn"/>
<xsl:param name="recCount"/>
<xsl:choose>
<xsl:when test="$recCount > $numRecs">
</xsl:when>
<xsl:when test="key('nodesByNumber', $num)[$posn]">
<!-- if a node with the given number and position exists, then
copy it to result tree and move on to the next number -->
<xsl:copy-of select="key('nodesByNumber', $num)[$posn]"/>
<xsl:call-template name="genOutput">
<xsl:with-param name="num" select="$num+1"/>
<xsl:with-param name="posn" select="$posn"/>
<xsl:with-param name="recCount" select="$recCount+1"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<!-- if a node with the given number and position doesn't exist, then
move back to number 1 and increment the position -->
<xsl:call-template name="genOutput">
<xsl:with-param name="num" select="1"/>
<xsl:with-param name="posn" select="$posn+1"/>
<xsl:with-param name="recCount" select="$recCount"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
The basic idea is that a key is defined to group the <person> nodes into sets which have the same <number>. Then I use two params to keep track of the number and position within that group of numbers, e.g. start at num=1, posn=1 then when we get to num=5 posn=1 it finds that no such node exists, so it tries again with num=1 posn=2, etc. (Edit: <s>It will keep going until all records are processed, so it should not matter if your numbers are not sequential, nor should it matter if the groups are uneven in number.</s> Actually, that's complete rubbish, the truth is the exact opposite  )
Maybe this is an oversimplification of your problem?
hth
Phil
|
|

October 1st, 2003, 09:28 AM
|
|
Authorized User
|
|
Join Date: Jun 2003
Posts: 14
Thanks: 0
Thanked 0 Times in 0 Posts
|
|
Thanks guys, both solutions were great!
|
|

October 1st, 2003, 11:51 AM
|
|
Authorized User
|
|
Join Date: Jun 2003
Posts: 14
Thanks: 0
Thanked 0 Times in 0 Posts
|
|
OK.. maybe I spoke too soon!
pgtips, I'm using your solution as I found it easier to follow and modify. It doesn't seem to work if one of the "numbers" is not present in the recordset... I'm thinking I need to somehow tell it to go onto the next one if it doesn't find a particular number, but then I also need it to remember that that number wasn't there so it doesn't search the whole recorset every time... any ideas?
Cheers
|
|

October 2nd, 2003, 02:39 AM
|
|
Friend of Wrox
|
|
Join Date: Jun 2003
Posts: 1,212
Thanks: 0
Thanked 1 Time in 1 Post
|
|
Yes I noticed that after I posted it, and edited my original post accordingly. Probably the best way is to include armen's template which calculates the max number, and use that max to decide when to increment the position, rather than my original method of incrementing it when a node is not found.
Alternatively, you could just use armen's method instead. The principle is the same as my attempt (i.e. a recursive template that moves through the person nodes in alternate order), it just uses a different method to group the nodes by number.
|
|

October 2nd, 2003, 04:38 AM
|
|
Authorized User
|
|
Join Date: Jun 2003
Posts: 14
Thanks: 0
Thanked 0 Times in 0 Posts
|
|
Yes you're right, the other solution does allow numbers to be missing.
I can't get it to work with MSXML4 though (works fine if I just view it with Netscape). Never mind, I will bash on.
Thanks guys.
|
|
 |