write_xlsx 0.97.0 → 0.99.0

Sign up to get free protection for your applications and to get access to all the features.
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