通过本文,您将了解使用 Groovy 分解 XML 是多么地容易。在本期的 实战 Groovy 中,作者 Scott Davis 演示了无论您是使用 MarkupBuilder 和 StreamingMarkupBuilder 创建 XML,还是使用 XmlParser 和 XmlSlurper 解析 XML,Groovy 都提供了一系列用于处理这类流行数据格式的工具。
XML 似乎已经由来已久。实际上,XML 在 2008 年迎来了它的 10 年庆典(参见 参考资料)。由于 Java™ 语言只比 XML 早几年出现,因此有人认为对于 Java 开发人员来说,XML 是 始终存在的。
Java 语言创始人 Sun Microsystems 一直是 XML 的积极支持者。毕竟,XML 的平台独立性承诺能与 Java 语言的 “编写一次,随处运行” 的口号完美契合。由于这两种技术具备一些相同的特性,您可能会认为 Java 语言和 XML 能很好地相处。事实上,在 Java 语言中解析和生成 XML 不但奇特而且还复杂。
幸运的是,Groovy 引入了一些全新的、更加合理的方法来创建和处理 XML。在一些示例的帮助下(均可通过 下载 获取),本文向您展示了如何通过 Groovy 简化 XML 的构建和解析。
比较 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(); } } } |