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

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

1. обзор

В этой статье мы рассмотримthe basics of XPath with the support in the standard Java JDK.

Мы собираемся использовать простой XML-документ, обработать его и посмотреть, как просмотреть документ, чтобы извлечь из него необходимую нам информацию.

XPath - это стандартный синтаксис, рекомендованный W3C, это набор выражений для навигации по документам XML. Вы можете найти полную ссылку на XPathhere.

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

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

Мы можем получить список руководств, содержащийся в корневом узле, с помощью выражения выше или с помощью выражения «//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);

Используя выражения такого типа, мы можем фильтровать любой элемент, который нам нужен, просто используя правильный синтаксис. Выражения такого типа называются предикатами и представляют собой простой способ найти конкретные данные в документе, например:

/Tutorials/Tutorial[1]

/Tutorials/Tutorial[first()]

/Tutorials/Tutorial[position()<4]

Вы можете найти полную ссылку на предикатыhere

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-документу, и вы можете найти полную документацию вofficial site.

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. Получение элементов из документа с определенным пространством имен

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




Теперь, когда мы используем выражение, подобное «//Tutorial», мы не получим никакого результата. Это выражение 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 на объект Documentthis.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. Вы можете найти полный рабочий образецhere.