使用Scala解析XML,充分體現了函數式編程的特色,簡潔和明瞭。用Java去解析不是不行,只不過代碼不夠清晰明瞭。編程
首先先把XML文件讀入到內存裏:ide
val someXml = XML.loadFile("file/FIXExample.xml")
這樣someXml是一個scala.xml.Elem對象。函數式編程
Scala XML API提供了相似XPath的語法來解析XML。在NodeSeq這類父類裏,定義了兩個很重要的操做符("\"和"\\"),用來得到解析XML:函數
先上一個XML的文件做爲例子:ui
<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,那麼這樣寫便可:this
val headerField = someXml\"header"\"field"
2.找全部的field:spa
val field = someXml\\"field"
3. 找特定的屬性(attribute),如找header下的全部field的name屬性的值:scala
val fieldAttributes = (someXml\"header"\"field").map(_\"@name") val fieldAttributes = someXml\"header"\"field"\\"@name"
兩個都能找到header下面全部field的name屬性,但問題是輸出的格式不同。前者會返回一個List-List(BeginString, MsgType),然後者僅僅是BeginStringMsgType。中間連空格也沒有。因此建議用前一種方法得到屬性。code
以前覺得,下面的方法,和第二種方法同樣可以得到屬性的值:xml
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"/>
本文完