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.
@@ -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
@@ -0,0 +1,207 @@
1
+ class Workbook
2
+ MACROS = {
3
+ 'Consolidate_Area' => 0x00,
4
+ 'Auto_Open' => 0x01,
5
+ 'Auto_Close' => 0x02,
6
+ 'Extract' => 0x03,
7
+ 'Database' => 0x04,
8
+ 'Criteria' => 0x05,
9
+ 'Print_Area' => 0x06,
10
+ 'Print_Titles' => 0x07, # in the docs it says Pint_Titles, I think its a mistake
11
+ 'Recorder' => 0x08,
12
+ 'Data_Form' => 0x09,
13
+ 'Auto_Activate' => 0x0A,
14
+ 'Auto_Deactivate' => 0x0B,
15
+ 'Sheet_Title' => 0x0C,
16
+ '_FilterDatabase' => 0x0D
17
+ }
18
+
19
+ attr_accessor :owner
20
+ attr_accessor :country_code
21
+ attr_accessor :wnd_protect
22
+ attr_accessor :obj_protect
23
+ attr_accessor :protect
24
+ attr_accessor :backup_on_save
25
+ attr_accessor :styles
26
+ attr_accessor :sst
27
+
28
+ def hpos_twips=(value)
29
+ @hpos_twips = value & 0xFFFF
30
+ end
31
+
32
+ attr_reader :vpos_twips
33
+ def vpos_twips=(value)
34
+ @vpos_twips = value & 0xFFFF
35
+ end
36
+
37
+ attr_reader :width_twips
38
+ def width_twips=(value)
39
+ @width_twips = value & 0xFFFF
40
+ end
41
+
42
+ attr_reader :height_twips
43
+ def height_twips=(value)
44
+ @height_twips = value & 0xFFFF
45
+ end
46
+
47
+ attr_reader :active_sheet
48
+ def active_sheet=(value)
49
+ @active_sheet = value & 0xFFFF
50
+ @first_tab_index = @active_sheet
51
+ end
52
+
53
+ attr_reader :tab_width_twips
54
+ def tab_width_twips=(value)
55
+ @tab_width_twips = value & 0xFFFF
56
+ end
57
+
58
+ attr_reader :default_style
59
+
60
+ def initialize(filename = nil)
61
+ @owner = 'None'
62
+ @wnd_protect = 0
63
+ @obj_protect = 0
64
+ @protect = 0
65
+ @backup_on_save = 0
66
+
67
+ @hpos_twips = 0x01E0
68
+ @vpos_twips = 0x005A
69
+ @width_twips = 0x3FCF
70
+ @height_twips = 0x2A4E
71
+
72
+ @active_sheet = 0
73
+ @first_tab_index = 0
74
+ @selected_tabs = 0x01
75
+ @tab_width_twips = 0x0258
76
+
77
+ @wnd_hidden = false
78
+ @wnd_mini = false
79
+ @hscroll_visible = true
80
+ @vscroll_visible = true
81
+ @tabs_visible = true
82
+
83
+ @styles = ::StyleCollection.new
84
+
85
+ @dates_1904 = false
86
+ @use_cell_values = true
87
+
88
+ @sst = SharedStringTable.new
89
+
90
+ @worksheets = []
91
+ @names = []
92
+ @refs = []
93
+
94
+ @filename = filename
95
+ end
96
+
97
+ def add_sheet(name = nil)
98
+ name ||= "Sheet#{@worksheets.length + 1}"
99
+ s = Worksheet.new(name, self)
100
+ @worksheets << s
101
+ s
102
+ end
103
+
104
+ def print_area(sheetnum, rstart, rend, cstart, cend)
105
+ if !sheetnum.is_a?(Integer)
106
+ i = 0
107
+ @worksheets.each_with_index do |w, i|
108
+ sheetnum = i+1 if w.name === sheetnum
109
+ break if sheetnum.is_a?(Integer)
110
+ end
111
+ end
112
+
113
+ options = 0x0020 # see Options Flags for Name record
114
+
115
+ # FIXME: this is just a bad hack, need to use Formula to make the rpn
116
+ #~ rpn = Formula.Formula('').rpn()[2:] # minus the size field
117
+ rpn = [0x3B, 0x0000, rstart, rend, cstart, cend].pack('Cv5')
118
+ args = [options, 0x00, MACROS['Print_Area'], sheetnum, rpn]
119
+ @names << NameRecord.new(*args).to_biff
120
+ end
121
+
122
+ def to_biff
123
+ raise "You cannot save a workbook with no worksheets" if @worksheets.empty?
124
+
125
+ ### @export "to-biff"
126
+ section_1_array = []
127
+ section_1_array << Biff8BOFRecord.new(Biff8BOFRecord::BOOK_GLOBAL).to_biff
128
+ section_1_array << InterfaceHeaderRecord.new.to_biff
129
+ section_1_array << MMSRecord.new.to_biff
130
+ section_1_array << InterfaceEndRecord.new.to_biff
131
+ section_1_array << WriteAccessRecord.new(owner).to_biff
132
+ section_1_array << CodepageBiff8Record.new.to_biff
133
+ section_1_array << DSFRecord.new.to_biff
134
+ section_1_array << TabIDRecord.new(@worksheets.length).to_biff
135
+ section_1_array << FnGroupCountRecord.new.to_biff
136
+ section_1_array << WindowProtectRecord.new(as_numeric(@wnd_protect)).to_biff
137
+ section_1_array << ProtectRecord.new(as_numeric(@protect)).to_biff
138
+ section_1_array << ObjectProtectRecord.new(as_numeric(@obj_protect)).to_biff
139
+ section_1_array << PasswordRecord.new.to_biff
140
+ section_1_array << Prot4RevRecord.new.to_biff
141
+ section_1_array << Prot4RevPassRecord.new.to_biff
142
+ section_1_array << BackupRecord.new(@backup_on_save).to_biff
143
+ section_1_array << HideObjRecord.new.to_biff
144
+ section_1_array << window_1_record
145
+ section_1_array << DateModeRecord.new(@dates_1904).to_biff
146
+ section_1_array << PrecisionRecord.new(@use_cell_values).to_biff
147
+ section_1_array << RefreshAllRecord.new.to_biff
148
+ section_1_array << BookBoolRecord.new.to_biff
149
+ section_1_array << @styles.to_biff
150
+ section_1_array << '' # Palette
151
+ section_1_array << UseSelfsRecord.new.to_biff
152
+ section_1 = section_1_array.join
153
+ ### @end
154
+ section_3_array = []
155
+ section_3_array << CountryRecord.new(@country_code, @country_code).to_biff unless @country_code.nil?
156
+ # section_3_array << InternalReferenceSupBookRecord.new(@worksheets.length).to_biff
157
+ # section_3_array << ExternSheetRecord.new(@refs).to_biff
158
+ # section_3_array << @names.collect {|n| n.to_biff}.join
159
+ section_3_array << @sst.to_biff
160
+ section_3 = section_3_array.join
161
+
162
+ section_4 = '' # ExtSSTRecord
163
+ section_5 = EOFRecord.new.to_biff
164
+
165
+ @worksheets[@active_sheet].selected = true
166
+ worksheet_biff_data = @worksheets.collect {|w| w.to_biff }
167
+ worksheet_biff_data_lengths = worksheet_biff_data.collect {|w| w.length }
168
+ section_6 = worksheet_biff_data.join
169
+
170
+ # Need to know how long the bound sheet records will be
171
+ boundsheet_data_lengths = @worksheets.collect {|w| BoundSheetRecord.new(0x00, w.visibility, w.name).to_biff.length }
172
+ total_boundsheet_data_length = boundsheet_data_lengths.inject(0) {|sum, l| sum + l}
173
+ start_position = section_1.length + total_boundsheet_data_length + section_3.length + section_4.length + section_5.length
174
+
175
+ boundsheet_records = []
176
+ @worksheets.each_with_index do |w, i|
177
+ boundsheet_records << BoundSheetRecord.new(start_position, w.visibility, w.name).to_biff
178
+ start_position += worksheet_biff_data_lengths[i]
179
+ end
180
+
181
+ section_2 = boundsheet_records.join
182
+ section_1 + section_2 + section_3 + section_4 + section_5 + section_6
183
+ end
184
+
185
+ def window_1_record
186
+ flags = 0
187
+ flags |= (as_numeric(@wnd_hidden)) << 0
188
+ flags |= (as_numeric(@wnd_mini)) << 1
189
+ flags |= (as_numeric(@hscroll_visible)) << 3
190
+ flags |= (as_numeric(@vscroll_visible)) << 4
191
+ flags |= (as_numeric(@tabs_visible)) << 5
192
+
193
+ args = [@hpos_twips, @vpos_twips, @width_twips, @height_twips, flags, @active_sheet, @first_tab_index, @selected_tabs, @tab_width_twips]
194
+ Window1Record.new(*args).to_biff
195
+ end
196
+
197
+ def data
198
+ doc = ExcelDocument.new
199
+ doc.data(to_biff).read
200
+ end
201
+
202
+ def save(filename = nil)
203
+ @filename = filename unless filename.nil?
204
+ doc = ExcelDocument.new
205
+ doc.save(@filename, to_biff)
206
+ end
207
+ end
@@ -0,0 +1,574 @@
1
+ class Worksheet
2
+ include Utilities
3
+
4
+ attr_accessor :name
5
+ attr_accessor :parent
6
+ attr_accessor :rows
7
+ attr_accessor :cols
8
+ attr_accessor :merged_ranges
9
+ attr_accessor :bmp_rec
10
+ attr_accessor :show_formulas
11
+ attr_accessor :show_grid
12
+ attr_accessor :show_headers
13
+ attr_accessor :panes_frozen
14
+ attr_accessor :show_empty_as_zero
15
+ attr_accessor :auto_colour_grid
16
+ attr_accessor :cols_right_to_left
17
+ attr_accessor :show_outline
18
+ attr_accessor :remove_splits
19
+ attr_accessor :selected
20
+ # RED HERRING ALERT: "sheet_visible" is a clone of the "selected" attribute.
21
+ # Typically a workbook created by the Excel UI will have one sheet
22
+ # (the sheet that was selected when the user saved it)
23
+ # with both bits set to 1, and all other sheets will have both
24
+ # bits set to 0. The true visibility of the sheet is found in the "visibility"
25
+ # attribute obtained from the BOUNDSHEET record.
26
+ attr_accessor :sheet_visible
27
+ attr_accessor :page_preview
28
+ attr_accessor :first_visible_row
29
+ attr_accessor :first_visible_col
30
+ attr_accessor :grid_colour
31
+ attr_accessor :preview_magn
32
+ attr_accessor :normal_magn
33
+ attr_accessor :visibility
34
+ attr_accessor :vert_split_pos
35
+ attr_accessor :horz_split_pos
36
+ attr_accessor :vert_split_first_visible
37
+ attr_accessor :horz_split_first_visible
38
+
39
+ attr_accessor :delta
40
+ attr_accessor :save_recalc
41
+ attr_accessor :formula_options
42
+ attr_accessor :print_headers
43
+ attr_accessor :print_grid
44
+ attr_accessor :grid_set
45
+ attr_accessor :vert_page_breaks
46
+ attr_accessor :horz_page_breaks
47
+ attr_accessor :header_str
48
+ attr_accessor :footer_str
49
+ attr_accessor :print_centered_vert
50
+ attr_accessor :print_centered_horz
51
+ attr_accessor :left_margin
52
+ attr_accessor :right_margin
53
+ attr_accessor :top_margin
54
+ attr_accessor :bottom_margin
55
+ attr_accessor :paper_size_code
56
+ attr_accessor :print_scaling
57
+ attr_accessor :start_page_number
58
+ attr_accessor :fit_width_to_pages
59
+ attr_accessor :fit_height_to_pages
60
+ attr_accessor :print_in_rows
61
+ attr_accessor :portrait
62
+ attr_accessor :print_not_colour
63
+ attr_accessor :print_draft
64
+ attr_accessor :print_notes
65
+ attr_accessor :print_notes_at_end
66
+ attr_accessor :print_omit_errors
67
+ attr_accessor :print_hres
68
+ attr_accessor :print_vres
69
+ attr_accessor :header_margin
70
+ attr_accessor :footer_margin
71
+ attr_accessor :copies_num
72
+
73
+ attr_accessor :show_auto_page_breaks
74
+ attr_accessor :dialogue_sheet
75
+ attr_accessor :auto_style_outline
76
+ attr_accessor :outline_below
77
+ attr_accessor :outline_right
78
+ attr_accessor :fit_num_pages
79
+ attr_accessor :show_row_outline
80
+ attr_accessor :show_col_outline
81
+ attr_accessor :alt_expr_eval
82
+ attr_accessor :alt_formula_entries
83
+
84
+ attr_accessor :col_default_width
85
+ attr_reader :calc_mode
86
+ attr_accessor :calc_count
87
+
88
+ attr_accessor :protect
89
+ attr_accessor :wnd_protect
90
+ attr_accessor :obj_protect
91
+ attr_accessor :scen_protect
92
+ attr_accessor :password
93
+
94
+ def initialize(name, parent)
95
+ @name = name
96
+ @parent = parent
97
+ @rows = {}
98
+ @cols = {}
99
+ @merged_ranges = []
100
+ @bmp_rec = ''
101
+ @show_formulas = 0
102
+ @show_grid = 1
103
+ @show_headers = 1
104
+ @panes_frozen = 0
105
+ @show_empty_as_zero = 1
106
+ @auto_colour_grid = 1
107
+ @cols_right_to_left = 0
108
+ @show_outline = 1
109
+ @remove_splits = 0
110
+ @selected = 0
111
+ @sheet_visible = 0
112
+ @page_preview = 0
113
+
114
+ @first_visible_row = 0
115
+ @first_visible_col = 0
116
+ @grid_colour = 0x40
117
+ @preview_magn = 0
118
+ @normal_magn = 0
119
+ @visibility = 0
120
+
121
+ @vert_split_pos = nil
122
+ @horz_split_pos = nil
123
+ @vert_split_first_visible = nil
124
+ @horz_split_first_visible = nil
125
+ @split_active_pane = nil # TODO test implications of converting None -> Nil
126
+
127
+ @row_gut_width = 0
128
+ @col_gut_height = 0
129
+
130
+ @show_auto_page_breaks = 1
131
+ @dialogue_sheet = 0
132
+ @auto_style_outline = 0
133
+ @outline_below = 0
134
+ @outline_right = 0
135
+ @fit_num_pages = 0
136
+ @show_row_outline = 1
137
+ @show_col_outline = 1
138
+ @alt_expr_eval = 0
139
+ @alt_formula_entries = 0
140
+
141
+ @row_default_height = 0x00FF
142
+ @col_default_width = 0x0008
143
+
144
+ @default_row_height_mismatch = 0
145
+ @default_row_hidden = 0
146
+ @default_row_space_above = 0
147
+ @default_row_space_below = 0
148
+
149
+ @calc_mode = 1
150
+ @calc_count = 0x0064
151
+ @rc_ref_mode = 1
152
+ @iterations_on = 0
153
+ @delta = 0.001
154
+ @save_recalc = 0
155
+ @formula_options = Formula::RECALC_ALWAYS | Formula::CALC_ON_OPEN
156
+
157
+ @print_headers = 0
158
+ @print_grid = 0
159
+ @grid_set = 1
160
+ @vert_page_breaks = []
161
+ @horz_page_breaks = []
162
+ @header_str = '&P'
163
+ @footer_str = '&F'
164
+ @print_centered_vert = 0
165
+ @print_centered_horz = 1
166
+ @left_margin = 0.3 #0.5
167
+ @right_margin = 0.3 #0.5
168
+ @top_margin = 0.61 #1.0
169
+ @bottom_margin = 0.37 #1.0
170
+ @paper_size_code = 9 # A4
171
+ @print_scaling = 100
172
+ @start_page_number = 1
173
+ @fit_width_to_pages = 1
174
+ @fit_height_to_pages = 1
175
+ @print_in_rows = 1
176
+ @portrait = 1
177
+ @print_not_colour = 0
178
+ @print_draft = 0
179
+ @print_notes = 0
180
+ @print_notes_at_end = 0
181
+ @print_omit_errors = 0
182
+ @print_hres = 0x012C # 300 dpi
183
+ @print_vres = 0x012C # 300 dpi
184
+ @header_margin = 0.1
185
+ @footer_margin = 0.1
186
+ @copies_num = 1
187
+
188
+ @wnd_protect = 0
189
+ @obj_protect = 0
190
+ @protect = 0
191
+ @scen_protect = 0
192
+ @password = ''
193
+
194
+ @charts = []
195
+ end
196
+
197
+ # Accessors Performing Conversions
198
+ def row_default_height
199
+ twips_to_pixels(@row_default_height)
200
+ end
201
+
202
+ def row_default_height=(pixels)
203
+ @row_default_height = pixels_to_twips(pixels)
204
+ end
205
+
206
+ def calc_mode=(value)
207
+ @calc_mode = (value == 0xFFFF && value) || value & 0x01
208
+ end
209
+
210
+ def set_cell_style(r, c, style, create_blanks = false)
211
+ cell = rows[r].cell(c)
212
+ if cell.nil?
213
+ write(r, c, nil, style) if create_blanks
214
+ else
215
+ cell.set_style(style)
216
+ end
217
+ end
218
+
219
+ def hide_columns(col_range)
220
+ col_range.each do |c|
221
+ hide_column(c)
222
+ end
223
+ end
224
+
225
+ def unhide_columns(col_range)
226
+ col_range.each do |c|
227
+ unhide_column(c)
228
+ end
229
+ end
230
+
231
+ def set_column_widths(col_range, width)
232
+ col_range.each do |c|
233
+ set_column_width(c, width)
234
+ end
235
+ end
236
+
237
+ def hide_column(c)
238
+ col(c).hidden = true
239
+ end
240
+
241
+ def unhide_column(c)
242
+ col(c).hidden = false
243
+ end
244
+
245
+ # TODO fix this if column doesn't exist yet.
246
+ def set_column_width(c, width)
247
+ if width < 100
248
+ # Assume we are trying to use Excel-user style widths, scale up accordingly.
249
+ # You can call col's width method directly to avoid this.
250
+ width = width * 260
251
+ end
252
+ col(c).width = width
253
+ end
254
+
255
+ # Change the style for a range of cells. If nil is supplied for row_range,
256
+ # the new style is supplied to every row (i.e. the entire column). Only
257
+ # changes style for cells which actually exist, so this does not paint
258
+ # anything which has not been written to.
259
+ def set_range_style(row_range, col_range, style, create_blanks = false)
260
+ row_range ||= 0..65535
261
+ col_range ||= 0..255
262
+
263
+ @rows.each do |i, r|
264
+ next unless row_range.include?(i)
265
+ r.cells.each do |c|
266
+ next unless col_range.include?(c.col)
267
+ c.set_style(style)
268
+ end
269
+ end
270
+ end
271
+
272
+ # TODO get rid of meaningless default value for label, should be required?
273
+ ### @export "write-method"
274
+ def write(r, c, label = "", style = nil)
275
+ if label.is_a?(Array)
276
+ if label[0].is_a?(Array)
277
+ write_arrays(r, c, label, style || true)
278
+ else
279
+ write_array_to_row(label, r, c, style || true)
280
+ end
281
+ else
282
+ row(r).write(c, label, style)
283
+ end
284
+ end
285
+
286
+ ### @export "write-arrays"
287
+ def write_array_to_row(array, r, c = 0, style = true)
288
+ array.each_with_index do |a, i|
289
+ row(r).write(c + i, a, style)
290
+ end
291
+ end
292
+
293
+ def write_array_to_column(array, c, r = 0, style = true)
294
+ array.each_with_index do |a, i|
295
+ row(r + i).write(c, a, style)
296
+ end
297
+ end
298
+
299
+ def write_arrays(r, c, array_of_arrays, style = true)
300
+ array_of_arrays.each_with_index do |a, i|
301
+ raise "not an array of arrays!" unless a.is_a?(Array)
302
+ write_array_to_row(a, r + i, c, style)
303
+ end
304
+ end
305
+ ### @end
306
+
307
+ # Comment from xlwt:
308
+ ## Stand-alone merge of previously written cells.
309
+ ## Problems: (1) style to be used should be existing style of
310
+ ## the top-left cell, not an arg.
311
+ ## (2) should ensure that any previous data value in
312
+ ## non-top-left cells is nobbled.
313
+ ## Note: if a cell is set by a data record then later
314
+ ## is referenced by a [MUL]BLANK record, Excel will blank
315
+ ## out the cell on the screen, but OOo & Gnu will not
316
+ ## blank it out. Need to do something better than writing
317
+ ## multiple records. In the meantime, avoid this method and use
318
+ ## write_merge() instead.
319
+ def merge(r1, r2, c1, c2, style = @parent.styles.default_style)
320
+ row(r1).write_blanks(c1 + 1, c2, style) if c2 > c1
321
+ ((r1+1)...(r2+1)).each do |r|
322
+ row(r).write_blanks(c1, c2, style)
323
+ end
324
+ @merged_ranges << [r1, r2, c1, c2]
325
+ end
326
+
327
+ def write_merge(r1, r2, c1, c2, label="", style = @parent.styles.default_style)
328
+ write(r1, c1, label, style)
329
+ merge(r1, r2, c1, c2, style)
330
+ end
331
+
332
+ ### @export "to-biff"
333
+ def to_biff
334
+ result = []
335
+ result << Biff8BOFRecord.new(Biff8BOFRecord::WORKSHEET).to_biff
336
+ # Calc Settings
337
+ result << CalcModeRecord.new(@calc_mode).to_biff
338
+ result << CalcCountRecord.new(@calc_count & 0xFFFF).to_biff
339
+ result << RefModeRecord.new(@rc_ref_mode & 0x01).to_biff
340
+ result << IterationRecord.new(@iterations_on & 0x01).to_biff
341
+ result << DeltaRecord.new(@delta).to_biff
342
+ result << SaveRecalcRecord.new(@save_recalc & 0x01).to_biff
343
+
344
+ result << guts_record
345
+ result << default_row_height_record
346
+ result << wsbool_record
347
+ result << @cols.sort.collect {|k, v| v.to_biff }.join
348
+ result << dimensions_rec
349
+ ### @end
350
+
351
+ # Print Settings
352
+ result << PrintHeadersRecord.new(@print_headers).to_biff
353
+ result << PrintGridLinesRecord.new(@print_grid).to_biff
354
+ result << GridSetRecord.new(@grid_set).to_biff
355
+ result << HorizontalPageBreaksRecord.new(@horz_page_breaks.collect {|b| b.is_a?(Integer) ? [b, 0, -1] : b }).to_biff
356
+ result << VerticalPageBreaksRecord.new(@vert_page_breaks.collect {|b| b.is_a?(Integer) ? [b, 0, -1] : b }).to_biff
357
+ result << HeaderRecord.new(@header_str).to_biff
358
+ result << FooterRecord.new(@footer_str).to_biff
359
+ result << HCenterRecord.new(@print_centered_horz).to_biff
360
+ result << VCenterRecord.new(@print_centered_vert).to_biff
361
+ result << LeftMarginRecord.new(@left_margin).to_biff
362
+ result << RightMarginRecord.new(@right_margin).to_biff
363
+ result << TopMarginRecord.new(@top_margin).to_biff
364
+ result << BottomMarginRecord.new(@bottom_margin).to_biff
365
+ result << setup_page_record
366
+
367
+ # Protection Settings
368
+ result << ProtectRecord.new(as_numeric(@protect)).to_biff()
369
+ result << ScenarioProtectRecord.new(as_numeric(@scen_protect)).to_biff()
370
+ result << WindowProtectRecord.new(as_numeric(@wnd_protect)).to_biff()
371
+ result << ObjectProtectRecord.new(as_numeric(@obj_protect)).to_biff()
372
+ result << PasswordRecord.new(@password).to_biff()
373
+
374
+ ### @export "to-biff-rows"
375
+ keys = @rows.keys.sort
376
+ keys.each do |i|
377
+ result << @rows[i].to_biff
378
+ result << @rows[i].cells_biff
379
+ end
380
+ ### @end
381
+
382
+ # @charts.each do |c|
383
+ # result << c.to_biff
384
+ # end
385
+ result << MergedCellsRecord.new(@merged_ranges).to_biff
386
+ result << @bmp_rec
387
+ result << window_2_record
388
+ result << panes_record
389
+ # result << hyperlink_table_record
390
+ result << EOFRecord.new.to_biff
391
+
392
+ result.join
393
+ end
394
+
395
+ def guts_record
396
+ max_row_level = @rows.values.inject(-1) {|level, row| row.level > level ? row.level : level }
397
+ max_col_level = @cols.values.inject(-1) {|level, col| col.level > level ? col.level : level }
398
+
399
+ row_visible_levels = @rows.empty? ? 0 : max_row_level + 1
400
+ col_visible_levels = @cols.empty? ? 0 : max_col_level + 1
401
+
402
+ GutsRecord.new(@row_gut_width, @col_gut_height, row_visible_levels, col_visible_levels).to_biff
403
+ end
404
+
405
+ def default_row_height_record
406
+ options = 0x00
407
+ options |= (@default_row_height_mismatch & 0x01) << 0
408
+ options |= (@default_row_hidden & 0x01) << 1
409
+ options |= (@default_row_space_above & 0x01) << 2
410
+ options |= (@default_row_space_below & 0x01) << 3
411
+
412
+ DefaultRowHeight.new(options, @row_default_height).to_biff
413
+ end
414
+
415
+ def wsbool_record
416
+ options = 0x00
417
+ options |= (@show_auto_page_breaks & 0x01) << 0
418
+ options |= (@dialogue_sheet & 0x01) << 4
419
+ options |= (@auto_style_outline & 0x01) << 5
420
+ options |= (@outline_below & 0x01) << 6
421
+ options |= (@outline_right & 0x01) << 7
422
+ options |= (@fit_num_pages & 0x01) << 8
423
+ options |= (@show_row_outline & 0x01) << 10
424
+ options |= (@show_col_outline & 0x01) << 11
425
+ options |= (@alt_expr_eval & 0x01) << 14
426
+ options |= (@alt_formula_entries & 0x01) << 15
427
+
428
+ WSBoolRecord.new(options).to_biff
429
+ end
430
+
431
+ def dimensions_rec
432
+ first_used_row = 0
433
+ last_used_row = 0
434
+ first_used_col = 0
435
+ last_used_col = 0
436
+
437
+ if !@rows.empty?
438
+ first_used_row = @rows.keys.sort.first
439
+ last_used_row = @rows.keys.sort.last
440
+ first_used_col = 0xFFFFFFFF
441
+ last_used_col = 0
442
+ end
443
+
444
+ first_used_col = @rows.values.inject(first_used_col) {|min_col, r| r.min_col_index < min_col ? min_col = r.min_col_index : min_col }
445
+ last_used_col = @rows.values.inject(last_used_col) {|max_col, r| r.max_col_index > max_col ? max_col = r.max_col_index : max_col }
446
+
447
+ DimensionsRecord.new(first_used_row, last_used_row, first_used_col, last_used_col).to_biff
448
+ end
449
+
450
+ def setup_page_record
451
+ setup_page_options = (@print_in_rows & 0x01) << 0
452
+ setup_page_options |= (@portrait & 0x01) << 1
453
+ setup_page_options |= (0x00 & 0x01) << 2
454
+ setup_page_options |= (@print_not_colour & 0x01) << 3
455
+ setup_page_options |= (@print_draft & 0x01) << 4
456
+ setup_page_options |= (@print_notes & 0x01) << 5
457
+ setup_page_options |= (0x00 & 0x01) << 6
458
+ setup_page_options |= (0x01 & 0x01) << 7
459
+ setup_page_options |= (@print_notes_at_end & 0x01) << 9
460
+ setup_page_options |= (@print_omit_errors & 0x03) << 10
461
+
462
+ args = [
463
+ @paper_size_code,
464
+ @print_scaling,
465
+ @start_page_number,
466
+ @fit_width_to_pages,
467
+ @fit_height_to_pages,
468
+ setup_page_options,
469
+ @print_hres,
470
+ @print_vres,
471
+ @header_margin,
472
+ @footer_margin,
473
+ @copies_num
474
+ ]
475
+ SetupPageRecord.new(*args).to_biff
476
+ end
477
+
478
+ def window_2_record
479
+ options = 0
480
+ options |= (as_numeric(@show_formulas ) & 0x01) << 0
481
+ options |= (as_numeric(@show_grid ) & 0x01) << 1
482
+ options |= (as_numeric(@show_headers ) & 0x01) << 2
483
+ options |= (as_numeric(@panes_frozen ) & 0x01) << 3
484
+ options |= (as_numeric(@show_empty_as_zero ) & 0x01) << 4
485
+ options |= (as_numeric(@auto_colour_grid ) & 0x01) << 5
486
+ options |= (as_numeric(@cols_right_to_left ) & 0x01) << 6
487
+ options |= (as_numeric(@show_outline ) & 0x01) << 7
488
+ options |= (as_numeric(@remove_splits ) & 0x01) << 8
489
+ options |= (as_numeric(@selected ) & 0x01) << 9
490
+ options |= (as_numeric(@sheet_visible ) & 0x01) << 10
491
+ options |= (as_numeric(@page_preview ) & 0x01) << 11
492
+
493
+ if @page_preview != 0
494
+ if @preview_magn == 0
495
+ scl_magn = 60
496
+ else
497
+ scl_magn = @preview_magn
498
+ end
499
+ else
500
+ scl_magn = @normal_magn
501
+ end
502
+
503
+ Window2Record.new(options, @first_visible_row, @first_visible_col, @grid_colour, @preview_magn, @normal_magn, scl_magn).to_biff
504
+ end
505
+
506
+ def panes_record
507
+ return '' if @vert_split_pos.nil? && @horz_split_pos.nil?
508
+ @vert_split_pos = 0 if @vert_split_pos.nil?
509
+ @horz_split_pos = 0 if @horz_split_pos.nil?
510
+ if @panes_frozen
511
+ @vert_split_first_visible = @vert_split_pos if @vert_split_first_visible.nil?
512
+ @horz_split_first_visible = @horz_split_pos if @horz_split_first_visible.nil?
513
+ else
514
+ @vert_split_first_visible = 0 if @vert_split_first_visible.nil?
515
+ @horz_split_first_visible = 0 if @horz_split_first_visible.nil?
516
+ # inspired by pyXLWriter
517
+ @horz_split_pos = 20 * @horz_split_pos + 255
518
+ @vert_split_pos = 113.879 * @vert_split_pos + 390
519
+ end
520
+ @split_active_pane = 0 if @vert_split_pos > 0 and @horz_split_pos > 0
521
+ @split_active_pane = 1 if @vert_split_pos < 0 and @horz_split_pos == 0
522
+ @split_active_pane = 2 if @vert_split_pos == 0 and @horz_split_pos > 0
523
+ @split_active_pane = 3
524
+
525
+ args = [@vert_split_pos, @horz_split_pos, @horz_split_first_visible, @vert_split_first_visible, @split_active_pane]
526
+ PanesRecord.new(*args).to_biff
527
+ end
528
+
529
+ def hyperlink_table_record
530
+ result = ''
531
+ return result if @links.nil?
532
+ @links.each do |a, b|
533
+ x, y = a
534
+ url, target, textmark, description = b
535
+ result += HyperlinkRecord.new(x, x, y, y, url, target, textmark, description).to_biff
536
+ result += QuicktipRecord(x, x, y, y).to_biff unless description.nil?
537
+ end
538
+ result
539
+ end
540
+
541
+ # Fetch the row indicated by index, or create it if necessary.
542
+ def row(index)
543
+ rows[index] ||= Row.new(index, self)
544
+ end
545
+
546
+ # Fetch the col indicated by index, or create it if necessary.
547
+ def col(index)
548
+ cols[index] ||= Column.new(index, self)
549
+ end
550
+ alias :column :col
551
+
552
+ def row_height(row)
553
+ if @rows.include?(row)
554
+ @rows[row].height_in_pixels
555
+ else
556
+ 17
557
+ end
558
+ end
559
+
560
+ def col_width(column_index)
561
+ if cols.keys.include?(column_index)
562
+ cols[column_index].width_in_pixels
563
+ else
564
+ 64
565
+ end
566
+ end
567
+
568
+ def insert_bitmap(filename, row, col, x = 0, y = 0, scale_x = 1, scale_y = 1)
569
+ bmp = ImDataBmpRecord.new(filename)
570
+ obj = ObjBmpRecord.new(row, col, self, bmp, x, y, scale_x, scale_y)
571
+
572
+ @bmp_rec += obj.to_biff + bmp.to_biff
573
+ end
574
+ end