Въведение в XPath с Java

1. Общ преглед

В тази статия ще разгледаме основите на XPath с поддръжката в стандартния Java JDK .

Ще използваме прост XML документ, ще го обработим и ще видим как да прегледаме документа, за да извлечем от него необходимата информация.

XPath е стандартен синтаксис, препоръчан от W3C, това е набор от изрази за навигация в XML документи. Можете да намерите пълна справка за XPath тук.

2. Прост анализатор на XPath

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

Сега нека разгледаме по-отблизо елементите, които ще намерите в 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);

Нека разбием това:

DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();

Ще използваме този обект, за да създадем DOM дърво на обекти от нашия xml документ:

DocumentBuilder builder = builderFactory.newDocumentBuilder();

Като екземпляр на този клас, можем да анализираме XML документи от много различни източници на вход като InputStream , File , URL и SAX :

Document xmlDocument = builder.parse(fileIS);

А Документ ( org.w3c.dom.Document ) представлява целия XML документ, е коренът на дървото с документи, предвижда първата ни достъп до информация:

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

От обекта XPath ще получим достъп до изразите и ще ги изпълним над нашия документ, за да извлечем от него това, от което се нуждаем:

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

Можем да компилираме XPath израз, предаден като низ и да дефинираме какъв тип данни очакваме да получим такъв NODESET , NODE или String например.

3. Нека започнем

Сега, когато разгледахме основните компоненти, които ще използваме, нека започнем с някакъв код, използвайки някакъв прост XML, за целите на тестването:

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

3.1. Изтеглете основен списък с елементи

Първият метод е просто използване на XPath израз за извличане на списък с възли от 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); 

Можем да извлечем списъка с уроци, съдържащ се в основния възел, като използваме израза по-горе или чрез израза „ // Урок “, но този ще извлече всички възли в документа от текущия възел, независимо къде се намират в документа, това означава на каквото и да е ниво на дървото, започвайки от текущия възел.

В NodeList го връща, като се посочва NODESET на компилация инструкция като тип връщане, е подредена съвкупност от възли, които могат да бъдат достъпни чрез преминаване индекс като параметър.

3.2. Извличане на конкретен възел по неговия идентификатор

Можем да търсим елемент въз основа на всеки даден идентификатор само чрез филтриране:

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

Използвайки този вид изрази, ние можем да филтрираме за какъвто и да е елемент, който трябва да търсим, само като използваме правилния синтаксис. Този вид изрази се наричат ​​предикати и те са лесен начин за намиране на конкретни данни в документ, например:

/ Уроци / Урок [1]

/ Уроци / Урок [първи ()]

/ Уроци / Урок [позиция () <4]

Можете да намерите пълна справка за предикати тук

3.3. Извличане на възли чрез конкретно име на етикет

Сега ще продължим по-нататък, като въведем оси, нека видим как работи това, като го използваме в израз на 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); 

С израза, използван по-горе, ние търсим всеки елемент, който има потомък с текст, предаден като параметър в променливата “name”.

Следвайки примерния xml, предоставен за тази статия, бихме могли да потърсим съдържащ текста „Гуава“ или „XML“ и ние ще извлечем цялото елемент с всичките му данни.

Осите предоставят много гъвкав начин за навигация в XML документ и можете да намерите пълна документация за него на официалния сайт.

3.4. Манипулиране на данни в изрази

XPath ни позволява да манипулираме данни и в изразите, ако е необходимо.

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

В този израз ние предаваме на нашия метод прост низ като дата, която прилича на „ddmmyyyy“, но XML съхранява тези данни с формата „ dd / mm / yyyy “, така че за да съвпаднем с резултат, ние манипулираме низа, за да го преобразуваме към правилния формат на данните, използван от нашия документ и го правим, като използваме една от функциите, предоставени от XPath

3.5. Извличане на елементи от документ с дефинирано пространство от имена

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.

По подразбиране има чудесна стандартна поддръжка за анализиране, четене и обработка на XML / HTML документи. Можете да намерите пълната работна проба тук.