( ꒪⌓꒪) ゆるよろ日記

( ゚∀゚)o彡°オパーイ!オパーイ! ( ;゚皿゚)ノシΣ フィンギィィーーッ!!!

scala.io.Sourceとscala.xml.parsing.XhtmlParser

scalaでファイルやURLからテキストを読み込みたいときには、scala.io.Sourceオブジェクトscala.io.Sourceクラスを利用するのが便利です。


scala.io.Sourceオブジェクトを利用するとファイルからの文字列の読込や、URLからフェッチする処理はこんなふうに簡単に書けます。

import scala.io.Source
import scala.xml.XML
import scala.xml.parsing.{ConstructingParser,XhtmlParser}

// textファイルから読み込んで出力
Source.fromFile("test.txt","UTF-8").foreach(print)

// URLからXMLを取得(twitterxml)
val src = scala.io.Source.fromURL("http://twitter.com/statuses/user_timeline/yuroyoro.xml";)
val xml = cpa.document()
// つぶやきを表示
(xml \\ "statuses" \ "status" \ "text").foreach( t => println( t.text ))

// URLのファイルをXHTMLとして読み込んでscala.xml.NodeSeqを作成
val xhtml = XhtmlParser( Source.fromURL("http://www.scala-lang.org/docu/files/api/scala/io/Source.html") )
// titleタグを抽出
xhtml \\ "title"

scala.xml.XMLという便利オブジェクトがあって、これはInputStreamや文字列からxmlを構築してくれます。ただ、Sourceオブジェクトからxmlを構築するメソッドだけなぜかないのです。
Sourceからxmlをparseするにはscala.xml.parsing.ConstructingParserで可能です。


対象がxmlではなくxhtmlならば、scala.xml.parsing.XhtmlParserでparse可能です。ただし、タグの閉じ忘れなどwell-formedなxmlじゃないと当然エラーになりますよ。


scala.io.Sourceはとっても便利ですが、注意しなければならないことがあります。

Streamのcloseは自動的に行われない

これはとっともイタイです。


ちょっとしたスクリプトでファイルを読み込むくらいならいいですけど、ある程度の規模のプログラムでSourceオブジェクトでファイルを読み込みまくると、そのうちjava.nio.BufferOverflowExceptionがthrowされてしまいます。


明示的にcloseしてやれればいいんですが、残念ながらscala.io.Sourceクラスにはcloseメソッドがありません(!?)


ではどうするかというと、こちらのコメント欄にあるように、java.io.InputStreamから Source.fromInputStreamを使って読み込んだあと、InputStreamをcloseするか、SourceをBufferedSourceにキャストしてcloseするしかないです(BufferedSourceにはcloseがあります)。

// InputStreamを使う場合
val in = new FileInputStream("test.txt")
try {
  for(line <- Source.fromInputStream(in).getLines){ println(line) }
}finally{
  in.close
}

// キャストする場合
val src = source( path )
try{
  XhtmlParser(src)
}finally{
  src.asInstanceOf[BufferedSource].close
}

追記:
scala.io.Sourceのダメさ加減とどうしようって議論がここでされてるらしい。
http://www.nabble.com/feedback-on-scala.io.Source-tc24940271.html