Chapter 5. Using DOM

CONTENTS

IN THIS CHAPTER

  •  What Is the DOM?
  •  Nodes and Tree Structure
  •  Programming with DOM
  •  JDOM, dom4j, and Deferred DOM
  •  Summary

XML is a great way to represent data in certain situations. However, there needs to be a way in which to access this data and document structure in an intuitive way. Without this access, XML is virtually useless. The two most prevalent APIs for accessing this data are the DOM and SAX. DOM will be covered here, and SAX will be covered in Chapter 6.

This chapter will discuss the Document Object Model (DOM), which is an in-memory representation of a well-formed document. After a walk-through of the basics of DOM, we'll show examples of the various ways that the DOM structure can be traversed and manipulated. We'll move, delete, and copy parts of documents. After that, we'll expand an example of creating a document using DOM programmatically from scratch to import parts of other DOM documents. Toward the end of this chapter, we will demonstrate an example of traversal methods. Finally, we will present an introduction to ranges and their uses.

What Is the DOM?

The Document Object Model (DOM) is a garden-variety tree structure consisting of a hierarchy of nodes stored in memory. This structure is used to represent the content and model of an XML document. Once a document is parsed using DOM, that tree-like structure will exist in memory, and can then be processed in various ways.

The DOM is a standard issued by the W3C, just like the XML specification. This specification is unrelated to any one programming language, and therefore isn't designed for use with Java specifically. Instead, various bindings of the DOM specification exist for the various programming languages that implement it.

The DOM binding for Java provides APIs that facilitate the manipulation of nodes. Through these manipulations, nodes may be created, modified, deleted, selected, or even removed from one document and added to another.

In this chapter, DOM will be used specifically with XML documents. However, DOM can represent other types of documents, such as HTML.

Strengths of DOM

DOM is the most prevalently used API for processing XML documents. The reasons for this include the following:

  • The tree structure created by DOM is easy to use once you understand its hierarchy and structure.

  • The entire content of any document is always available. This means that repeated access and movement through the document is relatively simple.

  • The selection of nodes, located anywhere within the document structure, is simple.

  • Once you have established an understanding of DOM in one language, using it in another language is very easy. The methods and structure are the same within any language.

  • In the Java language binding, all node types are the same. This enables each node to be accessed and coded in the same manner, with only minor exceptions.

Weaknesses of DOM

Although DOM has many advantages, many times it isn't the perfect choice for processing XML documents. Reasons for this include the following:

  • DOM requires memory proportional to the size and complexity of the XML document being parsed. This is because the entire document is available in memory during processing.

  • Before an XML document is usable through the DOM, the entire document must be parsed and added to the DOM model in memory.

  • Some ways in which DOM is implemented in Java can be confusing to a Java programmer. This is because DOM's specification is language neutral. As a result, some Java language features, such as method overloading, are not utilized. This is because other programming languages that implement DOM do not have this feature.

Nodes and Tree Structure

A tree structure in memory is the foundation of DOM. The DOM tree is composed of nodes, each of which represents a part of the parsed XML document. The arrangement and types of the nodes form the tree structure that represents a parsed document.

Nodes are represented through the org.w3c.dom.Node interface. Other interfaces are extended from this interface to more specifically represent the various types of nodes required to describe an XML document. An example of this is the org.w3c.dom.Element interface extending the node implementation. This allows methods to be defined that are more relevant to each specific type of node.

For each component of an XML document, a node of the correct type is created and properly placed in the hierarchy in memory. The most commonly recognized node is one of type Element. Descending from each Element node are all the nodes that combine to make up the contents of that element. If the element only contains text data, there will only be a single child Text node descending from the Element node.

By using the functionality available within the base Node interface, all nodes can be treated in a similar way. Also, methods such as getChildNodes() and getParentNode(), which are part of the Node interface, make navigation around the DOM tree easy.

Each different node type also contains methods that are directly relevant to that specific node type. For example, the Attr (attribute) node contains methods relevant only to attributes, such as getOwnerElement().

The Document Node

When a document is parsed, the entire document is available to be processed through a Document node. Descending from this node are the contents of the entire document represented as nodes. This includes one Element node that directly descends from the Document node and represents the root element of the XML document structure. If the document has a DTD associated with it then it will also have a single DocumentType node that represents the DOCTYPE component. Also, if processing instructions or comments exist at this level, they too will be modeled as either a ProcessingInstruction or Comment node, respectively, descending directly from the Document node.

If an image of an XML document comes to mind, you may be wondering what happened to the document declaration. It tells the version number and encoding, among other things. Remember, this is always the first line of an XML document. Shouldn't this node be one that directly descends from the Document node?

It would seem logical that the document declaration would be represented as a node descending from the Document node and would contain Attribute nodes for each of the properties therein. However, this is not the case.

The document declaration is not a processing instruction, and as such, the information contained therein should not be available outside the parser. (At least, this is the logic that the W3C used.) On some levels this makes sense. However, to anyone who has ever had to parse, process, and serialize an XML document, this is a big problem. The encoding attribute value is very important when the document needs to be serialized or processed in some manner specific to the correct encoding.

Currently, the W3C is working on the DOM Level 3 specification, which resolves these issues. The solution is the creation of methods such as getEncoding() and getVersion() invoked on the Document node. While this does not conform to the strict node and tree structure, it will enable access to this vital information when it is implemented.

Many other new methods have been created in the new DOM Level 3 specification besides those just mentioned. At the time of this writing, this specification hasn't been ratified yet, and as such, these methods are not yet implemented in the various language bindings. They will be available soon enough, so keep your eyes open.

In the XML document found in Listing 5.1 there are elements, text data, attributes, processing instructions, and entity references. Each of these components will become a single node, descending from the appropriate parent. Reproduce this file and save it as webapps\xmlbook\chapter5\House.xml. This XML document will be used throughout this chapter for examples.

Listing 5.1 House.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE HOUSE [ <!ENTITY newroom "project room"> ]> <HOUSE>     <UPSTAIRS sqfeet="200">         <ROOM>bedroom</ROOM>     </UPSTAIRS>     <?color blue?>     <DOWNSTAIRS sqfeet="900">         <ROOM>bathroom</ROOM>         <ROOM>kitchen</ROOM>         <ROOM>living <![CDATA[ room ]] ></ROOM>         <ROOM>&newroom;</ROOM>     </DOWNSTAIRS> </HOUSE>

Figure 5.1 shows a representation of the DOM tree structure created in Listing 5.1.

Figure 5.1. DOM representation of House.xml.

graphics/05fig01.gif

Note that the tree structure is followed in every sense. Text nodes are children of Element nodes when an element has text content. Element nodes are children of Element nodes when elements are nested. Instead of there being methods invoked on an Element node that return the contents found therein, a Text node contains that text and descends from it. This is true of all other components found within elements, such as CDATA sections, entity references, processing instructions, and comments.

Notice also in Figure 5.1 that the entity reference is descended from the element in which it is contained. This makes sense considering that text data of an element is also descended from its element. However, there is another node descending from the entity reference node. This is a text node that contains the entity character data. Again, the tree structure is enforced in every sense.

This rather strict tree structure has some benefits. It enables movement within the tree to be relatively simple. One reason for this is that moving between nodes requires the same method calls no matter the node types. This makes recursive tree-walking methods simple to write because there aren't many special cases. Each movement between nodes can be made using the same methods, regardless of node types.

Although specific interfaces exist for the different node types, there are still some things to be aware of when using the core Node methods. One of these is the seemingly benign method getNodeName() found in the Node interface. For nodes whose type is one of Text, CDATASection, or Comment, this will return various strings that are garbage, depending upon the parser. The Xerces parser will return the literal #text when the getNodeName() method is invoked on a Text node. On the other hand, it will return the target of a processing instruction or the tag name of an element. The point is, you shouldn't assume that these convenience methods of the core Node interface will always return what's expected. What they return will depend on the type of node and the parser implementation being used.

Programming with DOM

In this first DOM example, an XML document will be parsed into a DOM representation. Then the code will iterate through the nodes of the tree structure and output information regarding each node. This iterating method will be embedded within the JSP page to make it easy to alter in this chapter.

NOTE

Our examples serialize DOMs to the output. These examples are only intended to demonstrate the workings of DOM in Java. If you need a production-grade serializer, check out the XMLSerializer class found in the Xerces parser.

The complete JSP page is shown in Listing 5.2 and should be saved as webapps\xmlbook\chapter5\DOMExample.jsp.

Listing 5.2 DOMExample.jsp
<%@ page   import="org.w3c.dom.*,           org.apache.xerces.parsers.DOMParser" %> <html> <head><title>DOM Parser Example</title></head> <body> <% try{     DOMParser parser = new DOMParser();     String  path = request.getServletPath();     path = path.substring(0,path.indexOf("DOMExample.jsp"));     String  xml  = application.getRealPath(path + "House.xml");     parser.parse(xml);     Document doc = parser.getDocument();    listNodes(doc.getChildNodes(), out, ""); } catch (Exception e){     out.print("<br/><br/><font color=\"red\">There was an error<BR>");     out.print (e.toString() + "</font>"); } %> </body> </html>     <%!     private void listNodes(NodeList nlist, JspWriter out, String spacer)     throws Exception{         try{             for(int i = 0; i < nlist.getLength(); i++){                 out.print(spacer + "[");                 out.print(" " + nlist.item(i).getNodeName());                 out.print(" type:" + nlist.item(i).getNodeType());                 out.print("<font color=\"green\">&nbsp;"                    + nlist.item(i).getNodeValue() + "</font> ");                 out.print("]<BR>");                 if(nlist.item(i).getChildNodes().getLength() > 0)                     listNodes(nlist.item(i).getChildNodes(), out,                         spacer + "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");                 //if the node is of type Element then create closing string                 if(nlist.item(i).getNodeType() == 1)                    out.print(spacer + "[/" + nlist.item(i).getNodeName()                       + "]<BR>");             }         }         catch(Exception e){out.print( "<font color=\"red\">" +             e.toString() + "</font>");}     }     %>

The JSP page begins with the import statements necessary for our code and then some static HTML to create a page title. Next, a try block is used to catch any and all Exceptions created.

The only DOM-specific exception that can be thrown while using the DOM methods in these examples is DOMException. When an operation is impossible to perform for logical reasons or due to loss of data, this exception will be thrown. In general, however, DOM methods return specific error values in ordinary processing situations, such as NullPointerException when trying to process a node that doesn't exist.

<%@ page   import="org.w3c.dom.*,           org.apache.xerces.parsers.DOMParser" %> <html> <head><title>DOM Parser Example</title></head> <body> <% try{

Next, a DOMParser instance is created and into it are passed the path and filename of the XML document to be parsed. This instance will use the Xerces parser by providing the Xerces class. If you haven't yet placed the Xerces.jar file in the lib directory found in the root of the Tomcat installation, place it there now and restart the server.

DOMParser parser = new DOMParser(); String  path = request.getServletPath(); path = path.substring(0,path.indexOf("DOMExample.jsp")); String  xml  = application.getRealPath(path + "House.xml");

After the parsing is complete, the code obtains a reference to the Document node through the getDocument() method invoked on the parser. We now have access to the DOM for this XML file.

It may seem as though it would be better for the parser to return the DOM reference upon the invocation of the parse() method. However, this returns void so that it can be consistent when parsing with SAX, as described in the next chapter. This means that the code should explicitly get the Document node from the parser with the getDocument() method, as shown here:

parser.parse(xml); Document doc = parser.getDocument();

With access to the DOM model, we take the children of the Document node and pass them into our method. This will begin the traversal of the tree, and the outputting of the information contained in each processed node.

The method being used is called listNodes() and is shown in Listing 5.2. This method will indent the output strings to demonstrate the current depth of the nodes being processed in the DOM hierarchy. Due to this, the parameters used are a NodeList, the out that is of type javax.servlet.jsp.JspWriter, and the String that will be used to determine the indentation of those Nodes passed in.

After each of the Nodes found in the NodeList argument is processed, listNodes() is again invoked on the children of the node that was just processed. The listNodes() method recursively walks the DOM tree and enables the output of properly indented strings for each node.

This method begins by performing some actions within a try block. This permits the catching and handling of any exceptions that may occur. However, this chapter only briefly discusses exceptions with regards to the DOM. The catch block of the try block outputs the Exception in string format and colors it red.

The next step is to use a for loop to step through the NodeList. The NodeList is a creation of the W3C specification, and as such does not take advantage of the language-specific implementations for collections. Due to this, iterators cannot be used on NodeLists. Instead, the methods item() and getLength() must be used to step through the NodeList.

Once each node has been accessed, the code outputs the results of getNodeName(), getNodeType(), and getNodeValue(). These return a String, a short, and a String, respectively. What should the value of an Element node be? Or what about the name of a Text node? In the case of the value of an Element node, a null is returned due to its having no value. The String returned when invoking the getNodeName() method on a Text node is the unexpected string literal of #text mentioned earlier.

After the preceding output and some HTML markup, we continue by invoking the getChildNodes() method on the node being processed. This will return the NodeList that contains all the children of this node. If there are child nodes as determined by getLength() then code will take the NodeList and re-call listNodes() on them. In this case, however, whitespace is going to be added to the spaces parameter to create a deeper indention than the parent node has.

Next, a string emulating a closing tag will be output to show that the descendants of an Element node have all been processed. By obtaining the node type through getNodeType() we can determine whether it's an Element node, and thus output a closing string that emulates the closing tag of an XML document.

Now it's time to see the output of DOMExample.jsp and House.xml. It will have the same structure as Figure 5.1, but won't be as visually oriented, as Figure 5.2 shows.

Figure 5.2. Output of DOMExample.jsp and House.xml.

graphics/05fig02.gif

The first thing to examine is the difference between the output shown in Figures 5.1 and 5.2. Notice how in Figure 5.2 there are a number of additional Text nodes that have no string value. These extra Text nodes are a result of the whitespace found between elements. The parser has no way of knowing whether whitespace is text data or just blank space caused by indentations and such. Due to this, the parser will return each of these blocks of space as a Text node.

Attributes

We're missing a very large and important part of XML: The attributes of the XML document are nowhere to be seen. This seems strange considering the Attr interface is based on the Node interface, just like all the other types of nodes. But attribute nodes don't show up when you're invoking the getChildNodes() of any type of nodes, including Element nodes.

Attribute nodes don't follow the strict tree structure that the rest of DOM follows. They are not considered children of the element in which they are found. Instead, it is almost as if they are at the same level as the Element node. The relationship isn't clear. For this reason, attributes are only accessible from their parent Element node with the getAttributes(), getAttribute(), and getAttributeNode() methods. These methods return a NamedNodeMap, a String with the value of the attribute, and an Attr node, respectively.

A NamedNodeMap is a collection of nodes unrelated to NodeList. The difference between these two sets of nodes has to do with the differences between child elements and attributes. The order in which attributes are found within an element makes no difference, whereas order is important with element siblings. Another difference between these two collections of nodes has to do with the distinctness of attributes. There can never be two attributes with the same name within an element. However, two child elements that have the same name can exist. As a result, NodeLists maintain order among selected Nodes and can have Nodes that have all the same properties, while NamedNodeMaps do not.

We will now alter the code for the listNodes method from Listing 5.2 so that it will output attributes. The new JSP is shown in Listing 5.3. (Additions and changes from Listing 5.2 are noted in boldface.) Save this new file as webapps\xmlbook\chapter5\DOMExampleAttr.jsp.

Listing 5.3 DOMExampleAttr.jsp
<%@ page   import="org.w3c.dom.*,           org.apache.xerces.parsers.DOMParser" %> <html> <head><title>DOM Parser Example With Attributes</title></head> <body> <% try{     DOMParser parser = new DOMParser();     String  path = request.getServletPath();     path = path.substring(0,path.indexOf("DOMExampleAttr.jsp"));     String  xml  = application.getRealPath(path + "House.xml");     parser.parse(xml);     Document doc = parser.getDocument();     listNodes(doc.getChildNodes(), out, ""); } catch (Exception e){     out.print("<br/><br/><font color=\"red\">There was an error<BR>");     out.print (e.toString() + "</font>"); } %> </body> </html>     <%!     private void listNodes(NodeList nlist, JspWriter out, String spacer)     throws Exception{         try{             for(int i = 0; i < nlist.getLength(); i++){                 out.print(spacer + "[");                 out.print(" " + nlist.item(i).getNodeName());                 out.print(" type:" + nlist.item(i).getNodeType());                 out.print("<font color=\"green\">&nbsp;"                    + nlist.item(i).getNodeValue() + "</font> ");                 if(nlist.item(i).hasAttributes()){                     NamedNodeMap attributes = nlist.item(i).getAttributes();                     for(int j = 0; j < attributes.getLength(); j++){                         out.print(" {");                         out.print(" name:" + attributes.item(j).getNodeName());                         out.print(" value:" + attributes.item(j).getNodeValue());                         out.print(" } ");                     }                 }                out.print("]<BR>");                if(nlist.item(i).getChildNodes().getLength() > 0)                    listNodes(nlist.item(i).getChildNodes(), out,                        spacer + "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");                //if the node is of type Element then create closing string                if(nlist.item(i).getNodeType() == 1)                   out.print(spacer + "[/" + nlist.item(i).getNodeName()                       + "]<BR>");            }        }        catch(Exception e){out.print( "<font color=\"red\">" +            e.toString() + "</font>");}    }    %>

The modified code in Listing 5.3 checks to see whether the node being processed has attributes with the appropriately named method hasAttributes(). If it does, the code obtains NamedNodeMap by invoking the getAttributes() method. Once these attribute nodes are obtained, the for loop iterates through them and outputs their name and value. The getNodeName() and getNodeValue() methods are from the Node interface. The Attr-specific methods not used in this example that return the same results are named getName() and getValue(). To use the Attr-specific methods, the nodes would have to be cast to Attr, because a NamedNodeMap is a collection of Nodes, not Attrs. This example uses the more generic methods of the Node interface.

Now that attributes have been taken care of, let's look at the new output, shown in Figure 5.3. The attributes from the House.xml document are output just where they should be.

Figure 5.3. Output from addition of attribute-handling code.

graphics/05fig03.gif

Namespaces

Programmatically accessing any other part of an XML document is the same as in the previous examples. For instance, you can obtain namespace information through the use of the following methods found in the Node interface:

  • getNamespaceURI() returns the namespace in which the element resides. This only returns a String when the node is of type Element and the namespace has been declared. This means that either the default namespace or the namespace in which this element resides has been declared. Otherwise, this method returns null. In essence, if the namespace attribute that describes the element is not present, this will return null; otherwise, expect the value of the namespace attribute.

  • getLocalName() returns the Element name as a String with the namespace prefix removed. When no namespace is associated with an element, it returns the same String as getNodeName().

  • getPrefix() returns the namespace prefix found on an Element name. The method returns null when no prefix exists.

For example, let's look at namespaces using the following small XML document:

<?xml version="1.0" encoding="UTF-8"?>     <BLOCK xmlns="http://www.default.com" xmlns:xo="http://www.xo.com">          <xo:HOUSE/>    </BLOCK>

When this is invoked on the Element node representing the BLOCK element, the following occur:

  • getNodeName() returns the string BLOCK

  • getLocalName() returns the string BLOCK

  • getNamespaceURI() returns http://www.default.com

  • getPrefix() returns null

However, when these methods are used on the <xo:HOUSE/> Element node, they return the following:

  • getNodeName() returns xo:HOUSE

  • getLocalName() returns HOUSE

  • getNamespaceURI() returns http://www.xo.com

  • getPrefix() returns xo

If the namespace declarations are removed

<?xml version="1.0" encoding="UTF-8"?>     <BLOCK>          <xo:HOUSE/>    </BLOCK>

the preceding method results would be the same except for getNamespaceURI(), which would return null in both cases.

Now that we've discussed the behavior of these methods on Element nodes, let's touch on Attr (attribute) nodes. The return values of the methods are almost the same. The only difference is that even when a default namespace has been declared, getNamespaceURI() will not return that namespace when invoked on an attribute unless the attribute has been explicitly placed in that namespace (that is, it has a prefix). If an attribute has not been explicitly associated with a namespace, it's not considered part of that namespace even if a default namespace has been declared, as in the following:

xmlns="http://www.default.com"

The results of namespace methods differ depending on whether they are invoked on attribute nodes or Element nodes. Don't assume the same behaviors.

Removing a Node

Now that you've gotten the idea behind the methods available through the DOM implementation, let's modify a document. Continuing with the previous example, let's remove the UPSTAIRS Element node and child nodes.

We will do this in the next example, shown in Listing 5.4, with the addition of only four lines of code. (Additions from Listing 5.3 are noted in boldface type.) Save this file as webapps/xmlbook/chapter5/DOMExampleRemove.jsp.

Listing 5.4 DOMExampleRemove.jsp with Additional Node Selection
<%@ page   import="org.w3c.dom.*,           org.apache.xerces.parsers.DOMParser" %> <html> <head><title>Example Removing an Element with DOM Parser</title></head> <body> <% try{     DOMParser parser = new DOMParser();     String  path = request.getServletPath();     path = path.substring(0,path.indexOf("DOMExampleRemove.jsp"));     String  xml  = application.getRealPath(path + "House.xml");     parser.parse(xml);     Document doc = parser.getDocument();     NodeList list = doc.getElementsByTagName("DOWNSTAIRS");     Node downstairs = list.item(0);     downstairs.getParentNode().removeChild(downstairs);     listNodes(doc.getChildNodes(), out, ""); } catch (Exception e){     out.print("<br/><br/><font color=\"red\">There was an error<BR>");     out.print (e.toString() + "</font>"); } %> </body> </html>     <%!     private void listNodes(NodeList nlist, JspWriter out, String spacer)     throws Exception{         try{             for(int i = 0; i < nlist.getLength(); i++){                 out.print(spacer + "[");                 out.print(" " + nlist.item(i).getNodeName());                 out.print(" type:" + nlist.item(i).getNodeType());                 out.print("<font color=\"green\">&nbsp;"                    + nlist.item(i).getNodeValue() + "</font> ");             if(nlist.item(i).hasAttributes()){                 NamedNodeMap attributes = nlist.item(i).getAttributes();                 for(int j = 0; j < attributes.getLength(); j++){                     out.print(" {");                     out.print(" name:" + attributes.item(j).getNodeName());                     out.print(" value:" + attributes.item(j).getNodeValue());                     out.print(" } ");                 }             }                out.print("]<BR>");                if(nlist.item(i).getChildNodes().getLength() > 0)                    listNodes(nlist.item(i).getChildNodes(), out,                        spacer + "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");                //if the node is of type Element then create closing string                if(nlist.item(i).getNodeType() == 1)                   out.print(spacer + "[/" + nlist.item(i).getNodeName()                       + "]<BR>");            }        }        catch(Exception e){out.print( "<font color=\"red\">" +            e.toString() + "</font>");}    }    %>

Any node can be removed using the removeChild() method. However, this method requires a reference of the node to be removed. This requires some programmatic traversal of the tree.

This example begins by obtaining a NodeList of all elements with the tag name DOWNSTAIRS, as shown in the boldface code in Listing 5.4. The XML document House.xml from Listing 5.1 only has one of these elements. You can obtain the reference to this particular Node by selecting the first item (index 0) in the NodeList returned from the getElementsByTagName() method.

Once you've obtained this reference, it is very simple to remove this node. All that you must do is invoke the method removeChild() on the parent node of the node being removed. A convenient method found in the base Node interface is getParentNode(). This method permits simple access to the parent node of any node that can be referenced. You can use this method to access the parent node and thus invoke the removeNode() method on the parent node of the node that is being removed, as in the last of the boldface lines in Listing 5.4.

The results are shown in Figure 5.4. Note that when this DOWNSTAIRS node is removed, all child nodes are also removed. This makes perfect sense and demonstrates the ease with which nodes can be removed using DOM.

Figure 5.4. Results of removing the DOWNSTAIRS element subtree.

graphics/05fig04.gif

Moving Nodes

Now that we've seen how easy it is to remove nodes, let's move one around. In the following example, the DOWNSTAIRS element will be removed and placed as a child descending from the UPSTAIRS element.

The DOM provides us with a single method that will both remove a node and append it. This method is called appendChild() and takes as a parameter a reference to the Node being repositioned.

The differences between the previous JSP, which removes a node, and this listing appear as boldface text. Save this altered file, shown in Listing 5.5, as webapps/xmlbook/chapter5/DOMExampleAppend.jsp.

Listing 5.5 DOMExampleAppend.jsp
<%@ page   import="org.w3c.dom.*,           org.apache.xerces.parsers.DOMParser" %> <html> <head><title>Example Appending Node with DOM Parser</title></head> <body> <% try{     DOMParser parser = new DOMParser();     String  path = request.getServletPath();     path = path.substring(0,path.indexOf("DOMExampleAppend.jsp"));     String  xml  = application.getRealPath(path + "House.xml");     parser.parse(xml);     Document doc = parser.getDocument();     NodeList list = doc.getElementsByTagName("DOWNSTAIRS");     Node downstairs = list.item(0);     doc.getElementsByTagName("UPSTAIRS").item(0).appendChild(downstairs);     listNodes(doc.getChildNodes(), out, ""); } catch (Exception e){     out.print("<br/><br/><font color=\"red\">There was an error<BR>");     out.print (e.toString() + "</font>"); } %> </body> </html>     <%!     private void listNodes(NodeList nlist, JspWriter out, String spacer)     throws Exception{         try{             for(int i = 0; i < nlist.getLength(); i++){                 out.print(spacer + "[");                 out.print(" " + nlist.item(i).getNodeName());                 out.print(" type:" + nlist.item(i).getNodeType());                 out.print("<font color=\"green\">&nbsp;"                    + nlist.item(i).getNodeValue() + "</font> ");             if(nlist.item(i).hasAttributes()){                 NamedNodeMap attributes = nlist.item(i).getAttributes();                 for(int j = 0; j < attributes.getLength(); j++){                     out.print(" {");                     out.print(" name:" + attributes.item(j).getNodeName());                     out.print(" value:" + attributes.item(j).getNodeValue());                     out.print(" } ");                 }             }                out.print("]<BR>");                if(nlist.item(i).getChildNodes().getLength() > 0)                    listNodes(nlist.item(i).getChildNodes(), out,                        spacer + "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");                //if the node is of type Element then create closing string                if(nlist.item(i).getNodeType() == 1)                   out.print(spacer + "[/" + nlist.item(i).getNodeName()                       + "]<BR>");            }        }        catch(Exception e){out.print( "<font color=\"red\">" +            e.toString() + "</font>");}    }    %>

The second new line of code in this listing begins with the Document node variable named doc, and grabs all the Element nodes whose tag name is UPSTAIRS.

House.xml only has one of these nodes; therefore, it's obtained by indexing the first item (index 0) of the NodeList returned from getElementsByTagName(). Once a reference to the UPSTAIRS Element node is obtained, the node referenced by the downstairs variable can be appended through the invocation of the appendChild() method.

The appendChild() method will first check to see whether the node referenced by the downstairs variable already exists in the tree to which it is being appended. This information is obtained by comparing the Document node that they both descend from. If they are both Nodes from the same Document, this node and the subtree descending from it will be removed. At this point, this subtree will be appended as the last child of the Element node on which the appendChild() method is invoked.

This method can be very helpful if an XML document needs to be programmatically altered before using a stylesheet or some other processing occurs.

The results of our alteration to Listing 5.5 are shown in Figure 5.5. Notice that the DOWNSTAIRS element (along with all its contents) was removed as a sibling of UPSTAIRS and then added as the last child of the UPSTAIRS element.

Figure 5.5. Results of appending the DOWNSTAIRS element.

graphics/05fig05.gif

Copying and Appending Nodes

What is required to copy an existing node and its descendants and then append them somewhere else in the same XML document? For example, perhaps we would like to still append the UPSTAIRS node in the same place, but without removing it from its original location.

In essence, the node must be copied, or cloned, and then appended somewhere else. Listing 5.6 does this.(Differences from the previous listing are noted in boldface type.) Save this file as webapps/xmlbook/chapter5/DOMExampleClone.jsp.

Listing 5.6 DOMExampleClone.jsp; Copy and Append Node
<%@ page   import="org.w3c.dom.*,           org.apache.xerces.parsers.DOMParser" %> <html> <head><title>Example Cloning and Appending Node with DOM Parser</title></head> <body> <% try{     DOMParser parser = new DOMParser();     String  path = request.getServletPath();     path = path.substring(0,path.indexOf("DOMExampleClone.jsp"));     String  xml  = application.getRealPath(path + "House.xml");     parser.parse(xml);     Document doc = parser.getDocument();     NodeList list = doc.getElementsByTagName("DOWNSTAIRS");     Node downstairs = list.item(0);     Node cloneDownstairs = downstairs.cloneNode(false);     doc.getElementsByTagName("UPSTAIRS").item(0).appendChild(cloneDownstairs);     listNodes(doc.getChildNodes(), out, ""); } catch (Exception e){     out.print("<br/><br/><font color=\"red\">There was an error<BR>");     out.print (e.toString() + "</font>"); } %> </body> </html>     <%!     private void listNodes(NodeList nlist, JspWriter out, String spacer)     throws Exception{         try{             for(int i = 0; i < nlist.getLength(); i++){                 out.print(spacer + "[");                 out.print(" " + nlist.item(i).getNodeName());                 out.print(" type:" + nlist.item(i).getNodeType());                 out.print("<font color=\"green\">&nbsp;"                    + nlist.item(i).getNodeValue() + "</font> ");             if(nlist.item(i).hasAttributes()){                 NamedNodeMap attributes = nlist.item(i).getAttributes();                 for(int j = 0; j < attributes.getLength(); j++){                     out.print(" {");                     out.print(" name:" + attributes.item(j).getNodeName());                     out.print(" value:" + attributes.item(j).getNodeValue());                     out.print(" } ");                 }             }                out.print("]<BR>");                if(nlist.item(i).getChildNodes().getLength() > 0)                    listNodes(nlist.item(i).getChildNodes(), out,                        spacer + "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");                //if the node is of type Element then create closing string                if(nlist.item(i).getNodeType() == 1)                   out.print(spacer + "[/" + nlist.item(i).getNodeName()                       + "]<BR>");            }        }        catch(Exception e){out.print( "<font color=\"red\">" +            e.toString() + "</font>");}    }    %>

The new code, shown in boldface type, begins by creating a copy of the DOWNSTAIRS Element node with the cloneNode() method. The boolean parameter of this method determines whether the copy will include all the node's children or not. In our example, we are not including the entire subtree descending from the DOWNSTAIRS element. However, this shallow copy will include all attributes if the node being copied is of type Element.

The copy of the DOWNSTAIRS element and its children is then appended in the same manner as demonstrated in the previous example.

NOTE

Once a node is cloned using the cloneNode() method, that copy is editable regardless of whether or not the original document was read-only. Any changes to the copy will not affect the original document. They are completely unrelated and separate nodes.

The output is shown in Figure 5.6. Notice that the DOWNSTAIRS element and attributes were indeed copied and appended as the last child of the UPSTAIRS element. Also, notice that the original DOWNSTAIRS element, the sibling of the UPSTAIRS element, is still present.

Figure 5.6. Cloning and appending nodes.

graphics/05fig06.gif

Programmatically Creating an XML Document

Next, we will create an XML document in its entirety programmatically. This JSP page, shown in Listing 5.7, is almost the same as the one in Listing 5.6. The difference is the additional code for the programmatic creation of a DOM document. This code is noted in boldface type. The complete JSP page should be saved as webapps\xmlbook\chapter5\DOMExampleProgram.jsp.

Listing 5.7 Complete JSP Page with Code to Create a DOM Document
<%@ page   import="org.w3c.dom.*,           org.apache.xerces.parsers.DOMParser" %> <html> <head><title>Programmatically Using DOM to Create Document</title></head> <body> <% try{     //create new XML Document programmatically     DOMImplementation domImpl = new org.apache.xerces.dom.DOMImplementationImpl();     Document newdoc = domImpl.createDocument(null, "TREEROOT", null);     Element root = newdoc.getDocumentElement();     Element firstEl = newdoc.createElement("FIRSTELEMENT");     root.appendChild(firstEl);     Element secondEl = newdoc.createElement("SECONDELEMENT");     root.appendChild(secondEl);     Text firsttext = newdoc.createTextNode("this is text data");     firstEl.appendChild(firsttext);     Text secondtext = newdoc.createTextNode("here is more text data");     secondEl.appendChild(secondtext);     listNodes(newdoc.getChildNodes(), out, ""); } catch (Exception e){     out.print("<br/><br/><font color=\"red\">There was an error<BR>");     out.print (e.toString() + "</font>"); } %> </body> </html>     <%!     private void listNodes(NodeList nlist, JspWriter out, String spacer)     throws Exception{         try{             for(int i = 0; i < nlist.getLength(); i++){                 out.print(spacer + "[");                 out.print(" " + nlist.item(i).getNodeName());                 out.print(" type:" + nlist.item(i).getNodeType());                 out.print("<font color=\"green\">&nbsp;"                    + nlist.item(i).getNodeValue() + "</font> ");             if(nlist.item(i).hasAttributes()){                 NamedNodeMap attributes = nlist.item(i).getAttributes();                 for(int j = 0; j < attributes.getLength(); j++){                     out.print(" {");                     out.print(" name:" + attributes.item(j).getNodeName());                     out.print(" value:" + attributes.item(j).getNodeValue());                     out.print(" } ");                 }             }                out.print("]<BR>");                if(nlist.item(i).getChildNodes().getLength() > 0)                    listNodes(nlist.item(i).getChildNodes(), out,                        spacer + "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");                //if the node is of type Element then create closing string                if(nlist.item(i).getNodeType() == 1)                   out.print(spacer + "[/" + nlist.item(i).getNodeName()                       + "]<BR>");            }        }        catch(Exception e){out.print( "<font color=\"red\">" +            e.toString() + "</font>");}    }    %>

The code begins by creating an instance of the DOMImplementation interface. It does this by instantiating the implementing class constructor DOMImplementationImpl():

//create new XML Document programmatically DOMImplementation domImpl = new org.apache.xerces.dom.DOMImplementationImpl();

Once this is done, a DOM container is established and we can create an XML document from scratch. This container is required in order to avoid associating any of our newly created nodes with an already existing DOM. If the code did not use this container, the newly created Nodes would have to be associated with an already existing DOM.

Now that the container exists which permits the creation and positioning of nodes, let's begin. First, we need to create a Document node that can contain DTD information and the root element of our XML hierarchy, among other things.

The createDocument() method is invoked with three parameters. The first is the namespace URI that this element is associated with. There will not be any namespaces in this example. The next parameter is the root Element node of the XML document being created. This node will be referenced as TREEROOT. Last is the DTD parameter. If we were creating an XML document that had an associated DTD, this is where that DocumentType node would be passed in and associated with this XML document:

Document newdoc = domImpl.createDocument(null, "TREEROOT", null);

Armed with the Document node, the next step is to obtain the root element created in the createDocument() method earlier. We can do this using the familiar getDocumentElement() method invoked on the Document node.

Element root = newdoc.getDocumentElement();

From this point on, all we need to do is create various types of Nodes and append them in the desired place. The Document interface is filled with various methods for the creation of each type of Node:

Element firstEl = newdoc.createElement("FIRSTELEMENT"); root.appendChild(firstEl); Element secondEl = newdoc.createElement("SECONDELEMENT"); root.appendChild(secondEl); Text firsttext = newdoc.createTextNode("this is text data"); firstEl.appendChild(firsttext); Text secondtext = newdoc.createTextNode("here is more text data"); secondEl.appendChild(secondtext);

Now let's output our results through the listNodes() method to see what we've got:

listNodes(newdoc.getChildNodes(), out, "");

The output shows the new DOM tree in Figure 5.7. Notice that the Text nodes are positioned as children within the Element nodes. This is because they were appended to the Element nodes, and not the root node.

Figure 5.7. Output of the programmatically created XML document.

graphics/05fig07.gif

Creating any other type of node is as simple as in the previous example. The Document node has many methods for creating the various types of nodes. Methods also exist for creating namespace-aware elements and attributes. These methods are used in the exact same way as the non-namespace-aware methods used previously. The only difference is in the parameters. The methods createElementNS() and createAttributeNS() take the qualified element or attribute name (this is the tagname with the namespace prefix, or no prefix if the default namespace is used), and the namespace URI.

By using these methods when creating an XML document, we ensure that the other namespace-aware methods will work as expected.

NOTE

When a node has been created with an associated namespace, the namespace attribute node will not be automatically added to the root Element node. You must add it by hand. To obtain the proper namespace structure, create the appropriate Attr node and add it to the root element.

Moving Nodes Between Documents

What if a node from one XML document needs to be moved to another document? This is a relatively simple procedure and uses the same methods, such as appendNode(), that we used previously. However, you'll need to watch out for one particular error, as we'll explain in the following short example.

Continuing with the previous example, the code will move the DOWNSTAIRS Element node from one DOM to another. This node began as a result of the parsing of an XML file. The element is then appended to the DOM programmatically created in the previous example.

Save the JSP in Listing 5.8 as webapps/xmlbook/chapter5/DOMExampleImport.jsp. (Additions from the previous listing are shown in boldface type.)

Listing 5.8 DOMExampleImport.jsp
<%@ page   import="org.w3c.dom.*,           org.apache.xerces.parsers.DOMParser" %> <html> <head><title>Copying Node from One Document to Another</title></head> <body> <% try{     DOMParser parser = new DOMParser();     String  path = request.getServletPath();     path = path.substring(0,path.indexOf("DOMExampleImport.jsp"));     String  xml  = application.getRealPath(path + "House.xml");     parser.parse(xml);     Document doc = parser.getDocument();     NodeList list = doc.getElementsByTagName("DOWNSTAIRS");     Node downstairs = list.item(0);     //create new XML Document programmatically     DOMImplementation domImpl = new org.apache.xerces.dom.DOMImplementationImpl();     Document newdoc = domImpl.createDocument(null, "TREEROOT", null);     Element root = newdoc.getDocumentElement();     Element firstEl = newdoc.createElement("FIRSTELEMENT");     root.appendChild(firstEl);     Element secondEl = newdoc.createElement("SECONDELEMENT");     root.appendChild(secondEl);     Text firsttext = newdoc.createTextNode("this is text data");     firstEl.appendChild(firsttext);     Text secondtext = newdoc.createTextNode("here is more text data");     secondEl.appendChild(secondtext);     Node importednode = newdoc.importNode(downstairs, false);     secondEl.appendChild(importednode);     listNodes(newdoc.getChildNodes(), out, ""); } catch (Exception e){     out.print("<br/><br/><font color=\"red\">There was an error<BR>");     out.print (e.toString() + "</font>"); } %> </body> </html>     <%!     private void listNodes(NodeList nlist, JspWriter out, String spacer)     throws Exception{         try{             for(int i = 0; i < nlist.getLength(); i++){                 out.print(spacer + "[");                 out.print(" " + nlist.item(i).getNodeName());                 out.print(" type:" + nlist.item(i).getNodeType());                 out.print("<font color=\"green\">&nbsp;"                    + nlist.item(i).getNodeValue() + "</font> ");             if(nlist.item(i).hasAttributes()){                 NamedNodeMap attributes = nlist.item(i).getAttributes();                 for(int j = 0; j < attributes.getLength(); j++){                     out.print(" {");                     out.print(" name:" + attributes.item(j).getNodeName());                     out.print(" value:" + attributes.item(j).getNodeValue());                     out.print(" } ");                 }             }                out.print("]<BR>");                if(nlist.item(i).getChildNodes().getLength() > 0)                    listNodes(nlist.item(i).getChildNodes(), out,                        spacer + "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");                //if the node is of type Element then create closing string                if(nlist.item(i).getNodeType() == 1)                   out.print(spacer + "[/" + nlist.item(i).getNodeName()                       + "]<BR>");            }        }        catch(Exception e){out.print( "<font color=\"red\">" +            e.toString() + "</font>");}    }    %>

This is a rather straightforward example using techniques covered previously. However, if a node from one document is appended to another document, it will throw a strange error. It will simply report, "Wrong document."

The point is that the node must be imported into the target document before the appending action can occur. Any nodes that are moved among different DOMs must be imported to the proper document with the importNode() method and then appended. This method will create a copy of the node being imported, including the subtree of ancestor nodes and attribute nodes.

Notice that the importNode() method has two parameters. The first is the Node being imported. The second, a boolean, indicates whether the entire subtree descending from this node should also be imported. Previously, the code passed false into this method, and the entire subtree was not included:

Node importednode = newdoc.importNode(downstairs, false);

However, if we change this boolean value to true, it will cause only the Node being directly referenced and any attributes contained therein to be imported. All other nodes are discarded. If the node is not an Element node, it will be imported alone with the boolean value set to false. If it's an Element node, the attributes will also be imported.

In this example, we are importing the Node but not the children through the false parameter.

Now that the Node has been imported, it must be placed in the DOM representation hierarchy. This is done with the familiar appendChild() method from the previous example.

This brings up the situation where the node is imported, but never appended. If this occurs and the DOM is output or traversed, this node will not be there. It's almost there, but it doesn't exist in terms of the hierarchy.

Let's take a look at the output in Figure 5.8. Notice that the DOWNSTAIRS Element appears as the last child node of the SECONDELEMENT node. This includes the attributes found in the appended Element node but not the entire subtree descending from it.

Figure 5.8. Output of node moved between documents without subtree.

graphics/05fig08.gif

The processes of altering and traversing DOMs are very easy. This is especially true when the structure of the document is known beforehand. However, there are other ways to traverse the tree when the structure of the document and the depth of a particular node are unknown. These classes for DOM traversal are found in the org.w3c.dom.traversal package.

TreeWalker

The first technique for altering and traversing a DOM involves the use of a TreeWalker.

TreeWalker objects are very powerful because they enable you to create a "view" on the XML document. This view is similar to database views, in that it is used to look at very specific parts of tables or combinations of tables.

With an XML document, a view can be used to look at the document with different types of nodes filtered out. In fact, you can pass an entire document through the TreeWalker, and only the nodes that meet particular requirements will be traversed.

This has the potential of making a tree structure change dramatically. For example, removing every Element node of an XML document leaves very little hierarchy in the document. This also causes all text nodes to be siblings of each other instead of being nested at various depths within particular elements.

The next simple example is designed to get you started using TreeWalkers. Save this file, shown in Listing 5.9, as webapps/xmlbook/chapter5/TreeWalker.jsp.

Listing 5.9 TreeWalker.jsp; Example using a TreeWalker
<%@ page   import="org.w3c.dom.*,           org.apache.xerces.parsers.DOMParser,           org.w3c.dom.traversal.*" %> <html> <head><title>TreeWalker Example</title></head> <body> <% try{     DOMParser parser = new DOMParser();     String  path = request.getServletPath();     path = path.substring(0,path.indexOf("TreeWalker.jsp"));     String  xml  = application.getRealPath(path + "House.xml");     parser.parse(xml);     Document doc = parser.getDocument();     TreeWalker treewalker = ((DocumentTraversal)doc).createTreeWalker         (doc.getDocumentElement(),         NodeFilter.SHOW_ELEMENT, null, true);     Node n = treewalker.firstChild();     while( n != null){          out.print(n.getNodeName() + "&nbsp;&nbsp;Type: "             + n.getNodeType() + "&nbsp;&nbsp;Value: "             + n.getNodeValue() + "<BR>");          n = treewalker.nextNode();     } } catch (Exception e){     out.print("<br/><br/><font color=\"red\">There was an error<BR>");     out.print (e.toString() + "</font>"); } %> </BODY> </HTML>

This example begins by importing the appropriate packages and then loads the XML document in the same manner as before. Once that has been completed, a TreeWalker instance is created on the Document node of the XML document to be traversed. Note that we need to cast the Document node (doc) to the DocumentTraversal type in order to invoke the createTreeWalker method on it.

The createTreeWalker() method has four arguments:

  1. The Node after which the tree traversal will start. This causes the subtree formed under this node to be selected and traversed. All other Nodes, beyond those in this subtree, do not exist according to the TreeWalker and therefore cannot be returned using TreeWalker methods.

  2. An int that indicates which nodes are filtered out from the subtree. This enables the exclusive traversal of one particular type of node present in the subtree. For example, if you use the int value 1, only nodes of type Element will be selected for traversal. The constants that make up the values of this int can be found in the org.w3c.dom.traversal.NodeFilter class.

  3. A NodeFilter object that can be programmed to filter nodes found through the TreeWalker in any way possible using code. The user writes a class that will implement the NodeFilter interface and define the only method present: acceptNode(). Each node the TreeWalker is about to traverse will be passed into this method and processed according to the return value. If the acceptNode() method accepts the node with FILTER_ACCEPT, the TreeWalker will traverse it; otherwise, it will be skipped. Entire subtrees can be skipped if acceptNode() returns FILTER_REJECT. The return value is a short whose constants can again be found in the NodeFilter class.

  4. A boolean that indicates whether entity references should be expanded.

You may have noticed that the createTreeWalker() method has two different ways of excluding Nodes. The distinction is subtle but important.

The first way to filter Nodes is through the int constant that is the second parameter of the createTreeWalker() method. As previously stated, this constant can be used to select which nodes are processed according to node type. You can choose to process either a specific type of Node or all Nodes. It is not possible to select only two types of nodes to process. This form of filtering is helpful, but not specific enough for all situations.

The second way to filter nodes can be as specific as you want. It involves the creation of a class that implements the NodeFilter interface. By definition, the acceptNode() method of this interface allows very specific nodes to be selected for processing. These nodes can be selected according to type, value, or position.

Before each node is processed with the TreeWalker, it is first passed into the acceptNode() method of the NodeFilter implementation class. The result of the method invocation on each node determines whether that node will continue to be processed and traversed with the TreeWalker.

In this example, we chose to use the static SHOW_ELEMENT to only allow nodes of type Element into the TreeWalker. Also, we did not define a NodeFilter implementation for the createTreeWalker() method and thus did not use the more specific filtering capabilities.

The results are shown in Figure 5.9. It is important to restate that this example doesn't demonstrate the full power of this tool. When the TreeWalker is created, one parameter is a NodeFilter. Our code passed null in this argument. The power of the TreeWalker lies in its ability to filter nodes through a NodeFilter implementation before the TreeWalker traverses it. The power of this feature is enormous.

Figure 5.9. Results of the TreeWalker traversal.

graphics/05fig09.gif

Notice in the output that only nodes of type Element were output. Element nodes are indicated by the value 1.

NodeIterator

A NodeIterator is used in the same way as a TreeWalker. The only difference between a NodeIterator and a TreeWalker is that a TreeWalker maintains the tree structure of a document.

A NodeIterator, on the other hand, collapses the tree structure into a NodeList that you can step through and filter just like a TreeWalker. The order of this set is the same order in which the start tags occur in the text representation of the document.

The only other difference with a NodeIterator is that the detach() method should be invoked when the list of nodes returned by the NodeIterator is finished. This releases the set that it iterated over and frees other resources.

Ranges

Several modules were released in the DOM Level 2 specification. One of those was the DOM Level 2 Traversal module, which includes the NodeIterator and TreeWalker discussed previously. Another one is the DOM Level 2 Range module implemented in the org.w3c.dom.ranges package. The only reason we mention them is to give you some knowledge of their existence. This will permit you to delve deeper into them if the need arises.

A range is a set of Nodes in a DOM tree. It has nothing to do with values of nodes or numeric quantities. Rather, it is a way of indicating a grouping of Nodes that need to be labeled together as a collection and manipulated.

In some ways a range is similar to selecting a node and its descendants. However, a range isn't limited to a tree or subtree structure. It's possible to start a range at a node that is the child of the root element and end it at a great-grandchild descending from the starting element's sibling. All nodes that are between these two points are a part of that range, and that range isn't a tree.

This can be useful if the structure of the document is unknown, and it is necessary to remove or edit parts of it. Feel free to check out the JavaDocs and see the many things that you can do using ranges.

JDOM, dom4j, and Deferred DOM

Before closing, we should mention JDOM, dom4j, and deferred DOM. JDOM and dom4j are very similar to DOM except that they were designed and written only for Java. This allows users to overload methods and to use other features of the Java language to make these Java representations of documents more intuitive to an experienced Java programmer. Some examples of JDOM and dom4j can be found in Chapter 7, "Successfully Using JSP and XML in an Application."

Deferred DOM is an answer to the memory issues that large and complex DOM structures create. Through the use of deferred DOM, nodes are only created in memory when they are accessed. This is a feature that certain parsers, such as Xerces, support. This is a solution to memory problems in certain situations. If only a small portion of the document will be used, and that portion is nonsequentially accessed, deferred DOM is the way to go. If it's sequential, use SAX, which is covered in the next chapter and is a very fast way to sequentially access documents.

Summary

This chapter covered the vast majority of tasks that any programmer will have to do with DOM. These include copying, moving, and deleting nodes and subtrees.

Most of the chapter described the uses of the DOM Level 2 Core module and the DOM Level 2 Traversal and Range modules. The entire Level 2 specification was broken into modules due to its size. Four other modules of the DOM Level 2 specification exist. Also, there is a module that enables HTML documents to be manipulated in the same ways described in this chapter.

CONTENTS


JSP and XML[c] Integrating XML and Web Services in Your JSP Application
JSP and XML[c] Integrating XML and Web Services in Your JSP Application
ISBN: 672323540
EAN: N/A
Year: 2005
Pages: 26

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net