M12i.

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

Apache POIによるWordファイルの編集

Microsoft Officeスイートの各種ファイルをJavaからプログラマブルに編集するライブラリとして、Apache POIがある。これは皆さんご存じのところ。
このライブラリを使用することで、Excelについていえば旧形式のファイル(xlt)も新形式のファイル(xltx)も、同じインターフェースを通じて読み書きできる。これも皆さんご存じのところ。

つまりExcelファイルの操作については、いくつもの情報サイトでハウツーや紹介記事が掲載されていて、イテレータなど一部の問題を除けばAPIもわかりやすく操作しやすい。もちろんExcelのGUIでもってファイルを編集しているときとは勝手がちがい戸惑うところもあるけれど。

Wordファイルの処理

問題はExcel以外のファイルの場合。今回問題になったのはWordファイルの編集。APIを見る限り、WordのファイルはRangeSectionParagraphCharacterRunという階層構造になっているようだけれど、そもそもすべてのクラスがRangeの実装なので正確なところの構造がわからない。

Sectionインスタンスに対してはgetParagraph(int)のみならずgetCharacterRun(int)も呼び出すことができるし、ParagraphやCharacterRunでもこれらのメソッドが変わらず使用できるということ。すくなくとも使用できるように見える。

そんな混沌とした感じではあるけれど、Wordファイルに含まれている内容をテキストで取り出すのであれば、比較的簡単に済ませられる。
Rangeのサブクラスはすべてtext()メソッドを実装していてドキュメントの全体もしくは一部をStringとして返す。

Wordファイルの編集

読み取りの場合も、取得できたテキストを見るとBEL文字が改行文字のように使用されていたりと意味不明なものを目撃することになったりと、厄介なことはある。

けれども表がParagraphの集合体として内部的に表現されていて…、というところからさらに事態が悪化する。表はisInTable()メソッドがtrueを返すParagraphインスタンスからはじまって、isTableRowEnd()がtrueを返すParagraphインスタンスで終わる(らしい)。

先頭のParagraphを引数にSection#getTable(Paragraph)メソッドをコールするとTableのインスタンスが返される。Table→TableRow→TableCellという構造になっていて、TableCell#getRange()メソッドで、前述の階層構造が再度登場する。

ところでこのテーブルのセルに含まれるSection、ParagraphやCharacterRunの内容を編集するには、左記オブジェクト群のinsertBefore(String)やinsertAfter(String)、replaceText(String, String)、delete()などいくつかの編集のためのメソッドを使用することになる。
一見してわかるとおり、単純な追加を表現するようなadd(int)やappend()といったメソッドはなく、あくまでもすでに存在する要素にコンテンツを連結するかたちでしか操作ができない。

困ったことに、TableはしょせんParagraphやCharacterRunの集合体なので(?)、delete()メソッドやinsertXxxx系メソッドの使い方次第で簡単にファイルが破損してしまう。破損したファイルをWordで開くと、当然表はぐちゃぐちゃ。

さらにもともとのWordファイルの表内の編集対象セルに改行を含むテキストが存在していれば、insertXxxx系メソッドで追加するString中のCR(復帰文字)は改行文字としてWordファイルに反映される。
ところが改行を含まないテキストや空文字列しかもともとのセルに存在しなかった場合、insertXxxx系メソッドで追加したString中のCRは単なる空白としてWordファイルに反映される。

ここらへんでもうワケが分からなくなってくる。

ここでPOIの、とくにWordファイル操作APIのテストコードが公開されているけれど、そもそもこれだけのコードで何が検証できているのだろうかと疑問を抱かざるを得ない。

結局のところPOIのExcelファイル操作API以外の部分、──すくなくともWordファイル操作APIの部分は、なるほど確かに"HWPF is still in early development."なのである。