LL脳がscalaの勉強を始めたよ その91
Scalaコップ本の26章残りをやってしまいますよー。26章はXML操作についてですね(`・ω・´)
前回作成したXML操作用のサンプルオブジェクトはこんな感じですね
abstract class CCTherm { // 温度計の説明 val description: String // 製作年 val yearMade:int // 購入日 val dateObtained:String // 流通平均価格:単位はセント val bookPrice:Int // 購入価格:単位はセント val purchasePrice:Int // 状態:1~10で表現 val condition:Int override def toString = description // 各データをXMLに変換するメソッド def toXML = <cctherm> <description>{description}</description> <yearMade>{yearMade}</yearMade> <dateObtained>{dateObtained}</dateObtained> <bookPrice>{bookPrice}</bookPrice> <purchasePrice>{purchasePrice}</purchasePrice> <condition>{condition}</condition> </cctherm> }
上記オブジェクトは下記手順で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 } // XMLに変換 val node = therm.toXML
今回も上記サンプルをベースに進めていきますよ
ロードと保存
XMLから文字列への変換はtoStringメソッドでOKみたいですが、XMLとバイトストリームの変換には文字符号化方式を指定できるなどのメリットがあるのでラブラリールーチンを使った方がヨロシイデスとのことです。
実際にXMLをファイルに変換するにはXML.saveFullコマンドを使いますね。XML.saveFileコマンドではファイル名、保存ノード、文字符号化方式を指定してオプションとして文字符号化方式のXML宣言の出力可否、XMLファイルのドキュメントタイプを指定するです。サンプルとしてはこんな感じですね。
// 文字符号化XML宣言ありでドキュメントタイプは指定なしです scala.xml.XML.saveFull("therm1.xml", node, "UTF-8", true, null)
上記処理で生成した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>
ちなみに上記をロードするときはXML.loadFileを使えば一発みたいです。XML.loadFileはこんな感じで使いますね(´・ω・`)
scala> val loadnode = scala.xml.XML.loadFile("therm1.xml") loadnode: scala.xml.Elem = <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を対象とするパターンマッチ
前回はtextやXPath的な\や\\でのXMLの分解についてやってみたのですが、パターンマッチを使ったXMLの分解についてやってみたいと思います。パターンマッチを使うことで複雑なXMLの制御が可能になるみたいですね。
XMLのパターンは次のようにXMLリテラルに似た形式になります。ただし{}で括られた部分がパターンとして利用できるようになりますね。サンプルとしてはこんな感じになりますです(`・ω・´)
def proc(node:scala.xml.Node):String = node match { case <a>{ contents }</a> => "これはaの" + contents case <b>{ contents }</b> => "これはbの" + contents case _ => "なんでもないデス(`・ω・´)" } // 実際に使ってみますよ scala> proc(<a>apple</a>) res4: String = これはaのapple scala> proc(<b>apple</b>) res5: String = これはbのapple scala> proc(<c>apple</c>) res6: String = なんでもないデス(`・ω・´)
上記サンプルでは単一ノードのみのマッチングですが、一連のノードに対してマッチングさせるためには次のように任意のシーケンスを表す_*を利用するといいみたいですね。例えばこんなふうに書けるみたいです。
// サブ要素のシーケンスに対応したパターンマッチです def proc2(node:scala.xml.Node):String = node match { // @パターンを利用して_*を使ってみますデス(´・ω・`) case <a>{ contents @ _* }</a> => "これはaの" + contents case <b>{ contents @ _* }</b> => "これはbの" + contents case _ => "なんでもないデス(`・ω・´)" } //// 実行してみますよ // @パターンで_*の内容もcontentsに束縛しておりますね scala> proc2(<a>a <em>red</em> apple</a>) res10: String = これはaのArrayBuffer(a , <em>red</em>, apple) //シーケンス要素を追加してみます scala> proc2(<b>a <em>blue</em> <span><em>big</em></span> apple</b>) res14: String = これはbのArrayBuffer(a , <em>blue</em>, , <span><em>big</em></span>, apple)
こんな感じで一つ一つのXMLノードをパターンマッチで処理させることが出来るわけですね(`・ω・´)
for式でXMLを処理
上でやったパターンマッチはあくまでもXMLノード単体を対象としたものですが、XML全体を処理する場合はfor式と組み合わせるのが良いみたいですね。例えば次のような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>
上記XMLの
// 空白処理を考慮せずにマッチングしてみます 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:
上記は
scala> catalog match { | case <catalog>{ therms @ _* }</catalog> => | // for式内で<cctherm>要素かどうかを判定して反復処理します | for(therm @ <cctherm>{ _* }</cctherm> <- therms) | println("processing: " + (therm \ "description").text) | } processing: hot dog #5 processing: Sprite Boy
スーパー蛇足で
ちなみに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>
上記XMLであれば空白考慮なしバージョンを実行しても普通に処理できます
scala> catalog match { | case <catalog>{ therms @ _* }</catalog> => | for(therm <- therms) | println("processing: " + (therm \ "description").text) | } processing: hot dog #5 processing: Sprite Boy