前節で説明したラウンドトリップ仕様(ラウンドトリップ仕様を参照)とは異なり、変数
after-insert-file-functions と write-region-annotate-functions
を使用して、それぞれの読み込み変換と書き込み変換を個別に制御できます。
変換はある表現から始まり、別の表現を生成します。行うべき変換が1つだけであれば、何から始めるかについて競合は生じません。しかし、複数の変換が関わる場合、2つの変換が同じデータから開始する必要があるときに競合が生じる可能性があります。
この状況は、write-region
におけるテキストプロパティの変換という文脈で理解するのが最適です。例えば、バッファの位置 42 にある文字が ‘X’ で、テキストプロパティが foo
であるとします。もし foo の変換を、例えば ‘FOO:’ をバッファに挿入することで行うと、位置 42 の文字は ‘X’ から ‘F’
に変更されます。すると、次の変換は最初から誤ったデータに基づいて行われることになります。
競合を避けるため、協調的変換ではバッファを変更せず、代わりにアノテーション((position . string)形式の要素のリストで、positionの昇順にソートされたもの)を指定します。
変換が複数ある場合、write-region
はそれらのアノテーションを破壊的に統合し、ソート済みの単一リストにまとめます。その後、バッファ内のテキストが実際にファイルに書き出される際、指定されたアノテーションが対応する位置に混在して書き込まれます。これら一連の処理は、バッファを変更することなく行われます。
これに対し、読み取り時には、テキストに混在するアノテーションが即座に処理されます。insert-file-contents
は変換対象のテキストの先頭にポイントを移動させ、そのテキストの長さを引数として変換関数を呼び出します。これらの関数は、常に挿入されたテキストの先頭にポイントを置いた状態で戻る必要があります。この手法が読み取りにおいて有効なのは、最初のコンバータで除去されたアノテーションが、後続のコンバータによって誤って処理されることがないためです。各変換関数は、認識可能なアノテーションをスキャンして除去し、バッファ内のテキストを変更し(例えばテキストプロパティを設定するなど)、変更後のテキストの長さを返して返す必要があります。ある関数が返した値が、次の関数の引数となります。
write-region
が呼び出す関数の一覧。一覧内の各関数は、書き込み対象領域の開始位置と終了位置という2つの引数で呼び出される。これらの関数はバッファの内容を変更してはならない。代わりに、アノテーションを返す必要がある。
特殊なケースとして、関数が異なるバッファカレントを返して戻ってくることがあります。Emacsはこれを、現在のバッファに出力用の変更済みテキストが含まれていると解釈します。そのため、write-region 呼び出しの引数 start と end を変更し、それぞれ新しいバッファにおける point-min と point-max
の値を代入します。また、それまでのアノテーションはすべて破棄されます。それらはこの関数によって処理されるべきものであるためです。
この変数の値が nil 以外の場合、関数である必要があります。この関数は、write-region の完了後、引数なしで呼び出されます。
write-region-annotate-functions
内のいずれかの関数が異なるバッファカレントを返して終了した場合、Emacs は write-region-post-annotation-function を複数回呼び出します。Emacs
は、最後にカレントだったバッファでこれを呼び出し、さらにその前のバッファで呼び出し、というように元のバッファまで遡って呼び出します。
したがって、write-region-annotate-functions
内の関数は、バッファを作成し、そのバッファ内でこの変数に kill-buffer
のローカル値を代入し、テキストを変更してバッファを設定し、そのバッファをカレントにすることができます。write-region の処理が完了すると、バッファは破棄されます。
このリストにある各関数は、挿入された文字数という1つの引数を受け取り、ポイントを挿入テキストの先頭に置いた状態で insert-file-contents
によって呼び出されます。各関数はポイントを変更せず、関数によって修正された挿入テキストの新しい文字数を返す必要があります。
ユーザーの皆様には、こうしたフックを用いて、ファイル内のテキストプロパティを保存・取得するLispプログラムを記述し、さまざまなデータ形式を試しながら、最適な形式を見出していただきたいと考えています。最終的には、Emacsに導入できるような、優れた汎用的な拡張機能をユーザーの皆様に作成していただけることを期待しています。
任意のLispオブジェクトをテキストプロパティの名前や値として扱おうとすることはお勧めしません。そうした汎用的なプログラムは、おそらく記述が困難で、動作も遅くなるためです。代わりに、適度な柔軟性があり、エンコードもそれほど難しくない、候補となるデータ型のセットを選択してください。