Usando XmlSlurper: Como selecionar sub-elementos, enquanto a iteração sobre um GPathResult

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

Pergunta

Eu estou escrevendo um analisador HTML, que usa TagSoup para passar uma estrutura bem formada para XmlSlurper.

Aqui está o código generalizada:

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

Espero que o que cada um deixe-me selecionar cada 'li' por sua vez, para que eu possa recuperar os correspondentes href e endereço. Em vez disso, eu estou recebendo esta saída:

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

Eu verifiquei vários exemplo na web e estes quer lidar com XML, ou são exemplos de uma linha como "recuperar todos os links deste arquivo". É parece que a it.h3.a. @ expressão href está a recolher todos os hrefs no texto, mesmo que eu estou passando-o uma referência para o pai nó 'li'.

Você pode me deixar saber:

  • Por que eu estou recebendo a saída mostrada
  • Como posso recuperar os pares href / endereço para cada item 'li'

Graças.

Foi útil?

Solução

Substituir grep com 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"
}

então você vai ter

#href1: Here is the addressTelephone number: telephone

#href2: Here is another addressAnother telephone: 0845 1111111

grep retorna um ArrayList mas encontrar retornos uma classe NodeChild:

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

resulta em:

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

Assim, se você quisesse usar grep Você poderia, então ninho outro cada como este para que ele funcione

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

Para encurtar a história, no seu caso, o uso encontrar, em vez de grep.

Outras dicas

Este foi é um assunto delicado. Quando há apenas um elemento com 'divclass' class = a resposta anterior certeza é bom. Se houvesse vários resultados de grep, então um find () para um único resultado não é a resposta. Salientando que o resultado é um ArrayList está correto. Inserindo um loop externo .each aninhada () fornece uma GPathResult no parâmetro fechamento div . A partir daqui o baixo broca pode continuar com o resultado esperado.

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

O comportamento do código original pode usar um pouco mais de uma explicação bem. Quando uma propriedade é acessado em uma lista em Groovy, você vai ter uma nova lista (mesmo tamanho) com a propriedade de cada elemento na lista. A lista encontrada pelo grep () tem apenas uma entrada. Então, temos uma entrada para a propriedade ol , que é bom. Em seguida, obter o resultado de ol.it para essa entrada. É uma lista de tamanho () == 1 novamente, mas desta vez com uma entrada de tamanho () == 2. Poderíamos aplicar o loop externo lá e obter o mesmo resultado, se quiséssemos:

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

Em qualquer GPathResult representando vários nós, temos a concatenação de todo o texto. Esse é o resultado original, pela primeira vez para @href , em seguida, para a endereço .

Eu acredito que as respostas anteriores estão todos corretos no momento da escrita, para a versão utilizada. Mas eu estou usando HTTPBuilder 0.7.1 e 2.4.4 Grails com Groovy 2.3.7 e não é um grande problema - elementos HTML são transformados em maiúsculas Parece que este é devido a NekoHTML usado sob o capô. :

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

Devido a isso, a solução na resposta aceita deve ser escrita como:

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

Isto foi muito frustrante para depuração, espero que ajude alguém.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top