XMLファイルの要素と属性をベタに表示
XMLファイルを読み込むプログラムを勉強がてらつくりました。
Scala は XML 操作が便利。
scala.xml.parsing.ConstructingParser に Source を渡すとXML操作用の Document が取得できるので Node を使って要素のラベルと値、属性、子要素をすべてベタに表示。
import scala.collection.mutable.Seq import scala.io.Source import scala.io.BufferedSource import scala.xml._ import scala.xml.parsing.ConstructingParser object XmlFlatReader { def main(args: Array[String]): Unit = { val enc = encoding(args(0)) if (enc.isEmpty) { println("not XML file : " + args(0)) return } println("encoding = " + enc.get) val f = new java.io.File(args(0)) val s = Source.fromFile(f, enc.get) using[Unit](s) { s => val doc = ConstructingParser.fromSource(s, false).document() statement(doc.docElem) } } def statement(node: Node) { val elems = node \ "_" println(node.label + (if (!elems.isEmpty || node.text.isEmpty) "" else " : " + node.text)) val attrmap = node.attributes.asAttrMap attrmap.foreach { pa => println(" attr : " + pa._1 + " = " + pa._2) } elems.foreach { statement } } def encoding(fname: String): Option[String] = { val src = Source.fromFile(fname) using[Option[String]](src) { src => for (line <- src.getLines) { if (!line.contains("<?xml") || !line.contains("?>")) return None val items = line.split(" ") for (str <- items) { if (str.contains("encoding")) { val ent = str.split("=") return Some(ent(1).replace("\"", "").trim) } } } None } } def using[T](s: Source)(f: Source => T): T = { try f(s) finally s.asInstanceOf[BufferedSource].close } }
Node は子要素がある場合、text に子要素の値をすべて持っているので、自己の値のtext だけを表示してます。
ConstructingParser は XML の encoding を自動で識別してくれないのでencoding の取得のためだけに一旦、XMLファイルを読み込んでXML宣言にある encoding を取得。
もうちょっと良い方法があるのかもしれないが、とりあえず encoding が取れて指定できました。
XML宣言がない XMLファイルは読めないので XML宣言がない時は決め打ちで、UTF-8 を指定してもよかったかもしれない。
Source は close がないのでローンパターン *1 で BufferedSource にキャストして close。using に渡すクロージャの戻り値の型を型パラメータで指定してます。
Map で foreach すると キーと値の Pair *2 を渡してくれるのですね。
\ は NodeSeq のメソッドで、指定タグの要素を返してくれて便利。
ここでは使っていないが \\ はさらにネストした要素をサーチしてくれて、指定する名前の先頭に @ を付けると属性をサーチできるようだ。
"_" を指定したときは全ての子要素の集合を返してくれます。これで全要素を取得。
要素の Node パース用の statement メソッドは末尾再帰になっていないですが、末尾再帰関数にする良い方法が他にあるのかもしれません。