Tip: Moving DOM nodes

How to avoid Wrong document exceptions

This tip takes a look at a common exception that occurs when you attempt to move DOM nodes. Here you'll learn the causes of this exception and, most important, how to avoid it when doing DOM programming. The code listings demonstrate how to move nodes from one document to another, what the code that generates the dreaded Wrong document exception looks like, and how to write correct code that doesn't present this problem. This tip assumes that you already know the basics about how to work with the DOM."

Brett McLaughlin (brett@newInstance.com), Enhydra strategist, Lutris Technologies

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.



01 March 2001

Also available in Japanese

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.

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into XML on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=XML
ArticleID=11978
ArticleTitle=Tip: Moving DOM nodes
publish-date=03012001