Chapter 15. E4X (XML)

<feature><title>In This Chapter</title> </feature>

XML got a major overhaul with the release of ActionScript 3.0. The much-anticipated E4X has finally made its way into Flash Player 9. This makes working with XML in Flash much simpler and reduces the need for complex XML logic. Nearly everything you know about working with XML in Flash has changed with this release.

In this chapter, we examine the new features of E4X, look at how loading external XML files has changed, review the classic XML API, and create an example application that uses a few of these advanced features.

E4X stands for ECMAScript for XML. ECMAScript is the language ActionScript is based on. The E4X specification was developed by ECMA International and was released in the summer of 2004. The goal of E4X is to simplify the way developers use XML inside ECMAScript languages. It also introduces features that were not available in ActionScript 2.0.

Creating XML Objects

ActionScript 3.0 provides two basic ways to create XML objects: using a constructor or using literal notation. The constructor works just as you would expect:

var xml:XML = new XML();

Optionally you can pass a string to the constructor that you want to parse into XML:

var xml:XML = new XML("<data><value>a</value></data>");

However, using literal notation is often far easier and more readable. The following example shows a simple XML object that is created with literal syntax:

var catalog:XML = <catalog>
   <product id="0">
      <name>Product One</name>
      <price>50</price>
   </product>
   <product id="1">
      <name>Product Two</name>
      <price>35</price>
   </product>
</catalog>;

You can see that literal notation is easier and more readable, especially when you compare the literal notation with the equivalent constructor notation, shown here:

var catalog:XML = new XML('<catalog><product id="0"><name>Product One</
name><price>50</price></product><product id="1"><name>Product Two</name><price>35</
price></product></catalog>'),

You can also use literals to declare XMLList objects. When using XMLList literal notation, you should use <></> as the root tag. The following example creates two XML objects named product in an XMLList object:

var products:XMLList = <>
   <product id="0">
      <name>Product One</name>
      <price>50</price>
   </product>
   <product id="1">
      <name>Product Two</name>
      <price>35</price>
   </product>
</>;

Sometimes you’ll have a situation in which you need variable values in your XML object. You can achieve this by using curly braces ({}) to surround the variable within the XML literal. The following example shows how you can create a XML object named product using variable values for both name and price:

var productName:String = "Product One";
var productPrice:Number = 50;
var product:XML = <product id="0">
   <name>{productName}</name>
      <price>{productPrice}</price>
   </product>;
   var catalog:XML = <catalog>
   {product}
   </catalog>;

Note

The name of the variable product is the same as the first element of the XML object. Although this is not required, we do this for simplicity. If we were to use a different name for the variable, we would treat that variable name as though it were the name of the first element of our XML object. By naming them the same, we minimize confusion.

Property Accessors

E4X syntax reuses and extends the property accessor syntax already familiar to ActionScript developers. For example, ActionScript developers are already familiar with how to use dot-notation to access a property such as the x or y properties of a display object.

sprite.x = 100;
sprite.y = 100;

Because of this familiarity, the learning curve for XML in ActionScript 3.0 is lowered.

For the following discussion we’ll use this XML object.

var catalog:XML = <catalog>
   <product id="0">
      <name>Product One</name>
      <price>50</price>
   </product>
   <product id="1">
      <name>Product Two</name>
      <price>35</price>
   </product>
</catalog>;

First, we’ll access the XMLList that is a child of the root node. Because the child nodes of the root are called product, you can use dot notation to access a property of that same name which will reference the XMLList:

var products:XMLList = catalog.product;

XMLList objects allow you to use array-access notation to access the elements of the list. For example, the following code retrieves a reference to the first product XML node.

var firstProduct:XML = catalog.product[0];

Although up to this point the syntax has been familiar, we’ll now introduce one new syntax that uses an @ symbol to reference attributes. By using the @ symbol, you can target attributes of an XML object as shown in the following example. This code retrieves the id attribute value of the first product node.

var id:Number = Number(catalog.product[0].@id);

If you use the @ symbol for an XMLList, it will return an XMLList of all the attributes of that name for all the XMLList elements. Here’s an example that retrieves all the id attributes for all the product nodes:

var allProductIds:XMLList = catalog.product.@id;

You can use an asterisk as a wildcard to get all the items at a given level. For example, the following code retrieves all the product nodes without having to know the name of the nodes:

var firstLevel:XMLList = catalog.*;

You can also use wildcards with attributes. This example retrieves all the attributes of all the product nodes:

var firstLevel:XMLList = catalog.product.@*;

XML Filtering

Arguably, the most powerful and useful feature of E4X is its ability to filter data. This new feature replaces the need for XPath libraries and the loops previously used to search an XML document in earlier versions of ActionScript. Now you can do all this natively in ActionScript 3.0 using as little as one line of code.

To use filtering, you can enclose expressions in parentheses as part of an E4X expression. The following examples show a few uses of filtering. We’ll use the same XML object for all our examples:

var catalog:XML = <catalog>
   <product id="0">
      <name>Product One</name>
      <price>50</price>
   </product>
   <product id="1">
      <name>Product Two</name>
      <price>35</price>
   </product>
</catalog>;

The following line retrieves the product element that has an id attribute equal to 1. This example returns just one XML object, but it would return an XMLList if more than one element matches the criteria.

var product1:XML = catalog.product.(@id == 1);

This next line grabs the name of the product element that has a price less than 50. It’s important to understand that this would return an XMLList if more than one element met the criteria:

var cheapestProductName:String = catalog.product.(price < 50).name;

This example retrieves the product elements that have a price greater than 35 and less than 50:

var productRange:XMLList = catalog.product.(price >= 35 && price <= 50);

Iterating Through an XMLList

Developers must often iterate, or loop, through an XMLList object. ActionScript 3.0 provides a few ways to do this. We’ll use the same catalog example to demonstrate three different ways to iterate through the products and add up the price values. Here again is the literal syntax for creating the catalog object:

var catalog:XML = <catalog>
   <product id="0">
      <name>Product One</name>
      <price>50</price>
   </product>
   <product id="1">
      <name>Product Two</name>
      <price>35</price>
   </product>
</catalog>;

The first method of iterating through an XMLList is a simple for loop. We use the XMLList.length() method to get the number of items in the XMLList. Then we simply iterate that number of times, incrementing the index (i) with each iteration. You use a similar syntax to loop through an indexed array.

var total:Number = 0;
for(var i:int = 0; i < catalog.product.length(); i++) {
   total += (catalog.product[i].price as Number);

}

The second method is a for...in loop that iterates over the XMLList. This method is commonly used to loop over an associative array or an object.

var total:Number = 0;
for(var i:String in catalog.product) {
total += (catalog.product[i].price as Number);
}

The third method is new to ActionScript 3,0. It’s a for each..in loop and provides a cleaner way to loop over XML.

var total:Number = 0;
for each(var product:XML in catalog.product) {
   total += (product.price as Number);
}

It’s also possible to use the new for each..in loop structure to iterate over attributes in an XML object.

var product:XML = <product id="0" name="Product One" price="50" />;
for each(var attribute:XML in product.@*) {
   trace(attribute.name() + ": " + attribute.toXMLString());
}

Namespaces

E4X has the added capability of XML namespaces. Namespaces are used to avoid element name conflicts. Namespaces are most commonly found in complex XML documents that contain XML data from multiple sources. SOAP, the XML format behind many Web services, is one of the most common examples of XML documents that use namespaces. Many developers never use namespaces in their XML documents, but if you do, there are some new ways to work with them in ActionScript 3.0.

We’ll use a simple SOAP envelope to show how you can work with namespaces. The following code shows how to create an XML namespace using literals:

var envelope:XML = <soap:envelope xmlns:soap="http://www.w3.org/2003/05/
soap-envelope" />;

Next, we’ll add a body element to the envelope. The body should have the same namespace as the envelope. We can do this a few different ways. We can use the literal syntax to create the body element, as in this example:

envelope.body = <soap:body xmlns:soap="http://www.w3.org/2003/05/soap-envelope" />

We can also create the body element by declaring the namespace and using the :: operator, like this:

var soap:Namespace = new Namespace("http://www.w3.org/2003/05/soap-envelope");
envelope.soap::body = new XML();

Both of these examples create an XML object that looks like the following:

<soap:envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
 <soap:body />
</soap:envelope>

We can also create an XML object using a default namespace, like this:

default xml namespace = new Namespace("http://www.w3.org/2003/05/soap-envelope");
var envelope:XML = <envelope><body /></envelope>;

Sending and Loading XML Data

The two main classes that send or load data in ActionScript 3.0 are URLRequest and URLLoader. The URLRequest class is used to create any HTTP request, and the URLLoader class is used to transfer the request and listen for the response. Chapter 14, “Sending and Loading Data,” explains how to use URLLoader to send and load data. Here in the following sections we’ll revisit this information in the context of sending and loading XML.

Simple Soap Example

To demonstrate many of the new features of E4X, we’ll create a simple example that uses SOAP-formatted XML to send and receive data from a remote Web service. The service accepts an IP address and returns the geographical location it resolves to.

Note

For more information on sending and loading XML, see Chapter 14.

The following example sends and loads data using a public Web service whose WSDL document is located at http://ws.cdyne.com/ip2geo/ip2geo.asmx. You can read more about the Web service at http://ws.cdyne.com/ip2geo/ip2geo.asmx?op=ResolveIP.

Before we create the example, we’ll first look at the format of the request and response packets. The request packet is a standard SOAP-formatted request. The heart of the request is inside the <m:ResolveIP> tags. It has two child elements: the first is the IP address value, and the second is the license key. Here’s an example of what the packet might look like:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.
w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <SOAP-ENV:Body>
      <m:ResolveIP xmlns:m="http://ws.cdyne.com/">
         <m:IPaddress>24.118.19.171</m:IPaddress>
         <m:LicenseKey>0</m:LicenseKey>
      </m:ResolveIP>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

In this example, notice that the license key is 0. This particular Web service allows us to test the service by using a license key of 0.

The response packet is a SOAP-formatted response. Inside the <ResolveIPResult> tag are the values for the response:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:
xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/
XMLSchema">
   <soap:Body>
      <ResolveIPResponse xmlns="http://ws.cdyne.com/">
         <ResolveIPResult>
            <City>Chicago</City>
            <StateProvince>IL</StateProvince>
            <Country>USA</Country>
            <Latitude>41.5000000</Latitude>
            <Longitude>-87.4100000</Longitude>
            <HasDaylightSavings>false</HasDaylightSavings>
            <Certainty>16</Certainty>
         </ResolveIPResult>
      </ResolveIPResponse>
   </soap:Body>
</soap:Envelope>

You can see that the response includes information such as the city, state, and country to which the IP address resolves. This example contains specific data such as Chicago, IL, and USA. The actual Web service responses will contain data specific to the IP address passed in using the request.

Now that we’ve looked at the structure of the request and response packets, we can build the sample application.

Building the Custom Event

The very first thing we’ll need to build in this example is a custom event type. Because our event has data associated with it, we will create a custom event class called LocationEvent. This class extends the built-in Event class and simply adds five public properties. These properties are later populated from the data returned by the Web service.

package com.peachpit.aas3wdp.e4xexample {

   import flash.events.Event;

   public class LocationEvent extends Event {

      private var _city:String;
      private var _stateProvince:String;
      private var _country:String;
      private var _latitude:String;
      private var _longitude:String;
      
      public function get city():String {
         return _city;
      }

      public function set city(value:String):void {
         _city = value;
      }

      public function get stateProvince():String {
         return _stateProvince;
      }

      public function set stateProvince(value:String):void {
         _stateProvince = value;
      }

      public function get country():String {
         return _country;
      }

      public function set country(value:String):void {
         _country = value;
      }
   
      public function get latitude():String {
         return _latitude;
      }
   
      public function set latitude(value:String):void {
         _latitude = value;
      }
   
      public function get longitude():String {
         return _longitude;
      }

      public function set longitude(value:String):void {
         _longitude = value;
      }

      public function LocationEvent(type:String, bubbles:Boolean = false,
cancelable:Boolean = false) {
         super(type, bubbles, cancelable);
      }

   }

}

This class requires a fair amount of code, but it is quite simple. It merely extends Event and adds additional properties specific to location. We’ll see how to use this event type in the next few sections.

Building the Web Service Class

The ResolveIP class does all the heavy lifting in this example. It is responsible for making the request and listening for the response. All the SOAP communication is encapsulated in this class.

The first thing our class does in the constructor is to create the URLRequest object. This object is where we define the request data (the request SOAP packet), headers, URL, methods, and content type. Notice the use of curly braces to populate the request data with the IP address that is passed into the constructor. The URLLoader object is used to execute this request.

The onComplete() event handler is where we respond to the URLLoader.COMPLETE event. Here we create a custom event object using our LocationEvent class.

package com.peachpit.aas3wdp.e4xexample {

   import com.peachpit.aas3wdp.e4xexample.LocationEvent;
   import flash.events.Event;
   import flash.events.EventDispatcher;
   import flash.net.URLLoader;
   import flash.net.URLRequest;
   import flash.net.URLRequestHeader;

   public class ResolveIP extends EventDispatcher {

      private var urlLoader:URLLoader;

      public function ResolveIP(ipAddress:String) {
         var urlRequest:URLRequest = new URLRequest();
         urlRequest.contentType = "text/xml; charset=utf-8";
         urlRequest.method = "POST";
         urlRequest.url = "http://ws.cdyne.com/ip2geo/ip2geo.asmx";
         var soapAction:URLRequestHeader = new URLRequestHeader("SOAPAction",
            "http://ws.cdyne.com/ResolveIP");
         urlRequest.requestHeaders.push(soapAction);
         urlRequest.data = <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.
            org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/
            encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:
            xsd="http://www.w3.org/2001/XMLSchema">
            <SOAP-ENV:Body>
               <m:ResolveIP xmlns:m="http://ws.cdyne.com/">
                  <m:IPaddress>{ipAddress}</m:IPaddress>
                  <m:LicenseKey>0</m:LicenseKey>
               </m:ResolveIP>
            </SOAP-ENV:Body>
         </SOAP-ENV:Envelope>;

         urlLoader = new URLLoader();
         urlLoader.addEventListener(Event.COMPLETE, onComplete);
         urlLoader.load(urlRequest);
      }

      private function onComplete(event:Event):void {
         var envelope:XML = XML(urlLoader.data);
         var soap:Namespace = envelope.namespace("soap");
         var response:Namespace = new Namespace("http://ws.cdyne.com/");
         var result:XMLList = envelope.soap::Body.response::ResolveIPResponse.
            response::ResolveIPResult;
         var locationEvent:LocationEvent = new LocationEvent(Event.COMPLETE, true, true);
         locationEvent.city = result.response::City.toString();
         locationEvent.stateProvince = result.response::StateProvince.toString();
         locationEvent.country = result.response::Country.toString();
         locationEvent.latitude = result.response::Latitude.toString();
         locationEvent.longitude = result.response::Longitude.toString();
         dispatchEvent(locationEvent);
      }

   }

}

Creating the Main Class

Invoking the ResolveIP class is simple. Just create a new instance of the class and pass an IP address into the constructor during creation. Then register for the complete event and the error event. The complete event returns a LocationEvent object. The error events are not actually thrown by ResolveIP, but instead are thrown by the URLLoader object inside the ResolveIP object. Because the error events aren’t caught by the ResolveIP object, they bubble up to our WebServiceExample object.

Here’s the main class for our SOAP-parsing Web service example:

package {

   import com.peachpit.aas3wdp.e4xexample.LocationEvent;
   import com.peachpit.aas3wdp.e4xexample.ResolveIP;
   import flash.events.Event;
   import flash.events.IOErrorEvent;
   import flash.events.SecurityErrorEvent;
   import flash.events.KeyboardEvent;
   import flash.ui.Keyboard;
   import flash.display.Sprite;
   import flash.text.TextField;
   import flash.text.TextFieldType;

   public class WebServiceExample extends Sprite {

      private var _resolveIP:ResolveIP;
      private var _text:TextField;

      public function WebServiceExample() {

         // Create a text field to accept user input and 
         // display the response.
         _text = new TextField();
         _text.type = TextFieldType.INPUT;
         _text.width = 200;
         _text.height = 22;
         _text.background = true;
         _text.border = true;

         // Initially populate the text field with a valid
         // IP address. This makes it easy to test the
         // application. You can also change this at runtime
         // if you want to test a different IP address.
         _text.text = "24.118.19.171";

         // Listen for keyboard events.
         _text.addEventListener(KeyboardEvent.KEY_UP, onKey);
         addChild(_text);
      }

      private function onKey(event:KeyboardEvent):void {

         // If the user presses the Enter key then createa a
         // new ResolveIP object, passing it the text from
         // the text field. Then listen for events.
         if(event.keyCode == Keyboard.ENTER) {
            _resolveIP = new ResolveIP(_text.text);
            _resolveIP.addEventListener(Event.COMPLETE, onComplete);
            _resolveIP.addEventListener(IOErrorEvent.IO_ERROR, onError);
            _resolveIP.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);
         }
      }

      // When the response is returned, display the location
      // to which the IP address resolves.
      private function onComplete(locationEvent:LocationEvent):void {
         _text.text = locationEvent.city + ", " + locationEvent.stateProvince;
      }

      private function onError(event:Event):void {
         _text.text = "error: " + event.type;
      }

   }

}

You can test this example by entering a valid IP address in the text field and pressing the Enter key. The Web service will respond shortly, and the result will be displayed in the text field.

Summary

E4X is one of the biggest improvements to ActionScript in years. This enhancement increases the power of XML and makes it an even more robust option for ActionScript programmers. In this chapter, you learned about the new features of E4X and created a simple Web service application.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.14.67.185