VTD无法评估“找到没有属性的所有空节点” xpath

时间:2019-06-26 15:34:25

标签: xpath vtd-xml

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

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

,这不起作用(=找不到任何节点,而应该找到“描述”)。如果我使用自封闭标签,则上面的代码有效:

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

重点是每个xpath评估程序都可以使用这两个xml版本。可悲的是,我从外部来源收到xml,所以我无法控制它。 打破xpath,我注意到同时评估了两者

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

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

给出错误的结果。另外我尝试了类似的东西:

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

并且此打印空白空间,因此VTD以某种方式“认为”节点具有文​​本,即使是空白但仍然是空白,而我希望没有匹配项。有提示吗?

1 个答案:

答案 0 :(得分:0)

TL; DR

当我面对这个问题时,主要剩下三个选择(见下文)。我选择了第二个选项:使用XMLModifier修复VTDNav。在答案的底部,您将找到此选项的实现和示例输出。


漫长的故事...

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

1。将空元素转换成XML源中的自封闭标签。

并非总是可以使用此选项(例如在OP情况下)。而且,可能很难对XML进行“预处理”。

2。使用XMLModifier修复VTDNav

使用xpath表达式查找空元素,将其替换为自封闭标签,然后重建VTDNav。

2.bis使用XMLModifier#removeToken

上述解决方案的一个较低级别的变体是在VTDNav中循环令牌并借助XMLModifier#removeToken删除不必要的令牌。

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

走这条路可能需要更多的精力和更多的时间。 IMO,优化的vtd-xml代码一见钟情并不容易。


在我的情况下,选项1不可行。我没有实现选项2之二。 “不必要的”令牌仍然存在。我没有考虑选项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("<$1/>");

        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>