object matchTest {
def main(args: Array[String]): Unit = {
val doc = <nsb:block xmlns:nsb="urn:block">
<nsi:inline xmlns:nsi="urn:inline1" seq="1">phrase1
<nsi:inline xmlns:nsi="urn:inline2" seq="2">phrase2
</nsi:inline>
</nsi:inline>
</nsb:block>
val nodeSeq = doc \\ "_"
for (node <- nodeSeq)
node match{
case Elem("nsi", "inline", _, NamespaceBinding("nsi", "urn:inline1", _), child @ _*)=>println("Match {urn:inline1}:inline exactly!")
case Elem("nsi", "inline", _, NamespaceBinding("nsi", "urn:inline2", _), child @ _*)=>println("Match {urn:inline2}:inline exactly!")
case other => println("Other:"+other.namespace+":"+other.label)
}
}
}
このプログラムを走らせると、
Other:urn:block:block
Match {urn:inline1}:inline exactly!
Match {urn:inline2}:inline exactly!
object Elem extends Serializable
このマッチングの仕組みについては、「第26章 抽出子 Extractors(Scala スケーラブルプログラミング p.536~)」をご覧いただきたいと思います.
しかし、どうもコンストラクタパターンのNamespaceBinding("nsi", "urn:inline1", _)、NamespaceBinding("nsi", "urn:inline2", _)という記述が大変気になります.何故かと言うと、目的のネームスペースバインディングが先頭に定義されていることを前提としているからです.実際NamespaceBindingというのはちょっと変なデータ構造で、
new NamespaceBinding(prefix: String, uri: String, parent: NamespaceBinding)
というコンストラクタを持ったケースクラスです.つまりparentで次のネームスペースをポイントしているのです.
次のようなプログラムを作ってみると
object elemTest {
def main(args: Array[String]): Unit = {
val doc = <nsb:block xmlns:nsb="urn:block">
<nsi:inline xmlns:nsi="urn:inline1" seq="1">phrase1
<nsi:inline xmlns:nsi="urn:inline2" seq="2">phrase2
</nsi:inline>
</nsi:inline>
</nsb:block>
val nodeSeq = doc \\ "_"
for (node <- nodeSeq)
node match{
case Elem(ns, name, _, nsb, child @ _*)=>println("Match:'" + ns + ":" + name + "'")
println("Namespace Binding='"+printNsb(nsb,0)+"'")
case elem =>println("Elem:"+elem)
}
}
def printNsb(nsb:NamespaceBinding,level:Int): String={
Option(nsb) match{
case Some(nsb) => "("+(level+1)+") "+nsb.prefix+":"+nsb.uri+","+ printNsb(nsb.parent,level+1)
case None => "("+(level+1)+") :None"
}
}
}
Match:'nsb:block'
Namespace Binding='(1) nsb:urn:block,(2) null:null,(3) :None'
Match:'nsi:inline'
Namespace Binding='(1) nsi:urn:inline1,(2) nsb:urn:block,(3) null:null,(4) :None'
Match:'nsi:inline'
Namespace Binding='(1) nsi:urn:inline2,(2) nsi:urn:inline1,(3) nsb:urn:block,(4) null:null,(5) :None'
のように出力されます.つまりNamespaceBindingには使われていない接頭辞+URIの組み合わせも保存されているようです.そして、たまたまその要素のNamespaceBindingが先頭に配置されています.
最初のプログラムではNamespaceBindingの実装依存になってしまうので、プログラムを次のように変えてみました.NamespaceBindingをサーチして、最初に定義されている接頭辞に一致したらOKとするものです.
object matchTest {
def main(args: Array[String]): Unit = {
val doc = <nsb:block xmlns:nsb="urn:block">
<nsi:inline xmlns:nsi="urn:inline1" seq="1">phrase1
<nsi:inline xmlns:nsi="urn:inline2" seq="2">phrase2
</nsi:inline>
</nsi:inline>
</nsb:block>
val nodeSeq = doc \\ "_"
for (node <- nodeSeq)
node match{
case Elem(prefix, "inline", _, nsb, child @ _*)
if (hasNs(nsb,prefix,"urn:inline1")) => println("Match {urn:inline1}:inline exactly!")
case Elem(prefix, "inline", _, nsb, child @ _*)
if (hasNs(nsb,prefix,"urn:inline2")) => println("Match {urn:inline2}:inline exactly!")
case other => println("Other:"+other.namespace+":"+other.label)
}
}
def hasNs(nsb:NamespaceBinding,prefix:String,uri:String):Boolean={
Option(nsb) match{
case Some(nsb) => if (nsb.prefix==prefix)
case None => false
}
}
}
こうすればNamespaceBindingにコンストラクタを書かずに
Other:urn:block:block
Match {urn:inline1}:inline exactly!
Match {urn:inline2}:inline exactly!
という結果を得ることができます.結局コンストラクタパターン一発でマッチングというわけには行きませんでした.
しかし、名前空間接頭辞と名前空間URIの表現をNamespaceBindingというクラス構造でやっていて、しかもその実装が不要なデータを含んでいる(nsi:urn:inline2のあるところにnsi:urn:inline1が残っている)というのは実にイマイチな気がします.
例えば、Anti-XMLでは
com.codecommit.antixml Elem
で
case class Elem(prefix: Option[String], name: String, attrs: Attributes, scope: Map[String, String], override val children: Group[Node]) extends Node with Selectable[Elem]
次は属性をマッチングさせる方法について調べます.