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 .