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


No comments: