Chapter 19

ECMAScript for XML

WHAT’S IN THIS CHAPTER?

  • Additional types introduced by E4X
  • Using E4X for XML manipulation
  • Syntax changes

In 2002, a group of companies led by BEA Systems proposed an extension to ECMAScript to add native XML support to the language. In June 2004, ECMAScript for XML (E4X) was released as ECMA-357, which was revised in December 2005. E4X is not its own language; rather, it is an optional extension to the ECMAScript language. As such, E4X introduces new syntax for dealing with XML, as well as for XML-specific objects.

Though browser adoption has been slow, Firefox versions 1.5 and later support almost the entire E4X standard. This chapter focuses on the Firefox implementation.

E4X TYPES

As an extension to ECMAScript, E4X introduces the following new global types:

  • XML — Any single part of an XML structure
  • XMLList — A collection of XML objects
  • Namespace — Mapping between a namespace prefix and a namespace URI
  • QName — A qualified name made up of a local name and a namespace URI

Using these four types, E4X is capable of representing all parts of an XML document by mapping each type, specifically XML and XMLList, to multiple DOM types.

The XML Type

The XML type is the most important new type introduced in E4X, because it can represent any single part of an XML structure. An instance of XML can represent an element, an attribute, a comment, a processing instruction, or a text node. The XML type inherits from the Object type, so it inherits all of the default properties and methods of all objects. There are a few ways to create a new XML object, the first of which is to call the constructor like this:

var x = new XML();

This line creates an empty XML object that can be filled with data. You can also pass in an XML string to the constructor, as shown in this example:

var x = new XML("<employee position="Software Engineer"><name>Nicholas " +    
                "Zakas</name></employee>");

The XML string passed into the constructor is parsed into an object hierarchy of XML objects. Additionally, you can pass in a DOM document or node to the constructor as follows and have its data represented in E4X:

var x = new XML(xmldom);

Even though these methods of construction are useful, the most powerful and interesting method is direct assignment of XML data into a variable via an XML literal. XML literals are nothing more than XML code embedded into JavaScript code. Here’s an example:

image
var employee = <employee position="Software Engineer">
                    <name>Nicholas C. Zakas</name>
               </employee>;

XMLTypeExample01.htm

Here, an XML data structure is assigned directly to a variable. This augmented syntax creates an XML object and assigns it to the employee variable.

image

The Firefox implementation of E4X doesn’t support the parsing of XML prologs. If <?xml version="1.0"?> is present, either in text that is passed to the XML constructor or as part of the XML literal, a syntax error occurs.

The toXMLString() method returns an XML string representing the object and its children. The toString() method, on the other hand, behaves differently based on the contents of the XML object. If the contents are simple (plain text), then the text is returned; otherwise toString() acts the same as toXMLString(). Consider the following example:

var data = <name>Nicholas C. Zakas</name>;
alert(data.toString());     //"Nicholas C. Zakas"
alert(data.toXMLString());  //"<name>Nicholas C. Zakas</name>"

Between these two methods, most XML serialization needs can be met.

The XMLList Type

The XMLList type represents ordered collections of XML objects. The DOM equivalent of XMLList is NodeList, although the differences between XML and XMLList are intentionally small as compared to the differences between Node and NodeList. To create a new XMLList object explicitly, you can use the following XMLList constructor:

var list = new XMLList();

As with the XML constructor, you can pass in an XML string to be parsed. The string need not contain a single document element, as the following code illustrates:

var list = new XMLList("<item/><item/>");

XMLListTypeExample01.htm

Here the list variable is filled with an XMLList containing two XML objects, one for each <item/> element.

An XMLList object also can be created by combining two or more XML objects via the plus (+) operator. This operator has been overloaded in E4X to perform XMLList creation, as in this example:

var list = <item/> + <item/>;

This example combines two XML literals into an XMLList by using the plus operator. The same can be accomplished by using the special <> and </> syntax and omitting the plus operator, as shown here:

var list = <><item/><item/></>;

Although it’s possible to create stand-alone XMLList objects, they are typically created as part of parsing a larger XML structure. Consider the following:

image
var employees = <employees>
    <employee position="Software Engineer">
        <name>Nicholas C. Zakas</name>
    </employee>
    <employee position="Salesperson">
        <name>Jim Smith</name>
    </employee>
</employees>;

XMLListTypeExample02.htm

This code defines an employees variable that becomes an XML object representing the <employees/> element. Since there are two child <employee/> elements, an XMLList object is created and stored in employees.employee. It’s then possible to access each element using bracket notation and its position like this:

var firstEmployee = employees.employee[0]; 
var secondEmployee = employees.employee[1];

Each XMLList object also has a length() method that returns the number of items it contains. For example:

alert(employees.employee.length());   //2

Note that length() is a method, not a property. This is intentionally different from arrays and NodeLists.

One interesting part of E4X is the intentional blurring of the XML and XMLList types. In fact, there is no discernible difference between an XML object and an XMLList containing a single XML object. To minimize these differences, each XML object also has a length() method and a property referenced by [0] (which returns the XML object itself).

The compatibilities between XML and XMLList allow for much easier E4X usage, because some methods may return either type.

The toString() and toXMLString() methods for an XMLList object return the same string value, which is a concatenated serialization of all XML objects it contains.

The Namespace Type

Namespaces are represented in E4X by Namespace objects. A Namespace object generally is used to map a namespace prefix to a namespace URI, although a prefix is not always necessary. You can create a Namespace object by using the Namespace constructor as follows:

var ns = new Namespace();

You can also initialize a Namespace object with either a URI or a prefix and URI, as shown here:

image
var ns = new Namespace("http://www.wrox.com/");    //no prefix namespace
var wrox = new Namespace("wrox", "http://www.wrox.com/");  //wrox namespace

NamespaceTypeExample01.htm

The information in a Namespace object can be retrieved using the prefix and uri properties like this:

alert(ns.uri);                //"http://www.wrox.com/"
alert(ns.prefix);             //undefined
alert(wrox.uri);              //"http://www.wrox.com/"
alert(wrox.prefix);           //"wrox"

NamespaceTypeExample01.htm

Whenever a prefix isn’t assigned as part of the Namespace object, its prefix property is set to undefined. To create a default namespace, you should set the prefix to an empty string.

If an XML literal contains a namespace or if an XML string containing namespace information is parsed via the XML constructor, a Namespace object is created automatically. You can then retrieve a reference to the Namespace object by using the namespace() method and specifying the prefix. Consider the following example:

image
var xml = <wrox:root xmlns:wrox="http://www.wrox.com/">
            <wrox:message>Hello World!</wrox:message>
          </wrox:root>;
                   
var wrox = xml.namespace("wrox");
alert(wrox.uri);
alert(wrox.prefix);

NamespaceTypeExample02.htm

In this example, an XML fragment containing a namespace is created as an XML literal. The Namespace object from the wrox namespace can be retrieved via namespace("wrox"), after which point you can access the uri and prefix properties. If the XML fragment has a default namespace, that can be retrieved by passing an empty string into the namespace() method.

The toString() method for a Namespace object always returns the namespace URI.

The QName Type

The QName type represents qualified names of XML objects, which are the combination of a namespace and a local name. You can create a new QName object manually using the QName constructor and passing in either a name or a Namespace object and a name, as shown here:

var wrox = new Namespace("wrox", "http://www.wrox.com/");
var wroxMessage = new QName(wrox, "message");    //represents "wrox:message"

QNameTypeExample01.htm

After the object is created, it has two properties that can be accessed: uri and localName. The uri property returns the URI of the namespace specified when the object is created (or an empty string if no namespace is specified), and the localName property returns the local name part of the qualified name, as the following example shows:

alert(wroxMessage.uri);        //"http://www.wrox.com/"
alert(wroxMessage.localName);  //"message"

QNameTypeExample01.htm

These properties are read-only and cause an error if you try to change their values. The QName object overrides the toString() object to return a string in the form uri::localName, such as “http://www.wrox.com/::message” in the previous example.

When parsing an XML structure, QName objects are created automatically for XML objects that represent elements or attributes. You can use the XML object’s name() method to return a reference to the QName object associated with the XML object, as in this example:

image
var xml = <wrox:root xmlns:wrox="http://www.wrox.com/">
            <wrox:message>Hello World!</wrox:message>
          </wrox:root>;
                   
var wroxRoot = xml.name();
alert(wroxRoot.uri);           //"http://www.wrox.com/"
alert(wroxRoot.localName);     //"root"

QNameTypeExample02.htm

A QName object is created for each element and attribute in an XML structure even when no namespace information is specified.

You can change the qualified name of an XML object by using the setName() method and passing in a new QName object, as shown here:

xml.setName(new QName("newroot"));

This method typically is used when changing the tag name of an element or an attribute name that is part of a namespace. If the name isn’t part of a namespace, you can change the local name by simply using the setLocalName() method like this:

xml.setLocalName("newtagname");

GENERAL USAGE

When an XML object, elements, attributes, and text are assembled into an object hierarchy, you can then navigate the structure by using dot notation along with attribute and tag names. Each child element is represented as a property of its parent, with the property name being equal to the child element’s local name. If that child element contains only text, then it is returned whenever the property is accessed, as in the following example:

var employee = <employee position="Software Engineer">
                    <name>Nicholas C. Zakas</name>
               </employee>;
alert(employee.name);  //"Nicholas C. Zakas"

The <name/> element in this code contains only text. That text is retrieved via employee.name, which navigates to the <name/> element and returns it. Since the toString() method is called implicitly when passed into an alert, the text contained within <name/> is displayed. This ability makes it trivial to access text data contained within an XML document. If there’s more than one element with the same tag name, an XMLList is returned. Consider the following example:

var employees = <employees>
    <employee position="Software Engineer">
        <name>Nicholas C. Zakas</name>
    </employee>
    <employee position="Salesperson">
        <name>Jim Smith</name>
    </employee>
</employees>;
                   
alert(employees.employee[0].name);    //"Nicholas C. Zakas"
alert(employees.employee[1].name);    //"Jim Smith"

This example accesses each <employee/> element and outputs the value of their <name/> elements. If you aren’t sure of a child element’s local name or if you want to retrieve all child elements regardless of their name, you can use an asterisk (*), as shown here:

image
var allChildren = employees.*;      //return all children regardless of local name
alert(employees.*[0].name);         //"Nicholas C. Zakas"

UsageExample01.htm

As with other properties, the asterisk may return either a single XML object or an XMLList object, depending on the XML structure.

The child() method behaves in the exact same way as property access. Any property name or index can be passed into the child() method and it will return the same value. Consider this example:

var firstChild = employees.child(0);           //same as employees.*[0]
var employeeList = employees.child("employee");  //same as employees.employee
var allChildren = employees.child("*");          //same as employees.*

For added convenience, a children() method is provided that always returns all child elements. Here’s an example:

var allChildren = employees.children(); //same as employees.*

There is also an elements() method, which behaves similar to child() with the exception that it will return only XML objects that represent elements. For example:

var employeeList = employees.elements("employee");  //same as employees.employee
var allChildren = employees.elements("*");          //same as employees.*

These methods provide a more familiar syntax for JavaScript developers to access XML data.

Child elements can be removed by using the delete operator, as shown here:

delete employees.employee[0];
alert(employees.employee.length());   //1

This is one of the major advantages of treating child nodes as properties.

Accessing Attributes

Attributes can also be accessed using dot notation, although the syntax is slightly augmented. To differentiate an attribute name from a child-element tag name, you must prepend an “at” character (@) before the name. This syntax is borrowed from XPath, which also uses @ to differentiate between attributes and character names. The result is a syntax that looks a little strange, as you can see in this example:

image
var employees = <employees>
    <employee position="Software Engineer">
        <name>Nicholas C. Zakas</name>
    </employee>
    <employee position="Salesperson">
        <name>Jim Smith</name>
    </employee>
</employees>;
                   
alert(employees.employee[0].@position);  //"Software Engineer"

AttributesExample01.htm

As with elements, each attribute is represented as a property that can be accessed using this shorthand notation. An XML object representing the attribute is returned and its toString() method always returns the attribute value. To get the attribute name, use the name() method of the object.

You can also use the child() method to access an attribute by passing in the name of the attribute prefixed with @, as shown here:

alert(employees.employee[0].child("@position"));    //"Software Engineer"

AttributesExample01.htm

Since any XML object property name can be used with child(), the @ character is necessary to distinguish between tag names and attribute names.

It’s possible to access only attributes by using the attribute() method and passing in the name of the attribute. Unlike child(), there is no need to prefix the attribute name with an @ character. Here’s an example:

alert(employees.employee[0].attribute("position"));    //"Software Engineer"

AttributesExample01.htm

These three ways of accessing properties are available on both XML and XMLList types. When used on an XML object, an XML object representing the attribute is returned; when used on an XMLList object, an XMLList is returned containing attribute XML objects for all elements in the list. In the previous example, for instance, employees.employee.@position will return an XMLList containing two objects — one for the position attribute on the first <employee/> element and one for the second.

To retrieve all attributes in an XML or XMLList object, you can use the attributes() method. This method returns an XMLList of objects representing all attributes. This is the same as using the @* pattern, as illustrated in this example:

//both lines get all attributes
var atts1 = employees.employee[0].@*;
var atts2 = employees.employee[0].attributes();

Changing attribute values in E4X is as simple as changing a property value. Simply assign a new value to the property like this:

employees.employee[0].@position = "Author";     //change position attribute

The change is then reflected internally, so when you serialize the XML object, the attribute value is updated. This same technique can be used to add new attributes, as shown in the following example:

employees.employee[0].@experience = "8 years";     //add experience attribute
employees.employee[0].@manager = "Jim Smith";      //add manager attribute

Since attributes act like any other ECMAScript properties, you can also remove attributes by using the delete operator as follows:

delete employees.employee[0].@position;      //delete position attribute

Property access for attributes allows for very simple interaction with the underlying XML structure.

Other Node Types

E4X is capable of representing all parts of an XML document, including comments and processing instructions. By default, E4X will not parse comments or processing instructions, so they won’t show up in the object hierarchy. To force the parser to recognize them, you must set the following two properties on the XML constructor:

XML.ignoreComments = false;
XML.ignoreProcessingInstructions = false;

With these flags set, E4X parses comments and processing instructions into the XML structure.

Since the XML type represents all of the node types, it’s necessary to have a way to tell them apart. The nodeKind() method indicates what type of node an XML object represents and returns "text", "element", "comment", "processing-instruction", or "attribute". Consider the following XML object:

image
var employees = <employees>
    <?Dont forget the donuts?>
    <employee position="Software Engineer">
        <name>Nicholas C. Zakas</name>
    </employee>
    <!-- just added -->
    <employee position="Salesperson">
        <name>Jim Smith</name>
    </employee>
</employees>;

Given this XML, the following table shows what nodeKind() returns, depending on which node is in scope.

STATEMENT RETURNS
employees.nodeKind() "element"
employees.*[0].nodeKind() "processing-instruction"
employees.employee[0][email protected]() "attribute"
employees.employee[0].nodeKind() "element"
employees.*[2].nodeKind() "comment"
employees.employee[0].name.*[0].nodeKind() "text"

The nodeKind() method can’t be called on an XMLList that has more than one XML object in it; doing so throws an error.

It’s possible to retrieve just the nodes of a particular type by using one of the following methods:

  • attributes() — Returns all the attributes of an XML object.
  • comments() — Returns all the child comments of an XML object.
  • elements(tagName) — Returns all the child elements of an XML object. You can filter the results by providing the tagName of the elements you want to return.
  • processingInstructions(name) — Returns all the child processing instructions of an XML object. You can filter the results by providing the name of the processing instructions to return.
  • text() — Returns all text node children of an XML object.

Each of these methods returns an XMLList containing the appropriate XML objects.

You can determine if an XML object contains just text or more complex content by using the hasSimpleContent() and hasComplexContent() methods. The former returns true if there are only text nodes as children, whereas the latter returns true if there are any child nodes that aren’t text nodes. Here’s an example:

alert(employees.employee[0].hasComplexContent());   //true
alert(employees.employee[0].hasSimpleContent());    //false
alert(employees.employee[0].name.hasComplexContent());   //false
alert(employees.employee[0].name.hasSimpleContent());    //true

These methods, used in conjunction with the others, can aid in the querying of an XML structure for relevant data.

Querying

In truth, E4X provides a querying syntax that is similar in many ways to XPath. The simple act of retrieving element or attribute values is a basic type of query. The XML objects that represent various parts of an XML structure aren’t created until a query is made. In effect, all properties of XML or XMLList objects are simply parts of a query. This means referencing a property that doesn’t represent a part of the XML structure still returns an XMLList, it just has nothing in it. For example, if the following code is run against the previous XML example, nothing will be returned:

image
var cats = employees.cat;
alert(cats.length());     //0 

QueryingExample01.htm

This query looks for <cat/> elements under <employees/>. The first line returns an XMLList with nothing in it. Such behavior allows for querying without worrying about exceptions occurring.

Most of the previous example dealt with direct children of nodes using dot notation. You can expand the query to all descendants by using two dots, as shown here:

var allDescendants = employees..*;     //get all descendants of <employees/>

In this code, all descendants of the <employees/> element are returned. The results are limited to elements, text, comments, and processing instructions, with the latter two included based only on the flags specified on the XML constructor (discussed in the preceding section); attributes will not be included. To retrieve only elements of a specific tag name, replace the asterisk with the actual tag name as follows:

var allNames = employees..name;       //get all <name/> descendants of <employees/>

The same queries can be executed using the descendants() method. When used without any arguments, this method returns all descendants (which is the same as using ..*), or you can also supply a name as an argument to limit the results. Here are examples of both:

var allDescendants = employees.descendants();     //all descendants
var allNames = employees.descendants("name");     //all <name/> descendants

It is possible to retrieve all attributes of all descendants using either of the following:

var allAttributes = employees..@*;             //get all attributes on descendants
var allAttributes2 = employees.descendants("@*");  //same

As with element descendants, you can limit the results by supplying a full attribute name instead of an asterisk. For example:

var allAttributes = employees..@position;           //get all position attributes 
var allAttributes2 = employees.descendants("@position");  //same

In addition to accessing descendants, you can specify a condition that must be met. For instance, to return all <employee/> elements where the position attribute is "salesperson", you can use the following query:

var salespeople = employees.employee.(@position == "Salesperson");

This syntax can also be used to change parts of the XML structure. For example, you can change the position attribute of the first salesperson to "Senior Salesperson" with just the following line:

employees.employee.(@position == "Salesperson")[0].@position= "Senior Salesperson";

Note that the expression in parentheses returns an XMLList containing the results, so the brackets return the first item upon which the @position property is written.

You can travel back up the XML structure by using the parent() method, which returns the XML object representing the XML object’s parent. If called on an XMLList, the parent() method returns the common parent of all objects in the list. Consider this example:

var employees2 = employees.employee.parent();

Here, the employees2 variable contains the same value as the employees variable. The parent() method is most useful when dealing with an XML object of unknown origin.

XML Construction and Manipulation

There are numerous options for getting XML data into an XML object. As discussed earlier, you can pass in an XML string to the XML constructor or use an XML literal. XML literals can be made more useful by embedding JavaScript variables within curly braces: {}. A variable can be used anywhere within an XML literal, such as in this example:

image
var tagName = "color";
var color = "red";
var xml = <{tagName}>{color}</{tagName}>;
                   
alert(xml.toXMLString());    //"<color>red</color>

XMLConstructionExample01.htm

In this code, both the tag name and the text value of the XML literal are specified using variables inserted with curly braces. This capability makes it easy to build up XML structures without string concatenation.

E4X also makes it easy to build up an entire XML structure using standard JavaScript syntax. As mentioned previously, most operations are queries and won’t throw an error even if the elements or attributes don’t exist. Taking that one step further, if you assign a value to a nonexistent element or attribute, E4X will create the underlying structure first and then do the assignment. Here’s an example:

var employees = <employees/>;
employees.employee.name = "Nicholas C. Zakas";
employees.employee.@position = "Software Engineer";

XMLConstructionExample02.htm

This example begins with an <employees/> element and then builds on it. The second line creates an <employee/> element and a <name/> element inside it, assigning a text value. The next line adds the position attribute and assigns a value to it. In the end, the structure is as follows:

<employees>
    <employee position="Software Engineer">
        <name>Nicholas C. Zakas</name>
    </employee>
</employees>

It’s then possible to add a second <employee/> element using the + operator, like this:

image
employees.employee += <employee position="Salesperson">
                          <name>Jim Smith</name>
                      </employee>;

XMLConstructionExample02.htm

This results in a final XML structure, as follows:

<employees>
    <employee position="Software Engineer">
        <name>Nicholas C. Zakas</name>
    </employee>
    <employee position="Salesperson">
        <name>Jim Smith</name>
    </employee>
</employees>

Aside from this basic XML construction syntax, the following DOM-like methods are also available:

  • appendChild(child) — Appends the given child to the end of the XMLList representing the node’s children.
  • copy() — Returns a duplicate of the XML object.
  • insertChildAfter(refNode, child) — Inserts child after refNode in the XMLList representing the node’s children.
  • insertChildBefore(refNode, child) — Inserts child before refNode in the XMLList representing the node’s children.
  • prependChild(child) — Inserts the given child at the beginning of the XMLList representing the node’s children.
  • replace(propertyName, value) — Replaces the property named propertyName, which may be an element or an attribute, with the given value.
  • setChildren(children) — Replaces all current children with children, which may be an XML object or an XMLList object.

These methods are incredibly useful and easy to use. The following code illustrates some of these methods:

image
var employees = <employees>
    <employee position="Software Engineer">
        <name>Nicholas C. Zakas</name>
    </employee>
    <employee position="Salesperson">
        <name>Jim Smith</name>
    </employee>
</employees>;
                   
employees.appendChild(<employee position="Vice President">
                     <name>Benjamin Anderson</name>
                 </employee>);
                   
employees.prependChild(<employee position="User Interface Designer">
                     <name>Michael Johnson</name>
                  </employee>);
                   
employees.insertChildBefore(employees.child(2), 
                            <employee position="Human Resources Manager">
                                <name>Margaret Jones</name>
                            </employee>);
                   
employees.setChildren(<employee position="President">
                         <name>Richard McMichael</name>
                      </employee> +
                      <employee position="Vice President">
                          <name>Rebecca Smith</name>
                      </employee>);

XMLConstructionExample03.htm

First, the code adds a vice president named Benjamin Anderson to the bottom of the list of employees. Second, a user interface designer named Michael Johnson is added to the top of the list of employees. Third, a human resources manager named Margaret Jones is added just before the employee in position 2, which at this point is Jim Smith (because Michael Johnson and Nicholas C. Zakas now come before him). Finally, all the children are replaced with the president, Richard McMichael, and the new vice president, Rebecca Smith. The resulting XML looks like this:

<employees>
    <employee position="President">
        <name>Richard McMichael</name>
    </employee>
    <employee position="Vice President">
        <name>Rebecca Smith</name>
    </employee>
</employees>

Using these techniques and methods, you can perform any DOM-style operation using E4X.

Parsing and Serialization Options

The way E4X parses and serializes data is controlled by several settings on the XML constructor. The following three settings are related to XML parsing:

  • ignoreComments — Indicates that the parser should ignore comments in the markup. This is set to true by default.
  • ignoreProcessingInstructions — Indicates that the parser should ignore processing instructions in the markup. This is set to true by default.
  • ignoreWhitespace — Indicates that the parser should ignore white space in between elements rather than create text nodes to represent it. This is set to true by default.

These three settings affect parsing of XML strings passed into the XML constructor, as well as XML literals.

Additionally, the following two settings are related to the serialization of XML data:

  • prettyIndent — Indicates the number of spaces used per indent when serializing XML. The default is 2.
  • prettyPrinting — Indicates that the XML should be output in a human-readable format, with each element on a new line and children indented. This is set to true by default.

These settings affect the output from toString() and toXMLString().

All five of the settings are stored in a settings object that can be retrieved using the settings() method of the XML constructor, as in this example:

image
var settings = XML.settings();
alert(settings.ignoreWhitespace);    //true
alert(settings.ignoreComments);      //true

ParsingAndSerializationExample01.htm

Multiple settings can be assigned at once by passing an object into the setSettings() method containing all five settings. This is useful when you want to change settings temporarily, as in the following example:

var settings = XML.settings();
XML.prettyIndent = 8;
XML.ignoreComments = false;
                   
//do some processing
                   
XML.setSettings(settings);  //reset to previous settings

You can always get an object containing the default settings by using the defaultSettings() method, so you can reset the settings at any time using the following line:

XML.setSettings(XML.defaultSettings());

Namespaces

E4X makes namespaces quite easy to use. As discussed previously, you can retrieve a Namespace object for a particular prefix using the namespace() method. You can also set the namespace for a given element by using the setNamespace() method and passing in a Namespace object. Here’s an example:

var messages = <messages>
    <message>Hello world!</message>
</messages>;
messages.setNamespace(new Namespace("wrox", "http://www.wrox.com/"));

When setNamespace() is called, the namespace gets applied to only the element on which it was called. Serializing the messages variable results in the following:

<wrox:messages xmlns:wrox="http://www.wrox.com/">
    <message>Hello world!</message>
</wrox:messages>

The <messages/> element gets prefixed with the wrox namespace because of the call to setNamespace(), whereas the <message/> element remains unchanged.

To simply add a namespace declaration without changing the element, use the addNamespace() method and pass in a Namespace object, as in this example:

messages.addNamespace(new Namespace("wrox", "http://www.wrox.com/"));

When this code is applied to the original messages XML, the following XML structure is created:

<messages xmlns:wrox="http://www.wrox.com/">
    <message>Hello world!</message>
</messages>

By calling removeNamespace() and passing in a Namespace object, you can remove the namespace declaration for the namespace with the given namespace prefix and URI; it is not necessary to pass in the exact Namespace object representing the namespace. Consider this example:

messages.removeNamespace(new Namespace("wrox", "http://www.wrox.com/"));

This code removes the wrox namespace. Note that qualified names referencing the prefix will not be affected.

There are two methods that return an array of the Namespace object related to a node. The first is namespaceDeclarations(), which returns an array of all namespaces that are declared on the given node. The second is inScopeNamespaces(), which returns an array of all namespaces that are in the scope of the given node, meaning they have been declared either on the node itself or on an ancestor node. Consider this example:

var messages = <messages xmlns:wrox="http://www.wrox.com/">
    <message>Hello world!</message>
</messages>;
                   
alert(messages.namespaceDeclarations());    //"http://www.wrox.com"
alert(messages.inScopeNamespaces());        //",http://www.wrox.com"
                   
alert(messages.message.namespaceDeclarations());    //""
alert(messages.message.inScopeNamespaces());        //",http://www.wrox.com"

Here, the <message/> element returns an array containing one namespace when namespace Declarations() is called, and an array with two namespaces when inScopeNamespaces() is called. The two in-scope namespaces are the default namespace (represented by an empty string) and the wrox namespace. When these methods are called on the <message/> element, namespaceDeclarations() returns an empty array, whereas inScopeNamespaces() returns the same results.

A Namespace object can also be used to query an XML structure for elements in a specific namespace by using the double colon (::). For example, to retrieve all <message/> elements contained in the wrox namespace, you could use the following:

var messages = <messages xmlns:wrox="http://www.wrox.com/">
    <wrox:message>Hello world!</wrox:message>
</messages>;
var wroxNS = new Namespace("wrox", "http://www.wrox.com/");
var wroxMessages = messages.wroxNS::message;

The double colon indicates the namespace in which the element to be returned should exist. Note that it is the name of the JavaScript variable that is used, not the namespace prefix.

You can also set the default namespace for all XML objects created within a given scope. To do so, use the default xml namespace statement and assign either a Namespace object or simply a namespace URI. Here’s an example:

default xml namespace = "http://www.wrox.com/";
                   
function doSomething(){
                   
    //set default namespace just for this function
    default xml namespace = new Namespace("your", "http://www.yourdomain.com");
                   
}

The default XML namespace for the global scope is not set. This statement is useful when all XML data within a given scope will be using a specific namespace, avoiding constant references to the namespace itself.

OTHER CHANGES

To work seamlessly with standard ECMAScript, E4X makes some changes to the base language. One change is the introduction of the for-each-in loop. As opposed to the for-in loop, which iterates over each property and returns the property name, the for-each-in loop iterates over each property and returns the value of the property, as this example illustrates:

image
var employees =  <employees>
                    <employee position="Software Engineer">
                        <name>Nicholas C. Zakas</name>
                    </employee>
                    <employee position="Salesperson">
                        <name>Jim Smith</name>
                    </employee>
                </employees>;
                
for each (var child in employees){
    alert(child.toXMLString());
}

ForEachInExample01.htm

The for-each-in loop in this example fills the child variable with each child node of <employees/>, which may include comments, processing instructions, and/or text nodes. Attributes aren’t returned in the loop unless you use an XMLList of attributes, such as the following:

for each (var attribute in employees.@*){ //iterate over attributes
    alert(attribute);
}

Even though the for-each-in loop is defined as part of E4X, it can also be used on normal arrays and objects, as shown here:

var colors = ["red","green","blue"];
for each(var color in colors){
    alert(color);
}

ForEachInExample01.htm

For arrays, the for-each-in loop returns each array item. For non-XML objects, it returns the value of each property.

E4X also adds a global function called isXMLName() that accepts a string and returns true if the name is a valid local name for an element or attribute. This is provided as a convenience to developers who may be using unknown string data to construct XML data structures. Here’s an example:

alert(isXMLName("color"));        //true
alert(isXMLName("hello world"));  //false

If you are unsure of the origin of a string that should be used as a local name, it’s best to use isXMLName() first to determine if the string is valid or will cause an error.

The last change to standard ECMAScript is to the typeof operator. When used on an XML object or an XMLList object, typeof returns the string "xml". This differs from when it is used on other objects, in which case it returns "object", as shown here:

var xml = new XML();
var list = new XMLList();
var object = {};
                   
alert(typeof xml);     //"xml"
alert(typeof list);    //"xml"
alert(typeof object);  //"object"

In most cases, it is unnecessary to distinguish between XML and XMLList objects. Since both types are considered primitives in E4X, you cannot use the instanceof operator to make this distinction either.

ENABLING FULL E4X

Because E4X does many things differently than standard JavaScript, Firefox enables only the parts that work best when E4X is intermixed with other code. To fully enable E4X, you need to set the type attribute of the <script> tag to "text/javascript;e4x=1", as in this example:

<script type="text/javascript;e4x=1" src="e4x_file.js"></script>

When this switch is turned on, full E4X support is enabled, including the proper parsing of embedded comments and CData sections in E4X literals. Using comments and/or CData sections without full E4X enabled results in syntax errors.

SUMMARY

ECMAScript for XML (E4X) is an extension to ECMAScript defined in the ECMA-357 specification. The purpose of E4X is to provide syntax for working with XML data that is more like that of standard ECMAScript. E4X has the following characteristics:

  • Unlike the DOM, there is only one type to represent all of the different node types present in XML.
  • The XML object encapsulates data and the behavior necessary for all nodes. To represent a collection of multiple nodes, the specification defines an XMLList object.
  • Two other types, Namespace and QName, are present to represent namespaces and qualified names, respectively.

E4X changes standard ECMAScript syntax as follows to allow for easier querying of an XML structure:

  • Using two dots (..) indicates that all descendants should be matched, whereas using the @ character indicates that one or more attributes should be returned.
  • The asterisk character (*) represents a wildcard that can match any node of the given type.
  • All of these queries can also be accomplished via a series of methods that perform the same operation.

By the end of 2011, Firefox was the only browser to support E4X. Though no other browser vendors have committed to implementing E4X, it has gained a certain amount of popularity on the server with the BEA Workshop for WebLogic and YQL from Yahoo!.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.244.217