#!/usr/local/bin/ruby
# CSV to table(HTML) converter - csv2table.rb
# (c)2000, 2007 by HIROSE, Yuuji
# $Id: csv2table.rb,v 1.4 2007/11/14 14:07:36 yuuji Exp $
# Last Modified Wed Nov 14 23:07:05 2007 on firestorm
=begin
CSVもしくはそれに準ずる(TAB区切りとの併用など)形式で書いた
データファイルを HTMLの に変換する。HTMLテキストからは
SSIによって csv2table.rb を利用することを想定している。
データファイルの先頭行のCSV列がtableの見出し(th)として扱われ、
見出し項目の後ろに :l :c :r をつけると対応する(二行目以降の)カラムが
それぞれ 左寄せ、中央寄せ、右寄せ される。
例:
*元ファイルが純正CSV(sample.csv)
氏名,住所,電話番号:r
慶應太郎,東京都港区,01-2345-6789
山口燃え,山口県防府市,09-9999-8765
↓
./csv2table.rb sample.csv
↓
| 氏名 | 住所 | 電話番号 |
| 慶應太郎 | 東京都港区 |
01-2345-6789 |
| 山口燃え | 山口県防府市 |
09-9999-8765 |
データ区切りのカンマの前後に空白を許したいときは -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要素内で
などと記述しておけば、カラムガイド列が別の色になって判別しやすくなる。
さらに、デフォルトで奇数行の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 " "
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 = "
" end
l.sub!(/^[ \t]+$/, "
")
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 "
\n"
end
# 本体
c = CSV.new
count = 0
print "\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 "
\n"