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:
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:
- The XML Structure
- Loading an XML File
- Reading the XML Data
- XML and XMLList
- Accessing Data Directly
- Accessing Data Indirectly
- Reading Attributes
- 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.
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:
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!
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.
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.
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.
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:
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.
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.
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.
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.
|