forked from ruport/ruport-util
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added support to load an excel file with the Kernel method Table. Exa…
…mple: Table(excelfile.xls) git-svn-id: http://stonecode.svnrepository.com/svn/ruport-util/trunk@40 8bb0c7d9-1235-0410-a803-a4cf5c4fc5c0
- Loading branch information
wes
committed
Jan 31, 2008
1 parent
c3aba2e
commit 643a978
Showing
4 changed files
with
373 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
require 'ruport' | ||
|
||
# === Overview | ||
# | ||
# This class extends the core class Ruport::Data::Table and adds support for loading Excel | ||
# spreadsheet files using roo. The idea is to get data from speadsheets that may contain | ||
# already calculated values entered by non-programmers. | ||
# | ||
# Once your data is in a Table object, it can be manipulated | ||
# to suit your needs, then used to build a report. | ||
# | ||
# Copyright (C) 2008, Wes Hays | ||
# All Rights Reserved. | ||
# | ||
class Ruport::Data::Table | ||
|
||
# === Overview | ||
# | ||
# This module provides facilities for creating tables from Excel spreadsheet file (xls). | ||
# | ||
module FromXLS | ||
# Loads a xls file directly into a Table using the roo library. | ||
# | ||
# Example: | ||
# | ||
# # Load data from an Excel xls file with defaults | ||
# table = Table.load_xls('myspreadsheet.xls') | ||
# | ||
# # do not assume the data has column names - default is false. | ||
# table = Table.load_xls('myspreadsheet.xls',{:has_column_names => false}) | ||
# | ||
# # Select sheet - default is the first sheet. | ||
# table = Table.load_xls('myspreadsheet.xls', {:select_sheet => 1}) | ||
# | ||
# # Start row - default is the first row. Use this to override where | ||
# the first row should start. | ||
# table = Table.load_xls('myspreadsheet.xls', {:start_row => 1}) | ||
# | ||
def load_xls(xls_file, options={}) | ||
get_table_from_xls_file(xls_file, options) | ||
end | ||
|
||
# Creates a Table from an Excel object (from roo library). | ||
# | ||
# Example: | ||
# | ||
# # parse excel object with defaults. | ||
# table = Table.parse_xls(excel_object) | ||
# | ||
# # do not assume the data has column names. | ||
# table = Table.parse_xls(excel_object,{:has_column_names => false}) | ||
# | ||
# # Select sheet - default is the first sheet. | ||
# table = Table.parse_xls(excel_object, {:select_sheet => 1}) | ||
# | ||
# # Start row - default is the first row. Use this to override where | ||
# the first row should start. | ||
# table = Table.parse_xls('myspreadsheet.xls', {:start_row => 1}) | ||
# | ||
def parse_xls(xls_object, options={}) | ||
get_table_from_xls(xls_object, options) | ||
end | ||
|
||
private | ||
|
||
def get_table_from_xls_file(xls_file, options) #:nodoc: | ||
require 'roo' | ||
oo = Excel.new(xls_file) | ||
get_table_from_xls(oo, options) | ||
end | ||
|
||
def get_table_from_xls(oo, options) #:nodoc: | ||
options = {:has_column_names => true, | ||
:select_sheet => oo.sheets.first, | ||
:start_row => 0}.merge(options) | ||
oo.default_sheet = options[:select_sheet] | ||
|
||
options[:start_row] = options[:start_row].to_i + 1 unless options[:start_row].nil? | ||
start_row = options[:start_row] | ||
|
||
raise 'start_row must be greater than or equal to zero' if options[:start_row].to_i < 0 | ||
|
||
last_row_index_zero = oo.last_row - 1 | ||
raise "start_row must be less than or equal to #{last_row_index_zero}" if !oo.last_row.nil? and | ||
(options[:start_row].to_i > oo.last_row) | ||
|
||
table = self.new(options) do |feeder| | ||
|
||
if options[:has_column_names] == true | ||
feeder.data.column_names = oo.row(start_row) | ||
start_row = start_row + 1 | ||
end | ||
|
||
unless oo.last_row.nil? | ||
start_row.upto(oo.last_row) do |row| | ||
tempArr = [] | ||
1.upto(oo.last_column) do |col| | ||
tempArr << oo.cell(row,col) | ||
end | ||
feeder << tempArr | ||
end | ||
end | ||
|
||
end | ||
|
||
return table | ||
end | ||
|
||
end | ||
|
||
extend FromXLS | ||
|
||
end | ||
|
||
|
||
module Kernel | ||
|
||
alias :RuportTableMethod2 :Table | ||
|
||
# Updates the Ruport interface for creating Data::Tables with | ||
# the ability to pass in a XLS file or Roo Excel object. | ||
# | ||
# t = Table("myspreadsheet.xls") | ||
# t = Table("myspreadsheet.xls", :has_column_names => true) | ||
def Table(*args,&block) | ||
table= | ||
case(args[0]) | ||
when /\.xls/ | ||
Ruport::Data::Table.load_xls(*args) | ||
else | ||
RuportTableMethod2(*args,&block) | ||
end | ||
|
||
return table | ||
end | ||
end |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,236 @@ | ||
# Copyright (C) 2008, Wes Hays | ||
# All Rights Reserved. | ||
|
||
require 'test/helper' | ||
testcase_requires 'roo' | ||
|
||
describe 'Ruport::Data::TableFromXLS' do | ||
before(:each) do | ||
@xls_file = 'test/samples/people.xls' | ||
@csv_file = 'test/samples/data.csv' | ||
|
||
@xls_file_column_names = %w(Name Age DOB) | ||
# This test will pass once Spreadsheet and Roo support | ||
# formulas in an excel file. | ||
# @rows = [ ['Andy', 27.0, Date.parse('01/20/1980')], | ||
# ['Bob', 26.0, Date.parse('02/11/1981')], | ||
# ['Charlie', 20.0, Date.parse('03/14/1987')], | ||
# ['David', 73.0, Date.parse('04/26/1997')] ] | ||
|
||
# Delete this once Roo supports formulas in an excel file. | ||
@rows = [ ['Andy', 27.0, Date.parse('01/20/1980')], | ||
['Bob', 26.0, Date.parse('02/11/1981')], | ||
['Charlie', 20.0, Date.parse('03/14/1987')], | ||
['David', nil, Date.parse('04/26/1997')] ] | ||
|
||
|
||
@xls_file_column_names2 = %w(Name Age Pet_Type) | ||
# This test will pass once Spreadsheet and Roo support | ||
# formulas in an excel file. | ||
# @rows2 = [ ['Tigger', 3.0, 'Cat'], | ||
# ['Chai', 4.0, 'Dog'], | ||
# ['Rusky', 6.0, 'Dog'], | ||
# ['Sam', 13.0, 'Dog'] ] | ||
|
||
# Delete this once Roo supports formulas in an excel file. | ||
@xls_file_column_names2 = %w(Name Age Pet_Type) | ||
@rows2 = [ ['Tigger', 3.0, 'Cat'], | ||
['Chai', 4.0, 'Dog'], | ||
['Rusky', 6.0, 'Dog'], | ||
['Sam', nil, 'Dog'] ] | ||
end | ||
|
||
# ==== File check ==== | ||
# Raise error if file is not found | ||
it "should raise if xls file is not found" do | ||
lambda do | ||
Ruport::Data::Table.load_xls('people.xls') | ||
end.should raise_error | ||
end | ||
|
||
# Raise error if file is not found | ||
it "shouldn't raise if xls file exists" do | ||
lambda do | ||
Ruport::Data::Table.load_xls(@xls_file) | ||
end.should_not raise_error | ||
end | ||
|
||
|
||
# ==== Constructor check ==== | ||
it "shouldn't be nil if a xls file is passed" do | ||
table = Table(@xls_file) | ||
table.should_not be_nil | ||
end | ||
|
||
it "shouldn't be nil if a Excel object is passed" do | ||
oo = Excel.new(@xls_file) | ||
oo.default_sheet = oo.sheets.first | ||
table = Table(oo) # This will be passed to the base Ruport::Data::Table class. | ||
table.should_not be_nil | ||
end | ||
|
||
it "shouldn't be nil if a Ruport::Data::Table parameter is passed" do | ||
table = Table(@csv_file) # Pass cs file | ||
table.should_not be_nil | ||
end | ||
|
||
|
||
# ==== Constructor check with options params ==== | ||
it "shouldn't be nil if a xls file is passed with options params" do | ||
table = Table(@xls_file, {:has_column_names => false}) | ||
table.should_not be_nil | ||
end | ||
|
||
it "shouldn't be nil if a Excel object is passed with options params using parse_xls method" do | ||
oo = Excel.new(@xls_file) | ||
oo.default_sheet = oo.sheets.first | ||
table = Ruport::Data::Table.parse_xls(oo, {:has_column_names => false}) | ||
table.should_not be_nil | ||
end | ||
|
||
it "shouldn't be nil if a Ruport::Data::Table parameter is passed with options params" do | ||
table = Table(@csv_file, {:has_column_names => false}) # Pass cs file | ||
table.should_not be_nil | ||
end | ||
|
||
it "should raise if start_row is less than zero" do | ||
lambda do | ||
Table(@xls_file, {:start_row => -2}) | ||
end.should raise_error | ||
end | ||
|
||
it "should raise if start_row is greater than the number of rows (starting at 0) in the spreadsheet" do | ||
lambda do | ||
Table(@xls_file, {:start_row => 20}) | ||
end.should raise_error | ||
end | ||
|
||
|
||
# ==== Table load check ==== | ||
|
||
# Output: | ||
# +-----------------------------+ | ||
# | Name | Age | DOB | | ||
# | Andy | 27.0 | 1980-01-20 | | ||
# | Bob | 26.0 | 1981-02-11 | | ||
# | Charlie | 20.0 | 1987-03-14 | | ||
# | David | 73.0 | 1997-04-26 | | ||
# +-----------------------------+ | ||
it "table should be valid without column names loaded from xls file starting at the row 1 (index 0) - column names will be data" do | ||
# Load data from xls file but do not load column headers. | ||
table = Table(@xls_file, {:has_column_names => false, :start_row => 0}) | ||
table.should_not be_nil | ||
table.column_names.should == [] | ||
|
||
# Add headers to the first position | ||
@rows.insert(0, @xls_file_column_names) | ||
|
||
table.each { |r| r.to_a.should == @rows.shift | ||
r.attributes.should == [0, 1, 2] } | ||
end | ||
|
||
# Output: | ||
# +-----------------------------+ | ||
# | Bob | 26.0 | 1981-02-11 | | ||
# | Charlie | 20.0 | 1987-03-14 | | ||
# | David | 73.0 | 1997-04-26 | | ||
# +-----------------------------+ | ||
it "table should be valid without column names loaded from xls file starting at row 3 (index 2)" do | ||
# Load data from xls file but do not load column headers. | ||
# Will start at Row 3 (index 2): ['Bob', 26.0, Date.parse('02/11/1981')] | ||
table = Table(@xls_file, {:has_column_names => false, :start_row => 2}) | ||
table.should_not be_nil | ||
table.column_names.should == [] | ||
|
||
# The header row has not been included yet so don't worry about that one | ||
# just delete the first row in @rows. | ||
@rows.delete_at(0) # delete ['Andy', 27.0, Date.parse('01/20/1980')] | ||
|
||
table.each { |r| r.to_a.should == @rows.shift | ||
r.attributes.should == [0, 1, 2] } | ||
end | ||
|
||
# Output: | ||
# +-----------------------------+ | ||
# | Name | Age | DOB | | ||
# | Andy | 27.0 | 1980-01-20 | | ||
# | Bob | 26.0 | 1981-02-11 | | ||
# | Charlie | 20.0 | 1987-03-14 | | ||
# | David | 73.0 | 1997-04-26 | | ||
# +-----------------------------+ | ||
it "table should be valid without column names loaded from xls file" do | ||
# Load data from xls file but do not load column headers. | ||
table = Table(@xls_file, {:has_column_names => false}) | ||
table.should_not be_nil | ||
table.column_names.should == [] | ||
|
||
# Add headers to the first position | ||
@rows.insert(0, @xls_file_column_names) | ||
|
||
table.each { |r| r.to_a.should == @rows.shift | ||
r.attributes.should == [0, 1, 2] } | ||
end | ||
|
||
# Output: | ||
# +-----------------------------+ | ||
# | Name | Age | DOB | | ||
# +-----------------------------+ | ||
# | Andy | 27.0 | 1980-01-20 | | ||
# | Bob | 26.0 | 1981-02-11 | | ||
# | Charlie | 20.0 | 1987-03-14 | | ||
# | David | 73.0 | 1997-04-26 | | ||
# +-----------------------------+ | ||
it "table should be valid with column names loaded from xls file" do | ||
# Load data from xls file but do not load column headers. | ||
table = Table(@xls_file) | ||
table.should_not be_nil | ||
table.column_names.should == @xls_file_column_names | ||
|
||
table.each { |r| r.to_a.should == @rows.shift | ||
r.attributes.should == @xls_file_column_names } | ||
end | ||
|
||
# Output: | ||
# +--------------------------+ | ||
# | Name | Age | Pet_Type | | ||
# +--------------------------+ | ||
# | Tigger | 3.0 | Cat | | ||
# | Chai | 4.0 | Dog | | ||
# | Rusky | 6.0 | Dog | | ||
# | Sam | 13.0 | Dog | | ||
# +--------------------------+ | ||
it "table should be valid with column names loaded from xls file using Sheet2" do | ||
# Load data from xls file but do not load column headers. | ||
table = Table(@xls_file, {:select_sheet => 'Sheet2'}) | ||
table.should_not be_nil | ||
table.column_names.should == @xls_file_column_names2 | ||
|
||
table.each { |r| r.to_a.should == @rows2.shift | ||
r.attributes.should == @xls_file_column_names2 } | ||
end | ||
|
||
# Output: | ||
# +--------------------------+ | ||
# | Name | Age | Pet_Type | | ||
# +--------------------------+ | ||
# | Tigger | 3.0 | Cat | | ||
# | Chai | 4.0 | Dog | | ||
# | Rusky | 6.0 | Dog | | ||
# | Sam | 13.0 | Dog | | ||
# +--------------------------+ | ||
it "should be valid if an Excel object is passed using parse_xls method" do | ||
oo = Excel.new(@xls_file) | ||
oo.default_sheet = oo.sheets.first | ||
table = Ruport::Data::Table.parse_xls(oo) | ||
table.should_not be_nil | ||
|
||
table.column_names.should == @xls_file_column_names | ||
|
||
table.each { |r| r.to_a.should == @rows.shift | ||
r.attributes.should == @xls_file_column_names } | ||
end | ||
|
||
end | ||
|
||
|
||
|