使用XmlSlurper:如何在迭代GPathResult时选择子元素

时间:2009-11-04 17:51:09

标签: html parsing groovy xmlslurper

我正在编写一个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表达式正在收集文本中的所有href,即使我传递了对父“li”节点的引用。

你可以告诉我吗:

  • 为什么我要显示输出
  • 如何检索每个'li'项目的href /地址对

感谢。

3 个答案:

答案 0 :(得分:11)

将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。

答案 1 :(得分:1)

这是一个棘手的问题。当只有一个元素有class =&#39; divclass&#39;以前的答案肯定没问题。如果grep有多个结果,那么单个结果的find()不是答案。指出结果是ArrayList是正确的。插入外部嵌套的.each()循环会在闭包参数 div 中提供GPathResult。从这里向下钻取可以继续预期的结果。

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中的List上访问属性时,您将获得一个新列表(大小相同),其中包含列表中每个元素的属性。 grep()找到的列表只有一个条目。然后我们得到一个属性 ol 的条目,这很好。接下来我们得到该条目的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 ,然后是地址

答案 2 :(得分:0)

我相信在撰写本文时,对于使用的版本,之前的答案都是正确的。但我使用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"
}

调试非常令人沮丧,希望它可以帮助某人。