Перейти к основному содержимому

Изменение атрибута XML в Java

· 5 мин. чтения

1. Введение

Одним из распространенных действий при работе с XML является работа с его атрибутами. В этом руководстве мы рассмотрим, как изменить атрибут XML с помощью Java.

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">john@email.com</to>
<from>mary@email.com</from>
</notification>

Чтобы обработать его, мы будем использовать Java API для обработки XML (JAXP) , который входит в состав Java, начиная с версии 1.4.

Давайте изменим атрибут клиента и изменим его значение на false .

Во- первых, нам нужно создать объект Document из XML-файла, и для этого мы будем использовать 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);

Обратите внимание: чтобы отключить обработку внешних сущностей (XXE) для класса DocumentBuilderFactory , мы настраиваем функции XMLConstants.FEATURE_SECURE_PROCESSING и http://apache.org/xml/features/disallow-doctype-decl . Рекомендуется настраивать его при анализе ненадежных XML-файлов.

После инициализации нашего входного объекта нам нужно найти узел с атрибутом, который мы хотим изменить. Давайте используем выражение 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 возвращает нам список узлов с совпавшими узлами.

Давайте пройдемся по списку, чтобы изменить значение:

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

Если мы напечатаем содержимое выходного объекта, мы получим результирующий XML с измененным атрибутом клиента :

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

Кроме того, мы можем использовать метод assertThat XMLUnit , если нам нужно проверить его в модульном тесте:

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

4. Использование dom4j

dom4j — это платформа с открытым исходным кодом для обработки XML, интегрированная с XPath и полностью поддерживающая коллекции DOM, SAX, JAXP и Java.

4.1. Зависимость от Maven

Нам нужно добавить зависимости dom4j и jaxen в наш 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>

Подробнее о dom4j мы можем узнать в нашей статье Поддержка библиотек XML .

4.2. Использование org.dom4j.Element.addAttribute

dom4j предлагает интерфейс Element как абстракцию для XML-элемента. Мы будем использовать метод addAttribute для обновления нашего атрибута клиента .

Давайте посмотрим, как это работает.

Во- первых, нам нужно создать объект Document из XML-файла — на этот раз мы будем использовать 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 , позволяющая свободно создавать XML-документы и манипулировать ими там, где требуется DOM, но он слишком многословен. jOOX только обертывает базовый документ и может использоваться для улучшения DOM, а не в качестве альтернативы.

5.1. Зависимость от Maven

Нам нужно добавить зависимость в наш pom.xml , чтобы использовать jOOX в нашем проекте.

Для использования с 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 в репозитории Maven Central.

5.2. Использование org.w3c.dom.Element.setAttribute

Сам jOOX API вдохновлен jQuery , как мы можем видеть в примерах ниже. Давайте посмотрим, как его использовать.

Во-первых, нам нужно загрузить документ :

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

Теперь нам нужно выбрать его:

Match $ = $(input);

Чтобы выбрать элемент клиента, мы можем использовать метод find или выражение XPath. В обоих случаях мы получим список элементов, которые ему соответствуют.

Давайте посмотрим на метод find в действии:

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

Чтобы получить результат в виде строки , нам просто нужно вызвать метод 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. Заключение

В этом кратком руководстве мы рассказали, как изменять XML-атрибуты с помощью JAXP, dom4j и jOOX. Кроме того, мы измерили производительность этих библиотек с помощью теста JMH.

Как обычно, все приведенные здесь примеры кода доступны на GitHub .