write_xlsx 0.85.7 → 0.86.0

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