rmasalov-surpass 0.1.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/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
|