Programming In Scala笔记-第二十八章、XML相关

  本章介绍Scala对XML的支持,包括的主要内容参考本文一级标题。

  有关半结构化数据的概念和XML格式的说明就跳过了,直接进入Scala中XML相关的语法和使用。

一、XML语法

  在Scala命令行中可以直接输入以个Tag开头,编译器会自动进入XML输入模式,直到遇到匹配该Tag的结束标志为止。如下所示:

scala> <a>
     |   This is some XML.
     |   Here is a tag: <atag/>
     | </a>
res25: scala.xml.Elem =
<a>
  This is some XML.
  Here is a tag: <atag/>
</a>

  上面代码中得到的是一个scala.xml.Elem类型的对象。在Scala中,XML相关的其他类还有:
Node XML相关的所有node类的父类
Text 一个只包含XML值的node。比如<a>stuff</a>中的stuff部分。
NodeSeq node序列

  除了像上图中直接输入完整的XML标签和值之外,还可以在值部分以{}来动态计算值部分。如下图所示,

scala> <a> {"hello"+", world"} </a>
res26: scala.xml.Elem = <a> hello, world </a>

  上面代码中{}中间是一个字符串拼接表达式,在{}中可以包含任意形式的Scala表达式,比如if... else…等,也可以访问在{}定义的变量。

二、序列化

  这里的序列化是指,将一个对象转换成XML形式。与后面的反序列化相对应。

  如下所示代码定义了一个CCTherm抽象类。

abstract class CCTherm {
  val description: String
  val yearMade: Int
  val dateObtained: String
  val bookPrice: Int // in US cents
  val purchasePrice: Int // in US cents
  val condition: Int // 1 to 10
  override def toString = description

  def toXML =
    <cctherm>
      <description>{description}</description>
      <yearMade>{yearMade}</yearMade>
      <dateObtained>{dateObtained}</dateObtained>
      <bookPrice>{bookPrice}</bookPrice>
      <purchasePrice>{purchasePrice}</purchasePrice>
      <condition>{condition}</condition>
    </cctherm>
}

  上面代码中toXML方法可以根据CCTherm类的属性将其转化成一个scala.xml.Elem类型的变量。

  下面代码构造一个CCTherm类型的对象并调用toXML方法对类进行XML转化。

val therm = new CCTherm {
  val description = "hot dog #5"
  val yearMade = 1952
  val dateObtained = "March 14, 2006"
  val bookPrice = 2199
  val purchasePrice = 500
  val condition = 9
}
println(therm)
println(therm.toXML)

  结果如下:

hot dog #5
<cctherm>
      <description>hot dog #5</description>
      <yearMade>1952</yearMade>
      <dateObtained>March 14, 2006</dateObtained>
      <bookPrice>2199</bookPrice>
      <purchasePrice>500</purchasePrice>
      <condition>9</condition>
    </cctherm>

  最后,如果想要在XML的内容中输入{或者},需要同时输入两个,如下所示:

scala> <a> {{{{brace yourself!}}}} </a>
res27: scala.xml.Elem = <a> {{brace yourself!}} </a>

三、XML数据访问

  在XML类中提供的多个方法中,有三个方法可以用来对XML进行解析,直接获取其中的部分信息。

1、text方法,获取XML node中的值部分

scala> <a>Sounds <tag/> good</a>.text
res28: String = Sounds  good

2、\方法和\\方法,获取XML中的子元素

(1)\方法

scala> <a><b><c>hello</c></b></a> \ "b"
res4: scala.xml.NodeSeq = NodeSeq(<b><c>hello</c></b>)

scala> <a><b><c>hello</c></b></a> \ "c"
res5: scala.xml.NodeSeq = NodeSeq()

scala> <a><b><c>hello</c></b></a> \ "b" \ "c"
res6: scala.xml.NodeSeq = NodeSeq(<c>hello</c>)

scala> <a><b><c>hello</c></b></a>.text
res7: String = hello

  从上图可以看到,\方法返回的是一个NodeSeq类型的对象,哪怕指定的子元素只有一个。
  上图中还可以看到,text方法在嵌套XML中也能直接获取其中的值部分。

(2)\\方法
  上面\方法只能获取下一层的子元素,比如,直接访问子元素”c”时,得到的是空的NodeSeq。如果想要直接访问”c”元素并得到对应的子元素,那么就需要使用\\方法了。

scala> <a><b><c>hello</c></b></a> \ "c"
res8: scala.xml.NodeSeq = NodeSeq()

scala> <a><b><c>hello</c></b></a> \\ "c"
res9: scala.xml.NodeSeq = NodeSeq(<c>hello</c>)

scala> <a><b><c>hello</c></b></a> \ "a"
res10: scala.xml.NodeSeq = NodeSeq()

scala> <a><b><c>hello</c></b></a> \\ "a"
res11: scala.xml.NodeSeq = NodeSeq(<a><b><c>hello</c></b></a>)

3、@方法,获取标签的属性值

  在XML的标签中,可以指定一些标签属性。不带属性的标签如<a/>,带属性的标签如<a name=“x” age=“12”/>。使用@方法就可以获取到”name”, “age”部分的值。
  
@符号需要与`或\\方法配合使用。

scala> val joe = <employee
     | name="Joe"
     | rank="code monkey"
     | serial="123"/>
joe: scala.xml.Elem = <employee name="Joe" rank="code monkey" serial="123"/>

scala> joe \ "@name"
res12: scala.xml.NodeSeq = Joe

scala> joe \ "@serial"
res13: scala.xml.NodeSeq = 123

scala> joe \ "@x"
res14: scala.xml.NodeSeq = NodeSeq()

  该方法返回的也是NodeSeq类型的变量。当访问不存在的属性时,得到的是空NodeSeq。

四、反序列化

  反序列化和前面介绍的序列化过程正好相反,是用于将一个XML元素转化成普通对象的过程。结合上面提到的三个方法,就可以方便的获取指定元素的值,从而将XML转化成普通对象。

  继续在前面的CCTherm类中添加fromXML方法,如下所示:

def fromXML(node: scala.xml.Node): CCTherm =
  new CCTherm {
    val description = (node \ "description").text
    val yearMade = (node \ "yearMade").text.toInt
    val dateObtained = (node \ "dateObtained").text
    val bookPrice = (node \ "bookPrice").text.toInt
    val purchasePrice = (node \ "purchasePrice").text.toInt
    val condition = (node \ "condition").text.toInt
  }

五、加载和保存

  从前面命令行模式可以看到,XML类型自带的toString方法可以将其中的内容进行String转换。

  但是最好还是使用XML提供的一些方法手动将XML内容转化成字符串形式进行输出。因为这种输出会包含一些属性,比如字符编码等。

1、scala.xml.XML.save方法,将XML内容保存到指定文件中

  用法如下scala.xml.XML.save(“therm1.xml”, node)。执行后,打开therm1.xml`,其中内容如下:

<?xml version='1.0' encoding='UTF-8'?>
<cctherm>
      <description>hot dog #5</description>
      <yearMade>1952</yearMade>
      <dateObtained>March 14, 2006</dateObtained>
      <bookPrice>2199</bookPrice>
      <purchasePrice>500</purchasePrice>
      <condition>9</condition>
    </cctherm>

2、xml.XML.loadFile方法,将XML文件直接加载到scala.xml.Elem对象中

  如下所示val loadnode = xml.XML.loadFile("therm1.xml”),运行结果如下:

<cctherm>
      <description>hot dog #5</description>
      <yearMade>1952</yearMade>
      <dateObtained>March 14, 2006</dateObtained>
      <bookPrice>2199</bookPrice>
      <purchasePrice>500</purchasePrice>
      <condition>9</condition>
    </cctherm>

六、XML中的模式匹配

  到目前为止,已经知道了如何生存XML类型对象,以及如何从XML中取出指定元素的值了。但是上面介绍的那些方法只有在你明确知道XML文件的格式,并且知道自己取出来的内容结构时才好用。

1、模式匹配简单应用

  XML中的模式匹配和XML表达式很类似,唯一的区别就是,在XML表达式中{}中的内容是scala代码,但是在模式匹配中{}中的内容是一个匹配的模式。

  示例如下:

def proc(node: scala.xml.Node): String =
  node match {
  case <a>{contents}</a> => "It's an a: "+ contents
  case <b>{contents}</b> => "It's a b: "+ contents
  case _ => "It's something else."
}

  运行结果如下:

scala> proc(<a>apple</a>)
res15: String = It's an a: apple

scala> proc(<b>banana</b>)
res16: String = It's a b: banana

scala> proc(<c>cherry</c>)
res17: String = It's something else.

  但是上面的模式匹配,只能处理一层的XML对象,对于嵌套的XML,将无法解析,如下所示:

scala> proc(<a>a <em>red</em> apple</a>)
res18: String = It's something else.

scala> proc(<a/>)
res19: String = It's something else.

2、模式匹配高级应用

  我们通常会希望XML模式匹配的功能不止如此简单,最好也能匹配到嵌套的XML元素。嵌套XML中取出的是XML node序列。在模式匹配中应该写成_*的形式。

  改进后的proc方法如下所示:

def proc(node: scala.xml.Node): String =
  node match {
  case <a>{contents @ _*}</a> => "It's an a: "+ contents
  case <b>{contents @ _*}</b> => "It's a b: "+ contents
  case _ => "It's something else."
}

  在代码中有一个@符号在_*之前,这个是Scala模式匹配中提到过的变量绑定,通过@符号,可以将匹配到的内容绑定到contents变量上,在后续代码中就可以直接使用该变量。

  运行结果如下

scala> proc(<a>a <em>red</em> apple</a>)
res20: String = It's an a: ArrayBuffer(a , <em>red</em>,  apple)

scala> proc(<a/>)
res21: String = It's an a: WrappedArray()

  模式匹配能够更好的进行XML解析,看如下这个示例:

val catalog =
<catalog>
  <cctherm>
    <description>hot dog #5</description>
    <yearMade>1952</yearMade>
    <dateObtained>March 14, 2006</dateObtained>
    <bookPrice>2199</bookPrice>
    <purchasePrice>500</purchasePrice>
    <condition>9</condition>
  </cctherm>
  <cctherm>
    <description>Sprite Boy</description>
    <yearMade>1964</yearMade>
    <dateObtained>April 28, 2003</dateObtained>
    <bookPrice>1695</bookPrice>
    <purchasePrice>595</purchasePrice>
    <condition>5</condition>
  </cctherm>
</catalog>

  从代码中看到在<catalog>标签下有两个<cctherm>子元素。但是打印出来后的结果如下。这是由于在标签<catalog>之后,里面包含的内容都是它的子元素,我们可以看到在最该标签的开始和结束各有一个换行,并且在两个<cctherm>之间也有换行符的存在。

scala> catalog match {
     |   case <catalog>{therms @ _*}</catalog> =>
     |     for (therm <- therms)
     |       println("processing: "+
     |       (therm \ "description").text)
     | }
processing: 
processing: hot dog #5
processing: 
processing: Sprite Boy
processing: 

  从结果可以看到在每个<catalog>标签前后都有一个空的元素。使用下面这段代码对XML进行处理,就可以跳过这些空元素了。

scala> catalog match {
     |   case <catalog>{therms @ _*}</catalog> =>
     |     for (therm @ <cctherm>{_*}</cctherm> <- therms)
     |       println("processing: "+
     |       (therm \ "description").text)
     | }
processing: hot dog #5
processing: Sprite Boy
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值