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 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'
まず、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としてやらねばなりません.
例えば
val xpath = ScalesXPath("count(/musicLibrary/cd)")
は
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
(of class java.lang.String)
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)
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)
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に軍配が上がらざるを得ません.まあこのあたりはJavaやScalaと.netという文化の違いもあるのかもしれませんが...
という訳で、Scales XMLのXPathサポートインタフェースについては、ちょっとがっかりしました.でもScales XMLはScala標準のXMLサポートよりもっと優れた機能を持っています.こんどはそれを紹介したいと思います.