2009-07-21 3 views
5

Ecco un esempio di qualche stranezza:Come posso ottenere Nokogiri per analizzare e restituire un documento XML?

#!/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" 

L'esecuzione di questo ritorna:

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

Senza la read rendimenti XML, e con esso è l'HTML? La pagina Web è definito come "XHTML di transizione", quindi in un primo momento ho pensato Nokogiri deve essere stato la lettura di "Content-Type" di OpenURI dal flusso, ma che restituisce 'text/html':

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

che è ciò che il server sta tornando . Quindi, ora sto cercando di capire perché Nokogiri sta restituendo due valori diversi. Non sembra che analizzi il testo e utilizzi l'euristica per determinare se il contenuto è HTML o XML.

La stessa cosa sta accadendo con il feed ATOM puntato da quella pagina:

(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 

ho bisogno di essere in grado di analizzare una pagina senza sapere di cosa si tratta in anticipo, HTML o un feed (RSS o ATOM) e determinare in modo affidabile quale sia. Ho chiesto a Nokogiri di analizzare il corpo di un file di feed HTML o XML, ma sto vedendo quei risultati incoerenti.

ho pensato che avrei potuto scrivere alcuni test per determinare il tipo, ma poi mi sono imbattuto in XPaths non trovare elementi, ma le ricerche normali di lavoro:

(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 

ho pensato XPaths avrebbe funzionato con XML, ma i risultati non sembra affidabile.

Questi test sono stati tutti eseguiti sulla mia scatola Ubuntu, ma ho visto lo stesso comportamento sul mio Macbook Pro. Mi piacerebbe scoprire che sto facendo qualcosa di sbagliato, ma non ho visto un esempio per l'analisi e la ricerca che mi ha dato risultati coerenti. Qualcuno può mostrarmi l'errore dei miei modi?

+0

Ironicamente questo in realtà NON è una domanda ... –

risposta

12

Ha a che fare con il modo in cui funziona Nokogiri parse method. Ecco la fonte:

# 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 

La chiave è la linea if string =~ /^\s*<[^Hh>]*html/i # Probably html. Quando usi semplicemente open, restituisce un oggetto che non funziona con espressioni regolari, quindi restituisce sempre false. D'altra parte, read restituisce una stringa, quindi è possibile che possa essere considerato HTML come. In questo caso lo è, perché corrisponde alla regex. Ecco l'inizio di quella stringa:

<!DOCTYPE html PUBLIC 

l'espressione regolare corrisponde al "DOCTYPE!" Per [^Hh>]* e poi corrisponde al "html", assumendo così è HTML. Perché qualcuno ha selezionato questo regex per determinare se il file è HTML è oltre me. Con questa regex, un file che inizia con un tag come <definitely-not-html> è considerato HTML, ma <this-is-still-not-html> è considerato XML. Probabilmente stai meglio stando lontano da questa stupida funzione e invocando direttamente Nokogiri::HTML::Document#parse o Nokogiri::XML::Document#parse.

+0

Ah. E Ugh. Sì, è molto facile ingannare. Per aggirare il problema ho scritto alcuni metodi per entrambi i tipi di documento che eseguono alcuni test per "/ html/head" e i tag per RSS e ATOM e sembrano catturare in modo affidabile i documenti HTML, RSS e ATOM. Sto analizzando un documento sia come HTML :: Document che XML :: Document, e non mi piace doverlo fare. Penso che Hpricot punti un punto perché ha solo un tipo di documento. Ora, perché una ricerca ".xpath ('/ feed/entry') fallisce ma" .search (feed entry) "avrà esito positivo su un Nokogiri :: XML :: Document? Questo mi fa impazzire anche perché non lo fa t sembra coerente –

+3

Tecnicamente il selettore CSS 'feed entry' non è equivalente a XPath'/feed/entry'. L'XPath equivalente è '// feed // entry'. Nel caso di Atom, il tuo XPath originale è il problema è che devi includere gli spazi dei nomi Prova questo: '/ xmlns: feed/xmlns: entry' – Pesto

+0

Grazie Pesto, sei stato molto utile! –

5

In risposta a questa parte della tua domanda:

ho pensato che avrei potuto scrivere alcuni test per determinare il tipo, ma poi mi sono imbattuto in XPaths non trovare elementi, ma ricerche normali di lavoro:

Mi sono appena imbattuto in questo problema usando nokogiri per analizzare un feed atomico. Il problema sembrava fino alla dichiarazione di nome-spazio anonimo:

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

Rimozione della dichiarazione xmlns dal XML di origine permetterebbe Nokogiri per cercare con XPath come al solito. Rimuovere la dichiarazione dal feed ovviamente non era un'opzione, quindi ho solo rimosso gli spazi dei nomi dal documento dopo l'analisi. es .:

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

Brutto lo so, ma ha funzionato.

+3

+1 per il metodo remove_namespaces! Non l'ho mai saputo e il tuo commento mi ha fatto risparmiare un sacco di tempo – rhh

+0

Il sito di Nokogiri dice di farlo, con l'avvertenza che devi sapere che non ci sono collisioni tra Un tag, o, se ci sono collisioni non ti interessa. –