ScalaでXMLからjQueryっぽく要素を抜き出せるものを作った
ScalaでXMLから要素を抜き出す方法について。
// テストデータ val html = <html> <head> <title> </title> </head> <body> <ul> <li>akemashite</li> <li>omedetou</li> </ul> <div id="container"> <div class="contents"> <span class="foo">happy</span> <span class="foo">new</span> <span class="foo">year</span> </div> </div> <div id="container2"> <div class="contents"> <span class="bar">o</span> <span class="bar">sho</span> <span class="bar">gatsu</span> </div> </div> </body> </html>
\ や \\ などのメソッドを使って、XPathっぽくやります。
scala> html \\ "ul" \ "li" res13: scala.xml.NodeSeq = NodeSeq(<li>akemashite</li>, <li>omedetou</li>)
便利ですねえ。
@をつけると属性の値を取り出せます。
scala> html \\ "span" \\ "@class" res15: scala.xml.NodeSeq = NodeSeq(foo, foo, foo, foo, foo, foo)
あと、パターンマッチもできます。
scala> html \\ "span" foreach { | case <span>{ text }</span> => print(text) | } happynewyearoshogatsu
だがしかし
属性でのマッチができない!!
// コンパイルエラー! html \\ "span" foreach { case <span class="foo">{ text }</span> => print(text) }
これができない!
がんばる
仕方ないので属性でのマッチをしたい時はこうやります。
パターンマッチ上級編って感じですね。
// うざい! scala> html \\ "span" foreach { | case span @ <span>{ text }</span> if (span \ "@class" text) == "foo" => print(text) | case _ => () | } happynewyear
for式だと
// うざい!! scala> for { | span @ <span>{ text }</span> <- html \\ "span" | if (span \ "@class" text) == "foo" | } yield span.text res26: scala.collection.immutable.Seq[String] = List(happy, new, year)
嬉しくない。
ということで
jQueryっぽく
$("span.class")
という風に属性でのマッチができるものを作ってみました。
https://github.com/tototoshi/selector
使いかたはこんなんです。
scala> val $ = new Selector(html) $: Selector = Selector@1ff63402 scala> $("span.foo") res27: scala.xml.NodeSeq = NodeSeq(<span class="foo">happy</span>, <span class="foo">new</span>, <span class="foo">year</span>) scala> $("div#container2 span").text res30: String = oshogatsu scala> $("div#container2 span.bar").text res31: String = oshogatsu scala> $("span[class=foo]") res35: scala.xml.NodeSeq = NodeSeq(<span class="foo">happy</span>, <span class="foo">new</span>, <span class="foo">year</span>)
classの指定は".", idの指定は#でやります。
classやid以外の属性指定も[attrName=value]でできます。
ただし、タグ名指定は必須で、属性単独での指定はできません。
実装は特に変わったことはしていなくて、
$("a b c d e") が html \\ "a" \\ "b" \\ "c" \\ "d" \\ "e" のaliasになったり、
$("a.foo")が html \\ "a" filter {a => (a \ "@class" text) == "foo" } のaliasになるようにがんばってるだけです。
というわけで、あけましておめでとうございます。
今年の目標はQOLです。よろしくお願いします。