rubyXL 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/Gemfile +16 -0
  2. data/Gemfile.lock +34 -0
  3. data/LICENSE.txt +20 -0
  4. data/README +0 -0
  5. data/README.rdoc +19 -0
  6. data/Rakefile +53 -0
  7. data/VERSION +1 -0
  8. data/lib/.DS_Store +0 -0
  9. data/lib/Hash.rb +60 -0
  10. data/lib/cell.rb +360 -0
  11. data/lib/color.rb +14 -0
  12. data/lib/parser.rb +413 -0
  13. data/lib/private_class.rb +182 -0
  14. data/lib/rubyXL.rb +9 -0
  15. data/lib/test.html +1 -0
  16. data/lib/tests/test.rb +110 -0
  17. data/lib/tests/test10.rb +16 -0
  18. data/lib/tests/test2.rb +118 -0
  19. data/lib/tests/test3.rb +76 -0
  20. data/lib/tests/test4.rb +92 -0
  21. data/lib/tests/test5.rb +90 -0
  22. data/lib/tests/test6.rb +50 -0
  23. data/lib/tests/test7.rb +48 -0
  24. data/lib/tests/test8.rb +12 -0
  25. data/lib/tests/test9.rb +60 -0
  26. data/lib/workbook.rb +336 -0
  27. data/lib/worksheet.rb +1245 -0
  28. data/lib/writer/app_writer.rb +62 -0
  29. data/lib/writer/calc_chain_writer.rb +33 -0
  30. data/lib/writer/content_types_writer.rb +77 -0
  31. data/lib/writer/core_writer.rb +51 -0
  32. data/lib/writer/root_rels_writer.rb +25 -0
  33. data/lib/writer/shared_strings_writer.rb +44 -0
  34. data/lib/writer/styles_writer.rb +376 -0
  35. data/lib/writer/theme_writer.rb +346 -0
  36. data/lib/writer/workbook_rels_writer.rb +59 -0
  37. data/lib/writer/workbook_writer.rb +77 -0
  38. data/lib/writer/worksheet_writer.rb +208 -0
  39. data/lib/zip.rb +20 -0
  40. data/pkg/rubyXL-1.0.4.gem +0 -0
  41. data/rubyXL.gemspec +106 -0
  42. data/spec/lib/cell_spec.rb +359 -0
  43. data/spec/lib/color_spec.rb +14 -0
  44. data/spec/lib/hash_spec.rb +28 -0
  45. data/spec/lib/parser_spec.rb +49 -0
  46. data/spec/lib/workbook_spec.rb +51 -0
  47. data/spec/lib/worksheet_spec.rb +1650 -0
  48. metadata +222 -0
@@ -0,0 +1,14 @@
1
+ module RubyXL
2
+ class Color
3
+
4
+ #validates hex color code, no '#' allowed
5
+ def Color.validate_color(color)
6
+ if color =~ /^([a-f]|[A-F]|[0-9]){6}$/
7
+ return true
8
+ else
9
+ raise 'invalid color'
10
+ end
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,413 @@
1
+ require 'rubygems'
2
+ require 'nokogiri'
3
+ require 'zip/zip' #rubyzip
4
+ require File.expand_path(File.join(File.dirname(__FILE__),'html_generator'))
5
+ require File.expand_path(File.join(File.dirname(__FILE__),'Hash'))
6
+
7
+ module RubyXL
8
+
9
+ class Parser
10
+ attr_reader :data_only, :num_sheets
11
+
12
+ # converts cell string (such as "AA1") to matrix indices
13
+ def Parser.convert_to_index(cell_string)
14
+ index = Array.new(2)
15
+ index[0]=-1
16
+ index[1]=-1
17
+ if(cell_string =~ /^([A-Z]+)(\d+)$/)
18
+ one = $1.to_s
19
+ row = Integer($2) - 1 #-1 for 0 indexing
20
+ col = 0
21
+ i = 0
22
+ one = one.reverse #because of 26^i calculation
23
+ one.each_byte do |c|
24
+ int_val = c - 64 #converts A to 1
25
+ col += int_val * 26**(i)
26
+ i=i+1
27
+ end
28
+ col -= 1 #zer0 index
29
+ index[0] = row
30
+ index[1] = col
31
+ end
32
+ return index
33
+ end
34
+
35
+
36
+ # data_only allows only the sheet data to be parsed, so as to speed up parsing
37
+ def Parser.parse(file_path, data_only=false)
38
+ @data_only = data_only
39
+ files = Parser.decompress(file_path)
40
+ wb = Parser.fill_workbook(file_path, files)
41
+
42
+ if(files['sharedString'] != nil)
43
+ wb.num_strings = Integer(files['sharedString'].css('sst').attribute('count').value())
44
+ wb.size = Integer(files['sharedString'].css('sst').attribute('uniqueCount').value())
45
+
46
+ shared_strings = files['sharedString'].css('si t').children
47
+ wb.shared_strings = {}
48
+ shared_strings.each_with_index do |string,i|
49
+ wb.shared_strings[i] = string.to_s
50
+ wb.shared_strings[string.to_s] = i
51
+ end
52
+ end
53
+
54
+ unless @data_only
55
+ styles = files['styles'].css('cellXfs xf')
56
+ style_hash = Hash.xml_node_to_hash(files['styles'].root)
57
+ fill_styles(wb,style_hash)
58
+
59
+ #will be nil if these files do not exist
60
+ wb.external_links = files['externalLinks']
61
+ wb.drawings = files['drawings']
62
+ wb.printer_settings = files['printerSettings']
63
+ wb.worksheet_rels = files['worksheetRels']
64
+ wb.macros = files['vbaProject']
65
+ end
66
+
67
+ #for each worksheet:
68
+ #1. find the dimensions of the data matrix
69
+ #2. Fill in the matrix with data from worksheet/shared_string files
70
+ #3. Apply styles
71
+ wb.worksheets.each_index do |i|
72
+ Parser.fill_worksheet(wb,i,files,shared_strings)
73
+ end
74
+
75
+ return wb
76
+ end
77
+
78
+ private
79
+
80
+ #fills hashes for various styles
81
+ def Parser.fill_styles(wb,style_hash)
82
+ wb.num_fmts = style_hash[:numFmts]
83
+
84
+ ###FONTS###
85
+ wb.fonts = {}
86
+ if style_hash[:fonts][:attributes][:count]==1
87
+ style_hash[:fonts][:font] = [style_hash[:fonts][:font]]
88
+ end
89
+
90
+ style_hash[:fonts][:font].each_with_index do |f,i|
91
+ wb.fonts[i.to_s] = {:font=>f,:count=>0}
92
+ end
93
+
94
+ ###FILLS###
95
+ wb.fills = {}
96
+ if style_hash[:fills][:attributes][:count]==1
97
+ style_hash[:fills][:fill] = [style_hash[:fills][:fill]]
98
+ end
99
+
100
+ style_hash[:fills][:fill].each_with_index do |f,i|
101
+ wb.fills[i.to_s] = {:fill=>f,:count=>0}
102
+ end
103
+
104
+ ###BORDERS###
105
+ wb.borders = {}
106
+ if style_hash[:borders][:attributes][:count] == 1
107
+ style_hash[:borders][:border] = [style_hash[:borders][:border]]
108
+ end
109
+
110
+ style_hash[:borders][:border].each_with_index do |b,i|
111
+ wb.borders[i.to_s] = {:border=>b, :count=>0}
112
+ end
113
+
114
+ wb.cell_style_xfs = style_hash[:cellStyleXfs]
115
+ wb.cell_xfs = style_hash[:cellXfs]
116
+ wb.cell_styles = style_hash[:cellStyles]
117
+
118
+ wb.colors = style_hash[:colors]
119
+
120
+ #fills out count information for each font, fill, and border
121
+ if wb.cell_xfs[:xf].is_a?(::Hash)
122
+ wb.cell_xfs[:xf] = [wb.cell_xfs[:xf]]
123
+ end
124
+ wb.cell_xfs[:xf].each do |style|
125
+ id = style[:attributes][:fontId].to_s
126
+ unless id.nil?
127
+ wb.fonts[id][:count] += 1
128
+ end
129
+
130
+ id = style[:attributes][:fillId].to_s
131
+ unless id.nil?
132
+ wb.fills[id][:count] += 1
133
+ end
134
+
135
+ id = style[:attributes][:borderId].to_s
136
+ unless id.nil?
137
+ wb.borders[id][:count] += 1
138
+ end
139
+ end
140
+ end
141
+
142
+ # i is the sheet number
143
+ # files is the hash which includes information for each worksheet
144
+ # shared_strings has group of indexed strings which the cells reference
145
+ def Parser.fill_worksheet(wb,i,files,shared_strings)
146
+ wb.worksheets[i] = Parser.create_matrix(wb, i, files)
147
+ j = i+1
148
+
149
+ unless @data_only
150
+ hash = Hash.xml_node_to_hash(files[j].root)
151
+
152
+ wb.worksheets[i].sheet_view = hash[:sheetViews][:sheetView]
153
+
154
+ ##col styles##
155
+ col_data = hash[:cols]
156
+ unless col_data.nil?
157
+ wb.worksheets[i].cols=col_data[:col]
158
+ end
159
+ ##end col styles##
160
+
161
+ ##merge_cells##
162
+ merge_data = hash[:mergeCells]
163
+ unless merge_data.nil?
164
+ wb.worksheets[i].merged_cells = merge_data[:mergeCell]
165
+ end
166
+ ##end merge_cells##
167
+
168
+ ##sheet_view pane##
169
+ pane_data = hash[:sheetViews][:sheetView][:pane]
170
+ wb.worksheets[i].pane = pane_data
171
+ ##end sheet_view pane##
172
+
173
+ ##data_validation##
174
+ data_validation = hash[:dataValidations]
175
+ unless data_validation.nil?
176
+ data_validation = data_validation[:dataValidation]
177
+ end
178
+ wb.worksheets[i].validations = data_validation
179
+ ##end data_validation##
180
+
181
+ #extLst
182
+ wb.worksheets[i].extLst = hash[:extLst]
183
+ #extLst
184
+
185
+ ##legacy drawing##
186
+ drawing = hash[:legacyDrawing]
187
+ wb.worksheets[i].legacy_drawing = drawing
188
+ ##end legacy drawing
189
+ end
190
+
191
+ row_data = files[j].css('sheetData row')
192
+
193
+ if(row_data.to_s != "")
194
+ row_data.each do |row|
195
+
196
+ unless @data_only
197
+ ##row styles##
198
+ unless row.css('c').nil?
199
+ row_style = '0'
200
+ unless row.attribute('s').nil?
201
+ row_style = row.attribute('s').value.to_s
202
+ end
203
+
204
+ wb.worksheets[i].row_styles[row.attribute('r').to_s] = { :style => row_style.to_s }
205
+
206
+ unless row.attribute('ht').to_s == ""
207
+ wb.worksheets[i].change_row_height(Integer(row.attribute('r').to_s)-1,
208
+ Float(row.attribute('ht').to_s))
209
+ end
210
+ end
211
+ ##end row styles##
212
+ end
213
+
214
+ row = row.css('c')
215
+ row.each do |value|
216
+ cell_index = Parser.convert_to_index(value.attribute('r').to_s)
217
+ style_index = nil
218
+
219
+ data_type = value.attribute('t').to_s
220
+
221
+ if data_type == 's' #shared string
222
+ str_index = Integer(value.css('v').children.to_s)
223
+ cell_data = shared_strings[str_index].to_s
224
+ elsif data_type=='str' #raw string
225
+ cell_data = value.css('v').children.to_s
226
+ elsif data_type=='e' #error
227
+ cell_data = value.css('v').children.to_s
228
+ elsif value.css('v').to_s != "" #is number
229
+ data_type = ''
230
+ if(value.css('v').children.to_s =~ /\./) #is float
231
+ cell_data = Float(value.css('v').children.to_s)
232
+ else
233
+ cell_data = Integer(value.css('v').children.to_s)
234
+ end
235
+ else #no data
236
+ cell_data = nil
237
+ end
238
+ cell_formula = nil
239
+ if(value.css('f').to_s != "")
240
+ cell_formula = value.css('f').children.to_s
241
+ end
242
+
243
+ unless @data_only
244
+ style_index = Integer(value['s']) #nil goes to 0 (default)
245
+ else
246
+ style_index = 0
247
+ end
248
+
249
+ wb.worksheets[i].sheet_data[cell_index[0]][cell_index[1]] =
250
+ Cell.new(wb.worksheets[i],cell_index[0],cell_index[1],cell_data,cell_formula,
251
+ data_type,style_index)
252
+ cell = wb.worksheets[i].sheet_data[cell_index[0]][cell_index[1]]
253
+ end
254
+ end
255
+ end
256
+ end
257
+
258
+ def Parser.decompress(file_path)
259
+ #ensures it is an xlsx/xlsm file
260
+ if(file_path =~ /(.+)\.xls(x|m)/)
261
+ dir_path = $1.to_s
262
+ else
263
+ raise 'Not .xlsx or .xlsm excel file'
264
+ end
265
+
266
+ dir_path = File.join(File.dirname(dir_path), make_safe_name(Time.now.to_s))
267
+ #copies excel file to zip file in same directory
268
+ zip_path = dir_path + '.zip'
269
+
270
+ FileUtils.cp(file_path,zip_path)
271
+
272
+ MyZip.new.unzip(zip_path,dir_path)
273
+ File.delete(zip_path)
274
+
275
+ files = Hash.new
276
+
277
+ files['app'] = Nokogiri::XML.parse(File.read(File.join(dir_path,'docProps','app.xml')))
278
+ files['core'] = Nokogiri::XML.parse(File.read(File.join(dir_path,'docProps','core.xml')))
279
+
280
+ files['workbook'] = Nokogiri::XML.parse(File.read(File.join(dir_path,'xl','workbook.xml')))
281
+
282
+ if(File.exist?(File.join(dir_path,'xl','sharedStrings.xml')))
283
+ files['sharedString'] = Nokogiri::XML.parse(File.read(File.join(dir_path,'xl','sharedStrings.xml')))
284
+ end
285
+
286
+ unless @data_only
287
+ #preserves external links
288
+ if File.directory?(File.join(dir_path,'xl','externalLinks'))
289
+ files['externalLinks'] = {}
290
+ ext_links_path = File.join(dir_path,'xl','externalLinks')
291
+ files['externalLinks']['rels'] = []
292
+ dir = Dir.new(ext_links_path).entries.reject {|f| [".", "..", ".DS_Store", "_rels"].include? f}
293
+
294
+ dir.each_with_index do |link,i|
295
+ files['externalLinks'][i+1] = File.read(File.join(ext_links_path,link))
296
+ end
297
+
298
+ if File.directory?(File.join(ext_links_path,'_rels'))
299
+ dir = Dir.new(File.join(ext_links_path,'_rels')).entries.reject{|f| [".","..",".DS_Store"].include? f}
300
+ dir.each_with_index do |rel,i|
301
+ files['externalLinks']['rels'][i+1] = File.read(File.join(ext_links_path,'_rels',rel))
302
+ end
303
+ end
304
+ end
305
+
306
+ if File.directory?(File.join(dir_path,'xl','drawings'))
307
+ files['drawings'] = {}
308
+ drawings_path = File.join(dir_path,'xl','drawings')
309
+
310
+ dir = Dir.new(drawings_path).entries.reject {|f| [".", "..", ".DS_Store"].include? f}
311
+ dir.each_with_index do |draw,i|
312
+ files['drawings'][i+1] = File.read(File.join(drawings_path,draw))
313
+ end
314
+ end
315
+
316
+ if File.directory?(File.join(dir_path,'xl','printerSettings'))
317
+ files['printerSettings'] = {}
318
+ printer_path = File.join(dir_path,'xl','printerSettings')
319
+
320
+ dir = Dir.new(printer_path).entries.reject {|f| [".","..",".DS_Store"].include? f}
321
+
322
+ dir.each_with_index do |print, i|
323
+ files['printerSettings'][i+1] = File.open(File.join(printer_path,print), 'rb').read
324
+ end
325
+ end
326
+
327
+ if File.directory?(File.join(dir_path,"xl",'worksheets','_rels'))
328
+ files['worksheetRels'] = {}
329
+ worksheet_rels_path = File.join(dir_path,'xl','worksheets','_rels')
330
+
331
+ dir = Dir.new(worksheet_rels_path).entries.reject {|f| [".","..",".DS_Store"].include? f}
332
+ dir.each_with_index do |rel, i|
333
+ files['worksheetRels'][i+1] = File.read(File.join(worksheet_rels_path,rel))
334
+ end
335
+ end
336
+
337
+ if File.exist?(File.join(dir_path,'xl','vbaProject.bin'))
338
+ files['vbaProject'] = File.open(File.join(dir_path,"xl","vbaProject.bin"),'rb').read
339
+ end
340
+
341
+ files['styles'] = Nokogiri::XML.parse(File.read(File.join(dir_path,'xl','styles.xml')))
342
+ end
343
+
344
+ @num_sheets = files['workbook'].css('sheets').children.size
345
+ @num_sheets = Integer(@num_sheets)
346
+
347
+ #adds all worksheet xml files to files hash
348
+ i=1
349
+ 1.upto(@num_sheets) do
350
+ filename = 'sheet'+i.to_s
351
+ files[i] = Nokogiri::XML.parse(File.read(File.join(dir_path,'xl','worksheets',filename+'.xml')))
352
+ i=i+1
353
+ end
354
+
355
+ FileUtils.rm_rf(dir_path)
356
+
357
+ return files
358
+ end
359
+
360
+ def Parser.fill_workbook(file_path, files)
361
+ wb = Workbook.new([nil],file_path)
362
+
363
+ unless @data_only
364
+ wb.creator = files['core'].css('dc|creator').children.to_s
365
+ wb.modifier = files['core'].css('cp|last_modified_by').children.to_s
366
+ wb.created_at = files['core'].css('dcterms|created').children.to_s
367
+ wb.modified_at = files['core'].css('dcterms|modified').children.to_s
368
+
369
+ wb.company = files['app'].css('Company').children.to_s
370
+ wb.application = files['app'].css('Application').children.to_s
371
+ wb.appversion = files['app'].css('AppVersion').children.to_s
372
+ end
373
+
374
+ wb.shared_strings_XML = files['sharedString']
375
+
376
+ wb.worksheets = Array.new(@num_sheets) #array of Worksheet objs
377
+ wb
378
+ end
379
+
380
+ #sheet_names, dimensions
381
+ def Parser.create_matrix(wb,i, files)
382
+ sheet_names = files['app'].css('TitlesOfParts vt|vector vt|lpstr').children
383
+ sheet = Worksheet.new(wb,sheet_names[i].to_s,[])
384
+
385
+ dimensions = files[i+1].css('dimension').attribute('ref').to_s
386
+ if(dimensions =~ /^([A-Z]+\d+:)?([A-Z]+\d+)$/)
387
+ index = convert_to_index($2)
388
+
389
+ rows = index[0]+1
390
+ cols = index[1]+1
391
+
392
+ #creates matrix filled with nils
393
+ rows.times {sheet.sheet_data << Array.new(cols)}
394
+ else
395
+ raise 'invalid file'
396
+ end
397
+ sheet
398
+ end
399
+
400
+ def Parser.safe_filename(name, allow_mb_chars=false)
401
+ # "\w" represents [0-9A-Za-z_] plus any multi-byte char
402
+ regexp = allow_mb_chars ? /[^\w]/ : /[^0-9a-zA-Z\_]/
403
+ name.gsub(regexp, "_")
404
+ end
405
+
406
+ # Turns the passed in string into something safe for a filename
407
+ def Parser.make_safe_name(name, allow_mb_chars=false)
408
+ ext = safe_filename(File.extname(name), allow_mb_chars).gsub(/^_/, '.')
409
+ "#{safe_filename(name.gsub(ext, ""), allow_mb_chars)}#{ext}".gsub(/\(/, '_').gsub(/\)/, '_').gsub(/__+/, '_').gsub(/^_/, '').gsub(/_$/, '')
410
+ end
411
+
412
+ end
413
+ end
@@ -0,0 +1,182 @@
1
+ module RubyXL
2
+ class PrivateClass
3
+ private
4
+
5
+ #validate and modify methods
6
+ def validate_horizontal_alignment(alignment)
7
+ if alignment.to_s == '' || alignment == 'center' || alignment == 'distributed' || alignment == 'justify' || alignment == 'left' || alignment == 'right'
8
+ return true
9
+ end
10
+ raise 'Only center, distributed, justify, left, and right are valid horizontal alignments'
11
+ end
12
+
13
+ def validate_vertical_alignment(alignment)
14
+ if alignment.to_s == '' || alignment == 'center' || alignment == 'distributed' || alignment == 'justify' || alignment == 'top' || alignment == 'bottom'
15
+ return true
16
+ end
17
+ raise 'Only center, distributed, justify, top, and bottom are valid vertical alignments'
18
+ end
19
+
20
+ def validate_border(weight)
21
+ if weight.to_s == '' || weight == 'thin' || weight == 'thick' || weight == 'hairline' || weight == 'medium'
22
+ return true
23
+ end
24
+ raise 'Border weights must only be "hairline", "thin", "medium", or "thick"'
25
+ end
26
+
27
+ def validate_nonnegative(row_or_col)
28
+ if row_or_col < 0
29
+ raise 'Row and Column arguments must be nonnegative'
30
+ end
31
+ end
32
+
33
+ #modifies font array (copies,appends) then styles array (copies,appends)
34
+ #does not actually modify font object,
35
+ #allows method caller to do that (cell or worksheet)
36
+ def modify_font(workbook, style_index)
37
+ # xf_obj = workbook.get_style(style_index)
38
+ xf = workbook.get_style_attributes(workbook.get_style(style_index))
39
+
40
+ #modify fonts array
41
+ font_id = xf[:fontId]
42
+ font = workbook.fonts[font_id.to_s][:font]
43
+
44
+ #else, just change the attribute itself, done in calling method.
45
+ if workbook.fonts[font_id.to_s][:count] > 1 || font_id == 0
46
+ old_size = workbook.fonts.size.to_s
47
+ workbook.fonts[old_size] = {}
48
+ workbook.fonts[old_size][:font] = deep_copy(font)
49
+ workbook.fonts[old_size][:count] = 1
50
+ workbook.fonts[font_id.to_s][:count] -= 1
51
+
52
+ #modify styles array
53
+ font_id = old_size
54
+
55
+ if workbook.cell_xfs[:xf].is_a?Array
56
+ workbook.cell_xfs[:xf] << deep_copy({:attributes=>xf})
57
+ else
58
+ workbook.cell_xfs[:xf] = [workbook.cell_xfs[:xf], deep_copy({:attributes=>xf})]
59
+ end
60
+
61
+ xf = workbook.get_style_attributes(workbook.cell_xfs[:xf].last)
62
+ xf[:fontId] = font_id
63
+ xf[:applyFont] = '1'
64
+ workbook.cell_xfs[:attributes][:count] += 1
65
+ return workbook.cell_xfs[:xf].size-1 #returns new style_index
66
+ else
67
+ return style_index
68
+ end
69
+ end
70
+
71
+ #modifies fill array (copies, appends, adds color and solid attribute)
72
+ #then styles array (copies, appends)
73
+ def modify_fill(workbook, style_index, rgb)
74
+ xf_obj = workbook.get_style(style_index)
75
+ xf = workbook.get_style_attributes(xf_obj)
76
+ #modify fill array
77
+ fill_id = xf[:fillId]
78
+
79
+ fill = workbook.fills[fill_id.to_s][:fill]
80
+ if workbook.fills[fill_id.to_s][:count] > 1 || fill_id == 0 || fill_id == 1
81
+ old_size = workbook.fills.size.to_s
82
+ workbook.fills[old_size] = {}
83
+ workbook.fills[old_size][:fill] = deep_copy(fill)
84
+ workbook.fills[old_size][:count] = 1
85
+ workbook.fills[fill_id.to_s][:count] -= 1
86
+
87
+ change_wb_fill(workbook, old_size,rgb)
88
+
89
+ #modify styles array
90
+ fill_id = old_size
91
+ if workbook.cell_xfs[:xf].is_a?Array
92
+ workbook.cell_xfs[:xf] << deep_copy({:attributes=>xf})
93
+ else
94
+ workbook.cell_xfs[:xf] = [workbook.cell_xfs[:xf], deep_copy({:attributes=>xf})]
95
+ end
96
+ xf = workbook.get_style_attributes(workbook.cell_xfs[:xf].last)
97
+ xf[:fillId] = fill_id
98
+ xf[:applyFill] = '1'
99
+ workbook.cell_xfs[:attributes][:count] += 1
100
+ return workbook.cell_xfs[:xf].size-1
101
+ else
102
+ change_wb_fill(workbook, fill_id.to_s,rgb)
103
+ return style_index
104
+ end
105
+ end
106
+
107
+ def modify_border(workbook, style_index)
108
+ xf_obj = workbook.get_style(style_index)
109
+ xf = workbook.get_style_attributes(xf_obj)
110
+
111
+ border_id = Integer(xf[:borderId])
112
+ border = workbook.borders[border_id.to_s][:border]
113
+ if workbook.borders[border_id.to_s][:count] > 1 || border_id == 0 || border_id == 1
114
+ old_size = workbook.borders.size.to_s
115
+ workbook.borders[old_size] = {}
116
+ workbook.borders[old_size][:border] = deep_copy(border)
117
+ workbook.borders[old_size][:count] = 1
118
+
119
+ border_id = old_size
120
+
121
+ if workbook.cell_xfs[:xf].is_a?Array
122
+ workbook.cell_xfs[:xf] << deep_copy(xf_obj)
123
+ else
124
+ workbook.cell_xfs[:xf] = [workbook.cell_xfs[:xf], deep_copy(xf_obj)]
125
+ end
126
+
127
+ xf = workbook.get_style_attributes(workbook.cell_xfs[:xf].last)
128
+ xf[:borderId] = border_id
129
+ xf[:applyBorder] = '1'
130
+ workbook.cell_xfs[:attributes][:count] += 1
131
+ return workbook.cell_xfs[:xf].size-1
132
+ else
133
+ return style_index
134
+ end
135
+ end
136
+
137
+ #is_horizontal is true when doing horizontal alignment,
138
+ #false when doing vertical alignment
139
+ def modify_alignment(workbook, style_index, is_horizontal, alignment)
140
+ old_xf_obj = workbook.get_style(style_index)
141
+
142
+ xf_obj = deep_copy(old_xf_obj)
143
+
144
+ if xf_obj[:alignment].nil? || xf_obj[:alignment][:attributes].nil?
145
+ xf_obj[:alignment] = {:attributes=>{:horizontal=>nil, :vertical=>nil}}
146
+ end
147
+
148
+ if is_horizontal
149
+ xf_obj[:alignment][:attributes][:horizontal] = alignment.to_s
150
+ else
151
+ xf_obj[:alignment][:attributes][:vertical] = alignment.to_s
152
+ end
153
+
154
+ if workbook.cell_xfs[:xf].is_a?Array
155
+ workbook.cell_xfs[:xf] << deep_copy(xf_obj)
156
+ else
157
+ workbook.cell_xfs[:xf] = [workbook.cell_xfs[:xf], deep_copy(xf_obj)]
158
+ end
159
+
160
+ xf = workbook.get_style_attributes(workbook.cell_xfs[:xf].last)
161
+ xf[:applyAlignment] = '1'
162
+ workbook.cell_xfs[:attributes][:count] += 1
163
+ workbook.cell_xfs[:xf].size-1
164
+ end
165
+
166
+ #returns non-shallow copy of hash
167
+ def deep_copy(hash)
168
+ Marshal.load(Marshal.dump(hash))
169
+ end
170
+
171
+ def change_wb_fill(workbook, fill_index, rgb)
172
+ if workbook.fills[fill_index][:fill][:patternFill][:fgColor].nil?
173
+ workbook.fills[fill_index][:fill][:patternFill][:fgColor] = {:attributes => {:rgb => ''}}
174
+ end
175
+ workbook.fills[fill_index][:fill][:patternFill][:fgColor][:attributes][:rgb] = rgb
176
+
177
+ #previously none, doesn't show fill
178
+ workbook.fills[fill_index][:fill][:patternFill][:attributes][:patternType] = 'solid'
179
+ end
180
+
181
+ end
182
+ end