write_xlsx 0.97.0 → 0.99.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/Changes +27 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +2 -2
  5. data/lib/write_xlsx/chart.rb +25 -22
  6. data/lib/write_xlsx/chart/axis.rb +2 -2
  7. data/lib/write_xlsx/chart/legend.rb +14 -0
  8. data/lib/write_xlsx/chart/pie.rb +9 -7
  9. data/lib/write_xlsx/chartsheet.rb +30 -2
  10. data/lib/write_xlsx/format.rb +4 -4
  11. data/lib/write_xlsx/package/comments.rb +50 -47
  12. data/lib/write_xlsx/package/conditional_format.rb +9 -1
  13. data/lib/write_xlsx/package/table.rb +5 -0
  14. data/lib/write_xlsx/utility.rb +59 -1
  15. data/lib/write_xlsx/version.rb +1 -1
  16. data/lib/write_xlsx/workbook.rb +30 -5
  17. data/lib/write_xlsx/worksheet.rb +31 -13
  18. data/lib/write_xlsx/worksheet/data_validation.rb +10 -14
  19. data/test/chart/test_write_legend_pos.rb +9 -1
  20. data/test/chartsheet/test_write_sheet_protection.rb +91 -0
  21. data/test/package/comments/test_comments_01.rb +54 -0
  22. data/test/package/comments/test_comments_02.rb +54 -0
  23. data/test/perl_output/formats.xlsx +0 -0
  24. data/test/regression/images/happy.jpg +0 -0
  25. data/test/regression/test_array_formula03.rb +36 -0
  26. data/test/regression/test_autofilter08.rb +110 -0
  27. data/test/regression/test_autofilter09.rb +110 -0
  28. data/test/regression/test_autofilter10.rb +110 -0
  29. data/test/regression/test_chart_axis42.rb +44 -0
  30. data/test/regression/test_chart_axis43.rb +44 -0
  31. data/test/regression/test_chart_legend03.rb +41 -0
  32. data/test/regression/test_chart_legend04.rb +41 -0
  33. data/test/regression/test_chart_legend05.rb +41 -0
  34. data/test/regression/test_chart_legend06.rb +41 -0
  35. data/test/regression/test_chart_legend07.rb +38 -0
  36. data/test/regression/test_comment13.rb +36 -0
  37. data/test/regression/test_cond_format19.rb +64 -0
  38. data/test/regression/test_cond_format20.rb +43 -0
  39. data/test/regression/test_format15.rb +26 -0
  40. data/test/regression/test_image36.rb +26 -0
  41. data/test/regression/test_table23.rb +56 -0
  42. data/test/regression/xlsx_files/array_formula03.xlsx +0 -0
  43. data/test/regression/xlsx_files/autofilter08.xlsx +0 -0
  44. data/test/regression/xlsx_files/autofilter09.xlsx +0 -0
  45. data/test/regression/xlsx_files/autofilter10.xlsx +0 -0
  46. data/test/regression/xlsx_files/chart_axis42.xlsx +0 -0
  47. data/test/regression/xlsx_files/chart_axis43.xlsx +0 -0
  48. data/test/regression/xlsx_files/chart_legend03.xlsx +0 -0
  49. data/test/regression/xlsx_files/chart_legend04.xlsx +0 -0
  50. data/test/regression/xlsx_files/chart_legend05.xlsx +0 -0
  51. data/test/regression/xlsx_files/chart_legend06.xlsx +0 -0
  52. data/test/regression/xlsx_files/chart_legend07.xlsx +0 -0
  53. data/test/regression/xlsx_files/comment13.xlsx +0 -0
  54. data/test/regression/xlsx_files/cond_format19.xlsx +0 -0
  55. data/test/regression/xlsx_files/cond_format20.xlsx +0 -0
  56. data/test/regression/xlsx_files/format15.xlsx +0 -0
  57. data/test/regression/xlsx_files/image36.xlsx +0 -0
  58. data/test/regression/xlsx_files/table23.xlsx +0 -0
  59. data/test/workbook/test_write_workbook_view.rb +36 -0
  60. data/test/worksheet/test_write_data_validation_02.rb +17 -0
  61. data/test/worksheet/test_write_sheet_view.rb +19 -1
  62. metadata +79 -4
  63. data/test/package/comments/test_write_text_t.rb +0 -44
@@ -749,7 +749,15 @@ def write_cf_rule
749
749
  write_formula_tag(maximum)
750
750
  end
751
751
  else
752
- write_cf_rule_formula_tag(value)
752
+ quoted_value = value.to_s
753
+ numeric_regex = /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/
754
+ # String "Cell" values must be quoted, apart from ranges.
755
+ if !(quoted_value =~ /(\$?)([A-Z]{1,3})(\$?)(\d+)/) &&
756
+ !(quoted_value =~ numeric_regex) &&
757
+ !(quoted_value =~ /^".*"$/)
758
+ quoted_value = (%Q!"#{value}"!)
759
+ end
760
+ write_cf_rule_formula_tag(quoted_value)
753
761
  end
754
762
  end
755
763
  end
@@ -240,6 +240,11 @@ def handle_the_function_for_the_table_row(row2, col_data, col_num, user_data)
240
240
  # Convert a table total function to a worksheet formula.
241
241
  #
242
242
  def table_function_to_formula(function, col_name)
243
+ col_name = col_name.gsub(/'/, "''").
244
+ gsub(/#/, "'#").
245
+ gsub(/\[/, "'[").
246
+ gsub(/\]/, "']")
247
+
243
248
  subtotals = {
244
249
  :average => 101,
245
250
  :countNums => 102,
@@ -119,7 +119,7 @@ def check_dimensions(row, col)
119
119
  # nil if the date is invalid.
120
120
  #
121
121
  def convert_date_time(date_time_string) #:nodoc:
122
- date_time = date_time_string.sub(/^\s+/, '').sub(/\s+$/, '').sub(/Z$/, '')
122
+ date_time = date_time_string.to_s.sub(/^\s+/, '').sub(/\s+$/, '').sub(/Z$/, '')
123
123
 
124
124
  # Check for invalid date char.
125
125
  return nil if date_time =~ /[^0-9T:\-\.Z]/
@@ -361,6 +361,64 @@ def float_to_str(float)
361
361
  end
362
362
  end
363
363
 
364
+ #
365
+ # Convert user defined legend properties to the structure required internally.
366
+ #
367
+ def legend_properties(params)
368
+ legend = Writexlsx::Chart::Legend.new
369
+
370
+ legend.position = params[:position] || 'right'
371
+ legend.delete_series = params[:delete_series]
372
+ legend.font = convert_font_args(params[:font])
373
+
374
+ # Set the legend layout.
375
+ legend.layout = layout_properties(params[:layout])
376
+
377
+ # Turn off the legend.
378
+ if params[:none]
379
+ legend.position = 'none'
380
+ end
381
+
382
+ # Set the line properties for the legend.
383
+ line = line_properties(params[:line])
384
+
385
+ # Allow 'border' as a synonym for 'line'.
386
+ if params[:border]
387
+ line = line_properties(params[:border])
388
+ end
389
+
390
+ # Set the fill properties for the legend.
391
+ fill = fill_properties(params[:fill])
392
+
393
+ # Set the pattern properties for the legend.
394
+ pattern = pattern_properties(params[:pattern])
395
+
396
+ # Set the gradient fill properties for the legend.
397
+ gradient = gradient_properties(params[:gradient])
398
+
399
+ # Pattern fill overrides solid fill.
400
+ if pattern
401
+ fill = nil
402
+ end
403
+
404
+ # Gradient fill overrides solid and pattern fills.
405
+ if gradient
406
+ pattern = nil
407
+ fill = nil
408
+ end
409
+
410
+ # Set the legend layout.
411
+ layout = layout_properties(params[:layout])
412
+
413
+ legend.line = line
414
+ legend.fill = fill
415
+ legend.pattern = pattern
416
+ legend.gradient = gradient
417
+ legend.layout = layout
418
+
419
+ @legend = legend
420
+ end
421
+
364
422
  #
365
423
  # Convert user defined layout properties to the format required internally.
366
424
  #
@@ -1 +1 @@
1
- WriteXLSX_VERSION = "0.97.0"
1
+ WriteXLSX_VERSION = "0.99.0"
@@ -123,7 +123,7 @@ def initialize(file, *option_params)
123
123
  @y_window = 15
124
124
  @window_width = 16095
125
125
  @window_height = 9660
126
- @tab_ratio = 500
126
+ @tab_ratio = 600
127
127
  @excel2003_style = options[:excel2003_style] || false
128
128
  @table_count = 0
129
129
  @image_types = {}
@@ -784,6 +784,19 @@ def set_size(width = nil, height = nil)
784
784
  end
785
785
  end
786
786
 
787
+ #
788
+ # Set the ratio of space for worksheet tabs.
789
+ #
790
+ def set_tab_ratio(tab_ratio = nil)
791
+ return if !tab_ratio
792
+
793
+ if tab_ratio < 0 || tab_ratio > 100
794
+ raise "Tab ratio outside range: 0 <= zoom <= 100"
795
+ else
796
+ @tab_ratio = (tab_ratio * 10).to_i
797
+ end
798
+ end
799
+
787
800
  #
788
801
  # The set_properties method can be used to set the document properties
789
802
  # of the Excel file created by WriteXLSX. These properties are visible
@@ -1308,7 +1321,7 @@ def write_workbook_view #:nodoc:
1308
1321
  ['windowWidth', @window_width],
1309
1322
  ['windowHeight', @window_height]
1310
1323
  ]
1311
- if @tab_ratio != 500
1324
+ if @tab_ratio != 600
1312
1325
  attributes << ['tabRatio', @tab_ratio]
1313
1326
  end
1314
1327
  if @firstsheet > 0
@@ -1512,9 +1525,17 @@ def prepare_num_formats #:nodoc:
1512
1525
  # string but would evaluate to zero.
1513
1526
  #
1514
1527
  if num_format.to_s =~ /^\d+$/ && num_format.to_s !~ /^0+\d/
1528
+ # Number format '0' is indexed as 1 in Excel.
1529
+ if num_format == 0
1530
+ num_format = 1
1531
+ end
1515
1532
  # Index to a built-in number format.
1516
1533
  format.num_format_index = num_format
1517
1534
  next
1535
+ elsif num_format.to_s == 'General'
1536
+ # The 'General' format has an number format index of 0.
1537
+ format.num_format_index = 0
1538
+ next
1518
1539
  end
1519
1540
 
1520
1541
  if num_formats[num_format]
@@ -2064,16 +2085,20 @@ def process_jpg(data, filename)
2064
2085
  offset = 2
2065
2086
  data_length = data.bytesize
2066
2087
 
2067
- # Search through the image data to read the height and width in the
2068
- # 0xFFC0/C2 element. Also read the DPI in the 0xFFE0 element.
2088
+ # Search through the image data to read the JPEG markers.
2069
2089
  while offset < data_length
2070
2090
  marker = data[offset+0, 2].unpack("n")[0]
2071
2091
  length = data[offset+2, 2].unpack("n")[0]
2072
2092
 
2073
- if marker == 0xFFC0 || marker == 0xFFC2
2093
+ # Read the height and width in the 0xFFCn elements
2094
+ # (Except C4, C8 and CC which aren't SOF markers).
2095
+ if (marker & 0xFFF0) == 0xFFC0 &&
2096
+ marker != 0xFFC4 && marker != 0xFFCC
2074
2097
  height = data[offset+5, 2].unpack("n")[0]
2075
2098
  width = data[offset+7, 2].unpack("n")[0]
2076
2099
  end
2100
+
2101
+ # Read the DPI in the 0xFFE0 element.
2077
2102
  if marker == 0xFFE0
2078
2103
  units = data[offset + 11, 1].unpack("C")[0]
2079
2104
  x_density = data[offset + 12, 2].unpack("n")[0]
@@ -312,14 +312,15 @@ def initialize(workbook, index, name) #:nodoc:
312
312
 
313
313
  @page_setup = PageSetup.new
314
314
 
315
- @screen_gridlines = true
316
- @show_zeros = true
317
- @dim_rowmin = nil
318
- @dim_rowmax = nil
319
- @dim_colmin = nil
320
- @dim_colmax = nil
321
- @selections = []
322
- @panes = []
315
+ @screen_gridlines = true
316
+ @show_zeros = true
317
+ @dim_rowmin = nil
318
+ @dim_rowmax = nil
319
+ @dim_colmin = nil
320
+ @dim_colmax = nil
321
+ @selections = []
322
+ @panes = []
323
+ @hide_row_col_headers = 0
323
324
 
324
325
  @tab_color = 0
325
326
 
@@ -5344,6 +5345,13 @@ def print_row_col_headers(headers = true)
5344
5345
  # end
5345
5346
  end
5346
5347
 
5348
+ #
5349
+ # Set the option to hide the row and column headers in Excel.
5350
+ #
5351
+ def hide_row_col_headers
5352
+ @hide_row_col_headers = 1
5353
+ end
5354
+
5347
5355
  #
5348
5356
  # The fit_to_pages() method is used to fit the printed area to a specific
5349
5357
  # number of pages both vertically and horizontally. If the printed area
@@ -6757,9 +6765,12 @@ def write_sheet_views #:nodoc:
6757
6765
 
6758
6766
  def write_sheet_view #:nodoc:
6759
6767
  attributes = []
6760
- # Hide screen gridlines if required
6768
+ # Hide screen gridlines if required.
6761
6769
  attributes << ['showGridLines', 0] unless @screen_gridlines
6762
6770
 
6771
+ # Hide the row/column headers.
6772
+ attributes << ['showRowColHeaders', 0] if ptrue?(@hide_row_col_headers)
6773
+
6763
6774
  # Hide zeroes in cells.
6764
6775
  attributes << ['showZeros', 0] unless show_zeros?
6765
6776
 
@@ -7294,13 +7305,20 @@ def write_filter_column(col_id, type, *filters) #:nodoc:
7294
7305
  # Write the <filters> element.
7295
7306
  #
7296
7307
  def write_filters(*filters) #:nodoc:
7297
- if filters.size == 1 && filters[0] == 'blanks'
7308
+ non_blanks = filters.reject { |filter| filter =~ /^blanks$/i }
7309
+ attributes = []
7310
+
7311
+ if filters != non_blanks
7312
+ attributes = [ ['blank', 1] ]
7313
+ end
7314
+
7315
+ if filters.size == 1 && non_blanks.empty?
7298
7316
  # Special case for blank cells only.
7299
- @writer.empty_tag('filters', [ ['blank', 1] ])
7317
+ @writer.empty_tag('filters', attributes)
7300
7318
  else
7301
7319
  # General case.
7302
- @writer.tag_elements('filters') do
7303
- filters.each { |filter| write_filter(filter) }
7320
+ @writer.tag_elements('filters', attributes) do
7321
+ non_blanks.sort.each { |filter| write_filter(filter) }
7304
7322
  end
7305
7323
  end
7306
7324
  end
@@ -219,10 +219,17 @@ def check_valid_citeria_types
219
219
  end
220
220
 
221
221
  def convert_date_time_value_if_required
222
- @date_1904 = date_1904?
223
222
  if @validate == 'date' || @validate == 'time'
224
- unless convert_date_time_value(:value) && convert_date_time_value(:maximum)
225
- raise WriteXLSXOptionParameterError, "Invalid date/time value."
223
+ date_time = convert_date_time(@value)
224
+ if date_time
225
+ @value = date_time
226
+ end
227
+
228
+ if @maximum
229
+ date_time = convert_date_time(@maximum)
230
+ if date_time
231
+ @maximum = date_time
232
+ end
226
233
  end
227
234
  end
228
235
  end
@@ -292,17 +299,6 @@ def valid_criteria_type # :nodoc:
292
299
  }
293
300
  end
294
301
 
295
- def convert_date_time_value(key) # :nodoc:
296
- value = instance_variable_get("@#{key}")
297
- if value && value.to_s =~ /T/
298
- date_time = convert_date_time(value)
299
- instance_variable_set("@#{key}", date_time) if date_time
300
- date_time
301
- else
302
- true
303
- end
304
- end
305
-
306
302
  def date_1904?
307
303
  @date_1904
308
304
  end
@@ -7,9 +7,17 @@ def setup
7
7
  @chart = Writexlsx::Chart.new('Bar')
8
8
  end
9
9
 
10
- def test_write_legend_pos
10
+ def test_write_legend_pos_default
11
11
  expected = '<c:legendPos val="r"/>'
12
12
  result = @chart.__send__('write_legend_pos', 'r')
13
13
  assert_equal(expected, result)
14
14
  end
15
+
16
+ def test_write_legend_overlay_top_right
17
+ expected = '<c:legend><c:legendPos val="tr"/><c:layout/><c:overlay val="1"/></c:legend>'
18
+ @chart.set_legend(:position => 'overlay_top_right')
19
+ @chart.__send__('write_legend')
20
+ result = @chart.instance_variable_get(:@writer).string
21
+ assert_equal(expected, result)
22
+ end
15
23
  end
@@ -0,0 +1,91 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'helper'
3
+
4
+ class TestChartsheetWriteSheetProtection < Minitest::Test
5
+ def setup
6
+ workbook = WriteXLSX.new(StringIO.new)
7
+ workbook.add_chart(:type => 'line')
8
+ @chartsheet = workbook.sheets.first
9
+ end
10
+
11
+ def test_chartsheet_write_sheet_protection
12
+ expected = '<sheetProtection password="83AF" content="1" objects="1"/>'
13
+
14
+ @chartsheet.protect('', {})
15
+ result = @chartsheet.__send__(:write_sheet_protection)
16
+ assert_equal(expected_to_array(expected), got_to_array(result))
17
+ end
18
+
19
+ def test_chartsheet_write_sheet_protection
20
+ expected = '<sheetProtection password="83AF" content="1" objects="1"/>'
21
+
22
+ @chartsheet.protect('password', {})
23
+ result = @chartsheet.__send__(:write_sheet_protection)
24
+ assert_equal(expected_to_array(expected), got_to_array(result))
25
+ end
26
+
27
+ def test_chartsheet_write_sheet_protection_without_password_and_content
28
+ expected = '<sheetProtection content="1"/>'
29
+
30
+ @chartsheet.protect('', {:objects => 0})
31
+ result = @chartsheet.__send__(:write_sheet_protection)
32
+ assert_equal(expected_to_array(expected), got_to_array(result))
33
+ end
34
+
35
+ def test_chartsheet_write_sheet_protection_with_opjects0_option
36
+ expected = '<sheetProtection password="83AF" content="1"/>'
37
+
38
+ @chartsheet.protect('password', { :objects => 0 })
39
+ result = @chartsheet.__send__(:write_sheet_protection)
40
+ assert_equal(expected_to_array(expected), got_to_array(result))
41
+ end
42
+
43
+ def test_chartsheet_write_sheet_protection_without_password
44
+ expected = '<sheetProtection objects="1"/>'
45
+
46
+ @chartsheet.protect('', { :content => 0 })
47
+ result = @chartsheet.__send__(:write_sheet_protection)
48
+ assert_equal(expected_to_array(expected), got_to_array(result))
49
+ end
50
+
51
+ def test_chartsheet_write_sheet_protection_without_password_and_content_option
52
+ expected = ''
53
+
54
+ @chartsheet.protect('', { :content => 0, :objects => 0 })
55
+ result = @chartsheet.__send__(:write_sheet_protection) || ''
56
+ assert_equal(expected_to_array(expected), got_to_array(result))
57
+ end
58
+
59
+ def test_chartsheet_write_sheet_protection_with_password_and_content_objects_option
60
+ expected = '<sheetProtection password="83AF"/>'
61
+
62
+ @chartsheet.protect('password', { :content => 0, :objects => 0 })
63
+ result = @chartsheet.__send__(:write_sheet_protection)
64
+ assert_equal(expected_to_array(expected), got_to_array(result))
65
+ end
66
+
67
+ def test_chartsheet_write_sheet_protection_with_password_full_options
68
+ expected = '<sheetProtection password="83AF" content="1" objects="1"/>'
69
+
70
+ options = {
71
+ :objects => 1,
72
+ :scenarios => 1,
73
+ :format_cells => 1,
74
+ :format_columns => 1,
75
+ :format_rows => 1,
76
+ :insert_columns => 1,
77
+ :insert_rows => 1,
78
+ :insert_hyperlinks => 1,
79
+ :delete_columns => 1,
80
+ :delete_rows => 1,
81
+ :select_locked_cells => 0,
82
+ :sort => 1,
83
+ :autofilter => 1,
84
+ :pivot_tables => 1,
85
+ :select_unlocked_cells => 0
86
+ }
87
+ @chartsheet.protect('password', options)
88
+ result = @chartsheet.__send__(:write_sheet_protection)
89
+ assert_equal(expected_to_array(expected), got_to_array(result))
90
+ end
91
+ end
@@ -0,0 +1,54 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'helper'
3
+
4
+ class TestComments01 < Minitest::Test
5
+ def setup
6
+ @workbook = WriteXLSX.new(StringIO.new)
7
+ @worksheet = @workbook.add_worksheet('')
8
+ end
9
+
10
+ ###############################################################################
11
+ #
12
+ # Test the _assemble_xml_file() method.
13
+ #
14
+ def test_assemble_xml_file
15
+ @worksheet.write_comment(
16
+ 1, 1, 'Some text',
17
+ :author => 'John', :visible => nil, :color => 81,
18
+ :font => 'Tahoma', :font_size => 8, :font_family => 2
19
+ )
20
+
21
+ comments = @worksheet.comments
22
+ comments.assemble_xml_file
23
+ result = got_to_array(comments.instance_variable_get(:@writer).string)
24
+
25
+ expected = expected_to_array(expected_xml)
26
+ assert_equal(expected, result)
27
+ end
28
+
29
+ def expected_xml
30
+ <<EOS
31
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
32
+ <comments xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
33
+ <authors>
34
+ <author>John</author>
35
+ </authors>
36
+ <commentList>
37
+ <comment ref="B2" authorId="0">
38
+ <text>
39
+ <r>
40
+ <rPr>
41
+ <sz val="8"/>
42
+ <color indexed="81"/>
43
+ <rFont val="Tahoma"/>
44
+ <family val="2"/>
45
+ </rPr>
46
+ <t>Some text</t>
47
+ </r>
48
+ </text>
49
+ </comment>
50
+ </commentList>
51
+ </comments>
52
+ EOS
53
+ end
54
+ end