Using XML in Flash CS3/AS3 - Page 1
       by kirupa  |  28 May 2007

One of the major changes in AS3 is how you deal with XML content. While a lot of similarities carried over from the AS2 days, there are new time-saving niceties in AS3 that make working with XML files easier. One of the introductions is Ecmascript for XML, known as E4X for short. In this tutorial, you will gradually learn more XML tricks as you try to parse the various parts of the following XML data:

Example of XML Document 

This tutorial attempts to cover a lot of ground, so I've provided a short table of contents to give you an idea of what to expect:

  1. The XML Structure
  2. Loading an XML File
  3. Reading the XML Data
    1. XML and XMLList
    2. Accessing Data Directly
    3. Accessing Data Indirectly
    4. Reading Attributes
  4. Filtering Values

As you can see, there are a lot of topics that you'll learn how to juggle in Flash. Let's start at the top and being by first describing what an XML file is.

The XML Structure
An XML file is essentially a tree with various branches and leaves commonly known as nodes and values. Our above XML data is no exception. The following diagram shows one way of representing our sample XML data:

XML Data

Think of each box in the above image as a node. We have our main root node called Books, and that root node has four child nodes called Book. The Book node contains the ISBN information, and information stored directly on the node is called an attribute. You can also store information in your child nodes, and the book's title and author information is stored in child nodes aptly called title and author.

This type of a hierarchy, like all XML data, is essentially a tree. In the computer world, trees are great because they help you to categorize information all the way from a broad overview at the top of the tree to the details at the leaves. From the above data, you can easily see that the parent node (Books) sets the agenda for what the child nodes (Book, Title, Author) will follow.

Before the end of this tutorial, you will learn several ways of accessing all of the node and attribute information. Before you can access the data, you need to load the XML data first. Let's look at how to do that on the next page.

Onwards to the next page!

Loading an XML File
When loading an XML file, there are several steps. The following is the code to load an XML file into Flash:

var xmlLoader:URLLoader = new URLLoader();
var xmlData:XML = new XML();
 
xmlLoader.addEventListener(Event.COMPLETE, LoadXML);
 
xmlLoader.load(new URLRequest("http://www.kirupa.com/net/files/sampleXML.xml"));
 
function LoadXML(e:Event):void {
xmlData = new XML(e.target.data);
trace(xmlData);
}

When you paste the above code into the Actions window in Flash CS3 and press Ctrl + Enter to preview, you will see our XML file's contents displayed in the Output window:

Now that you know that our code works, let's take a better look at the code and understand how it helps you load an XML file into memory:

var xmlLoader:URLLoader = new URLLoader();
var xmlData:XML = new XML();

In these two lines you declare two variables called xmlLoader and xmlData. The xmlLoader variable is of type URLLoader, and the URLLoader class helps you to load data from an external source such as a URL.

The xmlData variable is of type XML, and the XML class provides you with a lot of functionality for accessing and manipulating XML data. We'll be using XML objects throughout this tutorial, so I'll describe XML objects more in the upcoming sections.


xmlLoader.addEventListener(Event.COMPLETE, LoadXML);

In this line of code, we register an event listener to our xmlLoader object. An event listener basically, as its name implies, listens for a certain event, and when that event occurs, calls an event handler.

In our line of code, we listen for the completed event (Event.COMPLETE), and when we hear that event, we call the LoadXML function. This is a good way of ensuring that we do not prematurely start fiddling with the data until all of our data has been loaded.


xmlLoader.load(new URLRequest("http://www.kirupa.com/net/files/sampleXML.xml"));

We aren't done with our xmlLoader object yet. In the above line, we call our xmlLoader's load method. The load method only takes a URLRequest object as its argument. The reason is, when you download data from the internet, the data is downloaded piecewise as streams. The URLRequest class ensures all of the data is loaded in its entirety, so our load method gets all of the XML data at once.


Let's now look at the LoadXML method which I briefly mentioned when discussing the addEventListener method earlier:

function LoadXML(e:Event):void {
xmlData = new XML(e.target.data);
trace(xmlData);
}

Your LoadXML event handler method gets called when your xmlLoader's event listener detects a COMPLETE event. The COMPLETE event only gets fired when all of the external data via the load method fully gets loaded.

Our LoadXML method takes one argument of type event because it as an event handler. The argument, e, contains data related to the event that fired it. You can access the data sent to e by our event listener by checking e.target.data. Since our event listener is located inside the URLLoader class, the data sent to it is the XML data you loaded via the URLRequest earlier.

All of the data is stored in our XML object called xmlData. After your LoadXML method has run its course, all of your XML data is then stored in memory.


Reading the XML Data
In the previous section, you learned how to load XML data into your application. Now, let's learn how to read the various pieces of information stored in our file. Before we get to some code, let's take a look at two classes you'll be using.

XML and XMLList
The first class, the XML class, should already be familiar to you. You declared an XML object earlier, and the data returned by our event listener was stored in as a new XML object. This class provides you with the basic functionality needed to manipulate and access data stored in an XML file.

The second class that you will use is XMLList. In many instances when you are working with the XML data, you will find yourself dealing with multiple XML objects, and those XML objects will be placed within a list-like structure called an XMLList.

Accessing Data Directly
In AS3, accessing XML data is more straightforward than it was in AS2. Part of the reason, like I mentioned at the beginning of this article, is the use of E4X. Replace your existing LoadXML function with the following two functions:

function LoadXML(e:Event):void {
xmlData = new XML(e.target.data);
ParseBooks(xmlData);
}
 
function ParseBooks(bookInput:XML):void {
trace("XML Output");
trace("------------------------");
trace(bookInput);
}

Notice that you now have a new ParseBooks function that takes an XML object as an argument. The LoadXML function has also been modified with the trace statement from before replaced with a call to the ParseBooks method with our xmlData XML object sent as the argument.

When you test your application by pressing Ctrl + Enter, you'll see the same XML data that you saw earlier:

Example of XML Document

Let's say we wanted to access all of the Book elements from our above data. In AS2, you would have had to write some code to scan our XML file and stop once it had reached the Book node. In AS3, all you have to do is add one extra word to our trace(bookInput) line:

function ParseBooks(bookInput:XML):void {
trace("XML Output");
trace("------------------------");
trace(bookInput.Book);
}

We changed the trace statement from just bookInput to that of bookInput.Book where Book represents the name of element(s) we are interested in. When you run your above code you will only see the nodes that match the Book element name:

Let's go a step further. Let's say we only wanted the names of all of the authors located inside our Book nodes. The names of the book authors are stored in the <author> elements, so we change our trace statement from bookInput.Book to bookInput.Book.author:

function ParseBooks(bookInput:XML):void {
trace("XML Output");
trace("------------------------");
trace(bookInput.Book.author);
}

When you test your application again, you will see the following displayed in your Output window:

Notice that now, you are seeing a list of authors instead. The author names are surrounded by the <author> tags themselves. The reason is you are tracing the actual XML element itself - not the XML element's contents. To retrieve the contents of an XML element, you can use the text() function:

function ParseBooks(bookInput:XML):void {
trace("XML Output");
trace("------------------------");
trace(bookInput.Book.author.text());
}

When you test your application this time, all of the authors are displayed without their surrounding <author> tag names. Unfortunately, the names are all on one line with no space between them:

That is a small side-effect that can easily be changed. One way to fix this is by using index positions to retrieve just the values we are interested in. For example, to retrieve Sir Arthur Conan Doyle, I pass in the index position 0 as in:

function ParseBooks(bookInput:XML):void {
trace("XML Output");
trace("------------------------");
trace(bookInput.Book.author.text()[0]);
}

Running the above code will display Sir Arthur Conan Doyle as we wanted. In many cases, it is probably not convenient to manually enter index values. That is especially true if you do not have advance knowledge of how many nodes or child-nodes your XML data will have. In cases such as that, you will need an indirect, iterative approach different from the direct approach used in this section.

Accessing Data Indirectly
The indirect approach involves using loops and iterating through the child nodes and extracting the information as needed. When we traced the author information, we received all of the author names in one single line. Our goal is to figure out a way to iterate through our list and display the information individually.

To do that, remember that collections of XML information are always returned as an XMLList, and an XMLList contains only XML objects. Knowing that, we know that the author information returned is in the form of an XMLList, and each author is an XML object.

The following is the code I use inside our ParseBooks function for displaying each author information using a loop:

function ParseBooks(bookInput:XML):void {
trace("XML Output");
trace("------------------------");
 
var authorList:XMLList = bookInput.Book.author;
 
for each (var authorElement:XML in authorList) {
trace(authorElement);
}
}

Let's look at our above code in greater detail:

var authorList:XMLList = bookInput.Book.author;

I first create an object called authorList that is of type XMLList, and I initialize authorList to the XMLList returned by bookInput.Book.author.

for each (var authorElement:XML in authorList) {
trace(authorElement);
}

In the above lines, I iterate through our authorList using a for-each statement. Because this is a for-each statement, I do not worry about index positions. Instead, just let the built-in iteration process extract the subsequent value and assign that value to a new XML object called authorElement. The reason authorElement is of type XML is because, like I mentioned before, an XMLList contains XML objects.


If you want to use a for loop instead of the above for-each loop, the code would be:

for (var i:int = 0; i < authorList.length(); i++)
{
var authorElement:XML = authorList[i];
trace(authorElement);
}

The only thing to note is that the loop ends when our index variable is is less than the total number of items in authorList. You use the XMLList's length property to determine the number of items the list contains. Also, you end the loop when the index variable is less than the total number of items because lists are zero-based, so you start counting at 0 instead of the more natural 1.


If you happen to not even know the name of of the nodes you are looking for, you can use the more generic children() function. The children function returns all of a node's children, and they are returned in the form of an XMLList. Once you have your XMLList, you can process the data in any way you choose:

For example, the following is an example on how to access information by looping through a node's children:

function ParseBooks(bookInput:XML):void {
trace("XML Output");
trace("------------------------");
 
var bookChildren:XMLList = bookInput.Book.children();
 
for each (var bookInfo:XML in bookChildren) {
trace(bookInfo);
}
}

Unlike in the previous examples where only the author information was printed, this time all of our Book node's children - title and author - are displayed:

You can filter by author checking your XML object, bookInfo's name property. The name property returns the name of the node your data is stored in. For example, altering our above trace code from trace(bookInfo) to trace(bookInfo.name()) produces the following output:

What you see is the node names of the data you saw earlier. With this information, you can filter what data you want to be accessing the node value from the node names you are interested in. For example, the following would be the altered ParseBooks function for displaying only the authors via the children property:

function ParseBooks(bookInput:XML):void {
trace("XML Output");
trace("------------------------");
 
var bookChildren:XMLList = bookInput.Book.children();
 
for each (var bookInfo:XML in bookChildren) {
if (bookInfo.name() == "author") {
trace(bookInfo);
}
}
}

Notice that I check to see what our current XML node's name is. If the node's name matches what I am looking for, I then display the relevant information. In the above code, I check to see if our child node bookInfo is called author, and if it is, I display the author information.

Reading Attributes
So far, we have primarily dealt with reading nodes and their nested information. Attributes are different in that they are information stored directly on the node itself. As such, the approach for accessing that data is a little different.

In our XML example, the attribute is the ISBN information located directly on the Book node. Let's look at the AS used to extract that information:

function ParseBooks(bookInput:XML):void {
trace("XML Output");
trace("------------------------");
 
var bookAttributes:XMLList = bookInput.Book.attributes();
 
for each (var bookISBN:XML in bookAttributes) {
trace(bookISBN);
}
}

If you overwrite your earlier ParseBooks function with the above function and test your application (Ctrl + Enter), you will see the ISBN numbers printed. Let's look at the one line of code that makes this work:

var bookAttributes:XMLList = bookInput.Book.attributes();

To access the list of attributes, I call the attributes() method on our Book node. The attributes information, if available, is returned in the form of...you guessed it...an XMLList! I then iterate through that list like before and print out that information.

This approach, like our earlier XML's children() approach, prints out all of the matching data. In this case, all attribute values are printed out. That is not a problem because we only have one attribute per Book node, but if you had multiple attributes, all of them would be printed. Let's look at two ways of keeping track of them.

The first approach is similar to what you already used earlier. You check for the attribute's name and see if it matches what you are looking for:

function ParseBooks(bookInput:XML):void {
trace("XML Output");
trace("------------------------");
 
var bookAttributes:XMLList = bookInput.Book.attributes();
 
for each (var bookISBN:XML in bookAttributes) {
if (bookISBN.name() == "ISBN") {
trace(bookISBN);
}
}
}

This works because the name() method returns the name of node. The name() method does not distinguish between children, attributes, parents, etc. It only cares about what the name of the XML object is.

Another approach is where you filter based on the attribute name when generating your XMLList itself:

function ParseBooks(bookInput:XML):void {
trace("XML Output");
trace("------------------------");
 
var bookAttributes:XMLList = bookInput.Book.attribute("ISBN");
 
for each (var bookISBN:XML in bookAttributes) {
trace(bookISBN);
}
}

In this approach, you use the attribute() method where you pass in the name of the attribute you are looking for:

var bookAttributes:XMLList = bookInput.Book.attribute("ISBN");

Your bookAttributes XMLList object will only contain a list of XML objects that contain attributes matching the ISBN name. This approach avoids you having to iterate through a larger list of XML objects and checking each name manually.

Filtering Values
Another new feature in AS3 is the ability to specifically filter and display the data you are interested in seeking. In many examples in the preceding pages, you scanned through XML objects and tried to see if a name matched the value you are looking for.

Let's say I wanted to find all books by Stephen E. Ambrose from my XML data. I could create a function that scans each XML object's value and returns the parent XML node the value was found in. In AS3, a simpler, more powerful approach exists, and the following is my code for returning all books whose author was Stephen E. Ambrose:

function ParseBooks(bookInput:XML):void {
trace("XML Output");
trace("------------------------");
 
var authorList:XMLList = bookInput.Book.(author == "Stephen E. Ambrose");
trace(authorList);
}

When you run the above code, your Output window will display the following:

Notice that the data returned are the actual XML Book objects that contain XML nodes named author whose value is Stephen E. Ambrose. This was possible because of the filtering mechanism introduced in AS3:

var authorList:XMLList = bookInput.Book.(author == "Stephen E. Ambrose");

Instead of specifying bookInput.Book.author, returning a list of XML objects, and scanning each object for the author name Stephen E. Ambrose, I simply provide the keyword, a comparison operator, and the value that I want to search for. The rest are taken care of behind the scenes, and what you are left with in the end, is a collection of XML objects that match the criteria you specify.

Your results are currently in the form of XML objects. To display the actual titles of books written by Stephen Ambrose, you can simply append the .title keyword following your filter command:

var authorList:XMLList = bookInput.Book.(author == "Stephen E. Ambrose").title;

The reason you can do this is because the results of your filter command are also returned in the form of an XMLList. Any operations you perform on an XMLList, such as searching for the title, are applied to each XML object stored within the XMLList.

Filtering your data based on attribute information is only slightly different. To return a list of books whose ISBN matches a certain value, all you need to do is prepend the @ symbol in front of your attribute keyword.

Try out the following code:

function ParseBooks(bookInput:XML):void {
trace("XML Output");
trace("------------------------");
 
var bookList:XMLList = bookInput.Book.(@ISBN == "0743203178").title;
trace(bookList);
}

When you run your application with the above ParseBooks function, you will see "Nothing Like It In the World" displayed. Let's look at the authorList declaration in greater detail:

var authorList:XMLList = bookInput.Book.(@ISBN == "0743203178").title;
trace(authorList);

Instead of typing in ISBN == "....", you have to prepend the keyword with the @ symbol to flag that as an attribute keyword. By typing @ISBN == "0743203178", any nodes whose ISBN attribute matches that number is returned.

In all of the previous filtering examples, you only filtered your values based on one condition. You can actually have as many conditions as you want, so you can create sophisticated filters for precisely find the data you need.

For example, try the following code:

function ParseBooks(bookInput:XML):void {
trace("XML Output");
trace("------------------------");
 
var bookList:XMLList = bookInput.Book.(author == "Stephen E. Ambrose" && title != "Nothing Like It In the World").title;
trace(bookList);
}

When you test your application with the above code, Undaunted Courage will be the only returned result. Notice that we are filtering our data by both author and title. The author must be Stephen E. Ambrose, but the title cannot be Nothing Like It In the World.


 

1 | 2 | 3




SUPPORTERS:

kirupa.com's fast and reliable hosting provided by Media Temple.