【问题标题】:Dynamic lookup in VTD-XML using only XPath仅使用 XPath 在 VTD-XML 中进行动态查找
【发布时间】:2017-07-19 13:12:50
【问题描述】:

我正在尝试使用 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”的变量表达式声明为“.”。表示“当前的书”,但这也不起作用,因为(顾名思义),变量表达式不存储表达式的结果,而是表达式本身。

有没有办法只使用 XPath 表达式在 VTD-XML 中实现这种效果? (我意识到在代码中有多种实现方式,但我想使用纯 XPath,以便用户可以轻松创建描述其数据格式的配置文件)

编辑: 接受的答案的结果是我想要的不能使用单个 XPath 表达式来完成。我最终添加了一个选项,这样用户就可以基本上指定如何找到当前书籍的唯一标识符(即“./@id”或“./isbn”)。然后我的代码执行这个表达式并将结果替换为评级搜索 XPath 中的某个占位符(例如“$$”)。

【问题讨论】:

  • 不,我的问题已经得到解答。明天我会发送一个跟进,并附上这篇文章的链接。顺便感谢 VTD-XML,太好了!

标签: xml xpath vtd-xml


【解决方案1】:

//*/rating[./@book-id=//book/@id]/@value 这样的 XPath 表达式应该只检索与可用图书 ID 匹配的评级值。

如果您将&lt;rating book-id="3" value="4"/&gt; 添加到 XML 文档,XPath 将只返回第 1 和第 2 本书的值 53,因为没有 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 代码中获取当前书籍的 id,然后用它构造一个 XPath 表达式,但正如我解释的那样,我希望用户能够使用 XPath 来定义他们的文档格式,所以我不希望代码中有任何特定于格式的东西

VTD 仅支持 XPath 1.0。如果您(或您的客户)能够提出 XPath 1.0 查询,您也应该能够通过 VTD 提取相应的值。我想,普通的 XPath 查询不足以直接提供您需要的内容。

由于示例对于您需要的用例来说可能很简单,因此很难就如何设计您的应用程序来处理此类场景提供任何建议。也许用更详细的例子更新你的问题。处理此问题的一种简单方法是引入必须单独定义的占位符变量,然后在尝试执行此类 XPath 表达式时遇到此类占位符时,只需将这些占位符替换为先前提取值的具体值。

【讨论】:

  • 感谢您的回答,但我认为您错过了这样一个事实,即我正在迭代书籍并希望仅使用 XPath 获得 current 书籍的评级。 (是的,很容易在 Java 代码中获取当前书籍的 id,然后用它构造一个 XPath 表达式,但是正如我所解释的,我希望用户能够使用 XPath 来定义他们的文档格式,所以我不希望代码中有任何特定于格式的东西)
  • @zwets 更新帖子以使用当前处理的图书以提取名称和评级
  • 谢谢!我采用了类似于您在帖子中建议的方法,基本上实现了我自己的变量系统。顺便说一句,只是一个建议,但您可以在答案的顶部添加一个简短的注释,归结为“不,这是不可能的,但请参阅下面的建议”。
猜你喜欢
  • 2019-10-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-18
  • 1970-01-01
  • 2010-11-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多