Перейти к основному содержимому

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

· 7 мин. чтения

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 для целей тестирования:

<?xml version="1.0"?>
<Tutorials>
<Tutorial tutId="01" type="java">
<title>Guava</title>
<description>Introduction to Guava</description>
<date>04/04/2016</date>
<author>GuavaAuthor</author>
</Tutorial>
<Tutorial tutId="02" type="java">
<title>XML</title>
<description>Introduction to XPath</description>
<date>04/05/2016</date>
<author>XMLAuthor</author>
</Tutorial>
</Tutorials>

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

Мы можем получить список учебных пособий, содержащийся в корневом узле, с помощью приведенного выше выражения или с помощью выражения « //Tutorial », но это извлечет все узлы <Tutorial> в документе из текущего узла, независимо от того, где они расположены. в документе это означает любой уровень дерева, начиная с текущего узла.

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]

/Учебники/Учебник[первый()]

/Учебники/Учебник[position()<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);

С помощью выражения, использованного выше, мы ищем каждый элемент <Tutorial> , у которого есть потомок <title> с текстом, переданным в качестве параметра в переменной «name».

Следуя примеру XML, предоставленному для этой статьи, мы могли бы найти <title> , содержащий текст «Guava» или «XML», и получить весь элемент <Tutorial> со всеми его данными.

Оси обеспечивают очень гибкий способ навигации по 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);

В этом выражении мы передаем нашему методу простую строку в виде даты, которая выглядит как «ддммгггг», но XML хранит эти данные в формате « дд/мм/гггг », поэтому, чтобы соответствовать результату, мы манипулируем строкой для ее преобразования. к правильному формату данных, используемому нашим документом, и мы делаем это с помощью одной из функций, предоставляемых XPath

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

Если в нашем xml-документе пространство имен определено так же, как в используемом здесь example_namespace.xml, правила извлечения необходимых нам данных изменятся, поскольку наш xml начинается так:

<?xml version="1.0"?>
<Tutorials xmlns="/full_archive">

</Tutorials>

Теперь, когда мы используем выражение, похожее на « //Tutoria l», мы не получим никакого результата. Это выражение XPath вернет все элементы <Tutorial> , которые не находятся ни в одном пространстве имен, а в нашем новом example_namespace.xml все элементы <Tutorial> определены в пространстве имен /full_archive .

Давайте посмотрим, как обращаться с пространствами имен.

Прежде всего нам нужно установить контекст пространства имен, чтобы XPath мог знать, где мы ищем наши данные:

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

В приведенном выше методе мы определяем « bdn » как имя для нашего пространства имен « /full_archive », и с этого момента нам нужно добавить « bdn » к выражениям XPath, используемым для поиска элементов:

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

Используя приведенное выше выражение, мы можем получить все элементы <Tutorial> в пространстве имен « bdn ».

3.6. Как избежать проблем с пустыми текстовыми узлами

Как вы могли заметить, в коде в разделе 3.3 этой статьи новая функция вызывается сразу после преобразования нашего XML в объект Document, это .clean( xmlDocument );

Иногда, когда мы перебираем элементы, дочерние узлы и т. д., если в нашем документе есть пустые текстовые узлы, мы можем обнаружить неожиданное поведение в результатах, которые хотим получить.

Мы вызвали node .getFirstChild() , когда перебираем все элементы <Tutorial> в поисках информации <title> , но вместо того, что мы ищем, мы имеем просто «#Text» в качестве пустого узла.

Чтобы решить эту проблему, мы можем перемещаться по нашему документу и удалять эти пустые узлы, например:

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

Делая это, мы можем проверить каждый тип найденного узла и удалить те, которые нам не нужны.

4. Выводы

Здесь мы только что представили поддержку XPath по умолчанию, но сейчас есть много популярных библиотек, таких как JDOM, Saxon, XQuery, JAXP, Jaxen или даже Jackson. Существуют библиотеки для специального анализа HTML, такие как JSoup.

Это не ограничивается java, выражения XPath могут использоваться языком XSLT для навигации по XML-документам.

Как видите, существует широкий спектр возможностей обработки файлов такого типа.

По умолчанию существует отличная стандартная поддержка для разбора, чтения и обработки документов XML/HTML. Полный рабочий образец вы можете найти здесь .