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
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
Para descrever informações inerentes às pessoas, e não aos dados da mesma, utilizamos
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 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.
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