10.6. XML processing with E4X

Caution. E4X and ActionScript are now deprecated for all practical purposes, so you should consider other tools for processing XML such as the use of JavaScript described in the previous section JavaScript.

As more and more Javascript programs are expected to process XML data, it has been thought useful to provide a simple notation for handling XML data directly within Javascript. ECMAScript for XML (E4X)[66] has been recently standardized to support a natural use of XML data within Javascript programs. Its main innovation is the addition of an XML data type and the possibility of using XML literals that can be accessed directly using the usual dot notation used for accessing properties of Javascript objects. It is possible to access the content of an XML node but also to change its value or to add or remove new XML nodes.

For example, it is possible to declare a new wine element directly in a Javascript program using the following notation, which is the same as the usual notation for all XML data:

var wine:XML = <wine name="Château La Piquette" format="1l">
				<properties>
					<color>red</color>
				</properties>
				<comment>Should be thrown away</comment>
				<year>2007</year>
			</wine>;

wine is then a Javascript variable whose value can be retrieved or changed using the element name. For example, wine.properties.color will return "red". If there are more than one element of the same name, a specific one can be selected by putting its ordinal number (in document order) within square brackets after the name. To change an element or add a new one, it is only a matter of using its name as the target of an assignment. Attributes can be accessed by prefixing their names with an @. The .. operator is the equivalent of the XPath // operator.

Here are a few examples of E4X expressions:

There is a limited XPath-like notation to access a list of XML nodes over which it is possible to iterate. For example, for each (var w in wine.@*) will loop over all attributes of a wine.

            var elems:XMLList = wine.*;
            for(var i=0;i&lt;elems.length();i++){ 
                  ... elems[i]...
            }

will access all wine elements in turn. XMLList is a predefined type defining a list of XML nodes and providing specific methods. In order to simplify programming (in most cases), the E4X specification points out that it deliberately blurs the distinction between an XMLNode and an XMLList containing a single XMLNode. This design choice is also made in XSL.

It is also possible to select elements and attributes from a list with a boolean expression evaluated on each element of the list. Figure 10.3 gives a few examples of E4X expressions for accessing information in the cellar book.

Figure 10.3. E4X expression examples applied to Example D.1

These expressions are the same as the ones used in Example 4.1. cellarBook is a Javascript variable in which the XML document has been stored. It corresponds to the root node (/cellar-book) of the XML document.

  1 cellarBook.owner                                 (1)
    cellarBook.cellar.wine.(quantity<=2)             (2)
    cellarBook.cellar.wine[0]                        (3)
    cellarBook.descendants("*").(elements("postal-code").length()>0)     (4)
  5 cellarBook.owner.street                          (5)
    cellarBook..wine.@code                           (6)
    var catNS:Namespace                              (7)
       = new Namespace("cat","http://www.iro.umontreal.ca/lapalme/wine-catalog");
    cellarBook.addNamespace(catNS);
 10 cellarBook..catNS::wine.@code
    
    var wines:XMLList = cellarBook..wine;            (8)
    wines[wines.length()-1].@code
    
 15 cellarBook.cellar.wine[0].comment.catNS::bold    (9)
    
    var sum:Number=0;                                (10)
    for each (var q in cellarBook.cellar.wine.quantity)
    	sum += Number(q);
 20 sum
    
    var res:String="";                               (11)
    for each (var w in cellarBook..wine)
    	res+=w.quantity+":"+cellarBook..catNS::wine.(@code==w.@code).@name+"\n";
 25 res
    
    cellarBook..catNS::wine.(catNS::origin.catNS::country=="France"      (12)
                             && catNS::price < 20)
                    

1

The owner element of the cellar. Result: node on line 103.

2

The wines for which we have 2 bottles or less. The nodes returned are the wine elements that are filtered with a boolean expression (predicate) in parentheses. The predicate uses quantity, an internal element evaluated in the current context of the path specified. Result: nodes on lines 131 and 136.

3

The first wine of the cellar. Result: node on line 120.

4

The elements which contain a postal-code element. This is achieved by keeping only descendants for which the list of all postal-code elements is not empty. Result: nodes at lines 103 and 113.

5

The street of the cellar's owner. Result: "1234 rue des Châteaux".

6

The value of the code attribute for all wines in the cellar. Note the use of .. to find all descendants of a node, analogous to // in XPath. Result: "C00043125", "C00312363", "C10263859", "C00929026".

7

The value of the code attribute for all wines in the catalog. Note the use of the namespace prefix that must be defined and then added to the XML object. It is used by prefixing the element name with ::. Result: "C00043125", "C00042101", "C10263859", "C00312363", "C00929026".

8

The code of the last wine of the cellar,obtained by getting the list of wines and returning the one having an index that is one less than the length of the list. Result: "C00929026".

9

The cat:bold element (note again the use of the namespace prefix) within the comment of the first wine of the cellar. Result: "Guy Lapalme, Montréal" (expanded from the entity &GL;).

10

Total number of bottles in the cellar obtained by iterating over the values of all quantity elements of the wines in the cellar. As each XML value is a string, it must first be converted to a number for the sum. Otherwise, + would concatenate the strings. Result: 14.

11

Sequence of 4 strings, each giving the number of bottles of each wine in the cellar, followed by a colon and the name of the corresponding wine. Result: 2:Domaine de l'Île Margaux, 5:Mumm Cordon Rouge, 6:Château Montguéret, 1:Prado Rey Roble.

12

Sequence of French wines in the catalog costing less than 20 dollars. Result: wines that start on lines 24 and 42.


10.6.1. DOM parsing using E4X

Parsing a string to get an XML value with E4X is achieved by casting the string to XML using the XML function. Because the content of a file can be read in a string, we can use this function on the resulting string. Example 10.32 presents an ActionScript file that provides a class with methods for converting a string containing an XML source into a string with the compact notation we have seen in previous chapters. It is meant to be called from a Flash program XMLProcessing.fla not described in this document but available on the companion Web site. It that allows the user to select a given file and to display the output of the compacting process in a scrollable window. This application can also transform the XML elements into sprites that can be zoomed on when they are clicked.

Example 10.32. [DOMCompact.as] Text compaction of the cellar book (Example 2.2) with E4X using the DOM model

Compare this program with Example 8.1.

  1 package{
        public class DOMCompact{                                         (1)
            var doc:XML;
            
  5         function DOMCompact(s:String){                               (2)
                doc = XML(s);
            }
            
            public function toString():String {                          (3)
 10             return compact(doc);
            }
            
            var blanks:String = "     ";                                 (4)
            private function spaces(n:int){
 15             while(blanks.length<n)blanks+="     ";
                return blanks.substr(0,n);
            }
                    
            private function compact(node:XML,indent:String=""):String { (5)
 20             var type:String = node.nodeKind();
                switch (type) {
                    case "element":                                      (6)
                        var out:String = node.localName()+"[";
                        indent += spaces(node.localName().length+1);
 25                     var attributes:XMLList = node.@*;                (7)
                        var first:Boolean = true;
                        for (var i:int=0;i<attributes.length();i++){
                            if(i>0)out+="\n"+indent;
                            out+="@"+attributes[i].name()+"["+attributes[i]+"]";
 30                         first=false;
                        }
                        for each (var child:XML in node.children()){     (8)
                            if(!first) out+="\n"+indent;
                            out+=compact(child,indent);
 35                         first=false;
                        }
                        return out+"]";
                    case "text":                                         (9)
                        return node[0].toString().replace(/ *\n */g,' ');
 40             }
                return "Should never happen!!!";
            }
        }
    }

1

Defines the class with one instance variable, the document of type XML.

2

Constructor of the class that creates an XML document from a string. Parsing is done through the XML function.

3

Produces a compact string by calling the compact function.

4

Defines as instance variable a string from which the spaces function will produce a certain number of spaces with the substring function. If blanks is not long enough, more spaces are added to the string.

5

Main function for producing a compact version of the XML document, starting with a given node and a certain indent given as a string of spaces. By default, the indent is empty.

6

If the node is an element, we initialize the output string with the name of the element followed with an opening bracket. The length of this string is used as indent to be added to the current one for the subsequent lines.

7

All attributes are output with their names and values within brackets.

8

Each child node is output with a recursive call to compact.

9

A text node is the content of the node but normalized by replacing newlines and their surrounding whitespaces with a single space.


10.6.2. Creating an XML document using E4X

Parsing a compact form in order to create an XML document follows an organization similar to the one we have shown in Chapter 9 and Section 10.1.3. We first define a class for a specialized tokenizer which returns only meaningful units that will be used to create the XML elements and attributes of the resulting XML document.

Example 10.33. [CompactTokenizer.as] E4X specialized string scanner that returns tokens of compact form

Compare this program with Example 9.1.

  1 package {
        class CompactTokenizer {                                         (1)
            private var tokens:Array = [];
            private var tok:String = "";
  5         
            function CompactTokenizer(source:String):void{               (2)
                tokens=source.split(/(\[|\]|\n|@|[^\[\]\n@]+)/).filter(notEmpty);
            }
            
 10         private function notEmpty(element:*,index:int,a:Array):Boolean{   (3)
                return element.length>0 && !element.match(/^\n? *$/);
            }
            
            public function nextToken():String{                          (4)
 15             return tok = tokens.length==0 ? null : tokens.shift();
            }
            
            public function token():String {                             (5)
                return tok;
 20         }
            
            public function skip(sym:String){                            (6)
                if(token()==sym)
                    return nextToken();
 25             else 
                    throw new ArgumentError("skip:"+sym+" expected but "+
                                            token()+ " found");
            }
                    
 30     }
    }
    

1

Defines a class with two private instance variables: tokens, an array of all tokens whose elements will be returned one by one; tok, the current token.

2

Builds the array of all tokens by splitting according to important elements of the inputs. Here we are interested in the content matched by the regular expressions, so we capture the content matched. However as the input is matched by the regular expression, empty strings between each match are produced. These empty matches and those containing only spaces are removed by the call to filter.

3

Defines a function to match empty strings and those containing only spaces.

4

Retrieves the first element of the tokens array, removes it and saves it as the current token, which is then returned as the value of the call.

5

Returns the current token.

6

Checks that the current token corresponds to the parameter. If this is the case, returns the next token, otherwise raises an exception. In principle, this exception should never be raised but this function might be useful (and it was!) for debugging purposes.


The creation of a new XML document is done by a recursive call to the expand function that creates each element of the document in turn.

Example 10.34. [DOMExpand.as] E4X compact form parsing in order to create an XML document

A sample input for this program is Example 5.8 to yield Example 2.2. Compare this program with Example 9.2.

  1 package{
        public class DOMExpand {                                         (1)
            var doc:XML;
            
  5         function DOMExpand(s:String){                                (2)
                var st:CompactTokenizer = new CompactTokenizer(s);
                var rootName:String;
                while (st.nextToken()!="[") rootName=st.token();
                doc = expand(st,rootName);                               (3)
 10         }
            
            public function toString():String {                          (4)
                return doc.toXMLString();
            }
 15         
            function expand(st:CompactTokenizer,elementName:String):XML{ (5)
                var elem:XML= <{elementName}/>;
                st.skip("[");
                while(st.token()=="@"){
 20                 var attName:String=st.skip("@");                     (6)
                    st.nextToken();
                    elem.@[attName]=st.skip("[");
                    st.nextToken();
                    st.skip("]");
 25             }
                while (st.token()!="]"){                                 (7)
                    var s:String = st.token().replace(/^\s*(.*?)\s*$/,"$1");
                    st.nextToken();
                    if(st.token()=="[")
 30                     elem.appendChild(expand(st,s));                  (8)
                    else
                        elem.appendChild(s);                             (9)
                }
                st.skip("]");
 35             return elem;
            }
        }
    }
    

1

Defines a class with a single instance variable: the XML document to create.

2

The constructor instantiates a compact tokenizer and then finds the first string followed by an opening bracket which will be the name of the root.

3

Creates a new XML structure by calling expand on the name of the root element.

4

The serialization of the XML structure is the string representation of the doc instance variable.

5

Creates a new element whose name is the second parameter. We use the st string tokenizer. We first create an empty element using an XML literal. As the name has to be evaluated, it is put within braces.

6

Each attribute is created with a single assignment statement making use of the attribute name within brackets. This name is the result of the call to skip("[") which returns the token after the opening bracket.

7

Loops on each child. Gets the string that follows, which is then trimmed using a regular expression.

8

If the current token is an opening bracket, then we create a new element by a recursive call to expand which is added as a child of the current node.

9

If the current token is not an opening bracket, then its value is used to create a text node as a child of the current node.