rmasalov-surpass 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/LICENSE.txt +110 -0
- data/README.txt +26 -0
- data/Rakefile +36 -0
- data/bin/surpass +8 -0
- data/lib/surpass.rb +64 -0
- data/lib/surpass/ExcelFormula.g +393 -0
- data/lib/surpass/ExcelFormula.tokens +32 -0
- data/lib/surpass/ExcelFormulaLexer.rb +1490 -0
- data/lib/surpass/ExcelFormulaParser.rb +1822 -0
- data/lib/surpass/biff_record.rb +2173 -0
- data/lib/surpass/bitmap.rb +218 -0
- data/lib/surpass/chart.rb +16 -0
- data/lib/surpass/column.rb +40 -0
- data/lib/surpass/document.rb +406 -0
- data/lib/surpass/excel_magic.rb +1016 -0
- data/lib/surpass/formatting.rb +607 -0
- data/lib/surpass/formula.rb +25 -0
- data/lib/surpass/row.rb +173 -0
- data/lib/surpass/style.rb +194 -0
- data/lib/surpass/surpass_cell.rb +187 -0
- data/lib/surpass/tokens.txt +2 -0
- data/lib/surpass/utilities.rb +118 -0
- data/lib/surpass/workbook.rb +207 -0
- data/lib/surpass/worksheet.rb +574 -0
- data/rmasalov-surpass +0 -0
- data/surpass.gemspec +39 -0
- metadata +120 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
class Formula
|
2
|
+
NO_CALCS=0x00
|
3
|
+
RECALC_ALWAYS=0x01
|
4
|
+
CALC_ON_OPEN=0x02
|
5
|
+
PART_OF_SHARED_FORMULA=0x08
|
6
|
+
|
7
|
+
attr_reader :parser
|
8
|
+
|
9
|
+
def initialize(formula_string)
|
10
|
+
raise "formulas not available" unless FORMULAS_AVAILABLE
|
11
|
+
@lexer = ExcelFormula::Lexer.new(formula_string)
|
12
|
+
@parser = ExcelFormula::Parser.new(@lexer)
|
13
|
+
begin
|
14
|
+
@parser.formula
|
15
|
+
rescue RuntimeError => e
|
16
|
+
puts e
|
17
|
+
raise "invalid Excel formula"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_biff
|
22
|
+
rpn = @parser.rpn
|
23
|
+
[rpn.length].pack('v') + rpn
|
24
|
+
end
|
25
|
+
end
|
data/lib/surpass/row.rb
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
class Row
|
2
|
+
attr_accessor :index
|
3
|
+
attr_accessor :parent
|
4
|
+
attr_accessor :parent_wb
|
5
|
+
attr_accessor :cells
|
6
|
+
attr_accessor :min_col_index
|
7
|
+
attr_accessor :max_col_index
|
8
|
+
attr_accessor :total_str
|
9
|
+
attr_accessor :xf_index
|
10
|
+
attr_accessor :has_default_format
|
11
|
+
attr_accessor :height_in_pixels
|
12
|
+
|
13
|
+
attr_accessor :height
|
14
|
+
attr_accessor :has_default_height
|
15
|
+
attr_accessor :height_mismatch
|
16
|
+
attr_accessor :level
|
17
|
+
attr_accessor :collapse
|
18
|
+
attr_accessor :hidden
|
19
|
+
attr_accessor :space_above
|
20
|
+
attr_accessor :space_below
|
21
|
+
|
22
|
+
def initialize(index, parent_sheet)
|
23
|
+
is_int = index.is_a?(Integer)
|
24
|
+
in_range = (index >= 0) && (index <= 65535)
|
25
|
+
raise "row index #{index} is not valid" unless is_int && in_range
|
26
|
+
|
27
|
+
@index = index
|
28
|
+
@parent = parent_sheet
|
29
|
+
@parent_wb = parent_sheet.parent()
|
30
|
+
@cells = []
|
31
|
+
@min_col_index = 0
|
32
|
+
@max_col_index = 0
|
33
|
+
@total_str = 0
|
34
|
+
@xf_index = 0x0F
|
35
|
+
@has_default_format = 0
|
36
|
+
@height_in_pixels = 0x11
|
37
|
+
|
38
|
+
@height = 0x00FF
|
39
|
+
@has_default_height = 0x00
|
40
|
+
@height_mismatch = 0
|
41
|
+
@level = 0
|
42
|
+
@collapse = 0
|
43
|
+
@hidden = 0
|
44
|
+
@space_above = 0
|
45
|
+
@space_below = 0
|
46
|
+
end
|
47
|
+
|
48
|
+
def adjust_height(style)
|
49
|
+
twips = style.font.height
|
50
|
+
points = twips/20.0
|
51
|
+
# Cell height in pixels can be calcuted by following approx. formula:
|
52
|
+
# cell height in pixels = font height in points * 83/50 + 2/5
|
53
|
+
# It works when screen resolution is 96 dpi
|
54
|
+
pix = (points*83.0/50.0 + 2.0/5.0).round
|
55
|
+
@height_in_pixels = pix if (pix > @height_in_pixels)
|
56
|
+
end
|
57
|
+
|
58
|
+
def set_height(height)
|
59
|
+
@height = height * 20 #This seems to correspond to row height in excel.
|
60
|
+
@height_mismatch = 1
|
61
|
+
end
|
62
|
+
|
63
|
+
def adjust_boundary_column_indexes(*args)
|
64
|
+
args.each do |a|
|
65
|
+
is_int = (a.to_i == a)
|
66
|
+
in_range = (0 <= a) && (a <= 255)
|
67
|
+
raise "invalid boundary index #{a}" unless is_int && in_range
|
68
|
+
@min_col_index = a if a < @min_col_index
|
69
|
+
@max_col_index = a if a > @max_col_index
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# TODO can we get rid of this? Tests pass if it is commented out.
|
74
|
+
def style=(style)
|
75
|
+
adjust_height(style)
|
76
|
+
@xf_index = @parent_wb.styles.add(style)
|
77
|
+
@has_default_format = 1
|
78
|
+
end
|
79
|
+
|
80
|
+
def cells_count
|
81
|
+
@cells.length
|
82
|
+
end
|
83
|
+
|
84
|
+
### @export "to-biff"
|
85
|
+
def to_biff
|
86
|
+
height_options = (@height & 0x07FFF)
|
87
|
+
height_options |= (@has_default_height & 0x01) << 15
|
88
|
+
|
89
|
+
options = (@level & 0x07) << 0
|
90
|
+
options |= (@collapse & 0x01) << 4
|
91
|
+
options |= (@hidden & 0x01) << 5
|
92
|
+
options |= (@height_mismatch & 0x01) << 6
|
93
|
+
options |= (@has_default_format & 0x01) << 7
|
94
|
+
options |= (0x01 & 0x01) << 8
|
95
|
+
options |= (@xf_index & 0x0FFF) << 16
|
96
|
+
options |= (@space_above & 0x01) << 28
|
97
|
+
options |= (@space_below & 0x01) << 29
|
98
|
+
|
99
|
+
args = [@index, @min_col_index, @max_col_index, height_options, options]
|
100
|
+
RowRecord.new(*args).to_biff
|
101
|
+
end
|
102
|
+
|
103
|
+
def cells_biff
|
104
|
+
cells.collect {|c| c.to_biff }.join
|
105
|
+
end
|
106
|
+
### @end
|
107
|
+
|
108
|
+
def cell(col_index)
|
109
|
+
cells.select {|c| c.index == col_index}.first
|
110
|
+
end
|
111
|
+
|
112
|
+
def write(col, label, style)
|
113
|
+
case style
|
114
|
+
when StyleFormat
|
115
|
+
# leave it alone
|
116
|
+
when Hash
|
117
|
+
style = StyleFormat.new(style)
|
118
|
+
### @export "autoformats"
|
119
|
+
when TrueClass # Automatically apply a nice numeric format.
|
120
|
+
case label
|
121
|
+
when DateTime, Time
|
122
|
+
style = @parent_wb.styles.default_datetime_style
|
123
|
+
when Date
|
124
|
+
style = @parent_wb.styles.default_date_style
|
125
|
+
when Float
|
126
|
+
style = @parent_wb.styles.default_float_style
|
127
|
+
else
|
128
|
+
style = @parent_wb.styles.default_style
|
129
|
+
end
|
130
|
+
### @end
|
131
|
+
when NilClass
|
132
|
+
style = @parent_wb.styles.default_style
|
133
|
+
else
|
134
|
+
raise "I don't know how to use this to format a cell #{style.inspect}"
|
135
|
+
end
|
136
|
+
|
137
|
+
style_index = @parent_wb.styles.add(style)
|
138
|
+
|
139
|
+
raise "trying to write to cell #{self.index}, #{col} - already exists!" if cell(col)
|
140
|
+
|
141
|
+
adjust_height(style)
|
142
|
+
adjust_boundary_column_indexes(col)
|
143
|
+
|
144
|
+
### @export "label-classes"
|
145
|
+
case label
|
146
|
+
when TrueClass, FalseClass
|
147
|
+
@cells << BooleanCell.new(self, col, style_index, label)
|
148
|
+
when String, NilClass
|
149
|
+
if label.to_s.length == 0
|
150
|
+
@cells << BlankCell.new(self, col, style_index)
|
151
|
+
else
|
152
|
+
@cells << StringCell.new(self, col, style_index, @parent_wb.sst.add_str(label))
|
153
|
+
@total_str += 1
|
154
|
+
end
|
155
|
+
when Numeric
|
156
|
+
@cells << NumberCell.new(self, col, style_index, label)
|
157
|
+
when Date, DateTime, Time
|
158
|
+
@cells << NumberCell.new(self, col, style_index, as_excel_date(label))
|
159
|
+
when Formula
|
160
|
+
@cells << FormulaCell.new(self, col, style_index, label)
|
161
|
+
else
|
162
|
+
raise "You are trying to write an object of class #{label.class.name} to a spreadsheet. Please convert this to a supported class such as String."
|
163
|
+
end
|
164
|
+
### @end
|
165
|
+
end
|
166
|
+
|
167
|
+
def write_blanks(c1, c2, style)
|
168
|
+
raise unless c1 <= c2
|
169
|
+
adjust_height(style)
|
170
|
+
adjust_boundary_column_indexes(c1, c2)
|
171
|
+
@cells << MulBlankCell.new(self, c1, c2, @parent_wb.styles.add(style))
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
class StyleFormat
|
2
|
+
attr_accessor :number_format_string
|
3
|
+
attr_accessor :font
|
4
|
+
attr_accessor :alignment
|
5
|
+
attr_accessor :borders
|
6
|
+
attr_accessor :pattern
|
7
|
+
attr_accessor :protection
|
8
|
+
|
9
|
+
def initialize(hash = {})
|
10
|
+
@number_format_string = hash[:number_format_string] || 'General'
|
11
|
+
|
12
|
+
@font = Font.new(hash_select(hash, /^font_/))
|
13
|
+
@alignment = Alignment.new(hash_select(hash, /^text_/))
|
14
|
+
@borders = Borders.new(hash_select(hash, /^border_/))
|
15
|
+
@pattern = Pattern.new(hash_select(hash, /^(fill|pattern)_/))
|
16
|
+
@protection = Protection.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def hash_select(hash, pattern)
|
20
|
+
new_hash = {}
|
21
|
+
hash.keys.each do |k|
|
22
|
+
next unless k.to_s =~ pattern
|
23
|
+
new_key = k.to_s.gsub(pattern, '').to_sym
|
24
|
+
new_hash[new_key] = hash[k]
|
25
|
+
end
|
26
|
+
new_hash
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class StyleCollection
|
31
|
+
attr_accessor :fonts
|
32
|
+
attr_accessor :number_formats
|
33
|
+
attr_accessor :styles
|
34
|
+
attr_accessor :default_style
|
35
|
+
attr_accessor :default_format
|
36
|
+
|
37
|
+
FIRST_USER_DEFINED_NUM_FORMAT_INDEX = 164
|
38
|
+
|
39
|
+
STANDARD_NUMBER_FORMATS = [
|
40
|
+
'General',
|
41
|
+
'0',
|
42
|
+
'0.00',
|
43
|
+
'#,##0',
|
44
|
+
'#,##0.00',
|
45
|
+
'"$"#,##0_);("$"#,##',
|
46
|
+
'"$"#,##0_);[Red]("$"#,##',
|
47
|
+
'"$"#,##0.00_);("$"#,##',
|
48
|
+
'"$"#,##0.00_);[Red]("$"#,##',
|
49
|
+
'0%',
|
50
|
+
'0.00%',
|
51
|
+
'0.00E+00',
|
52
|
+
'# ?/?',
|
53
|
+
'# ??/??',
|
54
|
+
'M/D/YY',
|
55
|
+
'D-MMM-YY',
|
56
|
+
'D-MMM',
|
57
|
+
'MMM-YY',
|
58
|
+
'h:mm AM/PM',
|
59
|
+
'h:mm:ss AM/PM',
|
60
|
+
'h:mm',
|
61
|
+
'h:mm:ss',
|
62
|
+
'M/D/YY h:mm',
|
63
|
+
'_(#,##0_);(#,##0)',
|
64
|
+
'_(#,##0_);[Red](#,##0)',
|
65
|
+
'_(#,##0.00_);(#,##0.00)',
|
66
|
+
'_(#,##0.00_);[Red](#,##0.00)',
|
67
|
+
'_("$"* #,##0_);_("$"* (#,##0);_("$"* "-"_);_(@_)',
|
68
|
+
'_(* #,##0_);_(* (#,##0);_(* "-"_);_(@_)',
|
69
|
+
'_("$"* #,##0.00_);_("$"* (#,##0.00);_("$"* "-"??_);_(@_)',
|
70
|
+
'_(* #,##0.00_);_(* (#,##0.00);_(* "-"??_);_(@_)',
|
71
|
+
'mm:ss',
|
72
|
+
'[h]:mm:ss',
|
73
|
+
'mm:ss.0',
|
74
|
+
'##0.0E+0',
|
75
|
+
'@'
|
76
|
+
]
|
77
|
+
|
78
|
+
def initialize
|
79
|
+
# Populate default font list.
|
80
|
+
@fonts = {}
|
81
|
+
# Initialize blank fonts into slots 0,1,2,3,5 in order to skip slot 4.
|
82
|
+
[0,1,2,3,5].each do |i|
|
83
|
+
@fonts[i] = Font.new
|
84
|
+
end
|
85
|
+
|
86
|
+
# Populate default number format list.
|
87
|
+
@number_formats = {}
|
88
|
+
STANDARD_NUMBER_FORMATS.each_with_index do |s, i|
|
89
|
+
index = (i <= 23) ? i : i + 14
|
90
|
+
@number_formats[index] = s
|
91
|
+
end
|
92
|
+
|
93
|
+
@styles = {}
|
94
|
+
@default_style = StyleFormat.new
|
95
|
+
|
96
|
+
# Store the 6 parameters of the default_style
|
97
|
+
@default_format = add_style(@default_style)[0]
|
98
|
+
end
|
99
|
+
|
100
|
+
### @export "autoformats"
|
101
|
+
def default_date_style
|
102
|
+
@default_date_style ||= StyleFormat.new(:number_format_string => 'dd-mmm-yyyy')
|
103
|
+
end
|
104
|
+
|
105
|
+
def default_datetime_style
|
106
|
+
@default_datetime_style ||= StyleFormat.new(:number_format_string => 'dd-mmm-yyyy hh:mm:ss')
|
107
|
+
end
|
108
|
+
|
109
|
+
def default_float_style
|
110
|
+
@default_float_style ||= StyleFormat.new(:number_format_string => '#,##0.00')
|
111
|
+
end
|
112
|
+
### @end
|
113
|
+
|
114
|
+
def add(style)
|
115
|
+
if style.nil?
|
116
|
+
0x10 # Return the index of the default style.
|
117
|
+
else
|
118
|
+
# TODO find way to freeze style so if someone modifies a StyleFormat instance it won't affect previously formatted cells.
|
119
|
+
add_style(style)[1] # Return the index of the style just stored.
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def number_format_index(number_format_string)
|
124
|
+
index = @number_formats.index(number_format_string)
|
125
|
+
if index.nil?
|
126
|
+
# TODO implement regex to check if valid string
|
127
|
+
index = FIRST_USER_DEFINED_NUM_FORMAT_INDEX + @number_formats.length - STANDARD_NUMBER_FORMATS.length
|
128
|
+
@number_formats[index] = number_format_string
|
129
|
+
end
|
130
|
+
index
|
131
|
+
end
|
132
|
+
|
133
|
+
def font_index(font)
|
134
|
+
index = @fonts.index(font)
|
135
|
+
if index.nil?
|
136
|
+
index = @fonts.length + 1
|
137
|
+
@fonts[index] = font
|
138
|
+
end
|
139
|
+
index
|
140
|
+
end
|
141
|
+
|
142
|
+
def format_index(format)
|
143
|
+
index = @styles.index(format)
|
144
|
+
if index.nil?
|
145
|
+
index = 0x10 + @styles.length
|
146
|
+
@styles[index] = format
|
147
|
+
end
|
148
|
+
index
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
# This is private, please use add(style) instead.
|
153
|
+
def add_style(style)
|
154
|
+
number_format_index = number_format_index(style.number_format_string)
|
155
|
+
font_index = font_index(style.font)
|
156
|
+
|
157
|
+
format = [font_index, number_format_index, style.alignment, style.borders, style.pattern, style.protection]
|
158
|
+
[format, format_index(format)]
|
159
|
+
end
|
160
|
+
|
161
|
+
public
|
162
|
+
def to_biff
|
163
|
+
fonts_biff + number_formats_biff + cell_styles_biff + StyleRecord.new.to_biff
|
164
|
+
end
|
165
|
+
|
166
|
+
# TODO use inject here?
|
167
|
+
def fonts_biff
|
168
|
+
result = ''
|
169
|
+
@fonts.sort.each do |i, f|
|
170
|
+
result += f.to_biff
|
171
|
+
end
|
172
|
+
result
|
173
|
+
end
|
174
|
+
|
175
|
+
def number_formats_biff
|
176
|
+
result = ''
|
177
|
+
@number_formats.sort.each do |i, f|
|
178
|
+
next if i < FIRST_USER_DEFINED_NUM_FORMAT_INDEX
|
179
|
+
result += NumberFormatRecord.new(i, f).to_biff
|
180
|
+
end
|
181
|
+
result
|
182
|
+
end
|
183
|
+
|
184
|
+
def cell_styles_biff
|
185
|
+
result = ''
|
186
|
+
0.upto(15) do |i|
|
187
|
+
result += XFRecord.new(@default_format, 'style').to_biff
|
188
|
+
end
|
189
|
+
@styles.sort.each do |i, f|
|
190
|
+
result += XFRecord.new(f).to_biff
|
191
|
+
end
|
192
|
+
result
|
193
|
+
end
|
194
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
class SurpassCell
|
2
|
+
attr_reader :index
|
3
|
+
|
4
|
+
def set_style(style)
|
5
|
+
style = StyleFormat.new(style) if style.is_a?(Hash)
|
6
|
+
@format_index = @parent.parent_wb.styles.add(style)
|
7
|
+
end
|
8
|
+
|
9
|
+
def row
|
10
|
+
@parent
|
11
|
+
end
|
12
|
+
|
13
|
+
def col
|
14
|
+
@index
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
### @export "string-cell"
|
19
|
+
class StringCell < SurpassCell
|
20
|
+
def initialize(parent, index, format_index, sst_index)
|
21
|
+
@parent = parent
|
22
|
+
@index = index
|
23
|
+
@format_index = format_index
|
24
|
+
@sst_index = sst_index
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_biff
|
28
|
+
LabelSSTRecord.new(@parent.index, @index, @format_index, @sst_index).to_biff
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
### @export "blank-cell"
|
33
|
+
class BlankCell < SurpassCell
|
34
|
+
def initialize(parent, index, format_index)
|
35
|
+
@parent = parent
|
36
|
+
@index = index
|
37
|
+
@format_index = format_index
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_biff
|
41
|
+
BlankRecord.new(@parent.index, @index, @format_index).to_biff
|
42
|
+
end
|
43
|
+
end
|
44
|
+
### @end
|
45
|
+
|
46
|
+
class NumberCell < SurpassCell
|
47
|
+
def initialize(parent, index, format_index, number)
|
48
|
+
@parent = parent
|
49
|
+
@index = index
|
50
|
+
@format_index = format_index
|
51
|
+
@number = number
|
52
|
+
end
|
53
|
+
|
54
|
+
def rk_record(rk_encoded)
|
55
|
+
RKRecord.new(@parent.index, @index, @format_index, rk_encoded).to_biff
|
56
|
+
end
|
57
|
+
|
58
|
+
# TODO test this section to be sure numbers are categorized and packed correctly.
|
59
|
+
def to_biff
|
60
|
+
# 30 bit signed int
|
61
|
+
in_range = (-0x20000000 <= @number) && (@number < 0x20000000)
|
62
|
+
is_int = (@number.to_i == @number)
|
63
|
+
if in_range && is_int
|
64
|
+
rk_encoded = 2 | (@number.to_i << 2)
|
65
|
+
return rk_record(rk_encoded)
|
66
|
+
end
|
67
|
+
|
68
|
+
# try scaling by 100 then using a 30 bit signed int
|
69
|
+
in_range = (-0x20000000 <= @number * 100) && (@number * 100 < 0x20000000)
|
70
|
+
round_trip = (@number.to_i*100) == @number*100
|
71
|
+
if in_range && round_trip
|
72
|
+
rk_encoded = (3 | (@number.to_i*100 << 2))
|
73
|
+
return rk_record(rk_encoded)
|
74
|
+
end
|
75
|
+
|
76
|
+
w0, w1, w2, w3 = [@number].pack('E').unpack('v4')
|
77
|
+
|
78
|
+
is_float_rk = (w0 == 0) && (w1 == 0) && (w2 & 0xFFFC) == w2
|
79
|
+
if is_float_rk
|
80
|
+
rk_encoded = (w3 << 16) | w2
|
81
|
+
return rk_record(rk_encoded)
|
82
|
+
end
|
83
|
+
|
84
|
+
w0, w1, w2, w3 = [@number * 100].pack('E').unpack('v4')
|
85
|
+
|
86
|
+
is_float_rk_100 = w0 == 0 && w1 == 0 && w2 & 0xFFFC == w2
|
87
|
+
if is_float_rk_100
|
88
|
+
rk_encoded = 1 | (w3 << 16) | w2
|
89
|
+
return rk_record(rk_encoded)
|
90
|
+
end
|
91
|
+
|
92
|
+
# If not an RK value, use a NumberRecord instead.
|
93
|
+
NumberRecord.new(@parent.index, @index, @format_index, @number).to_biff
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class MulNumberCell < SurpassCell
|
98
|
+
def initialize(parent, index, format_index, sst_index)
|
99
|
+
@parent = parent
|
100
|
+
@index = index
|
101
|
+
@format_index = format_index
|
102
|
+
@sst_index = sst_index
|
103
|
+
end
|
104
|
+
|
105
|
+
def to_biff
|
106
|
+
raise "not implemented"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
class MulBlankCell < SurpassCell
|
111
|
+
def initialize(parent, col1, col2, xf_idx)
|
112
|
+
raise unless col1 < col2
|
113
|
+
@parent = parent
|
114
|
+
@col1 = col1
|
115
|
+
@col2 = col2
|
116
|
+
@xf_idx = xf_idx
|
117
|
+
end
|
118
|
+
|
119
|
+
def to_biff
|
120
|
+
MulBlankRecord.new(@parent.index, @col1, @col2, @xf_idx).to_biff
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
### @export "formula-cell"
|
125
|
+
class FormulaCell < SurpassCell
|
126
|
+
def initialize(parent, index, format_index, formula, calc_flags = 0)
|
127
|
+
@parent = parent
|
128
|
+
@index = index
|
129
|
+
@format_index = format_index
|
130
|
+
@formula = formula
|
131
|
+
@calc_flags = calc_flags
|
132
|
+
end
|
133
|
+
|
134
|
+
def to_biff
|
135
|
+
args = [@parent.index, @index, @format_index, @formula.to_biff, @calc_flags]
|
136
|
+
FormulaRecord.new(*args).to_biff
|
137
|
+
end
|
138
|
+
end
|
139
|
+
### @end
|
140
|
+
|
141
|
+
class BooleanCell < SurpassCell
|
142
|
+
def initialize(parent, index, format_index, number)
|
143
|
+
@parent = parent
|
144
|
+
@index = index
|
145
|
+
@format_index = format_index
|
146
|
+
@number = number
|
147
|
+
@is_error = 0
|
148
|
+
end
|
149
|
+
|
150
|
+
def to_biff
|
151
|
+
number = @number ? 1 : 0
|
152
|
+
BoolErrRecord.new(@parent.index, @index, @format_index, number, @is_error).to_biff
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
class ErrorCell < SurpassCell
|
157
|
+
ERROR_CODES = {
|
158
|
+
0x00 => 0, # Intersection of two cell ranges is empty
|
159
|
+
0x07 => 7, # Division by zero
|
160
|
+
0x0F => 15, # Wrong type of operand
|
161
|
+
0x17 => 23, # Illegal or deleted cell reference
|
162
|
+
0x1D => 29, # Wrong function or range name
|
163
|
+
0x24 => 36, # Value range overflow
|
164
|
+
0x2A => 42, # Argument or function not available
|
165
|
+
'#NULL!' => 0, # Intersection of two cell ranges is empty
|
166
|
+
'#DIV/0!' => 7, # Division by zero
|
167
|
+
'#VALUE!' => 36, # Wrong type of operand
|
168
|
+
'#REF!' => 23, # Illegal or deleted cell reference
|
169
|
+
'#NAME?' => 29, # Wrong function or range name
|
170
|
+
'#NUM!' => 36, # Value range overflow
|
171
|
+
'#N/A!' => 42 # Argument or function not available
|
172
|
+
}
|
173
|
+
|
174
|
+
def initialize(parent, index, format_index, error_string_or_code)
|
175
|
+
@parent = parent
|
176
|
+
@index = index
|
177
|
+
@format_index = format_index
|
178
|
+
@number = ERROR_CODES[error_string_or_code]
|
179
|
+
@is_error = 1
|
180
|
+
|
181
|
+
raise "invalid error code #{error_string_or_code}" if @number.nil?
|
182
|
+
end
|
183
|
+
|
184
|
+
def to_biff
|
185
|
+
BoolErrRecord.new(@parent.index, @index, @format_index, @number, @is_error)
|
186
|
+
end
|
187
|
+
end
|