清单 12. XmlParser 将属性作为 HashMap 对待
def langs = new XmlParser().parseText(xml) println langs.attribute("count") // 3 langs.attributes().each{k,v-> println "-" * 15 println k println v } //output: --------------- type current --------------- count 3 --------------- mainstream true |
与操作属性相类似,XmlParser 为处理元素提供了更好的支持。
使用 XmlParser 获取元素
XmlParser 提供了一种直观的查询元素的方法,称作 GPath。(它与 XPath 类似,仅在 Groovy 中得到了实现。)举例来说,清单 13 演示了我之前使用的 langs.language 结构返回了包含查询结构的 groovy.util.NodeList。NodeList 扩展了 java.util.ArrayList,因此它基本上就是一个赋予了 GPath 超级权限的 List。
清单 13. 使用 GPath 和 XmlParser 进行查询
def langs = new XmlParser().parseText(xml) // shortcut query syntax // on an anonymous NodeList langs.language.each{ println it.text() } // separating the query // and the each closure // into distinct parts def list = langs.language list.each{ println it.text() } println list.getClass() // groovy.util.NodeList |
当然,GPath 是对 MarkupBuilder 的补充。它所采用的技巧与调用不存在的 phantom 方法相同,区别仅在于它用于查询已有的 XML 而不是动态地生成 XML。
知道 GPath 查询的结果是 List 之后,您可以让您的代码更加简练。Groovy 提供了一个 spread-dot 运算符。在单行代码中,它基本上能迭代整个列表并对每个项执行方法调用。结果将作为 List 返回。举例来说,如果您只关心对查询结果中的各个项调用 Node.text() 方法,那么清单 14 展示了如何在一行代码中实现它:
清单 14. 结合 spread-dot 运算符与 GPath
// the long way of gathering the results def results = [] langs.language.each{ results << it.text() } // the short way using the spread-dot operator def values = langs.language*.text() // [Java, Groovy, JavaScript] // quickly gathering up all of the version attributes def versions = langs.language*.attribute("version") // [1.5, 1.6.0, 1.9] |
和功能强大的 XmlParser 一样,XmlSlurper 也实现了更高级别的处理。
使用 XmlSlurper 解析 XML
在 清单 2 中,我说过 Groovy 给我的感觉是在直接操作 XML。XmlParser 的功能相当不错,但它只允许您以编程的方式来操作 XML。您可以使用由 Node 组成的 List 以及由 Attribute 组成的 HashMap,并且仍然需要调用 Node.attribute() 和 Node.text() 等方法才能获取核心数据。XmlSlurper 将删除方法调用的最后痕迹,让您感觉就像是在直接处理 XML。
从技术上说,XmlParser 返回 Node 和 NodeList,而 XmlSlurper 返回一个 groovy.util.slurpersupport.GPathResult。但既然您已经知道,因此我希望您能忘记之前提到的 XmlSlurper 的实现细节。如果您能忽略其内部原理,那么将更好地领略其魔力。
清单 15 同时展示了一个 XmlParser 和一个 XmlSlurper:
清单 15. XmlParser 和 XmlSlurper
def xml = """ <langs type='current' count='3' mainstream='true'> <language flavor='static' version='1.5'>Java</language> <language flavor='dynamic' version='1.6.0'>Groovy</language> <language flavor='dynamic' version='1.9'>JavaScript</language> </langs> """ def langs = new XmlParser().parseText(xml) println langs.attribute("count") langs.language.each{ println it.text() } langs = new XmlSlurper().parseText(xml) println langs._cnnew1@count langs.language.each{ println it } |
注意,XmlSlurper 忽略了任何方法调用的概念。您并没有调用 langs.attribute("count"),而是调用了 langs.@count。@ 符号是从 XPath 借过来的,但其结果是,您感觉像是在直接操作属性(与调用 attribute() 方法相反)。您没有调用 it.text(),而仅仅调用了 it。我们假设您希望直接操作元素的内容。
现实中的 XmlSlurper
除了 langs 和 language 之外,这里还提供了实际的 XmlSlurper 示例。Yahoo! 以 RSS 提要的方式按 ZIP 码提供天气情况信息。当然,RSS 是 XML 中的一种专门术语。在 Web 浏览器中键入 http://weather.yahooapis.com/forecastrss?p=80020。可以随意将 Broomfield, Colorado 的 ZIP 码换成您自己的。清单 16 显示了最终 RSS 提要的简单版本:
清单 16. 显示最新天气情况的 Yahoo! RSS 提要
<rss version="2.0" xmlns:yweather="http://xml.weather.yahoo.com/ns/rss/1.0" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"> <channel> <title>Yahoo! Weather - Broomfield, CO</title> <yweather:location city="Broomfield" region="CO" country="US"/> <yweather:astronomy sunrise="6:36 am" sunset="5:50 pm"/> <item> <title>Conditions for Broomfield, CO at 7:47 am MST</title> <pubDate>Fri, 27 Feb 2009 7:47 am MST</pubDate> <yweather:condition text="Partly Cloudy" code="30" temp="25" date="Fri, 27 Feb 2009 7:47 am MST" /> </item> </channel> </rss> |
您要做的第一件事就是通过编程来使用这个 RSS。创建一个名称为 weather.groovy 的文件,并添加如清单 17 所示的代码:
清单 17. 以编程的方式获取 RSS
def baseUrl = "http://weather.yahooapis.com/forecastrss" if(args){ def zip = args[0] def url = baseUrl + "?p=" + zip def xml = url.toURL().text println xml }else{ println "USAGE: weather zipcode" } |
在命令行中键入 groovy weather 80020,确定您可以看到原始 RSS。
此脚本最重要的部分是 url.toURL().text。url 变量是一个格式良好的 String。Groovy 在所有 String 中都添加了一个 toURL() 方法,用于将它们转换成 java.net.URL。然后,Groovy 在所有 URL 中又添加了一个 getText() 方法,用于执行 HTTP GET 请求并将结构作为 String 返回。
现在,您已经将 RSS 存储在了 xml 变量中,并通过 XmlSlurper 实现了一些有趣的功能,如清单 18 所示:
清单 18. 使用 XmlSlurper 解析 RSS
def baseUrl = "http://weather.yahooapis.com/forecastrss" if(args){ def zip = args[0] def url = baseUrl + "?p=" + zip def xml = url.toURL().text def rss = new XmlSlurper().parseText(xml) println rss.channel.title println "Sunrise: ${rss.channel.astronomy.@sunrise}" println "Sunset: ${rss.channel.astronomy.@sunset}" println "Currently:" println "\t" + rss.channel.item.condition.@date println "\t" + rss.channel.item.condition.@temp println "\t" + rss.channel.item.condition.@text }else{ println "USAGE: weather zipcode" } //output: Yahoo! Weather - Broomfield, CO Sunrise: 6:36 am Sunset: 5:50 pm Currently: Fri, 27 Feb 2009 7:47 am MST 25 Partly Cloudy |
XmlSlurper 让您可以自然地处理 XML,不是吗?您通过直接引用 <title> 元素来打印它 — rss.channel.title。您使用一个简单的 rss.channel.item.condition.@temp 来去除 temp 属性。这与编程的感觉不同。它更像是在直接操作 XML。