Skip to content

Instantly share code, notes, and snippets.

@miau
Created August 14, 2010 20:25
Show Gist options
  • Save miau/524683 to your computer and use it in GitHub Desktop.
Save miau/524683 to your computer and use it in GitHub Desktop.
# -*- coding: utf-8 -*-
$KCODE = 'u'
# Excel の罫線確認用に HTML に吐き出す(Excel データ取得→YAML として吐き出す部分)
# 文字コード変換を簡単に行うためのヘルパメソッド
#
# 基本的に UTF-8 で統一したいが、ファイルシステムとのやりとりは Shift_JIS なので。
# "あいう".sjis で Shift_JIS に、"あいう".utf8 で UTF-8 に変換する
require 'nkf'
class String
def sjis; NKF.nkf('-Ws', self); end
def utf8; NKF.nkf('-Sw', self); end
end
# 入出力ファイルの設定
xls_file = "#{Dir.pwd.utf8}/xls2html_simple.xls"
yml_file = "#{Dir.pwd.utf8}/xls2html.yml"
require 'win32ole'
WIN32OLE.codepage = WIN32OLE::CP_UTF8
# xlXXX 系の定数を使うためのおまじない。
# xlXXX は Excel::XlXXX として利用できるようになる。(先頭は大文字になるので注意)
module Excel
end
WIN32OLE.const_load('Microsoft Excel 11.0 Object Library', Excel)
# Excel オブジェクトの準備
excel = WIN32OLE.new('Excel.Application')
# Excel を表示していた場合、スクロールが発生すると動作が非常に重くなるのでので気を付けること
excel.visible = TRUE
book = excel.Workbooks.Open(
xls_file,
false, # UpdateLinks: 更新なし
true # ReadOnly: true
)
book.Activate
data = []
1.upto(book.Worksheets.Count){|i|
sheet = book.Worksheets.Item(i)
puts "\t" + sheet.Name
sheet.Activate
# 末尾のセルを取得しておく
last_cell = excel.ActiveCell.SpecialCells(Excel::XlLastCell);
max_row, max_column = last_cell.Row, last_cell.Column
# 末尾のセルの右下を一時セルとして利用する
tmp_cell = sheet.Cells(max_row + 1, max_column + 1)
i = 0
sheet.Range(sheet.Cells(1, 1), last_cell).Cells.each{|cell|
# 罫線の状態を素直に取得すると隣接セルの情報も拾ってしまうので、
# 一旦右下の利用されていないセルにコピーする
cell.Select
excel.Selection.Copy
tmp_cell.Select
excel.ActiveSheet.Paste
data << [] if i % max_column == 0
width = tmp_cell.MergeArea.Columns.Count
height = tmp_cell.MergeArea.Rows.Count
# 罫線については MergeArea から拾わないと、XlEdgeRight が異なることがあったので注意
data.last << {
:value => tmp_cell.Value,
:width => width,
:height => height,
:left => tmp_cell.MergeArea.Borders(Excel::XlEdgeLeft ).LineStyle,
:top => tmp_cell.MergeArea.Borders(Excel::XlEdgeTop ).LineStyle,
:bottom => tmp_cell.MergeArea.Borders(Excel::XlEdgeBottom).LineStyle,
:right => tmp_cell.MergeArea.Borders(Excel::XlEdgeRight ).LineStyle,
:left_weight => tmp_cell.MergeArea.Borders(Excel::XlEdgeLeft ).Weight,
:top_weight => tmp_cell.MergeArea.Borders(Excel::XlEdgeTop ).Weight,
:bottom_weight => tmp_cell.MergeArea.Borders(Excel::XlEdgeBottom).Weight,
:right_weight => tmp_cell.MergeArea.Borders(Excel::XlEdgeRight ).Weight,
}
i += 1
# マージされたセルがコピーされたままだと前の罫線が残る?ようなので
# 結合を解除しておく。
if width > 1 || height > 1
excel.Selection.UnMerge
end
}
}
book.close(0) # SaveChanges: false
# YAML に出力する
require 'yaml'
File.open(yml_file.sjis, 'wb'){|f|
f.write data.to_yaml
}
excel.visible = TRUE
# -*- coding: utf-8 -*-
$KCODE = 'u'
# Excel の罫線確認用に HTML に吐き出す(YAML からデータを取り出して HTML を出力する部分)
# 文字コード変換を簡単に行うためのヘルパメソッド
#
# 基本的に UTF-8 で統一したいが、ファイルシステムとのやりとりは Shift_JIS なので。
# "あいう".sjis で Shift_JIS に、"あいう".utf8 で UTF-8 に変換する
require 'nkf'
class String
def sjis; NKF.nkf('-Ws', self); end
def utf8; NKF.nkf('-Sw', self); end
end
# 入出力ファイルの設定
yml_file = "#{Dir.pwd}/xls2html.yml"
html_file = "#{Dir.pwd}/xls2html.html"
require 'yaml'
require "cgi"
require 'win32ole'
WIN32OLE.codepage = WIN32OLE::CP_UTF8
# xlXXX 系の定数を使うためのおまじない。
# xlXXX は Excel::XlXXX として利用できるようになる。(先頭は大文字になるので注意)
module Excel
end
WIN32OLE.const_load('Microsoft Excel 11.0 Object Library', Excel)
# YAML ファイルの読み込み
yaml = File.open(yml_file.sjis, 'rb'){|f|
f.read
}
rows = YAML::load(yaml)
# Excel の罫線情報を CSS のどの class に割り当てるかのマッピング
$css_class = {
Excel::XlContinuous => {
Excel::XlHairline => "hairline",
Excel::XlThin => "continuous1",
Excel::XlMedium => "continuous2",
Excel::XlThick => "continuous3",
},
Excel::XlDash => {
Excel::XlThin => "dash1",
Excel::XlMedium => "dash2",
},
Excel::XlDashDot => {
Excel::XlThin => "dash_dot1",
Excel::XlMedium => "dash_dot2",
},
Excel::XlDashDotDot => {
Excel::XlThin => "dash_dot_dot1",
Excel::XlMedium => "dash_dot_dot2",
},
Excel::XlDot => "dot",
Excel::XlDouble => "double",
Excel::XlLineStyleNone => "none",
Excel::XlSlantDashDot => "slant_dash_dot",
}
# カラムの罫線情報から、適切な CSS のクラスを返す
def get_class(column)
%w(left top bottom right).map{|edge|
css_class = $css_class[column[edge.to_sym]]
if css_class.class == Hash
# LineStyle で決まらない場合は Weight 配列で格納されている
css_class = css_class[column["#{edge}_weight".to_sym]]
end
edge + "-" + css_class
}.join(" ")
end
# カラムの結合情報から、適切な colspan、rowspan の記述を返す
def get_spans(column)
spans = ""
spans += %Q( rowspan="#{column[:height]}") if column[:height] > 1
spans += %Q( colspan="#{column[:width]}") if column[:width] > 1
spans
end
#-------------------
# HTML の書き出し
#-------------------
f = File.open(html_file.sjis, 'wb')
# ヘッダの出力
f.puts <<HEADER;
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style type="text/css">
body { background-color: whitesmoke; }
td { background-color: white; }
HEADER
# CSS のメイン部分出力
# left、top、bottom、right それぞれの class を定義しておく
# (HTML を単純化するために全体を出力する class も定義しているが、未使用)
["", "left-", "top-", "bottom-", "right-"].each{|edge|
f.puts <<-STYLES;
td.#{edge}none { border-#{edge}width: 1; border-#{edge}style: none; border-#{edge}color: black; }
td.#{edge}hairline { border-#{edge}width: 1; border-#{edge}style: solid; border-#{edge}color: gray; }
td.#{edge}dot { border-#{edge}width: 1; border-#{edge}style: dotted; border-#{edge}color: black; }
td.#{edge}dash_dot_dot1 { border-#{edge}width: 1; border-#{edge}style: groove; border-#{edge}color: black; }
td.#{edge}dash_dot1 { border-#{edge}width: 1; border-#{edge}style: ridge; border-#{edge}color: black; }
td.#{edge}dash1 { border-#{edge}width: 1; border-#{edge}style: dashed; border-#{edge}color: black; }
td.#{edge}continuous1 { border-#{edge}width: 1; border-#{edge}style: solid; border-#{edge}color: black; }
td.#{edge}dash_dot_dot2 { border-#{edge}width: 2; border-#{edge}style: groove; border-#{edge}color: black; }
td.#{edge}slant_dash_dot { border-#{edge}width: 2; border-#{edge}style: ridge; border-#{edge}color: black; }
td.#{edge}dash_dot2 { border-#{edge}width: 2; border-#{edge}style: dotted; border-#{edge}color: black; }
td.#{edge}dash2 { border-#{edge}width: 2; border-#{edge}style: dashed; border-#{edge}color: black; }
td.#{edge}continuous2 { border-#{edge}width: 2; border-#{edge}style: solid; border-#{edge}color: black; }
td.#{edge}continuous3 { border-#{edge}width: 3; border-#{edge}style: solid; border-#{edge}color: black; }
td.#{edge}double { border-#{edge}width: 3; border-#{edge}style: double; border-#{edge}color: black; }
STYLES
}
f.puts <<BODY_HEADER;
</style>
</head>
<body>
<table>
BODY_HEADER
# テーブルの出力
merged_cells = {} # マージされたセルの座標を格納する二次元ハッシュ。merged_cells[row][column] = true
rows.each_with_index{|row, r|
f.puts "<tr>"
row.each_with_index{|column, c|
# マージされたセルであればスキップ
next if merged_cells.has_key?(r) && merged_cells[r].delete(c)
# css の class を取得
css_class = get_class(column)
# colspan、rowspan を取得
spans = get_spans(column)
if spans != ""
# マージされたセルを記録しておく
column[:height].times{|dy|
merged_cells[r + dy] = {} if !merged_cells.has_key?(r + dy)
column[:width].times{|dx|
merged_cells[r + dy][c + dx] = true
}
}
end
f.puts %(<td class="#{css_class}"#{spans}>)
if column[:value].nil?
# 空のセルは border が表示されるように &nbsp; を出力しておく
f.puts "&nbsp;"
else
f.puts CGI.escapeHTML(column[:value].to_s)
end
f.puts "</td>"
}
f.puts "</tr>"
}
f.puts <<FOOTER;
</table>
</body>
FOOTER
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment