ScalaでXMLからjQueryっぽく要素を抜き出せるものを作った

ScalaXMLから要素を抜き出す方法について。

// テストデータ
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になるようにがんばってるだけです。

https://github.com/tototoshi/selector/blob/1.2/src/main/scala/com/github/tototoshi/selector/Selector.scala


というわけで、あけましておめでとうございます。
今年の目標はQOLです。よろしくお願いします。