0
0

XMLを複製する際にWRONG_DOCUMENT_ERRが発生する

Posted at

発生した問題

employees.xmlには従業員の従業員番号・職種・名前が記載されています。

employees.xml
<?xml version="1.0" encoding="UTF-8"?>
<employees>
  <employee id="1" role="営業">中原一郎</employee>
  <employee id="2" role="経理">高津二郎</employee>
  <employee id="3" role="営業">宮前三郎</employee>
  <employee id="4" role="設計">多摩四郎</employee>
  <employee id="5" role="営業">麻生五郎</employee>
</employees>

employees.xmlから職種が営業の従業員を抜き出し、新しいXML(sales.xml)を作りたいとします。これを実現するため、以下のようなプログラムを作成しました。

DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();

// 新XMLをメモリ上に作成する。
Document newDocument = documentBuilder.newDocument();
newDocument.appendChild(newDocument.createElement("employees"));

// employees.xmlをメモリ上に展開する。
Document oldDocument = documentBuilder.parse(new File("employees.xml"));

// <employees>配下のすべてのノードについて、以下の処理を繰り返す。
NodeList oldChildNodes = oldDocument.getDocumentElement().getChildNodes();
for (int i = 0; i < oldChildNodes.getLength(); i++) {
    // ノードが<employee>以外の場合は何もしない。
    Node oldChildNode = oldChildNodes.item(i);
    if (oldChildNode.getNodeType() != Node.ELEMENT_NODE) {
        continue;
    }
    
    // <employee>のroleが営業の場合は、その<employee>を新XMLへコピーする。
    String role = oldChildNode.getAttributes().getNamedItem("role").getTextContent();
    if (role.equals("営業")) {
        newDocument.getDocumentElement().appendChild(oldChildNode);
    }
}

// 新XMLをsales.xmlに書き出す。
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.transform(new DOMSource(newDocument), new StreamResult(new File("sales.xml")));

このプログラムを実行したところ、次のようなエラーが発生しました。

Exception in thread "main" org.w3c.dom.DOMException: WRONG_DOCUMENT_ERR: ノードが、そのノードを作成したものとは異なるドキュメントで使用されています。
	at java.xml/com.sun.org.apache.xerces.internal.dom.ParentNode.internalInsertBefore(ParentNode.java:355)
	at java.xml/com.sun.org.apache.xerces.internal.dom.ParentNode.insertBefore(ParentNode.java:286)
	at java.xml/com.sun.org.apache.xerces.internal.dom.NodeImpl.appendChild(NodeImpl.java:230)
	at xml.sample.Main01.main(Main01.java:44)

原因と解決策

すべてのNodeはあるひとつのDocumentにひもづきます。今回のエラーは、とあるDocumentにひもづいているNodeを別のDocumentに紐づけようとしたことが原因です。

解決策はDocument.importNodeを利用することです。このメソッドは、別のDocumentにひもづくノードを複製して、対象のドキュメントにひもづいたノードを作成します。作成したノードは親ノードを持たないので、Node.appendChildなどで、親ノードを設定してやる必要があります。

ではプログラムを修正してみましょう。元のDocumentのemployee要素を新しいDocumentにコピーするところで、Document.importNodeを呼び出します (22~23行目付近)

DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();

// 新XMLをメモリ上に作成する。
Document newDocument = documentBuilder.newDocument();
newDocument.appendChild(newDocument.createElement("employees"));

// employees.xmlをメモリ上に展開する。
Document oldDocument = documentBuilder.parse(new File("employees.xml"));

// <employees>配下のすべてのノードについて、以下の処理を繰り返す。
NodeList oldChildNodes = oldDocument.getDocumentElement().getChildNodes();
for (int i = 0; i < oldChildNodes.getLength(); i++) {
    // ノードが<employee>以外の場合は何もしない。
    Node oldChildNode = oldChildNodes.item(i);
    if (oldChildNode.getNodeType() != Node.ELEMENT_NODE) {
        continue;
    }
    
    // <employee>のroleが営業の場合は、その<employee>を新XMLへコピーする。
    String role = oldChildNode.getAttributes().getNamedItem("role").getTextContent();
    if (role.equals("営業")) {
        Node newChildNode = newDocument.importNode(oldChildNode, true); 
        newDocument.getDocumentElement().appendChild(newChildNode);
    }
}

// 新XMLをsales.xmlに書き出す。
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.transform(new DOMSource(newDocument), new StreamResult(new File("sales.xml")));

このプログラムを実行すると、想定通りのsales.xmlが作成されました。

sales.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<employees>
    <employee id="1" role="営業">中原一郎</employee>
    <employee id="3" role="営業">宮前三郎</employee>
    <employee id="5" role="営業">麻生五郎</employee>
</employees>

環境情報

C:\>javac -version
javac 17.0.3

C:\>java -version
openjdk version "17.0.3" 2022-04-19
OpenJDK Runtime Environment Temurin-17.0.3+7 (build 17.0.3+7)
OpenJDK 64-Bit Server VM Temurin-17.0.3+7 (build 17.0.3+7, mixed mode, sharing)
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0