発生した問題
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)