Utilizzando XmlSlurper:Come selezionare i sottoelementi durante l'iterazione su GPathResult

StackOverflow https://stackoverflow.com/questions/1675542

Domanda

Sto scrivendo un parser HTML, che utilizza TagSoup per passare una struttura ben formata a XMLSlurper.

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 { it.@class == 'divclass' }.ol.li.each { linkItem ->
    def link = linkItem.h3.a.@href
    def address = linkItem.address.text()
    println "$link: $address\n"
}

Mi aspetto che ciascuno mi permetta di selezionare ciascun "li" a turno in modo da poter recuperare il corrispondente href e i dettagli dell'indirizzo.Invece, ottengo questo output:

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

Ho controllato vari esempi sul Web e questi riguardano XML o sono esempi di una riga come "recupera tutti i collegamenti da questo file".Sembra che l'espressione it.h3.a.@href raccolga tutti gli href nel testo, anche se le sto passando un riferimento al nodo genitore "li".

Puoi farmi sapere:

  • Perché ricevo l'output mostrato
  • Come posso recuperare le coppie href/indirizzo per ciascun elemento "li".

Grazie.

È stato utile?

Soluzione

Sostituisci grep con find:

html.'**'.find { it.@class == 'divclass' }.ol.li.each { linkItem ->
    def link = linkItem.h3.a.@href
    def address = linkItem.address.text()
    println "$link: $address\n"
}

allora otterrai

#href1: Here is the addressTelephone number: telephone

#href2: Here is another addressAnother telephone: 0845 1111111

grep restituisce un ArrayList ma find restituisce una classe NodeChild:

println html.'**'.grep { it.@class == 'divclass' }.getClass()
println html.'**'.find { it.@class == 'divclass' }.getClass()

risulta in:

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

quindi se volessi usare grep potresti nidificarne un altro in questo modo affinché funzioni

html.'**'.grep { it.@class == 'divclass' }.ol.li.each {
    it.each { linkItem ->
        def link = linkItem.h3.a.@href
        def address = linkItem.address.text()
        println "$link: $address\n"
    }
}

Per farla breve, nel tuo caso, usa find anziché grep.

Altri suggerimenti

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

html."**".grep { it.@class == 'divclass' }.each { div -> div.ol.li.each { linkItem ->
   def link = linkItem.h3.a.@href
   def address = linkItem.address.text()
   println "$link: $address\n"
}}

Anche il comportamento del codice originale può richiedere una spiegazione in più.Quando si accede a una proprietà su un elenco in Groovy, otterrai un nuovo elenco (della stessa dimensione) con la proprietà di ciascun elemento nell'elenco.L'elenco trovato da grep() ha una sola voce.Quindi otteniamo una voce per la proprietà ol, il 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 { it.@class == 'divclass' }.ol.li.each { it.each { linkItem ->
   def link = linkItem.h3.a.@href
   def address = linkItem.address
   println "$link: $address\n"
}}

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

Credo che le risposte precedenti siano tutte corrette al momento in cui scrivo, 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. Sembra che ciò sia dovuto a NekoHTML utilizzato sotto il cofano:

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

Per questo motivo, la soluzione nella risposta accettata deve essere scritta come:

html.'**'.find { it.@class == 'divclass' }.OL.LI.each { linkItem ->
    def link = linkItem.H3.A.@href
    def address = linkItem.ADDRESS.text()
    println "$link: $address\n"
}

È stato molto frustrante eseguire il debug, spero che aiuti qualcuno.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top