Groovy工具箱(xml)

在Groovy里,生成和解析xml是一件比较愉快的事情(如果你用过java处理xml的传统方法,你会同意的,即使对比java世界里提供方便的xml处理工具Dom4j,Groovy也有自己的优势)。

XmlSlurper

首先我们来认识XmlSlurper,我们通过一个例子来看下如何使用这个类。

def xmlSource = new File('xmllocation')
def slurper= new XmlSlurper().parse(xmlSource)

parse方法接受一个文件,然后得到一个GPathResult类型的结果,有了这个结果,我们就可以对整个xml文档进行遍历。使用“.”号,我们可以从父节点获得子节点。如果需要对特征节点针对性的查找,在GPathResult里为我们提供了如findAll、find这种可以以查找闭包为参数的方法,使针对性的查找变得简单起来。

下面具体来说一个例子:

<Persons>
 <person age="16" name="xiao1">
   <city>hangzhou</city>
   <link rel="http://test.com/xiao1"/>
 </person>
<person age="16" name="xiao2">
   <city>hangzhou</city>
   <link rel="http://test.com/xiao2"/>
 </person>
<person age="16" name="xiao3">
   <city>hangzhou</city>
   <link rel="http://test.com/xiao3"/>
</person>
<person age="16" name="xiao4">
   <city>hangzhou</city>
   <link rel="http://test.com/xiao4"/>
</person>
<person age="16" name="xiao5">
   <city>hangzhou</city>
   <link rel="http://test.com/xiao5"/>
 </person>
 <person age="16" name="xiao6">
   <city>hangzhou</city>
   <link rel="http://test.com/xiao6"/>
 </person>
</Persons>

这是一个待解析的xml文档,我们可以使用上面说过的XmlSlurper来试试解析它,我们先获得city的值。

def  xml = new XmlSlurper()
def  result = xml.parse(new File("C:/Users/xiaonanzhi/Person.xml"))
println  result.person*.city

注意,parse完成后,得到的起点就是根节点了,所以使用点号导航的时候无需体现Persons这个层级。result.person我们会得到多个节点,这时候使用列表操作符*.可以对多个节点收集信息并返回为一个集合。所以我们得到了city的一个列表如下:

[hangzhou, hangzhou, hangzhou, hangzhou, hangzhou, hangzhou]

然后我们来获得person的名字

println  result.person*.@name

使用@属性名的语法,我们就可以轻松获得对应属性的值

[xiao1, xiao2, xiao3, xiao4, xiao5, xiao6]

然后我们来个更常用的,找出name为xiao5的节点,然后把它的link值打印出来

result.person.find{it->
 if(it.@name == "xiao5"){
 println it.link.@rel
}

}
和期望的一样,我们得到了合适的结果,如下所示:

http://test.com/xiao5

XmlParser

在大部分的使用场景里,XmlParser就像是XmlSlurper的一个孪生兄弟,上面的例子,我们使用XmlParser做一下处理看看。

def  xml = new XmlParser()
def result = xml.parse(new File("C:/Users/xiaonanzhi/Person.xml"))
result.person.find{it->
 if(it.@name == "xiao5"){
 println it.link.@rel
 }
}

使用上面的代码,只是改变了类名,我们就得到了相同的结果,当时并不能说明他们是完全相同的。因为内部机制的不同(Slurper是基于SAX模式的解析器,它会完全载入文档,但是对于解析式懒加载的,而Parser则是把xml完全解析成了node,查询起来更快),Slurper对内存的消耗更小一点,如果是要完全处理文档,则Parser的性能会更好一点。另外,如果使用确定类型来接收parse方法的返回值,我们会发现两种解析器返回的结果完全不同,Slurper返回的是GPathResult,而parser返回的是groovy.util.Node。

命名空间处理

简单说了一下常规的解析xml方式,我们还需要谈及一个主题,对命名空间的支持。xml中的命名空间类似于java的包名,起着把相关的标签组织起来的作用,不过在实际的使用过程中,如何能访问命名空间中的元素却成为一个开发者经常面对的问题,让我们来看看groovy中的解决方案。

我们先来定义一份待解析的文档,这是某产品的一个请求结果,为了简化只保留了少量的xml元素。

<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:pris="http://www.163.com/pris/1.0" xml:base="http://easyread.163.com">
 <entry>
 <title>为什么我们会被电影中的替身演员轻易骗过</title>
 <pris:assess times="0" />
 <pris:image_thumbnail type="image/jpg" href="http://easyread.ph.126.net/vYu8kkZJxtNbBBMbElFdbg==/7916516705336671812.jpg" width="600" height="399" />
 <pris:entry_status type="news" style="0" subtype="" />
</entry>
<pris:entry_status type="news" style="0" subtype="" />
</feed>

XmlSlurper的解析过程如下:

1.import groovy.util.slurpersupport.GPathResult
2.def  xml = new XmlSlurper()
3.GPathResult result = xml.parse(new   File("C:/Users/xiaonanzhi/index.xml"))
4.result.declareNamespace("pris":"http://www.163.com/pris/1.0")
5.println result.entry.image_thumbnail.@type
6.println result.entry."pris:image_thumbnail".@type

值得注意的是,用slurper做解析的时候,实际上5、6两种方式都是能得到正确结果的。即slurper并不是严格遵守命名空间规范的,你可以不使用命名空间来访问节点,不过有时候我们也需要带着前缀来访问,就像6的情况(有时候命名空间去掉有些元素就同名了,这时候要带上命名空间才能访问被屏蔽的那个),在slurper里面,如果需要6的情况能被访问,则必须有标号为4的这条语句,即需要使用方法declareNamespace(Map)来定义前缀才能访问到。

接下来我们看下Parser的情况:

1.def  xml = new XmlParser()
2.def result = xml.parse(new File("C:/Users/xiaonanzhi/index.xml"))
//it won't work
3.//println result.entry.image_thumbnail.@type
4.def pris = new Namespace("http://www.163.com/pris/1.0", "pris")
5.//println result.entry."pris:image_thumbnail".@type
6.println result.entry[pris.image_thumbnail].@type

如果不带命名空间前缀,parser是不能获得正确的结果的。使用parser的时候,我们有两种方式来访问命名空间:一种是直接带着命名空间前缀来访问(因为命名空间和元素名称中间的冒号不能直接通过编译,你需要用引号包裹起来);另外一种是创建一个Namespace的对象,通过[]来访问,就像标号为6的代码所示。

xml的创建

接下来我们来讨论硬币的另外一面,如何用groovy来创建xml文档。

创建xml的一个类叫做MarkupBuilder,如果不涉及命名空间的话,这会是非常直观的一个过程。

def st1 = new StringWriter()
MarkupBuilder mb1  = new MarkupBuilder(st);
mb.feed{
entry(id:"1234567"){
title{
 show(name:"1234","fdfsa")
}
 link:"readf"
}
}
print st

我们来直接看一下运行结果:

 <feed>
  <entry id='1234567'>
     <title>
       <show name='1234'>fdfsa</show>
    </title>
  </entry>
</feed>

如你所见,构建xml的代码本身看起来就已经是xml的结构了,我们只要创建一个MarkupBuilder的实例,然后后面跟着的是xml的root节点,这就开始了整个build过程,

  • 如果是属性,我们就用小括号里键值对表达,
  • 如果是元素包含的文本,我们就直接把它放在小括号里,
  • 如果是子元素,我们就把它放到当前元素后面的大括号里,以此类推。

使用MarkupBuilder,如何加入命名空间呢,也用一段代码来说明:

def st = new StringWriter()
MarkupBuilder mb  = new MarkupBuilder(st);
mb.feed('xmlns:pris':'http://www.163.com/pris/1.0'){
entry(id:"1234567"){
 'pris:title'{
show("fdfsa")
}
link:"readf"
}
}
print st

首先可以在根节点声明命名空间,然后在后面的节点就可以使用带有命名空间前缀的名称来直接作为节点名称。得到的结果是:

<feed xmlns:pris='http://www.163.com/pris/1.0'>
  <entry id='1234567'>
    <pris:title>
    <show>fdfsa</show>
    </pris:title>
  </entry>
</feed>

除了MarkupBuilder之外,我们还可以使用StreamingMarkupBuilder来生成xml,这个类比起MarkupBuild来说更加的强大和复杂。

直接使用一个《Programming Groovy2》的例子

def langs = ['C++' : 'Stroustrup', 'Java' : 'Gosling', 'Lisp' : 'McCarthy']
def xmlDocument = new groovy.xml.StreamingMarkupBuilder().bind {
mkp.xmlDeclaration()
mkp.declareNamespace(computer: "Computer")
languages {
 comment << "Created using StreamingMarkupBuilder"
 langs.each { key, value ->
     computer.language(name: key) { author (value) }
 }
}
}
println xmlDocument

结果为:

<?xml version='1.0'?>
<languages xmlns:computer='Computer'><!--Created using StreamingMarkupBuilder -->
 <computer:language name='C++'>
    <author>Stroustrup</author>
</computer:language>
 <computer:language name='Java'>
     <author>Gosling</author>
</computer:language>
<computer:language name='Lisp'>
        <author>McCarthy</author>
</computer:language>
</languages>

后者高级的用法更多,不过写作起来比MarkupBuilder复杂一些,一般的使用用MarkupBuilder就足够了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,364评论 6 343
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • 1. XML简介 以下内容来自于http://www.w3school.com.cn/xml 基本知识 XML 和...
    WebSSO阅读 1,723评论 1 7
  • 最近,由于课题的问题。纠结的要死,感觉被逼入了死胡同,思维受阻,生活受困。其实这件事,和别人经历的事比根本不算什么...
    悦是欢阅读 202评论 0 0