namespace-unaware如果Saxon在CLASSPATH上,XPath表达式将失败

时间:2014-01-14 16:02:36

标签: java xml xpath saxon

我有以下示例XML文件:

<a xmlns="http://www.foo.com">
    <b>
    </b>
</a>

使用XPath表达式/foo:a/foo:b(在'foo'中正确配置NamespaceContext)我可以正确计算b个节点的数量,代码可以正常工作当Saxon-HE-9.4.jar在CLASSPATH上时以及它不在时。

但是,当我使用命名空间 - unaware DocumentBuilderFactory解析同一文件时,XPath表达式“/ a / b”正确计算b个节点的数量仅当CLASSPATH上的Saxon-HE-9.4.jar 时。

以下代码:

import java.io.*;
import java.util.*;
import javax.xml.xpath.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import javax.xml.namespace.NamespaceContext;

public class FooMain {

    public static void main(String args[]) throws Exception {

        String xmlSample = "<a xmlns=\"http://www.foo.com\"><b></b></a>";
        {
            XPath xpath = namespaceUnawareXpath();
            System.out.printf("[NS-unaware] Number of 'b' nodes is: %d\n", 
                              ((NodeList) xpath.compile("/a/b").evaluate(stringToXML(xmlSample, false),
                              XPathConstants.NODESET)).getLength());
        }
        {
            XPath xpath = namespaceAwareXpath("foo", "http://www.foo.com");
            System.out.printf("[NS-aware  ] Number of 'b' nodes is: %d\n", 
                              ((NodeList) xpath.compile("/foo:a/foo:b").evaluate(stringToXML(xmlSample, true),
                               XPathConstants.NODESET)).getLength());
        }

    }


    public static XPath namespaceUnawareXpath() {
        XPathFactory xPathfactory = XPathFactory.newInstance();
        XPath xpath = xPathfactory.newXPath();
        return xpath;
    }

    public static XPath namespaceAwareXpath(final String prefix, final String nsURI) {
        XPathFactory xPathfactory = XPathFactory.newInstance();
        XPath xpath = xPathfactory.newXPath();
        NamespaceContext ctx = new NamespaceContext() {
                @Override
                public String getNamespaceURI(String aPrefix) {
                    if (aPrefix.equals(prefix))
                        return nsURI;
                    else
                        return null;
                }
                @Override
                public Iterator getPrefixes(String val) {
                    throw new UnsupportedOperationException();
                }
                @Override
                public String getPrefix(String uri) {
                    throw new UnsupportedOperationException();
                }
            };
        xpath.setNamespaceContext(ctx);
        return xpath;
    }    

    private static Document stringToXML(String s, boolean nsAware) throws Exception {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(nsAware);
        DocumentBuilder builder = factory.newDocumentBuilder();
        return builder.parse(new ByteArrayInputStream(s.getBytes("UTF-8")));
    }


}

运行上述内容:

java -classpath dist/foo.jar FooMain

..产生:

[NS-unaware] Number of 'b' nodes is: 1
[NS-aware  ] Number of 'b' nodes is: 1

以:

运行
java -classpath Saxon-HE-9.4.jar:dist/foo.jar FooMain

...生产:

[NS-unaware] Number of 'b' nodes is: 0
[NS-aware  ] Number of 'b' nodes is: 1

3 个答案:

答案 0 :(得分:4)

正确观察。 Saxon不支持名称空间不知道的DOM。它没有理由应该这样做。如果你能找到一个与命名空间无关的DOM一起使用的XSLT / XPath处理器,那么请继续使用它,如果你愿意,但它的行为不是由任何标准定义的。

如果Saxon有可能检测到DOM是名称空间不知道的,那么它会抛出错误而不是给出虚假结果。可悲的是,DOM的许多设计失败之一是,如果你自己没有创建DOM,你就无法判断它是否是名称空间感知。

您的评论“我需要对命名空间保持宽容,因为我必须处理并非总是XSD有效的第三方XML实例。”是一个完整的非sequitur。确实,文档不能是XSD有效的,除非它是名称空间有效的,但相反的情况并非如此;大量文档是名称空间有效的,而不是XSD有效。

最后,正如您的经验所示,依赖于JAXP机制来加载恰好位于类路径上的任何XPath处理器非常容易出错。您无法控制是否通过此机制获得XPath 1.0或2.0处理器(再次,您无法轻易找到您所拥有的)。如果您的代码依赖于特定XPath实现的怪癖,那么您需要显式加载该实现,而不是依赖于JAXP搜索。

UPDATE(2015年9月):Saxon 9.6不再包含将其作为JAXP XPath提供程序公布的meta-inf服务文件。这意味着你永远不会将Saxon作为你的XPath处理器,因为它在类路径上:你必须明确地要求它。

答案 1 :(得分:1)

XPath语言仅在名称空间良好的XML上定义,因此不同的处理器在非命名空间感知的DOM树(甚至像<a><b/></a>之类的行为上的行为,如果在命名空间中解析了它 - 意识到的方式,实际上不会使用任何命名空间)最好是特定于实现,最坏的是完全未定义。

答案 2 :(得分:1)

Saxon 10现在支持不带名称空间的XPath,您可以这样配置它:

XPath xPath = new net.sf.saxon.xpath.XPathFactoryImpl().newXPath();
((XPathEvaluator)xPath).getStaticContext().setUnprefixedElementMatchingPolicy(UnprefixedElementMatchingPolicy.ANY_NAMESPACE);