Search jakarta

POIFS API の使用方法

POIFS API の使用方法

この文書は、どのようにPOIFS APIを使って、それらの内容を組織する為にPOIFSに互換性のあるデータ構造を採用するファイルの読み込み・書き出し・修正を行うか、について、記述します。

改訂履歴

  • 02.10.2002 - sourceforgeにあった頃に書き下したオリジナルの文書を全面的に書き換えました。

対象となる方々

この文書は、どのようにPOIFS APIを使って、それらの内容を組織する為にPOIFSに互換性のあるデータ構造を採用するファイルの読み込み・書き出し・修正を行うか、について必要性のあるJava開発者を対象にしています。開発者がPOIFSのデータ構造を理解しておく必要はありません、また、それらのデータ構造の説明については、この文書の扱う範囲を超えています。基礎的な階層的ファイルシステムへの理解、そしてAWT等といったJava APIにより採用されるイベントパターンについての精通がある事は、この文書の対象となる方々にとって有益であることと期待しております。

用語集

この文書は、技術用語に関して一貫性を保つ努力がなされています。その技術用語については、こちらで定義されます:

用語 定義
Directory 他のディレクトリや文書を保持する事もある特殊なファイル
DirectoryEntry 他のディレクトリ内にあるディレクトリに対する表現
Document ワード処理データあるいはスプレッドシート・ワークブックなどといったデータを含むファイル
DocumentEntry ディレクトリ内にあるドキュメントに対する表現
Entry ディレクトリ内のファイルに対する表現
File ファイルシステムによって管理・保持される、名前を有するエンティティ
File System POIFSデータ構造、プラス、ディレクトリやドキュメントを含んだもので、階層的ディレクトリ構造が維持されている
Root Directory ファイルシステムのベースとなるディレクトリ。全てのファイルシステムにはルートディレクトリが存在する。POIFS API は、ルートディレクトリの削除・変更は許可されていないが、その中身を読み込む目的、あるいは(ディレクトリやドキュメントを)そこに追加するために、アクセスすることは可能となっている。

ファイルシステムの読み込み

このセクションでは、ファイルシステムの読み込みに関することを取り扱います。ファイルシステムを読み込むためには2つの方法があります;それらのテクニックについては、以下のテーブルで概要が示されており、その更に下のセクションでより深い詳細について説明がなされています。

テクニック 利点 欠点
通常の読み込み 通常のファイルシステムを読み込むのと似たように、シンプルなAPIを使います
どのような順番からでもドキュメントを読み込むことが出来ます
アプリケーションがもはや必要としなくなったとしても、全てのファイルがメモリ内に残ってしまいます
イベント稼動型の読み込み 足跡が少なくすみます -- メモリ上で現在処理中のドキュメントのみが存在するだけです
パフォーマンスの改善 -- 処理対象となるドキュメントの読み込みに無駄な時間がかかりません
より複雑なAPIです
どのドキュメントを読み込もうとしているのか、より詳細を知っておく必要があります
ドキュメントを読み込む順番を制御・コントロールすることが出来ません
(例えば、ファイルシステムがランダムアクセスがサポートされない入力ストリームから読み込まれている場合など、不可能である場合もあるが)ファイルシステムを再び読み込まない限り、戻ったり追加ドキュメントを得たりする手段がありません

通常の読み込み

このテクニックを使う場合、ファイルシステム全体がメモリに格納され、アプリケーションは全てのディレクトリツリーを走査する事が出来ます。アプリケーションが都合の良いときに指定のドキュメントを読み込む事が出来ます。

下準備

アプリケーションがファイルシステムからファイルを読み込む前に、ファイルシステムはメモリ内にロードされてある必要があります。これは、org.apache.poi.poifs.filesystem.POIFSFileSystemクラスを使うことで成されます。一旦ファイルシステムがメモリ上にロードされれば、アプリケーションがルートディレクトリを必要とすることもあるでしょう。以下のコード(一部)は、このような下準備段階を満たすものとなります:

// need an open InputStream; for a file-based system, this would be appropriate:
// InputStream stream = new FileInputStream(fileName);
POIFSFileSystem fs;
try
{
    fs = new POIFSFileSystem(inputStream);
}
catch (IOException e)
{
    // an I/O error occurred, or the InputStream did not provide a compatible
    // POIFS data structure
}
DirectoryEntry root = fs.getRoot();

例外が投げられなかったことを仮定すれば、ファイルシステムが読み込まれたことになります。

注意:ファイルシステムのロードには結構時間がかかります。特に、大きいファイルシステムである場合には。

ディレクトリツリーの読み込み

一旦ファイルシステムがメモリ上にロードされてルートディレクトリが取得されると、ルートディレクトリを読み取ることが可能となります。以下のコード(一部)は、どのようにorg.apache.poi.poifs.filesystem.DirectoryEntryインスタンス内のエントリを読み取るかの例を提示します:

// dir is an instance of DirectoryEntry ...
for (Iterator iter = dir.getEntries(); iter.hasNext(); )
{
    Entry entry = (Entry)iter.next();
    System.out.println("found entry: " + entry.getName());
    if (entry instanceof DirectoryEntry)
    {
        // .. recurse into this directory
    }
    else if (entry instanceof DocumentEntry)
    {
        // entry is a document, which you can read
    }
    else
    {
        // currently, either an Entry is a DirectoryEntry or a DocumentEntry,
	// but in the future, there may be other entry subinterfaces. The
	// internal data structure certainly allows for a lot more entry types.
    }
}

指定ドキュメントの読み込み

ドキュメントの読み込みには2つの方法があり、それは、ドキュメントがルートディレクトリにあるかそれとも別のディレクトリにあるかに依存しています。どちらの方法にせよ、org.apache.poi.poifs.filesystem.DocumentInputStreamインスタンスを得ることになります。

DocumentInputStream

DocumentInputStreamクラスは、多少特筆する価値のある保証を与えるInputStreamのシンプルな実装であります:

  • available()メソッドは、常に、現在のドキュメントでの位置からのドキュメントのバイト数を返します。
  • markSupported()メソッドはtrueを返します。
  • mark(int limit)メソッドは、limit パラメタを無視します;基本的に、このメソッドはドキュメント内の現在のポジションをマークします。
  • reset()メソッドは、mark()メソッドが最後に呼び出されたポジションに移動します。あるいは、もしmark()メソッドが一度も呼び出されたことがなければ、ドキュメントの先頭に移動します。
  • skip(long n)メソッドは、現在のポジションからnだけプラスされた位置に移動します。(ただし、ドキュメントの末端を過ぎることはありません)

以下のように、availableメソッドを使うことで、ドキュメント内で1回だけのread()メソッド呼び出しを行うだけで済むようになります:

byte[] content = new byte[ stream.available() ];
stream.read(content);
stream.close();

mark, reset, skipを組み合わせて使うことで、ドキュメントの内容へのランダムアクセスの為の基本的なメカニズムが提供されます。

ルートディレクトリからのドキュメントの読み込み

もし、ルートディレクトリにドキュメントがあれば、DocumentInputStreamを以下のようにして得ることが出来ます:

// load file system
try
{
    DocumentInputStream stream = filesystem.createDocumentInputStream(documentName);
    // process data from stream
}
catch (IOException e)
{
    // no such document, or the Entry represented by documentName is not a
    // DocumentEntry
}
任意ディレクトリにあるドキュメントの読み込み

ドキュメントの読み込みのより一般的なテクニックは、ターゲットとするドキュメントを含むディレクトリのorg.apache.poi.poifs.filesystem.DirectoryEntryインスタンスを取得する事です(ファイルシステムからルートディレクトリを得るにはgetRoot()が使えることを思い出してください)。このDirectoryEntryから、以下のようにDocumentInputStreamを取得することが出来ます:

DocumentEntry document = (DocumentEntry)directory.getEntry(documentName);
DocumentInputStream stream = new DocumentInputStream(document);

イベント稼動型の読み込み

ドキュメント読み込み用のイベント稼動型APIは、多少より複雑であり、予めアプリケーションがどのファイルを読み込もうとしているのかを覚えさせておく必要があります。このAPIを使うメリットは、アプリケーションがドキュメントを読み込む際必要とするに十分なだけのメモリしか使われない、ということであり、決して読み込もうとしないドキュメントは、メモリの中には完全に存在しない、という事です。ターゲットとするドキュメントを読み込み終えれば、ファイルシステムはそれに関連したデータ構造をまったく保持せず、破棄される事が可能となります。

下準備

org.apache.poi.poifs.eventfilesystem.POIFSReaderのインスタンスの作成を伴い、またPOIFSReaderに1つかそれ以上のorg.apache.poi.poifs.eventfilesystem.POIFSReaderListenerインスタンスを登録しておく事もこの下準備フェーズに含まれます。

POIFSReader reader = new POIFSReader();
// register for everything
reader.registerListener(myOmnivorousListener);
// register for selective files
reader.registerListener(myPickyListener, "foo");
reader.registerListener(myPickyListener, "bar");
// register for selective files
reader.registerListener(myOtherPickyListener, new POIFSDocumentPath(),
     "fubar");
reader.registerListener(myOtherPickyListener, new POIFSDocumentPath(
    new String[] { "usr", "bin" ), "fubar");

POIFSReaderListener

org.apache.poi.poifs.eventfilesystem.POIFSReaderListenerは、ドキュメント登録の際に使用されるインタフェースです。マッチするドキュメントがorg.apache.poi.poifs.eventfilesystem.POIFSReaderにより読み込まれた際、POIFSReaderListenerインスタンスはorg.apache.poi.poifs.eventfilesystem.POIFSReaderEventインスタンス(すなわちオープンされた状態のDocumentInputStreamとドキュメントに関する情報を含むインスタンス)を受け取ります。

POIFSReaderListenerインスタンスは、各々のあるいは全てのドキュメントを登録することが出来ます;一旦全てのドキュメントの登録がなされれば、その後に続く(及び、其の前にすでにやってしまった!)個々のドキュメントの登録要求は無視されます。POIFSReaderListenerの登録を破棄する手段はありません。

従って、1つのPOIFSReaderListenerを複数のドキュメント(1つ、多少、あるいは全てのドキュメント)を登録することが出来ます。登録されるドキュメントに付き丁度一つの通知が1つのPOIFSReaderListenerに受け渡される事が保証されています。ドキュメントの通知を受け取る順番につき、保証はありません。POIFSReaderの将来的な実装で、ファイルシステムのディレクトリ構造動き回るアルゴリズムを変更することを自由に行えるようにする為です。

同一のドキュメントに対し1つ以上のPOIFSReaderListenerを登録することも出来ます。 POIFSReaderが同一ドキュメントを処理する際、そのドキュメント用に登録されたPOIFSReaderListenerインスタンスの通知の順番に保証はありません。

全ての通知が同一のスレッドで発生することが保証されています。将来的な拡張で、マルチスレッドの通知が行われるようにするかもしれませんが、そのような拡張を行う場合、新しいreaderクラス(ThreadedPOIFSReaderとかかな?)を作らなければならない可能性が非常に高いでしょう。

以下のテーブルでは、ドキュメントやドキュメントのセット用にPOIFSReaderListenerを登録するための3つの方法を紹介します:

メソッドの使い方 動作(詳細)
registerListener(POIFSReaderListener listener) 全てのドキュメントに対しlistnerを登録する
registerListener(POIFSReaderListener listener, String name) ルートディレクトリの中の指定したnameのドキュメントに対し、listnerを登録する
registerListener(POIFSReaderListener listener, POIFSDocumentPath path, String name) pathで指定されたディレクトリの中の指定したnameのドキュメントに対し、listenerを登録する

POIFSDocumentPath

org.apache.poi.poifs.filesystem.POIFSDocumentPathクラスは、POIFSファイルシステム内のディレクトリを表現するのに使われます。POIFSファイルシステムの中には、ファイル名の予約されたキャラクタが無いので、(ディレクトリ名の構成要素を定めている特殊キャラクタ:"/"とかを使った)ディレクトリ名を記述する文字列ベースの古典的なソリューションはうまくいかないでしょう。このクラスのコンストラクタは、以下のように使用します:

コンストラクタ例 表現されるディレクトリ
new POIFSDocumentPath() ルートディレクトリ
new POIFSDocumentPath(null) ルートディレクトリ
new POIFSDocumentPath(new String[ 0 ]) ルートディレクトリ
new POIFSDocumentPath(new String[ ] { "foo", "bar"} ) Unixでいうところの"/foo/bar"ディレクトリ
new POIFSDocumentPath(new POIFSDocumentPath(new String[] { "foo" }), new String[ ] { "fu", "bar"} ) Unixでいうところの"/foo/fu/bar"ディレクトリ

POIFSReaderEventイベントの処理

org.apache.poi.poifs.eventfilesystem.POIFSReaderEventイベントの処理は、比較的やさしいです。全てのPOIFSReaderListenerPOIFSReaderに登録された後、POIFSReader.read(InputStream stream)が呼ばれます。

データに問題が無いと仮定すれば、 POIFSReaderが指定されたInputStreamのデータのドキュメント処理を行うと、 POIFSReaderEventに登録されたPOIFSReaderListenerインスタンスのprocessPOIFSReaderEventメソッドがコールされます。

POIFSReaderEventインスタンスには、ドキュメントを特定する情報が含まれています(ドキュメントが入っているディレクトリを特定するPOIFSDocumentPathオブジェクトとドキュメント名)。また、ドキュメントが最初に読み込まれる、オープンされたDocumentInputStreamインスタンスも含まれます。

ファイルシステムの書き込み

ファイルシステムへの書き込みは、やり方が複数あるという点でも、ファイルシステムからの読み込みと非常によく似ています。既存のファイルシステムをメモリ上にロードし、それを修正(ファイルの移動、ファイル名の変更)及び/或いはそこに新たなファイルの追加や書き込みも可能ですし、又は、空の新しいファイルシステムからはじめる事も出来ます:

POIFSFileSystem fs = new POIFSFileSystem();

命名について

ファイルを作成する際、考慮しなければならないファイルシステム上のファイル名の制約が2つほどあります:

  1. ファイル名は31文字を超えてはなりません。もし超えてしまえば、POIFS APIはその制限にあわせるように残りの部分を(警告を出さずに)切り取ってしまいます。
  2. ファイル名は、それが含まれるディレクトリ内で一意でなければなりません。これは、一見当たり前のように思えますが、ここで明言しておかないと、きっと大変やっかいなことになるでしょう。一意性は、勿論、(もしオリジナルの名前が長い場合、 31文字以内に)切り詰められた名前で判断されます。

ドキュメントの作成

DirectoryEntryを取得し、2つのうちの1つのcreateDocumentメソッドをコールすることで、ドキュメントの作成を行うことが出来ます:

メソッドの使い方 利点 欠点
CreateDocument(String name, InputStream stream) シンプルなAPIです メモリの残存痕跡が増えます(ドキュメントは、ファイルシステムに書き込まれるまでメモリ上にあります)
CreateDocument(String name, int size, POIFSWriterListener writer) メモリの残存痕跡が減ります(非常に小さいドキュメントのみがメモリに残り、そして時間も短いです) より複雑なAPIです。
予めドキュメントサイズを指定することは、難しい事かもしれません。
ドキュメントに書き込みが行われる場合コントロールを失います。

読み込みの時と異なり、メモリ型・イベント稼動型の書き込みモデルを選択する、などという必要はありません;同一のファイルシステムで共存することが可能となっています。

POIFSFileSystemインスタンスのwriteFilesystem()メソッドが書き込み用のOutputStreamと共に呼ばれた際、書き込み処理が初期化されます。

イベント稼動型モデルは、読み込みの場合のイベント稼動型モデルと非常によく似ています。ドキュメントを読み込むタイミングの際、POIFSReaderPOIFSReaderListenerを呼び出すのと同様に、ドキュメントを書き出すタイミングの際、ファイルシステムがorg.apache.poi.poifs.filesystem.POIFSWriterListenerを呼び出すのですから。内部的には、writeFilesystem()メソッドが呼ばれた際、最終的なPOIFSデータ構造が作成され、特別なOutputStreamにその構造が書き込まれます。イベント稼動型モデルで作成されたドキュメントをファイルシステムが書き込む必要がある際、POIFSWriterListenerがコールバックされ、processPOIFSWriterEvent()メソッドが(org.apache.poi.poifs.filesystem.POIFSWriterEventインスタンスが渡されて)呼び出されます。このオブジェクトは、POIFSDocumentPathや、ドキュメントの名前、サイズ、オープンされたorg.apache.poi.poifs.filesystem.DocumentOutputStream(どこに書き込むべきか)といった情報を含んでいます。DocumentOutputStreamは、書き込むべきPOIFSFileSystemに対して与えられたOutputStreamのラッパーとなります。そして、アプリケーションが書き込むドキュメントが、指定したサイズ内におさまるかどうかを保証する役割を担います。

ディレクトリの作成

ディレクトリの作成は、ドキュメントの作成とよく似ていますが、唯一、方法が一つしかない点が異なります:

DirectoryEntry createdDir = existingDir.createDirectory(name);

ドキュメント・ディレクトリ作成にPOIFSFileSystemを直接使用する

ドキュメントの読み込みに関して言えば、POIFSFileSystemの便利なメソッドを使ってルートディレクトリ内に新規ドキュメントや新規ディレクトリを作成することが可能です。

DirectoryEntryメソッドの使い方 POIFSFileSystemメソッドの使い方
createDocument(String name, InputStream stream) createDocument(InputStream stream, String name)
createDocument(String name, int size, POIFSWriterListener writer) createDocument(String name, int size, POIFSWriterListener writer)
createDirectory(String name) createDirectory(String name)

ファイルシステムの修正

アプリケーションがメモリに既にロードされているものであれ、当に今しがた作成中のものであれ、既存のPOIFSファイルシステムの修正をする事が可能です。

ドキュメントの削除

ドキュメントの削除は単純です:ドキュメントに対応するEntryを取得し、そのdelete()メソッドを呼び出すだけです。これは、ブール値を返すメソッドですが、(処理が成功したことを示す)true値が常に戻るようです。

ディレクトリの削除

ディレクトリの削除も単純です:ディレクトリに対応するEntryを取得し、そのdelete()メソッドを呼び出すだけです。これは、ブール値を返すメソッドですが、ドキュメントの削除とは異なり、(処理が成功したことを示す)true値が常に戻るというわけではありません。以下に、(処理が失敗する場合の)その理由を記述します:

  • ディレクトリ内に、まだファイルが残っている(確認するためには、DirectoryEntryのisEmpty()メソッドを呼び出し、戻り値がfalseでないかどうかを調べます)。
  • ディレクトリがルートディレクトリである。ルートディレクトリは削除不可能です。

ファイル・ディレクトリ名の変更

ファイルであろうがディレクトリであろうが、名前の変更は可能です - が、唯一の例外があります。ルートディレクトリは、メジャーなソフトウェアベンダのOfficeスイートによって予想される特別な名前を持っています。ですから、POIFS APIは、其の名前を変更することが出来ません。ファイルに対応したEntryインスタンスを取得し、新規の名前を入れた状態でrenameToメソッドを呼び出すことで、名前の変更を行うことが出来ます。

処理が上手く行けばdeleteと同じように、renameTotrueを返しますし、そうでなければfalseを返します。失敗する原因には、以下のようなものもあります:

  • 同一ディレクトリにある他のファイルと同じ名前を使おうとした。呉々も忘れないでおいて欲しいのですが - もし、新しい名前を31文字以上にしようとした場合、31文字まででそれ以降はカットされてしまいます。ですから、オリジナルの(31文字以上の)名前に一意性がたとえあったとしても、31文字に切り詰められてしまいますので、もはや一意性が無い、ということも有り得るわけです。
  • ルートディレクトリ名を変更しようとした。
by Marc Johnson
Original English Page would be found from HERE     --    BBS/ApacheNews
Terra-International, Inc. -- テラ・インターナショナル
Special Thanks -- 【お問い合わせ/テキスト広告】