Использование XmlSlurper:Как выбрать подэлементы при переборе GPathResult

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

Вопрос

Я пишу анализатор HTML, который использует TagSoup для передачи правильно сформированной структуры в XMLSlurper.

Вот обобщенный код:

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

Я ожидаю, что каждый позволит мне выбрать каждый «li» по очереди, чтобы я мог получить соответствующие данные href и адреса.Вместо этого я получаю этот вывод:

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

Я проверил различные примеры в Интернете, и они либо связаны с XML, либо представляют собой однострочные примеры, такие как «получить все ссылки из этого файла».Кажется, что выражение it.h3.a.@href собирает все hrefs в тексте, хотя я передаю ему ссылку на родительский узел «li».

Можете ли вы сообщить мне:

  • Почему я получаю показанный результат
  • Как я могу получить пары href/адрес для каждого элемента «li»

Спасибо.

Это было полезно?

Решение

Замените grep на 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"
}

тогда ты получишь

#href1: Here is the addressTelephone number: telephone

#href2: Here is another addressAnother telephone: 0845 1111111

grep возвращает ArrayList, но find возвращает класс NodeChild:

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

приводит к:

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

таким образом, если вы хотите использовать grep, вы можете затем вложить еще один такой, чтобы он работал

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

Короче говоря, в вашем случае используйте find, а не grep.

Другие советы

Это был непростой вопрос.Когда есть только один элемент с class='divclass', предыдущий ответ, конечно, подойдет.Если было несколько результатов от grep, то find() для одного результата не является ответом.Указание на то, что результатом является ArrayList, является правильным.Вставка внешнего вложенного цикла .each() предоставляет GPathResult в параметре закрытия. div.Отсюда детализация может продолжиться с ожидаемым результатом.

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

Поведение исходного кода также требует более подробного объяснения.При доступе к свойству в списке в Groovy вы получаете новый список (того же размера) со свойством каждого элемента в списке.Список, найденный с помощью grep(), содержит только одну запись.Затем мы получаем одну запись для свойства ол, и это нормально.Затем мы получаем результат ol.it для этой записи.Это снова список size() == 1, но на этот раз с записью size() == 2.Мы могли бы применить внешний цикл и получить тот же результат, если бы захотели:

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

Для любого GPathResult, представляющего несколько узлов, мы получаем объединение всего текста.Это первоначальный результат, сначала для @href, то для адрес.

Я считаю, что все предыдущие ответы верны на момент написания для используемой версии.Но я использую HTTPBuilder 0.7.1 и Grails 2.4.4 с Groovy 2.3.7, и есть большая проблема: HTML-элементы преобразуются в верхний регистр. Похоже, это связано с тем, что под капотом используется NekoHTML:

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

По этой причине решение в принятом ответе должно быть записано как:

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

Это было очень неприятно отлаживать, надеюсь, это кому-то поможет.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top