#!/usr/local/bin/ruby # CSV to table(HTML) converter - csv2table.rb # (c)2000, 2007 by HIROSE, Yuuji <yuuji@gentei.org> # $Id$ # Last Modified Wed Nov 14 23:07:05 2007 on firestorm =begin CSVもしくはそれに準ずる(TAB区切りとの併用など)形式で書いた データファイルを HTMLの <table> に変換する。HTMLテキストからは SSIによって csv2table.rb を利用することを想定している。 データファイルの先頭行のCSV列がtableの見出し(th)として扱われ、 見出し項目の後ろに :l :c :r をつけると対応する(二行目以降の)カラムが それぞれ 左寄せ、中央寄せ、右寄せ される。 例: *元ファイルが純正CSV(sample.csv) 氏名,住所,電話番号:r 慶應太郎,東京都港区,01-2345-6789 山口燃え,山口県防府市,09-9999-8765 ↓ ./csv2table.rb sample.csv ↓ <table border="0"> <tr><th>氏名</th><th>住所</th><th align="right">電話番号</th></tr> <tr><td>慶應太郎</td><td>東京都港区</td> <td align="right">01-2345-6789</td></tr> <tr><td>山口燃え</td><td>山口県防府市</td> <td align="right">09-9999-8765</td></tr> </table> データ区切りのカンマの前後に空白を許したいときは -s オプション カンマではなくTAB区切りにしたいときは -t オプションを利用する。 また、-e オプションにより特定のカラムの内容を、別カラムから生成すること ができる。利用できるマクロは以下の通り。 $n nカラム目の値に置換される $prev[n] nカラム目の直前の行の値に置換される $sum[n] nカラム目の直前の行までの総和 $diff[n] nカラム目の現在行と直前の行の差 (それぞれ [n] を省略するとカレントカラム指定になる) また、$ を利用したカラムがあると、そのカラムは上記置換をした上で数式 評価が行われる。しがたって、 単価:r,個数:r,金額:r 50,3,$0*$1 20,2,$0*$1 は、 +----+----+----+ |単価|個数|金額| +----+----+----+ | 50| 3| 150| +----+----+----+ | 20| 2| 40| +----+----+----+ という表に変換される。$n の置換は、数式置換が行われるよりも前に行われる ので数式の結果を $n で取り込むことはできないことに注意する。 大きな表で、スクロールさせると項目名が見えなくなる場合は行ガイド、列ガイド を出せる。 -row 20 とすると、20行毎に1行目と同じ行が出る。 -col 30 とすると、30カラムごとに1カラム目と同じ項目が出る(カラムガイド)。なお、 カラムガイドのtd要素には自動的に class="colguide" が付加されるので、HTML head要素内で <style type="text/css"> <!-- th {background: #ff9;} td.colguide {background: #fdf;} --> </style> などと記述しておけば、カラムガイド列が別の色になって判別しやすくなる。 さらに、デフォルトで奇数行のtr要素に class="odd"、偶数行のtr要素に class="even" が付加される。CSSで tr.even {background: #eff;} tr.odd {background: #dff;} などとしておけば1行おきに背景色を変えて見分けやすくすることができる。 その他のオプションは ./csv2table.rb -h で参照。 =end class CSV # ruby-list ML より # (1) csv_splitの本体 def csv_split(source, delimiter = ',') chars = (source+",").split(//)# (A) 文字列を1文字ずつに分解する csv = [] while c = chars.shift# 配列の先頭から1要素取り出し, その # 要素を配列から削除する case c when '"' #" # 配列csvにフィールドを追加. csv.push csv_split_for_quoted_field(chars, delimiter) else # 先ほど取り出した文字を戻す. chars.unshift c csv.push csv_split_for_normal_field(chars, delimiter) end end csv end # (2) 通常のフィールドの処理 def csv_split_for_normal_field(chars, delimiter) field = "" while c = chars.shift case c when delimiter return field else field.concat c # 文字列fieldの最後に文字列cを追加 end end field end # (3) `"'で始まるフィールドの処理 "'` def csv_split_for_quoted_field(chars, delimiter) field = "" while c = chars.shift case c when '"' #" c1 = chars.shift if c1 == delimiter or c1.nil? return field elsif c1 == '"' field.concat '"' else # (B-1) 不正フォーマット field.concat c field.concat c1 end else field.concat c end end # (B-2) 不正フォーマット field end end # 本体ここから require 'kconv' # デフォルト値 $border="0" $numbering=false $strip=false $colalign=[] $tabdelim=false $expand=false $rowguide=0 $colguide=0 def usage print <<_EOU_ #{$0} [options] CSV-files... Options are... -border N Set border width of the table to N -n Enable row numbering -t Accept TAB as a field delimiter -s Strip entry-surrounding white spaces -e Enable expression expansion ($sum[n], $diff[n], $prev[n], $n, and math-exp.) -row N Put heading line each N rows -col N Put column guide each N columns _EOU_ end while /^-([a-z]+)$/no =~ ARGV[0] case $1 when "border" ARGV.shift $border=ARGV[0] when "n" $numbering=true when "s" $strip=true when "t" $tabdelim=true when "e" $expand=true when "row" ARGV.shift $rowguide=ARGV[0].to_i when "col" ARGV.shift $colguide=ARGV[0].to_i when "h" usage; exit 0 end ARGV.shift end def putrow(list, n, elm = "td") cgclass=" class=\"colguide\"" rgclass=" class=\"rowguide\"" rowclass = if n > 0 " class=\"" + (n%2 == 0 ? "even" : "odd") + "\"" else "" end print " <tr#{rowclass}>" if $numbering print "<#{elm} align=\"center\">" if n > 0 then print n else print "番号" end print "</#{elm}>" end i=0 list.each{|l| if /\$\d/ =~ l l.gsub!(/\$(\d+)/){|n| list[$1.to_i]} l.strip! STDERR.print "---------------l=#{l}\n" if $DEBUG begin list[i] = l = (eval l).to_s rescue end STDERR.print "---------------l2=#{list[i]}\n" if $DEBUG end if /^[-0-9. ]+$/ =~ l $diff[i] = l.to_f - $prev[i].to_f if $prev[i] # $prev[i] = l.to_f $sum[i] += l.to_f end i+=1 } if $expand i=0 col0='' list.each{|l| $strip and l.strip! if l <= "" then l = "<br>" end l.sub!(/^[ \t]+$/, "<br>") out = l if $expand if /\$/ =~ l l.gsub!(/(sum|diff|prev)(?!\[)/){|n| n + "[#{i}]"} #if /(sum|diff)(?!\[)/ =~ l then l << "[i]" end print("--- l=[#{l}]\n") if $DEBUG l.gsub!(/\$(sum|diff|prev)\[(\d+)\]/){|x| eval("\$#{$1}[#{$2}]") } print("--- l2=[#{l}]\n") if $DEBUG out = eval("#{l}") # "$sum[i].to_s" end $nprv[i] = out if out.is_a?(Numeric) \ || (out.is_a?(String) && /^[0-9]+\.[0-9]+$/ =~ out) out = (sprintf("%10.2f", out)).to_f end end #STDERR.print("out -> #{out}\n"); col0=out if i==0 tdc = (i==0 ? cgclass : '') if i > 1 && $colguide > 0 && i%$colguide == 0 then print "<#{elm}#{cgclass}#{$colalign[i]}>#{col0}</#{elm}>" end print "<#{elm}#{tdc}#{$colalign[i]}>#{out}</#{elm}>" i+=1 } $prev[0..-1] = $nprv print "</tr>\n" end # 本体 c = CSV.new count = 0 print "<table border=\"#{$border}\">\n" # 一行目は見出し while l=gets l = Kconv::toeuc(l) next if /^$|^\#/ =~ l if $tabdelim l.gsub!(/^\s+/, "") l.gsub!(/\t\s*/, ",") end if count == 0 th = c.csv_split(l.chop!) $sum=Array.new(th.length, 0.0) $diff=Array.new(th.length, 0.0) $prev=Array.new(th.length, 0.0) $nprv=Array.new(th.length, 0.0) i=0 th.each{|e| if /:([lcr])$/io =~ e $colalign[i] = sprintf(" align=\"%s\"", {"l"=>"left", "c"=>"center", "r"=>"right"}[$1]) e.sub!(/:[lcr]$/, "") end i+=1 } putrow(th, 0, "th") else if $rowguide > 0 && count%$rowguide == 0 putrow(th, 0, "th") end putrow(c.csv_split(l.chop!), count) end count+=1 end print "</table>\n"