Managing the State
The previous example is very simple. The script walks through the tree looking for a specific element, product. At each step, the script considers only the current node.
In many cases, the processing is more complicated. Specifically, it is common to process different nodes differently, to collect information from several elements, or to process elements only if they are children of other elements.
With XSL, you can write paths such as section/title and combine information from several elements with value-of.
How do you do something similar with DOM? Essentially, the script must maintain state information. In other words, as it examines a node, the script must remember where it's coming from (the node ancestor) or what children it is expecting.
A DOM Application That Maintains the State
Listing 7.5 is a slightly different list of products. This time, the price and the name of the product are stored in elements below product.
Listing 7.5: products2.xml
<?xml version="1.0"?> <xbe:products xmlns:xbe="http://www.psol.com/xbe2/listing7.5"> <xbe:product> <xbe:name>XML Editor</xbe:name> <xbe:price>499.00</xbe:price> </xbe:product> <xbe:product> <xbe:name>DTD Editor</xbe:name> <xbe:price>199.00</xbe:price> </xbe:product> <xbe:product> <xbe:name>XML Book</xbe:name> <xbe:price>19.99</xbe:price> </xbe:product> <xbe:product> <xbe:name>XML Training</xbe:name> <xbe:price>699.00</xbe:price> </xbe:product> </xbe:products>
Unlike Listing 7.1, to print the converted price and the product name, the script needs to remember that it is within a name, below a product.
As Listing 7.6 illustrates, this is very easy to do with special functions. Figure 7.9 shows the result in a browser.
Listing 7.6: stateful.html
<html> <head> <title>Currency Conversion</title> <script language="JavaScript"> var ns = "http://www.psol.com/xbe2/listing7.5"; function convert(form,xmlisland) { var fname = form.fname.value, output = form.output, rate = form.rate.value; output.value = ""; var document = loadIntoIsland(fname,xmlisland), root = document.documentElement; walkNode(root,output,rate) } function loadIntoIsland(fname,xmlisland) { xmlisland.async = false; xmlisland.load(fname); if(xmlisland.parseError.errorCode != 0) alert(xmlisland.parseError.reason); return xmlisland; } function walkNode(node,output,rate) { if(node.nodeType == 1) { if(node.baseName == "product" && node.namespaceURI == ns) walkProduct(node,output,rate); else { var children, i; children = node.childNodes; for(i = 0;i < children.length;i++) walkNode(children.item(i),output,rate); } } } function walkProduct(node,output,rate) { var children = node.childNodes, i; for(i = 0;i < children.length;i++) { var child = children.item(i); if(child.nodeType == 1) { if(child.baseName == "price" && child.namespaceURI == ns) walkPrice(child,output,rate); else if(child.baseName == "name" && child.namespaceURI == ns) walkName(child,output); } } output.value += "\r"; } function walkPrice(node,output,rate) { var children = node.childNodes, price = ""; for(i = 0;i < children.length;i++) { var child = children.item(i); if(child.nodeType == 3) price += child.data; } output.value += price * rate; } function walkName(node,output,rate) { var children = node.childNodes; for(i = 0;i < children.length;i++) { var child = children.item(i); if(child.nodeType == 3) output.value += child.data; } output.value += ": "; } </script> </head> <body> <center> <form id="controls"> File: <input type="text" name="fname" value="products2.xml"> Rate: <input type="text" name="rate" value="1.0622" size="6"><br> <input type="button" value="Convert" onclick="convert(controls,xmlisland)"> <input type="button" value="Clear" onclick="output.value=''"><br> <!-- make sure there is one character in the text area --> <textarea name="output" rows="10" cols="50" readonly> </textarea> </form> </center> <xml id="xmlisland"></xml> </body> </html>Figure 7.9: Running the conversion utility.
You recognize many elements from the previous listing. The novelty is in functions walkNode(), walkProduct(), walkName(), and walkPrice().
walkNode() is very similar to searchPrice(). It walks down the tree looking for product elements. When it finds a product, it hands it to walkProduct().
walkProduct() is a specialized function that processes only product elements. However, by virtue of being specialized, it knows that a product element contains a name element and a price element. It looks specifically for these two in the product's children and hands them to other specialized functions: walkName() and walkPrice(). This illustrates how the function maintains state information: It knows it is in a product element and it expects the product to contain specific elements.
You might also be interested in loadIntoIsland(). This function loads the price list in the XML island from a file. Indeed, the XML island is now empty; it is just a placeholder for the XML parser:
<xml id="xmlisland"></xml>
loadIntoIsland() returns the Document. Most of the code in this function is Internet Explorerspecific because, as explained before, DOM currently does not specify how to load XML documents:
function loadIntoIsland(fname,xmlisland) { xmlisland.async = false; xmlisland.load(fname); if(xmlisland.parseError.errorCode != 0) alert(xmlisland.parseError.reason); return xmlisland; }
The function first set the async property to false. async is specific to Internet Explorer 5; it enables or disables background download. Next, it calls load(), which is also specific to Internet Explorer 5. As the name implies, load() loads the document.
Finally, it checks for errors while parsing. The parseError property holds information about parsing errors.
A Note on Structure
If you compare Listing 7.1 and Listing 7.5, it appears that the structure of the two listings is different. The first listing has few elements. Important information, such as the product's price, is stored in attributes. In practice, it means that when the parser has found a product, it has all the information it needs.
In contrast, the second listing has more elements and a hierarchy that goes three levels deep. When the script hits the product, it needs to search the next level for name and price.
Compare Listing 7.2 with Listing 7.6. As you can see, walking the attribute-oriented listing is easier because there is no need to maintain state information.
TIP
One of the major reasons programmers like to place data in attributes is to avoid having to maintain state when walking down an XML file.