文字列テーブルデータの出力

前回までで取り出したラベルと文字列データのタプルおよび文字列データのオフセット辞書の情報をまとめて
バイナリデータを出力します。コメントによる擬似コードは以下のようになります。

# 3. 収集したデータを、バイナリデータのフォーマットにあわせて出力する 
# file  : 出力先
# data  : (ラベル, 文字列データ)のタプルのリスト
# table : {文字列データ:オフセット}の辞書
def output_string_table(file,data,table):
  # if file がファイルオブジェクトでなければ
    # file をファイル名として開く
  # ヘッダ部分の情報をまとめる
  # ヘッダを出力する
  # for data の各要素
    # 文字列データに対応するオフセットをファイルの先頭からのオフセットに変換する
    # オフセットを出力する
  # for data の各要素
    # if 文字列データが既に出力されたものの中に含まれていなければ
      # 文字列データを出力する
      # 出力を記録する

順に埋めてみます。

  # if file がファイルオブジェクトでなければ
  if no thasattr(file,"write"):
    # file をファイル名として開く
    file=open(file,"wb")

ここは前回の output_label_enum と同じく、 file がアトリビュート write を持っていなければ
ファイル名として扱い、 open しています。 open が失敗すると例外が投げられますが、これはこの関数の
呼び出し元に処理を任せることにしました。

  # ヘッダ部分の情報をまとめる
  image="4sIIIII"
  signature="STBL"
  version=20070815
  indexOffset=struct.calcsize(image)
  indexCount=len(data)
  stringOffset=indexOffset+struct.calcsize("I")*indexCount
  stringDataSize=0
  for d in data:
    stringDataSize+=(len(d[1])+1)

image は、 struct モジュールにバイナリデータのレイアウトを伝えるための文字列で、
4s は 4 文字の文字列、 I は unsigned int に対応しています。これは以下の C++ の構造体に対応しています。

struct StringTable {
  char  signature[4];   //!< 文字列テーブルデータをあらわす識別子
  ulong version;        //!< バージョンナンバー
  ulong indexOffset;    //!< インデックス(ポインタ配列)のデータ先頭からのバイトオフセット
  ulong indexCount;     //!< インデックスの要素数
  ulong stringOffset;   //!< 文字列データのデータ先頭からのバイトオフセット
  ulong stringDataSize; //!< 文字列データブロックのバイトサイズ
};

というわけで signature 以下、それぞれのデータを設定しています。
識別子の文字列には "STBL" (String TaBLe)、バージョンナンバーには日付から 20070815 を指定してみました。

  # ヘッダを出力する
  file.write(struct.pack(image, signature, version, indexOffset, indexCount, stringOffset, stringDataSize))

まとめたデータを struct.pack でひとつの文字列にし、 file に書き出しています。

  # for data の各要素
  for d in data:
    # 文字列データに対応するオフセットをファイルの先頭からのオフセットに変換する
    offset=table[d[1]]+stringOffset
    # オフセットを出力する
    file.write(struct.pack("I", offset))

文字列データのオフセットテーブルの出力です。辞書 table に含まれているオフセットの値は
先頭を 0 としたものなので、文字列データの先頭オフセットを加算してから出力しています。

  # 文字列をオフセット順に並べてから順に出力
  offset_table=[(table[k],k) for k in table.keys()]
  offset_table.sort()
  for t in offset_table:
    file.write(t[1]+"\0")

{文字列データ:オフセット} の辞書を (オフセット, 文字列データ) のタプルのリストに組み替えてからソートすることで、
要素がオフセット順に並びます。これを順に file に出力しています。出力するときに、後ろにヌル文字を付加しています。


データの取り出し、整理、出力の関数が出来たので、以下のコードで変換処理をテストしてみました。

if __name__=="__main__":
  data=input_from_csv("string_table.csv","日本語")
  table=offset_from_data(data)
  output_string_table("string_table.stbl",data,table)

出力されたバイナリデータは以下のようになりました。

00  header.Signature[0]     53 54 42 4C
04  header.Version          0132419F
08  header.IndexOffset      00000018
0C  header.IndexCount       00000002
10  header.StringDataOffset 00000020
14  header.StringDataSize   0000001A
18  offset[0]               00000020 0000002D
20  string[0][0]            82 B1 82 F1 82 C9 82 BF 82 CD 81 42 00
2D  string[1][0]            82 B3 82 E6 82 A4 82 C8 82 E7 81 42 00

output_string_table は、 offset_from_data と似たような処理を繰り返している部分があったりします。
事前に output_string_table での処理を考慮していなかったので offset_from_data がどんなデータを
返す必要があるかも漏れが出てしまった感じです。
また、出力されるバイナリデータは今のところリトルエンディアンにしか対応していません。
この辺は今後修正が必要です。