Navigating an XML Object

In Chapter 2, I talked about XML parsers. You might remember that we use a parser to process an XML document. Once parsed, the software can work with the content from the XML document.

There are two types of parsers: validating and nonvalidating. The difference is that validating parsers compare an XML document against a schema or DTD to make sure that youve constructed the document correctly. Nonvalidating parsers dont do this.

Flash contains a nonvalidating parser. When you load an XML document into Flash, it processes the contents and creates an XML document tree. If you include a reference to a schema or DTD within an XML document, Flash wont check the document for validity before it is loaded.

The Flash document tree includes all elements from the XML document. Flash uses a family analogy to refer to different branches within the tree. Elements can be children of another parent element, or siblings .

Each element includes a collection of child elements called childNodes . The collection is an array so we can use the standard array methods . For example, we can determine how many elements are in the collection using the length property. Elements with no child elements will have a childNodes length of . We can also loop through the collection when were processing an XML object.

In the address.xml file, shown here, the root element <phoneBook> has three child <contact> elements. We could programmatically work through each of the child elements, for example, adding them to a List component.

 <?xml version="1.0" encoding="UTF-8"?> <phoneBook>   <contact id="1">     <name>Sas Jacobs</name>     <address>123 Some Street, Some City, Some Country</address>     <phone>123 456</phone>   </contact>   <contact id="2">     <name>John Smith</name>     <address>4 Another Street, Another City, Another Country</address>     <phone>456 789</phone>   </contact>   <contact id="3">     <name>Jo Bloggs</name>     <address>7 Different Street, Different City, UK</address>     <phone>789 123</phone>   </contact> </phoneBook> 

You can refer to a child element by its position in the collection. Because the childNodes collection is an array, the first element is at number . The following line refers to the first child element of the myXML object. You can also refer to children of an XMLNode object.

 myXML.childNodes[0]; 

The firstChild and lastChild properties allow you to refer to the first and last items in the childNodes collection.

 myXML.firstChild; myXML.lastChild; 

When you are processing an XML document tree, you usually start by referencing the root node of the document. This is the parent of all other elements and is the firstChild or childNodes[0] of the XML object.

All elements are children of the XML object, so you can refer to each one using its position within the XML object. Its a bit like a map. Start with the root node and move to the first child. Go to the second child node and find the third child. Finish at the first child of this node. You can end up with long paths as shown here:

 myXML.firstChild.childNodes[1].childNodes[2].firstChild; 

Youll learn a bit more about locating specific child nodes in a document later in this chapter. In the next section, Ive shown the Flash notation for the XML elements in the file address.xml .

Mapping an XML document tree

Ive shown the complete address.xml document here. Table 4-1 shows how you can refer to specific parts of this document once its loaded into Flash. The table assumes that weve created an XML object called myXML .

Table 4-1: Mapping the XML document tree for address.xml

Element

Flash XML Element Path

<?xml version="1.0" encoding="UTF-8"?>

xmlDecl property

<phoneBook>

myXML.firstChild or myXML.childNodes[0]

<contact id="1">

myXML.firstChild.firstChild or myXML.childNodes[0].childNodes[0]

<name>

myXML.firstChild.firstChild.firstChild or myXML.childNodes[0].childNodes[0]. childNodes[0]

<address>

myXML.firstChild.firstChild.childNodes[1] or myXML.childNodes[0].childNodes[0]. childNodes[1]

<phone>

myXML.firstChild.firstChild.lastChild or myXML.childNodes[0].childNodes[0].childNodes[2]

<contact id="2">

myXML.firstChild.childNodes[1] or myXML.childNodes[0].childNodes[1]

<name> (within contact 2)

myXML.firstChild.childNodes[1].firstChild or myXML.childNodes[0].childNodes[1]. childNodes[0]

<address> (within contact 2)

myXML.firstChild.childNodes[1]. childNodes[1] or myXML.childNodes[0].childNodes[1]. childNodes[1]

<phone> (within contact 2)

myXML.firstChild.childNodes[1].lastChild or myXML.childNodes[0].childNodes[1]. childNodes[2]

<contact id="3">

myXML.firstChild.childNodes[2] or myXML.childNodes[0].childNodes[2]

<name> (within contact 3)

myXML.firstChild.childNodes[2].firstChild or myXML.childNodes[0].childNodes[2]. childNodes[0]

<address> (within contact 3)

myXML.firstChild.childNodes[2].childNodes[1] or myXML.childNodes[0].childNodes[2]. childNodes[1]

<phone> (within contact 3)

myXML.firstChild.childNodes[2].lastChild or myXML.childNodes[0].childNodes[2]. childNodes[2]

 <?xml version="1.0" encoding="UTF-8"?> <phoneBook>   <contact id="1">     <name>Sas Jacobs</name>     <address>123 Some Street, Some City, Some Country</address>     <phone>123 456</phone>   </contact>   <contact id="2">     <name>John Smith</name>     <address>4 Another Street, Another City, Another Country</address>     <phone>456 789</phone>   </contact>   <contact id="3">     <name>Jo Bloggs</name>     <address>7 Different Street, Different City, UK</address>     <phone>789 123</phone>   </contact> </phoneBook> 

You can replace the myXML references with this if youre including the references in the onLoad function of the XML object. Placing any of the paths in a trace statement will display the complete element in an Output window. For example, the following onLoad function traces the second contacts <name> element from the address.xml file, as shown in Figure 4-4.

image from book
Figure 4-4: Tracing an element from the document tree
 var myXML:XML = new XML(); myXML.ignoreWhite = true; myXML.onLoad = processXML; myXML.load("address.xml"); function processXML(success:Boolean):Void {   if (success) {  trace(this.firstChild.childNodes[1].firstChild);  }   else {     trace ("Error loading XML file");   } } 

Understanding node types

The XML class stores XML content in a document tree. Earlier in the book, we learned that XML documents can contain

  • Elements

  • Attributes

  • Text

  • Entities

  • Comments

  • Processing instructions

  • CDATA

Within an XML document tree, Flash recognizes only two types of nodesXML elements and text nodes. You can access the attributes within an XML element but Flash ignores comments, processing instructions, and CDATA.

You can use the property nodeType to identify which type of element youre working with. The property returns a value of 1 for element nodes and 3 for text nodes. Its important to know which type youre working with because some properties of the XML class are specific to certain node types. This code shows how you can use the nodeType property to display the node type:

 trace(myXML.nodeType); trace(myXMLNode.nodeType); 

You can find the name of an element node by using the nodeName property. This is the name of the tag included within the element, and you can use the property with an XML object or an XMLNode object.

 trace(myXML.firstChild.nodeName); trace(myXMLNode.nodeName); 

Text nodes dont have a tag name, so the nodeName property will return a value of null .

A text node is the child of the parent element node. Instead of a nodeName , text nodes have a nodeValue , which displays the text content. To display the text inside an element, you can use

 trace(myXML.firstChild.firstChild.firstChild.nodeValue); 

The nodeValue property for an element node will display null .

Table 4-2 shows some examples of how to access the text from the file address.xml .

Table 4-2: Locating the text nodes within XML document tree for address.xml

Text

Flash XML Element Path

Sas Jacobs

myXML.firstChild.firstChild.firstChild.firstChild.nodeValue or myXML.childNodes[0].childNodes[0].childNodes[0].childNodes[0] .nodeValue

7 Different Street, Different City, UK

m yXML.firstChild.childNodes[2].childNodes[1].firstChild.nodeValue or myXML.childNodes[0].childNodes[2].childNodes[1]. childNodes[0].nodeValue

456 789

myXML.firstChild.childNodes[1].childNodes[2].firstChild.nodeValue or myXML.childNodes[0].childNodes[1].childNodes[2].childNodes[0]. nodeValue

Again, you can replace the myXML references with this if youre including these references in an onLoad function. Adding any of the paths shown in Table 4.2 in a trace statement will display the complete element in an Output window.

The statements in the preceding table appear a little confusing. The paths are long, and its not easy to figure out which element were targeting with paths like firstChild.childNodes[1].childNode[2]. Your code will be much easier to read if you create XMLNode variables. These variables can act as signposts to specific parts of the XML document and make it easier to navigate the document tree.

Creating node shortcuts

As youve seen, writing a path to a specific element within the document tree can be an arduous process. Its much easier to use an XMLNode variable to provide a shortcut to a specific position in the document tree, as shown here:

 var myXMLNode:XMLNode = myXML.firstChild.firstChild.firstChild; 

By writing this line, you can use myXMLNode to refer to the element instead of the full path. If you use descriptive names for the XMLNode objects, youll find it much easier to understand your code:

 var NameNode:XMLNode = myXML.firstChild.firstChild.firstChild; trace(NameNode); 

Youd normally start this process by locating the root node of the document. Remember that each file has a single root node that contains all of the other elements.

Finding the root node

The root node of the tree is always the first child of the XML object, so you can locate it with the following code. Both lines are equivalent.

 myXML.firstChild; myXML.childNodes[0]; 

If you are referring to the first child within the onLoad function, you can also use

 this.firstChild; this.childNodes[0]; 

Displaying the firstChild of the XML document in an Output window is almost the same as displaying the entire XML document tree. The difference is that the firstChild doesnt include the XML declaration.

In the resource file simpleload.fla , replacing the line trace (this); with trace(this.firstChild); will show the document tree without the XML declaration.

 var myXML:XML = new XML(); myXML.ignoreWhite = true; myXML.onLoad = processXML; myXML.load("address.xml"); function processXML(success:Boolean):Void {   if (success) {  trace(this.firstChild);  }   else {     trace ("Error loading XML file");   } } 

It can be useful to assign the root node to a variable so that you dont have to keep writing this.firstChild each time.

Setting a root node variable

Its often useful to set a variable for the root node. I like to use the variable name RootNode . You can use the variable type XMLNode so that youll get code hints each time you type the variable name. These two lines are equivalent, and you can use either:

 var RootNode:XMLNode = myXML.firstChild; var RootNode:XMLNode = myXML.childNodes[0]; 

If youre referring to the root node from within the onLoad function, you can also use the word this :

 var RootNode:XMLNode = this.firstChild; var RootNode:XMLNode = this.childNodes[0]; 

Setting a variable provides a shortcut each time you want to refer to the root node. It saves you from having to write myXML.firstChild or myXML.childNodes[0] .

To get to the first child of the root node, you could use either of these two lines:

 this.firstChild.firstChild; this.childNodes[0].childNodes[0]; 

You could also write

 var RootNode:XMLNode = this.firstChild; RootNode.firstChild; 

or

 var RootNode:XMLNode = this.childNodes[0]; RootNode.childNodes[0]; 

Using the descriptive name RootNode makes it much easier to identify your position within the XML document. You can use the same approach with other elements within the XML document tree.

Displaying the root node name

You can find out the name of the root node by using its nodeName property:

 var RootNode:XMLNode = myXML.firstChild; trace(RootNode.nodeName); 

This is equivalent to the single line

 trace (myXML.firstChild.nodeName); 

You can try this with your resource file simpleload.fla . Modify the onLoad function as shown here:

 function processXML(success:Boolean):Void {   if (success) {  var RootNode:XMLNode = this.firstChild;   trace(RootNode.nodeName);  }   else {     trace ("Error loading XML file")   } } 

When you test the movie, you should see an Output window similar to that shown in Figure 4-5.

image from book
Figure 4-5: Displaying the root node name

You can see this example in the resource file simpleprocess.fla .

When you first start working with the XML class, it can be a very useful to trace the name of the root node as a first step. Making sure that the name is correct will help you to identify simple errors such as forgetting to set the ignoreWhite property value to true.

Once youve located the root node, you can start working your way through the document tree to find specific child nodes. Again, its useful to create variables for positions within the document tree to make your code easier to understand.

Locating child nodes

Earlier in the chapter, you saw some examples of how to locate the child nodes within an XML object. Tables 4-1 and 4-2 provide some useful summaries. You started with the root element and used properties to find a specific node.

Working with specific child nodes

To refer to a specific node in your document tree, you need to construct a path. You can refer to each section of the path using properties like firstChild or a position in the childNodes collection such as childNodes[2] .

For example, in the XML fragment that follows , the <contact> element is the firstChild of the <phoneBook> root element, which is the firstChild of the XML object. The <name> , <address> , and <phone> elements are childNodes[0] , childNodes[1] , and childNodes[2] , respectively, of the <contact> element.

 <phoneBook>   <contact id="1">     <name>Sas Jacobs</name>     <address>123 Some Street, Some City, Some Country</address>     <phone>123 456</phone>   </contact> </phoneBook> 

To refer to the <address> element, I could use the path

 myXML.firstChild.firstChild.childNodes[1]; 

or

 myXML.childNodes[0].childNodes[0].childNodes[1]; 

I could combine this with a root node variable to achieve the same result:

 var RootNode:XMLNode = myXML.firstChild; RootNode.firstChild.childNodes[1]; 

You can see an example of this in the resource file simpleprocess.fla .

I could use the following code to refer to the <phone> element:

 myXML.firstChild.firstChild.lastChild; 

or

 myXML.childNodes[0].childNodes[0].childNodes[3]; 

The childNodes collection and the firstChild and lastChild properties are read-only. This means you cant use them to change the structure of the XML object.

Text elements are always the firstChild of the element that contains them. To refer to the text inside the <address> element, I could use the expression

 myXML.firstChild.firstChild.childNodes[1].firstChild.nodeValue; 

or

 myXML.childNodes[0].childNodes[0].childNodes[1].firstChild.nodeValue; 

I could also use the RootNode variable as shown here:

 var RootNode:XMLNode = myXML.firstChild; RootNode.childNodes[0].childNodes[1].firstChild.nodeValue; 

Again, you can see an example of this in simpleprocess.fla . You may need to uncomment the relevant lines in the file.

All of the child nodes of an element live within the childNodes collection. This is an array of all the child nodes. As youll often want to treat each childNode in a similar way, it makes more sense to work with the collection as a whole.

Working with the childNodes collection

Its more common to work with all childNodes in a collection rather than finding single nodes within the document tree. You can loop through the collection and perform similar actions on all of the nodes. The code that follows shows how to use a for loop in this way. We can determine how many children are in the collection of childNodes by using the childNodes.length property. This is the same as the length property of an array.

 for (var i:Number=0; i < myXMLNode.childNodes.length; i++) {   //do something } 

You can determine if an element has child nodes by testing the length property of the collection or by using the hasChildNodes method. You may want to perform one action for elements with child nodes and another for elements without children. Using the hasChildNodes method returns a value of either true or false , so it is often used within if statements, as shown in this code snippet:

 if (RootNode.hasChildNodes()) {   //do something with the child nodes } else {   //do something else } 

The following example shows how we could display all of the names of the children of a specific node, in this case, the first <contact> element. Ive shown the relevant lines in bold. You can also open the resource file simpleprocess.fla to test the example.

 function processXML(success:Boolean):Void {   if (success) {     var RootNode:XMLNode = this.firstChild;  var ContactNode:XMLNode = RootNode.childNodes[0];   for (var i:Number=0; i < ContactNode.childNodes.length; i++) {   trace (ContactNode.childNodes[i].nodeName);   }  }   else {     trace ("Error loading XML file");   } } 

This code assigns the first contact node to an XMLNode variable called ContactNode . We can then loop through each of the child nodes of that variable and display their names.

If you test the movie, you should see an Output window similar to the one shown in Figure 4-6.

image from book
Figure 4-6: Displaying the child node names

Notice that I used an XMLNode variable called ContactNode to refer to the first <contact> element. The expression

 ContactNode.childNodes[i].nodeName; 

is much easier to understand than

 myXML.firstChild.firstChild.childNodes[i].nodeName; 

I could modify the function to display the text within each of the childNodes . Remember that the text within a node is always the firstChild of that node and that you can find the text using nodeValue . Ive shown an example here; you can also see it in the simpleprocess.fla resource file.

 function processXML(success:Boolean):Void {   if (success) {     var RootNode:XMLNode = this.firstChild;     var ContactNode:XMLNode = RootNode.childNodes[0];     for (var i:Number=0; i <ContactNode.childNodes.length; i++) {       trace (ContactNode.childNodes[i].firstChild.nodeValue);     }   }   else {     trace ("Error loading XML file");   } } 

Working your way through a complicated XML document can take some time. You have to understand the document structure and write code accordingly . An alternative way to work with the entire document tree is to use recursive functions. This can also be useful if you dont know the structure of the file or the names of the nodes.

Creating recursive functions

A recursive function is a function that calls itself. You can use a recursive function to extract the contents from the whole document tree. By calling the function again and passing the next branch of the tree, you can work your way through the entire XML object. You start by calling the function and passing the root node. If you find child nodes, you call the function again with each of the child nodes. You repeat the process until youve moved through the entire document tree.

This concept can be a little difficult to grasp, so Ill work through an example to help you understand it better.

Exercise 2: Processing an XML object with a recursive function
image from book
  1. Create a new Flash file and save it in the same folder as the address.xml file.

  2. Enter the following code. Instead of processing the XML object with the processXML function, Ive used it to call another function called showChildren . The showChildren function takes one parameter, the root node of the XML object, which Ive specified using this.firstChild .

     var myXML:XML = new XML(); myXML.ignoreWhite = true; myXML.onLoad = processXML; myXML.load("address.xml"); function processXML(success:Boolean):Void {   if (success) {     showChildren(this.firstChild);   }   else {     trace ("Error loading file");   } } 
  3. Add the showChildren function in the Actions panel, underneath the processXML function:

     function showChildren(startNode:XMLNode):Void{   if (startNode.nodeType == 1) {     if (startNode.hasChildNodes()) {       trace (startNode.nodeName + " has child elements:");       for (var i:Number = 0; i < startNode.childNodes.length; i++) {         if (startNode.childNodes[i].nodeType == 1) {           trace ("element: " + startNode.childNodes[i].nodeName);         }         else {           trace ("text: " + startNode.childNodes[i].nodeValue);         }         showChildren(startNode.childNodes[i]);       }     }   } } 

The function looks confusing at first. It takes an XMLNode variable as a parameter and only proceeds if the XMLNode is an element node, that is, nodeType == 1 . Text nodes cant have children.

The second if statement determines whether there are any childNodes of the current element node. If there are, the function traces the name of the node and the words has child elements .

Next, the function loops through the childNodes of the starting node. If the childNode is an element, the function traces the word element with the node name. Otherwise, for text elements, it traces the word text with the text content.

Finally, the function calls itself and passes the current childNode as a parameter. This repeats the process at the next level in the document tree. The function stops when it encounters a text node or when the current node has no childNodes .

  1. Save the Flash file and test the movie. You should see an Output window similar to the one shown in Figure 4-7. You can find the completed file in your resources saved under the name r ecursive.fla .

    image from book
    Figure 4-7: Displaying the contents of an XML document using a recursive function

Using a recursive function allows you to process the contents of the document tree without understanding the structure. It can also be a more efficient way to write code that processes the document tree.

So far, weve worked with child nodes, but its useful to know that you can use ActionScript to find sibling nodes. These are nodes that share the same parent node.

image from book
 

Locating Siblings

Flash provides two properties for dealing with siblings: nextSibling and previousSibling . These properties allow you to locate elements that share the same parent as the current node. You can refer to the previous and next siblings of the current node using

 myXMLNode.previousSibling; myXMLNode.nextSibling; 

If there is no previous or next sibling, the property will return undefined , so you cant find the previousSibling of the first child node or the nextSibling of the last child node. As both of these properties are read-only, you cant use them to move nodes within the document tree.

The following example shows the processXML function modified to return the next and previous siblings of the second <address> element:

 function processXML(success:Boolean):Void {   if (success) {     var RootNode:XMLNode = this.firstChild;  var AddressNode:XMLNode = RootNode.childNodes[0].childNodes[1];   trace (  "  current node:  "  + AddressNode.nodeName);   trace (  "  previous:  "  + AddressNode.previousSibling.nodeName);   trace (  "  next:  "  + AddressNode.nextSibling.nodeName);  }   else {     trace ("Error loading XML file");   } } 

Ive included this example in the simpleprocess.fla resource file. If you test this file, you should see something similar to the Output window shown in Figure 4-8.

image from book
Figure 4-8: Displaying the previous and next sibling node names

As well as working with siblings, you can find the parent element of a node. This might be a quicker way to locate a node rather than writing a full path starting from the root node.

Locating parent nodes

You can refer to the parent of a current node using the parentNode property, as shown here:

 myXMLNode.parentNode; 

In this example, the processXML function displays the name of the parent of the second <address> element. Ive made the relevant lines bold.

 function processXML(success:Boolean):Void {   if (success) {     var RootNode:XMLNode = this.firstChild;  var AddressNode:XMLNode = RootNode.childNodes[0].childNodes[1];   trace (  "  parent node is  "  + AddressNode.parentNode.nodeName);  }   else {     trace ("Error loading XML file");   } } 

You can see the example in the simpleprocess.fla resource file. If you test the file, you should see an Output window similar to that displayed in Figure 4-9.

image from book
Figure 4-9: Displaying the parent node name

Note that the parentNode property is read-only, so you cant use it to change the structure of a document tree.

So far in this chapter, weve looked at how to extract information from both element and text nodes within an XML document. In the next section, Ill explain how you can work with attributes.

Extracting information from attributes

You refer to attributes differently compared with elements and text. Attributes arent children of element. Rather, they are a collection, or array, within an element. Unlike the childNodes collection, the attributes collection is an associative array. This means that you cant use a position number. You have to refer to each attribute using its name.

The following lines show you how you can refer to the value of an attribute using its name. The two examples are equivalent. The first uses dot notation while the second uses associative array notation.

 myXML.firstChild.attributes.attName; myXML.firstChild.attributes["attName"]; 

In both examples, Im finding the attribute called attName within the first child of the XML object called myXML .

In this XML fragment,

 <?xml version="1.0" encoding="UTF-8"?> <phoneBook>   <contact id="1">     <name>Sas Jacobs</name>     <address>123 Some Street, Some City, Some Country</address>     <phone>123 456</phone>   </contact> </phoneBook> 

I could display the value of the id attribute of the <contact> element using the following code lines. Both the second and third lines are equivalent.

 var RootNode:XMLNode = myXML.firstChild; trace(RootNode.firsChild.attributes.id); trace(RootNode.firstChild.attributes["id"]); 

Its easy to refer to attributes when you know their names. However, there may be occasions when you dont know their names. In those cases, it can be useful to loop through the attributes collection, as shown here:

 for (var theAtt:String in myXMLNode.attributes) {   //reference the attribute name using theAtt   //reference the value using myXMLNode.attributes[theAtt]) } 

This code is equivalent to saying for each attribute in the attributes collection .

The next example shows the processXML function modified to show the attributes within the first <contact> element of the address.xml file. The function displays the name and value of each attribute. Unfortunately, the element only has one attribute so the loop repeats only once.

 function processXML(success:Boolean):Void {   if (success) {     var RootNode:XMLNode = this.firstChild;  var ContactNode:XMLNode = RootNode.childNodes[0];   for (var theAtt:String in ContactNode.attributes) {   trace(theAtt +  "  =  "  + ContactNode.attributes[theAtt]);   }  }   else {     trace ("Error loading file");   } } 

In this example, we create a new XMLNode variable called ContactNode to refer to the first <contact> element. We use a for loop to move through the collection of attributes. Because Im working with an associative array, I have to refer to the value of the attribute using ContactNode.attributes[theAtt]) .

You can see this example in the simpleprocess.fla resource file. Uncomment the relevant lines and test the movie. You should see an Output window similar to the one shown in Figure 4-10.

image from book
Figure 4-10: Looping through the attributes collection

Youve learned a lot about loading external documents and extracting their values within Flash. We covered the various properties that you could use to move through the document tree. I showed you how to find the name of a node and the value of text within a node. The theory weve covered so far will make more sense when you work through an exercise.



Foundation XML for Flash
Foundation XML for Flash
ISBN: 1590595432
EAN: 2147483647
Year: 2003
Pages: 93
Authors: Sas Jacobs

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net