Modifying an XML Attribute in Java – 在Java中修改一个XML属性

最后修改: 2019年 8月 3日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

One common activity when we are working with XML is working with its attributes. In this tutorial, we’ll explore how to modify an XML attribute using Java.

当我们在使用XML时,一个常见的活动是处理其属性。在本教程中,我们将探讨如何使用Java来修改XML的属性。

2. Dependencies

2.依赖性

In order to run our tests, we’ll need to add the JUnit and xmlunit-assertj dependencies to our Maven project:

为了运行我们的测试,我们需要将JUnitxmlunit-assertjdependencies添加到我们的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. Using JAXP

3.使用JAXP

Let’s start with an XML document:

让我们从一个XML文档开始。

<?xml version="1.0" encoding="UTF-8"?>
<notification id="5">
    <to customer="true">john@email.com</to>
    <from>mary@email.com</from>
</notification>

In order to process it, we’ll use the Java API for XML Processing (JAXP), which has been bundled with Java since version 1.4.

为了处理它,我们将使用Java API for XML Processing (JAXP),它从1.4版开始就被捆绑在Java中。

Let’s modify the customer attribute and change its value to false.

让我们修改customer属性,将其值改为false

First, we need to build a Document object from the XML file, and to do that, we’ll use a DocumentBuilderFactory:

首先,我们需要从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);

Note that in order to disable external entity processing (XXE) for the DocumentBuilderFactory class, we configure the XMLConstants.FEATURE_SECURE_PROCESSING and http://apache.org/xml/features/disallow-doctype-decl features. It’s a good practice to configure it when we parse untrusted XML files.

请注意,为了禁用外部实体处理(XXE) DocumentBuilderFactory类,我们配置XMLConstants.FEATURE_SECURE_PROCESSINGhttp://apache.org/xml/features/disallow-doctype-decl特性。当我们解析不受信任的XML文件时,对其进行配置是一个很好的做法。

After initializing our input object, we’ll need to locate the node with the attribute we’d like to change. Let’s use an XPath expression to select it:

在初始化我们的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);

In this case, the XPath evaluate method returns us a node list with the matched nodes.

在这种情况下,XPath evaluate方法返回给我们一个包含匹配节点的节点列表。

Let’s iterate over the list to change the value:

让我们在列表上进行迭代来改变值。

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

Or, instead of a for loop, we can use an IntStream:

或者,我们可以使用IntStream,而不是for循环。

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

Now, let’s use a Transformer object to apply the changes:

现在,让我们使用一个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));

If we print the output object content, we’ll get the resulting XML with the customer attribute modified:

如果我们打印output对象内容,我们将得到修改了customer属性的XML结果。

<?xml version="1.0" encoding="UTF-8"?>
<notification id="5">
    <to customer="false">john@email.com</to>
    <from>mary@email.com</from>
</notification>

Also, we can use the assertThat method of XMLUnit if we need to verify it in a unit test:

此外,如果我们需要在单元测试中进行验证,我们可以使用XMLUnitassertThat方法。

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

4. Using dom4j

4.使用dom4j

dom4j is an open-source framework for processing XML that is integrated with XPath and fully supports DOM, SAX, JAXP, and Java Collections.

dom4j是一个用于处理XML的开源框架,它与XPath集成,完全支持DOM、SAX、JAXP和Java集合。

4.1. Maven Dependency

4.1.Maven的依赖性

We need to add the dom4j and jaxen dependencies to our pom.xml to use dom4j in our project:

我们需要将dom4jjaxen依赖项添加到我们的pom.xml,以便在我们的项目中使用 dom4j。

<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>

We can learn more about dom4j in our XML Libraries Support article.

我们可以在我们的XML库支持文章中了解更多关于dom4j的信息

4.2. Using org.dom4j.Element.addAttribute

4.2.使用org.dom4j.Element.addAttribute

dom4j offers the Element interface as an abstraction for an XML element. We’ll be using the addAttribute method to update our customer attribute.

dom4j提供了Element接口作为一个XML元素的抽象。我们将使用addAttribute方法来更新我们的customer属性。

Let’s see how this works.

让我们看看这是如何运作的。

First, we need to build a Document object from the XML file — this time, we’ll use a SAXReader:

首先,我们需要从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);

We set the additional features in order to prevent XXE.

我们设置了额外的功能,以防止XXE。

Like JAXP, we can use an XPath expression to select the nodes:

像JAXP一样,我们可以使用XPath表达式来选择节点。

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

Now, we can iterate and update the attribute:

现在,我们可以迭代并更新该属性。

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

Note that with this method, if an attribute already exists for the given name, it will be replaced. Otherwise, it’ll be added.

注意,在这个方法中,如果一个属性已经存在于给定的名称中,它将被替换。否则,它将被添加。

In order to print the results, we can reuse the code from the previous JAXP section.

为了打印结果,我们可以重新使用前面JAXP部分的代码。

5. Using jOOX

5.使用jOOX

jOOX (jOOX Object-Oriented XML) is a wrapper for the org.w3c.dom package that allows for fluent XML document creation and manipulation where DOM is required but too verbose. jOOX only wraps the underlying document and can be used to enhance DOM, not as an alternative.

jOOX (jOOX Object-Oriented XML)是org.w3c.dom包的一个包装器,在需要DOM但过于冗长的地方,可以流畅地创建和操作XML文档。jOOX只包装底层文档,可以用来增强DOM,而不是作为替代品。

5.1. Maven Dependency

5.1.Maven的依赖性

We need to add the dependency to our pom.xml to use jOOX in our project.

我们需要在我们的pom.xml中添加该依赖关系,以便在我们的项目中使用jOOX。

For use with Java 9+, we can use:

对于使用Java 9+,我们可以使用。

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

Or with Java 6+, we have:

或者用Java 6+,我们有。

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

We can find the latest versions of joox and joox-java-6 in the Maven Central repository.

我们可以在Maven Central仓库中找到jooxjoox-java-6的最新版本。

5.2. Using org.w3c.dom.Element.setAttribute

5.2.使用org.w3c.dom.Element.setAttribute

The jOOX API itself is inspired by jQuery, as we can see in the examples below. Let’s see how to use it.

jOOX API本身受到jQuery的启发,我们在下面的例子中可以看到。让我们看看如何使用它。

First, we need to load the Document:

首先,我们需要加载Document

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

Now, we need to select it:

现在,我们需要选择它。

Match $ = $(input);

In order to select the customer Element, we can use the find method or an XPath expression. In both cases, we’ll get a list of the elements that match it.

为了选择customer Element,我们可以使用find方法或XPath表达式。在这两种情况下,我们将得到一个与之匹配的元素的列表。

Let’s see the find method in action:

让我们看看find方法的操作。

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

To get the result as a String, we simply need to call the toString() method:

要获得String的结果,我们只需要调用toString()方法。

$.toString();

6. Benchmark

6.标杆

In order to compare the performance of these libraries, we used a JMH benchmark.

为了比较这些库的性能,我们使用了JMH>基准。

Let’s see the results:

让我们看看结果。

| 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 |

As we can see, for this use case and our implementation, dom4j and JAXP have better scores than jOOX.

我们可以看到,对于这个用例和我们的实现,dom4j和JAXP比jOOX有更好的得分。

7. Conclusion

7.结论

In this quick tutorial, we’ve introduced how to modify XML attributes using JAXP, dom4j, and jOOX. Also, we measured the performance of these libraries with a JMH benchmark.

在这个快速教程中,我们已经介绍了如何使用JAXP、dom4j和jOOX来修改XML属性。此外,我们还用JMH基准测试了这些库的性能。

As usual, all the code samples shown here are available over on GitHub.

像往常一样,这里显示的所有代码样本都可以在GitHub上获得。