spreadsheet 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/GUIDE.txt +209 -0
- data/History.txt +8 -0
- data/LICENSE.txt +619 -0
- data/Manifest.txt +46 -0
- data/README.txt +54 -0
- data/Rakefile +15 -0
- data/lib/parseexcel.rb +27 -0
- data/lib/parseexcel/parseexcel.rb +75 -0
- data/lib/parseexcel/parser.rb +11 -0
- data/lib/spreadsheet.rb +79 -0
- data/lib/spreadsheet/datatypes.rb +99 -0
- data/lib/spreadsheet/encodings.rb +49 -0
- data/lib/spreadsheet/excel.rb +75 -0
- data/lib/spreadsheet/excel/error.rb +26 -0
- data/lib/spreadsheet/excel/internals.rb +322 -0
- data/lib/spreadsheet/excel/internals/biff5.rb +17 -0
- data/lib/spreadsheet/excel/internals/biff8.rb +19 -0
- data/lib/spreadsheet/excel/offset.rb +37 -0
- data/lib/spreadsheet/excel/reader.rb +798 -0
- data/lib/spreadsheet/excel/reader/biff5.rb +22 -0
- data/lib/spreadsheet/excel/reader/biff8.rb +168 -0
- data/lib/spreadsheet/excel/row.rb +67 -0
- data/lib/spreadsheet/excel/sst_entry.rb +45 -0
- data/lib/spreadsheet/excel/workbook.rb +76 -0
- data/lib/spreadsheet/excel/worksheet.rb +85 -0
- data/lib/spreadsheet/excel/writer.rb +1 -0
- data/lib/spreadsheet/excel/writer/biff8.rb +66 -0
- data/lib/spreadsheet/excel/writer/format.rb +270 -0
- data/lib/spreadsheet/excel/writer/workbook.rb +586 -0
- data/lib/spreadsheet/excel/writer/worksheet.rb +556 -0
- data/lib/spreadsheet/font.rb +86 -0
- data/lib/spreadsheet/format.rb +172 -0
- data/lib/spreadsheet/formula.rb +9 -0
- data/lib/spreadsheet/row.rb +87 -0
- data/lib/spreadsheet/workbook.rb +120 -0
- data/lib/spreadsheet/worksheet.rb +215 -0
- data/lib/spreadsheet/writer.rb +29 -0
- data/test/data/test_copy.xls +0 -0
- data/test/data/test_version_excel5.xls +0 -0
- data/test/data/test_version_excel95.xls +0 -0
- data/test/data/test_version_excel97.xls +0 -0
- data/test/excel/row.rb +29 -0
- data/test/font.rb +163 -0
- data/test/integration.rb +1021 -0
- data/test/workbook.rb +21 -0
- data/test/worksheet.rb +62 -0
- metadata +113 -0
@@ -0,0 +1,215 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'spreadsheet/encodings'
|
3
|
+
require 'spreadsheet/row'
|
4
|
+
|
5
|
+
module Spreadsheet
|
6
|
+
##
|
7
|
+
# The Worksheet class. Contains most of the Spreadsheet data in Rows.
|
8
|
+
#
|
9
|
+
# Interesting Attributes
|
10
|
+
# #name :: The Name of this Worksheet.
|
11
|
+
# #default_format:: The default format used for all cells in this Workhseet
|
12
|
+
# that have no format set explicitly or in
|
13
|
+
# Row#default_format.
|
14
|
+
# #rows :: The Rows in this Worksheet. It is not recommended to
|
15
|
+
# Manipulate this Array directly. If you do, call
|
16
|
+
# #updated_from with the smallest modified index.
|
17
|
+
class Worksheet
|
18
|
+
include Encodings
|
19
|
+
attr_accessor :name, :workbook
|
20
|
+
attr_reader :rows
|
21
|
+
include Enumerable
|
22
|
+
def initialize opts={}
|
23
|
+
@dimensions = [0,0,0,0]
|
24
|
+
@name = opts[:name] || 'Worksheet'
|
25
|
+
@workbook = opts[:workbook]
|
26
|
+
@rows = []
|
27
|
+
end
|
28
|
+
##
|
29
|
+
# Add a Format to the Workbook. If you use Row#set_format, you should not
|
30
|
+
# need to use this Method.
|
31
|
+
def add_format fmt
|
32
|
+
@workbook.add_format fmt
|
33
|
+
end
|
34
|
+
##
|
35
|
+
# Get the enriched value of the Cell at _row_, _column_.
|
36
|
+
# See also Worksheet#[], Row#[].
|
37
|
+
def cell row, column
|
38
|
+
row(row)[column]
|
39
|
+
end
|
40
|
+
##
|
41
|
+
# The number of columns in this Worksheet which contain data.
|
42
|
+
def column_count
|
43
|
+
dimensions[3] - dimensions[2]
|
44
|
+
end
|
45
|
+
##
|
46
|
+
# Delete the Row at _idx_ (0-based) from this Worksheet.
|
47
|
+
def delete_row idx
|
48
|
+
res = @rows.delete_at idx
|
49
|
+
updated_from idx
|
50
|
+
res
|
51
|
+
end
|
52
|
+
##
|
53
|
+
# The default Format of this Worksheet, if you have set one.
|
54
|
+
# Returns the Workbook's default Format otherwise.
|
55
|
+
def default_format
|
56
|
+
@default_format || @workbook.default_format
|
57
|
+
end
|
58
|
+
##
|
59
|
+
# Set the default Format of this Worksheet.
|
60
|
+
def default_format= format
|
61
|
+
@default_format = format
|
62
|
+
add_format format if format
|
63
|
+
format
|
64
|
+
end
|
65
|
+
##
|
66
|
+
# Dimensions:: [ first used row, first unused row,
|
67
|
+
# first used column, first unused column ]
|
68
|
+
# ( First used means that all rows or columns before that are
|
69
|
+
# empty. First unused means that this and all following rows
|
70
|
+
# or columns are empty. )
|
71
|
+
def dimensions
|
72
|
+
@dimensions || recalculate_dimensions
|
73
|
+
end
|
74
|
+
##
|
75
|
+
# If no argument is given, #each iterates over all used Rows (from the first
|
76
|
+
# used Row until but omitting the first unused Row, see also #dimensions).
|
77
|
+
#
|
78
|
+
# If the argument skip is given, #each iterates from that row until but
|
79
|
+
# omitting the first unused Row, effectively skipping the first _skip_ Rows
|
80
|
+
# from the top of the Worksheet.
|
81
|
+
def each skip=dimensions[0], &block
|
82
|
+
skip.upto(dimensions[1] - 1) do |idx|
|
83
|
+
block.call row(idx)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
def encoding # :nodoc:
|
87
|
+
@workbook.encoding
|
88
|
+
end
|
89
|
+
##
|
90
|
+
# Insert a Row at _idx_ (0-based) containing _cells_
|
91
|
+
def insert_row idx, cells=[]
|
92
|
+
res = @rows.insert idx, Row.new(self, idx, cells)
|
93
|
+
updated_from idx
|
94
|
+
res
|
95
|
+
end
|
96
|
+
def inspect
|
97
|
+
"#<#{self.class}:#{object_id} "
|
98
|
+
names = instance_variables
|
99
|
+
names.delete '@rows'
|
100
|
+
variables = names.collect do |name|
|
101
|
+
"%s=%s" % [name, instance_variable_get(name)]
|
102
|
+
end.join(' ')
|
103
|
+
sprintf "#<%s:0x%014x %s @rows[%i]>", self.class, object_id,
|
104
|
+
variables, row_count
|
105
|
+
end
|
106
|
+
##
|
107
|
+
# Replace the Row at _idx_ with the following arguments. Like #update_row,
|
108
|
+
# but truncates the Row if there are fewer arguments than Cells in the Row.
|
109
|
+
def replace_row idx, *cells
|
110
|
+
if(row = @rows[idx]) && cells.size < row.size
|
111
|
+
cells.concat Array.new(row.size - cells.size)
|
112
|
+
end
|
113
|
+
update_row idx, *cells
|
114
|
+
end
|
115
|
+
##
|
116
|
+
# The Row at _idx_ or a new Row.
|
117
|
+
def row idx
|
118
|
+
@rows[idx] || Row.new(self, idx)
|
119
|
+
end
|
120
|
+
##
|
121
|
+
# The number of Rows in this Worksheet which contain data.
|
122
|
+
def row_count
|
123
|
+
dimensions[1] - dimensions[0]
|
124
|
+
end
|
125
|
+
##
|
126
|
+
# Tell Worksheet that the Row at _idx_ has been updated and the #dimensions
|
127
|
+
# need to be recalculated. You should not need to call this directly.
|
128
|
+
def row_updated idx, row
|
129
|
+
@dimensions = nil
|
130
|
+
row = @rows[idx] = shorten(row)
|
131
|
+
format_dates row
|
132
|
+
row
|
133
|
+
end
|
134
|
+
##
|
135
|
+
# Updates the Row at _idx_ with the following arguments.
|
136
|
+
def update_row idx, *cells
|
137
|
+
res = if row = @rows[idx]
|
138
|
+
row[0, cells.size] = cells
|
139
|
+
row
|
140
|
+
elsif cells = shorten(cells)
|
141
|
+
Row.new self, idx, cells
|
142
|
+
end
|
143
|
+
row_updated idx, res
|
144
|
+
res
|
145
|
+
end
|
146
|
+
##
|
147
|
+
# Renumbers all Rows starting at _idx_ and calls #row_updated for each of
|
148
|
+
# them.
|
149
|
+
def updated_from idx
|
150
|
+
idx.upto(@rows.size - 1) do |idx|
|
151
|
+
row = row(idx)
|
152
|
+
row.idx = idx
|
153
|
+
row_updated idx, row
|
154
|
+
end
|
155
|
+
end
|
156
|
+
##
|
157
|
+
# Get the enriched value of the Cell at _row_, _column_.
|
158
|
+
# See also Worksheet#cell, Row#[].
|
159
|
+
def [] row, column
|
160
|
+
row(row)[column]
|
161
|
+
end
|
162
|
+
##
|
163
|
+
# Set the value of the Cell at _row_, _column_ to _value_.
|
164
|
+
# See also Row#[]=.
|
165
|
+
def []= row, column, value
|
166
|
+
row(row)[column] = value
|
167
|
+
end
|
168
|
+
private
|
169
|
+
def format_dates row # :nodoc:
|
170
|
+
## If no format is set in a cell which contains a Date or Time, we will
|
171
|
+
# add a format. At the moment, the number formats correspond to builtin
|
172
|
+
# Excel number-formats. We may need to add a level of abstraction if
|
173
|
+
# additional writers are added to the library.
|
174
|
+
return unless row
|
175
|
+
row.each_with_index do |value, idx|
|
176
|
+
unless row.formats[idx]
|
177
|
+
format = nil
|
178
|
+
case value
|
179
|
+
when Date
|
180
|
+
format = @workbook.formats.find do |fmt| fmt.date? end
|
181
|
+
format ||= Format.new :number_format => client('M/D/YY', 'UTF8')
|
182
|
+
when DateTime, Time
|
183
|
+
format = @workbook.formats.find do |fmt| fmt.datetime? end
|
184
|
+
format ||= Format.new :number_format => client('M/D/YY h:mm', 'UTF8')
|
185
|
+
end
|
186
|
+
if format
|
187
|
+
row.formats[idx] = format
|
188
|
+
@workbook.add_format format
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
def index_of_first ary # :nodoc:
|
194
|
+
return unless ary
|
195
|
+
ary.index(ary.find do |elm| elm end)
|
196
|
+
end
|
197
|
+
def recalculate_dimensions # :nodoc:
|
198
|
+
shorten @rows
|
199
|
+
@dimensions = []
|
200
|
+
@dimensions[0] = index_of_first @rows
|
201
|
+
@dimensions[1] = @rows.size
|
202
|
+
compact = @rows.compact
|
203
|
+
@dimensions[2] = compact.collect do |row| index_of_first row end.compact.min
|
204
|
+
@dimensions[3] = compact.collect do |row| row.size end.max
|
205
|
+
@dimensions
|
206
|
+
end
|
207
|
+
def shorten ary # :nodoc:
|
208
|
+
return unless ary
|
209
|
+
while !ary.empty? && !ary.last
|
210
|
+
ary.pop
|
211
|
+
end
|
212
|
+
ary unless ary.empty?
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Spreadsheet
|
2
|
+
##
|
3
|
+
# Parent Class for all Writers. Implements the copying of unmodified
|
4
|
+
# Spreadsheet documents.
|
5
|
+
class Writer
|
6
|
+
def initialize io_or_path
|
7
|
+
@io_or_path = io_or_path
|
8
|
+
end
|
9
|
+
def write workbook
|
10
|
+
if @io_or_path.is_a? IO
|
11
|
+
write_workbook workbook, @io_or_path
|
12
|
+
else
|
13
|
+
File.open(@io_or_path, "wb+") do |fh|
|
14
|
+
write_workbook workbook, fh
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
private
|
19
|
+
def write_workbook workbook, io
|
20
|
+
reader = workbook.io
|
21
|
+
unless io == reader
|
22
|
+
reader.rewind
|
23
|
+
data = reader.read
|
24
|
+
io.rewind
|
25
|
+
io.write data
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/test/excel/row.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Excel::TestRow -- Spreadsheet -- 12.10.2008 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
$: << File.expand_path('../../lib', File.dirname(__FILE__))
|
5
|
+
|
6
|
+
require 'test/unit'
|
7
|
+
require 'spreadsheet'
|
8
|
+
|
9
|
+
module Spreadsheet
|
10
|
+
module Excel
|
11
|
+
class TestRow < Test::Unit::TestCase
|
12
|
+
def setup
|
13
|
+
@workbook = Excel::Workbook.new
|
14
|
+
@worksheet = Excel::Worksheet.new
|
15
|
+
@workbook.add_worksheet @worksheet
|
16
|
+
end
|
17
|
+
def test_date
|
18
|
+
row = Row.new @worksheet, 0, [nil, 27627.6789]
|
19
|
+
assert_equal Date.new(1975,8,21), row.date(1)
|
20
|
+
end
|
21
|
+
def test_datetime
|
22
|
+
row = Row.new @worksheet, 0, [nil, 27627.6789]
|
23
|
+
d1 = DateTime.new(1975,8,21) + 0.6789
|
24
|
+
d2 = row.datetime 1
|
25
|
+
assert_equal d1, d2
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/test/font.rb
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# TestFont -- Spreadsheet -- 09.10.2008 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
$: << File.expand_path('../lib', File.dirname(__FILE__))
|
5
|
+
|
6
|
+
require 'test/unit'
|
7
|
+
require 'spreadsheet/font'
|
8
|
+
|
9
|
+
module Spreadsheet
|
10
|
+
class TestFont < Test::Unit::TestCase
|
11
|
+
def setup
|
12
|
+
@font = Font.new 'Arial'
|
13
|
+
end
|
14
|
+
def test_italic
|
15
|
+
assert_equal false, @font.italic
|
16
|
+
@font.italic!
|
17
|
+
assert_equal true, @font.italic
|
18
|
+
@font.italic = nil
|
19
|
+
assert_equal false, @font.italic
|
20
|
+
@font.italic = 1
|
21
|
+
assert_equal true, @font.italic
|
22
|
+
end
|
23
|
+
def test_encoding
|
24
|
+
assert_equal :default, @font.encoding
|
25
|
+
@font.encoding = :apple_roman
|
26
|
+
assert_equal :apple_roman, @font.encoding
|
27
|
+
@font.encoding = 'Chinese Simplified'
|
28
|
+
assert_equal :chinese_simplified, @font.encoding
|
29
|
+
assert_raises ArgumentError do @font.size = 'ascii' end
|
30
|
+
assert_equal :chinese_simplified, @font.encoding
|
31
|
+
@font.encoding = nil
|
32
|
+
assert_equal :default, @font.encoding
|
33
|
+
end
|
34
|
+
def test_family
|
35
|
+
assert_equal :none, @font.family
|
36
|
+
@font.family = :roman
|
37
|
+
assert_equal :roman, @font.family
|
38
|
+
@font.family = 'Swiss'
|
39
|
+
assert_equal :swiss, @font.family
|
40
|
+
assert_raises ArgumentError do @font.size = :greek end
|
41
|
+
assert_equal :swiss, @font.family
|
42
|
+
@font.family = nil
|
43
|
+
assert_equal :none, @font.family
|
44
|
+
end
|
45
|
+
def test_name
|
46
|
+
assert_equal 'Arial', @font.name
|
47
|
+
@font.name = 'Helvetica'
|
48
|
+
assert_equal 'Helvetica', @font.name
|
49
|
+
end
|
50
|
+
def test_outline
|
51
|
+
assert_equal false, @font.outline
|
52
|
+
@font.outline!
|
53
|
+
assert_equal true, @font.outline
|
54
|
+
@font.outline = nil
|
55
|
+
assert_equal false, @font.outline
|
56
|
+
@font.outline = 1
|
57
|
+
assert_equal true, @font.outline
|
58
|
+
end
|
59
|
+
def test_escapement
|
60
|
+
assert_equal :normal, @font.escapement
|
61
|
+
@font.escapement = :superscript
|
62
|
+
assert_equal :superscript, @font.escapement
|
63
|
+
@font.escapement = 'sub'
|
64
|
+
assert_equal :subscript, @font.escapement
|
65
|
+
assert_raises ArgumentError do @font.size = "upwards" end
|
66
|
+
assert_equal :subscript, @font.escapement
|
67
|
+
@font.escapement = nil
|
68
|
+
assert_equal :normal, @font.escapement
|
69
|
+
end
|
70
|
+
def test_shadow
|
71
|
+
assert_equal false, @font.shadow
|
72
|
+
@font.shadow!
|
73
|
+
assert_equal true, @font.shadow
|
74
|
+
@font.shadow = nil
|
75
|
+
assert_equal false, @font.shadow
|
76
|
+
@font.shadow = 1
|
77
|
+
assert_equal true, @font.shadow
|
78
|
+
end
|
79
|
+
def test_size
|
80
|
+
assert_equal 10, @font.size
|
81
|
+
@font.size = 12
|
82
|
+
assert_equal 12, @font.size
|
83
|
+
@font.size = 11.2
|
84
|
+
assert_equal 11.2, @font.size
|
85
|
+
assert_raises ArgumentError do @font.size = "123" end
|
86
|
+
end
|
87
|
+
def test_strikeout
|
88
|
+
assert_equal false, @font.strikeout
|
89
|
+
@font.strikeout!
|
90
|
+
assert_equal true, @font.strikeout
|
91
|
+
@font.strikeout = nil
|
92
|
+
assert_equal false, @font.strikeout
|
93
|
+
@font.strikeout = 1
|
94
|
+
assert_equal true, @font.strikeout
|
95
|
+
end
|
96
|
+
def test_underline
|
97
|
+
assert_equal :none, @font.underline
|
98
|
+
@font.underline = :single
|
99
|
+
assert_equal :single, @font.underline
|
100
|
+
@font.underline = 'double accounting'
|
101
|
+
assert_equal :double_accounting, @font.underline
|
102
|
+
assert_raises ArgumentError do @font.size = :triple end
|
103
|
+
assert_equal :double_accounting, @font.underline
|
104
|
+
@font.underline = nil
|
105
|
+
assert_equal :none, @font.underline
|
106
|
+
@font.underline = true
|
107
|
+
assert_equal :single, @font.underline
|
108
|
+
end
|
109
|
+
def test_weight
|
110
|
+
assert_equal :normal, @font.weight
|
111
|
+
@font.weight = :bold
|
112
|
+
assert_equal :bold, @font.weight
|
113
|
+
@font.weight = 100
|
114
|
+
assert_equal 100, @font.weight
|
115
|
+
assert_raises ArgumentError do @font.weight = Object.new end
|
116
|
+
assert_equal 100, @font.weight
|
117
|
+
@font.weight = 'bold'
|
118
|
+
assert_equal :bold, @font.weight
|
119
|
+
@font.weight = nil
|
120
|
+
assert_equal :normal, @font.weight
|
121
|
+
end
|
122
|
+
def test_key
|
123
|
+
expected = 'Arial_10_normal_normal_none_text_none_default'
|
124
|
+
assert_equal expected, @font.key
|
125
|
+
@font.name = 'Helvetica'
|
126
|
+
expected = 'Helvetica_10_normal_normal_none_text_none_default'
|
127
|
+
assert_equal expected, @font.key
|
128
|
+
@font.size = 12
|
129
|
+
expected = 'Helvetica_12_normal_normal_none_text_none_default'
|
130
|
+
assert_equal expected, @font.key
|
131
|
+
@font.weight = :bold
|
132
|
+
expected = 'Helvetica_12_bold_normal_none_text_none_default'
|
133
|
+
assert_equal expected, @font.key
|
134
|
+
@font.italic!
|
135
|
+
expected = 'Helvetica_12_bold_italic_normal_none_text_none_default'
|
136
|
+
assert_equal expected, @font.key
|
137
|
+
@font.strikeout!
|
138
|
+
expected = 'Helvetica_12_bold_italic_strikeout_normal_none_text_none_default'
|
139
|
+
assert_equal expected, @font.key
|
140
|
+
@font.outline!
|
141
|
+
expected = 'Helvetica_12_bold_italic_strikeout_outline_normal_none_text_none_default'
|
142
|
+
assert_equal expected, @font.key
|
143
|
+
@font.shadow!
|
144
|
+
expected = 'Helvetica_12_bold_italic_strikeout_outline_shadow_normal_none_text_none_default'
|
145
|
+
assert_equal expected, @font.key
|
146
|
+
@font.escapement = :super
|
147
|
+
expected = 'Helvetica_12_bold_italic_strikeout_outline_shadow_superscript_none_text_none_default'
|
148
|
+
assert_equal expected, @font.key
|
149
|
+
@font.underline = :double
|
150
|
+
expected = 'Helvetica_12_bold_italic_strikeout_outline_shadow_superscript_double_text_none_default'
|
151
|
+
assert_equal expected, @font.key
|
152
|
+
@font.color = :blue
|
153
|
+
expected = 'Helvetica_12_bold_italic_strikeout_outline_shadow_superscript_double_blue_none_default'
|
154
|
+
assert_equal expected, @font.key
|
155
|
+
@font.family = :swiss
|
156
|
+
expected = 'Helvetica_12_bold_italic_strikeout_outline_shadow_superscript_double_blue_swiss_default'
|
157
|
+
assert_equal expected, @font.key
|
158
|
+
@font.encoding = :iso_latin1
|
159
|
+
expected = 'Helvetica_12_bold_italic_strikeout_outline_shadow_superscript_double_blue_swiss_iso_latin1'
|
160
|
+
assert_equal expected, @font.key
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|