Как заставить Нокогири анализировать и возвращать XML-документ?
-
18-09-2019 - |
Вопрос
Вот пример какой-то странности:
#!/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», поэтому сначала я подумал, что Нокогири, должно быть, читал «тип контента» OpenURI из потока, но это возвращает 'text/html'
:
(rdb:1) doc = open(('http://weblog.rubyonrails.org/'))
(rdb:1) doc.content_type
"text/html"
что возвращает сервер.Итак, теперь я пытаюсь понять, почему Нокогири возвращает два разных значения.Похоже, что он не анализирует текст и не использует эвристику для определения того, является ли контент 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), и надежно определять, что это такое.Я попросил Нокогири проанализировать тело файла фида HTML или XML, но увидел противоречивые результаты.
Я думал, что смогу написать несколько тестов для определения типа, но потом столкнулся с тем, что 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
Я полагал, что xpaths будет работать с XML, но результаты также не заслуживают доверия.
Все эти тесты проводились на моем компьютере с Ubuntu, но я видел такое же поведение на своем Macbook Pro.Мне бы очень хотелось узнать, что я делаю что-то не так, но я не видел примера синтаксического анализа и поиска, который дал бы мне последовательные результаты.Может ли кто-нибудь показать мне ошибку моего пути?
Решение
Это связано с тем, как Нокогири метод анализа работает.Вот источник:
# 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#parse
или Nokogiri::XML::Document#parse
напрямую.
Другие советы
Отвечая на эту часть вашего вопроса:
Я подумал, что могу написать некоторые тесты, чтобы определить тип, но тогда я столкнулся с XPaths, не исходя из элементов, но обычные поиски работают:
Я только что столкнулся с этой проблемой, используя nokogiri для анализа потока атомов.Проблема, похоже, сводилась к анонимному объявлению пространства имен:
<feed xmlns="http://www.w3.org/2005/Atom">
Удаление объявления xmlns из исходного xml позволит Нокогири выполнять поиск с помощью xpath, как обычно.Удаление этого объявления из фида, очевидно, здесь не вариант, поэтому вместо этого я просто удалил пространства имен из документа после анализа.например:
doc = Nokogiri.parse(open('http://feeds.feedburner.com/RidingRails'))
doc.remove_namespaces!
doc.xpath('/feed/entry').length
Я знаю, это некрасиво, но это сработало.