rubyXL 1.0.4

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.
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