Scala 解析 XML
编辑本文主要了解如何使用Scala解析XML
Scala 原生方法
XML基本结构
<school address="联榕路8号">
<strdents>
<xiaomin strId="202012040293221" sex="男">小明</xiaomin>
<xiaohua strId="202012040293222" sex="女">小花</xiaohua>
</strdents>
</school>
根元素
:这里 school是根元素
子/父元素
:students是school的子元素,school是students的父元素
属性
:address是school的属性
文本
:小明、小花
scala原生对XML字面量支持
你甚至可以在代码中直接使用XML片段
val elem: Elem = //scala.xml.Elem
<a>
<b>
hello
</b>
</a>
在我们直接写XML的时候,Scala会将其解析并识别为scala.xml.Elem类型
主要的类
Node类。它的一个抽象类,用于对象XML中的所有节点进行抽象:
Text类,仅包含文本的节点,例如<url>http://www.baidu.com/</url>
中的http://www.baidu.com/就是一种Text对象
NodeSeq类,它同样是一个抽象类,指的是节点的序列,Node继承自NodeSeq,可以看Node可作是NodeSeq只有一个元素的情况。
将字符串转成XML.Elem
import scala.xml._
val string="<play><scala></scala></play>"
XML.loadString(string)
//res0: scala.xml.Elem = <play><scala/></play>
获取文本
通过text方法可以获取文本
val elem:Elem = <p>Hello Scala</p>
elem.text
//res0: String = Hello Scala
获取属性
可以通过attributes方法得到当前元素的某个属性
val elem:Elem = <a href="http://www.baidu.com"></a>
elem.attributes("href").text
//res1: String = http://www.baidu.com
也可以通过 \
或 \\
获取
其实 \ 和 \\ 是 Elem的一个方法
scala允许方法之间不写“.”
val elem:Elem = <a href="http://www.baidu.com"></a>
elem \ "@href" //带上@符号,让scala知道你要查询的是属性
//res2: scala.xml.NodeSeq = http://www.baidu.com
遍历元数的属性
val elem:Elem = <a href="http://www.baidu.com" id="web"></a>
for(arr <- elem.attributes) println(arr)
href="http://www.baidu.com" id="web"
id="web"
获取属性(Map类型)
val elem:Elem = <a href="http://www.baidu.com" id="web"></a>
elem.attributes.asAttrMap
// res3: Map[String,String] = Map(href -> http://www.baidu.com, id -> web)
进入子元素
可以通过 \
或 \\
进入当前节点下的某个子节点
val elem:Elem = <div><p>Hello Scala</p></div>
val newElem = elem \\ "p"
//newElem: scala.xml.NodeSeq = NodeSeq(<p>Hello Scala</p>)
newElem.text
// res4: String = Hello Scala
序列化与反序列化
import scala.language.postfixOps
import scala.xml.Elem
object test5 {
def main(args: Array[String]): Unit = {
val mimi = new Cat("咪咪", "black")
//序列化
val mimiElem = mimi.toXML()
println(mimiElem \ "name" text)
//反序列化
mimi.fromXML(mimiElem).jump()
}
}
class Cat(name:String,color:String){
//序列化
def toXML():Elem ={
<Cat>
<name>{name}</name>
<color>{color}</color>
</Cat>
}
//反序列化
def fromXML(xml:Elem): Cat ={
new Cat(xml \ "name" text, xml \ "color" text)
}
//跳
def jump(): Unit ={
println(name + "一蹦跳老高")
}
}
咪咪
咪咪一蹦跳老高
XML模式匹配
elem match {
case <Cat><name>{name}</name></Cat> => println(name) //匹配单个元数
case <Cat><name>{name}</name></Cat> => println(name.attribute("enName")) //匹配属性
case <Cat>{info}</Cat> => { //匹配多个元素,返回ArrayBuffer
for (e <- info) println(e.text) //打印文本
}
case _ => print("无匹配") //未被匹配
}
XML中执行Scala表达式
<sum>{1+2}</sum>
// res5:scala.xml.Elem = <sum>3</sum>
利用Dom4j
Dom4J是dom4j.org出品的应用于Java上的一个开源的XML解析包
由于Scala基于Java,故也可使用
DOM4J 最大的特色是使用大量的接口。它的主要接口都在org.dom4j里面定义:
在使用之前需要先引入dom4j,以maven为例,在Pom文件添加
<!--dom4j-->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!--前者xPath依赖-->
<dependency>
<groupId>com.google.code.maven-play-plugin.org.allcolor.shanidom</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.1-patched-shani-1.4.17</version>
</dependency>
读取
Document是一个接口类,里面定义了XML文档数据
Dom4J提供了非常多的方法以便使用者能灵活读取各种来源的数据
import org.dom4j.{Document, DocumentHelper}
import org.dom4j.io.{DOMReader, SAXReader}
import java.io.{File, FileInputStream, FileReader, InputStream, Reader}
import java.net.URL
object DomForJavaTest {
def main(args: Array[String]): Unit = {
/*---------------------------数据来自XML文件------------------------------*/
//读取有两种实现方式,一种是SAXReader,还有一种DOMReader,两者调用了同一个接口,所以调用方式是一样的
//读取的数据将被转换为org.dom4j.Document对象
val saxReader = new SAXReader() //一般来说用这种,下面演示的也是这种
val domReader = new DOMReader() //这种只能从org.w3c.dom.Document对象中获取数据
//可以在systemId中直接传入路径
val document1:Document = saxReader.read("./data.xml")
//也可传入一个java.io.File对象
val file:File = new File("./data.xml")
val document2:Document = saxReader.read(file)
//也可是一个统一资源定位符(java.net.URL)读取互联网上的XML文件
// val url: URL = new URL("xxxx")
// val document3:Document = saxReader.read(url)
//也可通过通过java的各种读取器(java.io.Reader)
val fileReader:Reader = new FileReader(file)
val document4 = saxReader.read(fileReader)
//结束了吗,不!还可以读输入取流(java.io.InputStream)中的数据
val fileStream:InputStream = new FileInputStream(file)
val document5:Document = saxReader.read(fileStream)
/*---------------------------数据来自字符串------------------------------*/
//解析字符串的数据需要通过一个文档助手(字面意思)的类实现
//官方解释该类是Dom4J的一个辅助工具集合
//注意 他是一个finalClass,请直接使用它
val str:String =
"""
|<school>
| <student id="1001">
| <name>小明</name>
| <sex>男</sex>
| </student>
| <student id="1002">
| <name>小花</name>
| <sex>女</sex>
| </student>
| <student id="1003">
| <name>小军</name>
| <sex>男</sex>
| </student>
| <student id="1004">
| <name>小鹏</name>
| <sex>男</sex>
| </student>
|</school>
|""".stripMargin
val document6:Document = DocumentHelper.parseText(str)
}
}
操作
接下来的演示数据
<school> <student id="1001"> <name>小明</name> <sex>男</sex> </student> <student id="1002"> <name>小花</name> <sex>女</sex> </student> <student id="1003"> <name>小军</name> <sex>男</sex> </student> <student id="1004"> <name>小鹏</name> <sex>男</sex> </student> </school>
进入
特此补充,TNN的网上写的都是啥啊,只字不提Node与Element两者的的概念
为了防止后人混淆。为此我特意翻了源码
Element(元素)
Element是一个接口,他定义了一个XML元素,各元数中可以声明名称、注释、属性、子节点以及其中的文本内容,这就很想正常的XML格式
Node(节点)
Node是Element的组成部分,他可以是一个元素中的某个子节点甚至只是某个属性,是Element的多态性为,我们可以通过.getNodeTypeName()得到该节点是属性还是文本等别的
可以将节点转换为XML格式的字符串,还可以对节点本身计算XPath表达式(xPath语法教程略)。
//获取根元素
val rootElement:Element = document
.getRootElement
//获取下面的所有的student元素
val studentElements:Array[Element] = rootElement
.elements("student")
.toArray()
.map(_.asInstanceOf[Element])
//继续进入id为1002的student元素的name元素
val index = studentElements
.map(_.attribute("id").getData.asInstanceOf[String]) //拿各元素的id属性(后面细说)
.indexOf("1002") //得到下标
val tmp = studentElements(index)
.element("name")
// println(tmp.getData) //小花
// //进入小花在的那个元素(无法使用,正在研究)
// val xiaoHuaNode:Node = document
// .selectNodes("//student[@id='1002]")
// .toArray()
// .map(_.asInstanceOf[Node])
// .head
// println(xiaoHuaNode.asXML())
读取
//读取某个元素的文本
val xiaoMinElement:Element = document
.getRootElement
.element("student") //默认进第一个
val text = xiaoMinElement
.elementText("name")
//println(text) //小明
//读取某个元素的某个属性
val attribute:Attribute = xiaoMinElement
.attribute("id")
//println(attribute.getName) //id
//println(attribute.getData) //1001
//遍历节点,得到每个节点的XML
val nodes:mutable.HashMap[String,Any] = mutable.HashMap()
for(i <- Range(0,xiaoMinElement.nodeCount())){
val node:Node = xiaoMinElement.node(i)
nodes.put(node.getName,node.asXML())
}
//println(nodes.mkString("|"))
//name -> <name>小明</name>|sex -> <sex>男</sex>|null -> (这有一个\n)
//遍历各个元数
val iterator = xiaoMinElement.elementIterator() //得到可迭代对象
while (iterator.hasNext){
val data:String = iterator
.next()
.asInstanceOf[Element]
.getData
.toString
println(data)
}
/*
* 小明
* 男
* */
修改
//修改/添加某个元素的属性(在dom4j 1.6+被弃用)
document
.getRootElement
.element("student")
.setAttributeValue("id","2001") //当属性不存在时将创建
//另一种没被弃用的方法,就是不能新增属性
val attribute = document
.getRootElement
.element("student")
.attribute("id")
attribute
.setValue("2011")
//添加需要用另一个方法
document
.getRootElement
.element("student")
.addAttribute("type","寄宿生")
//还可以通过节点来做,会更方便,不过遇到点技术问题,无法使用xPath
// val attribute_2 = document
// .selectNodes("//student[@id='1002']/@id")
// .iterator()
// .next()
// .asInstanceOf[DefaultAttribute]
//
// attribute_2
// .setValue("2012")
/*......
<student id="2011" type="寄宿生">
<name>小明</name>
<sex>男</sex>
</student>
......*/
//修改文本内容
document
.getRootElement
.element("student")
.element("name")
.setText("小徐")
/*......
<student id="2011" type="寄宿生">
<name>小徐</name>
<sex>男</sex>
</student>
......*/
//理论上用节点也行
//添加元素
//各个学生都添加一个元素,元素名为class,根据id第一位生成
val iterator = document
.getRootElement
.elements()
.iterator()
while (iterator.hasNext){
val element:Element = iterator.next().asInstanceOf[Element]
val stuClass:String = element.attribute("id").getValue.head + "班"
element //新增空元素
.addElement("class")
element //设置元素文本
.element("class")
.setText(stuClass)
}
/*<school>
<student id="2011" type="寄宿生">
<name>小徐</name>
<sex>男</sex>
<class>2班</class></student>
<student id="1002">
<name>小花</name>
<sex>女</sex>
<class>1班</class></student>
<student id="1003">
<name>小军</name>
<sex>男</sex>
<class>1班</class></student>
<student id="1004">
<name>小鹏</name>
<sex>男</sex>
<class>1班</class></student>
</school>
*/
val tmp = document
.node(0)
.asXML()
println(tmp)
保存
写入到CSV
文件会被复写,不用担心写入问题
val writer:Writer = new FileWriter("./newData.xml")
document
.write(writer)
writer.close() //记得释放资源,不然写不进去
- 1
-
赞助
微信支付宝 -
分享