Monday, June 25, 2007

addendum AS3 E4X: Sorting Hierarchical XML Data for Tree Control

I’ve taken some private and thankfully friendly flak about my preceding post on sorting data in an XML object for the purpose of providing data to a tree control.  I'm told I should use the ItreeDataDescriptor interface to get into the real spirit of Flex.

I did a little homework and learned a few things that should have been part of the preceding post.  At this point, I'm not going to create a class implementing ItreeDataDescriptor but I’ll keep an open mind on the subject.  However I did have some misunderstandings about what I was doing.  (I did mention on my first post, this blog would be a journal of my experiences and I’m new to Flex.)

When you set the Tree control’s dataProvider property to an XML object, it gets converted internally to an XMLListCollection which I have verified by running a trace.

The official Adobe documentation is wrong.  It does say that 3 object types get converted to an XMLListCollection: a valid XML string, an XMLNode, and an XMLList.  I suppose it is reasonable to assume if a valid XML string is converted along with an XMLList object, then an XML object would be also.

Okay, I didn't actually read the documentation on a Tree's dataProvider before writing my code.  I assumed all dataProvider's would be the same. I'm not sure where my basic knowledge of dataProvider's came from but it was probably from Adobe's "Training from the Source".  And anyway, the documentation is wrong.

The first thing I should have done, and I don't know why I don't do this all of the time, is look at the Flex source code.  Sometimes it takes a while to weed through the code, but this time it took all of 30 seconds to verify:  When you set a Tree's dataProvider to an XML object, it gets converted internally to an XMLListCollection object.

The embarrassing thing about my previous post is that I was contrasting how well I could get an XML object to work but blasting the XMLListCollection as a Tree’s dataProvider.  My real problem was trying to sort descendents and I haven’t figured how to do that with XMLListCollection’s.  I’m sticking with the XML sort and plan to expand upon it real soon.

Friday, June 22, 2007

ActionScript E4X: Example Sorting Hierarchical XML Data for Tree Control

I’ve been able to find examples of how to sort XMLListCollection objects which are useful data providers for controls like the DataGrid. But I wanted to sort hierarchical data for a Tree control. XMLListCollection objects sort top level elements but apparently don’t sort children, grandchildren, etc. At least I couldn’t figure out how to do it.

I needed the tree sort for an AIR application. I built a sort function for XML objects and plugged an XML object into the Tree’s data provider. It works. I decided this function would be a good post by itself so I put together a Flex 2 example with only one mxml file which is provided at the bottom of the post.

Tree node labels are specified with the property “labelField” and normally you would want to sort on these labels.

Actually, you might want to first sort on the node type and then the labels, but I haven’t gotten far enough in my application to need that yet. I know I’ll need it in the future, so stay tuned.

I set the Tree’s “labelField” property to one of the attribute names of the elements in the XML object. The example uses the “name” attribute preceded by the “@” character which is the E4X designator for attributes.

<mx:Tree dataProvider="{mvXml}" labelField="@name"/>
The data provider variable “mvXml” is defined as:

var mvXml:XML =
    <Country name= "USA">
     <State name="Texas">
       <City name="Corpus Christi"/>
       <City name="austin"/>
       <City name="San Antonio"/>
     </State>
     <State name="Michigan">
       <City name="Ypsilanti"/>
       <City name="lansing"/>
       <City name="Ann Arbor"/>
       <City name="Kalamazoo"/>
       <City name="Allen Park"/>
     </State>
   </Country>
Now the fine print

It is not unusual to have alphanumeric, numeric, currency and date nodes in a single tree. Of course all of the tree node labels can be sorted as simple text, but if you have numbers in some of the labels then 10 would come before 2. It is important to understand we are actually sorting the Tree’s data provider, an XML object, instead of the Tree nodes directly. My function sorts on only one attribute and it must be in every element of the XML object.


Lower expectations and raise false hope
Still a lot of trees will look fine if the labels are all sorted the same way. Also, I put the labels in an Array object to sort and allow you to pass in Array sorting arguments. So you may do descending, numeric, case insensitive and custom sorts.

If you are really desperate to sort on multiple types of labels, you could write a custom sort that would analyze the field and determine if it were alphanumeric, numeric, currency or a date before sorting.

There is another way to handle unusual situations. You could add an XML attribute to be used for sorting and another attribute for the label. The sorting attribute would never be seen, but would be used by my sorting function. This way you can sort numbers as alphanumeric by right aligning them and dates by first converting them to numbers.

Or you could wait because I know I will have to deal with this soon.



The example...
I have buttons for 5 different test sorts for the tree and the button click events are as follows:

Button "Sort Ascending" Click Event:
sortXml( mvXml, "name" );
expandAllTreeNodes( Tree1 );
Button "Sort Case Insensitive" Click Event:
sortXml( mvXml, "name", Array.CASEINSENSITIVE );
expandAllTreeNodes( Tree1 );
Button "Sort Descending" Click Event:
sortXml( mvXml,
         "name",
         Array.CASEINSENSITIVE | Array.DESCENDING );
expandAllTreeNodes( Tree1 );
Button "Sort Reverse" Click Event:
sortXml( mvXml, "name", sortReverseText );
expandAllTreeNodes( Tree1 );
Button "Sort Reverse Descending" Click Event:
sortXml( mvXml, 
         "name",
         sortReverseText,
         Array.DESCENDING );
expandAllTreeNodes( Tree1 );
You can pass the Array sort options in the optional 3rd and 4th function arguments. The last 2 click events use the custom sort function, "sortReverseText", which can be seen in the complete listing at the bottom of the post.

Note that it is necessary to expand the trees after each sort. Of course, this only applies to the example. In a real application you would want to restore the expanded states to their original settings. This presents a whole different problem.

  Bug   Feature

The "name" argument refers to the name of the attribute to be sorted in each element. This attribute must be in every element. Otherwise the element, and descendents, will be lost after a sort.

Actually this could be a feature. Our product not only sorts, it automatically permanently filters out elements that don't have the required attribute.

The XML Sort Function
Here is a breakdown of the sort function, "sortXml", which is a recursive static function.

 1 public static function sortXml
 2     ( avXml                :XML,
 3       avAttributeName      :String,
 4       avArraySortArgument0 :* = 0,
 5       avArraySortArgument1 :* = 0 )
 6     :void
 7 {
 8     var lvChildrenCount:int = avXml.children().length();
 9     
10     if( lvChildrenCount == 0 )
11         return;
12 
13     if( lvChildrenCount > 1 )
14     {
15         var lvAttributeValue:String;
16 
17         var lvArray:Array = new Array();
18         avXml.children().
19         (
20          lvAttributeValue = attribute( avAttributeName ),
21          lvArray.indexOf( lvAttributeValue ) == -1
22              ? lvArray.push( lvAttributeValue ) : null
23         );
24         
25         lvArray.sort
26         (
27             avArraySortArgument0,
28             avArraySortArgument1
29         );
30         
31         var lvXmlList:XMLList = new XMLList();
32         for each( lvAttributeValue in lvArray )
33         {
34             lvXmlList += avXml.children().
35             (
36                 attribute( avAttributeName )
37                 ==
38                 lvAttributeValue
39             );
40         } // for each
41         
42         avXml.setChildren( lvXmlList );
43         
44     } // if             
45 
46     for each( var lvXmlChild:XML in avXml.children() )
47     {
48         sortXml
49         (
50             lvXmlChild,
51             avAttributeName,
52             avArraySortArgument0,
53             avArraySortArgument1
54         );
55     } // for each
56     
57 } // sortXml
Lines 17 - 29
I know you won't believe me, but I did not think I would ever want to use the exact code I used in the last example of my preceding post. The purpose is to get all unique attribute values into an Array and sort them.

Lines 31 - 40
The purpose of the code is to build a new XMLList object to contain all of the child elements in the sorted order. This code loops through each of the unique attribute values in the sorted Array, but the lines 34 thru 39 are a little more complicated. These lines are one statement using E4X syntax. Lines 36 thru 38 act a filter. It returns all child elements where the attribute value is equal to the loop's current sorted attribute value. It can return more than one element in the case where more than one element has the same attribute value. The resulting XmlList variable, "lvXmlList", ends up containing the same child elements of the XML input argument "avXml".

Line 42
We are fortunate the XML class has the "setChildren" function. With this function, the code replaces the existing children with the same, but sorted, elements in the XMLList object.

Lines 46 - 55
This is just the standard recursive stuff that calls this same function on all of the child elements so that their children, and so on, are sorted. I tried to build an E4X statement to do the same thing. Maybe a fresher mind, mine or yours can come up the code.

Complete Source Code for Example:

TreeSortExample.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    layout="absolute"
    creationComplete="onCreationComplete()"
    >
    <mx:VBox
        height="100%"
        width="100%"
        x="50"
        y="5"
        >
        <mx:Panel
            width="90%"
            height="50%"
            horizontalAlign="center"
            title="Jim Freer's E4X Sorting for Trees"
            >
            <mx:HBox
                height="100%"
                width="100%"
                >
                <mx:VBox>
                    <mx:Button
                        label="Sort Ascending"
                        width="200"
                        click="onClickButtonSortTree1Ascending()"
                        />
                    <mx:Button
                        label="Sort Case Insensitive"
                        width="200"
                        click="onClickButtonSortTree1CaseInsensitive()"
                        />
                    <mx:Button
                        label="Sort Descending"
                        width="200"
                        click="onClickButtonSortTree1Descending()"
                        />
                    <mx:Button
                        label="Sort Reverse"
                        width="200"
                        click="onClickButtonSortTree1Reverse()"
                        />
                    <mx:Button
                        label="Sort Reverse Descending"
                        width="200"
                        click="onClickButtonSortTree1ReverseDescending()"
                        />
                </mx:VBox>
                <mx:Tree
                    id="Tree1"
                    height="100%"
                    width="100%"
                    dataProvider="{mvXml}"
                    labelField="@name"
                    />
            </mx:HBox>
        </mx:Panel>
        <mx:Label
            text="*State capitals are in lowercase :) to demonstrate case insensitive sorting."
            />

    </mx:VBox>

    <mx:Script>
        <![CDATA[
        
        // ---------------------------------------------------------------------
        // Private Member Variables
        // ---------------------------------------------------------------------

        [Bindable]
        private var mvXml:XML =
            <Country name= "USA">
              <State name="Texas">
                <City name="Corpus Christi"/>
                <City name="austin"/>
                <City name="San Antonio"/>
              </State>
              <State name="Michigan">
                <City name="Ypsilanti"/>
                <City name="lansing"/>
                <City name="Ann Arbor"/>
                <City name="Kalamazoo"/>
                <City name="Allen Park"/>
              </State>
            </Country>;

        // ---------------------------------------------------------------------
        // expandAllTreeNodes
        // ---------------------------------------------------------------------
        
        private function expandAllTreeNodes
            ( avTree :Tree )
            :void
        {
            avTree.selectedIndex = 0;
            avTree.expandChildrenOf( avTree.selectedItem, true );
            
        } // expandAllTreeNodes     

        // ---------------------------------------------------------------------
        // onClickButtonSortTree1Ascending
        // ---------------------------------------------------------------------

        private function onClickButtonSortTree1Ascending
            ()
            :void
        {
            sortXml( mvXml, "name" );

            expandAllTreeNodes( Tree1 );
            
        } // onClickButtonSortTree1Ascending

        // ---------------------------------------------------------------------
        // onClickButtonSortTree1Descending
        // ---------------------------------------------------------------------
            
        private function onClickButtonSortTree1Descending
            ()
            :void
        {
            sortXml
                ( mvXml, "name", Array.CASEINSENSITIVE | Array.DESCENDING );

            expandAllTreeNodes( Tree1 );
            
        } // onClickButtonSortTree1Descending
        
        // ---------------------------------------------------------------------
        // onClickButtonSortTree1CaseInsensitive
        // ---------------------------------------------------------------------
        
        private function onClickButtonSortTree1CaseInsensitive
            ()
            :void
        {
            sortXml( mvXml, "name", Array.CASEINSENSITIVE );

            expandAllTreeNodes( Tree1 );
            
        } // onClickButtonSortTree1CaseInsensitive

        // ---------------------------------------------------------------------
        // onClickButtonSortTree1Reverse
        // ---------------------------------------------------------------------
            
        private function onClickButtonSortTree1Reverse
            ()
            :void
        {
            sortXml( mvXml, "name", sortReverseText );
            
            expandAllTreeNodes( Tree1 );
            
        } // onClickButtonSortTree1Reverse
        
        // ---------------------------------------------------------------------
        // onClickButtonSortTree1ReverseDescending
        // ---------------------------------------------------------------------
            
        private function onClickButtonSortTree1ReverseDescending
            ()
            :void
        {
            sortXml( mvXml, "name", sortReverseText, Array.DESCENDING );
            
            expandAllTreeNodes( Tree1 );
            
        } // onClickButtonSortTree1ReverseDescending
        
        // ---------------------------------------------------------------------
        // onCreationComplete
        // ---------------------------------------------------------------------

        private function onCreationComplete
            ()
            :void
        {
            expandAllTreeNodes( Tree1 );

        } // onCreationComplete
            
        // ---------------------------------------------------------------------
        // sortReverseText
        //
        // Reverses the strings before comparing.
        // For example, Kalamazoo is oozamalaK.
        // A lame example, but shows how to use a custom sort.
        // ---------------------------------------------------------------------

        private function sortReverseText
            ( avStringA :String,
              avStringB :String )
            :Number
        {
            var lvArray:Array;
            
            lvArray = avStringA.split( "" );
            lvArray = lvArray.reverse()
            var lvStringA:String = lvArray.join( "" );
            
            lvArray = avStringB.split( "" );
            lvArray = lvArray.reverse()
            var lvStringB:String = lvArray.join( "" );
            
            if( lvStringA > lvStringB )
            {
                return 1;
            } // if
            else if( lvStringA < lvStringB )
            {
                return -1;
            } // else if
            else
            {
                return 0;
            } // else
            
        } // sortReverseText

        // ---------------------------------------------------------------------
        // sortXml
        //
        // Warning: All elements in avXml must have an attribute with the name
        // in avAttributeName or the element will be deleted along with any
        // descendants.
        // ---------------------------------------------------------------------

        public static function sortXml
            ( avXml                :XML,
              avAttributeName      :String,
              avArraySortArgument0 :* = 0,
              avArraySortArgument1 :* = 0 )
            :void
        {
            var lvChildrenCount:int = avXml.children().length();
            
            if( lvChildrenCount == 0 )
                return;

            if( lvChildrenCount > 1 )
            {
                var lvAttributeValue:String;
    
                var lvArray:Array = new Array();
                avXml.children().
                (
                    lvAttributeValue = attribute( avAttributeName ),
                    lvArray.indexOf( lvAttributeValue ) == -1
                        ? lvArray.push( lvAttributeValue ) : null
                );
                
                lvArray.sort( avArraySortArgument0, avArraySortArgument1 )
                
                var lvXmlList:XMLList = new XMLList();
                for each( lvAttributeValue in lvArray )
                {
                    lvXmlList += avXml.children().
                    (
                        attribute( avAttributeName ) == lvAttributeValue
                    );
                } // for each
                
                avXml.setChildren( lvXmlList );
                
            } // if             

            for each( var lvXmlChild:XML in avXml.children() )
            {
                sortXml
                (
                    lvXmlChild,
                    avAttributeName,
                    avArraySortArgument0,
                    avArraySortArgument1
                );
            } // for each
            
        } // sortXml
        ]]>
    </mx:Script>
        
</mx:Application>


  Shameful   Shameless Solicitation

One more thing, I am looking for work (this time for money). Any advice would be appreciated.

Tuesday, June 12, 2007

More, Getting XML Elements and Attributes VIA Variables with AS3 E4X


I found some useful information pertaining to my preceding post at ZEUSLABS More XML filtering with E4X ActionScript 3. The blog, by Josh Tynjala, has several useful tips. His last example in that post is tackling the same problem with a few twists.

His example is certainly more in the spirit of E4X but I’m actually glad I built my solution anyway. Now I can compare the two approaches. I modified my code to use his technique as follows:

(2) XmlElementAndAttributeViaVariables.as
package
{
 import flash.utils.getDefinitionByName;
 import flash.utils.describeType;

 public class XmlElementAndAttributeViaVariables
 {
  // ---------------------------------------------------------------------
  // Constructors
  // ---------------------------------------------------------------------
  
  public function XmlElementAndAttributeViaVariables
   ( avDescribeTypeName :String = "flash.text::TextField" )
  {
   var lvXml:XML = getDescribeType( avDescribeTypeName );
   
   testAndTrace( lvXml, "accessor", "type" );
   testAndTrace( lvXml, "method", "returnType" );
   
   //trace( lvXml.toXMLString() );
   
  } // Constructor
  
  // ---------------------------------------------------------------------
  // addUniqueValue
  // ---------------------------------------------------------------------
  
  private function addUniqueValue
   ( avAttributeValue :String,
     avArray          :Array )
   :Array
  {
   if( avArray.indexOf( avAttributeValue ) == -1 )
    avArray.push( avAttributeValue );
    
   return avArray;
   
  } // addUniqueValue
  
  // ---------------------------------------------------------------------
  // getDescribeType
  // ---------------------------------------------------------------------
  
  private function getDescribeType
   ( avClassName :String )
   :XML
  {
   return describeType( getDefinitionByName( avClassName ) );
   
  } // getDescribeType
  
  // ---------------------------------------------------------------------
  // getUniqueElementAttributeOfAllDescendants
  //
  // Returns a sorted string array of unique attribute values found in the
  // input object specified in the argument avXml for the element type with
  // the name given in the arguemnt avElementName.
  // The name of the attribute is specified in the argument
  // avAttributeName.
  // The function will use all of the named elements including nested
  // elements.
  // ---------------------------------------------------------------------
  
  public function getUniqueElementAttributeOfAllDescendants
   ( avXml           :XML,
     avElementName   :String,
     avAttributeName :String )
   :Array
  {
   var lvArray:Array = new Array();
   avXml.descendants( avElementName ).
   (
    lvArray = addUniqueValue( attribute( avAttributeName ), lvArray )
   );
   
   lvArray.sort();
   
   return lvArray;
   
  } // getUniqueElementAttributeOfAllDescendants
  
  // ---------------------------------------------------------------------
  // testAndTrace
  // ---------------------------------------------------------------------
  
  private function testAndTrace
   ( avXml           :XML,
     avElementName   :String,
     avAttributeName :String )
   :void
  {
   trace
   (
    "Element: " + avElementName + " Attribute: " + avAttributeName
   ); // trace
   
   var lvArray:Array =
    getUniqueElementAttributeOfAllDescendants
    (
     avXml,
     avElementName,
     avAttributeName
    )

   for( var lvIndex:int = 0; lvIndex < lvArray.length; lvIndex++ )
    trace( lvIndex.toString() + " [" + lvArray[lvIndex] + "]" );
   
   trace( "\n" );
   
  } // testAndTrace
  
 } // class XmlElementAndAttributeViaVariables
 
} // package


The function, "getUniqueElementAttributeOfAllDescendants" was modified and I needed to add a new function, "addUniqueValue". At this point, I would probably stick with my original code. However, if I found out the approach shown in Josh’s example was faster I would jump ship.

One reason for sticking with mine is I like to make a line of code do only one thing. It is an old programming style theory of mine. I like to look at a line of code and grasp what it is doing without disassembling it. I’m planning to make a post on my other blog, An American Programmer in the future about this. I feel it’s worthy of a posting because I‘ve gone through a few battles over the years on this and I think I am losing the war. I’m afraid E4X is just another assault on my style. This is from my application from the listing above and is the essence of Josh’s example:

avXml.descendants( avElementName ).
(
    lvArray = addUniqueValue( attribute( avAttributeName ), lvArray )
);

The code, which is E4X syntax, is radically different from anything I’ve coded before and it kind of defies my programming style. I like to keep my code short enough to be printed without line wrapping, but these 4 lines are one statement (with an embedded statement ). I’ve broken the line with the dot dangling at then end. I don’t like doing that because it’s easy to overlook the dot. But that’s not my biggest concern. The 3rd line is just really weird. That statement is executed for every XML element derived from the first line. First of all, I don’t think the use of the symbols (a dot followed by a statement enclosed in parentheses) is very good symbolism. If the dot and parentheses were replaced with characters that better represented the true operation I might feel better about it.

The other reason why I like my original code is because I also have to add a function, “addUniqueValue” which is referenced in the 3rd line. In all fairness, these are example applications. In a real application breaking this into another function where things are more complex might not be an issue. But in the example, the function is two lines of code that have to be broken out because you are limited in what you can put in the statement. I’m not sure what the specifications say you can put in the statement because I have no ideal where this is documented. I tried various things to see if I could eliminate the function and do everything within the parentheses. It won’t, for example, allow an “if” statement. You can’t declare a variable with a “var”. I keep getting compile errors indicating that the statement must begin with an identifier.

Breaking News!

This is the problem I have with blogging. When I am in the middle of writing a post I discover new things that contradict what I’ve just written. This is a real problem because I don’t write that fast.

Anyway you can forget about what I just wrote about having to add a function. The discovery I’ve just made is that you are not limited to just one statement. You can add multiple statements by separating them with commas. (I swear I discovered this without any outside help.) Apparently you are still stuck with starting a statement with an identifier so you can’t do an “if” statement. But the “?:” statement begins with an identifier.

So the preceding example can be rewritten as follows:

var lvAttributeValue:String;
avXml.descendants( avElementName ).
(
    lvAttributeValue = attribute( avAttributeName ),
    lvArray.indexOf( lvAttributeValue ) == -1
        ? lvArray.push( lvAttributeValue ) : null
);

I still think my original solution is clearer and I think my last solution is the least clear. But E4X is probably going to be around for a while so I'm going to try to keep an open mind and explore how I can use it effectively.

This is the last version (at least in this post) of the entire class:

(3) XmlElementAndAttributeViaVariables.as

package
{
 import flash.utils.getDefinitionByName;
 import flash.utils.describeType;

 public class XmlElementAndAttributeViaVariables
 {
  // ---------------------------------------------------------------------
  // Constructors
  // ---------------------------------------------------------------------
  
  public function XmlElementAndAttributeViaVariables
   ( avDescribeTypeName :String = "flash.text::TextField" )
  {
   var lvXml:XML = getDescribeType( avDescribeTypeName );
   
   testAndTrace( lvXml, "accessor", "type" );
   testAndTrace( lvXml, "method", "returnType" );
   
   //trace( lvXml.toXMLString() );
   
  } // Constructor
  
  // ---------------------------------------------------------------------
  // getDescribeType
  // ---------------------------------------------------------------------
  
  private function getDescribeType
   ( avClassName :String )
   :XML
  {
   return describeType( getDefinitionByName( avClassName ) );
   
  } // getDescribeType
  
  // ---------------------------------------------------------------------
  // getUniqueElementAttributeOfAllDescendants
  //
  // Returns a sorted string array of unique attribute values found in the
  // input object specified in the argument avXml for the element type with
  // the name given in the arguemnt avElementName.
  // The name of the attribute is specified in the argument
  // avAttributeName.
  // The function will use all of the named elements including nested
  // elements.
  // ---------------------------------------------------------------------
  
  public function getUniqueElementAttributeOfAllDescendants
   ( avXml           :XML,
     avElementName   :String,
     avAttributeName :String )
   :Array
  {
   var lvArray:Array = new Array();
   var lvAttributeValue:String;
   avXml.descendants( avElementName ).
   (
    lvAttributeValue = attribute( avAttributeName ),
    lvArray.indexOf( lvAttributeValue ) == -1 ? lvArray.push( lvAttributeValue ) : null
   );
   
   lvArray.sort();
   
   return lvArray;
   
  } // getUniqueElementAttributeOfAllDescendants
  
  // ---------------------------------------------------------------------
  // testAndTrace
  // ---------------------------------------------------------------------
  
  private function testAndTrace
   ( avXml           :XML,
     avElementName   :String,
     avAttributeName :String )
   :void
  {
   trace
   (
    "Element: " + avElementName + " Attribute: " + avAttributeName
   ); // trace
   
   var lvArray:Array =
    getUniqueElementAttributeOfAllDescendants
    (
     avXml,
     avElementName,
     avAttributeName
    )

   for( var lvIndex:int = 0; lvIndex < lvArray.length; lvIndex++ )
    trace( lvIndex.toString() + " [" + lvArray[lvIndex] + "]" );
   
   trace( "\n" );
   
  } // testAndTrace
  
 } // class XmlElementAndAttributeViaVariables
 
} // package


Sunday, June 10, 2007

Getting XML Elements and Attributes VIA Variables with AS3 E4X


For some reason this ended up being a lot harder for me than it should have been. The code ended up being a little more traditional than you would think when using the exotic syntax of ActionScript’s E4X but it is still very concise.

I think part of my problem was that I was expecting to need the exotic part of the E4X syntax. E4X is very hard to describe. I know authors, with much better writing skills than mine, have tried but so far I haven’t seen a great explanation. Most everyone relies on showing examples which I end up copying and then trial and error editing to fit my needs.

Unfortunately, the following is typical of the examples:

var lvXmlList:XMLList = lvXml..accessor;

Variable lvXml’s class type is XML in this example. The code would return an XMLList object containing all of the <accessor/> elements including those nested inside of other elements. The nesting functionality comes from the powerful double dot operator. The example is neat, particularly when you are giving a one hour presentation on “What’s New in ActionScript 3”. It will “wow” the audience. However, over time you may find this type of coding to be too inflexible.

In my case, I want some code where “accessor” can be variable and at the same time I want to retrieve the value of an attribute of the elements which is also variable. In particular (for my example listing below) I want the same code to get elements <accessor/> or <method/> and their corresponding attributes “type” and “returnType” or any other element, attribute combination.

<accessor name=”x” type=”Number”/>
<method name=”border” returnType=”Boolean”/>

The key to doing this is to make use of two functions, “descendants(elementName)” and “attribute(attributeName)” of the class XML. Now the previous code:

var lvXmlList:XMLList = lvXml..accessor;

can be redone as:

var lvElementName:String = “accessor”;
var lvXmlList:XMLList = lvXml.descendants( lvElementName );

You can then loop through each of the “accessor” elements as follows:

var lvAttributeName:String = “type”;
for each( var lvXmlElement:XML in lvXmlList )
    trace( lvXmlElement.attribute( lvAttributeName ) );

I have built an example class. The class's function, 'getUniqueElementAttributeOfAllDescendants()' shows how the two XML functions are used.

XmlElementAndAttributeViaVariables.as
package
{
 import flash.utils.getDefinitionByName;
 import flash.utils.describeType;

 public class XmlElementAndAttributeViaVariables
 {
  // ---------------------------------------------------------------------
  // Constructors
  // ---------------------------------------------------------------------
  
  public function XmlElementAndAttributeViaVariables
   ( avDescribeTypeName :String = "flash.text::TextField" )
  {
   var lvXml:XML = getDescribeType( avDescribeTypeName );
   
   testAndTrace( lvXml, "accessor", "type" );
   testAndTrace( lvXml, "method", "returnType" );
   
   //trace( lvXml.toXMLString() );
   
  } // Constructor
  
  // ---------------------------------------------------------------------
  // getDescribeType
  // ---------------------------------------------------------------------
  
  private function getDescribeType
   ( avClassName :String )
   :XML
  {
   return describeType( getDefinitionByName( avClassName ) );
   
  } // getDescribeType
  
  // ---------------------------------------------------------------------
  // getUniqueElementAttributeOfAllDescendants
  //
  // Returns a sorted string array of unique attribute values found in the
  // input object specified in the argument avXml for the element type with
  // the name given in the arguemnt avElementName.
  // The name of the attribute is specified in the argument
  // avAttributeName.
  // The function will use all of the named elements including nested
  // elements.
  // ---------------------------------------------------------------------
  
  public function getUniqueElementAttributeOfAllDescendants
   ( avXml           :XML,
     avElementName   :String,
     avAttributeName :String )
   :Array
  {
   var lvArray:Array = new Array();
   
   for each( var lvXmlAccessor:XML
              in avXml.descendants(avElementName) )
   {
    var lvString:String
     = lvXmlAccessor.attribute( avAttributeName );
    
    if( lvArray.indexOf( lvString ) == -1 )
     lvArray.push( lvString );
     
   } // for each
   
   lvArray.sort();
   
   return lvArray;
   
  } // getUniqueElementAttributeOfAllDescendants
  
  // ---------------------------------------------------------------------
  // testAndTrace
  // ---------------------------------------------------------------------
  
  private function testAndTrace
   ( avXml           :XML,
     avElementName   :String,
     avAttributeName :String )
   :void
  {
   trace
   (
    "Element: " + avElementName + " Attribute: " + avAttributeName
   ); // trace
   
   var lvArray:Array =
    getUniqueElementAttributeOfAllDescendants
    (
     avXml,
     avElementName,
     avAttributeName
    )

   for( var lvIndex:int = 0; lvIndex < lvArray.length; lvIndex++ )
    trace( lvIndex.toString() + " [" + lvArray[lvIndex] + "]" );
   
   trace( "\n" );
   
  } // testAndTrace
  
 } // class XmlElementAndAttributeViaVariables
 
} // package


The example class deals with the built-in reflection function “flash.utils.describeType”. This function returns an XML description of an ActionScript object. By default, I use the XML description of a TextField object as input, What I want are all of the different types returned by the TextField class’s properties (accessor) and functions (method). When the example class is instantiated in the debug mode the following trace output is generated:

Element: accessor Attribute: type
0 [*]
1 [Array]
2 [Boolean]
3 [Number]
4 [Object]
5 [String]
6 [flash.accessibility::AccessibilityImplementation]
7 [flash.accessibility::AccessibilityProperties]
8 [flash.display::DisplayObject]
9 [flash.display::DisplayObjectContainer]
10 [flash.display::LoaderInfo]
11 [flash.display::Stage]
12 [flash.geom::Rectangle]
13 [flash.geom::Transform]
14 [flash.text::StyleSheet]
15 [flash.text::TextFormat]
16 [flash.ui::ContextMenu]
17 [int]
18 [uint]

Element: method Attribute: returnType
0 [Array]
1 [Boolean]
2 [String]
3 [flash.display::DisplayObject]
4 [flash.geom::Point]
5 [flash.geom::Rectangle]
6 [flash.text::TextFormat]
7 [flash.text::TextLineMetrics]
8 [int]
9 [void]

I have edited the resulting XML that is returned when running

trace( describeType( getDefinitionByName( “flash.text::TextField” ) ) )

which is shown in the listing below.

I have reduced it down to elements and attributes pertaining to this post. I also reordered the elements. The nesting ability of the descendants function can be seen within the first four lines. The accessor “prototype” has a “type” attribute of “*” which is the only accessor with that type and is included in the trace list above (0 [*]). But the “prototype” accessor’s parent is <type/> whereas all other accessors are the child of <factory/>

describeType( getDefinitionByName( “flash.text::TextField” ) )

<type name="flash.text::TextField" base="Class" isDynamic="true" isFinal="true" isStatic="true">

  <accessor name="prototype"                     type="*"/>

  <factory type="flash.text::TextField">

    <accessor name="filters"                     type="Array"/>
    
    <accessor name="alwaysShowSelection"         type="Boolean"/>
    <accessor name="background"                  type="Boolean"/>
    <accessor name="border"                      type="Boolean"/>
    <accessor name="cacheAsBitmap"               type="Boolean"/>
    <accessor name="condenseWhite"               type="Boolean"/>
    <accessor name="displayAsPassword"           type="Boolean"/>
    <accessor name="doubleClickEnabled"          type="Boolean"/>
    <accessor name="embedFonts"                  type="Boolean"/>
    <accessor name="mouseEnabled"                type="Boolean"/>
    <accessor name="mouseWheelEnabled"           type="Boolean"/>
    <accessor name="multiline"                   type="Boolean"/>
    <accessor name="selectable"                  type="Boolean"/>
    <accessor name="useRichTextClipboard"        type="Boolean"/>
    <accessor name="tabEnabled"                  type="Boolean"/>
    <accessor name="visible"                     type="Boolean"/>
    <accessor name="wordWrap"                    type="Boolean"/>
    
    <accessor name="alpha"                       type="Number"/>
    <accessor name="height"                      type="Number"/>
    <accessor name="mouseX"                      type="Number"/>
    <accessor name="mouseY"                      type="Number"/>
    <accessor name="rotation"                    type="Number"/>
    <accessor name="scaleX"                      type="Number"/>
    <accessor name="scaleY"                      type="Number"/>
    <accessor name="sharpness"                   type="Number"/>
    <accessor name="textHeight"                  type="Number"/>
    <accessor name="textWidth"                   type="Number"/>
    <accessor name="thickness"                   type="Number"/>
    <accessor name="x"                           type="Number"/>
    <accessor name="y"                           type="Number"/>
    <accessor name="width"                       type="Number"/>

    <accessor name="antiAliasType"               type="String"/>
    <accessor name="autoSize"                    type="String"/>
    <accessor name="blendMode"                   type="String"/>
    <accessor name="gridFitType"                 type="String"/>
    <accessor name="htmlText"                    type="String"/>
    <accessor name="name"                        type="String"/>
    <accessor name="restrict"      type="String"/>
    <accessor name="selectedText"                type="String" />
    <accessor name="text"                        type="String"/>
    <accessor name="type"                        type="String"/>
    
    <accessor name="focusRect"                   type="Object"/>
    <accessor name="opaqueBackground"            type="Object"/>

    <accessor name="accessibilityImplementation" type="flash.accessibility::AccessibilityImplementation"/>
    
    <accessor name="accessibilityProperties"     type="flash.accessibility::AccessibilityProperties"/>
    
    <accessor name="mask"                        type="flash.display::DisplayObject"/>
    <accessor name="root"                        type="flash.display::DisplayObject"/>
    
    <accessor name="parent"                      type="flash.display::DisplayObjectContainer"/>
    
    <accessor name="loaderInfo"                  type="flash.display::LoaderInfo"/>
    
    <accessor name="stage"                       type="flash.display::Stage"/>

    <accessor name="scale9Grid"                  type="flash.geom::Rectangle"/>
    <accessor name="scrollRect"                  type="flash.geom::Rectangle"/>
    
    <accessor name="transform"                   type="flash.geom::Transform"/>
    
    <accessor name="styleSheet"                  type="flash.text::StyleSheet"/>
    
    <accessor name="defaultTextFormat"           type="flash.text::TextFormat"/>

    <accessor name="contextMenu"                 type="flash.ui::ContextMenu"/>
    
    <accessor name="bottomScrollV"               type="int"/>
    <accessor name="caretIndex"                  type="int"/>
    <accessor name="length"                      type="int"/>
    <accessor name="maxChars"                    type="int"/>
    <accessor name="maxScrollH"                  type="int"/>
    <accessor name="maxScrollV"                  type="int"/>
    <accessor name="numLines"                    type="int"/>
    <accessor name="scrollH"                     type="int"/>
    <accessor name="scrollV"                     type="int"/>
    <accessor name="selectionBeginIndex"         type="int"/>
    <accessor name="selectionEndIndex"           type="int"/>
    <accessor name="tabIndex"                    type="int"/>
    
    <accessor name="backgroundColor"             type="uint"/>
    <accessor name="borderColor"                 type="uint"/>
    <accessor name="textColor"                   type="uint"/>

    <method name="getTextRuns"                   returnType="Array"/>
    
    <method name="dispatchEvent"                 returnType="Boolean"/>
    <method name="hasEventListener"              returnType="Boolean"/>
    <method name="hitTestObject"                 returnType="Boolean"/>
    <method name="hitTestPoint"                  returnType="Boolean"/>
    <method name="willTrigger"                   returnType="Boolean"/>
    
    <method name="getLineText"                   returnType="String"/>
    <method name="getRawText"                    returnType="String"/>
    <method name="getXMLText"                    returnType="String"/>
    <method name="toString"                      returnType="String"/>

    <method name="getImageReference"             returnType="flash.display::DisplayObject"/>

    <method name="localToGlobal"                 returnType="flash.geom::Point"/>
    <method name="globalToLocal"                 returnType="flash.geom::Point"/>

    <method name="getBounds"                     returnType="flash.geom::Rectangle"/>
    <method name="getCharBoundaries"             returnType="flash.geom::Rectangle"/>
    <method name="getRect"                       returnType="flash.geom::Rectangle"/>

    <method name="getTextFormat"                 returnType="flash.text::TextFormat"/>

    <method name="getLineMetrics"                returnType="flash.text::TextLineMetrics"/>

    <method name="getCharIndexAtPoint"           returnType="int"/>
    <method name="getFirstCharInParagraph"       returnType="int"/>
    <method name="getLineIndexAtPoint"           returnType="int"/>
    <method name="getLineIndexOfChar"            returnType="int"/>
    <method name="getLineLength"                 returnType="int"/>
    <method name="getLineOffset"                 returnType="int"/>
    <method name="getParagraphLength"            returnType="int"/>
    
    <method name="addEventListener"              returnType="void"/>
    <method name="appendText"                    returnType="void"/>
    <method name="insertXMLText"                 returnType="void"/>
    <method name="removeEventListener"           returnType="void"/>
    <method name="replaceSelectedText"           returnType="void"/>
    <method name="replaceText"                   returnType="void"/>
    <method name="setSelection"                  returnType="void"/>
    <method name="setTextFormat"                 returnType="void"/>
    
  </factory>
</type>


After reading what I have just written, it seems too trivial to post. But I was unable to find an example on the internet on how to do this.