Manipulating Documents: The Document Object Model (DOM)
You will learn about the following in this chapter:
The various DOM Recommendations
The difference between elements and nodes
The DOM API and what it represents
Navigating the DOM tree
Creating a DOM document from a file
Creating and manipulating DOM data
Saving a DOM document to a file
When new XML users go to the W3C site (http://www.w3.org) and look up the Document Object Model (DOM), very often they're overwhelmed from the start. This is not surprising; there are three different levels, each of which has separate recommendations (or modules). So what is it that you're actually supposed to do with this stuff?
Simply put, the Document Object Model is a way of looking at a tree of XML data, and a group of APIs for reading and manipulating it. These APIs are common among DOM-compliant applications, so what works in one environment should work in another.
Your software determines the version of DOM available to you. At the time of this writing, most implementations (but not all) are at Level 2.0, with some support for Level 3.0. In most cases, you'll use the Core module, but there are situations in which each of the other modules will be useful.
This chapter provides an overview of all the recommendations, and focuses on the DOM Level 2.0 Core. In Chapter 4, "Advanced DOM Techniques," and Chapter 13, "XML in the Browser: XHTML," we'll look at some of the other modules.
What Is the Document Object Model?
The Document Object Model actually sprang out of browsers scripting HTML pages. Back when this sort of scripting (now known as DHTML, or Dynamic HTML) was new, each browser implemented it in its own way. For example, thinking of a form as an object was a fairly obvious choice, but how would you represent the value of a form input called username? Would it be one of the following?
document.forms.myForm.username document.forms.myForm.username.value document.forms["myform"].username document.forms.elements.value
The list wasn't exactly endless, and some of these choices were functionally equivalent, but there were enough differences in what was supported between different browsers (and even versions of the same browser) to cause a nightmare for Web developers who needed to make sure their pages worked everywhere that scripting was available.
The result was the development of the Document Object Model, a (sort-of) standard way of looking at HTML. This unofficial version, based on version 3.x browsers, is known as DOM Level 0.
The first official version, DOM Level 1.0, created a basic structure of nodes, elements, and attributes that allowed navigation around a document. Even from the beginning, however, everyone knew that this was just a starting point, and since then DOM Level 2.0, which incorporates more functionality, has been (mostly) approved, and DOM Level 3.0, which takes things a step further, is in the works.
As I said before, it's easy to get confused with all these recommendations. Let's start simple, with the Core Recommendation.
A Set of Interfaces
In some ways, it's misleading to say that the Document Object Model defines a set of objects. Actually, it defines a set of interfaces to objects. An interface is a set of the following:
Properties or data that an object holds
Methods or actions that an object can take
Parameters that you can pass to an object or method
The type of object that a method returns, if any
A programmer can implement these interfaces by creating objects that conform to them. This way, when another programmer wants to use these objects, he or she will know how, because they're based on these well-defined interfaces.
For example, the DOM Core Recommendation defines an interface called Element, with a method called getAttribute(). The interface specifies that when the getAttribute() method is called with the name of an attribute, it should return the value of the attribute. This means that if I create an object based on this Element interface, you can use it secure in the knowledge that if you want the value of an attribute, you can use the getAttribute() method.
Because the interfaces are standard, the implementation itself takes on less importance. All you care about is that if you call getAttribute("month") on the birthday element, you're going to get the value of the month attribute. This portability is the purpose of the Document Object Model.
The Importance of Nodes
When you come right down to it, all of DOM is based on the node. Documents, elements, attributes, text, and even processing instructions and comments are all special cases of the Node interface.
For example, the XML document
<?xml version="1.0"?> <friend> <handle degree="close">Harold</handle> </friend>
actually represents at least 8 nodes of different types:
one document node
two element nodes (friend and handle)
four text nodes (close, Harold, and the line feeds before and after the name element)
one attribute node (degree)
These nodes are arranged in parent-child relationships, as shown in Figure 3.1.
Figure 3.1 The parent-child relationships of a simple document.
Notice that the elements and the text they contain are separate nodes; the element node acts as the parent and the text node as the child, as indicated by the arrows. Notice also that the attribute node is not a child of the element node.
The Document Object Model defines 12 different node types, each of which implements the Node interface, and each of which adds methods that are peculiar to itself.
For example, the Node interface specifies the getNodeValue() method, because it's applicable to most node types. The getTarget() method, on the other hand, applies only to processing instructions, and as such is part of the ProcessingInstruction interface.
The Document Object Model specifies the following interfaces:
NodeThe overall type that applies to everything. The Node interface includes methods for getting information about the Node, such as getParentNode() and getNodeName(), and for manipulating the structure of a document or individual node, such as insertBefore() and removeChild(). It also defines numeric values for each node type, enabling you to determine what type of node you're dealing with even if the object type itself is Node.
DocumentThe Node that begets almost all other types of nodes. While the Node interface carries the methods that are used to move nodes around, the Document interface carries the methods that are used to actually create them, such as createElement() and createTextNode().
ElementThe type of node that can be associated with Attr nodes. This interface contains convenience methods such as getElementsByTagName().
AttrThis node, whose name is short for attribute, can have either Text nodes or EntityReferences as children.
TextThis is a string with no markup. This means that a mixed-content text node is broken up into separate Text and Element nodes.
CDATASectionThis node type "hides" markup. Unlike a Text node, markup within the value will not cause a separate Element node to be created.
DocumentTypeThis node type contains all the information that would normally be contained in the internal subset of a DTD as a string value. It also contains the name of the specified root element and any system or public identifiers that point to external subsets of the DTD.
Comment, Notation, Entity, EntityReference, and ProcessingInstructionThese nodes simply model their namesakes, providing the appropriate information about themselves.
We'll deal with entities, entity references, and notations in detail in Chapter 7, "Document Type Definitions (DTDs)."
Some DOM interfaces don't correspond to actual node types within the document, but are provided to make your life easier when you're working with documents:
DocumentFragmentThis implementation of the Node interface doesn't actually have any of its own methods at all, and instead acts as a sort of mini-document or container for other groups of nodes you may want to move around. For example, if you insert a DocumentFragment into a document, only the child nodes are actually inserted.
DOMImplementationThis interface provides a means to determine whether certain actions are supported using the hasFeature() method. It also allows you to create Documents and DocumentTypes.
DOMException and ExceptionCodeThese interfaces provide a way for the application to tell you that something has gone wrong, and what it is.
NodeList and NamedNodeMapThese two utility interfaces are essential in actually working with a DOM document, as you'll see when we start building the application. NodeList provides an ordered list of results, such as the children of a node, and NamedNodeMap provides a list that is unordered but accessible by name, such as the attributes of an element.
The Different Recommendations
All these interfaces are part of the DOM Level 1.0 Recommendation. Minor changes were made to Level 1.0 to create the DOM Level 2.0 Core Recommendation.
DOM Level 2.0 includes five other module recommendations, each of which has a specific purpose:
DOM Level 2.0 Traversal and RangeThis module includes two new interfaces, NodeIterator and TreeWalker, that provide alternative means for navigating the document. It also defines a means for referring to a particular portion of a document. (We'll talk about ranges in Chapter 14, "XML Linking Language (XLink)."
DOM Level 2.0 Style SheetsThis module specifies interfaces that deal with cascading style sheets and the data they contain.
DOM Level 2.0 ViewsThis module provides a way to distinguish between different versions of a document. For example, a cascading style sheet may change the data within a document, creating a different view. (As of the time of this writing, this module has been removed from DOM Level 3.0.)
DOM Level 2.0 EventsThis module stems from work on browser scripting, and provides a way to determine the behavior of objects when some action, such as a mouse click, affects them, their ancestors, or their descendants.
DOM Level 3.0, which is still in the Working Draft stage at the time of this writing, includes revisions of the Core and Events modules, as well as two new modules:
DOM Level 3.0 Load and SaveThis module attempts to actually detail the loading and saving of documents (which is, as of Level 2.0, still up to the individual implementers).
DOM Level 3.0 ValidationThis module provides a way to validate an existing Document object against a specific associated grammar, and to require that it remain valid throughout its existence.
DOM Level 3.0 XPathThis module allows greater flexibility in the use of XPath expressions to locate and specify nodes within a document. It's not an update of XPath, but rather a way to use XPath 1.0 with a DOM document.
So with all these different versions and implementations and modules, how do you know whether a particular method or interface is available for your application?
The answer lies in the DOMImplementation interface and its hasFeature() method. Each of these modules has a standard name and version, so to determine whether the TreeWalker interface is available, you can check the following value:
We'll see an example of this in the next section, once we've actually created a DOM document to work with.