初试Scala解析XML

使用Scala解析XML,充分体现了函数式编程的特点,简洁和明了。用Java去解析不是不行,只不过代码不够清晰明了。

首先先把XML文件读入到内存里:

val someXml = XML.loadFile("file/FIXExample.xml")

这样someXml是一个scala.xml.Elem对象。

 

Scala XML API提供了类似XPath的语法来解析XML。在NodeSeq这类父类里,定义了两个很重要的操作符("\"和"\\"),用来获得解析XML:

  • \ :Projection function, which returns elements of this sequence based on the string that--简单来说,\ 根据条件搜索下一子节点
  • \\:Projection function, which returns elements of this sequence and of all its subsequences, based on the string that--而 \\ 则是根据条件搜索所有的子节点

先上一个XML的文件作为例子:

<fix major="4" minor="2">

  <header>

    <field name="BeginString" required="Y">FIX4.2</field>

    <field name="MsgType" required="Y">Test</field>

  </header>

  <trailer>

    <field name="Signature" required="N"/>

    <field name="CheckSum" required="Y"/>

  </trailer>

  <messages>

    <message name="Logon" msgtype="A" msgcat="admin">

      <field name="ResetSeqNumFlag" required="N"/>

      <field name="MaxMessageSize" required="N"/>

      <group name="NoMsgTypes" required="N">

        <field name="RefMsgType" required="N"/>

        <field name="MsgDirection" required="N"/>

      </group>

    </message>

    <message name="ResendRequest" msgtype="2" msgcat="admin">

      <field name="BeginSeqNo" required="Y"/>

      <field name="EndSeqNo" required="Y"/>

    </message>

  </messages>

  <fields>

    <field number="1" name="TradingEntityId" type="STRING"/>

    <field number="4" name="AdvSide" type="STRING">

      <value enum="X" description="CROSS"/>

      <value enum="T" description="TRADE"/>

    </field>

    <field number="5" name="AdvTransType" type="STRING">

      <value enum="N" description="NEW"/>

    </field>

  </fields>

</fix>

 

1. 首先来个简单的,如果要找header下的field,那么这样写即可:

val headerField = someXml\"header"\"field"

 

2.找所有的field:

val field = someXml\\"field"

 

3. 找特定的属性(attribute),如找header下的所有field的name属性的值:

val fieldAttributes = (someXml\"header"\"field").map(_\"@name")



val fieldAttributes = someXml\"header"\"field"\\"@name"

两个都能找到header下面所有field的name属性,但问题是输出的格式不一样。前者会返回一个List-List(BeginString, MsgType),而后者仅仅是BeginStringMsgType。中间连空格也没有。所以建议用前一种方法获得属性。

之前以为,下面的方法,和第二种方法一样能够获得属性的值:

val fieldAttributes = someXml\"header"\"field"\"@name"

根据\操作符的定义,理论上应该可以输出name属性的。实际上输出的结果是空,什么也没有。

\操作符的源码里有这么一段:

    that match {

      case ""                                         => fail

      case "_"                                        => makeSeq(!_.isAtom)

      case _ if (that(0) == '@' && this.length == 1)  => atResult

      case _                                          => makeSeq(_.label == that)

    }

第三个case表面,只有当this.length==1时,才可以这样做。原因其实很简单,somXml\"header"\"field"返回的是一个Seq[Node]的集合,包含多个对象。而\"@"的操作无法确定操作哪一个对象的属性:

  val x = <b><h id="bla"/><h id="blub"/></b>

  val y = <b><h id="bla"/></b>

  println(x\\"h"\"@id") //Wrong

  println(y\\"h"\"@id") //Correct with output: bla

 

4. 查找并输出属性值和节点值的映射:

(someXml\"header"\"field").map(n=>(n\"@name", n.text, n\"@required"))

这样的输出是List((BeginString,FIX4.2,Y), (MsgType,Test,Y))

 

5. 有条件地查找节点,例如查找name=Logon的message:

val resultXml1 = (someXml\\"message").filter(_.attribute("name").exists(_.text.equals("Logon")))



val resultXml2 = (someXml\\"message").filter(x=>((x\"@name").text)=="Logon")

 

6. 通过 \\"_" 获得所有的子节点,例如:

println(resultXml1\\"_")

结果是:

<message msgcat="admin" msgtype="A" name="Logon">

      <field required="N" name="ResetSeqNumFlag"/>

      <field required="N" name="MaxMessageSize"/>

      <group required="N" name="NoMsgTypes">

        <field required="N" name="RefMsgType"/>

        <field required="N" name="MsgDirection"/>

      </group>

</message>

<field required="N" name="ResetSeqNumFlag"/>

<field required="N" name="MaxMessageSize"/>

<group required="N" name="NoMsgTypes">

        <field required="N" name="RefMsgType"/>

        <field required="N" name="MsgDirection"/>

</group>

<field required="N" name="RefMsgType"/>

<field required="N" name="MsgDirection"/>

 

本文完

 

 

你可能感兴趣的:(scala)