2009-11-04 8 views
8

J'écris un analyseur HTML, qui utilise TagSoup pour passer une structure bien formée à XMLSlurper.Utilisation de XmlSlurper: Comment sélectionner des sous-éléments lors de l'itération sur un GPathResult

Voici le code généralisé:

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" 
} 

Je me attends à chaque me permettre de sélectionner chaque « li » à son tour pour que je puisse récupérer les détails de href et adresse correspondants. Au lieu de cela, je reçois cette sortie:

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

J'ai vérifié divers exemple sur le web et ceux-ci soit traiter avec XML, ou sont des exemples d'une doublure comme « récupérer tous les liens de ce fichier ». Il semble que l'expression it.h3.a. @ href collecte tous les hrefs dans le texte, même si je lui passe une référence au nœud parent 'li'.

Pouvez-vous me faire savoir:

  • Pourquoi je reçois maintenant le résultat
  • Comment puis-je récupérer les paires href/adresse pour chaque 'li' article

Merci.

Répondre

11

Remplacer grep avec find:

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

vous obtiendrez

#href1: Here is the addressTelephone number: telephone 

#href2: Here is another addressAnother telephone: 0845 1111111 

grep retourne un ArrayList mais trouver retourne une classe de NodeChild:

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

résultats dans:

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

donc si vous vouliez utiliser grep vous pourriez alors une autre nid chacun comme celui-ci pour que cela fonctionne

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

Longue histoire courte, dans votre cas, utilisez plutôt que trouver grep.

+0

Excellente réponse! –

1

Cela a été difficile. Quand il n'y a qu'un élément avec class = 'divclass', la réponse précédente est correcte. S'il y avait plusieurs résultats de grep, alors find() pour un seul résultat n'est pas la réponse. Faire remarquer que le résultat est une ArrayList est correct. L'insertion d'une boucle .each() imbriquée externe fournit un paramètre GPathResult dans le paramètre de fermeture div. De là, le forage peut continuer avec le résultat attendu.

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" 
}} 

Le comportement du code d'origine peut également utiliser un peu plus d'explications. Quand une propriété est accédée sur une liste dans Groovy, vous obtiendrez une nouvelle liste (même taille) avec la propriété de chaque élément dans la liste. La liste trouvée par grep() a une seule entrée. Ensuite, nous obtenons une entrée pour la propriété ol, ce qui est bien. Nous obtenons ensuite le résultat de ol.it pour cette entrée. C'est une liste de taille() == 1 à nouveau, mais cette fois avec une entrée de size() == 2.Nous pourrions appliquer la boucle extérieure et il obtenir le même résultat, si nous voulions:

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

Sur tout GPathResult représentant plusieurs nœuds, nous obtenons la concaténation de tout le texte. C'est le résultat original, d'abord pour @href, puis pour adresse.

0

Je crois que les réponses précédentes sont toutes correctes au moment de l'écriture, pour la version utilisée. Mais j'utilise HTTPBuilder 0.7.1 et Grails 2.4.4 avec Groovy 2.3.7 et il y a un gros problème - Les éléments HTML sont transformés en majuscules. Il semble que cela est dû à NekoHTML utilisé sous le capot:

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

Pour cette raison, doit être écrit la solution dans la réponse acceptée comme:

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

Cela a été très frustrant de debug , j'espère que ça aide quelqu'un.