1. 序章

XMLを使用する場合の一般的なアクティビティの1つは、その属性を使用することです。 このチュートリアルでは、Javaを使用してXML属性を変更する方法について説明します。

2. 依存関係

テストを実行するには、JUnitおよびxmlunit-assertj依存関係をMavenプロジェクトに追加する必要があります。

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.8.1</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.xmlunit</groupId>
    <artifactId>xmlunit-assertj</artifactId>
    <version>2.6.3</version>
    <scope>test</scope>
</dependency>

3. JAXPの使用

XMLドキュメントから始めましょう:

<?xml version="1.0" encoding="UTF-8"?>
<notification id="5">
    <to customer="true">[email protected]</to>
    <from>[email protected]</from>
</notification>

それを処理するために、バージョン1.4からJavaにバンドルされているXML処理用のJava API(JAXP)使用します。

customer 属性を変更し、その値をfalseに変更してみましょう。

まず、XMLファイルから Document オブジェクトを作成する必要があります。そのために、DocumentBuilderFactoryを使用します。

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
Document input = factory
  .newDocumentBuilder()
  .parse(resourcePath);

DocumentBuilderFactory クラスの外部エンティティ処理(XXE)を無効にするために、XMLConstants.FEATURE_SECURE_PROCESSINGおよびhttp://apache.org/xml/featuresを構成することに注意してください。 / disallow-doctype-declfeatures。 信頼できないXMLファイルを解析するときに構成することをお勧めします。

input オブジェクトを初期化した後、変更する属性を持つノードを見つける必要があります。 XPath式を使用して選択してみましょう。

XPath xpath = XPathFactory
  .newInstance()
  .newXPath();
String expr = String.format("//*[contains(@%s, '%s')]", attribute, oldValue);
NodeList nodes = (NodeList) xpath.evaluate(expr, input, XPathConstants.NODESET);

この場合、XPath Evaluation メソッドは、一致したノードを含むノードリストを返します。

リストを繰り返し処理して、値を変更してみましょう。

for (int i = 0; i < nodes.getLength(); i++) {
    Element value = (Element) nodes.item(i);
    value.setAttribute(attribute, newValue);
}

または、 for ループの代わりに、IntStreamを使用できます。

IntStream
    .range(0, nodes.getLength())
    .mapToObj(i -> (Element) nodes.item(i))
    .forEach(value -> value.setAttribute(attribute, newValue));

次に、Transformerオブジェクトを使用して変更を適用しましょう。

TransformerFactory factory = TransformerFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
Transformer xformer = factory.newTransformer();
xformer.setOutputProperty(OutputKeys.INDENT, "yes");
Writer output = new StringWriter();
xformer.transform(new DOMSource(input), new StreamResult(output));

output オブジェクトのコンテンツを出力すると、customer属性が変更された結果のXMLが取得されます。

<?xml version="1.0" encoding="UTF-8"?>
<notification id="5">
    <to customer="false">[email protected]</to>
    <from>[email protected]</from>
</notification>

また、単体テストで検証する必要がある場合は、XMLUnitassertThatメソッドを使用できます。

assertThat(output.toString()).hasXPath("//*[contains(@customer, 'false')]");

4. dom4jの使用

dom4j は、XMLを処理するためのオープンソースフレームワークであり、XPathと統合されており、DOM、SAX、JAXP、およびJavaコレクションを完全にサポートしています。

4.1. Mavenの依存関係

プロジェクトでdom4jを使用するには、dom4jおよびjaxenの依存関係をpom.xmlに追加する必要があります。

<dependency>
    <groupId>org.dom4j</groupId>
    <artifactId>dom4j</artifactId>
    <version>2.1.1</version>
</dependency>
<dependency>
    <groupId>jaxen</groupId>
    <artifactId>jaxen</artifactId>
    <version>1.2.0</version>
</dependency>

dom4jの詳細については、XMLライブラリサポートの記事を参照してください。

4.2. org.dom4j.Element.addAttributeを使用する

dom4jは、XML要素の抽象化としてElementインターフェースを提供します。 addAttribute メソッドを使用して、customer属性を更新します。

これがどのように機能するか見てみましょう。

まず、XMLファイルから Document オブジェクトを作成する必要があります。今回は、SAXReaderを使用します。

SAXReader xmlReader = new SAXReader();
Document input = xmlReader.read(resourcePath);
xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
xmlReader.setFeature("http://xml.org/sax/features/external-general-entities", false);
xmlReader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);

XXEを防ぐために追加機能を設定します。

JAXPと同様に、XPath式を使用してノードを選択できます。

String expr = String.format("//*[contains(@%s, '%s')]", attribute, oldValue);
XPath xpath = DocumentHelper.createXPath(expr);
List<Node> nodes = xpath.selectNodes(input);

これで、属性を繰り返して更新できます。

for (int i = 0; i < nodes.size(); i++) {
    Element element = (Element) nodes.get(i);
    element.addAttribute(attribute, newValue);
}

この方法では、指定された名前の属性がすでに存在する場合、その属性が置き換えられることに注意してください。 それ以外の場合は追加されます。

結果を印刷するために、前のJAXPセクションのコードを再利用できます。

5. jOOXを使用する

jOOX (jOOX Object-Oriented XML)は、 org.w3c.dom パッケージのラッパーであり、DOMが必要であるが冗長すぎる場合に、流暢なXMLドキュメントの作成と操作を可能にします。 jOOXは基礎となるドキュメントをラップするだけであり、代替としてではなく、DOMを拡張するために使用できます。

5.1. Mavenの依存関係

プロジェクトでjOOXを使用するには、pom.xmlに依存関係を追加する必要があります。

Java 9以降で使用する場合は、次を使用できます。

<dependency>
    <groupId>org.jooq</groupId>
    <artifactId>joox</artifactId>
    <version>1.6.2</version>
</dependency>

または、Java 6以降では、次のようになります。

<dependency>
    <groupId>org.jooq</groupId>
    <artifactId>joox-java-6</artifactId>
    <version>1.6.2</version>
</dependency>

jooxおよびjoox-java-6の最新バージョンはMavenCentralリポジトリーにあります。

5.2. org.w3c.dom.Element.setAttributeを使用する

以下の例でわかるように、jOOXAPI自体はjQueryに触発されています。 使い方を見てみましょう。

まず、ドキュメントをロードする必要があります。

DocumentBuilder builder = JOOX.builder();
Document input = builder.parse(resourcePath);

次に、それを選択する必要があります。

Match $ = $(input);

customer Element、を選択するには、findメソッドまたはXPath式を使用できます。 どちらの場合も、それに一致する要素のリストを取得します。

findメソッドの動作を見てみましょう。

$.find("to")
    .get()
    .stream()
    .forEach(e -> e.setAttribute(attribute, newValue));

結果をStringとして取得するには、 toString()メソッドを呼び出すだけです。

$.toString();

6. 基準

これらのライブラリのパフォーマンスを比較するために、JMHベンチマークを使用しました。

結果を見てみましょう:

| Benchmark                          Mode  Cnt  Score   Error  Units |
|--------------------------------------------------------------------|
| AttributeBenchMark.dom4jBenchmark  avgt    5  0.150 ± 0.003  ms/op |
| AttributeBenchMark.jaxpBenchmark   avgt    5  0.166 ± 0.003  ms/op |
| AttributeBenchMark.jooxBenchmark   avgt    5  0.230 ± 0.033  ms/op |

ご覧のとおり、このユースケースと実装では、dom4jとJAXPのスコアがjOOXよりも優れています。

7. 結論

このクイックチュートリアルでは、JAXP、dom4j、およびjOOXを使用してXML属性を変更する方法を紹介しました。 また、JMHベンチマークを使用してこれらのライブラリのパフォーマンスを測定しました。

いつものように、ここに示されているすべてのコードサンプルは、GitHubから入手できます。