scalaではscala-xmlパッケージでパーシングやノード作成などがサポートされているため、基本的にはあまり不便がありません。
ですが、textメソッドだとタグを除いた全てのテキストが返ってきてしまうため、サブノードを除いてノードが持つテキストだけを取得することが出来ません。
textメソッドを使わずにサブノードのテキストを含まない文字列を取得する方法について、備忘録として記録しておきます。
以下の例ではscala.xmlをインポートした前提で行っています。
import scala.xml._
サンプルとして下記のようなノードを用います。
<top>
top node
<middle>
middle node
<bottom>
bottom node
</bottom>
</middle>
</top>
textで取得
サンプルノードをXML.loadFileで読み込んでtextメソッドを使ってみます。
val nodeWithChild = XML.loadFile("samlple.xml")
println(nodeWithChild.text)
出力結果:
top node
middle node
bottom bode
タグが空白で置き換わり、top
以下の全てのサブノードに含まれるテキストが得られました。
このようにtextメソッドだと全てのテキストが返ってきてしまうため、top
ノードのテキスト(top node
)だけ取得したい場合や、パーシングしながら各サブノードのテキストを順に取得したい場合などにちょっと不便です。
サブノードのテキストを除外
ここで紹介する方法に関しては、このページを参考にしました。
まず、サブノードに含まれるテキストを除くために、childメソッドとcollectメソッド、それとText抽出子を用いてTextクラスだけで構成される配列を取得します。
val nodeWithChild = XML.loadFile("samlple.xml")
val childArray = nodeWithChild.child.collect{case Text(t) => t}
結果:
childArray: Seq[String] =
ArrayBuffer("
top node
", "
")
得られた配列に対してmkStringメソッドを使えば文字列を得られるのですが、タグの部分が空白に置き換わっているために余白が残ってしまいます。
そこで、trimメソッドを使って余白を除いてから文字列にする処理を加えてみます。
childArray.map(_.trim).filterNot(_.isEmpty).mkString
結果:
res1: String = top node
top
ノードのテキストだけを余白を除いた形で得られました。
以上の処理をまとめて、getText関数を作成します。
def getText(x: Node): String = {
(x.child.collect{case Text(t) => t}).map(_.trim).filterNot(_.isEmpty).mkString
}
nodeWithChildにgetText関数を使用して、top
ノードのテキストだけ得られるか確認してみます。
println(getText(nodeWithChild))
結果:
top node
top
ノードのテキストだけ得られるようになりました。
つぎに、middle
ノードに対して使用してみます。middle
ノードの取得にはchildメソッドを使います。
println(getText(nodeWithChild.child(1)))
結果:
middle node
サブノードに対しても目的のテキストを得られています。
改行文字がある場合
改行文字(\n)が含まれたノードに対してchildメソッドを適用すると、空の要素がTextオブジェクトとして、テキスト要素がAtomオブジェクトとして返ってきてしまうみたいです。
例えば、下記のようにscalaのXMLリテラルで作成した改行文字を含んだノードに対して、childメソッドを使用してみます。
val nodeWithChildAndNewline =
<top>
{"top node\n"}
<middle>
{"middle node\n"}
<bottom>
{"bottom node\n"}
</bottom>
</middle>
</top>
nodeWithChildAndNewline.child
結果:
ArrayBuffer(
, top node
,
, <middle>
middle node
<bottom>
bottom node
</bottom>
</middle>,
)
1番目の要素として空の要素が入っています。
この要素のクラスをgetClassメソッドで確認すると、下記のようにTextとなっています。
nodeWithChildAndNewline.child(0).getClass
res1: Class[_ <: scala.xml.Node] = class scala.xml.Text
2番目の要素もTextクラスのように見えるのですが、これはAtomとなっています。
nodeWithChildAndNewline.child(1).getClass
res2: Class[_ <: scala.xml.Node] = class scala.xml.Atom
先ほど作ったgetText関数ではText抽出子を用いているため、このノードに対して使用すると何も返ってきません。
getText(nodeWithChildAndNewline)
res3: String = ""
そこでやや冗長なやり方ですが、toStringとsplit関数を用いて改行を除いた文字列を取得して、それをloadStringメソッドで新しいノードとして作りなおすtrimNode関数を用意します。1
def trimNode(x: Node): Node = {
XML.loadString(x.toString.split("\n").mkString)
}
trimNode関数によるトリミング結果をgetText関数で使います。
def getText(x: Node): String = {
(trimNode(x).child.collect{case Text(t) => t}).map(_.trim).filterNot(_.isEmpty).mkString
}
さきほどのnodeWithChildAndNewlineに使用してみます。
println(getText(nodeWithChildAndNewline))
res4: String = top node
改行文字による影響を受けずに、目的のテキストが得られました。
参考
- https://github.com/scala/scala-xml (scala.xmlのソースコードを確認するときに参考にしました。)
- http://www.scala-lang.org/api/2.11.1/scala-xml/#scala.xml.package (パッケージの中身一覧を調べるときに参考にしました。)
-
xml.Utility.escapeで特殊文字をエスケープする方がシンプルでした。(2016/05/19追記) ↩