【问题标题】:Select default namespace in XPath with HtmlUnit使用 HtmlUnit 在 XPath 中选择默认命名空间
【发布时间】:2011-08-31 22:58:58
【问题描述】:

我想用 HtmlUnit 解析 Feedburner 提要。 提要是这个:http://feeds.feedburner.com/alcoanewsreleases

从此提要中,我想读取所有 item 节点,所以通常//item XPath 应该可以解决问题。不幸的是,这在这种情况下不起作用。

groovy 代码 sn-p:

def page = webClient.getPage("http://feeds.feedburner.com/alcoanewsreleases")
def elements = page.getByXPath("//item")

XML 提要示例:

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss1full.xsl"?>
<?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?>

<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns="http://purl.org/rss/1.0/" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">

[...SNIP...]

<item rdf:about="http://www.alcoa.com/global/en/news/news_detail.asp?newsYear=2011&amp;pageID=20110518006002en">
    <title>Chris L. Ayers Named President, Alcoa Global Primary Products</title>
    <dc:date>2011-05-18</dc:date
    <link>http://feedproxy.google.com/~r/alcoanewsreleases/~3/PawvdhpJrkc/news_detail.asp</link>
    <description>NEW YORK--(BUSINESS WIRE)--Alcoa (NYSE:AA) announced today that Chris L. Ayers has been named President of Alcoa’s Global Primary Products (GPP) business, effective May 18, 2011. Ayers, previously Chief Operating Officer of GPP, succeeds John Thuestad, who will be handling special projects for the Company. Ayers joined Alcoa in February 2010 as Chief Operating Officer of Alcoa Cast, Forged and Extruded Products, a new position. He was elected a Vice President of Alcoa in April 2010 and Executive</description>
    <feedburner:origLink xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">http://www.alcoa.com/global/en/news/news_detail.asp?newsYear=2010&amp;pageID=20100104006194en</feedburner:origLink>
</item>

[...SNIP...]

</rdf:RDF>

我怀疑这是命名空间的问题,因为此文档有 4 个命名空间。命名空间是

  • (这是默认设置) xmlns="http://purl.org/rss/1.0/"
  • xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
  • xmlns:dc="http://purl.org/dc/elements/1.1/"
  • xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0"

我已尝试将 Nokogiri 与此一起使用(我用于 ruby​​ 脚本的另一个 XML 解析器)。 使用 Nokogiri,我可以只使用 XPath //xmlns:item,它可以工作并从提要中返回所有节点。

我用 HtmlUnit 尝试了相同的 XPath,但它不起作用。

所以我想我可以将我的问题表述为: 如何使用 HtmlUnit 从默认命名空间中选择节点?

有什么想法吗?

【问题讨论】:

    标签: xpath groovy namespaces xml-namespaces htmlunit


    【解决方案1】:

    我想从这个提要中阅读所有项目 节点,所以通常是//item XPath 应该做的伎俩。很遗憾 在这种情况下不起作用。

    在 XPath 中,这意味着“选择所有本地名称为 item 不在命名空间中的元素”。在 RSS 中,item 元素必须位于命名空间中。因此,上述内容不应该与符合标准的 XML 解析器和 XPath 引擎一起使用。

    令人困惑的是,在 XML 中,&lt;item&gt; 的意思是“一个名为 item 的元素,它位于 default 命名空间中,即在文档中此位置范围内的任何默认命名空间;”而在 XPath 中,“item”表示 no 命名空间中的元素。 (或者,您可以说,它表示默认命名空间中的一个元素,但除非您有办法告诉 XPath 默认命名空间是什么,否则默认命名空间是没有命名空间的。通常(总是?)在 XPath 1.0 中没有办法为 XPath 表达式声明默认命名空间。)

    另一个令初学者困惑的事情是,XPath 处理器认为源 XML 文档中的名称空间前缀映射并不重要。解析 XML 文档时,会构建一个数据结构,该结构会记住每个元素(和其他节点)的名称和名称空间。使用的命名空间前缀,包括默认命名空间的空前缀,被认为仅仅是语法上的便利。更多关于这下面...

    有了 Nokogiri,我就可以 XPath //xmlns:item 有效并且 从提要中返回所有节点。

    不管是什么,它都不是 XPath。也许它是对它的 Nokogiri 扩展(非常方便,但它的语法确实违反直觉)。

    所以我想我可以表达我的问题 as:如何从 带有 HtmlUnit 的默认命名空间?

    让我们将其表述为:如何使用 HtmlUnit 选择 RSS 项目元素?我这样说是因为 RSS 规范(实际上通常是任何符合 XML 词汇规范的规范)不要求其元素位于默认命名空间中。在您收到的示例中恰好是这样,但是服务提供商明天可能会更改它,并且仍然完全符合 RSS。明天,服务提供者可以为该命名空间使用“rss”命名空间前缀;或任何其他任意前缀。 RSS 所做 指定的是其元素将位于哪个命名空间:URI 为 http://purl.org/rss/1.0/ 的命名空间。

    这有点像问,“我如何编写一个函数(在 Javascript、C、Java 等中)可以告诉我变量 a 的值?”通常一个函数不知道调用者中使用了什么变量名。它所知道的只是其参数的。如果您拨打sqrt(4),您将得到与a = 4; sqrt(a)rumpelstiltzkin = 4; sqrt(rumpelstiltzkin) 相同的答案。显然,变量参数的名称对函数调用的结果没有直接影响。它只需要是包含正确值的变量的名称。如果编译器抱怨因为你写了b = 4; return sqrt(b) 而不是使用a,你会认为编译器疯了。只要您使用有效的标识符,它就不应该关心变量名。

    同样,在处理 RSS 时,我们不应该关心使用什么命名空间前缀,只要它是标识正确命名空间的前缀即可。它可以没有前缀(标识默认命名空间)。

    在 XPath 2.0 中,您可以使用通配符命名空间。如果您知道不需要命名空间来消除歧义,这将非常方便。在这种情况下,您可以选择//*:item。但是,我不认为 HTMLUnit 支持 XPath 2.0。同样在 XSLT 2.0 等 XPath 2.0 环境中,您可以为 XPath 表达式指定默认命名空间,但这对 HTMLUnit 没有帮助。

    所以你有几个选择:

    • 使用忽略命名空间的 XPath 表达式,例如 //*[local-name() = 'item']

    • 稳健的方法:为http://purl.org/rss/1.0/ 注册一个命名空间前缀并在您的XPath 表达式中使用它://rss:item。那么问题就变成了,如何在 HTMLUnit 中注册命名空间前缀并将其传递给 XPath 处理器?我快速浏览了文档,但没有找到任何工具。

    警告:我应该补充一点,以上是关于符合 XPath 处理器的。我不知道 HTMLUnit 使用什么 XPath 处理器。有一些 XPath 处理器忽略了规范,让每个人的世界变得更加混乱。

    我看到here 有人对 HTMLUnit 中默认命名空间中的元素使用以下语法:

    //:item
    

    但我不建议这样做,原因有以下三个:

    1. 它不是有效的 XPath,因此您不能指望它与其他程序一起使用。

    2. 它仅适用于将 RSS 命名空间声明为默认命名空间的 RSS 提要。使用命名空间前缀的 RSS 提要会导致上述操作失败。

    3. 它会阻碍您了解 XML 命名空间的真正工作原理,并且有助于保持工具的现状,这些工具不能充分支持命名空间。

    HTMLUnit 主要是为 HTML 设计的,因此对 XML 的不完整处理是可以理解的。但是声称支持 XPath,然后不提供声明命名空间前缀的方法是 bug。 HTMLUnit 使用一个 XPath 包,它似乎是 Xalan-J 的一部分。那个包有ways to provide namespace mappings to XPath,但我不知道HTMLUnit是否公开了那个功能。

    【讨论】:

    • 感谢您非常详细的回答! XPath //:item 确实可以与 HtmlUnit 一起使用,尽管不推荐您使用它。
    【解决方案2】:

    这听起来很熟悉,我很确定我过去已经成功地使用了名称空间和 XPath 与 HtmlUnit,但我当然找不到代码。我怀疑它一定只适用于 HTML 页面:您示例中的 page 引用是 XmlPage,它具有许多特定于命名空间的方法,所有这些方法在使用时都会引发“尚未实现”异常。 :-(

    HtmlUnit 的当前版本 (2.8) 已经有将近一年的历史了,因此在此期间可能已经完成了一些工作来支持 XML 命名空间。 "HtmlUnit Users" mailing list 将是找出答案的地方。

    与此同时,一如既往地有一个解决方法:

    final XmlPage page = webClient.getPage("http://feeds.feedburner.com/alcoanewsreleases");
    
    // no good
    List elements = page.getByXPath("//item");
    System.out.println( elements.size() ) ;
    
    // ugly, but it works
    DomElement de = (DomElement)page.getFirstByXPath( "//rdf:RDF" );
    List<DomNode> items = new ArrayList<DomNode>() ;
    for( DomNode dn : de.getChildNodes() )
    {
        String name = dn.getLocalName() ;
        if( ( name != null ) && ( name.equals( "item" ) ) )
            items.add( dn ) ;
    }
    System.out.println( "found " + items.size() ) ;
    

    在 Scala 中工作后,Java 很痛苦... ;-)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-10-09
      • 2010-09-11
      • 2013-06-23
      • 2010-10-07
      • 1970-01-01
      相关资源
      最近更新 更多