JavaScript XML

Parsing and using XML with JavaScript. Examples are taken [2] from that book.

1 Creating New Documents

1.1 Portable Function for Creating Documents

/**
 * Create a new Document object.  If no arguments are specified, 
 * the document will be empty.  If a root tag is specified, the document
 * will contain that single root tag.  If the root tag has a namespace
 * prefix, the second argument must specify the URL that identifies the
 * namespace.
 */
XML.newDocument = function(rootTagName, namespaceURL) {
    if (!rootTagName) rootTagName = "";
    if (!namespaceURL) namespaceURL = "";
    
    if (document.implementation && document.implementation.createDocument) {
        // This is the W3C standard way to do it
        return document.implementation.createDocument(namespaceURL,
                                                      rootTagName, null);
    }
    else { // This is the IE way to do it
        // Create an empty document as an ActiveX object
        // If there is no root element, this is all we have to do
        var doc = new ActiveXObject("MSXML2.DOMDocument");

        // If there is a root tag, initialize the document
        if (rootTagName) {
            // Look for a namespace prefix
            var prefix = "";
            var tagname = rootTagName;
            var p = rootTagName.indexOf(':');
            if (p != -1) {
                prefix = rootTagName.substring(0, p);
                tagname = rootTagName.substring(p+1);
            }

            // If we have a namespace, we must have a namespace prefix
            // If we don't have a namespace, we discard any prefix
            if (namespaceURL) {
                if (!prefix) prefix = "a0"; // What Firefox uses
            }
            else prefix = "";

            // Create the root element (with optional namespace) as a
            // string of text
            var text = "<" + (prefix?(prefix+":"):"") +  tagname +
                (namespaceURL
                 ?(" xmlns:" + prefix + '="' + namespaceURL +'"')
                 :"") +
                "/>";
            // And parse that text into the empty document
            doc.loadXML(text);
        }
        return doc;
    }
};

1.2 Loading XML from the Net

/**
 * Synchronously load the XML document at the specified URL and
 * return it as a Document object
 */
XML.load = function(url) {
    // Create a new document the previously defined function
    var xmldoc = XML.newDocument();  
    xmldoc.async = false;  // We want to load synchronously
    xmldoc.load(url);      // Load and parse
    return xmldoc;         // Return the document
};
/**
 * Asynchronously load and parse an XML document from the specified URL.
 * When the document is ready, pass it to the specified callback function.
 * This function returns immediately with no return value.
 */
XML.loadAsync = function(url, callback) {
    var xmldoc = XML.newDocument();

    // If we created the XML document using createDocument, use
    // onload to determine when it is loaded
    if (document.implementation && document.implementation.createDocument) {
        xmldoc.onload = function() { callback(xmldoc); };
    }
    // Otherwise, use onreadystatechange as with XMLHttpRequest
    else {
        xmldoc.onreadystatechange = function() {
            if (xmldoc.readyState == 4) callback(xmldoc);
        };
    }

    // Now go start the download and parsing
    xmldoc.load(url);
};

1.3 Parsing XML Text

/**
 * Parse the XML document contained in the string argument and return 
 * a Document object that represents it.
 */
XML.parse = function(text) {
    if (typeof DOMParser != "ftp://ftp.") {
        // Mozilla, Firefox, and related browsers
        return (new DOMParser()).parseFromString(text, "application/xml");
    }
    else if (typeof ActiveXObject != "undefined") {
        // Internet Explorer.
        var doc = XML.newDocument();  // Create an empty document
        doc.loadXML(text);            // Parse text into it
        return doc;                   // Return it
    }
    else {
        // As a last resort, try loading the document from a data: URL
        // This is supposed to work in Safari.  Thanks to Manos Batsis and
        // his Sarissa library (sarissa.sourceforge.net) for this technique.
        var url = "data:text/xml;charset=utf-8," + encodeURIComponent(text);
        var request = new XMLHttpRequest();
        request.open("GET", url, false);
        request.send(null);
        return request.responseXML;
    }
};

2 Manipulating XML with the DOM

2.1 Example: Table from XML Data

/**
 * Extract data from the specified XML document and format it as an HTML table.
 * Append the table to the specified HTML element.  (If element is a string,
 * it is taken as an element ID, and the named element is looked up.)
 *
 * The schema argument is a JavaScript object that specifies what data is to
 * be extracted and how it is to be displayed.  The schema object must have a 
 * property named "rowtag" that specifies the tag name of the XML elements that
 * contain the data for one row of the table.  The schema object must also have
 * a property named "columns" that refers to an array.  The elements of this
 * array specify the order and content of the columns of the table.  Each
 * array element may be a string or a JavaScript object.  If an element is a 
 * string, that string is used as the tag name of the XML element that contains
 * table data for the column, and also as the column header for the column.
 * If an element of the columns[] array is an object, it must have one property
 * named "tagname" and one named "label".  The tagname property is used to 
 * extract data from the XML document and the label property is used as the
 * column header text.  If the tagname begins with an @ character, it is 
 * an attribute of the row element rather than a child of the row.
 */
function makeTable(xmldoc, schema, element) {
    // Create the <table> element
    var table = document.createElement("table");

    // Create the header row of <th> elements in a <tr> in a <thead>
    var thead = document.createElement("thead");
    var header = document.createElement("tr");
    for(var i = 0; i < schema.columns.length; i++) {
        var c = schema.columns[i];
        var label = (typeof c == "string")?c:c.label;
        var cell = document.createElement("th");
        cell.appendChild(document.createTextNode(label));
        header.appendChild(cell);
    }
    // Put the header into the table
    thead.appendChild(header);
    table.appendChild(thead);

    // The remaining rows of the table go in a <tbody>
    var tbody = document.createElement("tbody");
    table.appendChild(tbody);

    // Now get the elements that contain our data from the xml document
    var xmlrows = xmldoc.getElementsByTagName(schema.rowtag);
    
    // Loop through these elements. Each one contains a row of the table
    for(var r=0; r < xmlrows.length; r++) {
        // This is the XML element that holds the data for the row
        var xmlrow = xmlrows[r];
        // Create an HTML element to display the data in the row
        var row = document.createElement("tr");
        
        // Loop through the columns specified by the schema object
        for(var c = 0; c < schema.columns.length; c++) {
            var sc = schema.columns[c];
            var tagname = (typeof sc == "string")?sc:sc.tagname;
            var celltext;
            if (tagname.charAt(0) == '@') {
                // If the tagname begins with '@', it is an attribute name
                celltext = xmlrow.getAttribute(tagname.substring(1));
            }
            else {
                // Find the XML element that holds the data for this column
                var xmlcell = xmlrow.getElementsByTagName(tagname)[0];
                // Assume that element has a text node as its first child
                var celltext = xmlcell.firstChild.data;
            }
            // Create the HTML element for this cell
            var cell = document.createElement("td");
            // Put the text data into the HTML cell
            cell.appendChild(document.createTextNode(celltext));
            // Add the cell to the row
            row.appendChild(cell);
        }

        // And add the row to the tbody of the table
        tbody.appendChild(row);
    }

    // Set an HTML attribute on the table element by setting a property.
    // Note that in XML we must use setAttribute() instead.
    table.frame = "border";

    // Now that we've created the HTML table, add it to the specified element.
    // If that element is a string, assume it is an element ID.
    if (typeof element == "string") element = document.getElementById(element);
    element.appendChild(table);
}
function displayAddressBook(){ 
    var schema = {
        rowtag: "contact",
        columns: [
            {tagname: "@name", label: "Name"},
            {tagname: "email", label: "Address"}
        ]
    };
    var xmldoc = XML.load("addresses.xml");
    makeTable(xmldoc, schema, "addresses");
}
addresses.xml:
<?xml version="1.0" encoding="ISO-8859-1"?>
<contacts>
  <contact name="Able Baker"><email>able@example.com</email></contact>
  <contact name="Careful Dodger"><email>dodger@example.com</email></contact>
  <contact name="Eager Framer" personal="true"><email>framer@example.com</email></contact>
</contacts>
<div id="addresses"><!--table here-->
</div>

3 XSLT

<?xml version="1.0"?><!-- this is an xml document -->
<!-- declare the xsl namespace to distinguish xsl tags from html tags -->
<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="html"/>

  <!-- When we see the root element, output the HTML framework of a table -->
  <xsl:template match="/">
    <table>
      <tr><th>Name</th><th>E-mail Address</th></tr>
      <xsl:apply-templates/>  <!-- and recurse for other templates -->
    </table>
  </xsl:template>

  <!-- When we see a <contact> element... -->
  <xsl:template match="contact">
    <tr> <!-- Begin a new row of the table -->
      <!-- Use the name attribute of the contact as the first column -->
      <td><xsl:value-of select="@name"/></td>
      <xsl:apply-templates/>  <!-- and recurse for other templates -->
    </tr>
  </xsl:template>

  <!-- When we see an <email> element, output its content in another cell --> 
  <xsl:template match="email">
    <td><xsl:value-of select="."/></td>
  </xsl:template>
</xsl:stylesheet>

3.1 Example Transform

news.xml
<?xml version="1.0" encoding="ISO-8859-1" ?>
<?xml-stylesheet type="text/xsl" href="./feedtohtml.xsl" ?>
<myrss> <!--changed from rss because firefox is too smart
            and asks me if I want to subscribe. -->
<channel xmlns:html="http://www.w3.org/1999/xhtml">
        <title>CSCE 782: Fall 2006</title>
        <link>http://jmvidal.cse.sc.edu/csce782/</link>
        <description>Announcements.</description>
        <language>en-us</language>
        <copyright>Copyright 2006 jmvidal</copyright>
        <docs>http://blogs.law.harvard.edu/tech/rss/</docs>
        <lastBuildDate>Mon, 04 Dec 2006 15:16:10 EST</lastBuildDate>
<item>
    <title>Final Project Handout</title>
    <pubDate>Mon, 04 Dec 2006 15:14:42 EST</pubDate>
    <guid>http://jmvidal.cse.sc.edu/csce782/news.rss#1165263282</guid>
    <description>

OK, I have posted the final project handout. Let me know if you have
questions.

    </description>
    <link>http://jmvidal.cse.sc.edu/csce782/news.rss</link>
</item>

<item>
    <title>Last Week: No Classes</title>
    <pubDate>Thu, 30 Nov 2006 09:06:43 EST</pubDate>
    <guid>http://jmvidal.cse.sc.edu/csce782/news.rss#1164895603</guid>
    <description>

We will not be meeting during the last week. But, I will be in my
office during class time in case you want to talk about your final
project.
    </description>
    <link>http://jmvidal.cse.sc.edu/csce782/news.rss</link>
</item>

<item>
    <title>Test 2</title>
    <pubDate>Tue, 28 Nov 2006 06:40:37 EST</pubDate>
    <guid>http://jmvidal.cse.sc.edu/csce782/news.rss#1164714037</guid>
    <description>
In case I did not mention it in class, Test 2 will be based solely on the rest of the book chapters. That is, it does not cover the papers I presented in class.
    </description>
    <link>http://jmvidal.cse.sc.edu/csce782/news.rss</link>
</item>

</channel>
</myrss>


feedtohtml.xsl:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/myrss">
<html>
<head>
  <link href="xsl.css" rel="stylesheet" type="text/css" />
        <style type="text/css">
        body {
                font-size:0.83em;
        }
        </style>
</head>
<body>
        <div id="logo">
                <xsl:element name="a">
                        <xsl:attribute name="href">
                                <xsl:value-of select="channel/link" />
                        </xsl:attribute>
                        <xsl:value-of select="channel/title" />
                </xsl:element>
        </div>
        <div class="Snippet" style="border-width:0; background-color:#FFF; margin:1em">
                <div class="titleWithLine">
                        <xsl:value-of select="channel/description" />
                </div>
                <dl style="padding-right:1em">
                        <xsl:for-each select="channel/item">
                                <dd>
                                  <xsl:value-of select="title"/>
                                </dd>
                                <dt>
                                        <xsl:value-of select="description" disable-output-escaping="yes"/><br />
                                        <span class="comments"><xsl:value-of select="pubDate" /></span>
                                </dt>
                        </xsl:for-each>
                </dl>
        </div>
        <div id="footer">
                <xsl:value-of select="channel/copyright" />
        </div>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

4 XPath

<?xml version="1.0" encoding="ISO-8859-1"?>
<contacts>
  <contact name="Able Baker"><email>able@example.com</email></contact>
  <contact name="Careful Dodger"><email>dodger@example.com</email></contact>
  <contact name="Eager Framer" personal="true"><email>framer@example.com</email></contact>
</contacts>
Let context = contacts:

4.1 XPath Example

/**
 * XML.XPathExpression is a class that encapsulates an XPath query and its
 * associated namespace prefix-to-URL mapping.  Once an XML.XPathExpression
 * object has been created, it can be evaluated one or more times (in one
 * or more contexts) using the getNode() or getNodes() methods.
 *
 * The first argument to this constructor is the text of the XPath expression.
 * 
 * If the expression includes any XML namespaces, the second argument must
 * be a JavaScript object that maps namespace prefixes to the URLs that define
 * those namespaces.  The properties of this object are the prefixes, and
 * the values of those properties are the URLs.
 */
XML.XPathExpression = function(xpathText, namespaces) {
    this.xpathText = xpathText;    // Save the text of the expression
    this.namespaces = namespaces;  // And the namespace mapping

    if (document.createExpression) {
        // If we're in a W3C-compliant browser, use the W3C API
        // to compile the text of the XPath query
        this.xpathExpr = 
            document.createExpression(xpathText, 
                                      // This function is passed a 
                                      // namespace prefix and returns the URL.
                                      function(prefix) {
                                          return namespaces[prefix];
                                      });
    }
    else {
        // Otherwise, we assume for now that we're in IE and convert the
        // namespaces object into the textual form that IE requires.
        this.namespaceString = "";
        if (namespaces != null) {
            for(var prefix in namespaces) {
                // Add a space if there is already something there
                if (this.namespaceString) this.namespaceString += ' ';
                // And add the namespace
                this.namespaceString += 'xmlns:' + prefix + '="' +
                    namespaces[prefix] + '"';
            }
        }
    }
};

/**
 * This is the getNodes() method of XML.XPathExpression.  It evaluates the
 * XPath expression in the specified context.  The context argument should
 * be a Document or Element object.  The return value is an array 
 * or array-like object containing the nodes that match the expression.
 */
XML.XPathExpression.prototype.getNodes = function(context) {
    if (this.xpathExpr) {
        // If we are in a W3C-compliant browser, we compiled the
        // expression in the constructor.  We now evaluate that compiled
        // expression in the specified context
        var result =
            this.xpathExpr.evaluate(context, 
                                    // This is the result type we want
                                    XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
                                    null);

        // Copy the results we get into an array.
        var a = new Array(result.snapshotLength);
        for(var i = 0; i < result.snapshotLength; i++) {
            a[i] = result.snapshotItem(i);
        }
        return a;
    }
    else {
        // If we are not in a W3C-compliant browser, attempt to evaluate
        // the expression using the IE API.
        try {
            // We need the Document object to specify namespaces
            var doc = context.ownerDocument;
            // If the context doesn't have ownerDocument, it is the Document
            if (doc == null) doc = context;
            // This is IE-specific magic to specify prefix-to-URL mapping
            doc.setProperty("SelectionLanguage", "XPath");
            doc.setProperty("SelectionNamespaces", this.namespaceString);

            // In IE, the context must be an Element not a Document, 
            // so if context is a document, use documentElement instead
            if (context == doc) context = doc.documentElement;
            // Now use the IE method selectNodes() to evaluate the expression
            return context.selectNodes(this.xpathText);
        }
        catch(e) {
            // If the IE API doesn't work, we just give up
            throw "XPath not supported by this browser.";
        }
    }
}


/**
 * This is the getNode() method of XML.XPathExpression.  It evaluates the
 * XPath expression in the specified context and returns a single matching
 * node (or null if no node matches).  If more than one node matches,
 * this method returns the first one in the document.
 * The implementation differs from getNodes() only in the return type.
 */
XML.XPathExpression.prototype.getNode = function(context) {
    if (this.xpathExpr) {
        var result =
            this.xpathExpr.evaluate(context, 
                                    // We just want the first match
                                    XPathResult.FIRST_ORDERED_NODE_TYPE,
                                    null);
        return result.singleNodeValue;
    }
    else {
        try {
            var doc = context.ownerDocument;
            if (doc == null) doc = context;
            doc.setProperty("SelectionLanguage", "XPath");
            doc.setProperty("SelectionNamespaces", this.namespaceString);
            if (context == doc) context = doc.documentElement;
            // In IE call selectSingleNode instead of selectNodes
            return context.selectSingleNode(this.xpathText);
        }
        catch(e) {
            throw "XPath not supported by this browser.";
        }
    }
};

// A utility to create an XML.XPathExpression and call getNodes() on it
XML.getNodes = function(context, xpathExpr, namespaces) {
    return (new XML.XPathExpression(xpathExpr, namespaces)).getNodes(context);
};

// A utility to create an XML.XPathExpression and call getNode() on it
XML.getNode  = function(context, xpathExpr, namespaces) {
    return (new XML.XPathExpression(xpathExpr, namespaces)).getNode(context);
};
<?xml version="1.0" encoding="ISO-8859-1"?>
<contacts>
  <contact name="Able Baker"><email>able@example.com</email></contact>
  <contact name="Careful Dodger"><email>dodger@example.com</email></contact>
  <contact name="Eager Framer" personal="true"><email>framer@example.com</email></contact>
</contacts>
var xmldoc = XML.load('addresses.xml');
var query = 'contacts/contact[@personal="true"]';
var serializer = new XMLSerializer();
serializer.serializeToString(XML.getNode(xmldoc,query)) [6]

URLs

  1. JavaScript, http://www.amazon.com/exec/obidos/ASIN/0596101996/multiagentcom
  2. taken, http://www.davidflanagan.com/javascript5/
  3. W3C Recommendation, http://www.w3.org/TR/xslt
  4. wikipedia:XSLT, http://www.wikipedia.org/wiki/XSLT
  5. XML Path Language, http://www.w3.org/TR/xpath
  6. serializer.serializeToString(XML.getNode(xmldoc,query)), javascript:alert(serializer.serializeToString(XML.getNode(xmldoc,query)));

This talk available at http://jmvidal.cse.sc.edu/talks/javascriptxml/
Copyright © 2009 José M. Vidal . All rights reserved.

28 February 2007, 02:14PM