VTD 无法评估 "find all empty nodes with no attributes" xpath

VTD fails to evaluate a "find all empty nodes with no attributes" xpath

我在使用 2.13.4 版本的 vtd-xml 时发现了一个错误(我认为)。好吧,简而言之,我有以下代码片段:

String test = "<catalog><description></description></catalog>";
VTDGen vg = new VTDGen();
vg.setDoc(test.getBytes("UTF-8"));
vg.parse(true);
VTDNav vn = vg.getNav();
//get nodes with no childs, text and attributes
String xpath = "/catalog//*[not(child::node()) and not(child::text()) and count(@*)=0]";
AutoPilot ap = new AutoPilot(vn);
ap.selectXPath(xpath);
//block inside while is never executed
 while(ap.evalXPath()!=-1) {
   System.out.println("current node "+vn.toRawString(vn.getCurrentIndex()));
}

这不起作用(=没有找到任何节点,而它应该找到 "description")。如果我使用自闭标签,上面的代码有效:

String test = "<catalog><description/></catalog>";

重点是每个 xpath 评估器都适用于 xml 的两个版本。可悲的是,我从外部来源收到 xml,所以我无能为力...... 打破 xpath 我注意到评估两者

/catalog//*[not(child::node())]

/catalog//*[not(child::text())]

结果为 false。作为额外的一点,我尝试了类似的东西:

String xpath = "/catalog/description/text()";
ap.selectXpath(xpath);
if(ap.evalXPath()!=-1)
   System.out.println(vn.toRawString(vn.getCurrentIndex()));

并且此打印为空 space,所以在某种程度上 VTD "thinks" 节点有文本,即使是空的但仍然是,而我预计没有匹配。有什么提示吗?

TL;DR

When I faced this issue, I was left mainly with three options (see below). I went for the second option : Use XMLModifier to fix the VTDNav. At the bottom of my answser, you'll find an implementation of this option and a sample output.


说来话长...

我遇到了同样的问题。以下是我首先想到的主要三个选项(按难度排序):

1。在 XML 源代码中将空元素变成自闭标签。

这个选项并不总是可行的(比如在 OP 的情况下)。而且,可能很难“pre-process”之前的xml。

2。使用 XMLModifier to fix the VTDNav.

用 xpath 表达式找到空元素,用自闭标签替换它们并重建 VTDNav。

2.bis 使用 XMLModifier#removeToken

由于 XMLModifier#removeToken.

,上述解决方案的较低级别变体将包括遍历 VTDNav 中的标记并删除不必要的标记

3。直接修补vtd-xml代码。

走这条路可能需要更多的努力和更多的时间。 IMO,优化后的 vtd-xml 代码乍一看并不容易掌握。


选项 1 对我来说不可行。我未能实施选项 2bis。 “不必要的”令牌仍然存在。我没有看选项 3,因为我不想修复一些(相当复杂的)第三方代码。

我只剩下选项 2。这是一个实现:

代码

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.ximpleware.AutoPilot;
import com.ximpleware.NavException;
import com.ximpleware.VTDException;
import com.ximpleware.VTDGen;
import com.ximpleware.VTDNav;
import com.ximpleware.XMLModifier;

@Test
public void turnEmptyElementsIntoSelfClosedTags() throws VTDException, IOException {
    // STEP 1 : Load XML into VTDNav
    // * Convert the initial xml code into a byte array
    String xml = "<root><empty-element></empty-element><self-closed/><empty-element2 foo='bar'></empty-element2></root>";
    byte[] ba = xml.getBytes(StandardCharsets.UTF_8);

    // * Build VTDNav and dump it to screen
    VTDGen vg = new VTDGen();
    vg.setDoc(ba);
    vg.parse(false); // Use `true' to activate namespace support

    VTDNav nav = vg.getNav();
    dump("BEFORE", nav);


    // STEP 2 : Prepare to fix the VTDNAv
    // * Prepare an autopilot to find empty elements
    AutoPilot ap = new AutoPilot(nav);
    ap.selectXPath("//*[count(child::node())=1][text()='']");

    // * Prepare a simple regex matcher to create self closed tags
    Matcher elementReducer = Pattern.compile("^<(.+)></.+>$").matcher("");


    // STEP 3 : Fix the VTDNAv
    // * Instanciate an XMLModifier on the VTDNav
    XMLModifier xm = new XMLModifier(nav);
    ByteArrayOutputStream baos = new ByteArrayOutputStream(); // baos will hold the elements to fix
    String utf8 = StandardCharsets.UTF_8.name();

    // * Find all empty elements and replace them
    while (ap.evalXPath() != -1) {
        nav.dumpFragment(baos);
        String emptyElementXml = baos.toString(utf8);
        String selfClosingTagXml = elementReducer.reset(emptyElementXml).replaceFirst("</>");

        xm.remove();
        xm.insertAfterElement(selfClosingTagXml);

        baos.reset();
    }

    // * Rebuild VTDNav and dump it to screen
    nav = xm.outputAndReparse(); // You MUST call this method to save all your changes
    dump("AFTER", nav);
}

private void dump(String msg,VTDNav nav) throws NavException, IOException {
    System.out.print(msg + ":\n  ");
    nav.dumpFragment(System.out);
    System.out.print("\n\n");
}

输出

BEFORE:
  <root><empty-element></empty-element><self-closed/><empty-element2 foo='bar'></empty-element2></root>

AFTER:
  <root><empty-element/><self-closed/><empty-element2 foo='bar'/></root>