入力データの整理
csv を読み込んで作成した入力データを整理して、出力に必要なデータを作成します。
入力データを for でまわし、文字列データの重複チェック&オフセットの計算、
およびラベルの重複エラーチェックを行おうと思ったのですが、割と複雑になってしまうので
二つの関数に分けることにしました。まずはコードをコメントで書き出してみました。
# 2. 入力データを順に取り出し、各文字列データのサイズに基づいて情報を整理する # 2.1. ラベルに対応するオフセット配列のためのデータ作成 # ・最終的に必要なデータ : ラベル→文字列オフセットが取り出しやすいデータ # ・同じ文字列に対するオフセットは同じ値になる(重複をまとめる) # ・この関数で作成したデータを使用して最終的な出力を行う # data : (ラベル, 文字列データ)のタプルのリスト # return 文字列→オフセットの辞書 def offset_from_data(data): # オフセットを 0 で初期化 # 文字列→オフセットのための空の辞書を作成 # for 各データ # if 文字列が既出でなければ # 文字列に対応するオフセットを辞書に記録する # オフセットを文字列データのサイズ分進める # end if # end for # 文字列→オフセット辞書を返す # 2.2. ラベルを enum 形式で出力 # file : 出力先 # data : (ラベル, 文字列データ)のタプルのリスト # enum : 列挙名に使う文字列 def output_label_enum(file,data,enum=""): # if file がファイルオブジェクトでなければ # file をファイル名として開く # end if # ヘッダ ( enum 定義 ) を出力 # ラベル重複チェック用のカウンタとして辞書を作成 # for 各データ # if ラベルが既出ならば # エラーを出力 # end if # ラベルのカウントを増やす # ラベルを出力 # end if # フッタを出力
まず、2.1 の方を埋めてみました。
from StringIO import StringIO # 2.1. ラベルに対応するオフセット配列のためのデータ作成 # ・最終的に必要なデータ : ラベル→文字列オフセットが取り出しやすいデータ # ・同じ文字列に対するオフセットは同じ値になる(重複をまとめる) # ・この関数で作成したデータを使用して最終的な出力を行う # data : (ラベル, 文字列データ)のタプルのリスト # return 文字列→オフセットの辞書 def offset_from_data(data): # オフセットを 0 で初期化 offset=0 # 文字列→オフセットのための空の辞書を作成 table={} buf=StringIO() # for 各データ for d in data: # if 文字列が既出でなければ if not table.has_key(d[1]): # 文字列に対応するオフセットを辞書に記録する table[d[1]]=offset # オフセットを文字列データのサイズ分進める buf.seek(0) buf.write(d[1]) offset+=buf.tell()+1 # 文字列→オフセット辞書を返す buf.close() return table
文字列データのオフセットは、辞書として記録していきます。
重複している文字列データは利用する C++ のプログラム中では同じ実体を参照させたいので、
複数のラベルに同じオフセットの値が対応することになります。
shift-jis の文字列サイズは以前に調べた方法で計算しています。
# テスト用 if __name__=="__main__": data=[("STR_HELLO", "こんにちは。"), ("STR_BYE", "さようなら。"), ("STR_HI", "こんにちは。")] table=offset_from_data(data) for x in data: print "%s : %s -> %d" % (x[0], x[1], table[x[1]])
テスト用プログラムでは重複文字列を含むタプルのリストを作成して offset_from_data を呼び出しています。
STR_HELLO : こんにちは。 -> 0 STR_BYE : さようなら。 -> 13 STR_HI : こんにちは。 -> 0
続いて 2.2. のラベル出力。
# 2.2. ラベルを enum 形式で出力 # file : 出力先 # data : (ラベル, 文字列データ)のタプルのリスト # head : 列挙に先立って出力する文字列 # foot : 列挙の後で出力する文字列 def output_label_enum(file,data,head="",foot=""): # if file がファイルオブジェクトでなければ if not hasattr(file, "write"): # file をファイル名として開く file = open(file, "wb") # ヘッダを出力 file.write(head) # ラベル重複チェック用のカウンタとして辞書を作成 label_count={} # for 各データ it=iter(data) # 最初のデータに対する処理 d=it.next() label_count[d[0]]=1 file.write("%s" % d[0]) # 残りの行に対する処理 for d in it: # if ラベルが既出ならば if label_count.has_key(d[0]): # エラーを出力 print "ラベル %s は重複しています" % d[0] # else else: # ラベルのカウントを 0 で初期化 label_count[d[0]]=0 # ラベルのカウントを増やす label_count[d[0]]+=1 # ラベルを出力 file.write(",\n%s" % d[0]) # フッタを出力 file.write("%s\n" % foot)
まず、引数の enum ですが、いざ実装してみると用途が限定的過ぎる感じなので head, foot に置き換えました。
次に、関数の先頭の「 file がファイルオブジェクトでなければ」という部分ですが、
これは既に開いてあるファイルの途中に挿入することも、新たなファイル名を指定して
そこに出力させることも出来るようになっています。 Python のライブラリの、 ElementTree のソースを参考にしました。
C++ のコンパイラによっては最後の列挙子の後ろにカンマがあるとコンパイル時に警告を発します。そのため単純に
for d in data: file.write("%s,\n" % d[0])
とすることが出来ません。代わりに、「前の行に対するカンマ+改行」+「現在の行のラベル」という出力をしています。
ただし、最初の行のみ「前の行に対するカンマ+改行」は不要です。そこで data をイテレータに変換して、
最初の行のみ特別に処理をして残りを for で処理しています。
StringIO オブジェクトを利用して以下のようなテストコードを試してみました。
# テスト用 if __name__=="__main__": data=[("STR_HELLO", "こんにちは。"), ("STR_BYE", "さようなら。"), ("STR_HI", "こんにちは。")] f=StringIO() output_label_enum(f,data) f.seek(0) for x in f.readlines(): print x.rstrip() f.close() print "----------" f=StringIO() output_label_enum(f,data,head="enum {\n",foot="\n};") f.seek(0) for x in f.readlines(): print x.rstrip() f.close() print "----------" f=StringIO() output_label_enum(f,data,head="enum EStrTbl {\n",foot=",\nscSTR_LABEL_COUNT //<! ラベルの数\n};") f.seek(0) for x in f.readlines(): print x.rstrip() f.close()
STR_HELLO, STR_BYE, STR_HI ---------- enum { STR_HELLO, STR_BYE, STR_HI }; ---------- enum EStrTbl { STR_HELLO, STR_BYE, STR_HI, scSTR_LABEL_COUNT //<! ラベルの数 };