创建xml并解析xml_构建,解析和提取XML

感觉像XML永远存在。 事实上,XML迎来了它的十周年之际,2008年随着Java™语言只用了几年早于XML,人们可以说,对于Java开发人员,XML 已经撒手人寰。

从一开始,Java语言的发明者Sun Microsystems就一直是XML的大力支持者。 毕竟,XML对平台独立性的承诺与Java语言的“编写一次,随处运行”消息非常吻合。 鉴于这两种技术的共同敏感性,您认为Java语言和XML会比它们相处得更好。 实际上,用Java语言解析和生成XML感觉很奇怪,而且令人费解。

令人高兴的是,Groovy引入了新的更好的方法来创建和处理XML。 在一些示例(都可以下载 )的帮助下,本文向您展示了Groovy如何使构建和解析XML变得非常简单。

比较Java和Groovy XML解析

在“ Reaching Each ”的结尾,我介绍了清单1中所示的简单XML文档。(这次我添加了type属性,使事情变得更加有趣。)

清单1.列出我知道的语言的XML文档
<langs type="current">
  <language>Java</language>
  <language>Groovy</language>
  <language>JavaScript</language>
</langs>

如清单2所示,在Java语言中解析这个琐碎的XML文档绝对是不平凡的。它需要30行代码来解析一个五行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代码短得多,尽管使用五行Groovy来解析五行XML是一个明显的胜利。 我最喜欢Groovy代码的地方是它更具表现力。 当我编写langs.language.each ,感觉就像我在直接使用XML。 在Java版本中,您再也看不到XML。

字符串变量和XML

当将XML存储在String变量而不是文件中时,在Groovy中使用XML的好处变得更加明显。 Groovy的三引号(在其他语言中通常称为HereDoc )使在内部存储XML变得毫不费力,如清单4所示。与清单3的Groovy示例相比,唯一的变化是翻转了parse()XmlParser方法调用(该句柄FileInputStreamsReaderURI )到parseText()

清单4.在Groovy内部存储XML
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变量确实是一个普通的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显示了创建五行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证明了一种语言的错误是另一种语言的功能。 如果您查看MarkupBuilder的API文档,将找不到langs()方法, language()方法或任何其他元素名称。 幸运的是,Groovy可以捕获对不存在的方法的这些调用,并用它们进行一些富有成效的工作。 对于MarkupBuilder ,它采用幻像方法调用并生成格式正确的XML。

清单8扩展了我刚刚向您展示的简单MarkupBuilder示例。 如果要捕获String变量中的XML输出, MarkupBuilder 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
呈现HTML

使用StreamingMarkupBuilder创建XML

MarkupBuilder非常适合同步构建简单的XML文档。 对于更高级的XML创建,Groovy提供了StreamingMarkupBuilder 。 使用它,您可以使用mkp帮助器对象添加各种XML附加功能,例如处理指令,名称空间和未转义的文本(对于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>

注意,在调用bind()方法之前, StreamingMarkupBuilder不会生成最终的XML,并传入包含标记和所有指令的闭包。 这使您可以异步构建XML文档的各个部分,然后一次全部输出。

了解XmlParser

Groovy为您提供了两种生成XML的方法MarkupBuilderStreamingMarkupBuilder每种都有不同的功能。 解析XML同样如此。 您可以使用XmlParserXmlSlurper

XmlParser提供了以程序员为中心的XML文档视图。 如果您习惯于用ListMap分别考虑文档(分别适用于ElementAttribute ),那么您应该习惯XmlParser 。 清单11 XmlParser解构了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 。 使用您在“ 实现每个目标 ”中了解到的each闭包,遍历每个属性都没什么大不了的。 有关此示例,请参见清单12。

清单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获取元素

XmlParser提供了一种查询元素的直观方法,称为GPath 。 (它类似于XPath ,仅在Groovy中实现。)例如,清单13演示了我之前使用的langs.language构造返回了一个包含查询结果的groovy.util.NodeListNodeList扩展了java.util.ArrayList ,因此它基本上是一个被授予GPath超能力的List

清单13.使用GPathXmlParser查询
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的补充。 它使用相同的技巧来调用不存在的幻像方法,只是这次仅用于查询现有XML,而不是即时生成它。

知道GPath查询的结果是一个List ,您可以使代码更加简洁。 Groovy提供了一个散点运算符。 在单行代码中,它实质上遍历列表并在每个项目上执行方法调用。 结果以List形式返回。 例如,如果您关心的Node.text()在查询结果中的每个项目上调用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.attribute() NodeListAttributeHashMap ,并且仍然被迫调用诸如Node.attribute()Node.text()来获取核心数据。 XmlSlurper消除了方法调用的最后痕迹,给您一种直接处理XML的令人愉悦的幻想。

从技术上讲, XmlParser返回NodeNodeList ,而XmlSlurper返回groovy.util.slurpersupport.GPathResult 。 但是现在您知道了,我希望您忘记我曾经提到过XmlSlurper的实现细节。 如果您不偷看窗帘,就可以尽情享受魔术表演的乐趣。

清单15 XmlParser显示了XmlParserXmlSlurper

清单15. XmlParserXmlSlurper
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.@count
langs.language.each{
  println it
}

请注意, XmlSlurper会删除任何方法调用的概念。 而不是调用langs.attribute("count") ,而是调用langs.@count 。 从XPath借来了at符号( @ ),但结果是产生了一种幻想,即您直接使用该属性而不是调用attribute()方法。 it.text()调用it.text() ,而只需调用it 。 假设您要直接使用元素的内容。

现实世界中的XmlSlurper

超越langslanguage ,这里是XmlSlurper的实际示例。 雅虎! 通过邮政编码作为RSS feed提供当前的天气状况。 RSS当然是XML的一种特殊方言。 在Web浏览器中键入http://weather.yahooapis.com/forecastrss?p=80020 。 随意将自己的邮政编码替换为科罗拉多州布鲁姆菲尔德。 清单16显示了生成的RSS feed的简化版本:

清单16. Yahoo! RSS feed显示当前天气状况
<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().texturl变量是格式良好的String 。 Groovy向所有String都添加了toURL()方法,该方法将它们转换为java.net.URL 。 依次,所有URL都有一个由Groovy添加的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像通过黄油XmlSlurper的热刀一样切成XML切片。

结论

要成为当今这样一个成功的软件开发人员,您需要一套使XML处理变得轻松的工具。 Groovy的MarkupBuilderStreamingMarkupBuilder使动态创建XML变得轻而易举。 XmlParser很好地完成了为您提供ElementListAttributeHashMap的工作,而XmlSlurper使代码完全消失,给您一种直接使用XML的令人愉悦的幻想。

没有Groovy的动态功能,XML处理的大部分功能将无法实现。 在下一篇文章中,我将与您详细探讨Groovy的动态本质。 您将学习到Groovy List.each()编程的工作原理,从添加到标准JDK类(如String.toURL()List.each() )的炫酷方法,到您将自己添加的自定义方法。 在此之前,我希望您能找到Groovy的许多实际用途。


翻译自: https://www.ibm.com/developerworks/java/library/j-pg05199/index.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值