Manipulando arquivos XML em Java

Por que XML?

Nos dias de hoje, as empresas tem investido bastante em informatização dos seus serviços. Para torná-los acessíveis, os mesmos são expostos como serviços web, permitindo assim a sua utilização por qualquer pessoa. Agora imagine se cada empresa fizer o seu serviço gerar seus dados em um formato específico. Seria necessário um esforço de integração para que os serviços consigam se comunicar, concorda? E esse esforço torna os serviços bem menos atrativos, pois ninguém quer gastar tempo traduzindo os dados gerados por um serviço externo para serem consumidos pelos seus serviços internos. Uma forma de evitar esse problema é fazer os serviços conversarem na mesma língua, assim a comunicação seria natural, como a de pessoas que falam o mesmo idioma. É aí que entra o famoso XML. Ele é uma linguagem de marcação semelhante ao HTML, mas com o foco na estruturação de dados. Usando o XML, os serviços podem conversar na mesma língua, e assim evitamos ao máximo os esforços de integração entre sistemas.
💡 O JSON tem sido bastante utilizado como linguagem de comunicação entre serviços atualmente. Não quero entrar no mérito de quem é o melhor nesse post, cada um tem as suas vantagens. A questão é que desenvolvedores geralmente precisam trabalhar com ambos os formatos, pois não tem como ter o controle da linguagem que os serviços externos irão utilizar. Como, em minha experiência profissional, já vi ambas sendo utilizadas, acho importante falar sobre o XML e como trabalhar com ele no Java.

Sintaxe

Como qualquer linguagem, existem regras de sintaxe para construir documentos XML. A linguagem é bastante robusta para representar dados, incluindo mecanismos sofisticados como herança e validação de valores para garantir a integridade dos dados do arquivo.

O formato de um arquivo XML é simples: Uma hierarquia de tags. Para facilitar o entendimento, vamos pensar em uma entidade Pessoa. Pessoa possui um nome e uma idade. Para representar isso no XML, teríamos:

1
2
3
4
<pessoa>
 <nome>Joey Tribbiani</nome>
 <idade>28</idade>
</pessoa>

Observe que existe um comportamento hierárquico nessa representação, pois a pessoa (nó pai) possui um nome e uma idade (nós filhos). Se quisermos que o arquivo XML possua mais de uma pessoa, precisamos apenas criar uma tag superior chamada pessoas, que irá englobar várias tags pessoa:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<pessoas>
 <pessoa>
  <nome>Joey Tribbiani</nome>
  <idade>28</idade>
 </pessoa>
 <pessoa>
  <nome>Monica Geller</nome>
  <idade>28</idade>
 </pessoa>
 <pessoa>
  <nome>Phoebe Buffay</nome>
  <idade>28</idade>
 </pessoa>
 <pessoa>
  <nome>Rachel Green</nome>
  <idade>28</idade>
 </pessoa>
 <pessoa>
  <nome>Ross Geller</nome>
  <idade>28</idade>
 </pessoa>
</pessoas>

Para descrever informações inerentes às pessoas, e não aos dados da mesma, utilizamos atributos. Por exemplo, no nosso XML, todas as pessoas são personagens, então podemos adicionar o atributo tipo na tag pessoas:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<pessoas tipo="Personagens">
 <pessoa>
  <nome>Joey Tribbiani</nome>
  <idade>28</idade>
 </pessoa>
 <pessoa>
  <nome>Monica Geller</nome>
  <idade>28</idade>
 </pessoa>
 <pessoa>
  <nome>Phoebe Buffay</nome>
  <idade>28</idade>
 </pessoa>
 <pessoa>
  <nome>Rachel Green</nome>
  <idade>28</idade>
 </pessoa>
 <pessoa>
  <nome>Ross Geller</nome>
  <idade>28</idade>
 </pessoa>
</pessoas>

É possível adicionar outras informações, mas por simplicidade, vamos manter esse XML enxuto para os exemplos a seguir.

XSD

No tópico anterior, você entendeu como é a estrutura de um arquivo XML. Imagine agora que alguém precise criar um arquivo XML de dados seguindo o formato pessoas que nós estabelecemos. Se a pessoa se descuidar, poderá digitar as tags com nomes errados, e isso irá gerar problemas nos sistemas que consomem esses arquivos. Para evitar esse tipo de problema, existem os arquivos XSD. Esses arquivos estabelecem como deve ser a estrutura de um arquivo XML. É como se fosse a Interface de um Objeto Java. Se o Objeto não obedecer a Interface, ocorre erro. 

Para que você entenda como construir um XSD, vamos usar o XML de pessoas, elaborado no tópico anterior. A estrutura básica de um arquivo XSD é a seguinte:

1
2
<schema xmlns="http://www.w3.org/2001/XMLSchema">
</schema>

Todo o conteúdo do arquivo deverá estar delimitado pela tag schema. O atributo xmlsn referencia o namespace usado para encontrar os elementos adicionados no arquivo em questão, que no caso dos XSDs deverá ser o https://www.w3.org/2001/XMLSchema. Uma boa prática é adicionar apelidos aos namespaces, para evitar erros de conflito na busca de elementos. Lembre-se que um arquivo XSD pode conter a definição de vários elementos, que são encontrados através do seu namespace, por isso a ocorrência de conflitos é bastante provável. Para adicionar o apelido XSD, a mudança seria:

1
2
<schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
</schema>

De agora em diante, sempre que você informar uma tag do XMLSchema, deverá informá-la com o prefixo xsd. Essa prática deve parecer para você um desperdício de tempo, mas quando você estiver trabalhando com arquivos XSD complexos, vai notar quantos problemas serão evitados.

Continuando a elaboração do XSD, precisamos informar que existirão as tags do arquivo XML elaborado como modelo. Para isso, vamos usar a tag element:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <xsd:element name="pessoa">
  <xsd:complexType>
   <xsd:sequence>
    <xsd:element name="nome" type="xsd:string" />
    <xsd:element name="idade" type="xsd:integer" />
   </xsd:sequence>
  </xsd:complexType>
 </xsd:element>

 <xsd:element name="pessoas">
  <xsd:complexType>
   <xsd:sequence>
    <xsd:element ref="pessoa" maxOccurs="unbounded" />
   </xsd:sequence>
   <xsd:attribute name="tipo" type="xsd:string" />
  </xsd:complexType>
 </xsd:element>
</xsd:schema>

A tag element possui os atributos name e type, usados para informar o nome e tipo de dado que estará contido na tag. Veja que o elemento pessoa possui um tipo complexo, e isso é informado através da tag complexType. Todos os elementos desse tipo complexo estarão delimitados por uma tag sequence, como é possível observar no elemento pessoas. Para referenciar o elemento pessoa dentro do elemento pessoas, foi utilizado o atributo ref, que tem como valor o nome do elemento referenciado. O atributo maxOccurs com o valor unbounded foi utilizado para informar que podem existir várias tags pessoa dentro de pessoas. Finalmente, para representar o atributo tipo de pessoas, foi utilizada a tag attribute.

Mas e agora, como vincular o arquivo XML ao seu XSD? Isso será feito através dos atributos xsi:noNamespaceSchemaLocation e xmlns:xsi:

1
2
3
<pessoas tipo="Personagens" xsi:noNamespaceSchemaLocation="pessoas.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 [...]
</pessoas>

O caminho do XSD criado foi especificado no atributo xsi:noNamespaceSchemaLocation, e o namespace que contém esse atributo é especificado no atributo xmlns:xsi.

Com as mudanças realizadas, se você estiver usando o Eclipse, vai notar que se houver erros no arquivo XML, os mesmos serão apontados.

Na prática

Agora que você entendeu como um arquivo XML é escrito e a necessidade de utilizar essa linguagem, vamos criar um projeto para ilustrar como o seu sistema pode manipular esse tipo de arquivo em Java.

Pré-requisitos

Será necessário utilizar o Eclipse e criar um projeto Java para testar as implementações sugeridas. Esse projeto vai conter os arquivos pessoas.xml e pessoas.xsd elaborados anteriormente. Para a implementação, é necessário apenas criar uma classe Main, que vai permitir a execução do código de forma rápida.

Criando a factory: DocumentBuilderFactory

O Java possui uma biblioteca built-in para manipulação de XML, o javax.xml.parsers. Os parsers XML foram implementados utilizando o padrão de projeto Builder. Para criar um Builder, precisamos primeiro de uma fábrica, e ela é obtida através da classe DocumentBuilderFactory. Para obter a fábrica, precisamos do seguinte comando Java:

1
2
// Cria a fábrica de construtores de documentos
DocumentBuilderFactory fabrica = DocumentBuilderFactory.newInstance();

Com a fábrica gerada, podemos criar os builders que irão ler o nosso documento XML.

Criando o builder: DocumentBuilder

Vamos obter um builder a partir da fábrica criada no passo anterior utilizando a classe DocumentBuilder:

1
2
// Cria um construtor de documentos
DocumentBuilder builder = fabrica.newDocumentBuilder();

Com o builder podemos finalmente gerar um documento que conterá todos os dados do XML.

Document

No nosso exemplo, queremos ler dados de um documento XML e converter esses dados em um objeto, operação chamada na literatura de Unmarshal ou Deserialização. Para isso, vamos usar o método parse do nosso builder:

1
2
// Cria documento a partir de arquivo
Document document = builder.parse("src/pessoas.xml");

O documento está sendo construído a partir do arquivo pessoas.xml, localizado na pasta src do projeto. Se desejarmos realizar o parse com o texto do XML, poderíamos executar a operação da seguinte forma:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// Cria o documento a partir de uma String:
InputSource is = new InputSource(new StringReader("<pessoas tipo=\"Pessoa Física\">\n" + 
 " <pessoa>\n" + 
 "  <nome>Joey Tribbiani</nome>\n" + 
 "  <idade>28</idade>\n" + 
 " </pessoa>\n" + 
 " <pessoa>\n" + 
 "  <nome>Mônica Geller</nome>\n" + 
 "  <idade>28</idade>\n" + 
 " </pessoa>\n" + 
 "</pessoas>"));
Document document = builder.parse(is);

Conteúdo documento

Com os passos acima você tem o documento completo encapsulado no objeto document. Para obter os dados dele, vamos utilizar basicamente dois métodos: getDocumentElement e getElementsByTagName

O método getDocumentElement é útil para obtermos atributos do elemento do documento XML, pois ele retorna o objeto completo, inclusive referenciando as tags que ele possui dentro dele. Por exemplo, para obter o atributo tipo da pessoa, poderíamos utilizar os seguintes comandos:

1
2
Element pessoa = document.getDocumentElement();
String tipo = pessoa.getAttribute("tipo");

O método getAttribute recebe como parâmetro o nome do atributo que será recuperado do elemento. O valor retornado é uma String, mas você poderá fazer as devidas conversões se desejar trabalhar com outros objetos (e.g. Valores numéricos).

Para recuperar os nós do arquivo, vamos usar o método getElementsByTagName. Lembre-se que a estrutura de um arquivo XML é a de uma árvore, então é necessário percorrer nó por nó da estrutura para recuperar os dados. No nosso exemplo, o arquivo XML possui o primeiro nó com o nome pessoas. Dessa forma, precisamos obter todos os nós que possuem esse identificador:

1
2
3
NodeList nodeList = document.getElementsByTagName("pessoas");
for (int i = 0; i < nodeList.getLength(); i++) {
}

O método retorna um objeto do tipo NodeList, que encapsula todos os nós encontrados com a tag pessoas. Agora, vamos obter as pessoas dentro dessa tag. Para isso, vamos transformar os itens da NodeList em um Element:

1
2
3
4
5
for (int i = 0; i < nodeList.getLength(); i++) {
   Element elementPessoas = (Element) nodeList.item(i);
   NodeList nodeListPessoas = elementPessoas.getElementsByTagName("pessoa");
}

Veja que recuperamos todos os itens com o nome pessoa que estão dentro da tag pessoas, ou seja, obtemos todas as pessoas declaradas no arquivo XML. Agora precisamos imprimir os seus atributos internos, e para isso vamos fazer praticamente o mesmo procedimento acima, mudando apenas o nome da tag buscada na NodeList:

1
2
3
4
5
6
NodeList nodeListPessoas = elementPessoas.getElementsByTagName("pessoa");
for (int j = 0; j < nodeListPessoas.getLength(); j++) {
   Element elementPessoa = (Element) nodeListPessoas.item(j);
   System.out.print("Nome: " + elementPessoa.getElementsByTagName("nome").item(0).getTextContent() + ", ");
   System.out.println("Idade: " + elementPessoa.getElementsByTagName("idade").item(0).getTextContent());
}

Utilizamos novamente o método getElementsByTagName para obter uma NodeList e então obter cada elemento convertendo-os em objetos Element. Dentro do Element, utilizamos novamente o método getElementsByTagName informando cada tag do elemento pessoa.
💡 Observe que nos exemplos nós precisávamos conhecer a representação dos objetos dentro do XML, pois informamos os nomes das tags de forma explícita para obter suas informações. Por isso é tão importante a utilização de documentos XSD. Eles garantem a integridade do arquivo a ser lido, evitando erros e a necessidade de inúmeras verificações dentro do código.
💡 A técnica apresentada, conhecida como abordagem DOM, tem a vantagem de ser bastante simples e robusta para a deserialização de arquivos XML. Entretanto, como o DocumentBuilder carrega todo o documento na memória, se o mesmo for muito grande, essa abordagem será prejudicial ao desempenho da sua aplicação. Para contornar esse problema, o Java provê alternativas como as bibliotecas Sax e Stax, que permitem que você customize as operações de leitura do XML através de eventos. 

Validando o XML com o XSD

Para que o código de leitura do XML valide o documento com base no seu XSD, é necessário informar a nossa fábrica que ela precisa realizar a validação. Isso pode ser feito da seguinte forma:

1
2
3
4
// Valida o arquivo com o XSD
factory.setValidating(true);
factory.setNamespaceAware(true);
factory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema");

As três linhas acima informam a fábrica que ela deve validar o arquivo XML, reconhecendo os namespaces utilizados, e reconhecendo a linguagem XMLSchema, que é a utilizada no arquivo XSD. Para testar, adicione um erro no XML e execute o código novamente. A mensagem de erro será logada no console informando o problema no arquivo XML mal-formatado.
💡 Se você adicionar um erro no arquivo XML e executar o programa, vai notar que será disparado um log de erro a respeito do arquivo inválido, mas esse log não impede a execução do programa. Esse erro pode ser melhor tratado por um handler, que você poderá informar através do método setErrorHandler.

Biding de Elementos - JAXB

Para carregar o conteúdo de um arquivo XML na memória, utilizamos a abordagem DOM, que recupera os dados do XML carregando-os em um objeto NodeList. Mas e se você precisar carregar esses dados em objetos Java customizados, que de fato representam os dados do arquivo XML? Isso pode ser feito através da operação conhecida como biding, utilizando a biblioteca JAXB. Com ela você não precisa implementar o carregamento dos dados de arquivos XML, é necessário apenas anotar as classes a serem mapeadas do XML e utilizar o Unmarshaller disponibilizado na biblioteca.

Vamos voltar ao nosso exemplo de arquivo XML e criar um elemento que engloba todos eles, para que seja possível criar uma classe que represente todo o conteúdo do arquivo. Para isso, criaremos o arquivo friends.xml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<friends>
 <pessoas tipo="Personagens"
  xsi:noNamespaceSchemaLocation="pessoas.xsd"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <pessoa>
   <nome>Joey Tribbiani</nome>
   <idade>28</idade>
  </pessoa>
  <pessoa>
   <nome>Monica Geller</nome>
   <idade>28</idade>
  </pessoa>
  <pessoa>
   <nome>Phoebe Buffay</nome>
   <idade>28</idade>
  </pessoa>
  <pessoa>
   <nome>Rachel Green</nome>
   <idade>28</idade>
  </pessoa>
  <pessoa>
   <nome>Ross Geller</nome>
   <idade>28</idade>
  </pessoa>
 </pessoas>
</friends>

A classe equivalente a esse arquivo seria criada da seguinte forma:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package br.com.condessalovelace.javaxml;

import java.util.List;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD) 
public class Friends {
 @XmlElementWrapper(name = "pessoas")
 @XmlElement(name = "pessoa") 
 public List<Pessoa> pessoas;

 public List<Pessoa> getPessoas() {
  return pessoas;
 }

 public void setPessoas(List<Pessoa> pessoas) {
  this.pessoas = pessoas;
 }

 @Override
 public String toString() {
  return pessoas.toString();
 }
}

Para informar que a classe Friends representa um documento XML, foi utilizada a anotação @XmlRootElement. Quando o arquivo for deserializado, é necessário informar como os dados serão acessados, e para fazê-lo através dos atributos da classe foi utilizada a anotação @XmlAccessorType. Como o documento possui uma coleção de dados, foi utilizada a notação @XmlElementWrapper para encapsular a lista de pessoas, que por sua vez contém o elemento pessoa, anotado como @XmlElement. Com esse mapeamento, o deserializador vai saber onde carregar os dados lidos do XML.

E então, como implementar a deserialização? Através a classe Unmarshaller:

1
2
3
4
JAXBContext context = JAXBContext.newInstance(Friends.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
Friends pessoas = (Friends) unmarshaller.unmarshal(new FileInputStream("src/friends.xml"));
System.out.println(pessoas);

Através do JAXBContext foi obtido um Unmarshaller que recebe o arquivo XML como parâmetro para realizar o carregamento dos dados na classe anotada como @XmlRootElement.

Podemos também realizar a operação contrária, i.e., carregar os dados de uma classe num arquivo XML. Para isso, vamos apenas mudar o método utilizado para marshal e passar os devidos parâmetros para a escrita do arquivo XML:

1
2
3
4
5
Friends friends = new Friends();
friends.setPessoas(new ArrayList<>());
friends.getPessoas().add(new Pessoa("Phoebe Buffay", 28));
Marshaller marshaller = context.createMarshaller();
marshaller.marshal(friends, new File("src/friends_novo.xml"));

Se você executar o código acima, será criado um arquivo XML com a pessoa "Phoebe Buffay", que foi a personagem adicionada na lista de pessoas do objeto friends.
💡 A técnica de biding apresentada para serializar/deserializar XML é bastante intuitiva pois é baseada em objetos Java, que precisam apenas ser anotados para que seja possível mapear as tags XML. A desavantagem aqui é a mesma da técnica DOM: Por simplicidade de uso, a performance é comprometida já que o conteúdo do objeto completo é carregado em memória. O desenvolvedor tem que ter a consciência dos prós e contras para optar ou não por essa abordagem. Se o arquivo XML não for muito grande, vale a pena apostar no JAXB.

Consultas em arquivos XML: XPath

O que vimos até agora foi uma forma de ler um arquivo XML utilizando uma abordagem DOM. Mas, e para buscar dados dentro de um arquivo, existe uma forma eficiente que não seja por cláusulas if? Pensando nisso foi criado o XPath. Como o nome sugere, é uma linguagem baseada em diretórios que permite acessar elementos do DOM adicionando inclusive cláusulas para consulta de dados. Para exemplificar, suponha que no nosso documento precisemos recuperar a segunda pessoa de um arquivo XML. Isso seria feito com a expressão: /pessoas/pessoa[2]. Veja que parece de fato um caminho, pois partimos da primeira tag pessoas e então vamos para a próxima tag filha, pessoa. Como existem múltiplas ocorrências dessa tag, é possível informar o índice do elemento usando o operador colchete. E se quisermos filtrar apenas as pessoas de nome "Geller"? Poderíamos usar a expressão: /pessoas/pessoa[contains(nome,'Geller')]. Utilizamos a função contains para verificar na tag nome se existe algum texto com valor "Geller". Existem diversas funções na linguagem, sugiro ao leitor consultá-las na documentação de referência.

Deu para notar que as expressões XPath são bastante simples. Então, como usá-las no Java? O pacote javax.xml.xpath possui uma factory e classes para processar expressões XPath. O trecho abaixo mostra como implementar a consulta pelas pessoas de sobrenome "Geller":

1
2
3
4
5
String expressao = "/pessoas/pessoa[contains(nome,'Geller')]";
XPath xPath = XPathFactory.newInstance().newXPath();
XPathExpression xPathExpression = xPath.compile(expressao);
NodeList nodeList = (NodeList) xPathExpression.evaluate(document, XPathConstants.NODESET);
// Continua a iteração que já vimos para o NodeList ...

Com essa expressão, ao iterar pela NodeList já vamos ter as tags pessoa filtradas, o que facilita bastante o nosso trabalho pois não precisaremos percorrer todos os elementos do DOM para chegar ao elemento desejado.

Conclusão

Esse post mostrou como utilizar o XML na prática para fazer aplicações se comunicarem na mesma língua. Existem outras formas de ler uma arquivo XML que foram adicionadas no projeto de exemplo. Cabe a você leitor se aprofundar no assunto, para encontrar a melhor forma de trabalhar com o XML em seus projetos.

Referências

Comentários

  1. Você me fez voltar no tempo agora, de quando eu estudava XML, WSDL e uma linguagem de composição de serviços web chamada PEWS, com que trabalhei no meu tcc. Ótimos tempos =)

    ResponderExcluir

Postar um comentário

Postagens mais visitadas deste blog

Configurando um projeto Java no GitHub com Travis e SonarLint

Análise de desempenho de aplicações Java usando o Metrics