[img]//www.ibm.com/i/c.gif" alt="" width="10" height="1[/img]
关于本系列
Groovy 是在 Java 平台上运行的一种现代编程语言。它提供与已有 Java 代码的无缝集成,同时引入了各种生动的新特性,比如说闭包和元编程。简单来讲,Groovy 是 Java 语言的 21 世纪版本。
将任何新工具整合到开发工具包中的关键是知道何时使用它以及何时将它留在工具包中。Groovy 的功能可以非常强大,但惟一的条件是正确应用于适当的场景。因此, 实战 Groovy 系列将探究 Groovy 的实际应用,以便帮助您了解何时以及如何成功使用它们。
比较 Java 和 Groovy XML 解析
在 “for each 剖析” 的结束部分,我提供了一个如清单 1 所示的简单 XML 文档。(这次,我添加了 type 属性,稍微增加了它的趣味性。)
清单 1. XML 文档,其中列出了我知道的语言
在 Java 语言中解析这个简单的 XML 文档却丝毫不简单,如清单 2 所示。它使用了 30 行代码来解析 5 行 XML 文件。
清单 2. 在 Java 中解析 XML 文件
比较清单 2 中的 Java 代码和清单 3 中相应的 Groovy 代码:
清单 3. 在 Groovy 中解析 XML
Groovy 代码最出色的地方并不是它要比相应的 Java 代码简短很多 — 虽然使用 5 行 Groovy 代码解析 5 行 XML 是一个压倒性的优势。Groovy 代码最让我欣喜的一个地方就是它更具表达性。在编写 langs.language.each 时,我的感觉就像是在直接操作 XML。在 Java 版本中,您再也看不到 XML。
字符串变量和 XML
当您将 XML 存储在 String 变量而不是文件中时,在 Groovy 中使用 XML 的好处会变得更加明显。Groovy 的三重引号(在其他语言中通常称作 HereDoc)使得在内部存储 XML 变得非常轻松,如清单 4 所示。这与清单 3 中的 Groovy 示例之间的惟一区别就是将 XmlParser 方法调用从 parse()(它处理 File、InputStreams、Reader 和 URI)切换到 parseText()。
清单 4. 将 XML 存储在 Groovy 内部
注意,三重引号可以轻松地处理多行 XML 文档。xml 变量是一个真正的普通旧式(plain-old)java.lang.String — 您可以添加 println xml.class 自己进行验证。三重引号还可以处理 type="current" 的内部引号,而不会强制您像在 Java 代码中那样使用反斜杠字符手动进行转义。
比较清单 4 中简洁的 Groovy 代码与清单 5 中相应的 Java 代码:
清单 5. 在 Java 代码内部存储 XML
注意,xml 变量受到了针对内部引号和换行符的转义字符的污染。然而,更糟的是需要将 String 转换成一个 byte 数组,然后再转换成 ByteArrayInputStream 才能进行解析。DocumentBuilder 未提供将简单 String 作为 XML 解析的直观方法。
通过 MarkupBuilder 创建 XML
Groovy 相对 Java 语言最大的优势体现于在代码中创建 XML 文档。清单 6 显示了创建 5 行 XML 代码段所需的 50 行 Java 代码:
清单 6. 使用 Java 代码创建 XML
我知道一些人会立刻抱怨。许多第三方库都可以简化此代码 — JDOM 和 dom4j 是其中最流行的两个。但是,任何 Java 库都无法与使用 Groovy MarkupBuilder 的简洁性相比,如清单 7 所示:
清单 7. 使用 Groovy 创建 XML
注意到这与 XML 代码的比率又重新回到了将近 1:1。更加重要的是,我可以再次查看 XML。当然,尖括号已经被替换为大括号,并且属性使用冒号(Groovy 的 HashMap 符号)而不是等号,但其基本结构在 Groovy 或 XML 中都是可以辨认的。它几乎类似于一个用于构建 XML 的 DSL,您认为呢?
Groovy 能够实现这种 Builder 魔法,因为它是一种动态的语言。另一方面,Java 语言则是静态的:Java 编译器将确保所有方法在您调用它们之前都是确实存在的。(如果您尝试调用不存在的方法,Java 代码甚至不进行编译,更不用说运行了。)但是,Groovy 的 Builder 证明,某种语言中的 bug 正是另一种语言的特性。如果您查阅 API 文档中的 MarkupBuilder 相关部分,您会发现它没有 langs() 方法、language() 方法或任何其他元素名称。幸运的是,Groovy 可以捕获这些不存在的方法调用,并采取一些有效的操作。对于 MarkupBuilder 的情况,它使用 phantom 方法调用并生成格式良好的 XML。
清单 8
对我刚才给出的简单的 MarkupBuilder 示例进行了扩展。如果您希望在 String 变量中捕获 XML 输出,则可以传递一个 StringWriter 到 MarkupBuilder 的构造函数中。如果您希望添加更多属性到 langs 中,只需要在传递时使用逗号将它们分开。注意,language 元素的主体是一个没有前置名称的值。您可以在相同的逗号分隔的列表中添加属性和主体。
清单 8. 经过扩展的 MarkupBuilder 示例
通过这些 MarkupBuilder 技巧,您可以实现一些有趣的功能。举例来说,您可以快速构建一个格式良好的 HTML 文档,并将它写出到文件中。清单 9 显示了相应的代码:
清单 9. 通过 MarkupBuilder 构建 HTML
图 1 显示了清单 9 所构建的 HTML 的浏览器视图:
图 1. 呈现的 HTML
使用 StreamingMarkupBuilder 创建 XML
MarkupBuilder 非常适合用于同步构建简单的 XML 文档。对于更加高级的 XML 创建,Groovy 提供了一个 StreamingMarkupBuilder。通过它,您可以添加各种各样的 XML 内容,比如说处理指令、名称空间和使用 mkp 帮助对象的未转义文本(非常适合 CDATA 块)。清单 10 展示了有趣的 StreamingMarkupBuilder 特性:
清单 10. 使用 StreamingMarkupBuilder 创建 XML
注意,StreamingMarkupBuilder 直到您调用 bind() 方法时才会生成最终的 XML,该方法将接受标记和所有指令。这允许您异步构建 XML 文档的各个部分,并同时输出它们。(参见 参考资料 了解更多信息。)
理解 XmlParser
Groovy 为您提供了两种生成 XML — MarkupBuilder 和 StreamingMarkupBuilder 的方式 — 它们分别具备不同的功能。解析 XML 也同样如此。您可以使用 XmlParser 或者 XmlSlurper。
XmlParser 提供了更加以程序员为中心的 XML 文档视图。如果您习惯于使用 List 和 Map(分别对应于 Element 和 Attribute)来思考文档,则应该能够适应 XmlParser。
清单 11 稍微解析了 XmlParser 的结构:
清单 11. XmlParser 详细视图
注意,XmlParser.parseText() 方法返回了一个 groovy.util.Node — 在本例中是 XML 文档的根 Node。当您调用 println langs 时,它会调用 Node.toString() 方法,以便返回调试输出。要获取真实数据,您需要调用 Node.attribute() 或者 Node.text()。
使用 XmlParser 获取属性
如前所述,您可以通过调用 Node.attribute("key") 来获取单独的属性。如果您调用 Node.attributes(),它会返回包含所有 Node 的属性的 HashMap。使用您在 “for each 剖析” 一文中所掌握的 each 闭包,遍历每个属性简直就是小菜一碟。清单 12 显示了一个相应的例子。(参见 参考资料,获取关于 groovy.util.Node 的 API 文档。)
清单 12. XmlParser 将属性作为 HashMap 对待
与操作属性相类似,XmlParser 为处理元素提供了更好的支持。
使用 XmlParser 获取元素
XmlParser 提供了一种直观的查询元素的方法,称作 GPath。(它与 XPath 类似,仅在 Groovy 中得到了实现。)举例来说,清单 13 演示了我之前使用的 langs.language 结构返回了包含查询结构的 groovy.util.NodeList。NodeList 扩展了 java.util.ArrayList,因此它基本上就是一个赋予了 GPath 超级权限的 List。
清单 13. 使用 GPath 和 XmlParser 进行查询
当然,GPath 是对 MarkupBuilder 的补充。它所采用的技巧与调用不存在的 phantom 方法相同,区别仅在于它用于查询已有的 XML 而不是动态地生成 XML。
知道 GPath 查询的结果是 List 之后,您可以让您的代码更加简练。Groovy 提供了一个 spread-dot 运算符。在单行代码中,它基本上能迭代整个列表并对每个项执行方法调用。结果将作为 List 返回。举例来说,如果您只关心对查询结果中的各个项调用 Node.text() 方法,那么清单 14 展示了如何在一行代码中实现它:
清单 14. 结合 spread-dot 运算符与 GPath
和功能强大的 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
注意,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。创建一个名称为 weather.groovy 的文件,并添加如清单 17 所示的代码:
清单 17. 以编程的方式获取 RSS
在命令行中键入 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
XmlSlurper 让您可以自然地处理 XML,不是吗?您通过直接引用 <title> 元素来打印它 — rss.channel.title。您使用一个简单的 rss.channel.item.condition.@temp 来去除 temp 属性。这与编程的感觉不同。它更像是在直接操作 XML。
您是否注意到 XmlSlurper 甚至忽略了名称空间?您在构造函数中启用名称空间感知,但我很少这样做。非常简单,XmlSlurper 能像切黄油那样分解 XML。
<hr/>
结束语
要在如今成为一名成功的开发人员,您需要一系列能简化 XML 处理的工具。Groovy 的 MarkupBuilder 和 StreamingMarkupBuilder 可以非常轻松地动态创建 XML。XmlParser 能为您提供由 Element 组成的 List 以及由 Attribute 组成的 HashMap,并且 XmlSlurper 可以让代码全部消失,让您感觉是在直接操作 XML。
如果没有 Groovy 的动态功能,XML 处理的强大功能将不可能实现。在下一章文章中,我将更加深入地探索 Groovy 的动态特性。您将了解元编程在 Groovy 中的工作原理,从标准 JDK 类(如 String.toURL() 和 List.each())中添加的出色方法到您自己添加的自定义方法。在阅读了这两篇文章之后,我希望您能充分了解 Groovy 的实际应用。
关于本系列
Groovy 是在 Java 平台上运行的一种现代编程语言。它提供与已有 Java 代码的无缝集成,同时引入了各种生动的新特性,比如说闭包和元编程。简单来讲,Groovy 是 Java 语言的 21 世纪版本。
将任何新工具整合到开发工具包中的关键是知道何时使用它以及何时将它留在工具包中。Groovy 的功能可以非常强大,但惟一的条件是正确应用于适当的场景。因此, 实战 Groovy 系列将探究 Groovy 的实际应用,以便帮助您了解何时以及如何成功使用它们。
比较 Java 和 Groovy XML 解析
在 “for each 剖析” 的结束部分,我提供了一个如清单 1 所示的简单 XML 文档。(这次,我添加了 type 属性,稍微增加了它的趣味性。)
清单 1. XML 文档,其中列出了我知道的语言
<langs type="current"> <language>Java</language> <language>Groovy</language> <language>JavaScript</language> </langs>
在 Java 语言中解析这个简单的 XML 文档却丝毫不简单,如清单 2 所示。它使用了 30 行代码来解析 5 行 XML 文件。
清单 2. 在 Java 中解析 XML 文件
import org.xml.sax.SAXException; import org.w3c.dom.*; import javax.xml.parsers.*; import java.io.IOException; public class ParseXml { public static void main(String[] args) { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); try { DocumentBuilder db = dbf.newDocumentBuilder(); Document doc = db.parse("src/languages.xml"); //print the "type" attribute Element langs = doc.getDocumentElement(); System.out.println("type = " + langs.getAttribute("type")); //print the "language" elements NodeList list = langs.getElementsByTagName("language"); for(int i = 0 ; i < list.getLength();i++) { Element language = (Element) list.item(i); System.out.println(language.getTextContent()); } }catch(ParserConfigurationException pce) { pce.printStackTrace(); }catch(SAXException se) { se.printStackTrace(); }catch(IOException ioe) { ioe.printStackTrace(); } } }
比较清单 2 中的 Java 代码和清单 3 中相应的 Groovy 代码:
清单 3. 在 Groovy 中解析 XML
def langs = new XmlParser().parse("languages.xml") println "type = ${langs.attribute("type")}" langs.language.each{ println it.text() } //output: type = current Java Groovy JavaScript
Groovy 代码最出色的地方并不是它要比相应的 Java 代码简短很多 — 虽然使用 5 行 Groovy 代码解析 5 行 XML 是一个压倒性的优势。Groovy 代码最让我欣喜的一个地方就是它更具表达性。在编写 langs.language.each 时,我的感觉就像是在直接操作 XML。在 Java 版本中,您再也看不到 XML。
字符串变量和 XML
当您将 XML 存储在 String 变量而不是文件中时,在 Groovy 中使用 XML 的好处会变得更加明显。Groovy 的三重引号(在其他语言中通常称作 HereDoc)使得在内部存储 XML 变得非常轻松,如清单 4 所示。这与清单 3 中的 Groovy 示例之间的惟一区别就是将 XmlParser 方法调用从 parse()(它处理 File、InputStreams、Reader 和 URI)切换到 parseText()。
清单 4. 将 XML 存储在 Groovy 内部
def xml = """ <langs type="current"> <language>Java</language> <language>Groovy</language> <language>JavaScript</language> </langs> """ def langs = new XmlParser().parseText(xml) println "type = ${langs.attribute("type")}" langs.language.each{ println it.text() }
注意,三重引号可以轻松地处理多行 XML 文档。xml 变量是一个真正的普通旧式(plain-old)java.lang.String — 您可以添加 println xml.class 自己进行验证。三重引号还可以处理 type="current" 的内部引号,而不会强制您像在 Java 代码中那样使用反斜杠字符手动进行转义。
比较清单 4 中简洁的 Groovy 代码与清单 5 中相应的 Java 代码:
清单 5. 在 Java 代码内部存储 XML
import org.xml.sax.SAXException; import org.w3c.dom.*; import javax.xml.parsers.*; import java.io.*; public class ParseXmlFromString { public static void main(String[] args) { String xml = "<langs type=\"current\">\n" + " <language>Java</language>\n" + " <language>Groovy</language>\n" + " <language>JavaScript</language>\n" + "</langs>"; byte[] xmlBytes = xml.getBytes(); InputStream is = new ByteArrayInputStream(xmlBytes); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); try { DocumentBuilder db = dbf.newDocumentBuilder(); Document doc = db.parse(is); //print the "type" attribute Element langs = doc.getDocumentElement(); System.out.println("type = " + langs.getAttribute("type")); //print the "language" elements NodeList list = langs.getElementsByTagName("language"); for(int i = 0 ; i < list.getLength();i++) { Element language = (Element) list.item(i); System.out.println(language.getTextContent()); } }catch(ParserConfigurationException pce) { pce.printStackTrace(); }catch(SAXException se) { se.printStackTrace(); }catch(IOException ioe) { ioe.printStackTrace(); } } }
注意,xml 变量受到了针对内部引号和换行符的转义字符的污染。然而,更糟的是需要将 String 转换成一个 byte 数组,然后再转换成 ByteArrayInputStream 才能进行解析。DocumentBuilder 未提供将简单 String 作为 XML 解析的直观方法。
通过 MarkupBuilder 创建 XML
Groovy 相对 Java 语言最大的优势体现于在代码中创建 XML 文档。清单 6 显示了创建 5 行 XML 代码段所需的 50 行 Java 代码:
清单 6. 使用 Java 代码创建 XML
import org.w3c.dom.*; import javax.xml.parsers.*; import javax.xml.transform.*; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import java.io.StringWriter; public class CreateXml { public static void main(String[] args) { DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); try { DocumentBuilder db = dbf.newDocumentBuilder(); Document doc = db.newDocument(); Element langs = doc.createElement("langs"); langs.setAttribute("type", "current"); doc.appendChild(langs); Element language1 = doc.createElement("language"); Text text1 = doc.createTextNode("Java"); language1.appendChild(text1); langs.appendChild(language1); Element language2 = doc.createElement("language"); Text text2 = doc.createTextNode("Groovy"); language2.appendChild(text2); langs.appendChild(language2); Element language3 = doc.createElement("language"); Text text3 = doc.createTextNode("JavaScript"); language3.appendChild(text3); langs.appendChild(language3); // Output the XML TransformerFactory tf = TransformerFactory.newInstance(); Transformer transformer = tf.newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); StringWriter sw = new StringWriter(); StreamResult sr = new StreamResult(sw); DOMSource source = new DOMSource(doc); transformer.transform(source, sr); String xmlString = sw.toString(); System.out.println(xmlString); }catch(ParserConfigurationException pce) { pce.printStackTrace(); } catch (TransformerConfigurationException e) { e.printStackTrace(); } catch (TransformerException e) { e.printStackTrace(); } } }
我知道一些人会立刻抱怨。许多第三方库都可以简化此代码 — JDOM 和 dom4j 是其中最流行的两个。但是,任何 Java 库都无法与使用 Groovy MarkupBuilder 的简洁性相比,如清单 7 所示:
清单 7. 使用 Groovy 创建 XML
def xml = new groovy.xml.MarkupBuilder() xml.langs(type:"current"){ language("Java") language("Groovy") language("JavaScript") }
注意到这与 XML 代码的比率又重新回到了将近 1:1。更加重要的是,我可以再次查看 XML。当然,尖括号已经被替换为大括号,并且属性使用冒号(Groovy 的 HashMap 符号)而不是等号,但其基本结构在 Groovy 或 XML 中都是可以辨认的。它几乎类似于一个用于构建 XML 的 DSL,您认为呢?
Groovy 能够实现这种 Builder 魔法,因为它是一种动态的语言。另一方面,Java 语言则是静态的:Java 编译器将确保所有方法在您调用它们之前都是确实存在的。(如果您尝试调用不存在的方法,Java 代码甚至不进行编译,更不用说运行了。)但是,Groovy 的 Builder 证明,某种语言中的 bug 正是另一种语言的特性。如果您查阅 API 文档中的 MarkupBuilder 相关部分,您会发现它没有 langs() 方法、language() 方法或任何其他元素名称。幸运的是,Groovy 可以捕获这些不存在的方法调用,并采取一些有效的操作。对于 MarkupBuilder 的情况,它使用 phantom 方法调用并生成格式良好的 XML。
清单 8
对我刚才给出的简单的 MarkupBuilder 示例进行了扩展。如果您希望在 String 变量中捕获 XML 输出,则可以传递一个 StringWriter 到 MarkupBuilder 的构造函数中。如果您希望添加更多属性到 langs 中,只需要在传递时使用逗号将它们分开。注意,language 元素的主体是一个没有前置名称的值。您可以在相同的逗号分隔的列表中添加属性和主体。
清单 8. 经过扩展的 MarkupBuilder 示例
def sw = new StringWriter() def xml = new groovy.xml.MarkupBuilder(sw) xml.langs(type:"current", count:3, mainstream:true){ language(flavor:"static", version:"1.5", "Java") language(flavor:"dynamic", version:"1.6.0", "Groovy") language(flavor:"dynamic", version:"1.9", "JavaScript") } println sw //output: <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>
通过这些 MarkupBuilder 技巧,您可以实现一些有趣的功能。举例来说,您可以快速构建一个格式良好的 HTML 文档,并将它写出到文件中。清单 9 显示了相应的代码:
清单 9. 通过 MarkupBuilder 构建 HTML
def sw = new StringWriter() def html = new groovy.xml.MarkupBuilder(sw) html.html{ head{ title("Links") } body{ h1("Here are my HTML bookmarks") table(border:1){ tr{ th("what") th("where") } tr{ td("Groovy Articles") td{ a(href:"http://ibm.com/developerworks", "DeveloperWorks") } } } } } def f = new File("index.html") f.write(sw.toString()) //output: <html> <head> <title>Links</title> </head> <body> <h1>Here are my HTML bookmarks</h1> <table border='1'> <tr> <th>what</th> <th>where</th> </tr> <tr> <td>Groovy Articles</td> <td> <a href='http://ibm.com/developerworks'>DeveloperWorks</a> </td> </tr> </table> </body> </html>
图 1 显示了清单 9 所构建的 HTML 的浏览器视图:
图 1. 呈现的 HTML
使用 StreamingMarkupBuilder 创建 XML
MarkupBuilder 非常适合用于同步构建简单的 XML 文档。对于更加高级的 XML 创建,Groovy 提供了一个 StreamingMarkupBuilder。通过它,您可以添加各种各样的 XML 内容,比如说处理指令、名称空间和使用 mkp 帮助对象的未转义文本(非常适合 CDATA 块)。清单 10 展示了有趣的 StreamingMarkupBuilder 特性:
清单 10. 使用 StreamingMarkupBuilder 创建 XML
def comment = "<![CDATA[<!-- address is new to this release -->]]>" def builder = new groovy.xml.StreamingMarkupBuilder() builder.encoding = "UTF-8" def person = { mkp.xmlDeclaration() mkp.pi("xml-stylesheet": "type='text/xsl' href='myfile.xslt'" ) mkp.declareNamespace('':'http://myDefaultNamespace') mkp.declareNamespace('location':'http://someOtherNamespace') person(id:100){ firstname("Jane") lastname("Doe") mkp.yieldUnescaped(comment) location.address("123 Main") } } def writer = new FileWriter("person.xml") writer << builder.bind(person) //output: <?xml version="1.0" encoding="UTF-8"?> <?xml-stylesheet type='text/xsl' href='myfile.xslt'?> <person id='100' xmlns='http://myDefaultNamespace' xmlns:location='http://someOtherNamespace'> <firstname>Jane</firstname> <lastname>Doe</lastname> <![CDATA[<!-- address is new to this release -->]]> <location:address>123 Main</location:address> </person>
注意,StreamingMarkupBuilder 直到您调用 bind() 方法时才会生成最终的 XML,该方法将接受标记和所有指令。这允许您异步构建 XML 文档的各个部分,并同时输出它们。(参见 参考资料 了解更多信息。)
理解 XmlParser
Groovy 为您提供了两种生成 XML — MarkupBuilder 和 StreamingMarkupBuilder 的方式 — 它们分别具备不同的功能。解析 XML 也同样如此。您可以使用 XmlParser 或者 XmlSlurper。
XmlParser 提供了更加以程序员为中心的 XML 文档视图。如果您习惯于使用 List 和 Map(分别对应于 Element 和 Attribute)来思考文档,则应该能够适应 XmlParser。
清单 11 稍微解析了 XmlParser 的结构:
清单 11. XmlParser 详细视图
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.getClass() // class groovy.util.Node println langs /* langs[attributes={type=current, count=3, mainstream=true}; value=[language[attributes={flavor=static, version=1.5}; value=[Java]], language[attributes={flavor=dynamic, version=1.6.0}; value=[Groovy]], language[attributes={flavor=dynamic, version=1.9}; value=[JavaScript]] ] ] */
注意,XmlParser.parseText() 方法返回了一个 groovy.util.Node — 在本例中是 XML 文档的根 Node。当您调用 println langs 时,它会调用 Node.toString() 方法,以便返回调试输出。要获取真实数据,您需要调用 Node.attribute() 或者 Node.text()。
使用 XmlParser 获取属性
如前所述,您可以通过调用 Node.attribute("key") 来获取单独的属性。如果您调用 Node.attributes(),它会返回包含所有 Node 的属性的 HashMap。使用您在 “for each 剖析” 一文中所掌握的 each 闭包,遍历每个属性简直就是小菜一碟。清单 12 显示了一个相应的例子。(参见 参考资料,获取关于 groovy.util.Node 的 API 文档。)
清单 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。
您是否注意到 XmlSlurper 甚至忽略了名称空间?您在构造函数中启用名称空间感知,但我很少这样做。非常简单,XmlSlurper 能像切黄油那样分解 XML。
<hr/>
结束语
要在如今成为一名成功的开发人员,您需要一系列能简化 XML 处理的工具。Groovy 的 MarkupBuilder 和 StreamingMarkupBuilder 可以非常轻松地动态创建 XML。XmlParser 能为您提供由 Element 组成的 List 以及由 Attribute 组成的 HashMap,并且 XmlSlurper 可以让代码全部消失,让您感觉是在直接操作 XML。
如果没有 Groovy 的动态功能,XML 处理的强大功能将不可能实现。在下一章文章中,我将更加深入地探索 Groovy 的动态特性。您将了解元编程在 Groovy 中的工作原理,从标准 JDK 类(如 String.toURL() 和 List.each())中添加的出色方法到您自己添加的自定义方法。在阅读了这两篇文章之后,我希望您能充分了解 Groovy 的实际应用。
相关推荐
《Grovvy 书籍》是关于Groovy编程语言的一份资源,虽然描述中没有提供具体信息,但从标签“源码”和“工具”可以推测,这份资料可能包含了Groovy在实际开发中的应用,特别是与代码解析和工具构建相关的知识。Groovy...
通过这个结构,我们可以像操作XML一样,使用选择器来定位和提取我们需要的元素。例如,`select("CSS选择器")`方法可以用来选取特定的HTML元素,而`text()`方法则用于获取选中元素的文本内容。 在开始项目前,确保...
2. **布局解析**:此布局文件包含了一个垂直方向的LinearLayout,其中包含了标题和一个水平方向的LinearLayout用于显示用户名提示和输入框。 - **TextView**:用于显示“用户登录”的标题以及“用户名:”的提示。 ...
除了上述提到的核心特性和实战应用外,Spring Boot还提供了许多其他功能,如安全性和国际化支持等,这些都是开发者在构建现代Java应用时不可或缺的重要工具。随着Spring Boot的不断迭代和发展,它将继续引领Java应用...
- Web服务与XML技术: 特别关注Web服务和XML在Java中的应用。 ### 6. JDon - **网址**: http://www.jdon.com/ - **简介**: 一个专注于企业级Java应用开发的社区。 - **特色资源**: - JDon框架: 一种用于快速构建...
《Spring 4.0框架深度探索:基于Maven构建的实战Demo》 Spring框架作为Java企业级应用开发的基石,自推出以来就以其强大的功能和灵活性赢得了广大开发者的心。Spring 4.0作为其一个重要版本,引入了许多改进和新...
1. 引入了Groovy配置:除了XML和Java配置之外,Spring 3.1.0开始支持Groovy语法进行配置,使得配置更加简洁、灵活。 2. 高效的数据访问:通过JDBC的模板改进,提升了数据访问的效率,同时增加了对NoSQL数据库的支持...
《Spring Framework 3.0.5.RELEASE:深入解析与应用》 Spring Framework,作为Java开发中的核心框架,以其强大的依赖注入(Dependency Injection,DI)和面向切面编程(Aspect-Oriented Programming,AOP)功能,极...
5. **数据处理**:通过Groovy解析和操作XML、JSON等数据格式,方便进行数据迁移或转换任务。 6. **命令行工具**:编写小巧的Groovy脚本作为命令行工具,用于日常的数据处理、文件操作等任务。 "Experience2-main"这...
- 解析器与解释器:讨论如何构建解析器和解释器来处理外部DSL输入。 - 集成策略:提出将外部DSL集成到现有系统中的方法。 - **知识点7:外部DSL的设计技巧** - 语法规则:探讨如何定义清晰易懂的语法规则来增强...
《Ant权威指南》是一本深度解析Apache Ant构建工具的专业书籍,旨在帮助读者全面掌握这个Java项目自动化构建系统的核心技术和应用。Ant是Apache软件基金会开发的一个开源项目,它以XML为基础,定义了构建过程中的...
2. `plugin.xml`:这是Grails插件的元数据文件,包含了插件的名称、版本、作者等信息,同时也是Grails构建系统识别和管理插件的关键文件。 3. `grails-app`目录:包含Grails应用的核心代码,如控制器(controllers...
总的来说,这个压缩包提供了一个学习和实践Java和Gradle的实战场景,对于初学者来说,可以通过它来提升编程技能,了解现代Java项目的构建和管理方式。对于经验丰富的开发者,可以从中获取灵感,或者快速启动一个新的...
《Spring Framework 3.0.5.RELEASE:深入解析与应用》 Spring Framework作为Java开发中的核心框架,自诞生以来就以其强大的功能和灵活的设计深受开发者喜爱。本篇将聚焦于Spring Framework 3.0.5.RELEASE版本,探讨...
【标题】:“Java构建工具——Maven与Gradle对比与实战” 【描述】:在Java开发领域,构建工具是不可或缺的一部分,它们自动化处理项目构建、依赖管理、测试和打包等任务,极大地提高了开发效率。本资料主要探讨了...
2. **REST测试**:除了SOAP,它还支持RESTful服务,包括JSON和XML数据格式,能够进行完整的REST API测试。 3. **性能测试**:集成负载和性能测试,模拟多用户并发访问,评估系统在高负载下的表现。 4. **数据驱动...
下面将深入解析一个基于WinForm的ATM基本功能实现的源码,帮助初学者理解其工作原理和编程思路。 一、用户登录模块 在ATM系统中,用户登录是第一道关卡。该模块通常需要用户输入银行卡号和密码,通过与后台数据库...
5. **源代码解析和编辑器构造**:插件可能实现了自定义的编辑器视图,涉及文本解析、语法高亮、代码提示等功能,这些都是通过TextEditor或SourceViewer实现的。 6. **调试和测试**:掌握如何在Eclipse环境中进行...
《Android串口通信实战解析与应用》 在Android开发中,串口通信是一个重要的功能,尤其是在物联网、嵌入式设备以及工业控制等领域有着广泛的应用。本文将深入探讨Android串口通信的基本原理,以及如何在Android ...