|
Chapter 5: The XML ApplicationNow that you've seen an XML document and a schema to describe its structure, let's create an application to use the data. I'll walk you through the two basic ways to deal with an XML document: as an in-memory tree object and as an object that generates events as it is processed by the parser and any attached applications. The Document Object ModelThe Document Object Model, or DOM, exposes an XML document as a tree structure in memory and provides an easy-to-use environment for the programmer. The DOM provides an accessible object that you can interrogate and manipulate like any other object in modern-day programming languages. The DOM defines a standard set of objects and interfaces that you can use to manipulate XML, providing access to documents, elements, and attributes. The DOM lets you express an XML document as an object, so you can work with it as you can any other object on your system—by using a well-documented application programming interface (API) with useful properties and methods. As you learned in earlier chapters, the DOM is a World Wide Web Consortium (W3C) Recommendation. Because the DOM is a large project, the W3C DOM Working Group faced and still faces a daunting task. To better manage the project, the group broke up the work into multiple parts, adopting the first part in October 1998; I expect that the second part will be complete in the first half of 2000. The W3C recommendation is useful as a blueprint for a common object model, but it does not go far enough in defining a specific implementation of the DOM. Each implementation of the DOM, therefore, will probably consist of a different view of the document object. For example, there are some key interfaces missing from the W3C version of the DOM that Microsoft felt were important to include. I use two methods, selectNodes and selectSingleNode, that are not in the W3C specification. Several parser providers offer implementations of the DOM in their products. Because the environment in which you implement each parser has different requirements, each of these implementations is different. Now I will describe the Microsoft implementation of the DOM, since it has by far the best documentation and support. The Microsoft DOM is part of the Microsoft XML parser object. Microsoft includes the Microsoft XML DOM object in Microsoft Internet Explorer 5, Microsoft Office 2000, and Microsoft Windows 2000. The XML DOM object is also a redeployable object that you can include in your own applications. The DOM's filename is msxml2.dll (see the sidebar), and it is registered as a COM object with the name MSXML2.DOMDocument. Since the DOM is a COM object, you can invoke it wherever you would invoke a COM object in any COM-enabled application. You can access it as an ActiveX control in scripting using Microsoft.XMLDOM. XMLDOM Versions Because development of the XML DOM is ongoing, you might find a number of versions of xmldom.dll on your machine. If you program to MSXML.DOMDocument, you are accessing version 2.5 of the DLL. Using MSXML2.DOMDocument lets you access version 2.6 of the DLL, and MSXML.DOMDocument30 gives you access to version 3 of the DLL. You can run the file xmlinst.exe after installing the latest version of MSXML to point all of your registry entries to the latest version of the file. In this chapter we'll program to MSXML2.DOMDocument. The DOM in ActionThink of the DOM as a dynamic hierarchical object with a set of interfaces, properties, and methods. It is important to note that the computer sees the object we call an XML document as just a serial collection of bytes. Because this collection of bytes takes the form of plain text, it's easy to read and easy to move around our networks and over the Internet. However, for the computer to interrogate and manipulate the information in an XML document, you must turn the document into an in-memory object that is better suited for treatment by high-level programming languages. You do this by instantiating a copy of the DOM, which invokes a parser to break up the XML document into pieces. Figure 5-1 shows the operation of the parser in creating the DOM object. Figure 5-1. The DOM provides a standard set of interfaces that allows a programmer to access the hierarchical objects represented by an XML stream. At the top of Figure 5-1 is a small XML document representing a joke. You can see elements for Joke, Setup, and Punchline, and attributes for author and firstTold. The XML parser reads this document one character at a time, determining which characters are markup and which are content. If the parser doesn't find a schema, it follows the rules of well-formed XML. If the parser does find a schema, it reads the schema and then ensures that the document adheres to the structure described by the schema. Once the parser is satisfied that the document is properly defined, it creates a set of nodes that have certain properties. (I discussed XML nodes in Chapter 4.) Table 5-1 lists the 12 node types in the Microsoft DOM implementation. Table 5-1. Node types defined by the Microsoft implementation of the W3C DOM.
The properties and methods available for each node depend on the type of node it is. For example, you can load a document node with a serialized (raw text) XML document, but you can't load an element or attribute node directly. To access an element node, you must first successfully read the document into a document node. In Figure 5-1, Joke has four nodes. The first two are the element nodes Setup and Punchline. The second two nodes are the attribute nodes author and firstTold. In the <Joke> start tag, a namespace points to a schema on an external site. This schema is specified using XML Data Reduced (XDR) syntax. Notice the nodeDataType property of each node. All are strings except for the firstTold attribute, which has been declared a dateTime data type. If you don't specify a namespace, all nodeDataType properties are strings. By accessing the typedValue property of nodeDataType , the object will return the value of the date as a date variant, so your application does not need to validate the data type or convert the value for processing. You'll find the full Microsoft DOM API at http://msdn.microsoft.com. Like most APIs, the DOM API is rich, allowing you to do a number of things including loading and saving, creating elements and nodes, and of course, parsing XML documents. And as with most APIs, the DOM API has only a couple of methods and properties that you will use in your day-to-day work. Let's see how to use some of these more common properties and methods. Creating a DOM ObjectThe first requirement when you work with the DOM is to instantiate a copy of the XML parser/DOM object in your application. In JavaScript, you use the ActiveXObject function to create the object, as shown in the following code:
var objDocument = new ActiveXObject("MSXML2.DOMDocument");
objDocument.async = false;
The first line instantiates the object and creates a variant called objDocument. This object will contain the document node after you've loaded the document. You can test this by accessing the value of the objDocument.nodeType property. In this case, the property contains the value 9, which maps to the NODE_DOCUMENT node type in Table 5-1. The async property indicates whether the parser should load the entire document before making it available to the programmer. Setting the async property to false ensures that no actions will be taken against a document that is not fully loaded. This is the safe and easy way to program, but it might make your application work more slowly. When set to true (the default setting), the control returns to the caller before the download is finished. You can then use the readyState property to check the status of the download. You can also attach an onreadystatechange handler or connect to the onreadystatechange event to notify you when the ready state changes and the download are complete. For loading large documents, you will probably want to set the async property to true so that you can continue to do other processing while the object loads. In effect, the load is spun off as a separate thread. If you do set the async property to true, you should check the value of the readyState property before you try to access the document. Table 5-2 describes the values of readyState. Table 5-3 describes the two ways to load a stream of XML text into the object. Table 5-2. Values returned by the readyStateproperty.
Table 5-3. Methods for loading an XML document into a DOM object.
The following code loads an XML string into a DOM object:
objDocument.loadXML("<fact verified='2000-01-24'>Movies are " +
"better than books because you can't " +
"spill coffee on them.</fact>");
Once the object is loaded, the parseError object should be checked. A correctly parsed object will return 0, as in this example:
if (objDocument.parseError.errorCode != 0)
{
alert("Error: " + objDocument.parseError.reason +
" on line " + objDocument.parseError.line);
}
Table 5-4 describes the properties of this read-only parseError object. Table 5-4. Properties of the parseError object.
Accessing the documentElementOnce we are satisfied that the document has been loaded properly, we can start to access the contents of the object. We have many ways to do this. The easiest approach is to access the properties of the document element. The following code adds the documentElement.nodeName and the documentElement.text properties to the result string: result += "objDocument.parseError.errorCode: " + objDocument.parseError.errorCode + "\n"; result += "objDocument.documentElement.nodeName: " + objDocument.documentElement.nodeName + "\n"; result += "objDocument.documentElement.text: " + objDocument.documentElement.text + "\n"; alert (result); The nodeName and text properties work on any element node. The objDocument object is a document node. The documentElement property of this node gives us the element node. We can set a variant to this element to make our coding a little simpler: var rootElem = objDocument.documentElement; Attributes are contained in the XMLDOMElement object as a collection of named items. This collection belongs to the element in which the attributes are specified. You can think of the attributes collection as an associative array—that is, a collection of like objects, each keyed by a string rather than an offset index. To access the value of the verified attribute, you need to use the getNamedItem method:
result += "rootElem.attributes.getNamedItem('verified').nodeValue: "
+ rootElem.attributes.getNamedItem("verified").nodeValue + "\n";
It's easy to access the properties of the document element, but what about other elements in the document? They are a bit harder to access, but in the next section I'll show you some methods that help you access elements directly. Getting Items in the DocumentLet's load a more complex document. How about our favorite duck-bar joke from Chapter 4. <?xml version="1.0"?> <joke type="story" keywords="duck bar grapes nails"> <scene number="1"> A duck walks into a bar, goes to the bartender, and says, "Do you have any grapes?" The bartender says, "No, this is a bar, of course we don't have any grapes." </scene> <scene number="2"> The next day, the duck walks into the bar, goes up to the bartender, and says, "Do you have any grapes?" The bartender says, "I told you yesterday, 'no, we don't have any grapes.' If you come in here one more time asking for grapes, I'm going to nail your beak to that bar!" </scene> <scene number="3"> The next day, the duck walks into the bar, goes up to the bartender, and asks, "Do you have any nails?" The bartender says, "No, this is a bar, of course we don't have any nails." Then the duck says, "Do you have any grapes?" </scene> </joke> Assume this document is loaded into our object and the parser returns an errorCode of 0. We can easily access the element and attributes of any element node, as you learned in the previous section, but what about the scene elements? To get them, we can use the childNodes property. Then we can interrogate the scene elements to get the information we need: result += " rootElem.childNodes.item(1).text: " + rootElem.childNodes.item(1).text + "\n" The item property returns the child node. The collection of nodes returned from the childNodes method is zero-based, so the example here returns the text of the second scene in which the bartender threatens our little hero with physical violence. You can access an array of child nodes through the XMLDOMNodeList object. Table 5-5 lists the properties and methods available. Table 5-5. The XMLDOMNodeList interface exposes these properties and methods.
We can use the length property to iterate through the collection one member at a time:
for (i = 0; i < rootElem.childNodes.length; i++)
{
result += " rootElem.childNodes.item(" + i + ").text: "
+ rootElem.childNodes.item(i).text + "\n";
}
To make the preceding code more readable, we can create a new variant that contains the collection of items:
var colScenes = rootElem.childNodes;
for (i = 0; i < colScenes.length; i++)
{
result += "colScenes.item(" + i + ").text: "
+ colScenes.item(i).text + "\n";
}
The colScenes variant is a DOM NodeList object containing all the direct child elements of the root element joke. Using this object makes accessing elements in the DOM very straightforward. What if you want to access one of the scenes, but only if its attribute is a certain value? Here's one approach:
var colScenes = rootElem.childNodes
for (i = 0; i < colScenes.length; i++)
{
if (colScenes.item(i).attributes.getNamedItem("number").nodeValue == "1")
{
result += "colScenes.item(" + i + ").text: "
+ colScenes.item(i).text + "\n"
}
}
It works, but it's pretty clumsy. Let's take a look at an alternative approach for accessing nodes, and then I'll show you an easy way to get a particular node. The Microsoft DOM implements two handy methods for accessing exactly what you want: selectNodes(query) and selectSingleNode(query). These methods are described in Table 5-6. Table 5-6. You can access an element or set of elements by using the selectNodes and selectSingleNode methods in the Microsoft DOM implementation. The query argument contains a pattern defined by the W3C XPath specification.
The query strings passed to the methods in Table 5-6 are Extensible Stylesheet Language (XSL) patterns. I'll discuss XSL patterns in Chapter 6. For now, to access our document, let's take a look at the selectNodes method as an alternative to childNodes:
var colScenes = rootElem.selectNodes("scene")
for (i = 0; i < colScenes.length; i++)
{
result += "colScenes.item(" + i + ").text: "
+ colScenes.item(i).text + "\n"
}
Using the selectNodes method is a little bit of an improvement over using the childNodes method, and it is clearly more self-explanatory. The advantages of the selectNodes method become more obvious once you start delving deeper into a complex document. For example, you can easily access a collection of line items deep in an invoice document by using a complex XSL query pattern such as the following: selectNodes("/invoice/body/items/item") Accessing an item using the childNodes method to drill down through the hierarchy would require quite a bit of code. We would need to iterate through our node list a number of times to select the nodes that we want. To eliminate most of that code, you can use the selectSingleNode method:
result += rootElem.selectSingleNode("scene[@number='2']").text;
The XSL pattern here returns the first scene, which has an attribute (@) named number that has a value of 2. Now you have enough practical knowledge of the DOM to create an application that produces actual results. Exercise: Using the DOM in Visual BasicThe DOM provides an interface into an XML document, allowing you to access the document's contents. Because the Microsoft XML processor is a COM object and contains support for the W3C DOM, you can instantiate it in any tool that can use COM objects. Thus you can use the XML processor in Microsoft Visual Basic, as in the this exercise, and you can use it in a Web browser function written in JavaScript, in a C++ program, or even in a Java application. In this section, you will build a Visual Basic program that uses the DOM to access the contents of an XML document. The program instantiates a DOM object, loads an XML document into the object, creates a collection of elements, and iterates through the collection. You'll find all files—including the Architag XRay XML Editor—on the companion CD. Before you begin this exercise, install the editor from the XRaySetup.exe file. The Microsoft implementation of the W3C DOM is contained in a DLL named msxml2.dll. This object is registered as a COM object as MSXML2.DOMDocument. You can use it wherever you can instantiate a COM object.
Notice that Figure 5-2 is a well-formed XML document containing more than 150 entries. There is one entry for each baseball player who has won baseball's Most Valuable Player award. (For those of you who are counting, there have been three such awards: the Chalmers Award, given from 1911-1914; the League Award, given from 1922-1929; and finally the Baseball Writer's Award, also known as the MVP Award, first given in 1931.) Each entry in this example has a unique identifier on the entry element and seven elements describing the player. Our task is to calculate the batting average of each player that year and display the best and worst averages in a window. Figure 5-2. The file mvp.xml in the Architag XRay XML Editor. Figure 5-3. Microsoft Visual Basic 6.0. Figure 5-4. The Project Explorer window. Figure 5-5. Getting to the BaseballStats form. Figure 5-6. Making the BaseballStats form your startup form. Figure 5-7. Adding the MSXML parser to your project. You should see an environment that looks like Figure 5-8. Figure 5-8. The BaseballStats form.
Set oXMLDoc = CreateObject("MSXML2.DOMDocument")
oXMLDoc.Load ("c:\mvp.xml")
Replace the path c:\ with the path to the mvp.xml file on your machine. Notice that the IntelliSense processor pops up all the available properties and methods when you key in the second line, as shown in Figure 5-9. Figure 5-9. The IntelliSense processor shows all available properties and methods in a pop-up menu. Your subroutine should look like Figure 5-10 when you are finished. Figure 5-10. The Form_Load subroutine. The first line creates an instance of the Microsoft XML parser, "Microsoft.XMLDOM". This parser is a COM object that you can use in any program. Think of XML's processing capabilities as a part of the operating system. The second line loads the MVP document into the object to make it available for scripting. Figure 5-11. The Compute_Click subroutine.
Set Entries = oXMLDoc.selectNodes("//Entry")
As you enter code, you will see that the IntelliSense processor is helping you, as shown in Figure 5-12. Figure 5-12. The IntelliSense processor helps determine the parameters of the selectNode method. The Compute_Click subroutine creates a collection of element nodes containing all 154 baseball player entries. Now we can access this collection one player at a time to find which players have the highest and lowest averages. Once we find the highest and lowest averages, the HighPlayer and LowPlayer objects contain the entire entries for the appropriate players. These objects are interrogated at the end of the subroutine and presented in the text box of the application. Figure 5-13. The main window of the MVP program. Figure 5-14. The results of the MVP program. Event-Driven ModelsThe DOM provides a compact, easy-to-use set of interfaces for processing the contents of an XML document. However, sometimes using the DOM API is not the best approach. Suppose, for example, that you have a huge XML document to process. Because the DOM is a tree-based API, it does not allow you to process any of the document until the entire document is read successfully into the object. Event-driven APIs can report parsing events directly to the calling application, which can save a lot of processing time on large documents. For example, suppose you need to get just the first few elements at the top of a document. Loading the entire document if you want to process only the first few elements wastes cycles. The event-driven model allows you to access the elements as the parser encounters them during processing. You can access an element in this manner whether or not an error lurks below. Remember that with the in-memory DOM API, an error in the last element of the document will render the entire document in error. The tools that use this approach generate events that can be captured by rules. These rules then process the elements as they are encountered in the document. One event-driven model that has been competing for the attention of developers is SAX, the Simple API for XML. SAX was developed by members of the XML-DEV mailing list, hosted by OASIS. In some ways, SAX is considered a competitor of DOM. However, I don't see it that way. Sometimes the DOM is more appropriate for a given situation, and sometimes SAX is more appropriate. In fact, Microsoft has begun to implement some features of SAX in the Microsoft XML Parser (MSXML). You can find out more about SAX at http://www.megginson.com/SAX/. SAX is a great interface, but I want to concentrate on OmniMark, which is a more mature, event-driven, object model programming language. I'll describe OmniMark in detail in Appendix A.
Last Updated: Friday, July 6, 2001 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||