あるプログラマの日記

プログラマのメモ、出来事、考えたこと、勉強とかの雑記

XMLファイルの要素と属性をベタに表示

XMLファイルを読み込むプログラムを勉強がてらつくりました。

ScalaXML 操作が便利。
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 メソッドは末尾再帰になっていないですが、末尾再帰関数にする良い方法が他にあるのかもしれません。

*1:リソース処理後の処分(close)の保証

*2:Tuple2