write_xlsx 0.59.0 → 0.60.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.
Files changed (41) hide show
  1. data/README.rdoc +11 -2
  2. data/bin/extract_vba.rb +5 -0
  3. data/lib/write_xlsx/package/comments.rb +0 -2
  4. data/lib/write_xlsx/package/packager.rb +3 -2
  5. data/lib/write_xlsx/package/vml.rb +250 -31
  6. data/lib/write_xlsx/package/xml_writer_simple.rb +1 -1
  7. data/lib/write_xlsx/version.rb +1 -1
  8. data/lib/write_xlsx/workbook.rb +28 -18
  9. data/lib/write_xlsx/worksheet.rb +139 -4
  10. data/test/package/vml/test_write_div.rb +1 -1
  11. data/test/package/vml/test_write_fill.rb +10 -2
  12. data/test/package/vml/test_write_path.rb +10 -2
  13. data/test/package/vml/test_write_shapetype.rb +9 -1
  14. data/test/package/vml/test_write_textbox.rb +1 -1
  15. data/test/regression/test_button01.rb +23 -0
  16. data/test/regression/test_button02.rb +29 -0
  17. data/test/regression/test_button03.rb +24 -0
  18. data/test/regression/test_button04.rb +25 -0
  19. data/test/regression/test_button05.rb +28 -0
  20. data/test/regression/test_button06.rb +28 -0
  21. data/test/regression/test_button07.rb +32 -0
  22. data/test/regression/test_escapes07.rb +29 -0
  23. data/test/regression/test_escapes08.rb +30 -0
  24. data/test/regression/test_vml01.rb +29 -0
  25. data/test/regression/test_vml02.rb +31 -0
  26. data/test/regression/test_vml03.rb +40 -0
  27. data/test/regression/test_vml04.rb +41 -0
  28. data/test/regression/xlsx_files/button01.xlsx +0 -0
  29. data/test/regression/xlsx_files/button02.xlsx +0 -0
  30. data/test/regression/xlsx_files/button03.xlsx +0 -0
  31. data/test/regression/xlsx_files/button04.xlsx +0 -0
  32. data/test/regression/xlsx_files/button05.xlsx +0 -0
  33. data/test/regression/xlsx_files/button07.xlsm +0 -0
  34. data/test/regression/xlsx_files/escapes07.xlsx +0 -0
  35. data/test/regression/xlsx_files/escapes08.xlsx +0 -0
  36. data/test/regression/xlsx_files/vbaProject02.bin +0 -0
  37. data/test/regression/xlsx_files/vml01.xlsx +0 -0
  38. data/test/regression/xlsx_files/vml02.xlsx +0 -0
  39. data/test/regression/xlsx_files/vml03.xlsx +0 -0
  40. data/test/regression/xlsx_files/vml04.xlsx +0 -0
  41. metadata +54 -2
data/README.rdoc CHANGED
@@ -32,7 +32,7 @@ write_xlsx uses the same interface as writeexcel gem.
32
32
 
33
33
  Add this line to your application's Gemfile:
34
34
 
35
- gem 'writeexcel'
35
+ gem 'write_xlsx'
36
36
 
37
37
  And then execute:
38
38
 
@@ -40,7 +40,7 @@ And then execute:
40
40
 
41
41
  Or install it yourself as:
42
42
 
43
- $ gem install writeexcel
43
+ $ gem install write_xlsx
44
44
 
45
45
  == Synopsis
46
46
 
@@ -74,6 +74,15 @@ the first worksheet in an Excel XML spreadsheet called ruby.xlsx:
74
74
  workbook.close
75
75
 
76
76
  == Recent change
77
+ 2013-02-19 v0.60.0
78
+ Added Excel form buttons via the worksheet insert_button() method.
79
+ This allows the user to tie the button to an embedded macro imported
80
+ using add_vba_project().
81
+ The portal to the dungeon dimensions is now fully open.
82
+
83
+ bug fix in Worksheet#write_url
84
+ bug fix in bin/vba_extract.rb
85
+
77
86
  2013-02-17 v0.59.0
78
87
  Added macro support via VBA projects extracted from existing Excel
79
88
  xlsm files. User defined functions can be called from worksheets
data/bin/extract_vba.rb CHANGED
@@ -18,6 +18,11 @@ def extract_vba_project(src, dest, options = {})
18
18
  File.open(path, File::CREAT|File::WRONLY|File::BINARY) do |w|
19
19
  w.puts(is.read())
20
20
  end
21
+ # The mod data on vbaProject.bin isn't generally set correctly in the
22
+ # xlsm/zip file. This can cause issues on Windows so reset it to the
23
+ # current data.
24
+ ctime = Time.now
25
+ File::utime(ctime, ctime, path)
21
26
  break
22
27
  end
23
28
  end
@@ -167,8 +167,6 @@ def assemble_xml_file
167
167
  end
168
168
 
169
169
  def sorted_comments
170
- @sorted_comments if @sorted_comments
171
-
172
170
  @sorted_comments = []
173
171
  # We sort the comments by row and column but that isn't strictly required.
174
172
  @comments.keys.sort.each do |row|
@@ -42,6 +42,7 @@ def add_workbook(workbook)
42
42
  @sheet_names = workbook.sheetnames
43
43
  @chart_count = workbook.charts.size
44
44
  @drawing_count = workbook.drawings.size
45
+ @num_vml_files = workbook.num_vml_files
45
46
  @num_comment_files = workbook.num_comment_files
46
47
  @named_ranges = workbook.named_ranges
47
48
 
@@ -153,7 +154,7 @@ def write_chart_or_drawing_files(objects, filename)
153
154
  def write_vml_files
154
155
  index = 1
155
156
  @workbook.worksheets.each do |worksheet|
156
- next unless worksheet.has_comments?
157
+ next unless worksheet.has_vml?
157
158
  FileUtils.mkdir_p("#{@package_dir}/xl/drawings")
158
159
 
159
160
  vml = Package::Vml.new
@@ -273,7 +274,7 @@ def write_content_types_file
273
274
  (1 .. @chart_count).each { |i| content.add_chart_name("chart#{i}") }
274
275
  (1 .. @drawing_count).each { |i| content.add_drawing_name("drawing#{i}") }
275
276
 
276
- content.add_vml_name if @num_comment_files > 0
277
+ content.add_vml_name if @num_vml_files > 0
277
278
 
278
279
  (1 .. @table_count).each { |i| content.add_table_name("table#{i}") }
279
280
 
@@ -23,16 +23,27 @@ def assemble_xml_file(worksheet)
23
23
  # Write the o:shapelayout element.
24
24
  write_shapelayout(worksheet.vml_data_id)
25
25
 
26
- # Write the v:shapetype element.
27
- write_shapetype
28
-
29
26
  z_index = 1
30
27
  vml_shape_id = worksheet.vml_shape_id
31
- worksheet.comments_array.each do |comment|
32
- # Write the v:shape element.
33
- vml_shape_id += 1
34
- write_shape(vml_shape_id, z_index, comment)
35
- z_index += 1
28
+ unless worksheet.buttons_data.empty?
29
+ # Write the v:shapetype element.
30
+ write_button_shapetype
31
+ worksheet.buttons_data.each do |button|
32
+ # Write the v:shape element.
33
+ vml_shape_id += 1
34
+ write_button_shape(vml_shape_id, z_index, button)
35
+ z_index += 1
36
+ end
37
+ end
38
+ unless worksheet.comments_array.empty?
39
+ # Write the v:shapetype element.
40
+ write_comment_shapetype
41
+ worksheet.comments_array.each do |comment|
42
+ # Write the v:shape element.
43
+ vml_shape_id += 1
44
+ write_comment_shape(vml_shape_id, z_index, comment)
45
+ z_index += 1
46
+ end
36
47
  end
37
48
  end
38
49
  @writer.crlf
@@ -107,24 +118,40 @@ def write_idmap(data_id)
107
118
  #
108
119
  # Write the <v:shapetype> element.
109
120
  #
110
- def write_shapetype
111
- id = '_x0000_t202'
112
- coordsize = '21600,21600'
113
- spt = 202
114
- path = 'm,l,21600r21600,l21600,xe'
121
+ def write_comment_shapetype
122
+ attributes = [
123
+ 'id', '_x0000_t202',
124
+ 'coordsize', '21600,21600',
125
+ 'o:spt', 202,
126
+ 'path', 'm,l,21600r21600,l21600,xe'
127
+ ]
128
+
129
+ @writer.tag_elements('v:shapetype', attributes) do
130
+ # Write the v:stroke element.
131
+ write_stroke
132
+ # Write the v:path element.
133
+ write_comment_path('t', 'rect')
134
+ end
135
+ end
115
136
 
137
+ #
138
+ # Write the <v:shapetype> element.
139
+ #
140
+ def write_button_shapetype
116
141
  attributes = [
117
- 'id', id,
118
- 'coordsize', coordsize,
119
- 'o:spt', spt,
120
- 'path', path
142
+ 'id', '_x0000_t201',
143
+ 'coordsize', '21600,21600',
144
+ 'o:spt', 201,
145
+ 'path', 'm,l,21600r21600,l21600,xe'
121
146
  ]
122
147
 
123
148
  @writer.tag_elements('v:shapetype', attributes) do
124
149
  # Write the v:stroke element.
125
150
  write_stroke
126
151
  # Write the v:path element.
127
- write_path('t', 'rect')
152
+ write_button_path
153
+ # Write the o:lock element.
154
+ write_shapetype_lock
128
155
  end
129
156
  end
130
157
 
@@ -142,7 +169,7 @@ def write_stroke
142
169
  #
143
170
  # Write the <v:path> element.
144
171
  #
145
- def write_path(gradientshapeok, connecttype)
172
+ def write_comment_path(gradientshapeok, connecttype)
146
173
  attributes = []
147
174
 
148
175
  attributes << 'gradientshapeok' << 't' if gradientshapeok
@@ -151,10 +178,46 @@ def write_path(gradientshapeok, connecttype)
151
178
  @writer.empty_tag('v:path', attributes)
152
179
  end
153
180
 
181
+ #
182
+ # Write the <v:path> element.
183
+ #
184
+ def write_button_path
185
+ attributes = [
186
+ 'shadowok', 'f',
187
+ 'o:extrusionok', 'f',
188
+ 'strokeok', 'f',
189
+ 'fillok', 'f',
190
+ 'o:connecttype', 'rect'
191
+ ]
192
+ @writer.empty_tag('v:path', attributes)
193
+ end
194
+
195
+ #
196
+ # Write the <o:lock> element.
197
+ #
198
+ def write_shapetype_lock
199
+ attributes = [
200
+ 'v:ext', 'edit',
201
+ 'shapetype', 't'
202
+ ]
203
+ @writer.empty_tag('o:lock', attributes)
204
+ end
205
+
206
+ #
207
+ # Write the <o:lock> element.
208
+ #
209
+ def write_rotation_lock
210
+ attributes = [
211
+ 'v:ext', 'edit',
212
+ 'rotation', 't'
213
+ ]
214
+ @writer.empty_tag('o:lock', attributes)
215
+ end
216
+
154
217
  #
155
218
  # Write the <v:shape> element.
156
219
  #
157
- def write_shape(id, z_index, comment)
220
+ def write_comment_shape(id, z_index, comment)
158
221
  type = '#_x0000_t202'
159
222
  insetmode = 'auto'
160
223
  visibility = 'hidden'
@@ -199,15 +262,68 @@ def write_shape(id, z_index, comment)
199
262
 
200
263
  @writer.tag_elements('v:shape', attributes) do
201
264
  # Write the v:fill element.
202
- write_fill
265
+ write_comment_fill
203
266
  # Write the v:shadow element.
204
267
  write_shadow
205
268
  # Write the v:path element.
206
- write_path(nil, 'none')
269
+ write_comment_path(nil, 'none')
270
+ # Write the v:textbox element.
271
+ write_comment_textbox
272
+ # Write the x:ClientData element.
273
+ write_comment_client_data(comment)
274
+ end
275
+ end
276
+
277
+ #
278
+ # Write the <v:shape> element.
279
+ #
280
+ def write_button_shape(id, z_index, button)
281
+ type = '#_x0000_t201'
282
+
283
+ # Set the shape index.
284
+ id = "_x0000_s#{id}"
285
+
286
+ left, top, width, height = pixels_to_points(button[:_vertices])
287
+
288
+ left_str = float_to_str(left)
289
+ top_str = float_to_str(top)
290
+ width_str = float_to_str(width)
291
+ height_str = float_to_str(height)
292
+ z_index_str = float_to_str(z_index)
293
+
294
+ style =
295
+ 'position:absolute;' +
296
+ 'margin-left:' +
297
+ left_str + 'pt;' +
298
+ 'margin-top:' +
299
+ top_str + 'pt;' +
300
+ 'width:' +
301
+ width_str + 'pt;' +
302
+ 'height:' +
303
+ height_str + 'pt;' +
304
+ 'z-index:' +
305
+ z_index_str + ';' +
306
+ 'mso-wrap-style:tight'
307
+
308
+ attributes = [
309
+ 'id', id,
310
+ 'type', type,
311
+ 'style', style,
312
+ 'o:button', 't',
313
+ 'fillcolor', 'buttonFace [67]',
314
+ 'strokecolor', 'windowText [64]',
315
+ 'o:insetmode', 'auto'
316
+ ]
317
+
318
+ @writer.tag_elements('v:shape', attributes) do
319
+ # Write the v:fill element.
320
+ write_button_fill
321
+ # Write the o:lock element.
322
+ write_rotation_lock
207
323
  # Write the v:textbox element.
208
- write_textbox
324
+ write_button_textbox(button[:_font])
209
325
  # Write the x:ClientData element.
210
- write_client_data(comment)
326
+ write_button_client_data(button)
211
327
  end
212
328
  end
213
329
 
@@ -223,13 +339,28 @@ def float_to_str(float)
223
339
  #
224
340
  # Write the <v:fill> element.
225
341
  #
226
- def write_fill
342
+ def write_comment_fill
227
343
  color_2 = '#ffffe1'
228
344
  attributes = ['color2', color_2]
229
345
 
230
346
  @writer.empty_tag('v:fill', attributes)
231
347
  end
232
348
 
349
+ #
350
+ # Write the <v:fill> element.
351
+ #
352
+ def write_button_fill
353
+ color_2 = 'buttonFace [67]'
354
+ detectmouseclick = 't'
355
+
356
+ attributes = [
357
+ 'color2', color_2,
358
+ 'o:detectmouseclick', detectmouseclick
359
+ ]
360
+
361
+ @writer.empty_tag('v:fill', attributes)
362
+ end
363
+
233
364
  #
234
365
  # Write the <v:shadow> element.
235
366
  #
@@ -250,31 +381,67 @@ def write_shadow
250
381
  #
251
382
  # Write the <v:textbox> element.
252
383
  #
253
- def write_textbox
384
+ def write_comment_textbox
254
385
  style = 'mso-direction-alt:auto'
255
386
 
256
387
  attributes = ['style', style]
257
388
 
258
389
  @writer.tag_elements('v:textbox', attributes) do
259
390
  # Write the div element.
260
- write_div
391
+ write_div('left')
392
+ end
393
+ end
394
+
395
+ #
396
+ # Write the <v:textbox> element.
397
+ #
398
+ def write_button_textbox(font)
399
+ style = 'mso-direction-alt:auto'
400
+
401
+ attributes = ['style', style, 'o:singleclick', 'f']
402
+
403
+ @writer.tag_elements('v:textbox', attributes) do
404
+ # Write the div element.
405
+ write_div('center', font)
261
406
  end
262
407
  end
263
408
 
264
409
  #
265
410
  # Write the <div> element.
266
411
  #
267
- def write_div
268
- style = 'text-align:left'
412
+ def write_div(align, font = nil)
413
+ style = "text-align:#{align}"
269
414
  attributes = ['style', style]
270
415
 
271
- @writer.tag_elements('div', attributes) { }
416
+ @writer.tag_elements('div', attributes) do
417
+ if font
418
+ # Write the font element.
419
+ write_font(font)
420
+ end
421
+ end
422
+ end
423
+
424
+ #
425
+ # Write the <font> element.
426
+ #
427
+ def write_font(font)
428
+ caption = font[:_caption]
429
+ face = 'Calibri'
430
+ size = 220
431
+ color = '#000000'
432
+
433
+ attributes = [
434
+ 'face', face,
435
+ 'size', size,
436
+ 'color', color
437
+ ]
438
+ @writer.data_element('font', caption, attributes)
272
439
  end
273
440
 
274
441
  #
275
442
  # Write the <x:ClientData> element.
276
443
  #
277
- def write_client_data(comment)
444
+ def write_comment_client_data(comment)
278
445
  object_type = 'Note'
279
446
 
280
447
  attributes = ['ObjectType', object_type]
@@ -297,6 +464,30 @@ def write_client_data(comment)
297
464
  end
298
465
  end
299
466
 
467
+ #
468
+ # Write the <x:ClientData> element.
469
+ #
470
+ def write_button_client_data(button)
471
+ object_type = 'Button'
472
+
473
+ attributes = ['ObjectType', object_type]
474
+
475
+ @writer.tag_elements('x:ClientData', attributes) do
476
+ # Write the x:Anchor element.
477
+ write_anchor(button[:_vertices])
478
+ # Write the x:PrintObject element.
479
+ write_print_object
480
+ # Write the x:AutoFill element.
481
+ write_auto_fill
482
+ # Write the x:FmlaMacro element.
483
+ write_fmla_macro(button[:_macro])
484
+ # Write the x:TextHAlign element.
485
+ write_text_halign
486
+ # Write the x:TextVAlign element.
487
+ write_text_valign
488
+ end
489
+ end
490
+
300
491
  #
301
492
  # Write the <x:MoveWithCells> element.
302
493
  #
@@ -350,6 +541,34 @@ def write_row(data)
350
541
  def write_column(data)
351
542
  @writer.data_element('x:Column', data)
352
543
  end
544
+
545
+ #
546
+ # Write the <x:PrintObject> element.
547
+ #
548
+ def write_print_object
549
+ @writer.data_element('x:PrintObject', 'False')
550
+ end
551
+
552
+ #
553
+ # Write the <x:TextHAlign> element.
554
+ #
555
+ def write_text_halign
556
+ @writer.data_element('x:TextHAlign', 'Center')
557
+ end
558
+
559
+ #
560
+ # Write the <x:TextVAlign> element.
561
+ #
562
+ def write_text_valign
563
+ @writer.data_element('x:TextVAlign', 'Center')
564
+ end
565
+
566
+ #
567
+ # Write the <x:FmlaMacro> element.
568
+ #
569
+ def write_fmla_macro(data)
570
+ @writer.data_element('x:FmlaMacro', data)
571
+ end
353
572
  end
354
573
  end
355
574
  end