write_xlsx 0.59.0 → 0.60.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -112,7 +112,7 @@ def escape_attributes(str = '')
112
112
  end
113
113
 
114
114
  def escape_data(str = '')
115
- if str =~ /[&<>"]/
115
+ if str =~ /[&<>]/
116
116
  str.gsub(/&/, '&amp;').
117
117
  gsub(/</, '&lt;').
118
118
  gsub(/>/, '&gt;')
@@ -1,5 +1,5 @@
1
1
  require 'write_xlsx/workbook'
2
2
 
3
3
  class WriteXLSX < Writexlsx::Workbook
4
- VERSION = "0.59.0"
4
+ VERSION = "0.60.0"
5
5
  end
@@ -20,7 +20,8 @@ class Workbook
20
20
  attr_writer :firstsheet
21
21
  attr_reader :palette
22
22
  attr_reader :font_count, :num_format_count, :border_count, :fill_count, :custom_colors
23
- attr_reader :worksheets, :sheetnames, :charts, :drawings, :num_comment_files, :named_ranges
23
+ attr_reader :worksheets, :sheetnames, :charts, :drawings
24
+ attr_reader :num_comment_files, :num_vml_files, :named_ranges
24
25
  attr_reader :doc_properties
25
26
  attr_reader :image_types, :images
26
27
  attr_reader :shared_strings
@@ -100,6 +101,7 @@ def initialize(file, default_formats = {})
100
101
  @custom_colors = []
101
102
  @doc_properties = {}
102
103
  @local_time = Time.now
104
+ @num_vml_files = 0
103
105
  @num_comment_files = 0
104
106
  @optimization = 0
105
107
  @x_window = 240
@@ -1084,7 +1086,9 @@ def write_file_version #:nodoc:
1084
1086
  end
1085
1087
 
1086
1088
  def write_workbook_pr #:nodoc:
1087
- attributes = date_1904? ? ['date1904', 1] : []
1089
+ attributes = []
1090
+ attributes << 'codeName' << @vba_codename if ptrue?(@vba_codename)
1091
+ attributes << 'date1904' << 1 if date_1904?
1088
1092
  attributes << 'defaultThemeVersion' << 124226
1089
1093
  @writer.empty_tag('workbookPr', attributes)
1090
1094
  end
@@ -1207,10 +1211,14 @@ def store_workbook #:nodoc:
1207
1211
  # Set the active sheet.
1208
1212
  @worksheets[@activesheet].activate
1209
1213
 
1210
- prepare_comments # Prepare the worksheet cell comments.
1211
- prepare_defined_names # Set the defined names for the worksheets such as Print Titles.
1212
- prepare_drawings # Prepare the drawings, charts and images.
1213
- add_chart_data # Add cached data to charts.
1214
+ # Prepare the worksheet VML elements such as comments and buttons.
1215
+ prepare_vml_objects
1216
+ # Set the defined names for the worksheets such as Print Titles.
1217
+ prepare_defined_names
1218
+ # Prepare the drawings, charts and images.
1219
+ prepare_drawings
1220
+ # Add cached data to charts.
1221
+ add_chart_data
1214
1222
 
1215
1223
  # Package the workbook.
1216
1224
  packager = Package::Packager.new
@@ -1458,31 +1466,33 @@ def prepare_defined_names #:nodoc:
1458
1466
  end
1459
1467
 
1460
1468
  #
1461
- # Iterate through the worksheets and set up the comment data.
1469
+ # Iterate through the worksheets and set up the VML objects.
1462
1470
  #
1463
- def prepare_comments #:nodoc:
1464
- comment_id = 0
1465
- vml_data_id = 1
1466
- vml_shape_id = 1024
1471
+ def prepare_vml_objects #:nodoc:
1472
+ comment_id = 0
1473
+ vml_data_id = 1
1474
+ vml_shape_id = 1024
1475
+ vml_files = 0
1476
+ comment_files = 0
1467
1477
 
1468
1478
  @worksheets.each do |sheet|
1469
- next unless sheet.has_comments?
1479
+ next unless sheet.has_vml?
1480
+ vml_files += 1
1481
+ comment_files += 1 if sheet.has_comments?
1470
1482
 
1471
1483
  comment_id += 1
1472
- sheet.set_external_vml_links(comment_id)
1473
- sheet.set_external_comment_links(comment_id)
1474
- sheet.set_vml_data_id(vml_data_id)
1475
- sheet.vml_shape_id = vml_shape_id
1484
+ count = sheet.prepare_vml_objects(vml_data_id, vml_shape_id, comment_id)
1476
1485
 
1477
1486
  # Each VML file should start with a shape id incremented by 1024.
1478
1487
  vml_data_id += 1 * ( ( 1024 + sheet.comments_count ) / 1024.0 ).to_i
1479
1488
  vml_shape_id += 1024 * ( ( 1024 + sheet.comments_count ) / 1024.0 ).to_i
1480
1489
  end
1481
1490
 
1482
- @num_comment_files = comment_id
1491
+ @num_vml_files = vml_files
1492
+ @num_comment_files = comment_files
1483
1493
 
1484
1494
  # Add a font format for cell comments.
1485
- if comment_id > 0
1495
+ if comment_files > 0
1486
1496
  format = Format.new(
1487
1497
  @xf_format_indices,
1488
1498
  @dxf_format_indices,
@@ -356,6 +356,7 @@ def orientation?
356
356
  attr_accessor :vml_shape_id, :rel_count, :hlink_refs # :nodoc:
357
357
  attr_reader :comments_author # :nodoc:
358
358
  attr_accessor :dxf_priority # :nodoc:
359
+ attr_reader :vba_codename # :nodoc:
359
360
 
360
361
  def initialize(workbook, index, name) #:nodoc:
361
362
  @writer = Package::XMLWriterSimple.new
@@ -421,7 +422,9 @@ def initialize(workbook, index, name) #:nodoc:
421
422
 
422
423
  @merge = []
423
424
 
425
+ @has_vml = false
424
426
  @comments = Package::Comments.new(self)
427
+ @buttons_array = []
425
428
 
426
429
  @validations = []
427
430
 
@@ -2102,6 +2105,8 @@ def write_comment(*args)
2102
2105
  check_dimensions(row, col)
2103
2106
  store_row_col_max_min_values(row, col)
2104
2107
 
2108
+ @has_vml = true
2109
+
2105
2110
  # Process the properties of the cell comment.
2106
2111
  @comments.add(Package::Comment.new(@workbook, self, row, col, string, options))
2107
2112
  end
@@ -2643,8 +2648,21 @@ def write_url(*args)
2643
2648
  # External links to URLs and to other Excel workbooks have slightly
2644
2649
  # different characteristics that we have to account for.
2645
2650
  if link_type == 1
2646
- # Substiture white space in url.
2647
- url = url.sub(/[\s\x00]/, '%20')
2651
+ # Escape URL unless it looks already escaped.
2652
+ unless url =~ /%[0-9a-fA-F]{2}/
2653
+ # Escape the URL escape symbol.
2654
+ url = url.gsub(/%/, "%25")
2655
+
2656
+ # Escape whitespae in URL.
2657
+ url = url.gsub(/[\s\x00]/, '%20')
2658
+
2659
+ # Escape other special characters in URL.
2660
+ re = /(["<>\[\]`^{}])/
2661
+ while re =~ url
2662
+ match = $~[1]
2663
+ url = url.sub(re, sprintf("%%%x", match.ord))
2664
+ end
2665
+ end
2648
2666
 
2649
2667
  # Ordinary URL style external links don't have a "location" string.
2650
2668
  str = nil
@@ -3868,6 +3886,14 @@ def add_sparkline(param)
3868
3886
  @sparklines << sparkline
3869
3887
  end
3870
3888
 
3889
+ #
3890
+ # Insert a button form object into the worksheet.
3891
+ #
3892
+ def insert_button(*args)
3893
+ @buttons_array << button_params(*(row_col_notation(args)))
3894
+ @has_vml = 1
3895
+ end
3896
+
3871
3897
  #
3872
3898
  # :call-seq:
3873
3899
  # data_validation(cell_or_cell_range, options)
@@ -4729,6 +4755,10 @@ def comments_count # :nodoc:
4729
4755
  @comments.size
4730
4756
  end
4731
4757
 
4758
+ def has_vml? # :nodoc:
4759
+ @has_vml
4760
+ end
4761
+
4732
4762
  def has_comments? # :nodoc:
4733
4763
  !@comments.empty?
4734
4764
  end
@@ -4994,6 +5024,38 @@ def get_palette_color(index) #:nodoc:
4994
5024
  sprintf("FF%02X%02X%02X", *rgb[0, 3])
4995
5025
  end
4996
5026
 
5027
+ def buttons_data # :nodoc:
5028
+ @buttons_array
5029
+ end
5030
+
5031
+ #
5032
+ # Turn the HoH that stores the comments into an array for easier handling
5033
+ # and set the external links for comments and buttons.
5034
+ #
5035
+ def prepare_vml_objects(vml_data_id, vml_shape_id, comment_id)
5036
+ @external_vml_links <<
5037
+ [ '/vmlDrawing', "../drawings/vmlDrawing#{comment_id}.vml"]
5038
+
5039
+ if has_comments?
5040
+ @comments_array = @comments.sorted_comments
5041
+ @external_comment_links <<
5042
+ [ '/comments', "../comments#{comment_id}.xml" ]
5043
+ end
5044
+
5045
+ count = @comments.size
5046
+ start_data_id = vml_data_id
5047
+
5048
+ # The VML o:idmap data id contains a comma separated range when there is
5049
+ # more than one 1024 block of comments, like this: data="1,2".
5050
+ (1 .. (count / 1024)).each do |i|
5051
+ vml_data_id = "#{vml_data_id},#{start_data_id + i}"
5052
+ end
5053
+ @vml_data_id = vml_data_id
5054
+ @vml_shape_id = vml_shape_id
5055
+
5056
+ count
5057
+ end
5058
+
4997
5059
  private
4998
5060
 
4999
5061
  def check_for_valid_input_params(param)
@@ -5712,6 +5774,70 @@ def validate_shape(shape, index)
5712
5774
  end
5713
5775
  end
5714
5776
 
5777
+ #
5778
+ # This method handles the parameters passed to insert_button as well as
5779
+ # calculating the comment object position and vertices.
5780
+ #
5781
+ def button_params(row, col, params)
5782
+ button = { :_row => row, :_col => col }
5783
+
5784
+ button_number = 1 + @buttons_array.size
5785
+
5786
+ # Set the button caption.
5787
+ caption = params[:caption] || "Button #{button_number}"
5788
+
5789
+ button[:_font] = { :_caption => caption }
5790
+
5791
+ # Set the macro name.
5792
+ if params[:macro]
5793
+ button[:_macro] = "[0]!#{params[:macro]}"
5794
+ else
5795
+ button[:_macro] = "[0]!Button#{button_number}_Click"
5796
+ end
5797
+
5798
+ # Ensure that a width and height have been set.
5799
+ default_width = 64
5800
+ default_height = 20
5801
+ params[:width] = default_width if !params[:width]
5802
+ params[:height] = default_height if !params[:height]
5803
+
5804
+ # Set the x/y offsets.
5805
+ params[:x_offset] = 0 if !params[:x_offset]
5806
+ params[:y_offset] = 0 if !params[:y_offset]
5807
+
5808
+ # Scale the size of the comment box if required.
5809
+ if params[:x_scale]
5810
+ params[:width] = params[:width] * params[:x_scale]
5811
+ end
5812
+ if params[:y_scale]
5813
+ params[:height] = params[:height] * params[:y_scale]
5814
+ end
5815
+
5816
+ # Round the dimensions to the nearest pixel.
5817
+ params[:width] = (0.5 + params[:width]).to_i
5818
+ params[:height] = (0.5 + params[:height]).to_i
5819
+
5820
+ params[:start_row] = row
5821
+ params[:start_col] = col
5822
+
5823
+ # Calculate the positions of comment object.
5824
+ vertices = position_object_pixels(
5825
+ params[:start_col],
5826
+ params[:start_row],
5827
+ params[:x_offset],
5828
+ params[:y_offset],
5829
+ params[:width],
5830
+ params[:height]
5831
+ )
5832
+
5833
+ # Add the width and height for VML.
5834
+ vertices << [params[:width], params[:height]]
5835
+
5836
+ button[:_vertices] = vertices
5837
+
5838
+ button
5839
+ end
5840
+
5715
5841
  #
5716
5842
  # Based on the algorithm provided by Daniel Rentz of OpenOffice.
5717
5843
  #
@@ -5757,8 +5883,13 @@ def write_worksheet #:nodoc:
5757
5883
  # Write the <sheetPr> element for Sheet level properties.
5758
5884
  #
5759
5885
  def write_sheet_pr #:nodoc:
5760
- return if !fit_page? && !filter_on? && !tab_color? && !outline_changed?
5886
+ if !fit_page? && !filter_on? && !tab_color? &&
5887
+ !outline_changed? && !vba_codename?
5888
+ return
5889
+ end
5890
+ codename = @vba_codename
5761
5891
  attributes = []
5892
+ (attributes << 'codeName' << codename) if codename
5762
5893
  (attributes << 'filterMode' << 1) if filter_on?
5763
5894
 
5764
5895
  if fit_page? || tab_color? || outline_changed?
@@ -6678,7 +6809,7 @@ def write_drawing(id) #:nodoc:
6678
6809
  # Write the <legacyDrawing> element.
6679
6810
  #
6680
6811
  def write_legacy_drawing #:nodoc:
6681
- return unless has_comments?
6812
+ return unless @has_vml
6682
6813
 
6683
6814
  # Increment the relationship id for any drawings or comments.
6684
6815
  @rel_count += 1
@@ -7653,6 +7784,10 @@ def outline_changed?
7653
7784
  ptrue?(@outline_changed)
7654
7785
  end
7655
7786
 
7787
+ def vba_codename?
7788
+ ptrue?(@vba_codename)
7789
+ end
7790
+
7656
7791
  def zoom_scale_normal? #:nodoc:
7657
7792
  ptrue?(@zoom_scale_normal)
7658
7793
  end
@@ -6,7 +6,7 @@
6
6
  class TestWriteDiv < Test::Unit::TestCase
7
7
  def test_write_div
8
8
  vml = Writexlsx::Package::Vml.new
9
- vml.__send__('write_div')
9
+ vml.__send__('write_div', 'left')
10
10
  result = vml.instance_variable_get(:@writer).string
11
11
  expected = '<div style="text-align:left"></div>'
12
12
  assert_equal(expected, result)
@@ -4,11 +4,19 @@
4
4
  require 'write_xlsx/package/vml'
5
5
 
6
6
  class TestWriteFill < Test::Unit::TestCase
7
- def test_write_fill
7
+ def test_write_comment_fill
8
8
  vml = Writexlsx::Package::Vml.new
9
- vml.__send__('write_fill')
9
+ vml.__send__('write_comment_fill')
10
10
  result = vml.instance_variable_get(:@writer).string
11
11
  expected = '<v:fill color2="#ffffe1"/>'
12
12
  assert_equal(expected, result)
13
13
  end
14
+
15
+ def test_write_button_fill
16
+ vml = Writexlsx::Package::Vml.new
17
+ vml.__send__('write_button_fill')
18
+ result = vml.instance_variable_get(:@writer).string
19
+ expected = '<v:fill color2="buttonFace [67]" o:detectmouseclick="t"/>'
20
+ assert_equal(expected, result)
21
+ end
14
22
  end
@@ -6,7 +6,7 @@
6
6
  class TestWritePath < Test::Unit::TestCase
7
7
  def test_write_path
8
8
  vml = Writexlsx::Package::Vml.new
9
- vml.__send__('write_path', 't', 'rect')
9
+ vml.__send__('write_comment_path', 't', 'rect')
10
10
  result = vml.instance_variable_get(:@writer).string
11
11
  expected = '<v:path gradientshapeok="t" o:connecttype="rect"/>'
12
12
  assert_equal(expected, result)
@@ -14,9 +14,17 @@ def test_write_path
14
14
 
15
15
  def test_write_path_without_gradientshapeok
16
16
  vml = Writexlsx::Package::Vml.new
17
- vml.__send__('write_path', nil, 'none')
17
+ vml.__send__('write_comment_path', nil, 'none')
18
18
  result = vml.instance_variable_get(:@writer).string
19
19
  expected = '<v:path o:connecttype="none"/>'
20
20
  assert_equal(expected, result)
21
21
  end
22
+
23
+ def test_write_button_path
24
+ vml = Writexlsx::Package::Vml.new
25
+ vml.__send__('write_button_path')
26
+ result = vml.instance_variable_get(:@writer).string
27
+ expected = '<v:path shadowok="f" o:extrusionok="f" strokeok="f" fillok="f" o:connecttype="rect"/>'
28
+ assert_equal(expected, result)
29
+ end
22
30
  end
@@ -6,9 +6,17 @@
6
6
  class TestWriteShapetype < Test::Unit::TestCase
7
7
  def test_write_shapetype
8
8
  vml = Writexlsx::Package::Vml.new
9
- vml.__send__('write_shapetype')
9
+ vml.__send__('write_comment_shapetype')
10
10
  result = vml.instance_variable_get(:@writer).string
11
11
  expected = '<v:shapetype id="_x0000_t202" coordsize="21600,21600" o:spt="202" path="m,l,21600r21600,l21600,xe"><v:stroke joinstyle="miter"/><v:path gradientshapeok="t" o:connecttype="rect"/></v:shapetype>'
12
12
  assert_equal(expected, result)
13
13
  end
14
+
15
+ def test_write_shapetype2
16
+ vml = Writexlsx::Package::Vml.new
17
+ vml.__send__('write_button_shapetype')
18
+ result = vml.instance_variable_get(:@writer).string
19
+ expected = '<v:shapetype id="_x0000_t201" coordsize="21600,21600" o:spt="201" path="m,l,21600r21600,l21600,xe"><v:stroke joinstyle="miter"/><v:path shadowok="f" o:extrusionok="f" strokeok="f" fillok="f" o:connecttype="rect"/><o:lock v:ext="edit" shapetype="t"/></v:shapetype>'
20
+ assert_equal(expected, result)
21
+ end
14
22
  end
@@ -6,7 +6,7 @@
6
6
  class TestWriteTextbox < Test::Unit::TestCase
7
7
  def test_write_textbox
8
8
  vml = Writexlsx::Package::Vml.new
9
- vml.__send__('write_textbox')
9
+ vml.__send__('write_comment_textbox')
10
10
  result = vml.instance_variable_get(:@writer).string
11
11
  expected = '<v:textbox style="mso-direction-alt:auto"><div style="text-align:left"></div></v:textbox>'
12
12
  assert_equal(expected, result)
@@ -0,0 +1,23 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'helper'
3
+
4
+ class TestRegressionButton01 < Test::Unit::TestCase
5
+ def setup
6
+ setup_dir_var
7
+ end
8
+
9
+ def teardown
10
+ File.delete(@xlsx) if File.exist?(@xlsx)
11
+ end
12
+
13
+ def test_button01
14
+ @xlsx = 'button01.xlsx'
15
+ workbook = WriteXLSX.new(@xlsx)
16
+ worksheet = workbook.add_worksheet
17
+
18
+ worksheet.insert_button('C2', {})
19
+
20
+ workbook.close
21
+ compare_xlsx_for_regression(File.join(@regression_output, @xlsx), @xlsx)
22
+ end
23
+ end
@@ -0,0 +1,29 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'helper'
3
+
4
+ class TestRegressionButton02 < Test::Unit::TestCase
5
+ def setup
6
+ setup_dir_var
7
+ end
8
+
9
+ def teardown
10
+ File.delete(@xlsx) if File.exist?(@xlsx)
11
+ end
12
+
13
+ def test_button02
14
+ @xlsx = 'button02.xlsx'
15
+ workbook = WriteXLSX.new(@xlsx)
16
+ worksheet = workbook.add_worksheet
17
+
18
+ worksheet.insert_button('B4',
19
+ {
20
+ :x_offset => 4,
21
+ :y_offset => 3,
22
+ :caption => 'my text'
23
+ }
24
+ )
25
+
26
+ workbook.close
27
+ compare_xlsx_for_regression(File.join(@regression_output, @xlsx), @xlsx)
28
+ end
29
+ end