プログラミング言語とXPath(8) Scales XMLをつかってみる.

Scales XMLXPathを試してみました.プログラムは以下のように超簡単なものです.

package xmltest
import scales.utils._
import ScalesUtils._
import scales.xml._
import ScalesXml._
import scales.xml.Functions._
import java.io._
import scalaz._
import Scalaz._
import scales.xml.jaxen._

object main {
 def main(args: Array[String]): Unit = {
    val doc =  loadXml(new java.io.FileReader("MusicLibrary.xml"),defaultPathOptimisation)
    val root = top(doc)
    val xpath = ScalesXPath("/musicLibrary/cd[string(year) = '1994']/title")
    val title = xpath.evaluate(root).head
    println("The title='" + string(title.right.get) + "'")
  }
}

これを以前 http://blogs.yahoo.co.jp/tnakita/14649736.html で紹介したXMLに適用すると、

The title='The Dark Side of the Moon'

と表示されます.ついにScalaでもXPathが使えました.メデタシ!メデタシ!と言いたいところなのですが、実は私は大いに疑問があります.それはxpath.evaluateの戻り値型です.

まず、evaluateの戻り値はIterable[Either[scales.xml.AttributePath,scales.xml.XmlPath]]です.Iterableのheadをとっているので、型はEither[scales.xml.AttributePath,scales.xml.XmlPath]になります.

これはものすごく簡単にいうと属性かそれ以外かの選択になります.作者のChris Twinerさんに聞いてみたら

"The reason is non atomic values can be either an attribute or an element.  Using Either forces the user to consider that choice."

という答えでした.でもEitherってものすごくXPathの戻り値としては不自然な気がして仕方がありません.この例のXPathの場合、要素を選択しているので、取り出すにはtitle.right.getとしてやる必要があります.

もしXPathが"/musicLibrary/cd[1]/@id"であったなら、

val attr = xpath.evaluate(root).head
println("The attr @id='" + string(attr.left.get) + "'")

とattr.left.getとしてやらねばなりません.

また実はXPathというのはある意味化け物で、属性や要素だけでなく、doubleやstringやbooleanというatomic valueも返します.

Scales XMLの実装では、XPathの評価を受け持つJaxenがちゃんとそれなりの値を返しているにもかかわらず、それを戻り値としてxpath.evaluateを呼び出した側に返してくれません.

例えば

val xpath = ScalesXPath("count(/musicLibrary/cd)")
val value = xpath.evaluate(root)
println("The value='" + value.toString() + "'")


Exception in thread "main" scala.MatchError: 5.0 (of class java.lang.Double)
at scales.xml.jaxen.ScalesXPath.evaluate(JaxenNavigator.scala:121)
at xmltest.main2$.main(main2.scala:31)
at xmltest.main2.main(main2.scala)

となります.

val xpath = ScalesXPath("string(/musicLibrary/cd[1])")
val result = xpath.evaluate(root).head
println("The result='" + result.toString() + "'")


Exception in thread "main" scala.MatchError: 
    Parallel Lines
    2001
    Blondie
    New Wave
   (of class java.lang.String)
at scales.xml.jaxen.ScalesXPath.evaluate(JaxenNavigator.scala:121)
at xmltest.main2$.main(main2.scala:36)
at xmltest.main2.main(main2.scala)

となります.Scales XMLの該当箇所のコードが以下のようなmatch式になっているからです.

[JaxenNavigator.scala]
  def evaluate( path : XmlPath ) : Iterable[Either[AttributePath, XmlPath]] = {
    val res = selectNodes(path).asInstanceOf[java.util.List[AnyRef]]
    if (res.size < 2) 
      if (res.size == 0) 
      else {
val it = res.get(0)
if (it eq null) 
 Nil
else
 one(it match {
   case x @ DocsUp(a : AttributePath, p) => Left(a)
   case x @ DocsUp(xp : XmlPath, p) => Right(xp)
   case DocumentRoot(r) => Right(r)
 })
      }
    else
      DuplicateFilter(sortT[XmlItem, Elem, XCC, Either[AttributePath,XmlPath]](res.map{
case x @ DocsUp(a : AttributePath, p) => (Left(a), a.parent)
case x @ DocsUp(xp : XmlPath, p) => (Right(xp), xp)
case DocumentRoot(r) => (Right(r), r)
}).map{x => x._1})(eitherAOrXEqual)
  }

DocsUpは以下のようなcase classです.

/**
 * exists only to provide Jaxen and JXPath with the same document root
 */ 
case class DocsUp[WHAT](what : WHAT, docroot : DocumentRoot)

ちなみに.netの世界では、このあたりはちゃんとメソッドが使い分けられています.System.Xml.XPathクラスはF#の記法で

static member XPathEvaluate : 
        node:XNode * 
        expression:string -> Object

でObjectを返します.コメントによれば、Objectはbool, double, string or IEnumerable(T)のうちのどれかです.XPath 1.0の理にかなっています.

複数の要素を選択するには

static member XPathSelectElements : 
        node:XNode * 
        expression:string -> IEnumerable<XElement> 

です.一個の要素を選択するには

static member XPathSelectElement : 
        node:XNode * 
        expression:string -> XElement 

です.何故.netなんかと比べたかというと、.netの方がはるかに全面的にXPathをサポートしていると感じたからです.このあたりが、ライブラリの作られ方の違いとなって表れてきているように思えてなりません.

Scales XMLを作った人は非常に優秀です.何故ってたぶん一人でScalaの標準のXMLサポートにないものを作ってしまったのですから.ですが.netはどうでしょう?私はMicrosoftが集団でレビューにレビューを重ねてインタフェースを設計し、テストして出したものに間違いないと考えています.どちらが使いやすいか?全面的にXPathをサポートしているかといえば.netに軍配が上がらざるを得ません.まあこのあたりはJavaScalaと.netという文化の違いもあるのかもしれませんが...

という訳で、Scales XMLXPathサポートインタフェースについては、ちょっとがっかりしました.でもScales XMLScala標準のXMLサポートよりもっと優れた機能を持っています.こんどはそれを紹介したいと思います.