- Ruby でエクセルファイルを読み込みたい
CONTENTS
APPENDIX
Roo を使用してエクセルファイルを読み込む
エクセルを読む gem は色々とあるが、今回は Roo を使用することにした。
基本的には以下のようにすれば良い。
require "roo" xlsx = Roo::Excelx.new("sheet.xlsx") xlsx.sheet("Sheet1").each do |row| row[0] # => first cell end
each にオプションを渡すことで、特定の行をヘッダとして解釈して Hash として行を取り出すこともできる。
ハッシュでヘッダを指定
ヘッダ行の値に重複がない場合は、 Hash でヘッダのキーを指定することができる。
header = { item_name: "商品名", item_price: "価格", } sheet = xlsx.sheet("Sheet1") sheet.each(header) do |row| if row != header row[:item_name] # => 「商品名」のセル end end
each の中にはヘッダ行も渡される。
配列でヘッダを指定
ヘッダ行として使用する行の中に重複する値をもつセルがある場合、 Hash で指定する方法は使用できない。 (最初に現れた方の値になる)
配列で指定する方法は Roo では提供されていないので、自前で用意する。
header = [ [:data1, "データ"], [:data2, "データ"], [:data3, "データ"], [:data4, "データ"], ] map = nil sheet.each do |row| unless map map = to_header_map(row) else data = map.map{|i,key| [key,row[i]]}.to_h data[:data1] # => 最初の「データ」 end end unless map raise Roo::HeaderRowNotFoundError end def to_header_map(row) header_index = 0 map = nil row.each_with_index do |value,i| if header_match?(@header[header_index].last,value) map ||= [] map << [i,@header[header_index].first] header_index += 1 end end map end def header_match?(title,value) if title.start_with?("~") Regexp.new(title[1..-1]).match?(value) else title == value end end
xls 形式のファイルを読み込む
roo-xls を使用することで、同じ API で xls 形式のファイルを読み込むことができる。
require "roo" require "roo-xls" xls = Roo::Excel.new("sheet.xls") xls.sheet("Sheet1").each do |row| row[0] # => first cell end
まとめ
roo を使用することで xlsx や xls ファイルを、同じ API で読むことができる。 (この記事では紹介しなかったが、 Google Spreadsheet も読める模様)
参考資料
ExcelParser 全体
class ExcelParser def initialize(file:,sheet:,header:,require_cols:,exclude_data:) @sheet = parser(file).sheet(sheet) @header = header @require_cols = require_cols @exclude_data = exclude_data end def each(&block) case @header when Hash @sheet.each(@header) do |data| if data != @header out data, block end end when Array map = nil @sheet.each do |row| unless map map = to_header_map(row) else data = map.map{|i,key| [key,row[i]]}.to_h out data, block end end unless map raise Roo::HeaderRowNotFoundError end end end private def parser(input) case File.extname(input) when ".xls" Roo::Excel else Roo::Excelx end.new(input) end def to_header_map(row) header_index = 0 map = nil row.each_with_index do |value,i| if header_match?(@header[header_index].last,value) map ||= [] map << [i,@header[header_index].first] header_index += 1 end end map end def header_match?(title,value) if title.start_with?("~") Regexp.new(title[1..-1]).match?(value) else title == value end end def out(data,block) if isValidData?(data) block.call(data) end end def isValidData?(data) if !@require_cols || !@require_cols.all?{|col| data[col]} return false end if (@exclude_data || []).any?{|k,v| data[k] == v} return false end return true end end