Динамический поиск в VTD-XML с использованием только XPath

Я пытаюсь использовать выражение XPath, чтобы найти элементы, ссылающиеся на текущий элемент в VTD-XML. Скажем, мой XML содержит книги и рейтинги и выглядит так:

<root>
  <book id="1" name="Book1"/>
  <book id="2" name="Book1"/>
  <rating book-id="1" value="5"/>
  <rating book-id="2" value="3"/>
</root>

Сначала я перебираю все элементы книги. Затем для каждой книги я хочу выполнить выражение XPath, которое выбирает рейтинг для этой книги. Например:

/root/rating[@book-id=current()/@id]/@value

Это не работает, так как функция current() является эксклюзивной для XSLT. Поэтому я попытался объявить переменное выражение с именем "current" в "." означать "текущую книгу", но это тоже не работает, потому что (как следует из названия) переменное выражение хранит не результаты выражения, а само выражение.

Есть ли способ достичь этого эффекта в VTD-XML, используя только выражение XPath? (Я понимаю, что есть разные способы сделать это в коде, но я хочу использовать чистый XPath, чтобы пользователи могли легко создать файл конфигурации, описывающий их формат данных)

РЕДАКТИРОВАТЬ: Результатом принятого ответа является то, что я не могу сделать с помощью одного выражения XPath. В итоге я добавил опцию, чтобы пользователи могли по существу указать, как можно найти уникальный идентификатор для текущей книги (например, "./@id" или, возможно, "./isbn"). Затем мой код выполняет это выражение и подставляет результат для некоторого заполнителя (например, "$$") в XPath для поиска по рейтингу.

1 ответ

Решение

Выражение XPath, подобное //*/rating[./@book-id=//book/@id]/@value следует извлекать только значения рейтинга для оценок, которые соответствуют доступным идентификаторам книг.

Если бы вы добавили <rating book-id="3" value="4"/> в XML-документ XPath будет возвращать только значения 5 а также 3 для книг 1 и 2, так как не было книги с ID 3.

Простой метод тестирования с VTD может выглядеть так:

@Test
public void xpathReference() throws Exception {
    byte[] bytes = ("<root>\n"
                 + "  <book id=\"1\" name=\"Book1\"/>\n"
                 + "  <book id=\"2\" name=\"Book1\"/>\n"
                 + "  <rating book-id=\"1\" value=\"5\"/>\n"
                 + "  <rating book-id=\"2\" value=\"3\"/>\n"
                 + "  <rating book-id=\"3\" value=\"4\"/>\n"
                 + "</root>").getBytes();

    VTDGen vtdGenerator = new VTDGen();
    vtdGenerator.setDoc(bytes);
    vtdGenerator.parse(true);
    VTDNav vtdNavigator = vtdGenerator.getNav();

    AutoPilot autoPilot = new AutoPilot(vtdNavigator);
    autoPilot.selectXPath("//*/rating[./@book-id=//book/@id]/@value");
    int id;
    int count = 0;
    while ((id = autoPilot.evalXPath()) != -1) {
        String elementName = vtdNavigator.toString(id);
        int text = vtdNavigator.getAttrVal(elementName);
        String txt = text != -1 ? vtdNavigator.toNormalizedString(text) : "";
        System.out.println("Found match at ID " + id + " in field name '" + elementName + "' with value '" + txt + "'");
        count++;
    }
    System.out.println("Total number of matches: " + count);
    assertThat(count, is(equalTo(2)));
}

При выполнении этого метода теста вы должны увидеть вывод, похожий на этот:

Found match at ID 15 in field name 'value' with value '5'
Found match at ID 20 in field name 'value' with value '3'
Total number of matches: 2

Согласно комментарию, приведенный выше код не извлекал данные для текущей обработанной книги итеративным образом. Код ниже теперь пытается достичь этого:

@Test
public void xpathReference() throws Exception {
    byte[] bytes = ("<root>\n"
                    + "  <book id=\"1\" name=\"Book1\"/>\n"
                    + "  <book id=\"2\" name=\"Book2\"/>\n"
                    + "  <book id=\"4\" name=\"Book3\"/>\n"
                    + "  <rating book-id=\"1\" value=\"5\"/>\n"
                    + "  <rating book-id=\"2\" value=\"3\"/>\n"
                    + "  <rating book-id=\"3\" value=\"4\"/>\n"
                    + "</root>").getBytes();

    VTDGen vtdGenerator = new VTDGen();
    vtdGenerator.setDoc(bytes);
    vtdGenerator.parse(true);
    VTDNav vtdNavigator = vtdGenerator.getNav();

    AutoPilot autoPilot = new AutoPilot(vtdNavigator);
    autoPilot.selectXPath("//book/@id");
    int id;
    int count = 0;
    while ((id = autoPilot.evalXPath()) != -1) {
        String elementName = vtdNavigator.toString(id);
        int bookId_id = vtdNavigator.getAttrVal(elementName);
        String bookId = bookId_id != -1 ? vtdNavigator.toNormalizedString(bookId_id) : "";

        AutoPilot xpathBookName = new AutoPilot(vtdNavigator);
        xpathBookName.selectXPath("//book[@id=" + bookId + "]/@name");
        String bookName = xpathBookName.evalXPathToString();

        AutoPilot xpathRating = new AutoPilot(vtdNavigator);
        xpathRating.selectXPath("//rating[@book-id=" + bookId + "]/@value");
        String bookRating = xpathRating.evalXPathToString();

        if ("".equals(bookRating)) {
            System.out.println("Book " + bookName + " with id " + bookId + " has no rating yet");
        } else {
            System.out.println("Book " + bookName + " with id " + bookId + " has a rating of " + bookRating);
        }
        count++;
    }
    System.out.println("Total number of matches: " + count);
    assertThat(count, is(equalTo(3)));
}

Если вы выполните последний код, вы должны увидеть вывод, подобный следующему:

Book Book1 with id 1 has a rating of 5
Book Book2 with id 2 has a rating of 3
Book Book3 with id 4 has no rating yet
Total number of matches: 2

Обратите внимание, что я немного обновил название вашей второй книги, чтобы вы могли легче увидеть различия.


... и да, легко получить идентификатор текущей книги в коде Java и затем создать выражение XPath с этим, но, как я объяснил, я хочу, чтобы пользователь мог использовать XPath для определения формата своего документа так что я не хочу никаких специфических для формата вещей в коде

VTD поддерживает только XPath 1.0. Если вы (или ваши клиенты) можете предложить запрос XPath 1.0, вы также сможете извлечь соответствующие значения через VTD. Я предполагаю, что простые запросы XPath не достаточно выразительны, чтобы напрямую доставлять то, что вам нужно.

Поскольку пример может быть простым для нужного вам варианта использования, трудно дать какие-либо рекомендации о том, как спроектировать ваше приложение для обработки таких сценариев. Возможно, обновите ваш вопрос более подробными примерами. Один из простых способов справиться с этим - ввести переменные-заполнители, которые должны быть определены отдельно, а затем при попадании в такие заполнители при попытке выполнить такое выражение XPath, - просто заменить эти заполнители конкретными значениями ранее извлеченных значений.

Другие вопросы по тегам