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

使用HtmlUnit在XPath中选择默认名称空间,xpath,groovy,namespaces,xml-namespaces,htmlunit,Xpath,Groovy,Namespaces,Xml Namespaces,Htmlunit,我想用HtmlUnit解析Feedburner提要。 这个提要是: 从这个提要中,我想读取所有item节点,因此通常使用//itemXPath就可以了。不幸的是,这在这种情况下不起作用 groovy代码段: def page = webClient.getPage("http://feeds.feedburner.com/alcoanewsreleases") def elements = page.getByXPath("//item") XML提要的示例: <?xml version

我想用HtmlUnit解析Feedburner提要。 这个提要是:

从这个提要中,我想读取所有item节点,因此通常使用
//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>

[…剪断…]
Chris L.Ayers任命美国铝业全球初级产品总裁
2011-05-18
从这个提要中,我想阅读所有项目
节点,因此通常是
//项
XPath 我们应该做到这一点。不幸的是 这在这种情况下不起作用

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

令人困惑的是,在XML中,
表示“位于默认命名空间中的名为item的元素,即文档中此位置范围内的任何默认命名空间”;而在XPath中,“item”表示命名空间中的元素。(或者,您可以说,它意味着默认名称空间中的一个元素,但除非您有办法告诉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;返回sqrt(b)
而不是使用
a
,您会认为编译器是疯子。只要使用有效的标识符,就不应该关心变量名

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

在XPath2.0中,可以对名称空间进行通配符。如果您知道不需要名称空间来消除歧义,那么这非常方便。在这种情况下,您可以选择
/*:item
。但是,我认为HTMLUnit不支持XPath2.0。同样在XPath2.0环境(如XSLT2.0)中,可以为XPath表达式指定默认名称空间,但这对HTMLUnit没有帮助

因此,您有两个选择:

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

  • 健壮的方法:为
    http://purl.org/rss/1.0/
    并在XPath表达式中使用它:
    //rss:item
    。接下来的问题是,如何在HTMLUnit中注册名称空间前缀并将其传递给XPath处理器?我快速查看了一下文档,没有找到任何这样做的工具
警告:我应该补充一点,上面提到的是一致性XPath处理器。我不知道它使用什么XPath处理器。有一些XPath处理器忽略了规范,让世界变得更加混乱

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

//:item
但我不建议这样做,原因有三:

  • 它不是有效的XPath,因此不能期望它与其他程序一起工作

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

  • 它将阻碍您了解XML名称空间的实际工作方式,并有助于保持不充分支持名称空间的工具的现状

  • HTMLUnit主要是为HTML设计的,因此不完整的XML处理是可以理解的。但是声称支持XPath,然后是n
    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() ) ;