Einführung in XPath mit Java

1. Übersicht

In diesem Artikel werden wir die Grundlagen von XPath mit der Unterstützung im Standard-Java-JDK erläutern .

Wir werden ein einfaches XML-Dokument verwenden, es verarbeiten und sehen, wie Sie das Dokument durchgehen, um die benötigten Informationen daraus zu extrahieren.

XPath ist eine vom W3C empfohlene Standardsyntax. Es handelt sich um eine Reihe von Ausdrücken zum Navigieren in XML-Dokumenten. Eine vollständige XPath-Referenz finden Sie hier.

2. Ein einfacher 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; } } 

Schauen wir uns nun die Elemente im DefaultParser genauer an :

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

Lassen Sie uns das zusammenfassen:

DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();

Wir werden dieses Objekt verwenden, um einen DOM-Objektbaum aus unserem XML-Dokument zu erstellen:

DocumentBuilder builder = builderFactory.newDocumentBuilder();

Mit einer Instanz dieser Klasse können wir XML-Dokumente aus vielen verschiedenen Eingabequellen wie InputStream , File , URL und SAX analysieren :

Document xmlDocument = builder.parse(fileIS);

Ein Dokument ( org.w3c.dom.Document ) stellt das gesamte XML-Dokument dar, ist das Stammverzeichnis des Dokumentbaums und bietet unseren ersten Zugriff auf Daten:

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

Vom XPath-Objekt aus greifen wir auf die Ausdrücke zu und führen sie über unser Dokument aus, um das zu extrahieren, was wir benötigen:

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

Wir können einen als Zeichenfolge übergebenen XPath-Ausdruck kompilieren und definieren, welche Art von Daten wir beispielsweise für einen solchen NODESET , NODE oder String erwarten .

3. Fangen wir an

Nachdem wir uns nun die Basiskomponenten angesehen haben, die wir verwenden werden, beginnen wir mit etwas Code, der zu Testzwecken einfaches XML verwendet:

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

3.1. Rufen Sie eine grundlegende Liste von Elementen ab

Die erste Methode ist die einfache Verwendung eines XPath-Ausdrucks zum Abrufen einer Liste von Knoten aus dem 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); 

Wir können die im Stammknoten enthaltene Tutorial-Liste mit dem obigen Ausdruck oder mit dem Ausdruck " // Tutorial " abrufen, aber dieses wird alle abrufen Knoten im Dokument vom aktuellen Knoten, unabhängig davon, wo sie sich im Dokument befinden. Dies bedeutet, dass auf jeder Ebene des Baums vom aktuellen Knoten aus begonnen wird.

Die NodeList , die durch Angabe von NODESET für den Kompilierungsbefehl als Rückgabetyp zurückgegeben wird, ist eine geordnete Sammlung von Knoten, auf die durch Übergabe eines Index als Parameter zugegriffen werden kann.

3.2. Abrufen eines bestimmten Knotens anhand seiner ID

Wir können nach einem Element suchen, das auf einer bestimmten ID basiert, indem wir einfach filtern:

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

Mit dieser Art von Ausdrücken können wir nach jedem Element filtern, nach dem wir suchen müssen, indem wir einfach die richtige Syntax verwenden. Diese Art von Ausdrücken wird als Prädikate bezeichnet und ist eine einfache Möglichkeit, bestimmte Daten über ein Dokument zu lokalisieren, zum Beispiel:

/ Tutorials / Tutorial [1]

/ Tutorials / Tutorial [first ()]

/ Tutorials / Tutorial [position () <4]

Eine vollständige Referenz der Prädikate finden Sie hier

3.3. Abrufen von Knoten anhand eines bestimmten Tag-Namens

Jetzt gehen wir weiter, indem wir Achsen einführen. Lassen Sie uns sehen, wie dies funktioniert, indem Sie es in einem XPath-Ausdruck verwenden:

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

Mit dem oben verwendeten Ausdruck suchen wir nach jedem Element, das einen Nachkommen hat mit dem Text als Parameter in der Variablen "Name" übergeben.

Nach der für diesen Artikel bereitgestellten Beispiel-XML könnten wir nach einem suchen mit dem Text "Guava" oder "XML" und wir werden das Ganze abrufen Element mit all seinen Daten.

Achsen bieten eine sehr flexible Möglichkeit zum Navigieren in einem XML-Dokument. Eine vollständige Dokumentation finden Sie auf der offiziellen Website.

3.4. Bearbeiten von Daten in Ausdrücken

Mit XPath können wir bei Bedarf auch Daten in den Ausdrücken bearbeiten.

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

In diesem Ausdruck übergeben wir unserer Methode eine einfache Zeichenfolge als Datum, das wie "TTMJJJJ" aussieht, aber das XML speichert diese Daten im Format " TT / MM / JJJJ ". Um ein Ergebnis zu erzielen , bearbeiten wir die Zeichenfolge, um sie zu konvertieren auf das richtige Datenformat, das von unserem Dokument verwendet wird, und wir verwenden dazu eine der von XPath bereitgestellten Funktionen

3.5. Abrufen von Elementen aus einem Dokument mit definiertem Namespace

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.

Standardmäßig gibt es eine hervorragende Standardunterstützung für das Parsen, Lesen und Verarbeiten von XML / HTML-Dokumenten. Das vollständige Arbeitsmuster finden Sie hier.