slayer-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/.Rakefile.swp +0 -0
- data/.bnsignore +26 -0
- 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/cell.rb +187 -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 +605 -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/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/surpass.gemspec +39 -0
- metadata +119 -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,118 @@
|
|
|
1
|
+
module Utilities
|
|
2
|
+
# For ease of comparing with pyExcelerator output values
|
|
3
|
+
# python seems to automatically decode to hex values
|
|
4
|
+
def hex_array_to_binary_string(array_of_hex_values)
|
|
5
|
+
[array_of_hex_values.collect {|h| [sprintf("%02x", h)]}.join].pack('H*')
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def binary_string_to_hex_array(binary_string)
|
|
9
|
+
binary_string.unpack("H*")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def points_to_pixels(points)
|
|
13
|
+
points*(4.0/3)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def pixels_to_points(pixels)
|
|
17
|
+
pixels * (3.0 / 4)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def twips_to_pixels(twips)
|
|
21
|
+
twips / 15.0
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def pixels_to_twips(pixels)
|
|
25
|
+
pixels * 15.0
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def as_excel_date(date)
|
|
29
|
+
date = DateTime.parse(date.strftime("%c")) if date.is_a?(Time)
|
|
30
|
+
excel_date = (date - Date.civil(1899, 12, 31)).to_f
|
|
31
|
+
excel_date += 1 if excel_date > 59 # Add a day for Excel's missing leap day in 1900
|
|
32
|
+
excel_date
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def mock_unicode_string(s)
|
|
36
|
+
[s.length, 0].pack('vC') + s
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def as_boolean(input)
|
|
41
|
+
case input
|
|
42
|
+
when 1, true
|
|
43
|
+
true
|
|
44
|
+
when 0, false
|
|
45
|
+
false
|
|
46
|
+
else
|
|
47
|
+
raise "Can't convert #{input} from excel boolean!"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def as_numeric(input)
|
|
52
|
+
case input
|
|
53
|
+
when true, 1
|
|
54
|
+
1
|
|
55
|
+
when false, 0
|
|
56
|
+
0
|
|
57
|
+
else
|
|
58
|
+
raise "Can't convert #{input} to excel boolean!"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Mimic python's "hex" function 0x00
|
|
63
|
+
def hex(value)
|
|
64
|
+
"0x" + value.to_s(16)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
RE_CELL_EX = /^(\$)?([A-I]?[A-Z])(\$?)(\d+)$/i
|
|
68
|
+
|
|
69
|
+
def col_by_name(column_name)
|
|
70
|
+
col = 0
|
|
71
|
+
pow = 1
|
|
72
|
+
column_name.reverse.each_byte do |l|
|
|
73
|
+
col += (l - 64) * pow
|
|
74
|
+
pow *= 26
|
|
75
|
+
end
|
|
76
|
+
col - 1
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def cell_to_rowcol(cell)
|
|
80
|
+
match = RE_CELL_EX.match(cell)
|
|
81
|
+
raise "Ill-formed single cell reference #{cell}" if match.nil?
|
|
82
|
+
col_abs, col, row_abs, row = match.captures
|
|
83
|
+
row = row.to_i - 1
|
|
84
|
+
col = col_by_name(col.upcase)
|
|
85
|
+
[row, col, row_abs.nil?, col_abs.nil?]
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def cell_to_packed_rowcol(cell)
|
|
89
|
+
row, col, row_abs, col_abs = cell_to_rowcol(cell)
|
|
90
|
+
raise "Column #{col} is greater than IV (#{MAX_COL})" if col >= MAX_COL
|
|
91
|
+
raise "Row #{row} is greater than #{MAX_ROW} in #{cell}" if row >= MAX_ROW
|
|
92
|
+
|
|
93
|
+
col |= row_abs.to_i << 15
|
|
94
|
+
col |= col_abs.to_i << 14
|
|
95
|
+
|
|
96
|
+
[row, col]
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def String.random_alphanumeric(size=16)
|
|
101
|
+
s = ""
|
|
102
|
+
size.times { s << (i = Kernel.rand(62); i += ((i < 10) ? 48 : ((i < 36) ? 55 : 61 ))).chr }
|
|
103
|
+
s
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
class TrueClass
|
|
107
|
+
def to_i
|
|
108
|
+
1
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
class FalseClass
|
|
113
|
+
def to_i
|
|
114
|
+
0
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
include Utilities
|