【问题标题】:JAXP XPath 1.0 or 2.0 - how to distinguish empty strings from non-existent valuesJAXP XPath 1.0 或 2.0 - 如何区分空字符串和不存在的值
【发布时间】:2016-10-15 03:10:02
【问题描述】:

给定以下 XML 实例:

<entities>
    <person><name>Jack</name></person>
    <person><name></name></person>
    <person></person>
</entities>

我正在使用以下代码:(a) 遍历人员并 (b) 获取每个人的姓名:

XPathExpression expr = xpath.compile("/entities/person");
NodeList nodes = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
for (int i = 0 ; i < nodes.getLength() ; i++) {
    Node node = nodes.item(i);
    String innerXPath = "name/text()";
    String name  = xpath.compile(innerXPath).evaluate(node);
    System.out.printf("%2d -> name is %s.\n", i, name);
}

上面的代码无法区分第 2 人称(姓名为空字符串)和第 3 人称(根本没有姓名元素),只是打印:

0 -> name is Jack.
1 -> name is .
2 -> name is .

有没有办法使用不同的innerXPath 表达式来区分这两种情况?在this SO question 中,XPath 的方式似乎是返回一个空列表,但我也尝试过:

String innerXPath = "if (name) then name/text() else ()";

...输出还是一样的。

那么,有没有办法用不同的innerXPath 表达式来区分这两种情况?我的类路径中有 Saxon HE,因此我也可以使用 XPath 2.0 功能。

更新

因此,根据接受的答案,我能做的最好的事情如下:

XPathExpression expr = xpath.compile("/entities/person");                                                                                                                                                                                 
NodeList nodes = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);                                                                                                                                                                   
for (int i = 0 ; i < nodes.getLength() ; i++) {                                                                                                                                                                                           
    Node node = nodes.item(i);                                                                                                                                                                                                            
    String innerXPath = "name";                                                                                                                                                                                                           
    NodeList names = (NodeList) xpath.compile(innerXPath).evaluate(node, XPathConstants.NODESET);                                                                                                                                         
    String nameValue = null;                                                                                                                                                                                                              
    if (names.getLength()>1) throw new RuntimeException("impossible");                                                                                                                                                                    
    if (names.getLength()==1)                                                                                                                                                                                                             
        nameValue = names.item(0).getFirstChild()==null?"":names.item(0).getFirstChild().getNodeValue();                                                                                                                                  
    System.out.printf("%2d -> name is [%s]\n", i, nameValue);                                                                                                                                                                             
} 

以上代码打印:

0 -> name is [Jack]
1 -> name is []
2 -> name is [null]

在我看来,这不是很令人满意,因为逻辑分布在 XPathJava 代码中并且限制了 XPath 作为宿主语言和与 API 无关的符号。我的特定用例是将 XPath 集合保存在属性文件中并在运行时评估它们,以便获得我需要的信息,而无需任何特别的额外处理。显然这是不可能的。

【问题讨论】:

  • 那么你到底想完成什么?你想要的结果是什么?
  • 例如,如果元素名称不存在则返回 null,如果元素存在且内容为空字符串,则返回空字符串 ("")。我需要一个 XPath 表达式,如果元素 &lt;name&gt; 根本不存在与它以空值存在,它将以不同的方式计算。
  • 我没有过多地使用 Java 的 XML API,但听起来evaluate() 函数将始终返回一个非空字符串,方法是将结果转换为字符串值。像这样的东西怎么样:String innerXPath = "if (name) then name/text() else '[UNSPECIFIED]'";
  • @JLRishe 这是一个选项,但我想尽可能避免使用特殊字符串。

标签: java xpath xpath-2.0 jaxp


【解决方案1】:

基于 XPath 1.0 的 JAXP API 在这里非常有限。我的直觉是返回 Name 元素(作为 NodeList)。所以所需的 XPath 表达式只是“名称”。那么案例 1 和案例 2 将返回一个长度为 1 的节点列表,而案例 3 将返回一个长度为 0 的节点列表。案例 1 和案例 2 可以在应用程序中通过获取节点的值并测试它是否为零来轻松区分 -长度。

无论如何,最好避免使用 /text(),因为它会导致您的查询对 XML 中存在的 cmets 敏感。

【讨论】:

    【解决方案2】:

    作为 Saxon XSLT 的长期用户,我很高兴再次发现我喜欢 Michael Kay 的推荐。一般来说,我喜欢为查询返回一个集合的模式,即使对于那些预计最多只返回一个实例的查询也是如此。

    我不喜欢打开捆绑的界面来尝试解决特定需求,然后发现必须重新实现原始界面处理的大部分内容。

    因此,这是一种使用 Michael 建议的方法,同时避免了必须重新实现该线程中其他 cmets 中推荐的节点到字符串转换的成本。

    @Nonnull
    public Optional<String> findString( @Nonnull final String expression )
    {
        try
        {
            // for XpathConstants.STRING XPath returns an empty string for both values of no length
            // and for elements that are not present.
    
            // therefore, ask for a NODESET and then retrieve the first Node if any
    
            final FluentIterable<Node> matches = 
                    IterableNodeList.from( (NodeList) xpath.evaluate( expression, node, XPathConstants.NODESET ) );
    
            if ( matches.isEmpty() )
            {
                return Optional.absent();
            }
    
            final Node firstNode = matches.first().get();
    
            // now let XPath process a known-to-exist Node to retrieve its String value         
            return Optional.fromNullable( (String) xpath.evaluate( ".", firstNode, XPathConstants.STRING ) );
        }
        catch ( XPathExpressionException xee )
        {
            return Optional.absent();
        }
    }
    

    在这里,XPath.evaluate 被第二次调用,以执行它通常所做的任何事情,将第一个找到的 Node 转换为请求的 String 值。如果不这样做,重新实现可能会产生与在相同源节点和相同表达式上直接调用 XPathConstant.STRING 不同的结果。

    当然,这段代码使用了 Guava Optional 和 FluentIterable 来使意图更加明确。如果您不想要 Guava,请使用 Java 8 或使用 null 和 NodeList 自己的集合方法重构实现。

    【讨论】:

      猜你喜欢
      • 2023-03-12
      • 2023-01-30
      • 2020-01-31
      • 1970-01-01
      • 1970-01-01
      • 2023-04-10
      • 2020-06-10
      • 2018-12-17
      • 2015-07-02
      相关资源
      最近更新 更多