Modificando um atributo XML em Java
1. Introdução
Uma atividade comum quando estamos trabalhando com XML é trabalhar com seus atributos. Neste tutorial, vamos explorar como modificar um atributo XML usando Java.
2. Dependências
Para executar nossos testes, precisaremos adicionar as dependênciasJUnitexmlunit-assertj ao nosso projetoMaven:
org.junit.jupiter
junit-jupiter
5.5.0
test
org.xmlunit
xmlunit-assertj
2.6.3
test
3. Usando JAXP
Vamos começar com um documento XML:
[email protected]
[email protected]
Para processá-lo, usaremosuse the Java API for XML Processing (JAXP), que vem com o Java desde a versão 1.4.
Vamos modificar o atributocustomer e alterar seu valor parafalse.
Primeiro, precisamos construir um objetoDocument a partir do arquivo XML e, para fazer isso, usaremos umDocumentBuilderFactory:
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);
Observe que paradisable https://www.owasp.org/index.php/XML_External_Entity_(XXE)Processing[external entity processing (XXE)] para a classe _DocumentBuilderFactory,we configure the XMLConstants.FEATURE_SECURE_PROCESSING and http://apache.org/xml/features/disallow-doctype-decl features. É uma boa prática configurá-lo quando analisamos arquivos XML não confiáveis.
Depois de inicializar nosso objetoinput, precisaremos localizar o nó com o atributo que gostaríamos de alterar. Vamos usar umXPath expression para selecioná-lo:
XPath xpath = XPathFactory
.newInstance()
.newXPath();
String expr = String.format("//*[contains(@%s, '%s')]", attribute, oldValue);
NodeList nodes = (NodeList) xpath.evaluate(expr, input, XPathConstants.NODESET);
Nesse caso, o método XPathevaluate nos retorna uma lista de nós com os nós correspondentes.
Vamos repetir a lista para alterar o valor:
for (int i = 0; i < nodes.getLength(); i++) {
Element value = (Element) nodes.item(i);
value.setAttribute(attribute, newValue);
}
Ou, em vez de um loopfor, podemos usar umIntStream:
IntStream
.range(0, nodes.getLength())
.mapToObj(i -> (Element) nodes.item(i))
.forEach(value -> value.setAttribute(attribute, newValue));
Agora, vamos usar um objetoTransformer para aplicar as alterações:
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));
Se imprimirmos o conteúdo do objetooutput, obteremos o XML resultante com o atributocustomer modificado:
[email protected]
[email protected]
Além disso, podemos usar o métodoassertThat deXMLUnit se precisarmos verificá-lo em um teste de unidade:
assertThat(output.toString()).hasXPath("//*[contains(@customer, 'false')]");
4. Usando dom4j
dom4j é uma estrutura de código-fonte aberto para processamento de XML que está integrada com XPath e oferece suporte total a coleções DOM, SAX, JAXP e Java.
4.1. Dependência do Maven
org.dom4j
dom4j
2.1.1
jaxen
jaxen
1.2.0
4.2. Usandoorg.dom4j.Element.addAttribute
dom4j oferece a interfaceElement como uma abstração para um elemento XML. Estaremos usando o métodoaddAttribute para atualizar nosso atributocustomer.
Vamos ver como isso funciona.
Primeiro, precisamos construir um objetoDocument a partir do arquivo XML - desta vez, usaremos umSAXReader:
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);
Definimos os recursos adicionais para evitar XXE.
Como o JAXP, podemos usar uma expressão XPath para selecionar os nós:
String expr = String.format("//*[contains(@%s, '%s')]", attribute, oldValue);
XPath xpath = DocumentHelper.createXPath(expr);
List nodes = xpath.selectNodes(input);
Agora, podemos iterar e atualizar o atributo:
for (int i = 0; i < nodes.size(); i++) {
Element element = (Element) nodes.get(i);
element.addAttribute(attribute, newValue);
}
Observe que, com esse método, se já existir um atributo para o nome fornecido, ele será substituído. Caso contrário, ele será adicionado.
Para imprimir os resultados, podemos reutilizar o código da seção JAXP anterior.
5. Usando jOOX
jOOX (jOOX Object-Oriented XML) é um wrapper para o pacoteorg.w3c.dom que permite a criação e manipulação fluente de documentos XML onde o DOM é necessário, mas muito detalhado. O jOOX envolve apenas o documento subjacente e pode ser usado para aprimorar o DOM, não como uma alternativa.
5.1. Dependência do Maven
Precisamos adicionar a dependência ao nossopom.xml para usar jOOX em nosso projeto.
Para uso com Java 9+, podemos usar:
org.jooq
joox
1.6.2
Ou com Java 6+, temos:
org.jooq
joox-java-6
1.6.2
Podemos encontrar as versões mais recentes dejooxejoox-java-6 no repositório Maven Central.
5.2. Usandoorg.w3c.dom.Element.setAttribute
A API jOOX em si é inspirada emjQuery, como podemos ver nos exemplos abaixo. Vamos ver como usá-lo.
Primeiro, precisamos carregar oDocument:
DocumentBuilder builder = JOOX.builder();
Document input = builder.parse(resourcePath);
Agora, precisamos selecioná-lo:
Match $ = $(input);
Para selecionarcustomer Element,, podemos usar o métodofind ou uma expressão XPath. Em ambos os casos, obteremos uma lista dos elementos que correspondem a ele.
Vamos ver o métodofind em ação:
$.find("to")
.get()
.stream()
.forEach(e -> e.setAttribute(attribute, newValue));
Para obter o resultado comoString, simplesmente precisamos chamar o métodotoString():
$.toString();
6. Referência
Para comparar o desempenho dessas bibliotecas, usamos um benchmarkJMH.
Vamos ver os resultados:
| 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 |
Como podemos ver, para este caso de uso e nossa implementação, dom4j e JAXP têm pontuações melhores que o jOOX.
7. Conclusão
Neste tutorial rápido, apresentamos como modificar atributos XML usando JAXP, dom4j e jOOX. Além disso, medimos o desempenho dessas bibliotecas com uma referência JMH.
Como de costume, todos os exemplos de código mostrados aqui estão disponíveis emGitHub.