跳到内容

Scala解析XML

Canyue
发布日期:
5 分钟阅读

本文主要了解如何使用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里面定义:

Attribute定义了 XML 的属性。
Branch指能够包含子节点的节点。如XML元素(Element)和文档(Docuemnts)定义了一个公共的行为
CDATA定义了 XML CDATA 区域
CharacterData是一个标识接口,标识基于字符的节点。如CDATA,Comment, Text.
Comment定义了 XML 注释的行为
Document定义了XML 文档
DocumentType定义 XML DOCTYPE 声明
Element定义XML 元素
ElementHandler定义了Element 对象的处理器
ElementPath被 ElementHandler 使用,用于取得当前正在处理的路径层次信息
Entity定义 XML entity
Node为dom4j中所有的XML节点定义了多态行为
NodeFilter定义了在dom4j 节点中产生的一个滤镜或谓词的行为(predicate)
ProcessingInstruction定义 XML 处理指令
Text定义 XML 文本节点
Visitor用于实现 Visitor模式
XPath在分析一个字符串后会提供一个 XPath 表达式

在使用之前需要先引入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两者的的概念

为了防止后人混淆。为此我特意翻了源码

//获取根元素
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()                  //记得释放资源,不然写不进去
上一篇
GO语言快速上手
下一篇
操作系统与存储器管理