Monday, July 9, 2007

More Hierarchical Sorting E4X XML: for Flex Tree & Beyond

This is a continuation of my most recent posts.  I've changed the Tree's dataProvider to an XMLListCollection object instead of an XML object as discussed in a recent post in this series.

My hierarchical sort function still works with an XML object so it may be used for other purposes besides a Flex Tree.  The sort function is only dependent on the resources built in the Flash Player.

The earlier sort function had a limitation which required sorting an attribute that needed to be in all elements of the XML object.  The new sort function no longer has that requirement.  This opens the door for even more functionality.

The new function still only sorts on a single attribute but if you combine that with some old tech you can do quite a bit.

Old TechDo you know what this is?  No, it's not a computer but it frequently worked as a stunt double for computers in 60's and 70's movies.  It is a card sorter and I have used one many times. 

I still vividly recall a conversation I was having with my programmer son a few years ago.  We were driving up Highway 6 and it's one of those events in your life when you remember where you were.  We were having a recurring argument about cutting 25 years from my resume.  My son asked me what could you possibly use today that you learned in those days.  I guess I remember this incident so well because it forced up the reality that so much of my professional self esteem is based on things nobody cares about today.  It was a good question albeit a little cold.

Well here it is: I can use my experience of operating a card sorter today!  And do you know how to sort on one, son?  Of course you don't!  A field in a card must be sorted one column at a time backwards.  Multiple fields must be sorted in reverse order.  For example if you wanted to sort by last name in columns 1 thru 10 and first name in columns 11 thru 20 you would sort on columns 20,19,18...3,2,1; one at a time.

And if you combine this old tech with my simple function you can sort multiple attribute fields each one independently of the others.  That means you can sort some ascending, some descending, some dates, some numbers, etc. You can do it backwards (with one hand tied behind your back).  Hah!

No, you don't have to sort a character at a time.

Here is the new ActionScript 3 E4X sort XML attributes function:

 1 public static function 

sortXmlAttribute
 2     ( avXml                :XML,
 3       avAttributeName      :String,
 4       avPutEmptiesAtBottom :Boolean,
 5       avArraySortArgument0 :* = 0,
 6       avArraySortArgument1 :* = 0 )
 7     :void
 8 {
 9     var lvChildrenCount:int
10         = avXml.children().length();
11     
12     if( lvChildrenCount == 0 )
13         return;
14 
15     if( lvChildrenCount > 1 )
16     {
17         var lvAttributeValue    :String;
18         var lvXml               :XML;
19 
20         var lvSortOptions:int
21             = avArraySortArgument0 is Function
22               ? avArraySortArgument1
23               : avArraySortArgument0;
24             
25         var lvSortCaseInsensitive:Boolean
26             = ( lvSortOptions & Array.CASEINSENSITIVE )
27               == Array.CASEINSENSITIVE;
28               
29         var lvArray:Array = new Array();
30         
31         for each( lvXml in avXml.children() )
32         {
33           lvAttributeValue
34               = lvXml.attribute( avAttributeName );
35 
36           if( lvSortCaseInsensitive )
37               lvAttributeValue
38                   = lvAttributeValue.toUpperCase();
39                 
40           if( lvArray.indexOf( lvAttributeValue ) == -1 )
41               lvArray.push( lvAttributeValue );
42                 
43         } // for each
44         
45         if( lvArray.length > 1 )
46         {
47             lvArray.sort
48             (
49                 avArraySortArgument0,
50                 avArraySortArgument1
51             );
52             
53             if( avPutEmptiesAtBottom )
54             {
55                 if( lvArray[0] == "" )
56                     lvArray.push( lvArray.shift() );
57             } // if
58             
59         } // if
60             
61         var lvXmlList:XMLList = new XMLList();
62         for each( lvAttributeValue in lvArray )
63         {
64             for each( lvXml in avXml.children() )
65             {
66               var lvXmlAttributeValue:String
67                    = lvXml.attribute
68         ( avAttributeName );
69                      
70               if( lvSortCaseInsensitive )
71                   lvXmlAttributeValue
72                     = lvXmlAttributeValue.toUpperCase();
73                 
74               if( lvXmlAttributeValue
75                   ==
76                   lvAttributeValue )
77                   lvXmlList += lvXml;
78                     
79             } // for each
80             
81         } // for each
82         
83         avXml.setChildren( lvXmlList );
84         
85     } // if             
86 
87     for each( var lvXmlChild:XML in avXml.children() )
88     {
89         sortXmlAttribute
90         (
91             lvXmlChild,
92             avAttributeName,
93             avPutEmptiesAtBottom,
94             avArraySortArgument0,
95             avArraySortArgument1
96         );
97     } // for each
98     
99 } // sortXmlAttribute

Function sortXmlAttribute Description

Arguments

# Name Type Description
1 avXml XML Object to be sorted.
2 avAttributeName String Attribute Name to sort
3 avPutEmptiesAtBottom Boolean Where to put elements that have empty values for sorted attribute.
4 avArraySortArgument0 * Array.sort() 1st argument
5 avArraySortArgument1 * Array.sort() 2nd argument

 

There were two big changes from the earlier function.  One, I have removed E4X filtering and used the more conventional E4X syntax.  This really simplified the function.  I'm not putting down the filtering feature, but in this instance I am better off without it.

The second was dealing with case insensitive sorts.  The problem I had before was that I ended up separating strings that had different cases but were otherwise equal.  This isn't a problem when you are doing a single sort, but when you stack sorts back to back the problem shows up.

I also added a feature that controls where you put the XML elements that do not have an attribute value.  The need do this varies with the type of data you are looking at.  It was actually easy to implement.

Lines 20 thru 27

This code determines if the sort is case insensitive.

Lines 29 thru 43

This section builds an array of unique attribute values.  If the sort is case insensitive I convert all of the values to upper case.

Lines 47 thru 51

This sorts the array with the Array.sort() function.  That's all I ever sort; just the unique attribute values.  This is important.  I don't sort the XML object (because I can't).  I never upset the order of the children which may have been sorted on another field previously.

Lines 53 thru 57

This is where I move the XML elements that have empty attribute values if necessary.  It just moves an empty string from the beginning of the array to the end.

Lines 61 thru 81

This is where the real meat of the function is.  I want to build a new XMLList object that represents all of the XML children but in the new sorted order.  I iterate through each of the sorted unique attribute values.  Inside that iteration I iterate through all of the XML children.  If the child has the current attribute value I append it to the end of the temporary XMLList I'm building.  I have to allow for case insensitivity.  It is important make sure that children with the same value stay in the same order.  This actually means doing nothing and I do it well.

Line 83

I replace the children of the input XML object with the temporary XMLList I just built with the E4X setChildren function.  It is really nice to have this very simple function.

Lines 87 thru 97

This makes the function recursive.

That is the function.

It is very straightforward.  If all algorithms were like this, most everyone could be a programmer.

The Example

Previously, I have included the source code in the post but no compiled example.  I know personally I prefer to just look at the code and if I'm interested I'll compile it myself.  I've also kept the examples to one file.  I started this post and the example in the same manner, but the example just kept getting bigger.  It is still only one file, but in the future I will have to break it into more than one class.  I've decided to provide a page with the source code.  My intention is to do this in the future, but the page will probably contain more than one file.  In addition I have a link to a page that contains the SWF file.  I will also compile with the "Publish Source Code" option.  This will give you the ability to view the source code from the SWF program.  You'll also be able to download the program in a zip file along with the Flex 2 SDK.

 

The new example has a lot more goodies in it.  There are 2 built-in examples but you can also paste in your own XML.  So it actually can be used as an XML sort tool.

Next

I'm now starting to think about sorting element names and values.

Addendum

I came across someone wanting to sort XML via the E4X capabilities of JavaScript.  I think my function will work.  You might have to remove the static declaration. (I don't know.)

There are several functions in the example declared as static.  This is my way of implying the function stands on its own and it may have other uses.

I wouldn't mind expanding to other platforms if for no other reason than to improve my visibility.  (I probably only have a total of 30 lifetime hits.)  Please point me in the right direction on how to set up a test environment of E4X JavaScript.

21 comments:

Anonymous said...

Very useful. Thank you!

Jim Freer said...

Thanks Anonymous,

After thousands of downloads it's nice to find that someone thought it was useful. Makes me want to get back to blogging and come up with some new material.

Jim Freer

Goschan said...

Thank you so much for these 100 lines of code. Great job... It works exactly like I expected :)

Jim Freer said...

Thanks Goschan,

I can't tell you how good it makes me feel to hear that.

jim said...

Nice,
Never read a post about AS that was written in such a good way! Even some background information about you!

great!


Jim

www.strandjutter.net

Ps. and a useful function ofcourse

Jim Freer said...

Talking too much. Sure sign of old age. My 8 year old granddaughter only wants the facts. She asks me how to spell a word. I try to get her to “sound” it out. She tells me, “Don't go fortune cookie on me now.”

Thanks for the comment, Jim

MattFS218 said...

I'm a newbie to flex, but am trying to sort an e4x XML object by the element's value, rather than an attribute. Is this possible with your function? My XML data looks like

!contacts!
!contact id="1"!
!name!Matthew!/name!
!email!matthew@yahoo.com!/email!
!/contact!
!contact id="2"!
!name!Jim!/name!
!email!jim@gmail.com!/email!
!/contact!
!/contacts!

<'s and >'s replaced with !'s to bypass html filter

and I'd like to sort by either name or email value. Thanks for this great contribution, even if it won't work in my case :)

Jim Freer said...

My function won't sort elements. After writing the code, I thought about continuing on to sort elements while I had the algorithm in my brain. However due to the flexibility of XML I didn't come up with a nice neat approach. It would have helped to have had an application that needed sorting at the time, but I might not have published the code if it only solved an isolated problem.

Since that time, I have decided not to use Flex. My Flex experience has pretty much turned me off on using frameworks altogether. Unfortunately, I haven't figured out how to make a living doing pure ActionScript. Admittedly, Flex gives endless opportunity for coming up with Blog material but I'm afraid using it means endorsing it and I can't do that.

So right now I'm an old programmer looking for a new direction and it needs to be something that is free to use. Suggestions are welcome.

Thanks for the compliment Matt.

Heidi & Thomas said...

Hi Jim,

Nice sort method. Hovever to make the method even better i need som way to force it to sort caseinsensetive - my fast hack was:

var lvSortCaseInsensitive:Boolean = true;

But that is inflexible so what is your idea with the params:
avArraySortArgument0 :* = 0,
avArraySortArgument1 :* = 0

Best Regs - Thomas ; )

Jim Freer said...

Hello Heidi

The argument avArraySortArgument1 is a bitmapped argument. This becomes the 2nd argument to the sort function of the Array class. Setting avArraySortArgument1 to 1 will make the sort case insensitive. The Flex program I provided demonstrates how to do a case insensitive sort. The source is provided from a link in the demo.

Here are the values from the Adobe documentation available to the 2nd sort argument:

1 or Array.CASEINSENSITIVE
2 or Array.DESCENDING
4 or Array.UNIQUESORT
8 or Array.RETURNINDEXEDARRAY
16 or Array.NUMERIC

For example to do a case insensitive and descending sort set the argument to 3
or use the Flash constants provided:

Array.CASEINSENSITIVE | Array.DESCENDING

The | symbol represents a bitwise or operation.

Jim Freer

Alex said...

Hi Jim,

Just wanted to say thanks for making this code available -- great work!

Best,
Alex

Unkulunkulu said...

great function!

this line causes a warning in by the compiler:

var lvSortCaseInsensitive:Boolean = lvSortOptions & Array.CASEINSENSITIVE == Array.CASEINSENSITIVE;

but if you add parentheses like this:

var lvSortCaseInsensitive:Boolean = (lvSortOptions & Array.CASEINSENSITIVE) == Array.CASEINSENSITIVE;

it compiles without warnings. At least in FlashDevelop.

cip said...

Thanks for this function - it's not just useful, it essentially saved my day! Great!

Rany said...

This is great!

How about sorting multiple attributes with corresponding argument?

Jim Freer said...

I'm sorry Rany, I don't understand the question.

Anonymous said...

Thanks for very much for your hard work. Abraham

Bear10 said...

Apparently you said in order for it to sort all the children recursively the children must have the same attribute, so I added a little trick to it (I haven't tested it much but it seems to work).

By creating a "TreeSort" attribute in the XML on the parents who have children of a length > 1, you can then sort its children by different criteria. Once this recursive method reaches this parent it starts sorting by the criteria specified in the parent.

To get this to work add

This Code:

if(avXml.hasOwnProperty("@TreeSort")) avAttributeName = avXml.@TreeSort;

At the beginning of the function.

InvertedSpear said...

Written 2+ years ago and still relevant. I spent way too much time trying to find this. Thanks for putting this out there.

Anonymous said...

You have really great taste on catch article titles, even when you are not interested in this topic you push to read it

T.R.Sarma said...

Very useful function.
Very detailed explanation.
THANKS SO MUCH FOR WRITING THIS BLOG!!!

maddysam said...

Nice work there Jim. I am new to flex. I have a tree from value objects. I want to sort the children, from one parent to another. Can anyone suggest me an approach ?

Thanks