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.

1 comment:

Mehmet Esat Belviranli said...

Just to let you know, your article helped me a lot even after 4 years.

Thanks