PDIC/Unicodeの辞書(拡張子が.dicのやつ)の仕様を簡単に解説します。
ある程度プログラムの知識は要るかもしれませんが、バイナリファイルとかを普段扱わない人でも読めるように配慮しようと思います。
本家資料はこちらです
一部分かりやすさのために、本家資料の用語と違う言いかたをしているところがあります。
ここで対象にするのは辞書のバージョンが6.10のものです。PDIC/Unicode Ver.5以降で使われているようです。
最近のコンピュータでは、ファイルは2進数8桁のバイトという単位で、数値がひたすら並んだものです。(圧縮ファイルとかだと違ったりしますが) そのバイトの羅列を、適当なバイト数ごとに切り分けたりして読み込んでいきます。
二進数で16桁とか32桁とかの数値を記録する時は、複数のバイトに切り分けて並べます。並べかたには2種類の方法があって、PDICの辞書型式ではリトルエンディアンという方法に揃えています。
32桁の0x12345678
というデータがあると、0x78
0x56
0x34
0x12
という感じで、バイト単位で逆順に並べます。
逆に、オンかオフだけのデータ(フラグ)の場合は、1バイトの中に複数のフラグを含ませたりします。それぞれの桁に対応させればいいので。
まとめられたバイトから、フラグを個別に読みとる場合はビット積を使うことが多いです。(数値) & 0x02
とかやると、数値のうち、0x02
のフラグがオフなら0
に、オンなら0
以外になるので、そのままif
文の条件式に入れたりします。
辞書ファイルは、大きく分けて4つの部分でできています。ヘッダー部、拡張ヘッダー部、インデックス部、データ部です。ファイルの中でこの順番に並んでいます。
データ部が本体です。その中には、単語がUnicodeの文字コード順に並んで記録されています。と言いたいところですが、違います。
データ部はブロックの集まりになっていて、順番に物理ブロックIDが振られています。それぞれのデータブロックには単語が文字コード順に並んで入っています。ひとつの単語が複数のブロックにまたがって入ることはありません。
データブロックには、いっぱいに単語が詰まってるわけではなくて、最後の方に余白があります。
そしてこのデータブロックの順番を、インデックス部で記録しています。インデックス部に記録された順番にデータブロックを読み込んでいくと、全体として文字コード順に単語が得られます。
あとインデックス部には、それぞれのデータブロックについて、その中で最初に入っている単語の見出し語も記録しています。
なんでこんなことをしているかというと、PDICは巨大な辞書を扱う前提で作られているので、一度に辞書ファイル全部を読み書きしたくないのです。
見出し語から検索するときは、インデックス部にある「それぞれのデータブロックの最初の見出し語」と比較して、目的の単語が入っていそうなデータブロックを絞り込みます。そしてその分のデータブロックだけを実際に読み込んで、目的の単語を見つけます。
一方、単語を増やした時や、内容を書き換えて長くなった時は、それ以降の単語のデータを全部後ろにずらして、すきまを空けないといけません。また、単語を消した時や、内容が短くなった時は、それ以降の単語を全部前に詰めないといけません。
データとして記録されているファイルは巨大な配列みたいなものなので、途中に挿入したり途中を削除したりするためには、それより後ろを全部ずらす、つまりコピーして上書きしないといけないんですね。
しかし、データブロックの最後の方に余白があるので、その単語が入っているデータブロックだけを書き換えれば済みます。それでもし余白に収まらなくなってはみ出た場合は、ブロックを半分ぐらいに切って2つにします。増えた方のブロックは、空いてるところ(データ部の末尾とか)に置きます。
逆にブロックの中の余白が多くなりすぎた場合は、いくつかのブロックを合体させて、使わなくなったブロックを空きブロックにしたりします。空きブロックはインデックスから除外されます(多分)。空きブロックは次にまた新しくブロックを作らないといけない時に、優先的に使われていきます。
こうした工夫により、ファイル全体ではなく、1ブロック〜数ブロック分の読み書きだけで検索・編集ができます。編集していくうちに、データブロックの物理的な順番は、単語の実際の順番と合わなくなっていくので、インデックスでその順番を記録しています。
PDICの単語には見出し語という項目がありますが、少し扱いがやっかいです。内部では、キーワードと本来の見出し語をタブ("\t"
)で連結したものを、見出し語として扱っています。
キーワードは、入力した見出し語の大文字を小文字に変換したり、ハイフンを半角スペースにしたりして自動生成されます。Tools > 設定 > 動作環境 > 登録語編集 > キーワード欄を表示する にチェックを入れると手動でも編集できます。
例: 入力した見出し語 "Foo-bar"
→ キーワード "foo bar"
, 内部データとしての見出し語 "foo bar\tFoo-bar"
さっき、単語を文字コード順で並べると書きましたが、正確にはこの、内部データとしての見出し語の文字コード順で並べます。
ファイル内の全ての単語で、キーワードと見出し語が同じである場合は、そのまま内部データとして使うことがあります。ファイルを読む側の立場から言えば、"abc"
のようにタブが含まれていない場合は、キーワードも見出し語も"abc"
であるということです。
この省略をする場合は、全ての単語で省略しないといけません。混在していると文字コード順のソートに影響が出てしまうからです。
PDIC/Unicodeでは、テキストを全てUnicodeで扱いますが、そのエンコーディング方法としてはBOCU-1を使います。
近い文字コードの文字が連続しているところを省略して、前の文字からの差分を記録するような方法で、Unicode文字全てを扱いながら容量を減らすものです。詳しい説明は他に譲ります。
ファイルの先頭から始まります。長さは1024バイト固定です。
辞書全体に関する情報や、全体のファイル構造に関わる情報が主に入っています。
ここではデータの読み書きに必要な分だけを紹介します。
位置と長さはバイト単位。位置はそれぞれの値の開始位置のことです。
位置 | 長さ | 名前 | 説明 |
---|---|---|---|
0x00 | 100 | headername | 固定 |
0x8C | 2 | version | 辞書のバージョン。6.10では0x060A です。メジャーバージョン( 0x06 の部分)が同じなら、読み込みはできるそうです。 |
0x94 | 2 | index_block | インデックス部のブロック数 |
0xA0 | 4 | nword | 登録単語数 |
0xA5 | 1 | dictype | 属性 |
0xA8 | 4 | olenumber | 最新のOLEオブジェクト番号 (リンクデータとかするのに多分使います) |
0xB6 | 1 | index_blkbit | インデックス部の物理ブロック番号のビット数 |
0xB8 | 4 | extheader | 拡張ヘッダーの長さ(バイト単位) |
0xBC | 4 | empty_block2 | 先頭空きブロック番号 |
0xC0 | 4 | nindex2 | インデックスの数 |
0xC4 | 4 | nblock2 | データ部のブロック数 |
0xC8 | 8 | cypt | 暗号コード |
0xD8 | 8 | dicident | 辞書識別子 辞書を新規作成する時に生成された8バイトの乱数 |
headername
の内容は
" =============== Dictionary for PDIC =============== "
です。(なんか1つだけShift_JISの全角スペース)
ここで書いた「名前」は、変数のような感じで後の説明で使います。
最初の256バイトにだけ情報が入っています。ヘッダー部全体は1024バイトなので、残り768バイトは余白です。
属性には以下のフラグを含みます。
0x01
バイナリの圧縮をしている (圧縮されたデータがなくてもtrue
になります。圧縮されたデータを含む場合は必ずtrue
です)0x08
BOCU-1でエンコードしている (多分必ずこのフラグはtrue
です)0x40
暗号化している (暗号化の方法はよく分かりません)
ヘッダー部の次、つまりファイルの先頭から1024バイトの地点から始まります。
長さはextheader
バイトです。大抵は0バイトです。
中身は多分読まなくていいです。
拡張ヘッダー部の次、つまりファイルの先頭から1024 + extheader
バイトの地点から始まります。
長さは1024*index_block
バイトです。
データ部の物理ブロックについての情報(インデックス要素といいます)が、順番にnindex2
個記録されています。
そしてインデックス要素を並べた後、最後に
長さ | 値 |
---|---|
4 | 0x00000000 |
が置かれ、これ以上インデックス要素がないことを表します。
インデックス部の長さは1024の倍数
バイトなので、余ったりします。(ちなみにPDIC/Unicodeでは、インデックス部の長さは初期状態で1024*16
です。後でインデックスが増えて伸びると、後に続くデータ部がずれて面倒だから多めにしてあります。1単語しか入ってない辞書とか作ると、16000バイト以上の余白ができます。)
長さ | 値 |
---|---|
2 or 4 | 物理ブロック番号。index_blkbit が0 だと2バイト、1 だと4バイト |
可変 | そのブロックの中で最初の単語の見出し語(BOCU-1) |
1 | 0x00 。見出し語の長さは可変なので、0x00 が終了の目印になります。 |
インデックス部の次、つまりファイルの先頭から1024 + extheader + 1024*index_block
バイトの地点から始まります。
データブロックが並んで入っています。
それぞれのデータブロックには物理ブロックIDがついています。
それぞれのデータブロックの開始位置は、ファイルの先頭から1024 + extheader + 1024*index_block + 1024*(物理ブロック番号)
バイトの地点になります。
長さ | 値 |
---|---|
2 | ブロックの長さ |
可変 | 単語データ(複数) |
2 or 4 | 0x0000 or 0x00000000 |
ブロックの長さは、フィールド長サイズのフラグ0x8000
を含んでいます。
実際のブロックの長さは、最上位以外のビットを使います。((ブロックの長さ) & 0x7FFF
)
データブロック内で、長さが「2 or 4」となっているものは、フィールド長サイズのフラグがfalse
なら2バイト、true
なら4バイトになります。またtrue
の場合はひとつのデータブロックにひとつしか単語を含むことができません。
ひとつのデータブロックの長さは、1024*(ブロックの長さ)
バイトです。つまり、長さが2以上だと物理ブロックIDに欠番がでます。
データブロックも、長さ1024の倍数
バイトなので、余白があります。検索・編集時のパフォーマンスのため、余白はデータブロックのうちある程度の割合を占めるように調整されます。
長さ | 値 |
---|---|
2 or 4 | 長さ |
1 | 見出し語省略バイト数 |
1 | 属性。 |
可変 | 見出し語(BOCU-1) |
1 | 0x00 |
可変 | 訳語(BOCU-1) |
長さは見出し語から単語データの最後までのバイト数です。最初の2+1+1バイトは含みません。
属性の下位4ビット((属性) & 0x0F
)で、単語レベルを表します。
0x00
なし0x01
〜0x0F
1 〜 15
上位4ビットは以下のフラグを含みます。
0x10
拡張構成0x20
暗記必須マーク (≠暗記マーク)0x40
修正マーク
属性が0xFF
の場合は「リファレンス登録語」という、既に廃止された機能の項目らしいです。飛ばしましょう。
暗記必須マークは Tools > 設定 > 動作環境 > 「暗記必須単語」機能を使う をチェックしたときだけ編集できます。これは過去バージョンのように使いたい人向けで、非推奨みたいです。
チェックしていない時(デフォルト)で編集できる暗記マークは、辞書ではなく単語帳ファイル(Bookmark.txt
)に保存されます。辞書提供者が作るマークではなくて、辞書利用者が利用するためのマークということですね。
ひとつ前の単語の見出し語と、最初のnバイトが一致している時、そのnを「見出し語省略バイト数」に入れて、見出し語には「最初のnバイト分を削ったもの」を入れます。
ブロック内で最初の単語の場合は、見出し語省略バイト数は0です。そして見出し語にはそのままの文字列が入ります。
例:
見出し語 | 見出し語(BOCU-1) | 省略バイト数 | 記録される見出し語 |
---|---|---|---|
"aaa\taaa" |
b1 b1 b1 09 b1 b1 b1 |
0 |
b1 b1 b1 09 b1 b1 b1 |
"aabb\taabb" |
b1 b1 b2 b2 09 b1 b1 b2 b2 |
2 |
b2 b2 09 b1 b1 b2 b2 |
"abc\tabc" |
b1 b2 b3 09 b1 b2 b3 |
1 |
b2 b3 09 b1 b2 b3 |
"abcd\tABCD" |
b1 b2 b3 b4 09 91 92 93 94 |
3 |
b4 09 91 92 93 94 |
見出し語と訳語以外の記述を含む場合は、拡張構成になります。
長さ | 値 |
---|---|
2 or 4 | 長さ |
1 | 見出し語省略バイト数 |
1 | 属性。 |
可変 | 見出し語(BOCU-1) |
1 | 0x00 |
可変 | 訳語(BOCU-1) |
1 | 0x00 |
可変 | 拡張データ(複数) |
1 | 0x80 |
最初の1バイトが拡張データの属性を表します。
下位4ビット((属性) & 0x0F
)で、データの内容の種類を表します。
0x01
用例0x02
発音記号0x04
リンクデータ (外部ファイルへのリンクやファイル埋め込みなど)
上位4ビットは以下のフラグを含みます。
0x10
バイナリデータ0x40
圧縮データ 圧縮データは必ずバイナリデータなので、実際には0x40
単体では使いません。必ず0x50
になります。
拡張データの構造は、バイナリデータかどうかで変わります。
長さ | 値 |
---|---|
1 | 属性 |
可変 | 内容(BOCU-1) |
1 | 0x00 |
長さ | 値 |
---|---|
1 | 属性 |
2 or 4 | 内容の長さ |
内容の長さ バイト |
内容 |
データの圧縮方法は独自実装のRangeCoderというものらしいです。
圧縮とリンクデータの型式は省略します。
長さ | 値 |
---|---|
2 | 0x0000 |
4 | 次の空きブロックの物理ブロックID |
最初の空きブロックの物理ブロックIDはempty_block2
で示されます。
それ以降順番に、次の空きブロックの物理ブロックID
でつながっていきます。
最後の空きブロックでは、次の空きブロックの物理ブロックID
が0xFFFFFFFF
になります。
空きブロックが一つもない場合は、empty_block2
が0xFFFFFFFF
になります。