M12i.

学術書・マンガ・アニメ・映画の消費活動とプログラミングについて

JavaによるRTFファイルのパース

この間RTFファイルを読み取って処理するプログラムを書く必要があって、あれこれ調べる機会があった。

このなかでたとえばLinuxTomcatを導入するときに同時にインストールされるGCC付属のJREにクセがあって、ファイルの文字化けが発生する(OpenJDKやJava for Mac、OracleJDKでは発生しない)問題なども見られて、実行環境──というかそれに付属する標準ライブラリの実装のちがいによる影響の存在というものを知るいい経験になった。

ところでJavaでRTFファイルを読み取るにはいくつかの方法があるようである。

  1. RTFは構造化されたテキストファイルなので、その仕様を知る人間であれば直にパース処理を書くこともできそうである。
  2. Javaの標準ライブラリSwingのパッケージにRTFEditorKit(javax.swing.text.rtf.RTFEditorKit)なるAPIがあり、これによってRTFを読み取り独自のDocumentクラス(javax.swing.text.Document)に変換してくれる。
  3. Apache Tikaプロジェクトの配布物の中にRTFParserなるAPIが存在していて、今回は結局これがいちばん目的に合っているようなのだが、いかんせんプロジェクトのドキュメントにUsageが存在しないのがあまりにも痛い。
  4. なおiTextなど他のドキュメント系ライブラリでもRTFの取り扱いができるようだが情報が少なすぎる。

それで、とりあえず1番の方法はまず却下。

さらに2番の方法がドキュメント的にも情報的にもほかの方法にくらべてマシだったので最初はこれを採用したのだが、なんとドキュメントをパースするとドキュメント内に埋め込まれたハイパーリンクが軒並み欠落してしまう。これはOpenJDK、Java for Mac(これとOpenJDKのリソース統合はどの程度進んでいるのだろう?)、OracleJDKのすべてで同じ動作だった。

結局選択肢はTikaだけになってしまったのだけれど、このTikaプロジェクトのParserインターフェースのドキュメントがあまりにも貧弱で、にもかかわらず処理のために必要なパラメータも多くて結局なにをどう与えたらいいのだ、というはなしであった。

参考になったのは下記のやりとりで、回答者が載せているサンプルコード。


Problems parsing a table inside an RTF file using Apache Tika/stackoverflow

I would suggest that rather than asking Tika for the plain text version, then wondering where all your nice HTML information has gone, you instead just ask Tika for the document as XHTML. You'll then be able to process that to find the information you want on your RTF File

If you look at the Tika Examples or the Tika Unit Tests, you'll see this same pattern for an easy way to get the XHTML output

Metadata metadata = new Metadata();

StringWriter sw = new StringWriter();
SAXTransformerFactory factory = (SAXTransformerFactory)
SAXTransformerFactory.newInstance();
TransformerHandler handler = factory.newTransformerHandler();
handler.getTransformer().setOutputProperty(OutputKeys.METHOD, "xml");
handler.getTransformer().setOutputProperty(OutputKeys.INDENT, "no");
handler.setResult(new StreamResult(sw));

parser.parse(input, handler, metadata, new ParseContext());

String xhtml = sw.toString();

問題のParserインターフェースのほとんどミニマルな構成での使い方が示されているようであった…。
もうロジカルな思考など何もなくコピペ&手直ししてみたらこれがふつうに動いた(当たり前?)。

結局下記のようなコードでRTFファイルを読み取ることにした。

	    Metadata metadata = new Metadata();
	    StringWriter sw = new StringWriter();
	    SAXTransformerFactory factory = (SAXTransformerFactory)
	             SAXTransformerFactory.newInstance();
	    TransformerHandler handler = factory.newTransformerHandler();
	    handler.getTransformer().setOutputProperty(OutputKeys.METHOD, "xml");
	    handler.getTransformer().setOutputProperty(OutputKeys.INDENT, "no");
	    handler.setResult(new StreamResult(sw));
	    new RTFParser().parse(new FileInputStream("xxxxx.rtf"), handler, metadata, new ParseContext());
	    String xhtml = sw.toString();

System.out.println(String...)でxhtmlの内容を確認したところ、問題の箇所──ハイパーリンクの箇所は正しくAタグとしてパースできていることを確認できた。