18.10. "multifile" --- 個別の部分を含んだファイル群のサポート
*************************************************************

バージョン 2.5 で非推奨: "multifile" モジュールよりも "email" パッケー
ジを使うべきです。このモジュールは後方互換性のためだけに存在しています
。

"MultiFile" オブジェクトは、テキストファイルのセクション分割をファイル
類似の入力オブジェクトのように扱えるようにします。指定したデリミタのパ
ターンに遭遇すると "readline()" が "''" を返します。このクラスの標準設
定は MIME マルチパートメッセージを解釈する上で便利となるように設計され
ていますが、サブクラス化を行って幾つかのメソッドを上書きすることで、簡
単に汎用目的に対応させることができます。

class multifile.MultiFile(fp[, seekable])

   マルチファイルを生成します。*fp* 引数には、例えば "open()" が返すフ
   ァイルオブジェクトのような、"MultiFile" インスタンスが行データを取
   得出来るオブジェクトを渡す必要があります。

   "MultiFile" は入力オブジェクトの "readline()" 、 "seek()" 、および
   "tell()" メソッドしか参照せず、後者の二つのメソッドは個々の MIME パ
   ートにランダムアクセスしたい場合にのみ必要です。 "MultiFile" を
   seek できないストリームオブジェクトで使うには、オプションの
   *seekable* 引数の値を偽にしてください; これにより、入力オブジェクト
   の "seek()" および "tell()" メソッドを使わないようになります。

"MultiFile" の視点から見ると、テキストは三種類の行データ: データ、セク
ション分割子、終了マーカ、からなることを知っていると役に立つでしょう。
MultiFile は、多重入れ子構造になっている可能性のある、それぞれが独自の
セクション分割子および終了マーカのパターンを持つメッセージパートをサポ
ートするように設計されています。

参考:

  "email" モジュール
     包括的な e-mail 処理パッケージです。 "multifile" に取って代わりま
     す。


18.10.1. MultiFile オブジェクト
===============================

"MultiFile" インスタンスは以下のメソッドを持っています:

MultiFile.readline(str)

   一行データを読みます。その行が (セクション分割子や終了マーカや本物
   の EOF でない) データの場合、行データを返します。その行がもっとも最
   近スタックにプッシュされた境界パターンにマッチした場合、 "''" を返
   し、マッチした内容が終了マーカかそうでないかによって "self.last" を
   1 か 0 に設定します。行がその他のスタックされている境界パターンにマ
   ッチした場合、エラーが送出されます。背後のストリームオブジェクトに
   おけるファイルの終端に到達した場合、全ての境界がスタックから除去さ
   れていない限りこのメソッドは "Error" を送出します。

MultiFile.readlines(str)

   このパートの残りの全ての行を文字列のリストとして返します。

MultiFile.read()

   次のセクションまでの全ての行を読みます。読んだ内容を単一の (複数行
   にわたる) 文字列として返します。このメソッドには size 引数をとらな
   いので注意してください!

MultiFile.seek(pos[, whence])

   ファイルを seek します。 seek する際のインデクスは現在のセクション
   の開始位置からの相対位置になります。 *pos* および *whence* 引数はフ
   ァイルの seek における引数と同じように解釈されます。

MultiFile.tell()

   現在のセクションの先頭に対して相対的なファイル位置を返します。

MultiFile.next()

   次のセクションまで行を読み飛ばします (すなわち、セクション分割子ま
   たは終了マーカが消費されるまで行データを読みます)。次のセクションが
   あった場合には真を、終了マーカが発見された場合には偽を返します。最
   も最近スタックにプッシュされた境界パターンを最有効化します。

MultiFile.is_data(str)

   *str* がデータの場合に真を返し、セクション分割子の可能性がある場合
   には偽を返します。このメソッドは行の先頭が (全ての MIME 境界が持っ
   ている) "'-""-'" 以外になっているかを調べるように実装されていますが
   、派生クラスで上書きできるように宣言されています。

   このテストは実際の境界テストにおいて高速性を保つために使われている
   ので注意してください; このテストが常に false を返す場合、テストが失
   敗するのではなく、単に処理が遅くなるだけです。

MultiFile.push(str)

   境界文字列をスタックにプッシュします。この境界文字列の修飾されたバ
   ージョンが入力行に見つかった場合、セクション分割子または終了マーカ
   であると解釈されます(どちらであるかは修飾に依存します。 **RFC
   2045** を参照してください)。それ以降の全てのデータ読み出しは、
   "pop()" を呼んで境界文字列を除去するか、 "next()" を呼んで境界文字
   列を再有効化しないかぎり、ファイル終端を示す空文字列を返します。

   一つ以上の境界をプッシュすることは可能です。もっとも最近プッシュさ
   れた境界に遭遇すると EOF が返ります; その他の境界に遭遇するとエラー
   が送出されます。

MultiFile.pop()

   セクション境界をポップします。この境界はもはや EOF として解釈されま
   せん。

MultiFile.section_divider(str)

   境界をセクション分割子にします。標準では、このメソッドは (全ての
   MIME 境界が持っている) "'--'" を境界文字列の先頭に追加しますが、こ
   れは派生クラスで上書きできるように宣言されています。末尾の空白は無
   視されることから考えて、このメソッドでは LF や CR-LF を追加する必要
   はありません。

MultiFile.end_marker(str)

   境界文字列を終了マーカ行にします。標準では、このメソッドは (MIME マ
   ルチパートデータのメッセージ終了マーカのように) "'--'" を境界文字列
   の先頭に追加し、かつ "'--'" を境界文字列の末尾に追加しますが、これ
   は派生クラスで上書きできるように宣言されています。末尾の空白は無視
   されることから考えて、このメソッドでは LF や CR-LF を追加する必要は
   ありません。

最後に、 "MultiFile" インスタンスは二つの公開されたインスタンス変数を
持っています:

MultiFile.level

   現在のパートにおける入れ子の深さです。

MultiFile.last

   最後に見つかったファイル終了イベントがメッセージ終了マーカであった
   場合に真となります。


18.10.2. "MultiFile" の例
=========================

   import mimetools
   import multifile
   import StringIO

   def extract_mime_part_matching(stream, mimetype):
       """Return the first element in a multipart MIME message on stream
       matching mimetype."""

       msg = mimetools.Message(stream)
       msgtype = msg.gettype()
       params = msg.getplist()

       data = StringIO.StringIO()
       if msgtype[:10] == "multipart/":

           file = multifile.MultiFile(stream)
           file.push(msg.getparam("boundary"))
           while file.next():
               submsg = mimetools.Message(file)
               try:
                   data = StringIO.StringIO()
                   mimetools.decode(file, data, submsg.getencoding())
               except ValueError:
                   continue
               if submsg.gettype() == mimetype:
                   break
           file.pop()
       return data.getvalue()
