Introduction à XPath avec Java

1. Vue d'ensemble

Dans cet article, nous allons passer en revue les bases de XPath avec le support dans le JDK Java standard .

Nous allons utiliser un simple document XML, le traiter et voir comment parcourir le document pour en extraire les informations dont nous avons besoin.

XPath est une syntaxe standard recommandée par le W3C, c'est un ensemble d'expressions pour naviguer dans les documents XML. Vous pouvez trouver une référence XPath complète ici.

2. Un analyseur XPath simple

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

Regardons maintenant de plus près les éléments que vous trouverez dans le 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);

Décomposons cela:

DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();

Nous utiliserons cet objet pour produire une arborescence d'objets DOM à partir de notre document xml:

DocumentBuilder builder = builderFactory.newDocumentBuilder();

Ayant une instance de cette classe, nous pouvons analyser des documents XML à partir de nombreuses sources d'entrée différentes telles que InputStream , File , URL et SAX :

Document xmlDocument = builder.parse(fileIS);

Un document ( org.w3c.dom.Document ) représente tout le document XML, est la racine de l'arborescence du document, fournit notre premier accès aux données:

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

À partir de l'objet XPath, nous accéderons aux expressions et les exécuterons sur notre document pour en extraire ce dont nous avons besoin:

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

Nous pouvons compiler une expression XPath passée sous forme de chaîne et définir le type de données que nous attendons de recevoir un tel NODESET , NODE ou String par exemple.

3. Commençons

Maintenant que nous avons jeté un coup d'œil aux composants de base que nous allons utiliser, commençons avec du code utilisant du XML simple, à des fins de test:

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

3.1. Récupérer une liste élémentaire d'éléments

La première méthode est une simple utilisation d'une expression XPath pour récupérer une liste de nœuds à partir du 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); 

On peut récupérer la liste du tutoriel contenue dans le nœud racine en utilisant l'expression ci-dessus, ou en utilisant l'expression « // Tutorial » mais celle-ci récupérera tout nœuds dans le document à partir du nœud actuel, peu importe où ils se trouvent dans le document, cela signifie à n'importe quel niveau de l'arbre à partir du nœud actuel.

Le NodeList qu'il retourne en spécifiant NODESET à l'instruction de compilation comme type de retour, est une collection ordonnée de nœuds auxquels on peut accéder en passant un index comme paramètre.

3.2. Récupération d'un nœud spécifique par son ID

Nous pouvons rechercher un élément basé sur un identifiant donné simplement en filtrant:

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

En utilisant ce type d'expressions, nous pouvons filtrer pour n'importe quel élément que nous devons rechercher simplement en utilisant la syntaxe correcte. Ces types d'expressions sont appelés prédicats et constituent un moyen simple de localiser des données spécifiques sur un document, par exemple:

/ Tutoriels / Tutoriel [1]

/ Tutoriels / Tutoriel [first ()]

/ Tutoriels / Tutoriel [position () <4]

Vous pouvez trouver une référence complète des prédicats ici

3.3. Récupération de nœuds par un nom de balise spécifique

Maintenant, nous allons plus loin en introduisant des axes, voyons comment cela fonctionne en l'utilisant dans une expression 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); 

Avec l'expression utilisée ci-dessus, nous recherchons chaque élément qui a un descendant avec le texte passé en paramètre dans la variable «nom».

À la suite de l'exemple de XML fourni pour cet article, nous pourrions rechercher un contenant le texte «Guava» ou «XML» et nous récupérerons le tout élément avec toutes ses données.

Les axes fournissent un moyen très flexible de naviguer dans un document XML et vous pouvez trouver une documentation complète sur le site officiel.

3.4. Manipulation de données dans des expressions

XPath nous permet également de manipuler les données dans les expressions si nécessaire.

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

Dans cette expression, nous transmettons à notre méthode une simple chaîne sous forme de date qui ressemble à «jjmmaaaa» mais le XML stocke ces données au format « jj / mm / aaaa », donc pour faire correspondre un résultat, nous manipulons la chaîne pour la convertir au format de données correct utilisé par notre document et nous le faisons en utilisant l'une des fonctions fournies par XPath

3.5. Récupération d'éléments d'un document avec un espace de noms défini

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.

Il existe par défaut un excellent support standard pour l'analyse, la lecture et le traitement des documents XML / HTML. Vous pouvez trouver l'exemple de travail complet ici.