The DOM
As I learned more about the Internet Explorer 5 XML parser, I found that my understanding of the parser depended on my understanding an interface established by the W3C: the Document Object Model (DOM). This model exposes an XML document as a tree structure composed of nodes. The Internet Explorer 5 XML component works with XML documents through four basic interfaces: the Document, the Node, the NodeList, and the NamedNodeMap. Although the foundation of any XML DOM instance is the Document, the Node interface is the one you'll probably find yourself using the most.
A node is a discrete piece of the XML tree structure and can be one of many NodeTypes, listed below. These NodeTypes are enumerated constants; the numeric value of each constant is in parentheses next to the name. NodeTypes that are beyond the scope of this article are noted with an asterisk (*). XML snippets that correspond to each enumerated NodeType are in bold.
NODE_ELEMENT (1) <ELEMENT> … </ELEMENT>
NODE_ATTRIBUTE (2) <ELEMENT ATTRIBUTE="This is a node attribute" />
NODE_TEXT (3) <ELEMENT>This is Node text.</ELEMENT>
NODE_CDATA_SECTION (4) * <ELEMENT><![CDATA[a CDATA section is used to escape a block of text that would otherwise be recognized as markup.]]></ELEMENT>
NODE_ENTITY_REFERENCE (5) * <Element>&ent;</Element>
NODE_ENTITY (6) * <!ENTITY % textgizmo "fontgizmo">
NODE_PROCESSING_INSTRUCTION (7) * <?XML version="1.0" standalone="yes" ?>
NODE_COMMENT (8) * <!-- This is a comment -->
NODE_DOCUMENT (9) <XMLROOTELEMENT> …(entire xml tree) … </XMLROOTELEMENT>
NODE_DOCUMENT_TYPE (10) * <!DOCTYPE INVOICE SYSTEM "http://www.xml.com/product.dtd">
NODE_DOCUMENT_FRAGMENT (11) * <ELEMENT> … (subtree) … </ELEMENT>
NODE_NOTATION (12) * <!NOTATION gif SYSTEM "viewer.exe">
* Not covered in this article. All NodeTypes are listed for reference only.
The NodeTypes that you will encounter most often in XML are NODE_ELEMENT, NODE_ATTRIBUTE, NODE_TEXT, NODE_DOCUMENT, and NODE_CDATA_SECTION. Figure 1 shows another view of some of the preceding NodeTypes, this time in the context of a full XML document.
Figure 1. NodeTypes within an XML document
You Have to Walk Before You Can Fly
After fully understanding the DOM, I rolled up my sleeves and began building my sample application, X-Ray. X-Ray demonstrates four general tasks you might want the to do using the Internet Explorer 5 XML parser:
Walk the XML tree.
Access elements of the DOM.
Add elements and attributes to an .xml file.
Apply XSL pattern strings to select specific nodes within an .xml file.
Even if an XML document arrives on my shores via a transmission over the Web, it will need to be parsed—server-side—into something meaningful for my enterprise. XML is exchangeable, reusable, transformable; the X-Ray application demonstrates how the Internet Explorer 5 XML parser makes it easy to find relevant information, do something useful with it, and alter or add information.
The Bones of the X-Ray Sample
Figure 2 is an overview diagram of the interface for the sample application, X-Ray.
Figure 2. The interface for the X-Ray sample
The sample application allows users to double-click element nodes to walk up a level to the parent node or down a level to the next set of child nodes. Although many XML applications, such as Microsoft's XML Notepad, use a file tree-style interface, this two-window parent/children view of the XML document more closely mirrors the logic used behind the scenes to navigate the DOM.
In the sample X-Ray application, I decided to walk the tree without a net. I decided that I would use the current element node as my context for walking the tree. I would not track how deep under the document root I was or in what order the nodes were traversed to get to the current element. If I had tracked the context within the document, I would have a record of clicks, which would tell me how far currNode is from the documentNode, the parent's ancestry, where in the list of each level's siblings each parent lives, and more. I chose not to track this so as to challenge myself to create a generic code base that could be applied to any XML document.
Being generic is not without a price: When a user selects an element, X-Ray must look to the selected element's parent node in order to determine which node, among a collection of sibling nodes, was actually selected. Nodes, as I just described, are not always element nodes. For example, if the second node in a list of sibling element nodes is selected, it may not be the second node in its parent's childNodes collection. The first node could be a NODE_TEXT type node, the second node may be a NODE_ATTRIBUTE type, the third may be a NODE_ELEMENT, and the fourth may be the second NODE_ELEMENT underneath the parent node. In this case, the second node in the NODE_ELEMENT list would be the fourth node in the parent node's childNodes collection. As you might guess, because the UI only displays element nodes and doesn't keep track of them in relation to all the other types of nodes, I had a bit of extra work to do.
The Code Behind the Interface
The first step to walking the tree is to create a new MSXML DOMDocument. In Visual Basic, this is done like so:
Dim xDoc As New DOMDocument
...
' load the DOMDocument from a file
' Note: You could also use the Load method to load a
' remote XML file using
' xDoc.Load("http://www.XMLHOST.com/XMLFILE.xml")
' however, in order to process remote files you would
' need an additional function to check
' the document's readyState property.
xDoc.Load (GetXML.Text1.Text)
' we are at the root node. refer to the document element
' xDoc should only have one child, the root element
...
Set currNode = xDoc.firstChild
...
Conceptually, at any point in the XML tree, X-Ray keeps track of where it is by means of a single node, currNode. The Internet Explorer 5 XML parser provides enough contextual information from a single node for you to be able to walk the tree, including the answers to the following questions:
What kind of node is currNode? (currNode.nodeType returns the enumerated nodeType)
Does this node have any children? (currNode.hasChildNodes returns a Boolean True or False)
What is the parent node's nodeType? (currNode.parentNode.nodeType returns the enumerated nodeType)
For this sample, these three questions provide enough context to be able to navigate the entire XML tree. If the parent nodeType is a NODE_DOCUMENT (9), the currNode is at the top of the XML tree. If the currNode has children, I store them all in an IXMLDOMNodeList collection—a collection of IXMLDOMNode objects. Because X-Ray displays all the nodeType=Element children in the Child Node window, this collection makes coding a little more convenient because I can access the child nodes directly in the list rather than having to constantly traverse down through the currNode's childNodes NodeList. The same is not true for the currNode's siblings, which is why the application needs to look at the currNode's parent every time a user wants to do something in the left-hand Element window.
Whew! I have to keep track of a big family of parents, siblings, and children. Once you start using the component, you'll see that it's simple to manage these relationships.
The collection of currNode's childNodes, stored in the IXMLDOMNodeList object currNodeList, makes it very simple to move up and down the tree. I do this with two small functions (the meat of each function is in bold):
Private Function WalkDownDOM()
...
Set currNode = currNodeList(ChildNodesList.ListIndex)
Set currNodeList = currNode.childNodes
' call functions to populate the listboxes in the interface.
' Because what was a child is now the parent, X-Ray will make
' this the selected element and populate the ChildNodesList
' accordingly.
FillRootNodesList
FillChildNodesList
End Function
Private Function WalkUpDOM()
...
If currNode.parentNode.nodeType = NODE_DOCUMENT Then
' it's the root
MsgBox ("You are at the top of the XML tree.")
Else
' go up the tree
Set currNode = currNode.parentNode
Set currNodeList = currNode.childNodes
' call function to populate Element listbox
' in the interface. Because no elements are selected,
' the ChildNodesList will not be populated right now.
FillRootNodesList
End If
End Function
Give Me Some Values: Getting Values from the DOM
Another major part of the X-Ray sample involves displaying the contents of the XML tree as you walk through it. This is slightly more complicated than moving the currNode around because we not only want to show the currNode's children, we also want to show the currNode's siblings. The following chart shows the strategy used to get all the siblings of the currNode.
The following function spins through the siblings of the currNode by walking up to the parentNode and then back down through all the parentNode's childNodes. When it comes to a NODE_ELEMENT type node, it adds the node name to the list box called RootNodes (in the UI, this is the left-hand Elements window).
Private Function FillRootNodesList()
...
For i = 0 To currNode.parentNode.childNodes.length - 1
If currNode.parentNode.childNodes(i).nodeType = _
NODE_ELEMENT Then
' don't show non- NODE_ELEMENTS
' add to RootNodes listbox
RootNodes.AddItem _
(currNode.parentNode.childNodes(i).nodeName)
End If
Next
End If
FillChildNodesList
End Function
The Internet Explorer 5 XML parser allows an alternate way of viewing a node's siblings: the nextSibling and previousSibling node properties. For my generic walk through an XML DOMDocument, these properties were not my first choice because I don't know which sibling in the list the currNode is. It may be the first, it may be in the middle, or it may be at the end; for data fidelity I wanted the application to show the nodes in the same sequence as the original document. If I were to use the nextSibling and previousSibling properties, I might have to back up, turn around, and walk to the other end of the Sibling list. Starting from the parentNode is clean and you'll get the nodes in the proper sequence, as long as you are sure to check for the top of the document!
The attributes and values for the selected currNode and the selected childNode in the Child Node window are displayed from the subroutine RootNodes_Click(). The largest task for this routine is to match the selected list item in the UI to the corresponding child node of the parent. Because the parent has more child nodes than the list box displays, a variable called selectedIndex must be set to the index of the selected element node in the parent's childNodes NodeList. Because the application does not explicitly associate the displayed items and the childNodes collection, it must do some fancy footwork at the time of the click.
Private Sub RootNodes_Click()
...
selectedIndex = RootNodes.ListIndex
For p = 0 To currNode.parentNode.childNodes.length - 1
If currNode.parentNode.childNodes(p).nodeType _
<> NODE_ELEMENT Then
' to account for invisible non-Element nodes
selectedIndex = selectedIndex + 1
End If
Next
...
Set currNode = currNode.parentNode.childNodes(selectedIndex)
Set currNodeList = _
currNode.parentNode.childNodes(selectedIndex).childNodes
' Call the function to show childNodesList in the interface
FillChildNodesList
...
End Sub
The preceding subroutine also takes care of displaying the selected node's text, if it exists, and the node's attributes. The overhead of having to go up to the parent to track down which node matches the list box item is a byproduct of the application's design. If all nodeTypes were listed, there would be no need to set the selectedIndex; the RootNodes.ListIndex would suffice. I could have saved myself some trouble if I'd kept an array to keep track of the nodes that are visible and those that are not from the time that the items are added to the list box.
Once we have found the selectedIndex, we can look at the properties of the selected node:
If an XML node has text between two tags, you may access the text with the currNode.Text interface. However, if that node has other child nodes that also contain text between tags, currNode.Text will return all of the child text as well. So, if you simply ask for the SOMETAG element's text in the following XML node, you'd probably want to get "this is some text", but instead you'd get "this is some text and more text":
<SOMETAG>this is some text
<CHILDELEMENT>and more text</CHILDELEMENT>
</SOMETAG>
To display the text only from the top element node (in the preceding example, SOMETAG), walk through all the childNodes of the selectedIndex node. The currNode.Text property gets all NODE_TEXT values together. By finding the specific NODE_TEXT child that is directly associated with currNode, you will retrieve only that node's text; in this example, elemTxt would be set to "this is some text" if currNode is the element node SOMETAG:
For j = 0 To currNode.childNodes.length - 1
If currNode.childNodes(j).nodeType = NODE_TEXT Then
elemTxt = currNode.childNodes(j).Text
End If
Next
Tag! You're It! Adding Tags and Attributes
One of the best things about the new Internet Explorer 5 XML parser is that .xml files can be manipulated directly—no more tacking tags and values together as a long string!
Adding tags is very simple. X-Ray's function fnAddChildElem is long because it must do some validation to make sure the chosen tag name is valid and because it must determine which tag to add the node to. Once you've done that, it's a piece of cake: Create a new node, append it to another NODE_ELEMENT, and you're done!
A modification of the X-Ray code shows the basic steps in creating a new node:
' Assumes: xDoc is an existing MSXML.DOMDocument
' currNode is a NODE_ELEMENT inside xDoc
' Append a new NODE_ELEMENT to currNode
Dim node1 As IXMLDOMNode
' This syntax--"element" instead of NODE_ELEMENT --
' works the same as the line after:
'---> Set node1 = xDoc.createNode("element", "NEWNODENAME", "")
Set node1 = xDoc.createNode(NODE_ELEMENT, "NEWNODENAME", "")
Set node1 = currNode.appendChild(node1)
Although the calling document, xDoc, does the job of creating the node, the node exists in the document without being attached to any other node—it has no parent. Once the node is appended to currNode using currNode's appendChild method, the node exists in a context inside xDoc.
Adding attributes is just as simple:
Create a new attribute.
Give the attribute some text.
Set the attribute using an existing node's setNamedItem method.
This simplified code shows how to add a new attribute to an existing element node:
' Assumes: xDoc is an existing MSXML.DOMDocument
' currNode is a NODE_ELEMENT inside xDoc
' Add a new attribute to currNode
Dim node1 As IXMLDOMAttribute
Dim node2 As IXMLDOMNode
' Create a new attribute in the document
Set node2 = xDoc.createAttribute("NewAttrName")
node2.Text = "Attribute value"
' Associate that attribute with an ELEMENT_NODE
Set node1 = currNode.Attributes.setNamedItem(node2)
Now currNode has a new attribute that looks like this: NewAttrName="Attribute value".
When you've added all your tags and attributes, you can save your .xml file back to disk using this method:
xDoc.save (destination)
Querying the DOM
The X-Ray sample uses the IXMLDOMDocument getElementsByTagName() method to return a collection of nodes, in the form of an IXMLDOMNodeList, filtered by tag name. The getElementsByTagName method uses XSL patterns for queries, so you can either search by a tag name, such as "PRODUCT," or you may do scoped queries, such as "PRODUCT/FEATURES."
For example, if a node named "CLASS" was being used to describe both COMPANY and PRODUCT classes, a generic query for "CLASS" wouldn't suffice. Consider this example:
Both the <COMPANY> and the <PRODUCT> contain <CLASS> elements. XSL pattern queries allow you to search for just the CLASS you want.
XML Is for Developers On and Off the Web
Armed with my trusty Internet Explorer 5 XML parser, I can share my data with others in a standard way. I can write generic applications that convert data into presentations. I can transform data into variable values for my server-side applications. I can even transform the data into different XML structures. XML is a meta-language that may be used for transferring data, but the real magic happens when the data contained inside the XML is morphed into something useful. That magic can happen in an Internet Explorer 5 browser or through an Internet Explorer 5 XML-enabled server-side application.
Steve Land is Senior Developer, Commerce Initiatives Lead at Corbis (www.corbis.com), where he develops commerce Web sites. His head is filled with metadata.