Scala で XML を読んで、そこからなにかを抜き出すには、パターンマッチや scala.xml.NodeSeq の \, \\ といったメソッドを使うだろう。また、新しい XML 文章を作るのなら XML リテラルでだらだら書ける。では、既存の XML 文章をちょっと変えるには、具体的には
- link rel="self" の href を変えて
- link rel="hub" な要素を追加
したいときにどうするんだろう、というのでややつまった。
最初は \\ で対象ノードをみつけて消し足ししようかと思っていたのだけど scala.xml のあたりは全部 immutable なのでそうはいかない。結局、Scala, XML and GAE というスライドの12ページの
def add(p: Node, newEntry: Node ): Node = {
p match {
case <div>{ ch @ _@ }</div> =>
<div>{ ch }{ newEntry }</div>
}
をまねて、こう落ち着いた。
import scala.xml._
import org.scalatest.FunSuite
class XMLModifySuite extends FunSuite {
def process(seq: Node): Option[Node] = {
seq match {
case link @ <link/> => {
if (link.attributes("rel") == "self") {
Some(Group(List(<link rel="self" href="http://example.org/" />,
<link rel="hub" href="http://example.org/hub" />)))
} else if (link.attributes("rel") == "hub") {
None
} else {
Some(link)
}
}
case e: Elem => {
val child = e.child.flatMap(c => process(c) match {
case Some(v) => v
case None => Nil
})
Some(Elem(e.prefix, e.label, e.attributes, e.scope, child : _*))
}
case _ @ n => {
Some(n)
}
}
}
test("Remove hub") {
assert(process(<link rel="hub" href="http://example.org/hub"/>) === None)
assert(process(<feed><link rel="hub" href="http://example.org/hub"/></feed>).get ===
<feed></feed>)
}
test("Replace self and remove hub") {
assert(process(<feed><link rel="self" href="http://example.com"/></feed>).get ===
<feed><link rel="self" href="http://example.org/"/><link rel="hub" href="http://example.org/hub"/></feed>)
}
test("Else") {
assert(process(<feed><title>Hello</title></feed>).get ===
<feed><title>Hello</title></feed>)
}
}
「マッチ」で編集するのは、気持ちとしては正規表現みたいな感じ。といっても XML は再帰的な構造をもってるので、ややごちゃっとするなあ。
追記
match が link と Elem しかうけてなかったので
case _ @ n => {
Some(n)
}
と、テストを足しました。