dataの抽出


古いKaleidaGraphから
かなり古いKaleidaGraphから数値dataを抽出したいという話を聞いたので,そのデータをもらって,構造を解析してみた.それなりに単純な構造をしていたので,データ抽出プログラムをrubyで書いてみた.それがこんな感じ.

qda=ARGV[0]
dat=qda.sub(/\.qda$/i,"_.dat")
d=open(qda,"rb"){|f|f.read}
n=d[0,16].unpack("n*")
c=n[8] # byte size of the last comment
n=n[1] # the number of data
sz=d[0x200,2*n].unpack("n*") # size of each data
tp=d[0x200+2*n,2*n].unpack("n*") # type
names=d[0x200+4*n,40*n].unpack("a40"*n).map(&:strip)
r=d[(0x200+4*n+40*n)..-1]
sz=sz.zip(tp).map{|s,t|
  a,b="g","" # big endian float
  a,b="N","Z21" if t==1 # string
  a,b="G","" if t==2 # big endian 64bit float
  *data,r=r.unpack(a*s+"n"*s+b*s+"a*")
  msk=data[s,s] # mask
  str=data[2*s,s] # string
  data=data[0,s] # numeric
  if t==2 # date or time
    td=r[0,8].unpack("C*")[3]
    data=data.map{|l|(Time.gm(l>3e9?1904:2000)+l).getutc.strftime("%d-%m-%Y")} if td==0 #date
    data=data.map{|l|Time.at(l).getutc.strftime("%H:%M:%S")} if td==1 #12:34:56
    data=data.map{|l|["%02d"]*2*":"%l.divmod(60)} if td==2 #12:34
  end
  r=r[136..-1]
  data=str if t==1
  data
}
mx=sz.map(&:size).max
sz=sz.map{|l|l.fill(0,l.size...mx)}.transpose
open(dat,"w"){|f|
  f.puts "# "+r.gsub(/\x0d\x0a?/,"\n# ") if r.size>0
  f.puts names*", "
  sz.each{|l|f.puts l*", "}
}

まず,引数として与えたファイルを読み込むが,2バイト目からdataの数を,14バイト目からがコメントのサイズを表すようなので,それを取り込む.200hバイトから,それぞれのdataの長さがあり,その次に型だと思われるdataがある.その後に,40バイトずつのdataの名前があり,そのあとにdata本体とマスクだと思われるdataと文字列の場合には21バイドずつの文字列があり,その後にはdataの名前と良くわからないdataが合計136バイトあるようだ.ちなみに,それぞれはbig endianである.さらに,日付の表記は日月年の順番で,内部では秒単位の倍精度浮動小数点で表されているようである.時刻も同様である.ただ,文字列のdataの場合に,data本体には何が保存されているかはよく調べなかった.まあ,特定のversionのKaleidaGraphでセーブしたdataしか変換できないかも知れないが,今回の目的は達成したので,良しとしよう.