使用HtmlUnit在XPath中选择默认名称空间

问题描述:

我想用HtmlUnit解析Feedburner提要。 饲料是这一个:http://feeds.feedburner.com/alcoanewsreleases使用HtmlUnit在XPath中选择默认名称空间

从这个饲料我想读的所有项目节点,所以通常是//item的XPath应该做的伎俩。不幸的是,在这种情况下不起作用。

Groovy代码片段:

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的默认命名空间中选择一个节点?

任何想法?

从这个饲料我想读的所有项目 节点,所以通常是//item的XPath 应该做的伎俩。不幸的是 在这种情况下不起作用。

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

有什么困惑的是,在XML,<item>机构“的一个元素命名的项目是在默认命名空间,即不管默认的命名空间是在这个地方在文档中范围;”而在XPath中,“item”表示名称空间中的一个元素,其名称空间为no。 (或者,你可以说,它意味着默认命名空间中的一个元素,但是除非你有办法告诉XPath默认的命名空间是什么,否则默认的命名空间不是命名空间。通常(总是?)在XPath 1.0中是没有办法的声明XPath表达式的默认名称空间。)

另一个让初学者感到困惑的是,源XML文档中的命名空间前缀映射不被XPath处理器视为重要。在解析XML文档时,会构建一个数据结构,用于记录每个元素(和其他节点)的名称和名称空间。名称空间使用的前缀,包括默认名称空间的空白前缀,都被视为单纯的语法方便。更多关于这下面...

随着引入nokogiri我可以只是我们 的XPath //xmlns:item其工作,并从进料 回报所有节点。

无论如何,它不是XPath。也许这是对它的一种Nokogiri扩展(非常方便,但它的语法实际上是违反直觉的)。

所以我想我可以那句我的问题 为:我怎样才能从与该的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。同样在XPath 2.0环境(如XSLT 2.0)中,您可以为XPath表达式指定默认名称空间,但这对HTMLUnit无帮助。

所以,你有两个选择:

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

  • 的可靠的方法:注册一个命名空间前缀http://purl.org/rss/1.0/和您的XPath表达式使用它://rss:item。接下来的问题就是,如何在HTMLUnit中注册一个名称空间前缀并将其传递给XPath处理器?我快速浏览了文档,没有找到任何设施来做这件事。

注意:我应该补充一点,以上是关于符合XPath处理器。我不知道XPath处理器HTMLUnit使用什么。那里有一些XPath处理器忽略了规范,并且让世界更加困惑于每个人。

我看到here有人用下面的语法在默认命名空间的元素中的HtmlUnit:

//:item 

但我不会建议,原因有三:

  1. 这不是有效的XPath,所以你不能指望它与其他程序一起工作。

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

  3. 它将阻止您了解XML名称空间如何真正起作用,并有助于保持不充分支持名称空间的工具的现状。

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

+0

谢谢您的** **非常详细的解答! XPath的'//:item'确实可以和HtmlUnit一起工作,尽管不像你所描述的那样推荐实践。 – spier 2011-05-25 19:07:50

这听起来很熟悉,我很确定我已经在HtmlUnit中成功地使用了名称空间和XPath,但是当然我找不到代码。我怀疑它只能用于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()) ; 

噢男孩Java是痛苦在斯卡拉工作后... ;-)