2009-11-04 11 views
8

Sto scrivendo un parser HTML, che utilizza TagSoup per passare una struttura ben formata a XMLSlurper.Utilizzo di XmlSlurper: Come selezionare elementi secondari durante l'iterazione su GPathResult

Ecco il codice generalizzato:

def htmlText = """ 
<html> 
<body> 
<div id="divId" class="divclass"> 
<h2>Heading 2</h2> 
<ol> 
<li><h3><a class="box" href="#href1">href1 link text</a> <span>extra stuff</span></h3><address>Here is the address<span>Telephone number: <strong>telephone</strong></span></address></li> 
<li><h3><a class="box" href="#href2">href2 link text</a> <span>extra stuff</span></h3><address>Here is another address<span>Another telephone: <strong>0845 1111111</strong></span></address></li> 
</ol> 
</div> 
</body> 
</html> 
"""  

def html = new XmlSlurper(new org.ccil.cowan.tagsoup.Parser()).parseText(htmlText); 

html.'**'.grep { [email protected] == 'divclass' }.ol.li.each { linkItem -> 
    def link = [email protected] 
    def address = linkItem.address.text() 
    println "$link: $address\n" 
} 

mi aspetterei il ciascuno per mi permette di selezionare ogni 'li', a sua volta in modo da poter recuperare le corrispondenti href e indirizzo. Invece, io sono sempre questo output:

#href1#href2: Here is the addressTelephone number: telephoneHere is another addressAnother telephone: 0845 1111111 

Ho controllato vari esempio sul web e questi sia occupo di XML, o sono esempi uno-liner come "recuperare tutti i link di questo file". Sembra che l'espressione href di it.h3.a. @ stia raccogliendo tutti gli hrefs nel testo, anche se sto passando un riferimento al nodo "li" principale.

Può farmi sapere:

  • Perché sto ottenendo l'output mostrato
  • Come posso recuperare le coppie href/indirizzo per ogni voce 'li'

Grazie.

risposta

11

Sostituire grep con find:

html.'**'.find { [email protected] == 'divclass' }.ol.li.each { linkItem -> 
    def link = [email protected] 
    def address = linkItem.address.text() 
    println "$link: $address\n" 
} 

allora avrai

#href1: Here is the addressTelephone number: telephone 

#href2: Here is another addressAnother telephone: 0845 1111111 

grep restituisce un ArrayList ma trovare restituisce una classe NodeChild:

println html.'**'.grep { [email protected] == 'divclass' }.getClass() 
println html.'**'.find { [email protected] == 'divclass' }.getClass() 

risultati in:

class java.util.ArrayList 
class groovy.util.slurpersupport.NodeChild 

quindi se si voleva utilizzare grep si potrebbe quindi nido un'altra ciascuno in questo modo per farlo funzionare

html.'**'.grep { [email protected] == 'divclass' }.ol.li.each { 
    it.each { linkItem -> 
     def link = [email protected] 
     def address = linkItem.address.text() 
     println "$link: $address\n" 
    } 
} 

Per farla breve, nel tuo caso, utilizzare piuttosto che trovare grep.

+0

Ottima risposta! –

1

Questo è stato difficile. Quando c'è solo un elemento con class = 'divclass' la risposta precedente va bene. Se ci sono più risultati di grep, un find() per un singolo risultato non è la risposta. Sottolineando che il risultato è un ArrayList è corretto. L'inserimento di un ciclo esterno .each() nidificato fornisce un GPathResult nel parametro di chiusura div. Da qui il drill down può continuare con il risultato atteso.

html."**".grep { [email protected] == 'divclass' }.each { div -> div.ol.li.each { linkItem -> 
    def link = [email protected] 
    def address = linkItem.address.text() 
    println "$link: $address\n" 
}} 

Il comportamento del codice originale può utilizzare anche un po 'più di spiegazione. Quando si accede a una proprietà su un elenco in Groovy, si otterrà un nuovo elenco (stessa dimensione) con la proprietà di ciascun elemento nell'elenco. L'elenco trovato da grep() ha solo una voce. Quindi otteniamo una voce per la proprietà o, che va bene. Successivamente otteniamo il risultato di ol.it per quella voce. È di nuovo una lista di size() == 1, ma questa volta con una voce di size() == 2.Potremmo applicare il ciclo esterno lì e ottenere lo stesso risultato, se volessimo:

html."**".grep { [email protected] == 'divclass' }.ol.li.each { it.each { linkItem -> 
    def link = [email protected] 
    def address = linkItem.address 
    println "$link: $address\n" 
}} 

Su qualsiasi GPathResult che rappresenta più nodi, si ottiene la concatenazione di tutto il testo. Questo è il risultato originale, prima per @ href, quindi per indirizzo.

0

Credo che le risposte precedenti siano tutte corrette al momento della stesura, per la versione utilizzata. Ma sto usando HTTPBuilder 0.7.1 e Grails 2.4.4 con Groovy 2.3.7 e c'è un grosso problema - Gli elementi HTML vengono trasformati in maiuscolo. Risulta questo è dovuto al NekoHTML utilizzato sotto il cofano:

http://nekohtml.sourceforge.net/faq.html#uppercase

A causa di questo, la soluzione nella risposta accettata deve essere scritto come:

html.'**'.find { [email protected] == 'divclass' }.OL.LI.each { linkItem -> 
    def link = [email protected] 
    def address = linkItem.ADDRESS.text() 
    println "$link: $address\n" 
} 

Questo è stato molto frustrante per il debug , spero che aiuti qualcuno.