Java MAVEN中的XpathException示例Expression使用未绑定的名称空间前缀

时间:2017-02-13 13:17:44

标签: java xml maven xpath

在谷歌上进行研究后,我找不到适合自己的解决方案。 ' MAVEN by Example'电子书使用Yahoo天气示例。不幸的是,看起来Yahoo改变了他们的界面。我尝试为此调整java代码,但是得到了这个令人烦恼的异常:

        exec-maven-plugin:1.5.0:java
     Failed to execute goal org.codehaus.mojo:exec-maven-plugin:1.5.0:java
            Caused by: org.dom4j.XPathException: 
Exception occurred evaluting XPath: /query/results/channel/yweather:location/@city. 
Exception: XPath expression uses unbound namespace prefix yweather

xml行本身是:

<query xmlns:yahoo="http://www.yahooapis.com/v1/base.rng" yahoo:count="1" yahoo:created="2017-02-13T10:57:34Z" yahoo:lang="en-US">
<results>
    <channel>
    ...
        <yweather:location xmlns:yweather="http://xml.weather.yahoo.com/ns/rss/1.0" city="Theale" country="United Kingdom" region=" England"/>

整个XML可以从:

生成
https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%3D91731537

我的代码(根据&#39; MAVEN By Example&#39;电子书,xpath和url已针对已更改的Yahoo进行了修改):

    public Weather parse(InputStream inputStream) throws Exception {
    Weather weather = new Weather();

    SAXReader xmlReader = createXmlReader();
    Document doc = xmlReader.read( inputStream );
    weather.setCity(doc.valueOf  ("//yweather:location/@city") );
   // and several more, such as setCountry, setTemp 
}

(我不是xpath专家,所以我试过

/query/results/channel/item/yweather:location/@city

同样,以防万一,结果相同。

xmlReader:

public InputStream retrieve(String woeid) throws Exception {
        String url = "https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%3D"+woeid; // eg 91731537
        URLConnection conn = new URL(url).openConnection();
        return conn.getInputStream();
    }

天气等级只是一组吸气剂和制定者

当我在this XML测试器中尝试这个时,它运行得很好,但这可能是XPATH-v2与Java v1的结果。

2 个答案:

答案 0 :(得分:1)

当您评估XPath //yweather:location/@city时,XPath处理器不知道yweather前缀绑定到哪个名称空间。您需要提供该信息。现在,您可能会想到#34;信息就在文档中!&#34;你是对的。但前缀只是实际命名空间的一种替代(如变量)。命名空间可以绑定到您喜欢的前缀命名规则之前的任何前缀,也可以绑定到多个前缀。就像Java中的变量名称一样,引用一个对象本身并不重要,多个变量可以引用同一个对象。

例如,如果您使用绑定到命名空间//yw:location/@city的前缀为yw的XPath http://xml.weather.yahoo.com/ns/rss/1.0,那么它仍然可以正常工作。

我建议您使用课程org.dom4j.xpath.DefaultXPath,而不是调用valueOf。创建它的实例并初始化命名空间上下文。有一个方法setNamespaceURIs that takes a Map from prefixes to namespaces,可以让你进行绑定。将上面的天气命名空间(实际URI)绑定到您选择的某个前缀(可能是yweather,但可以是您想要在实际XPath表达式中使用的任何其他内容),然后使用该实例对文档进行评估。

以下是我对某些问题的回答,该问题更深入地探讨了命名空间及其前缀的真正含义:https://stackoverflow.com/a/8231272/630136

编辑:您使用的在线XPath测试程序可能会做一些幕后魔术,从给定文档中提取名称空间及其前缀,并将它们绑定在XPath处理器中。

如果您查看他们的示例XML并像这样调整它......

<root xmlns:foo="http://www.foo.org/" xmlns:bar="http://www.bar.org">
    <actors>
        <actor id="1">Christian Bale</actor>
        <actor id="2">Liam Neeson</actor>
        <actor id="3">Michael Caine</actor>
    </actors>
    <foo:singers xmlns:test="http://www.foo.org/">
        <test:singer id="4">Tom Waits</test:singer>
        <foo:singer id="5">B.B. King</foo:singer>
        <foo:singer id="6">Ray Charles</foo:singer>
    </foo:singers>
</root>

XML在语义上是等效的,因为test前缀与foo绑定到同一名称空间。 XPath //foo:singer/@id仍会返回所有正确的结果,因此该工具对此很聪明。但是,它不知道如何处理XML ...

<root xmlns:foo="http://www.foo.org/" xmlns:bar="http://www.bar.org">
    <actors>
        <foo:actor id="1">Christian Bale</foo:actor>
        <actor id="2">Liam Neeson</actor>
        <actor id="3">Michael Caine</actor>
    </actors>
    <foo:singers xmlns:test="http://www.foo.org/" xmlns:foo="http://www.bar.org">
        <test:singer id="4">Tom Waits</test:singer>
        <foo:singer id="5">B.B. King</foo:singer>
        <foo:singer id="6">Ray Charles</foo:singer>
    </foo:singers>
</root>

和XPath //foo:*/@id。前缀foo绑定到singers元素范围中的不同命名空间,现在它只返回id 5和6.与此XPath对比,它不使用前缀但名称空间为uri ()函数://*[namespace-uri()='http://www.foo.org/']/@id

最后一个按预期返回ids 1和4。

答案 1 :(得分:0)

我发现了错误,这是我对命名空间的不熟悉。 'createXmlReader()' 我上面的例子中使用的是一个设置正确命名空间的方法,除了我忘了在Yahoo更改xml后更改它。仔细重新阅读Maven-by-example文档,生成的错误,并与此处给出的详细答案进行比较,它突然点击了。更新的代码(为了尝试相同示例的任何人的利益):

private SAXReader createXmlReader() {
    Map<String,String> uris = new HashMap<String,String>();
    uris.put( "yweather", "http://xml.weather.yahoo.com/ns/rss/1.0" );
    DocumentFactory factory = new DocumentFactory();
    factory.setXPathNamespaceURIs( uris );
    SAXReader xmlReader = new SAXReader();
    xmlReader.setDocumentFactory( factory );
    return xmlReader;
}

唯一的变化是'uris.put()'行 最初名称空间是“y”,现在它是“yweather”。