Modificando um atributo XML em Java

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

Precisamos adicionar as dependênciasdom4jejaxen ao nossopom.xml para usar o dom4j em nosso projeto:


    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.