XML相关

Posted dabokele

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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

以上是关于XML相关的主要内容,如果未能解决你的问题,请参考以下文章

XML相关

XML 相关技术总结

XML相关的安全漏洞-XXE,XPATH小结

ArcGIS切片服务获取切片方案xml文件(conf.xml)

[Spring框架]Spring开发实例: XML+注解.

[Spring框架]Spring开发实例: XML+注解.