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:
wine.comment="I found it excellent"
changes the value of the
comment;
wine.origin.country="USA"
adds a new element origin
containing the country
element;
wine.@name
returns the name of the wine ("Château La
Piquette"
);
wine..color
returns "red"
.
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<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.ownercellarBook.cellar.wine.(quantity<=2)
cellarBook.cellar.wine[0]
cellarBook.descendants("*").(elements("postal-code").length()>0)
5 cellarBook.owner.street
cellarBook..wine.@code
var catNS:Namespace
= new Namespace("cat","http://www.iro.umontreal.ca/lapalme/wine-catalog"); cellarBook.addNamespace(catNS); 10 cellarBook..catNS::wine.@code var wines:XMLList = cellarBook..wine;
wines[wines.length()-1].@code 15 cellarBook.cellar.wine[0].comment.catNS::bold
var sum:Number=0;
for each (var q in cellarBook.cellar.wine.quantity) sum += Number(q); 20 sum var res:String="";
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"
&& catNS::price < 20)
|
The |
|
The wines for which we have 2 bottles or less. The nodes returned are the
|
|
The first wine of the cellar. Result: node on line 120. |
|
The elements which contain a |
|
The street of the cellar's owner. Result: |
|
The value of the |
|
The value of the |
|
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: |
|
The |
|
Total number of bottles in the cellar obtained by iterating over the
values of all |
|
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: |
|
Sequence of French wines in the catalog costing less than 20 dollars. Result: wines that start on lines 24 and 42. |
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{var doc:XML; 5 function DOMCompact(s:String){
doc = XML(s); } public function toString():String {
10 return compact(doc); } var blanks:String = " ";
private function spaces(n:int){ 15 while(blanks.length<n)blanks+=" "; return blanks.substr(0,n); } private function compact(node:XML,indent:String=""):String {
20 var type:String = node.nodeKind(); switch (type) { case "element":
var out:String = node.localName()+"["; indent += spaces(node.localName().length+1); 25 var attributes:XMLList = node.@*;
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()){
if(!first) out+="\n"+indent; out+=compact(child,indent); 35 first=false; } return out+"]"; case "text":
return node[0].toString().replace(/ *\n */g,' '); 40 } return "Should never happen!!!"; } } }
|
Defines the class with one instance variable, the document of type
|
|
Constructor of the class that creates an XML document from a
string. Parsing is done through the |
|
Produces a compact string by calling the
|
|
Defines as instance variable a string from which the
|
|
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. |
|
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. |
|
All attributes are output with their names and values within brackets. |
|
Each child node is output with a recursive call to
|
|
A text node is the content of the node but normalized by replacing newlines and their surrounding whitespaces with a single space. |
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 {private var tokens:Array = []; private var tok:String = ""; 5 function CompactTokenizer(source:String):void{
tokens=source.split(/(\[|\]|\n|@|[^\[\]\n@]+)/).filter(notEmpty); } 10 private function notEmpty(element:*,index:int,a:Array):Boolean{
return element.length>0 && !element.match(/^\n? *$/); } public function nextToken():String{
15 return tok = tokens.length==0 ? null : tokens.shift(); } public function token():String {
return tok; 20 } public function skip(sym:String){
if(token()==sym) return nextToken(); 25 else throw new ArgumentError("skip:"+sym+" expected but "+ token()+ " found"); } 30 } }
|
Defines a class with two private instance variables:
|
|
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 |
|
Defines a function to match empty strings and those containing only spaces. |
|
Retrieves the first element of the |
|
Returns the current token. |
|
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 {var doc:XML; 5 function DOMExpand(s:String){
var st:CompactTokenizer = new CompactTokenizer(s); var rootName:String; while (st.nextToken()!="[") rootName=st.token(); doc = expand(st,rootName);
10 } public function toString():String {
return doc.toXMLString(); } 15 function expand(st:CompactTokenizer,elementName:String):XML{
var elem:XML= <{elementName}/>; st.skip("["); while(st.token()=="@"){ 20 var attName:String=st.skip("@");
st.nextToken(); elem.@[attName]=st.skip("["); st.nextToken(); st.skip("]"); 25 } while (st.token()!="]"){
var s:String = st.token().replace(/^\s*(.*?)\s*$/,"$1"); st.nextToken(); if(st.token()=="[") 30 elem.appendChild(expand(st,s));
else elem.appendChild(s);
} st.skip("]"); 35 return elem; } } }
|
Defines a class with a single instance variable: the XML document to create. |
|
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. |
|
Creates a new XML structure by calling |
|
The serialization of the XML structure is the string
representation of the |
|
Creates a new element whose name is the second parameter. We use the
|
|
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
|
|
Loops on each child. Gets the string that follows, which is then trimmed using a regular expression. |
|
If the current token is an opening bracket, then we create a new element
by a recursive call to |
|
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. |