初試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"/>

 

本文完

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM