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.
@@ -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
@@ -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,2 @@
1
+ 'B' -> 'C'
2
+ '2B' -> 'C2'
@@ -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