如何让Nokogiri解析并返回XML文档?

时间:2009-07-21 03:20:34

标签: ruby xpath nokogiri xml-parsing

以下是一些奇怪的例子:

#!/usr/bin/ruby

require 'rubygems'
require 'open-uri'
require 'nokogiri'

print "without read: ", Nokogiri(open('http://weblog.rubyonrails.org/')).class, "\n"
print "with read:    ", Nokogiri(open('http://weblog.rubyonrails.org/').read).class, "\n"

运行此命令返回:

without read: Nokogiri::XML::Document
with read:    Nokogiri::HTML::Document

没有read返回XML,并且它是HTML?网页被定义为“XHTML过渡”,所以起初我认为Nokogiri必须从流中读取OpenURI的“内容类型”,但返回'text/html'

(rdb:1) doc = open(('http://weblog.rubyonrails.org/'))
(rdb:1) doc.content_type
"text/html"

这是服务器返回的内容。所以,现在我想弄清楚为什么Nokogiri会返回两个不同的值。它似乎没有解析文本并使用启发式方法来确定内容是HTML还是XML。

该页面指向的ATOM提要也发生了同样的事情:

(rdb:1) doc = Nokogiri.parse(open('http://feeds.feedburner.com/RidingRails'))
(rdb:1) doc.class
Nokogiri::XML::Document

(rdb:1) doc = Nokogiri.parse(open('http://feeds.feedburner.com/RidingRails').read)
(rdb:1) doc.class
Nokogiri::HTML::Document

我需要能够在不知道事先是什么的情况下解析页面,HTML或提要(RSS或ATOM)并可靠地确定它是什么。我让Nokogiri解析HTML或XML feed文件的正文,但我看到了那些不一致的结果。

我以为我可以编写一些测试来确定类型,但后来我遇到xpaths没找到元素,但是常规搜索工作:

(rdb:1) doc = Nokogiri.parse(open('http://feeds.feedburner.com/RidingRails'))
(rdb:1) doc.class
Nokogiri::XML::Document
(rdb:1) doc.xpath('/feed/entry').length
0
(rdb:1) doc.search('feed entry').length
15

我认为xpath可以使用XML,但结果看起来也不值得信赖。

这些测试都是在我的Ubuntu盒子上完成的,但我在Macbook Pro上看到过相同的行为。我很想知道我做错了什么,但我没有看到解析和搜索的例子,这给了我一致的结果。任何人都可以向我展示我的方式错误吗?

2 个答案:

答案 0 :(得分:13)

这与Nokogiri parse method的工作方式有关。这是来源:

# File lib/nokogiri.rb, line 55
    def parse string, url = nil, encoding = nil, options = nil
      doc =
        if string =~ /^\s*<[^Hh>]*html/i # Probably html
          Nokogiri::HTML::Document.parse(string, url, encoding, options || XML::ParseOptions::DEFAULT_HTML)
        else
          Nokogiri::XML::Document.parse(string, url, encoding, options || XML::ParseOptions::DEFAULT_XML)
        end
      yield doc if block_given?
      doc
    end

关键是第if string =~ /^\s*<[^Hh>]*html/i # Probably html行。当您只使用open时,它返回一个不能与正则表达式一起使用的对象,因此它总是返回false。另一方面,read返回一个字符串,因此可以被视为HTML。在这种情况下,它是,因为它匹配该正则表达式。这是该字符串的开头:

<!DOCTYPE html PUBLIC

正则表达式将“!DOCTYPE”与[^Hh>]*匹配,然后匹配“html”,从而假设它是HTML。为什么有人选择这个正则表达式来确定文件是否是HTML是超出我的。使用此正则表达式,以<definitely-not-html>标记开头的文件被视为HTML,但<this-is-still-not-html>被视为XML。你可能最好远离这个愚蠢的功能并直接调用Nokogiri::HTML::Document#parseNokogiri::XML::Document#parse

答案 1 :(得分:5)

回答你问题的这一部分:

  

我以为我可以写一些测试   确定类型,但后来我遇到了   xpath没有找到元素,但是   定期搜索工作:

我刚刚使用nokogiri解析原子提要来解决这个问题。问题似乎取决于匿名名称空间声明:

<feed xmlns="http://www.w3.org/2005/Atom">

从源xml中删除xmlns声明将使Nokogiri能够按常规搜索xpath。从feed中删除该声明显然不是一个选项,所以我只是在解析后从文档中删除了名称空间。例如:

doc = Nokogiri.parse(open('http://feeds.feedburner.com/RidingRails'))
doc.remove_namespaces!
doc.xpath('/feed/entry').length

丑陋我知道,但它确实有效。