Introduzione a XPath con Java

1. Panoramica

In questo articolo esamineremo le basi di XPath con il supporto nello standard Java JDK .

Utilizzeremo un semplice documento XML, lo elaboreremo e vedremo come esaminare il documento per estrarne le informazioni di cui abbiamo bisogno.

XPath è una sintassi standard consigliata dal W3C, è un insieme di espressioni per navigare nei documenti XML. Puoi trovare un riferimento completo a XPath qui.

2. Un semplice XPath Parser

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; } } 

Ora diamo uno sguardo più da vicino agli elementi che troverai nel DefaultParser :

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);

Analizziamolo:

DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();

Useremo questo oggetto per produrre un albero di oggetti DOM dal nostro documento xml:

DocumentBuilder builder = builderFactory.newDocumentBuilder();

Avendo un'istanza di questa classe, possiamo analizzare documenti XML da molte diverse sorgenti di input come InputStream , File , URL e SAX :

Document xmlDocument = builder.parse(fileIS);

Un documento ( org.w3c.dom.Document ) rappresenta l'intero documento XML, è la radice dell'albero del documento, fornisce il nostro primo accesso ai dati:

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

Dall'oggetto XPath accederemo alle espressioni e le eseguiremo sul nostro documento per estrarne ciò che ci serve:

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

Possiamo compilare un'espressione XPath passata come stringa e definire quale tipo di dati ci aspettiamo di ricevere, ad esempio , NODESET , NODE o String .

3. Iniziamo

Ora che abbiamo dato un'occhiata ai componenti di base che useremo, iniziamo con del codice usando un semplice XML, a scopo di test:

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

3.1. Recupera un elenco di elementi di base

Il primo metodo è un semplice utilizzo di un'espressione XPath per recuperare un elenco di nodi dall'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); 

Possiamo recuperare l'elenco dei tutorial contenuto nel nodo radice usando l'espressione sopra, o usando l'espressione " // Tutorial " ma questo recupererà tutto nodi nel documento dal nodo corrente, non importa dove si trovano nel documento, questo significa a qualunque livello dell'albero a partire dal nodo corrente.

La NodeList restituita specificando NODESET all'istruzione di compilazione come tipo di ritorno, è una raccolta ordinata di nodi a cui è possibile accedere passando un indice come parametro.

3.2. Recupero di un nodo specifico in base al suo ID

Possiamo cercare un elemento in base a un dato id semplicemente filtrando:

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 questo tipo di espressioni possiamo filtrare qualsiasi elemento dobbiamo cercare semplicemente usando la sintassi corretta. Questi tipi di espressioni sono chiamati predicati e rappresentano un modo semplice per individuare dati specifici su un documento, ad esempio:

/ Tutorial / Tutorial [1]

/ Tutorial / Tutorial [first ()]

/ Tutorial / Tutorial [position () <4]

Puoi trovare un riferimento completo dei predicati qui

3.3. Recupero dei nodi con un nome di tag specifico

Ora andiamo oltre introducendo gli assi, vediamo come funziona usandolo in un'espressione 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); 

Con l'espressione usata sopra, stiamo cercando ogni elemento che ha un discendente con il testo passato come parametro nella variabile “nome”.

Seguendo l'esempio xml fornito per questo articolo, potremmo cercare un file contenente il testo “Guava” o “XML” e recupereremo il tutto elemento con tutti i suoi dati.

Axes fornisce un modo molto flessibile per navigare in un documento XML e puoi trovare una documentazione completa nel sito ufficiale.

3.4. Manipolazione dei dati nelle espressioni

XPath ci consente di manipolare anche i dati nelle espressioni, se necessario.

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

In questa espressione stiamo passando al nostro metodo una semplice stringa come una data che assomiglia a "ddmmyyyy" ma l'XML memorizza questi dati con il formato " dd / mm / yyyy ", quindi per abbinare un risultato manipoliamo la stringa per convertirlo al formato dati corretto utilizzato dal nostro documento e lo facciamo utilizzando una delle funzioni fornite da XPath

3.5. Recupero di elementi da un documento con spazio dei nomi definito

If our xml document has a namespace defined as it is in the example_namespace.xml used here, the rules to retrieve the data we need are going to change since our xml starts like this:

Now when we use an expression similar to “//Tutorial”, we are not going to get any result. That XPath expression is going to return all elements that aren't under any namespace, and in our new example_namespace.xml, all elements are defined in the namespace /full_archive.

Lets see how to handle namespaces.

First of all we need to set the namespace context so XPath will be able to know where are we looking for our data:

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; } }); 

In the method above, we are defining “bdn” as the name for our namespace “/full_archive“, and from now on, we need to add “bdn” to the XPath expressions used to locate elements:

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

Using the expression above we are able to retrieve all elements under “bdn” namespace.

3.6. Avoiding Empty Text Nodes Troubles

As you could notice, in the code at the 3.3 section of this article a new function is called just right after parsing our XML to a Document object, this .clean( xmlDocument );

Sometimes when we iterate through elements, childnodes and so on, if our document has empty text nodes we can find an unexpected behavior in the results we want to get.

We called node .getFirstChild() when we are iterating over all elements looking for the information, but instead of what we are looking for we just have “#Text” as an empty node.

To fix the problem we can navigate through our document and remove those empty nodes, like this:

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); } }

By doing this we can check each type of node we find and remove those ones we don't need.

4. Conclusions

Here we just introduced the default XPath provided support, but there are many popular libraries as JDOM, Saxon, XQuery, JAXP, Jaxen or even Jackson now. There are libraries for specific HTML parsing too like JSoup.

It's not limited to java, XPath expressions can be used by XSLT language to navigate XML documents.

As you can see, there is a wide range of possibilities on how to handle these kind of files.

Esiste un ottimo supporto standard per impostazione predefinita per l'analisi, la lettura e l'elaborazione di documenti XML / HTML. Puoi trovare l'esempio completo di lavoro qui.