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

# 発生した問題

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)を作りたいとします。これを実現するため、以下のようなプログラムを作成しました。

```java
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などで、親ノードを設定してやる必要があります。

https://docs.oracle.com/javase/jp/8/docs/api/org/w3c/dom/Document.html#importNode-org.w3c.dom.Node-boolean-

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

```java
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)
```