仅使用 XPath 在 VTD-XML 中进行动态查找
Dynamic lookup in VTD-XML using only 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" 的变量表达式声明为“.”。意思是 "the current book",但这也不起作用,因为(顾名思义)变量表达式不存储表达式的结果,而是表达式本身。
有没有办法在 VTD-XML 中仅使用 XPath 表达式来实现这种效果?
(我知道在代码中有多种方法,但我想使用纯 XPath,以便用户可以轻松创建描述其数据格式的配置文件)
编辑:
接受的答案的结果是我想要的东西不能使用单个 XPath 表达式来完成。我最后添加了一个选项,这样用户就可以基本上指定如何找到当前图书的唯一标识符(即“./@id”,或者可能是“./isbn”)。然后我的代码执行此表达式并将结果替换为评分搜索 XPath 中的某些占位符(例如“$$”)。
像 //*/rating[./@book-id=//book/@id]/@value
这样的 XPath 表达式应该只检索与可用图书 ID 匹配的评分的评分值。
如果您将 <rating book-id="3" value="4"/>
添加到 XML 文档,则 XPath 将 return 仅为第 1 本书和第 2 本书的值 5
和 3
没有 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
请注意,我稍微更新了您的第二本书的名称,以便您更容易看出差异。
... and yes, it is easy to just get the id of the current book in Java code and then construct an XPath expression with that, but as I explained, I want the user to be able to use XPath to define their document format, so I don't want any format-specific stuff in the code
VTD 仅支持 XPath 1.0。如果您(或您的客户)能够提出 XPath 1.0 查询,您应该也能够通过 VTD 提取相应的值。我想,普通的 XPath 查询的表现力不足以直接提供您需要的内容。
由于该示例对于您需要的用例来说可能过于简单,因此很难就如何设计您的应用程序来处理此类场景给出任何建议。也许用更详细的例子更新你的问题。处理此问题的一种简单方法是引入必须单独定义的占位符变量,然后在尝试执行此类 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" 的变量表达式声明为“.”。意思是 "the current book",但这也不起作用,因为(顾名思义)变量表达式不存储表达式的结果,而是表达式本身。
有没有办法在 VTD-XML 中仅使用 XPath 表达式来实现这种效果? (我知道在代码中有多种方法,但我想使用纯 XPath,以便用户可以轻松创建描述其数据格式的配置文件)
编辑: 接受的答案的结果是我想要的东西不能使用单个 XPath 表达式来完成。我最后添加了一个选项,这样用户就可以基本上指定如何找到当前图书的唯一标识符(即“./@id”,或者可能是“./isbn”)。然后我的代码执行此表达式并将结果替换为评分搜索 XPath 中的某些占位符(例如“$$”)。
像 //*/rating[./@book-id=//book/@id]/@value
这样的 XPath 表达式应该只检索与可用图书 ID 匹配的评分的评分值。
如果您将 <rating book-id="3" value="4"/>
添加到 XML 文档,则 XPath 将 return 仅为第 1 本书和第 2 本书的值 5
和 3
没有 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
请注意,我稍微更新了您的第二本书的名称,以便您更容易看出差异。
... and yes, it is easy to just get the id of the current book in Java code and then construct an XPath expression with that, but as I explained, I want the user to be able to use XPath to define their document format, so I don't want any format-specific stuff in the code
VTD 仅支持 XPath 1.0。如果您(或您的客户)能够提出 XPath 1.0 查询,您应该也能够通过 VTD 提取相应的值。我想,普通的 XPath 查询的表现力不足以直接提供您需要的内容。
由于该示例对于您需要的用例来说可能过于简单,因此很难就如何设计您的应用程序来处理此类场景给出任何建议。也许用更详细的例子更新你的问题。处理此问题的一种简单方法是引入必须单独定义的占位符变量,然后在尝试执行此类 XPath 表达式时遇到此类占位符时,只需将这些占位符替换为先前提取的值的具体值。