2012年1月21日土曜日

Anti-XML を使ってみた

XMLを弄る用があったので、Anti-XMLを使ってみた。

scala.xml の代替を目指しているライブラリなので使い勝手は似ているが、SelectorやZipperなど、scala.xmlには無い機能がある。

基本

com.codecommit.antixml._ をインポートすると、scala.xmlのクラスをantixmlのクラスに置き換えることができる。

import com.codecommit.antixml._

// scala.xml.Elem を com.codecommit.antixml.Elem に変換
val xml = Stay hungry, don't eat..convert
// ドキュメントではconvertメソッドではなく、
// antiメソッドを読んでいるのだが見つからない。。

// Text, Groupなどはcom.codecommit.antixml のクラスに上書きされてる。
val textGroup = Group(Text("stealth marketing"))

Selector

SelectorはPartialFunctionを継承していて、ノードをマッチさせるために使うのだが、任意の戻り値に変換できる。StringとSymbolはSelector[Elem]に暗黙変換される。

val text: Selector[String] = Selector({
  case Text(str) => str
})

// Vector[String]が返ってくる。
xml \ text // => Vector[String]

// Selector[Elem]の場合はZipper[Elem]が返ってくる。
xml \ "string" // => Zipper[Elem]
xml \ 'symbol // => Zipper[Elem]

Zipper

ZipperはXMLツリーをimmutableに更新する機能。ノードを探索して取得したZipperを書き換えた後に、unselectメソッドを呼ぶと更新されたXML全体が取得できる。

val xml = Carol Bartz.convert

val updatedXml = (xml \ 'ceo).updated(0, Scott Thompson.convert).unselect
// => Scott Thompson

探索したあとのノードから親ノードを再構成できるのは割とうれしい。でもscala.xml使うよりはXMLの更新が楽にはなっているが、まだちょっと面倒。使い方が悪いのかもしれないから今後調べる必要がある。

ちなみに xml \ 'hoge \ 'fuga のように2段階で探索した場合は、unselectを2回呼ぶ必要がある。

一応gistに上げといた↓

#!/usr/bin/env scalas
!#
/***
scalaVersion := "2.9.0-1"
libraryDependencies ++= Seq(
"com.codecommit" %% "anti-xml" % "0.3"
)
*/
import com.codecommit.antixml._
val movie =
<movie>
<title>Mulholland Drive</title>
<director>Unkown</director>
<actors>
<actor>Naomi Watts</actor>
<actor>Laura Harring</actor>
</actors>
</movie>.convert // convert scala.xml.Elem to com.codecommit.antixml.Elem
// create Selector
val text = Selector({
case Text(str) => str
})
// apply Selector
val actorNames = movie \ 'actors \ 'actor \ text
println(actorNames) // => Vector(Naomi Watts, Laura Harring)
// select director as zipper
val zipper:Zipper[Elem] = movie \ 'director
// update <director>'s text.
val crrectDirector:Elem = zipper.head.copy(children=Group(Text("David Lynch")))
// replace <director>
val updatedZipper = zipper.updated(0, crrectDirector)
// rebuild xml by updated zipper
val updatedXml = updatedZipper.unselect
// <director>Unkown</director> is replaced with <director>David Lynch</director>
println(updatedXml)