Introdução ao XPath com Java

Introdução ao XPath com Java

1. Visão geral

Neste artigo, examinaremosthe basics of XPath with the support in the standard Java JDK.

Vamos usar um documento XML simples, processá-lo e ver como revisar o documento para extrair as informações que precisamos dele.

XPath é uma sintaxe padrão recomendada pelo W3C, é um conjunto de expressões para navegar em documentos XML. Você pode encontrar uma referência XPath completahere.

2. Um analisador XPath simples

import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;

public class DefaultParser {

    private File file;

    public DefaultParser(File file) {
        this.file = file;
    }
}

Agora vamos dar uma olhada mais de perto nos elementos que você encontrará emDefaultParser:

FileInputStream fileIS = new FileInputStream(this.getFile());
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = builderFactory.newDocumentBuilder();
Document xmlDocument = builder.parse(fileIS);
XPath xPath = XPathFactory.newInstance().newXPath();
String expression = "/Tutorials/Tutorial";
nodeList = (NodeList) xPath.compile(expression).evaluate(xmlDocument, XPathConstants.NODESET);

Vamos decompô-lo:

DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();

Usaremos esse objeto para produzir uma árvore de objetos DOM a partir do nosso documento xml:

DocumentBuilder builder = builderFactory.newDocumentBuilder();

Tendo uma instância desta classe, podemos analisar documentos XML de muitas fontes de entrada diferentes, comoInputStream,File,URLeSAX:

Document xmlDocument = builder.parse(fileIS);

UmDocument (org.w3c.dom.Document) representa todo o documento XML, é a raiz da árvore do documento, fornece nosso primeiro acesso aos dados:

XPath xPath = XPathFactory.newInstance().newXPath();

A partir do objeto XPath, acessaremos as expressões e as executaremos em nosso documento para extrair o que precisamos dele:

xPath.compile(expression).evaluate(xmlDocument, XPathConstants.NODESET);

Podemos compilar uma expressão XPath passada como string e definir que tipo de dados esperamos receberNODESET,NODE ouString, por exemplo.

3. Vamos começar

Agora que demos uma olhada nos componentes básicos que usaremos, vamos começar com algum código usando um XML simples, para fins de teste:



    
        Guava
  Introduction to Guava
  04/04/2016
  GuavaAuthor
    
    
        XML
  Introduction to XPath
  04/05/2016
  XMLAuthor
    

3.1. Recupere uma lista básica de elementos

O primeiro método é um uso simples de uma expressão XPath para recuperar uma lista de nós do XML:

FileInputStream fileIS = new FileInputStream(this.getFile());
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = builderFactory.newDocumentBuilder();
Document xmlDocument = builder.parse(fileIS);
XPath xPath = XPathFactory.newInstance().newXPath();
String expression = "/Tutorials/Tutorial";
nodeList = (NodeList) xPath.compile(expression).evaluate(xmlDocument, XPathConstants.NODESET);

Podemos recuperar a lista de tutorial contida no nó raiz usando a expressão acima, ou usando a expressão “//Tutorial”, mas esta irá recuperar todos os nós<Tutorial> no documento do nó atual, não importa onde estão localizados no documento, isso significa em qualquer nível da árvore a partir do nó atual.

ONodeList que ele retorna especificandoNODESET para a instrução de compilação como tipo de retorno é uma coleção ordenada de nós que pode ser acessada passando um índice como parâmetro.

3.2. Recuperando um Nó Específico por seu ID

Podemos procurar um elemento com base em qualquer ID, basta filtrar:

DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = builderFactory.newDocumentBuilder();
Document xmlDocument = builder.parse(this.getFile());
XPath xPath = XPathFactory.newInstance().newXPath();
String expression = "/Tutorials/Tutorial[@tutId=" + "'" + id + "'" + "]";
node = (Node) xPath.compile(expression).evaluate(xmlDocument, XPathConstants.NODE);

Usando esse tipo de expressão, podemos filtrar qualquer elemento que precisamos procurar apenas usando a sintaxe correta. Esse tipo de expressão é chamado de predicado e é uma maneira fácil de localizar dados específicos sobre um documento, por exemplo:

/Tutorials/Tutorial[1]

/Tutorials/Tutorial[first()]

/Tutorials/Tutorial[position()<4]

Você pode encontrar uma referência completa dos predicadoshere

3.3. Recuperando nós por um nome de tag específico

Agora vamos além, introduzindo eixos, vamos ver como isso funciona usando-o em uma expressão XPath:

Document xmlDocument = builder.parse(this.getFile());
this.clean(xmlDocument);
XPath xPath = XPathFactory.newInstance().newXPath();
String expression = "//Tutorial[descendant::title[text()=" + "'" + name + "'" + "]]";
nodeList = (NodeList) xPath.compile(expression).evaluate(xmlDocument, XPathConstants.NODESET);

Com a expressão usada acima, estamos procurando todo elemento<Tutorial> que tenha um descendente<title> com o texto passado como parâmetro na variável “nome”.

Seguindo o xml de amostra fornecido para este artigo, poderíamos procurar um<title> contendo o texto “Guava” ou “XML” e recuperaremos o elemento<Tutorial> inteiro com todos os seus dados.

Os eixos fornecem uma maneira muito flexível de navegar em um documento XML e você pode encontrar uma documentação completa emofficial site.

3.4. Manipulando dados em expressões

XPath nos permite manipular dados também nas expressões, se necessário.

XPath xPath = XPathFactory.newInstance().newXPath();
String expression = "//Tutorial[number(translate(date, '/', '')) > " + date + "]";
nodeList = (NodeList) xPath.compile(expression).evaluate(xmlDocument, XPathConstants.NODESET);

Nesta expressão estamos passando para o nosso método uma string simples como uma data que se parece com “ddmmaaaa”, mas o XML armazena esses dados com o formato “dd/mm/yyyy“, então para combinar um resultado manipulamos a string para convertê-lo para o formato de dados correto usado por nosso documento e fazemos isso usando uma das funções fornecidas porXPath

3.5. Recuperando elementos de um documento com namespace definido

Se nosso documento xml tiver um namespace definido como no exemplo_namespace.xml usado aqui, as regras para recuperar os dados de que precisamos serão alteradas, pois nosso xml começa assim:




Agora, quando usamos uma expressão semelhante a “//Tutorial”, não vamos obter nenhum resultado. Essa expressão XPath retornará todos os elementos<Tutorial> que não estão em nenhum namespace e, em nosso novo example_namespace.xml, todos os elementos<Tutorial> são definidos no namespace/full_archive.++

Vamos ver como lidar com namespaces.

Primeiro, precisamos definir o contexto do espaço para nome para que o XPath possa saber onde estamos procurando nossos dados:

xPath.setNamespaceContext(new NamespaceContext() {
    @Override
    public Iterator getPrefixes(String arg0) {
        return null;
    }
    @Override
    public String getPrefix(String arg0) {
        return null;
    }
    @Override
    public String getNamespaceURI(String arg0) {
        if ("bdn".equals(arg0)) {
            return "/full_archive";
        }
        return null;
    }
});

No método acima, estamos definindo “bdn” como o nome do nosso namespace “/full_archive“, e a partir de agora, precisamos adicionar “bdn” às expressões XPath usadas para localizar elementos:

String expression = "/bdn:Tutorials/bdn:Tutorial";
nodeList = (NodeList) xPath.compile(expression).evaluate(xmlDocument, XPathConstants.NODESET);

Usando a expressão acima, podemos recuperar todos os elementos<Tutorial> no namespace “bdn”.

3.6. Evitando problemas de nós de texto vazio

Como você pode notar, no código da seção 3.3 deste artigo, uma nova função é chamada logo após a análise de nosso XML para um objeto Document,this.clean(xmlDocument);

Às vezes, quando iteramos através de elementos, nós filhos e assim por diante, se nosso documento tiver nós de texto vazios, podemos encontrar um comportamento inesperado nos resultados que queremos obter.

Chamamosnode.getFirstChild() quando estamos iterando sobre todos os elementos<Tutorial> procurando as informações de<title>, mas ao invés do que estamos procurando, temos apenas “#Text” como um nó vazio.

Para corrigir o problema, podemos navegar pelo nosso documento e remover os nós vazios, como este:

NodeList childs = node.getChildNodes();
for (int n = childs.getLength() - 1; n >= 0; n--) {
    Node child = childs.item(n);
    short nodeType = child.getNodeType();
    if (nodeType == Node.ELEMENT_NODE) {
        clean(child);
    }
    else if (nodeType == Node.TEXT_NODE) {
        String trimmedNodeVal = child.getNodeValue().trim();
        if (trimmedNodeVal.length() == 0){
            node.removeChild(child);
        }
        else {
            child.setNodeValue(trimmedNodeVal);
        }
    } else if (nodeType == Node.COMMENT_NODE) {
        node.removeChild(child);
    }
}

Fazendo isso, podemos verificar cada tipo de nó que encontramos e remover aqueles de que não precisamos.

4. Conclusões

Aqui, acabamos de apresentar o suporte padrão fornecido pelo XPath, mas agora existem muitas bibliotecas populares como JDOM, Saxon, XQuery, JAXP, Jaxen ou Jackson agora. Também existem bibliotecas para análise de HTML específica, como JSoup.

Não se limita a java, as expressões XPath podem ser usadas pela linguagem XSLT para navegar em documentos XML.

Como você pode ver, há uma ampla gama de possibilidades sobre como lidar com esse tipo de arquivo.

Por padrão, há um ótimo suporte padrão para análise, leitura e processamento de documentos XML / HTML. Você pode encontrar a amostra de trabalho completahere.