Welcome!

Machine Learning Authors: Zakia Bouachraoui, Liz McMillan, Roger Strukhoff, Pat Romanski, Carmen Gonzalez

Related Topics: Machine Learning

Machine Learning : Article

Real-World AJAX Book Preview: Seeing XML with Serialization

Real-World AJAX Book Preview: Seeing XML with Serialization

This content is reprinted from Real-World AJAX: Secrets of the Masters published by SYS-CON Books. To order the entire book now along with companion DVDs for the special pre-order price, click here for more information. Aimed at everyone from enterprise developers to self-taught scripters, Real-World AJAX: Secrets of the Masters is the perfect book for anyone who wants to start developing AJAX applications.

Seeing XML with Serialization
The flip side of parsing is serialization, which, in the loosest sense, is the conversion of an object from an internal representation to some (possibly text) format that can be reloaded in the future.

It's possible to write what are called tree-walked serializers, which recursively walk down each node of the tree to create an output. But most implementations of JavaScript that support XML also include a general text serializer. Unfortunately, Mozilla and others that conform to its JavaScript implementation use the DOM serializer (specifically the XMLSerializer class), while Internet Explorer uses a different implementation (see below).

Like DOMParser, the XMLSerializer class effectively has only one method (at least only one exposed to scripting) - the serializeToString() method that takes a document or an element and serializes it as a string representation. Typically, it's more convenient to create a serializer as needed rather than maintain one:

var xmlStr = (new XMLSerializer()).serializeToString(document);

This can work effectively with all XML nodes - in the case of documents and elements it serializes the tree, in the case of text nodes comments or attributes it just returns the text blocks associated with those nodes.

You can create the corresponding getter for the innerXHTML method very simply by using the serializeToString() method:

var e = Element.prototype;
e.__defineGetter__("innerXHTML",function(){
      var buffer = "";
      var currentElt = this;
      for (var index=0;index!=currentElt.childNodes.length;index++){
         var node = currentElt.childNodes.item(index);
         buffer += (new XMLSerializer()).serializeToString(node);
          }
      return buffer;
});

Because the innerXHTML method works on the children of the given node, the method iterates over each of the children in turn to serialize them, then puts each result in a growing text buffer that's returned once the last child has been encountered.

As with parsing, serialization is done differently in IE. Internet Explorer uses the read-only XML property to return the content of the node. Otherwise it's indistinguishable from the serialFrom- String() method, save perhaps in how non-essential white space is created.

XPath and Transformations
Through much of 2006, significant strides forward have been made as far as compatibility between browsers, including the use of such technologies of XPath and XSLT to handle transformations and queries against complex XML structures. Currently, Internet Explorer, Firefox, Netscape, and Opera 9 have XSLT/XPath capabilities, although as usual most have adopted the Mozilla-based implementations.

Walking the Walk with XPath
XPath is a language used for retrieving sets of XML nodes and it serves roughly the same purpose for XML that the SELECT statement serves for SQL. The details of the XPath language are fairly involved and beyond the scope of this book. (I can recommend Michael Kay's XSLT Programmer's Reference, Wiley Press) but it's worth going through the interfaces to illustrate how you can invoke XPath in your own code.

In Firefox, Opera, and Netscape, XPath is handled via the XPathEvaluator() object, which, while powerful, is also somewhat painful to work with. In essence, with the evaluator you pass both the XPath expression and the context that you're working with, along with a way of determining the namespaces for a given node and the expected form of the result.

For instance, suppose that you had an XML record of a person's name and address contained in a variable called recordDom:

<record>
    <identity>
       <firstName>Aleria</firstName>
       <surName>Delamare</surName>
       <gender>female</gender>
    </identity>
    <address type="send">
       <street>123 Sesame Street</street>
       <city>New York</city>
       <state>NY</state>
    </address>
    <address type="receive">
       <street>666 Apocalypse Lane </street>
       <city>Arkham</city>
       <state>MA</state>
    </address>
</record>

If you wanted to get a listing of all of the cities in a given record, you could use the XPath expression ("/record/address/city") and then take advantage of the XPathEvaluator to get the appropriate nodes:

var xpe = new XPathEvaluator();
var nodeArray = [];
var xpResolver = xpe.createResolver(recordDom.documentElement);
var xpathExpr = xpe.createExpression("/record/address/city",xpResolver);
var resultNodes = xpe.evaluate(xpathExpr,recordDom,xpResolver ,0,null);
    while (node = xresultset.iterateNext()){
      nodeArr.push(node);
      }

The result of this action is to create an array (nodeArr) that contains the set of two nodes.

It's worth walking through the evaluate function slowly. The full signature for the method is reasonably complex:

evaluate ( String expression , Node contextNode , XPathNSResolver resolver , short type , nsISupports result )

where the expression is the XPath expression (or an XPathExpression object), the contextNode is the node indicating where the XPath should be evaluated from, the XPathNSResolver is a namespace resolver, and the type is an integer representing the expected content - this is usually safely set as 0. The final result should be set to null for most cases since the result will be returned as the result of the method.

Namespaces can be somewhat problematic with XPath - if you have an XML document that uses namespaces, you'll have to have some way of mapping prefixes to the associated namespaces so they can be used effectively. A namespace resolver reads a given node and all of its children and creates an external lookup table associating namespace prefixes with their associated URIs, so that any time a prefix is encountered in an XPath expression, it will be understood to be part of a given namespace. The expression:

xpe.createResolver(recordDom.documentElement)

creates such a resolver for the context node. Similarly, the createExpression method creates a resolved XPath expression with namespaces intact.

The resulting object, contained in resultNodes, is an object of the xpathResult type. This is a set of nodes that can be retrieved using an iterator (via the iterateNext() method). Though in many cases, it's more convenient to push these nodes onto an array and then manipulate the array, as shown earlier.

In Internet Explorer, the selectNodes() method and selectSingleNode() methods on documents or elements are used to perform the same actions, though they have somewhat less support for namespace resolution. The selectNodes() method returns an iterator (IXMLDOMNodeList interface) for walking through the node set.

The following script illustrates a number of useful functions, including a getXPathNodes() method that returns an array of nodes, and a general getDocument() function that takes a URL and returns it to a processing function asynchronously on both Internet Explorer and Firefox.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
      <title></title>
      <script type="text/JavaScript">//<![CDATA[
// getXPathNodes returns a set of nodes in an array
getXPathNodes = function(context,xpath){
    var nodeArr = []
    if (window.ActiveXObject){
      var nodeset = context.selectNodes(xpath);
      while (node=nodeset.nextNode()){
         nodeArr.push(node);
         }
      }
    else {
      if (window.XPathEvaluator){
         var xpe = new XPathEvaluator();
         var xpr = xpe.createNSResolver(context);
        var xpResults = xpe.evaluate(xpath,context,xpr,0,null);
         while (node = xpResults.iterateNext()){
           nodeArr.push(node)
           }
      }
      else {
      alert("XPath not implemented");
      }
      }
    return nodeArr;
    }
// the getDocument() function takes a url and calls a callback function to process
that URL asynchronous.
function getDocument(url,fn){
    var browser = (window.ActiveXObject)?"IE":(window.XPathEvaluator)?"Mozilla":"Other";
    switch(browser){
      case "IE":
         var doc = new ActiveXObject("MSXML2.DOMDocument");
         doc.async = false;
         var http = new ActiveXObject("Microsoft.XMLHttp");
         http.open("GET",url,true);
         http.onreadystatechange = function(){
           if (http.readyState==4){
             if (http.status == 0 || http.status == 200){
             doc.loadXML(http.responseText);
             fn(doc);
             }
         else {
             alert(http.statusText);
             }
           }
         }
      http.send();
      break;
    case "Mozilla":
      var http = new XMLHttpRequest();
      http.open("GET",url,true);
      http.onreadystatechange = function(){
         if (http.readyState==4){
           if (http.status == 0 || http.status == 200){
             var doc = http.responseXML;
             fn(doc);
             }
           else {
             alert(http.statusText);
           }
         }
      }
         http.send(null);
         break;
      default:alert("AJAX not supported.");
      }
    }
// This illustrates both functions in use:
function test(){
    getDocument("record.xml",function(xmlDoc){
      var results = getXPathNodes(xmlDoc,"//address/city/text()");
      var display = document.getElementById("display");
      for (var index=0;index!=results.length;index++){
         results[index] = results[index].nodeValue;
         }
      display.innerHTML += results.join(", ");
      });
    }
// ]]></script>
    </head>
    <body onload="test()">
      <h1>Cities</h1>
      <div id="display"></div>
    </body>
</html>

XPath can be extraordinarily useful for working with complex XML trees, though, as should be obvious, there are probably better methods for working with smaller objects. XPath, on the other hand, is also a critical part of the XSLT transformation language, which has considerably more applicability in AJAX applications.

The Art of XSLT Transformations
Shortly after the XML specification was first ironed out, the architects of the specification realized that Cascading Style Sheets (CSS), while a very elegant solution for handling layout, wasn't necessarily complete enough to handle such things as altering the order of content, of producing intermediate content, or of filtering content based on some conditions in the content. Although CSS has advanced considerably over the years, this drawback - the ability to structurally alter incoming XML - remains true.

Because of this, a new XML-based language for transforming content was developed. Called the XML Stylesheet Language for Transformations (or XSLT), it was originally seen as part of a larger move towards generating content out either to HTML or a page description language called XML Stylesheet Language Formatting Objects (or XSL-FO). While FO has seen some resurgence in recent years, XSLT's ability to transform any XML into any other XML (or HTML) form immediately attracted a great deal of interest and made the technology far more visible in the marketplace.

As with XPath, a thorough discussion of XSLT is beyond the scope of this book, though it's worth exploring how XSLT can be invoked from JavaScript.

Under Firefox (and those technologies that are adopting the Mozilla API), transformations are handled using the XSLTProcessor object, with methods and properties given in Table 2.12.

Suppose that you had a set of XML records and you wanted to display them in tables, with the further caveat that names would be displayed surName, firstName (separated by a comma), and only the billing addresses in two records. Furthermore, you wanted the names sorted by surname. Let the records be given in the file records.xml:

<records>    <record>
     <identity>
       <firstName>Aleria</firstName>
         <surName>Delamare</surName>
         <gender>female</gender>
       </identity>
       <address>
         <street>123 Sesame Street</street>
         <city>New York</city>
         <state>NY</state>
       </address>
     </record>
     <record>
       <identity>
         <firstName>Sharon</firstName>
         <surName>Turing</surName>
         <gender>female</gender>
       </identity>
       <address>
         <street>1001 Binary Pt</street>
         <city>Sim City</city>
         <state>AZ</state>
       </address>
     </record>
     <record>
       <identity>
         <firstName>William</firstName>
         <surName>Martin</surName>
         <gender>male</gender>
       </identity>
       <address>
         <street>4212 Martin Way</street>
         <city>Martinique</city>
         <state>LA</state>
       </address>
     </record>
     <record>
       <identity>
         <firstName>Foster</firstName>
         <surName>Grant</surName>
         <gender>male</gender>
       </identity>
       <address>
         <street>2295 Shade St.</street>
         <city>Los Angeles</city>
         <state>CA</state>
       </address>
     </record>
     <record>
       <identity>
         <firstName>Diane</firstName>
         <surName>Weber</surName>
         <gender>female</gender>
       </identity>
       <address>
         <street>1754 Mermaid Lane</street>
         <city>Hollywood</city>
         <state>CA</state>
       </address>
     </record>
   </records>

The XSLT stylesheet (showRecordsTable.xsl) to create the requisite output would look as follows:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
   xmlns="http://www.w3.org/1999/xhtml">
   <xsl:output method="xml" media-type="text/xhtml" indent="yes" omit-xmldeclaration=" yes"/>
   <xsl:param name="sortKey" select="'surName'"/>
   <xsl:variable name="keySet" select="document('keys.xml')//option"/>
   <xsl:template match="/">
     <xsl:apply-templates select="records"/>
   </xsl:template>
   <xsl:template match="records">
     <div style="width:400px">
       <select style="float:right;" id="sortSelector" value="{$sortKey}"
onchange="transformer.sortByKey(this.value);">          <xsl:for-each select="$keySet">
           <xsl:choose>
             <xsl:when test="$sortKey = string(@value)">
               <option value="{@value}" selected="selected"><xsl:
value-of select="@title"/></option>              </xsl:when>
             <xsl:otherwise>
               <option value="{@value}"><xsl:value-of
select="@title"/></option>              </xsl:otherwise>
          </xsl:choose>
         </xsl:for-each>
       </select>
   <table border="1" style="width:100%;margin-top:24px;">
     <tr>
       <th>Name</th>
       <th>Street</th>
       <th>City</th>
       <th>State</th>
     </tr>
     <xsl:apply-templates select="record">
       <xsl:sort order="ascending" select="(identity|address)/
*[name(.) = string($sortKey)]"/>      </xsl:apply-templates>
   </table>
</div>
</xsl:template>
<xsl:template match="record">
   <tr>
     <xsl:apply-templates select="identity"/>
     <xsl:apply-templates select="address"/>
   </tr>
</xsl:template>
<xsl:template match="identity">
   <td>
     <xsl:value-of select="concat(surName,', ',firstName)"/>
   </td>
     </xsl:template>
     <xsl:template match="address">
   <td>
     <xsl:value-of select="street"/>
   </td>
   <td>
     <xsl:value-of select="city"/>
   </td>
   <td>
     <xsl:value-of select="state"/>
   </td>
</xsl:template>
</xsl:stylesheet>

In this particular case, each template corresponds to a pattern to be matched by a node, while each apply-templates invocation broadcasts the node to match the given XSLT templates.

This XSLT also assumes a second XML file (keys.xml) that contains specific configuration data for the XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<keys>
     <option title="First Name" value="firstName"/>
     <option title="Last Name" value="surName"/>
     <option title="City" value="city"/>
     <option title="State" value="state"/>
</keys>

The Mozilla Transformiix XSLT processor doesn’t recognize extensions, so there’s no way to create intermediate XML trees in such an XSLT file. By using an external XML resource, this gets around that limitation at the cost of having to do an additional download.

If you’re familiar with XSLT, you might note that this particular example doesn’t generate an entire XHTML page, only a

element that contains a table and a selection box. What this is specifically generating is a limited component that will let you order the table in whatever way you want based on the keys given in keys.xml.

The final file (primarily for use in Firefox and secondarily in Opera 9) is the corresponding HTML (or in this case XHTML) file transformationTest.xhtml, which brings all of this together as an application by defining a generalized transformer object:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
   <head>
     <title>Transformation Test</title>
     <script type="text/JavaScript">//<![CDATA[
var transformer = {    proc:new XSLTProcessor(),
   data:null,
   target:null,
   setTarget:function(targetName){
     this.target = document.getElementById(targetName);
     },
   loadStylesheet:function(url){
     var http = new XMLHttpRequest();
     http.open("GET",url,false);
     http.send(null);
     this.proc.importStylesheet(http.responseXML);
     },
   loadData:function(url){
     var http = new XMLHttpRequest();
     http.open("GET",url,false);
     http.send(null);
     this.data = http.responseXML;
     },
   sortByKey:function(key){
     if (key != null){
       this.proc.setParameter("","sortKey",key);
       }
     var resultDoc = this.proc.transformToDocument(this.data);
     this.target.innerHTML = (new XMLSerializer()).serializeToString(resultDoc);
     }
   }
function main(){
     transformer.setTarget("display");
     transformer.loadStylesheet("showRecordsTable.xsl");
     transformer.loadData("records.xml");
     transformer.sortByKey();
     }
       ]]></script>
   </head>
   <body onload="main()">
       <h1>Transformation Test</h1>
       <div id="display"/>
   </body>
</html>

This has been written as a synchronous application for the ease of following it, but should be readily castable as an asynchronous one. The singleton transformer object exposes interfaces to load in both the transformation and the data source, then sets the "sortKey" parameter in the stylesheet to determine which particular criterion is used for ordering the table.

The importStylesheet() method takes an XSLT DOM only, and in a full-grade application you should encapsulate the assignment in a try/catch construct, since the importStylesheet method will throw an exception if the transformation can’t be compiled.

The main() function in this particular case then designates a target element for depositing the results of the transformation, loads the stylesheet and data, then calls the sortByKey() method with no arguments to use the default one for the transformation (in this case, "lastName"). This in turn calls the transformation and puts the results in the "display" element. An example of this is shown in Figure 2.5.

The generated content is somewhat more coupled than may be ideal. In this particular case, the generated select box includes an OnChange event that also calls transformer.sortByKey(), though this time including the key value. One immediate effect is that the table can become instantly sortable along any key property, with the logic for that sort being contained not in JavaScript but in the generated content from the XSLT.

The Internet Explorer version of this is nearly identical in structure, though the interfaces vary slightly. The critical transformer object that was created in the sample above would be recast for IE as:

var transformer = {
   proc:new ActiveXObject("MSXML.XSLTProcessor"),
   data:null,
   target:null,
   setTarget:function(targetName){
     this.target = document.getElementById(targetName);
     },
   loadStylesheet:function(url){
     var http = new ActiveXObject("MSXML.XMLHttp");
     http.open("GET",url,false);http.send();
     this.proc.stylesheet = http.responseXML;
     },
   loadData:function(url){
     var http = new ActiveXObject("MSXML.XMLHttp");
     http.open("GET",url,false);
     http.send();
     this.data = http.responseXML;
         this.proc.input = this.data;
     },
   sortByKey:function(key){
     if (key != null){
       this.proc.setParameter("","sortKey",key);
       }
     this.proc.transform();
         var resultDoc = this.outpu;
     this.target.innerHTML = resultDoc.xml;
     }
   }

The combination of XSLT and AJAX is a powerful one, because it makes it possible to handle both presentation abstraction and data abstraction without putting a premium amount of effort into writing JavaScript code directly. The content is changing inline, not via whole screen refreshes, and really showcases ways where the natural decomposition of application pieces can take place.

This content is reprinted from Real-World AJAX: Secrets of the Masters published by SYS-CON Books. To order the entire book now along with companion DVDs, click here to order.

More Stories By Kurt Cagle

Kurt Cagle is a developer and author, with nearly 20 books to his name and several dozen articles. He writes about Web technologies, open source, Java, and .NET programming issues. He has also worked with Microsoft and others to develop white papers on these technologies. He is the owner of Cagle Communications and a co-author of Real-World AJAX: Secrets of the Masters (SYS-CON books, 2006).

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.


CloudEXPO Stories
The precious oil is extracted from the seeds of prickly pear cactus plant. After taking out the seeds from the fruits, they are adequately dried and then cold pressed to obtain the oil. Indeed, the prickly seed oil is quite expensive. Well, that is understandable when you consider the fact that the seeds are really tiny and each seed contain only about 5% of oil in it at most, plus the seeds are usually handpicked from the fruits. This means it will take tons of these seeds to produce just one bottle of the oil for commercial purpose. But from its medical properties to its culinary importance, skin lightening, moisturizing, and protection abilities, down to its extraordinary hair care properties, prickly seed oil has got lots of excellent rewards for anyone who pays the price.
The platform combines the strengths of Singtel's extensive, intelligent network capabilities with Microsoft's cloud expertise to create a unique solution that sets new standards for IoT applications," said Mr Diomedes Kastanis, Head of IoT at Singtel. "Our solution provides speed, transparency and flexibility, paving the way for a more pervasive use of IoT to accelerate enterprises' digitalisation efforts. AI-powered intelligent connectivity over Microsoft Azure will be the fastest connected path for IoT innovators to scale globally, and the smartest path to cross-device synergy in an instrumented, connected world.
There are many examples of disruption in consumer space – Uber disrupting the cab industry, Airbnb disrupting the hospitality industry and so on; but have you wondered who is disrupting support and operations? AISERA helps make businesses and customers successful by offering consumer-like user experience for support and operations. We have built the world’s first AI-driven IT / HR / Cloud / Customer Support and Operations solution.
ScaleMP is presenting at CloudEXPO 2019, held June 24-26 in Santa Clara, and we’d love to see you there. At the conference, we’ll demonstrate how ScaleMP is solving one of the most vexing challenges for cloud — memory cost and limit of scale — and how our innovative vSMP MemoryONE solution provides affordable larger server memory for the private and public cloud. Please visit us at Booth No. 519 to connect with our experts and learn more about vSMP MemoryONE and how it is already serving some of the world’s largest data centers. Click here to schedule a meeting with our experts and executives.
Darktrace is the world's leading AI company for cyber security. Created by mathematicians from the University of Cambridge, Darktrace's Enterprise Immune System is the first non-consumer application of machine learning to work at scale, across all network types, from physical, virtualized, and cloud, through to IoT and industrial control systems. Installed as a self-configuring cyber defense platform, Darktrace continuously learns what is ‘normal' for all devices and users, updating its understanding as the environment changes.