If you've been working with Java and XML for any length of time, you've
almost certainly come across a situation in which you either needed or
were told to use the DOM, the Document Object Model, from the W3C (see Resources). And if you've taken a step toward
using DOM, you've probably also run across the dreaded "" exception when
using the DOM. In this tip I'll use an example that moves DOM nodes
to explain exactly what causes that rather annoying error. I'll also show
you how to avoid running into this exception in your future DOM programming.
For a (somewhat cheesy) example, look at two XML documents in the first
two listings. Listing 1 shows test1.xml, a list of Playstation
2 (PS2) games in a store's current inventory.
<?xml version="1.0"?>
<videoGames>
<game system="PS2">
<title>Madden 2001</title>
<maxPlayers>8</maxPlayers>
</game>
<game system="PS2">
<title>SSX</title>
<maxPlayers>2</maxPlayers>
</game>
<game system="PS2">
<title>NHL 2001</title>
<maxPlayers>8</maxPlayers>
</game>
</videoGames>
|
The second example, test2.xml, displays the new game shipments
that have arrived at the store. It shows a new PS2 game, which is represented
in Listing 2:
<?xml version="1.0"?>
<newInventory>
<game system="PS2">
<title>DOA2: Hardcore</title>
<maxPlayers>4</maxPlayers>
</game>
</newInventory>
|
The point of this exercise is to take the game from the new inventory
(represented in DOM by the org.w3c.dom.Node interface) and move
it from test2.xml into the current inventory listing in test1.xml.
To do this, you pull up the DOM API documentation and your parser (I'm
using Xerces for the example) and code up a little program like the one
in Listing 3:
import org.apache.xerces.parsers.DOMParser;
import org.xml.sax.InputSource;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
public class MoveNode {
public static void main(String[] args) {
try {
DOMParser parser = new DOMParser();
// Create the first document
parser.parse(new InputSource(args[0]));
Document doc1 = parser.getDocument();
// Create the second document
parser.parse(new InputSource(args[1]));
Document doc2 = parser.getDocument();
// Get root of first document
Element firstRoot = doc1.getDocumentElement();
// Get Node to move
Element secondRoot = doc2.getDocumentElement();
NodeList kids = secondRoot.getElementsByTagName("game");
Element oneToMove = (Element)kids.item(0);
// Add to first document
firstRoot.appendChild(oneToMove);
} catch (Exception e) {
e.printStackTrace();
}
}
}
|
Looks pretty good, right?
The code compiles (with xerces.jar in the classpath, of course).
It creates DOM trees out of each XML document. It gets the root element
from the first document. It gets the element to move from the second, then
it moves that element beneath the first document's root element. Simple
enough... until you run the code, which is when you get the dreaded Wrong
document exception! Listing 4 shows this in action:
/usr/local/projects/tips>java MoveNode
file:///usr/local/projects/tips/test1.xml
file:///usr/local/projects/tips/test2.xml
org.apache.xerces.dom.DOMExceptionImpl: DOM005 Wrong document
at org.apache.xerces.dom.
ChildAndParentNode.internalInsertBefore(ChildAndParentNode.java:314)
at org.apache.xerces.dom.
ChildAndParentNode.insertBefore(ChildAndParentNode.java:296)
at org.apache.xerces.dom.NodeImpl.appendChild(NodeImpl.java:213)
at MoveNode.main(MoveNode.java:30)
|
If you aren't using Xerces, the exact message you get may be different
from this, but the idea is the same: Wrong document. What the
heck does that mean? Well, in DOM, the Document object actually
is a factory. As a result, it has the job of creating implementations of
the various DOM interfaces when requested, such as implementations of Node,
Element,
Attr, and so on. When you add a Node
to a document, that document must know how to work with the new Node
implementation.
In a nutshell, working with DOM requires that any Node always
be tied to a specific document, which is the factory, remember? Unfortunately,
DOM implementations do not, at least in any of the products I've used,
automatically take care of dealing with this little detail. So although
the code in Listing 3 added the child to the first
document's root element (with the appendChild() method), it didn't
let the first document know enough about the new node it needed to deal
with. Seems a little strange, huh? Yeah, I know, but all APIs have quirks,
and this is one fairly common to factory-based APIs. In any case, making
the code work takes a fairly simple change. Check out Listing 5, which
shows corrected code:
import org.apache.xerces.parsers.DOMParser;
import org.xml.sax.InputSource;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class MoveNode {
public static void main(String[] args) {
try {
DOMParser parser = new DOMParser();
// Create the first document
parser.parse(new InputSource(args[0]));
Document doc1 = parser.getDocument();
// Create the second document
parser.parse(new InputSource(args[1]));
Document doc2 = parser.getDocument();
// Get root of first document
Element firstRoot = doc1.getDocumentElement();
// Get Node to move
Element secondRoot = doc2.getDocumentElement();
NodeList kids = secondRoot.getElementsByTagName("game");
Element oneToMove = (Element)kids.item(0);
// Add to first document
Node newOneToMove = doc1.importNode(oneToMove, true);
firstRoot.appendChild(newOneToMove);
} catch (Exception e) {
e.printStackTrace();
}
}
} |
Listing 5 creates a new Node that is suitable
for use in the first document. Incidentally, this also preserves the use
of the original node (oneToMove) for use in the second document.
Once the node has been imported, it can be appended as in the first example.
So, moving nodes between DOM trees actually takes two method invocations,
rather than just one. Remember that, and the Wrong document error
message that your friends are getting will bother you no more! Go forth
and use the DOM wisely.
-
Download the Apache Xerces
XML parser.
-
Read the DOM specification for the
whole DOM story.
-
Check out other XML zone tips of the week:
- Shortcut to creating a Web page table of contents, using XSLT
- Documenting style sheets using RDF
- How to implement lookup tables in XSLT
-
IBM trial software: Build your next development project with trial software available for download directly from developerWorks.
- Find more XML resources on the developerWorks XML zone. For a complete list of XML tips to date, check out the tips summary page.
Brett McLaughlin (brett@newInstance.com) works as Enhydra strategist at Lutris Technologies and specializes in distributed systems architecture. He is author of Java and XML (O'Reilly). He is involved in technologies such as Java servlets, Enterprise JavaBeans technology, XML, and business-to-business applications. Along with Jason Hunter, he founded the JDOM project, which provides a simple API for manipulating XML from Java applications. He is also an active developer on the Apache Cocoon project and the EJBoss EJB server as well as a co-founder of the Apache Turbine project.