write_xlsx 0.85.7 → 0.86.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 (62) hide show
  1. checksums.yaml +5 -5
  2. data/Changes +29 -0
  3. data/README.md +1 -1
  4. data/examples/colors.rb +47 -0
  5. data/examples/comments2.rb +1 -1
  6. data/examples/conditional_format.rb +60 -9
  7. data/examples/data_validate.rb +7 -7
  8. data/examples/panes.rb +1 -1
  9. data/examples/tab_colors.rb +1 -1
  10. data/examples/update_range_format_with_params.rb +33 -0
  11. data/lib/write_xlsx/chart.rb +1 -1
  12. data/lib/write_xlsx/format.rb +4 -5
  13. data/lib/write_xlsx/package/app.rb +12 -0
  14. data/lib/write_xlsx/package/comments.rb +4 -2
  15. data/lib/write_xlsx/package/conditional_format.rb +8 -2
  16. data/lib/write_xlsx/package/shared_strings.rb +12 -10
  17. data/lib/write_xlsx/package/styles.rb +1 -1
  18. data/lib/write_xlsx/package/table.rb +17 -5
  19. data/lib/write_xlsx/package/xml_writer_simple.rb +9 -8
  20. data/lib/write_xlsx/utility.rb +4 -2
  21. data/lib/write_xlsx/version.rb +1 -1
  22. data/lib/write_xlsx/workbook.rb +17 -14
  23. data/lib/write_xlsx/worksheet.rb +117 -15
  24. data/lib/write_xlsx/worksheet/cell_data.rb +3 -1
  25. data/lib/write_xlsx/worksheet/data_validation.rb +23 -11
  26. data/lib/write_xlsx/worksheet/hyperlink.rb +14 -10
  27. data/test/regression/images/red_208.png +0 -0
  28. data/test/regression/test_data_validation08.rb +24 -0
  29. data/test/regression/test_hyperlink22.rb +24 -0
  30. data/test/regression/test_hyperlink23.rb +24 -0
  31. data/test/regression/test_hyperlink24.rb +24 -0
  32. data/test/regression/test_image28.rb +27 -0
  33. data/test/regression/test_image29.rb +27 -0
  34. data/test/regression/test_image30.rb +27 -0
  35. data/test/regression/test_image31.rb +30 -0
  36. data/test/regression/test_image32.rb +28 -0
  37. data/test/regression/test_image33.rb +32 -0
  38. data/test/regression/test_properties02.rb +28 -0
  39. data/test/regression/test_update_range_format_with_params.rb +42 -0
  40. data/test/regression/xlsx_files/data_validation08.xlsx +0 -0
  41. data/test/regression/xlsx_files/hyperlink22.xlsx +0 -0
  42. data/test/regression/xlsx_files/hyperlink23.xlsx +0 -0
  43. data/test/regression/xlsx_files/hyperlink24.xlsx +0 -0
  44. data/test/regression/xlsx_files/image28.xlsx +0 -0
  45. data/test/regression/xlsx_files/image29.xlsx +0 -0
  46. data/test/regression/xlsx_files/image30.xlsx +0 -0
  47. data/test/regression/xlsx_files/image31.xlsx +0 -0
  48. data/test/regression/xlsx_files/image32.xlsx +0 -0
  49. data/test/regression/xlsx_files/image33.xlsx +0 -0
  50. data/test/regression/xlsx_files/properties02.xlsx +0 -0
  51. data/test/regression/xlsx_files/table18.xlsx +0 -0
  52. data/test/regression/xlsx_files/table19.xlsx +0 -0
  53. data/test/regression/xlsx_files/update_range_format_with_params.xlsx +0 -0
  54. data/test/run_test.rb +9 -0
  55. data/test/test_xml_writer_simple.rb +3 -3
  56. data/test/worksheet/test_cond_format_21.rb +90 -0
  57. data/test/worksheet/test_format.rb +17 -0
  58. data/test/worksheet/test_sparkline_12.rb +94 -0
  59. data/test/worksheet/test_update_format_methods.rb +111 -0
  60. data/test/worksheet/test_write_data_validation_02.rb +14 -1
  61. data/write_xlsx.gemspec +1 -1
  62. metadata +69 -6
@@ -49,7 +49,7 @@ def set_style_properties(xf_formats, palette, font_count, num_format_count, bord
49
49
  # based on the default or user defined values in the Workbook palette.
50
50
  #
51
51
  def palette_color(index)
52
- if index =~ /^#([0-9A-F]{6})$/i
52
+ if index.to_s =~ /^#([0-9A-F]{6})$/i
53
53
  "FF#{$1.upcase}"
54
54
  else
55
55
  "FF#{super(index)}"
@@ -10,7 +10,7 @@ class Table
10
10
 
11
11
  class ColumnData
12
12
  attr_reader :id
13
- attr_accessor :name, :format, :formula
13
+ attr_accessor :name, :format, :formula, :name_format
14
14
  attr_accessor :total_string, :total_function
15
15
 
16
16
  def initialize(id, param = {})
@@ -20,6 +20,7 @@ def initialize(id, param = {})
20
20
  @total_function = ''
21
21
  @formula = ''
22
22
  @format = nil
23
+ @name_format = nil
23
24
  @user_data = param[id-1] if param
24
25
  end
25
26
  end
@@ -93,10 +94,14 @@ def overrite_the_defaults_with_any_use_defined_values(col_id, col_data, col_num)
93
94
  if user_data[:header] && !user_data[:header].empty?
94
95
  col_data.name = user_data[:header]
95
96
  end
97
+
98
+ # Get the header format if defined.
99
+ col_data.name_format = user_data[:header_format]
100
+
96
101
  # Handle the column formula.
97
102
  handle_the_column_formula(
98
- col_data, col_num, user_data[:formula], user_data[:format]
99
- )
103
+ col_data, col_num, user_data[:formula], user_data[:format]
104
+ )
100
105
 
101
106
  # Handle the function for the total row.
102
107
  if user_data[:total_function]
@@ -123,7 +128,9 @@ def overrite_the_defaults_with_any_use_defined_values(col_id, col_data, col_num)
123
128
 
124
129
  def write_the_column_headers_to_the_worksheet(col_num, col_data)
125
130
  if @param[:header_row] != 0
126
- @worksheet.write_string(@row1, col_num, col_data.name)
131
+ @worksheet.write_string(
132
+ @row1, col_num, col_data.name, col_data.name_format
133
+ )
127
134
  end
128
135
  end
129
136
 
@@ -268,7 +275,12 @@ def set_the_table_style
268
275
  end
269
276
 
270
277
  def set_the_table_name
271
- @name = @param[:name] if @param[:name]
278
+ if @param[:name]
279
+ name = @param[:name]
280
+
281
+ raise "Name '#{name} in add_table cannot contain spaces" if name =~ /\s/
282
+ @name = @param[:name]
283
+ end
272
284
  end
273
285
 
274
286
  def set_the_table_and_autofilter_ranges
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ # frozen_string_literal: true
2
3
  #
3
4
  # XMLWriterSimple
4
5
  #
@@ -29,10 +30,9 @@ def tag_elements(tag, attributes = [])
29
30
  end
30
31
 
31
32
  def tag_elements_str(tag, attributes = [])
32
- str = ''
33
- str << start_tag_str(tag, attributes)
34
- str << yield
35
- str << end_tag_str(tag)
33
+ str = start_tag_str(tag, attributes) +
34
+ yield +
35
+ end_tag_str(tag)
36
36
  end
37
37
 
38
38
  def start_tag(tag, attr = [])
@@ -65,7 +65,7 @@ def empty_tag_encoded_str(tag, attr = [])
65
65
  end
66
66
 
67
67
  def data_element(tag, data, attr = [])
68
- tag_elements(tag, attr) { io_write("#{escape_data(data)}") }
68
+ tag_elements(tag, attr) { io_write(escape_data(data)) }
69
69
  end
70
70
 
71
71
  #
@@ -118,17 +118,18 @@ def key_vals(attribute)
118
118
  end
119
119
 
120
120
  def escape_attributes(str = '')
121
- return str if !(str =~ /["&<>]/)
121
+ return str if !(str.to_s =~ /["&<>\n]/)
122
122
 
123
123
  str.
124
124
  gsub(/&/, "&amp;").
125
125
  gsub(/"/, "&quot;").
126
126
  gsub(/</, "&lt;").
127
- gsub(/>/, "&gt;")
127
+ gsub(/>/, "&gt;").
128
+ gsub(/\n/, "&#xA;")
128
129
  end
129
130
 
130
131
  def escape_data(str = '')
131
- if str =~ /[&<>]/
132
+ if str.to_s =~ /[&<>]/
132
133
  str.gsub(/&/, '&amp;').
133
134
  gsub(/</, '&lt;').
134
135
  gsub(/>/, '&gt;')
@@ -1,4 +1,5 @@
1
1
  # -*- coding: utf-8 -*-
2
+ # frozen_string_literal: true
2
3
  require 'write_xlsx/col_name'
3
4
 
4
5
  module Writexlsx
@@ -243,7 +244,7 @@ def put_deprecate_message(method)
243
244
 
244
245
  # Check for a cell reference in A1 notation and substitute row and column
245
246
  def row_col_notation(args) # :nodoc:
246
- if args[0] =~ /^\D/
247
+ if args[0].to_s =~ /^\D/
247
248
  substitute_cellref(*args)
248
249
  else
249
250
  args
@@ -303,11 +304,12 @@ def write_color(writer, name, value) #:nodoc:
303
304
  writer.empty_tag('color', attributes)
304
305
  end
305
306
 
307
+ PERL_TRUE_VALUES = [false, nil, 0, "0", "", [], {}].freeze
306
308
  #
307
309
  # return perl's boolean result
308
310
  #
309
311
  def ptrue?(value)
310
- if [false, nil, 0, "0", "", [], {}].include?(value)
312
+ if PERL_TRUE_VALUES.include?(value)
311
313
  false
312
314
  else
313
315
  true
@@ -1 +1 @@
1
- WriteXLSX_VERSION = "0.85.7"
1
+ WriteXLSX_VERSION = "0.86.0"
@@ -1,4 +1,5 @@
1
1
  # -*- coding: utf-8 -*-
2
+ # frozen_string_literal: true
2
3
  require 'write_xlsx/package/xml_writer_simple'
3
4
  require 'write_xlsx/package/packager'
4
5
  require 'write_xlsx/sheets'
@@ -785,17 +786,18 @@ def set_properties(params)
785
786
 
786
787
  # List of valid input parameters.
787
788
  valid = {
788
- :title => 1,
789
- :subject => 1,
790
- :author => 1,
791
- :keywords => 1,
792
- :comments => 1,
793
- :last_author => 1,
794
- :created => 1,
795
- :category => 1,
796
- :manager => 1,
797
- :company => 1,
798
- :status => 1
789
+ :title => 1,
790
+ :subject => 1,
791
+ :author => 1,
792
+ :keywords => 1,
793
+ :comments => 1,
794
+ :last_author => 1,
795
+ :created => 1,
796
+ :category => 1,
797
+ :manager => 1,
798
+ :company => 1,
799
+ :status => 1,
800
+ :hyperlink_base => 1
799
801
  }
800
802
 
801
803
  # Check for valid input parameters.
@@ -926,7 +928,7 @@ def set_calc_mode(mode, calc_id = nil)
926
928
  #
927
929
  def set_custom_color(index, red = 0, green = 0, blue = 0)
928
930
  # Match a HTML #xxyyzz style parameter
929
- if red =~ /^#(\w\w)(\w\w)(\w\w)/
931
+ if red.to_s =~ /^#(\w\w)(\w\w)(\w\w)/
930
932
  red = $1.hex
931
933
  green = $2.hex
932
934
  blue = $3.hex
@@ -972,8 +974,9 @@ def date_1904? #:nodoc:
972
974
  # Add a string to the shared string table, if it isn't already there, and
973
975
  # return the string index.
974
976
  #
975
- def shared_string_index(str, params = {}) #:nodoc:
976
- @shared_strings.index(str, params)
977
+ EMPTY_HASH = {}.freeze
978
+ def shared_string_index(str) #:nodoc:
979
+ @shared_strings.index(str, EMPTY_HASH)
977
980
  end
978
981
 
979
982
  def str_unique # :nodoc:
@@ -1,4 +1,5 @@
1
1
  # -*- coding: utf-8 -*-
2
+ # frozen_string_literal: true
2
3
  require 'write_xlsx/package/xml_writer_simple'
3
4
  require 'write_xlsx/package/button'
4
5
  require 'write_xlsx/colors'
@@ -295,6 +296,7 @@ class Worksheet
295
296
  attr_reader :comments, :comments_author # :nodoc:
296
297
  attr_accessor :dxf_priority # :nodoc:
297
298
  attr_reader :vba_codename # :nodoc:
299
+ attr_writer :excel_version
298
300
 
299
301
  def initialize(workbook, index, name) #:nodoc:
300
302
  @writer = Package::XMLWriterSimple.new
@@ -720,7 +722,7 @@ def protect_default_settings # :nodoc:
720
722
  #
721
723
  def set_column(*args)
722
724
  # Check for a cell reference in A1 notation and substitute row and column
723
- if args[0] =~ /^\D/
725
+ if args[0].to_s =~ /^\D/
724
726
  row1, firstcol, row2, lastcol, *data = substitute_cellref(*args)
725
727
  else
726
728
  firstcol, lastcol, *data = args
@@ -2188,7 +2190,7 @@ def write_comment(*args)
2188
2190
  def write_number(*args)
2189
2191
  # Check for a cell reference in A1 notation and substitute row and column
2190
2192
  row, col, num, xf = row_col_notation(args)
2191
- raise WriteXLSXInsufficientArgumentError if [row, col, num].include?(nil)
2193
+ raise WriteXLSXInsufficientArgumentError if row.nil? || col.nil? || num.nil?
2192
2194
 
2193
2195
  # Check that row and col are valid and store max and min values
2194
2196
  check_dimensions(row, col)
@@ -2230,13 +2232,13 @@ def write_string(*args)
2230
2232
  # Check for a cell reference in A1 notation and substitute row and column
2231
2233
  row, col, str, xf = row_col_notation(args)
2232
2234
  str &&= str.to_s
2233
- raise WriteXLSXInsufficientArgumentError if [row, col, str].include?(nil)
2235
+ raise WriteXLSXInsufficientArgumentError if row.nil? || col.nil? || str.nil?
2234
2236
 
2235
2237
  # Check that row and col are valid and store max and min values
2236
2238
  check_dimensions(row, col)
2237
2239
  store_row_col_max_min_values(row, col)
2238
2240
 
2239
- index = shared_string_index(str[0, STR_MAX])
2241
+ index = shared_string_index(str.length > STR_MAX ? str[0, STR_MAX] : str)
2240
2242
 
2241
2243
  store_data_to_table(StringCellData.new(self, row, col, index, xf))
2242
2244
  end
@@ -2527,6 +2529,91 @@ def write_array_formula(*args)
2527
2529
  end
2528
2530
  end
2529
2531
 
2532
+ #
2533
+ # :call-seq:
2534
+ # update_format_with_params(row, col, format_params)
2535
+ #
2536
+ # Update formatting of the cell to the specified row and column (zero indexed).
2537
+ #
2538
+ # worksheet.update_format_with_params(0, 0, color: 'red')
2539
+ #
2540
+ # This method is used to update formatting of the cell keeping cell contents
2541
+ # and formatting.
2542
+ #
2543
+ # If the cell doesn't have CellData object, this method create a CellData
2544
+ # using write_blank method.
2545
+ # If the cell has CellData, this method fetch contents and format of cell from
2546
+ # the CellData object and recreate CellData using write method.
2547
+ #
2548
+ def update_format_with_params(*args)
2549
+ row, col, params = row_col_notation(args)
2550
+ raise WriteXLSXInsufficientArgumentError if row.nil? || col.nil? || params.nil?
2551
+
2552
+ # Check that row and col are valid and store max and min values
2553
+ check_dimensions(row, col)
2554
+ store_row_col_max_min_values(row, col)
2555
+
2556
+ format = nil
2557
+ cell_data = nil
2558
+ if @cell_data_table[row].nil? || @cell_data_table[row][col].nil?
2559
+ format = @workbook.add_format(params)
2560
+ write_blank(row, col, format)
2561
+ else
2562
+ if @cell_data_table[row][col].xf.nil?
2563
+ format = @workbook.add_format(params)
2564
+ cell_data = @cell_data_table[row][col]
2565
+ else
2566
+ format = @workbook.add_format
2567
+ cell_data = @cell_data_table[row][col]
2568
+ format.copy(cell_data.xf)
2569
+ format.set_format_properties(params)
2570
+ end
2571
+ # keep original value of cell
2572
+ if cell_data.is_a? FormulaCellData
2573
+ value = "=#{cell_data.token}"
2574
+ elsif cell_data.is_a? FormulaArrayCellData
2575
+ value = "{=#{cell_data.token}}"
2576
+ elsif cell_data.is_a? StringCellData
2577
+ value = @workbook.shared_strings.string(cell_data.data[:sst_id])
2578
+ else
2579
+ value = cell_data.data
2580
+ end
2581
+ write(row, col, value, format)
2582
+ end
2583
+ end
2584
+
2585
+ #
2586
+ # :call-seq:
2587
+ # update_range_format_with_params(row_first, col_first, row_last, col_last, format_params)
2588
+ #
2589
+ # Update formatting of cells in range to the specified row and column (zero indexed).
2590
+ #
2591
+ # worksheet.update_range_format_with_params(0, 0, 3, 3, color: 'red')
2592
+ #
2593
+ # This method is used to update formatting of multiple cells keeping cells' contents
2594
+ # and formatting.
2595
+ #
2596
+ #
2597
+ def update_range_format_with_params(*args)
2598
+ row_first, col_first, row_last, col_last, params = row_col_notation(args)
2599
+
2600
+ raise WriteXLSXInsufficientArgumentError if [row_first, col_first, row_last, col_last, params].include?(nil)
2601
+
2602
+ # Swap last row/col with first row/col as necessary
2603
+ row_first, row_last = row_last, row_first if row_first > row_last
2604
+ col_first, col_last = col_last, col_first if col_first > col_last
2605
+
2606
+ # Check that column number is valid and store the max value
2607
+ check_dimensions(row_last, col_last)
2608
+ store_row_col_max_min_values(row_last, col_last)
2609
+
2610
+ (row_first..row_last).each do |row|
2611
+ (col_first..col_last).each do |col|
2612
+ update_format_with_params(row, col, params)
2613
+ end
2614
+ end
2615
+ end
2616
+
2530
2617
  #
2531
2618
  # The outline_settings() method is used to control the appearance of
2532
2619
  # outlines in Excel. Outlines are described in
@@ -4202,7 +4289,7 @@ def conditional_formatting(*args)
4202
4289
  # :header
4203
4290
  # :formula
4204
4291
  # :total_string
4205
- # :total_function
4292
+ # :
4206
4293
  # :format
4207
4294
  #
4208
4295
  # The column data must be specified as an array of hash. For example to
@@ -4830,9 +4917,8 @@ def insert_button(*args)
4830
4917
  # :custom
4831
4918
  #
4832
4919
  # +:any+ is used to specify that the type of data is unrestricted.
4833
- # This is the same as not applying a data validation. It is only
4834
- # provided for completeness and isn't used very often in the
4835
- # context of WriteXLSX.
4920
+ # This is usefl to display an input message without restricting the data
4921
+ # that can be entered.
4836
4922
  #
4837
4923
  # +:integer+ restricts the cell to integer values. Excel refers to this
4838
4924
  # as 'whole number'.
@@ -5716,6 +5802,22 @@ def get_range_data(row_start, col_start, row_end, col_end) # :nodoc:
5716
5802
  # width # Width of object frame.
5717
5803
  # height # Height of object frame.
5718
5804
  def position_object_pixels(col_start, row_start, x1, y1, width, height) #:nodoc:
5805
+ # Adjust start column for negative offsets.
5806
+ while x1 < 0 && col_start > 0
5807
+ x1 += size_col(col_start - 1)
5808
+ col_start -= 1
5809
+ end
5810
+
5811
+ # Adjust start row for negative offsets.
5812
+ while y1 < 0 && row_start > 0
5813
+ y1 += size_row(row_start - 1)
5814
+ row_start -= 1
5815
+ end
5816
+
5817
+ # Ensure that the image isn't shifted off the page at top left.
5818
+ x1 = 0 if x1 < 0
5819
+ y1 = 0 if y1 < 0
5820
+
5719
5821
  # Calculate the absolute x offset of the top-left vertex.
5720
5822
  if @col_size_changed
5721
5823
  x_abs = (0 .. col_start-1).inject(0) {|sum, col| sum += size_col(col)}
@@ -5810,7 +5912,7 @@ def excel2003_style? # :nodoc:
5810
5912
  # based on the default or user defined values in the Workbook palette.
5811
5913
  #
5812
5914
  def palette_color(index) #:nodoc:
5813
- if index =~ /^#([0-9A-F]{6})$/i
5915
+ if index.to_s =~ /^#([0-9A-F]{6})$/i
5814
5916
  "FF#{$1.upcase}"
5815
5917
  else
5816
5918
  "FF#{super(index)}"
@@ -6099,7 +6201,7 @@ def parse_filter_tokens(expression, tokens) #:nodoc:
6099
6201
  # Special handling of "Top" filter expressions.
6100
6202
  if tokens[0] =~ /^top|bottom$/i
6101
6203
  value = tokens[1]
6102
- if (value =~ /\D/ or value.to_i < 1 or value.to_i > 500)
6204
+ if (value.to_s =~ /\D/ or value.to_i < 1 or value.to_i > 500)
6103
6205
  raise "The value '#{value}' in expression '#{expression}' " +
6104
6206
  "must be in the range 1 to 500"
6105
6207
  end
@@ -6128,7 +6230,7 @@ def parse_filter_tokens(expression, tokens) #:nodoc:
6128
6230
  end
6129
6231
 
6130
6232
  # Special handling for Blanks/NonBlanks.
6131
- if (token =~ /^blanks|nonblanks$/i)
6233
+ if (token.to_s =~ /^blanks|nonblanks$/i)
6132
6234
  # Only allow Equals or NotEqual in this context.
6133
6235
  if (operator != 2 and operator != 5)
6134
6236
  raise "The operator '#{tokens[1]}' in expression '#{expression}' " +
@@ -6156,7 +6258,7 @@ def parse_filter_tokens(expression, tokens) #:nodoc:
6156
6258
 
6157
6259
  # if the string token contains an Excel match character then change the
6158
6260
  # operator type to indicate a non "simple" equality.
6159
- if (operator == 2 and token =~ /[*?]/)
6261
+ if (operator == 2 and token.to_s =~ /[*?]/)
6160
6262
  operator = 22
6161
6263
  end
6162
6264
 
@@ -7510,8 +7612,8 @@ def calc_spans(data, row_num, span_min, span_max)
7510
7612
  # Add a string to the shared string table, if it isn't already there, and
7511
7613
  # return the string index.
7512
7614
  #
7513
- def shared_string_index(str, params = {}) #:nodoc:
7514
- @workbook.shared_string_index(str, params)
7615
+ def shared_string_index(str) #:nodoc:
7616
+ @workbook.shared_string_index(str)
7515
7617
  end
7516
7618
 
7517
7619
  #
@@ -7634,7 +7736,7 @@ def set_active_pane_and_cell_selections(row, col, top_row, left_col, active_cell
7634
7736
 
7635
7737
  def prepare_filter_column(col) # :nodoc:
7636
7738
  # Check for a column reference in A1 notation and substitute.
7637
- if col =~ /^\D/
7739
+ if col.to_s =~ /^\D/
7638
7740
  col_letter = col
7639
7741
 
7640
7742
  # Convert col ref to a cell ref and then to a col number.
@@ -1,4 +1,5 @@
1
1
  # -*- encoding: utf-8 -*-
2
+ # frozen_string_literal: true
2
3
 
3
4
  module Writexlsx
4
5
  class Worksheet
@@ -63,9 +64,10 @@ def data
63
64
  { :sst_id => token }
64
65
  end
65
66
 
67
+ TYPE_STR_ATTRS = ['t', 's'].freeze
66
68
  def write_cell
67
69
  attributes = cell_attributes
68
- attributes << ['t', 's']
70
+ attributes << TYPE_STR_ATTRS
69
71
  @worksheet.writer.tag_elements('c', attributes) do
70
72
  @worksheet.write_cell_value(token)
71
73
  end