Wrox Programmer Forums
Go Back   Wrox Programmer Forums > XML > XSLT
| Search | Today's Posts | Mark Forums Read
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
  #1 (permalink)  
Old September 30th, 2003, 07:19 AM
Authorized User
 
Join Date: Jun 2003
Location: , , United Kingdom.
Posts: 14
Thanks: 0
Thanked 0 Times in 0 Posts
Default 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?
  #2 (permalink)  
Old September 30th, 2003, 07:52 AM
Friend of Wrox
 
Join Date: Jun 2003
Location: , , United Kingdom.
Posts: 1,212
Thanks: 0
Thanked 1 Time in 1 Post
Default

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?
  #3 (permalink)  
Old September 30th, 2003, 09:08 AM
Authorized User
 
Join Date: Jun 2003
Location: , , United Kingdom.
Posts: 14
Thanks: 0
Thanked 0 Times in 0 Posts
Default

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>
  #4 (permalink)  
Old September 30th, 2003, 02:10 PM
planoie's Avatar
Friend of Wrox
Points: 16,481, Level: 55
Points: 16,481, Level: 55 Points: 16,481, Level: 55 Points: 16,481, Level: 55
Activity: 0%
Activity: 0% Activity: 0% Activity: 0%
 
Join Date: Aug 2003
Location: Clifton Park, New York, USA.
Posts: 5,407
Thanks: 0
Thanked 16 Times in 16 Posts
Default

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
  #5 (permalink)  
Old October 1st, 2003, 03:29 AM
Friend of Wrox
 
Join Date: Jun 2003
Location: Nor Hachin, Kotayk, Armenia.
Posts: 147
Thanks: 0
Thanked 0 Times in 0 Posts
Send a message via Yahoo to armmarti
Default

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 &lt; $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 &lt; $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
  #6 (permalink)  
Old October 1st, 2003, 03:38 AM
Friend of Wrox
 
Join Date: Jun 2003
Location: , , United Kingdom.
Posts: 1,212
Thanks: 0
Thanked 1 Time in 1 Post
Default

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 &gt; $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
  #7 (permalink)  
Old October 1st, 2003, 09:28 AM
Authorized User
 
Join Date: Jun 2003
Location: , , United Kingdom.
Posts: 14
Thanks: 0
Thanked 0 Times in 0 Posts
Default

Thanks guys, both solutions were great!
  #8 (permalink)  
Old October 1st, 2003, 11:51 AM
Authorized User
 
Join Date: Jun 2003
Location: , , United Kingdom.
Posts: 14
Thanks: 0
Thanked 0 Times in 0 Posts
Default

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
  #9 (permalink)  
Old October 2nd, 2003, 02:39 AM
Friend of Wrox
 
Join Date: Jun 2003
Location: , , United Kingdom.
Posts: 1,212
Thanks: 0
Thanked 1 Time in 1 Post
Default

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.
  #10 (permalink)  
Old October 2nd, 2003, 04:38 AM
Authorized User
 
Join Date: Jun 2003
Location: , , United Kingdom.
Posts: 14
Thanks: 0
Thanked 0 Times in 0 Posts
Default

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.




Similar Threads
Thread Thread Starter Forum Replies Last Post
alternate method of using MapQuest tennisdad1 Javascript How-To 2 January 23rd, 2006 08:06 PM
Ordering rows problem ami4quest6 SQL Language 10 October 12th, 2005 01:13 AM
Ordering a dataview Louisa VB.NET 2002/2003 Basics 1 November 11th, 2004 10:04 AM
Ordering results EstherMStrom XSLT 6 October 13th, 2004 09:50 AM
Ordering lists POL XSLT 3 June 9th, 2003 11:39 PM





Powered by vBulletin®
Copyright ©2000 - 2020, Jelsoft Enterprises Ltd.
Copyright (c) 2020 John Wiley & Sons, Inc.