QA@IT

ScalaのXML要素マッチングを教えてください.

3236 PV

お世話になります.

ScalaのElemシングルトンオブジェクトでマッチングさせる方法を教えてください.

http://www.scala-lang.org/api/current/index.html#scala.xml.Elem$

には

This singleton object contains the apply and unapplySeq methods for convenient construction and deconstruction. It is possible to deconstruct any Node instance (that is not a SpecialNode or a Group) using the syntax case Elem(prefix, label, attribs, scope, child @ _*) => ...

と記述されています.

XMLの処理で一番ありがちな処理パターンは、「要素aで属性bの値が~のとき何々をする」というものではないかと思います.それでは、この要素名="a"、属性値bの値="~"という条件を、ScalaのElemシングルトンオブジェクトのマッチングで記述するにはどのように書いたらよいのでしょうか?

記述方法がわかりましたらおしえてください.

以上

  • 回答に追記しました。 -
  • 回答に追記しました。 -

回答

for convenient construction and deconstruction.

と、ありますのでシングルトンのメソッドは、

val doc = new scala.xml.Elem(<>)

val doc = scala.xml.Elem.apply(<>)

とできますよというようなことだと思います。
戻りがscala.xml.Elemなのでどちらでやってもその後は
¥ 、¥¥ や @ を使ってマッチングするのではないかと思っているのですが、

「Elemシングルトンオブジェクトのマッチング」というのはどういうものを想定されてますか?

ちなみにscalaのシングルトンオブジェクト、コンパニオンクラスは、
javaなどでのシングルトンパターンとはちょっと違いますのでそこも気をつけてください。


追記1

using the syntax case Elem(prefix, label, attribs, scope, child @ _*) => ...

の部分を言っていたんですね、失礼しました。

一旦シングルトンオブジェクトは置いておいて、

case <a>{ someElem @ _* }</a> => ... はできるが、
case <a b="〜">{ someElem @ _* }</a> => ... を実現したいという件ですが

以下のようにすればマッチング可能です。

val qaitXML = <a b="myattr">some text</a>

// こちらは普通の 
qaitXML match{
   case <a>{ someElem @ _* }</a> =>
       println("text = %s".format(someElem))
   case =>
}

// こちらが属性を条件に加えたもの
qaitXML.match{
  case <a>{someElem @ _*}</a> if (qaitXML ¥ "@b").toString == "myattr" =>
      println("attr: %s, text: %s".format( qaitXML ¥ "@b", someElem))
  case =>
}

ネストしている場合は以下のようにできます。

val qaitXML = <items><a b="myattr">some text</a></items>

qaitXML.match{
  case <items>{a @ <a>{someElem @ _*}</a>}</items> if (a ¥ "@b").toString == "myattr" =>
      println("attr: %s, text: %s".format( a ¥ "@b", someElem ))
  case =>
}

それ以外にもNodeSeqにしてからmatchさせるという手もありそうです。

scala> val qaitXML = <items><a b="myattr">some text</a></items>
qaitXML: scala.xml.Elem = <items><a b="myattr">some text</a></items>

scala> qaitXML \ "a" filter (_ \ "@b" contains scala.xml.Text("myattr"))
res31: scala.xml.NodeSeq = NodeSeq(<a b="myattr">some text</a>)

scala> (qaitXML \ "a" filter (_ \ "@b" contains scala.xml.Text("myattr"))).text
res32: String = some text

最後に <a b="〜"> を Elem(...) をつかってマッチングさせる書き方ですが、以下のように書けると思います。
→ 間違ってました。

import scala.xml.Elem
val qaitXML = <a b="myattr">some text</a>
val attr = scala.xml.Attribute(null, "b", "myattr", scala.xml.Null)
qaitXML2 match{
  case Elem(_, "a", attr, _, someElem @ _*) => println("%s".format(someElem))
  case _ =>
}

// このコードは Attributeを作成して条件としようとしていますが、 それはElemの第三引数の使い方として間違いだったみたいです。
// 追記2を参考にしてください。


追記2

失礼しました。実行できただけで安心してました。

第三引数の Attributeは条件ではなくて取り出されるようです。
以下の様にすれば大丈夫だと思います。

判定部分をメソッド呼び出しにするやり方もつけておきました。

以下はそのまま scalaファイルにして実行可能です。

import scala.xml.Elem
import scala.xml.Text

val qaitXML = <a b="myattr">some text</a>
// val attr = scala.xml.Attribute(null, "b", "myattr", scala.xml.Null)

// 1番目と2番目はそれぞれ属性の値、タグが異なるのでマッチせず、3番目の条件でマッチする
qaitXML match{
    case Elem(_, "a", attrs, _, someElem @ _*) 
      if (attrs("b").text == "hoge") 
        => println("match1")
    case Elem(_, "b", attrs, _, someElem @ _*) 
      if (attrs("b").text == "hoge") 
        => println("match2")
    case Elem(_, "a", attrs, _, someElem @ _*) 
      if (attrs("b").text == "myattr") 
        => println("match3")
    case _ => println("not match")
}

// チェックをメソッドで行う  
qaitXML match{
    case Elem(_, "a", attrs, _, someElem @ _*) 
       if chk(attrs) 
         => println("match")
    case _ => println("not match")
}


// b属性の値が異なるのでマッチしないことの確認  
val qaitXML2 = <a b="hogehoge">some text</a>
qaitXML2 match{
    case Elem(_, "a", attrs, _, someElem @ _*) 
       if chk(attrs) 
         => println("match")
    case _ => println("not match")
}


def chk(attributes: scala.xml.MetaData) =
  (attributes("b").text == "myattr")

編集 履歴 (9)
  • ご回答ありがとうございます.
    やりたいことは単純です.
    case <a>{ someElem @ _*}</a> =>...
    とは書けますが、
    case <a b="~">{ someElem @ _*}</a> =>...
    とはかけません.ならば、
    case Elem(...)=>...
    で、<a b="~">にマッチするように書けないか?という趣旨です.
    -
  • おお!ありがとうございます.私も夕べ、case a @ <a>{ someElem @ _*}</a> if (a \ "@b" text) == "1" => println("Match <a b='1'>:'" + someElem + "'")というのにたどり着きました.バッチリです. -
  • 「 Elem(...) をつかってマッチングさせる書き方」は実際にお試しでしょうか?テストすると、どのような属性にもマッチしてしまいます.Elem()のコンストラクタパターンでは属性を絞り込んでマッチさせることは無理のように感じています. -
  • IDEでカーソルを合わせて見た感じではval attr =とcase Elem(_, "a", attr, _, someElem @ _*)のattrは別物ではないかということです. -
  • ご指摘ありがとうございます。たしかにElemのパターンは間違っていました。修正しました、失礼しました。
    -
  • ありがとうございます.理解できました.大変勉強になります. -
  • case Elem(_, "a", Attribute("b", Text("~"), _), _, child @ _*)=>println("Match <a b='~'> and other attributes!:'" + child + "'")のようにも書けますが、「属性の記述位置」に依存します.やはり標準のScalaのXMLではコンストラクタパターンはうまくいきませんね. -
ウォッチ

この質問への回答やコメントをメールでお知らせします。