caxlsx 3.1.1 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (154) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +43 -1
  3. data/README.md +4 -11
  4. data/lib/axlsx/content_type/abstract_content_type.rb +1 -1
  5. data/lib/axlsx/doc_props/app.rb +1 -1
  6. data/lib/axlsx/drawing/chart.rb +25 -2
  7. data/lib/axlsx/drawing/d_lbls.rb +3 -2
  8. data/lib/axlsx/drawing/scatter_series.rb +31 -0
  9. data/lib/axlsx/drawing/title.rb +11 -1
  10. data/lib/axlsx/drawing/view_3D.rb +1 -1
  11. data/lib/axlsx/package.rb +15 -5
  12. data/lib/axlsx/rels/relationship.rb +1 -1
  13. data/lib/axlsx/stylesheet/border.rb +2 -0
  14. data/lib/axlsx/stylesheet/font.rb +1 -1
  15. data/lib/axlsx/stylesheet/styles.rb +139 -24
  16. data/lib/axlsx/util/constants.rb +16 -1
  17. data/lib/axlsx/util/serialized_attributes.rb +2 -2
  18. data/lib/axlsx/util/storage.rb +9 -9
  19. data/lib/axlsx/version.rb +1 -1
  20. data/lib/axlsx/workbook/workbook.rb +55 -0
  21. data/lib/axlsx/workbook/worksheet/border_creator.rb +76 -0
  22. data/lib/axlsx/workbook/worksheet/cell.rb +29 -2
  23. data/lib/axlsx/workbook/worksheet/cell_serializer.rb +1 -1
  24. data/lib/axlsx/workbook/worksheet/col.rb +4 -4
  25. data/lib/axlsx/workbook/worksheet/data_validation.rb +26 -5
  26. data/lib/axlsx/workbook/worksheet/pivot_table.rb +55 -14
  27. data/lib/axlsx/workbook/worksheet/rich_text_run.rb +1 -1
  28. data/lib/axlsx/workbook/worksheet/worksheet.rb +68 -7
  29. data/lib/axlsx.rb +43 -10
  30. metadata +6 -253
  31. data/test/benchmark.rb +0 -72
  32. data/test/content_type/tc_content_type.rb +0 -76
  33. data/test/content_type/tc_default.rb +0 -16
  34. data/test/content_type/tc_override.rb +0 -14
  35. data/test/doc_props/tc_app.rb +0 -43
  36. data/test/doc_props/tc_core.rb +0 -42
  37. data/test/drawing/tc_area_chart.rb +0 -39
  38. data/test/drawing/tc_area_series.rb +0 -71
  39. data/test/drawing/tc_axes.rb +0 -8
  40. data/test/drawing/tc_axis.rb +0 -112
  41. data/test/drawing/tc_bar_3D_chart.rb +0 -86
  42. data/test/drawing/tc_bar_chart.rb +0 -86
  43. data/test/drawing/tc_bar_series.rb +0 -46
  44. data/test/drawing/tc_bubble_chart.rb +0 -44
  45. data/test/drawing/tc_bubble_series.rb +0 -21
  46. data/test/drawing/tc_cat_axis.rb +0 -31
  47. data/test/drawing/tc_cat_axis_data.rb +0 -27
  48. data/test/drawing/tc_chart.rb +0 -123
  49. data/test/drawing/tc_d_lbls.rb +0 -57
  50. data/test/drawing/tc_data_source.rb +0 -23
  51. data/test/drawing/tc_drawing.rb +0 -80
  52. data/test/drawing/tc_graphic_frame.rb +0 -27
  53. data/test/drawing/tc_hyperlink.rb +0 -64
  54. data/test/drawing/tc_line_3d_chart.rb +0 -47
  55. data/test/drawing/tc_line_chart.rb +0 -39
  56. data/test/drawing/tc_line_series.rb +0 -71
  57. data/test/drawing/tc_marker.rb +0 -44
  58. data/test/drawing/tc_named_axis_data.rb +0 -27
  59. data/test/drawing/tc_num_data.rb +0 -31
  60. data/test/drawing/tc_num_val.rb +0 -29
  61. data/test/drawing/tc_one_cell_anchor.rb +0 -66
  62. data/test/drawing/tc_pic.rb +0 -103
  63. data/test/drawing/tc_picture_locking.rb +0 -72
  64. data/test/drawing/tc_pie_3D_chart.rb +0 -28
  65. data/test/drawing/tc_pie_series.rb +0 -33
  66. data/test/drawing/tc_scaling.rb +0 -36
  67. data/test/drawing/tc_scatter_chart.rb +0 -48
  68. data/test/drawing/tc_scatter_series.rb +0 -56
  69. data/test/drawing/tc_ser_axis.rb +0 -31
  70. data/test/drawing/tc_series.rb +0 -23
  71. data/test/drawing/tc_series_title.rb +0 -54
  72. data/test/drawing/tc_str_data.rb +0 -18
  73. data/test/drawing/tc_str_val.rb +0 -30
  74. data/test/drawing/tc_title.rb +0 -70
  75. data/test/drawing/tc_two_cell_anchor.rb +0 -36
  76. data/test/drawing/tc_val_axis.rb +0 -24
  77. data/test/drawing/tc_view_3D.rb +0 -54
  78. data/test/drawing/tc_vml_drawing.rb +0 -25
  79. data/test/drawing/tc_vml_shape.rb +0 -106
  80. data/test/fixtures/image1.gif +0 -0
  81. data/test/fixtures/image1.jpeg +0 -0
  82. data/test/fixtures/image1.jpg +0 -0
  83. data/test/fixtures/image1.png +0 -0
  84. data/test/fixtures/image1_fake.jpg +0 -0
  85. data/test/profile.rb +0 -24
  86. data/test/rels/tc_relationship.rb +0 -52
  87. data/test/rels/tc_relationships.rb +0 -37
  88. data/test/stylesheet/tc_border.rb +0 -37
  89. data/test/stylesheet/tc_border_pr.rb +0 -32
  90. data/test/stylesheet/tc_cell_alignment.rb +0 -81
  91. data/test/stylesheet/tc_cell_protection.rb +0 -29
  92. data/test/stylesheet/tc_cell_style.rb +0 -57
  93. data/test/stylesheet/tc_color.rb +0 -43
  94. data/test/stylesheet/tc_dxf.rb +0 -81
  95. data/test/stylesheet/tc_fill.rb +0 -18
  96. data/test/stylesheet/tc_font.rb +0 -133
  97. data/test/stylesheet/tc_gradient_fill.rb +0 -72
  98. data/test/stylesheet/tc_gradient_stop.rb +0 -31
  99. data/test/stylesheet/tc_num_fmt.rb +0 -30
  100. data/test/stylesheet/tc_pattern_fill.rb +0 -43
  101. data/test/stylesheet/tc_styles.rb +0 -261
  102. data/test/stylesheet/tc_table_style.rb +0 -44
  103. data/test/stylesheet/tc_table_style_element.rb +0 -45
  104. data/test/stylesheet/tc_table_styles.rb +0 -29
  105. data/test/stylesheet/tc_xf.rb +0 -120
  106. data/test/tc_axlsx.rb +0 -109
  107. data/test/tc_helper.rb +0 -10
  108. data/test/tc_package.rb +0 -314
  109. data/test/util/tc_mime_type_utils.rb +0 -13
  110. data/test/util/tc_serialized_attributes.rb +0 -19
  111. data/test/util/tc_simple_typed_list.rb +0 -77
  112. data/test/util/tc_validators.rb +0 -210
  113. data/test/workbook/tc_defined_name.rb +0 -49
  114. data/test/workbook/tc_shared_strings_table.rb +0 -59
  115. data/test/workbook/tc_workbook.rb +0 -160
  116. data/test/workbook/tc_workbook_view.rb +0 -50
  117. data/test/workbook/worksheet/auto_filter/tc_auto_filter.rb +0 -38
  118. data/test/workbook/worksheet/auto_filter/tc_filter_column.rb +0 -76
  119. data/test/workbook/worksheet/auto_filter/tc_filters.rb +0 -50
  120. data/test/workbook/worksheet/tc_break.rb +0 -49
  121. data/test/workbook/worksheet/tc_cell.rb +0 -453
  122. data/test/workbook/worksheet/tc_cfvo.rb +0 -31
  123. data/test/workbook/worksheet/tc_col.rb +0 -93
  124. data/test/workbook/worksheet/tc_color_scale.rb +0 -58
  125. data/test/workbook/worksheet/tc_comment.rb +0 -72
  126. data/test/workbook/worksheet/tc_comments.rb +0 -57
  127. data/test/workbook/worksheet/tc_conditional_formatting.rb +0 -224
  128. data/test/workbook/worksheet/tc_data_bar.rb +0 -46
  129. data/test/workbook/worksheet/tc_data_validation.rb +0 -265
  130. data/test/workbook/worksheet/tc_date_time_converter.rb +0 -124
  131. data/test/workbook/worksheet/tc_header_footer.rb +0 -151
  132. data/test/workbook/worksheet/tc_icon_set.rb +0 -45
  133. data/test/workbook/worksheet/tc_outline_pr.rb +0 -19
  134. data/test/workbook/worksheet/tc_page_margins.rb +0 -97
  135. data/test/workbook/worksheet/tc_page_set_up_pr.rb +0 -15
  136. data/test/workbook/worksheet/tc_page_setup.rb +0 -143
  137. data/test/workbook/worksheet/tc_pane.rb +0 -54
  138. data/test/workbook/worksheet/tc_pivot_table.rb +0 -143
  139. data/test/workbook/worksheet/tc_pivot_table_cache_definition.rb +0 -62
  140. data/test/workbook/worksheet/tc_print_options.rb +0 -72
  141. data/test/workbook/worksheet/tc_protected_range.rb +0 -17
  142. data/test/workbook/worksheet/tc_rich_text.rb +0 -44
  143. data/test/workbook/worksheet/tc_rich_text_run.rb +0 -173
  144. data/test/workbook/worksheet/tc_row.rb +0 -160
  145. data/test/workbook/worksheet/tc_selection.rb +0 -55
  146. data/test/workbook/worksheet/tc_sheet_calc_pr.rb +0 -18
  147. data/test/workbook/worksheet/tc_sheet_format_pr.rb +0 -88
  148. data/test/workbook/worksheet/tc_sheet_pr.rb +0 -49
  149. data/test/workbook/worksheet/tc_sheet_protection.rb +0 -117
  150. data/test/workbook/worksheet/tc_sheet_view.rb +0 -214
  151. data/test/workbook/worksheet/tc_table.rb +0 -77
  152. data/test/workbook/worksheet/tc_table_style_info.rb +0 -53
  153. data/test/workbook/worksheet/tc_worksheet.rb +0 -601
  154. data/test/workbook/worksheet/tc_worksheet_hyperlink.rb +0 -55
@@ -274,6 +274,12 @@ module Axlsx
274
274
  # cellXfs id for default date styling
275
275
  STYLE_DATE = 2
276
276
 
277
+ # worksheet maximum name length
278
+ WORKSHEET_MAX_NAME_LENGTH = 31
279
+
280
+ # worksheet name forbidden characters
281
+ WORKSHEET_NAME_FORBIDDEN_CHARS = '[]*/\?:'.chars.freeze
282
+
277
283
  # error messages RestrictionValidor
278
284
  ERR_RESTRICTION = "Invalid Data: %s. %s must be one of %s.".freeze
279
285
 
@@ -286,8 +292,11 @@ module Axlsx
286
292
  # error message for RangeValidator
287
293
  ERR_RANGE = "Invalid Data. %s must be between %s and %s, (inclusive:%s) you gave: %s".freeze
288
294
 
295
+ # error message for sheets that use explicit empty string name
296
+ ERR_SHEET_NAME_EMPTY = "Your worksheet name is empty. Worksheet name can't be empty. Please assign name of your sheet or don't use name option at all.".freeze
297
+
289
298
  # error message for sheets that use a name which is longer than 31 bytes
290
- ERR_SHEET_NAME_TOO_LONG = "Your worksheet name '%s' is too long. Worksheet names must be 31 characters (bytes) or less".freeze
299
+ ERR_SHEET_NAME_TOO_LONG = "Your worksheet name '%s' is too long. Worksheet names must be #{WORKSHEET_MAX_NAME_LENGTH} characters (bytes) or less".freeze
291
300
 
292
301
  # error message for sheets that use a name which include invalid characters
293
302
  ERR_SHEET_NAME_CHARACTER_FORBIDDEN = "Your worksheet name '%s' contains a character which is not allowed by MS Excel and will cause repair warnings. Please change the name of your sheet.".freeze
@@ -307,6 +316,12 @@ module Axlsx
307
316
  # error message for non 'integerish' value
308
317
  ERR_INTEGERISH = "You value must be, or be castable via to_i, an Integer. You provided %s".freeze
309
318
 
319
+ # error message for invalid cell reference
320
+ ERR_CELL_REFERENCE_INVALID = "Invalid cell definition `%s`".freeze
321
+
322
+ # error message for cell reference with last cell missing
323
+ ERR_CELL_REFERENCE_MISSING_CELL = "Missing cell `%s` for the specified range `%s`".freeze
324
+
310
325
  # Regex to match forbidden control characters
311
326
  # The following will be automatically stripped from worksheets.
312
327
  #
@@ -61,7 +61,7 @@ module Axlsx
61
61
  # seraialized_attributes and are not nil.
62
62
  # This requires ruby 1.9.3 or higher
63
63
  def declared_attributes
64
- instance_values.select do |key, value|
64
+ Axlsx.instance_values_for(self).select do |key, value|
65
65
  value != nil && self.class.xml_attributes.include?(key.to_sym)
66
66
  end
67
67
  end
@@ -75,7 +75,7 @@ module Axlsx
75
75
  # @return [String] The serialized output.
76
76
  def serialized_element_attributes(str='', additional_attributes=[], &block)
77
77
  attrs = self.class.xml_element_attributes + additional_attributes
78
- values = instance_values
78
+ values = Axlsx.instance_values_for(self)
79
79
  attrs.each do |attribute_name|
80
80
  value = values[attribute_name.to_s]
81
81
  next if value.nil?
@@ -122,15 +122,15 @@ module Axlsx
122
122
 
123
123
  # Creates a new storage object.
124
124
  # @param [String] name the name of the storage
125
- # @option options [Integer] color @default black
126
- # @option options [Integer] type @default storage
127
- # @option options [String] data
128
- # @option options [Integer] left @default -1
129
- # @option options [Integer] right @default -1
130
- # @option options [Integer] child @default -1
131
- # @option options [Integer] created @default 0
132
- # @option options [Integer] modified @default 0
133
- # @option options [Integer] sector @default 0
125
+ # @option options [Integer] color (black)
126
+ # @option options [Integer] type (storage)
127
+ # @option options [String] data
128
+ # @option options [Integer] left (-1)
129
+ # @option options [Integer] right (-1)
130
+ # @option options [Integer] child (-1)
131
+ # @option options [Integer] created (0)
132
+ # @option options [Integer] modified (0)
133
+ # @option options [Integer] sector (0)
134
134
  def initialize(name, options= {})
135
135
  @left = @right = @child = -1
136
136
  @sector = @size = @created = @modified = 0
data/lib/axlsx/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Axlsx
2
2
 
3
3
  # The current version
4
- VERSION = "3.1.1"
4
+ VERSION = "3.3.0"
5
5
  end
@@ -85,6 +85,9 @@ require 'axlsx/workbook/worksheet/selection.rb'
85
85
  # *workbookPr is only supported to the extend of date1904
86
86
  class Workbook
87
87
 
88
+ BOLD_FONT_MULTIPLIER = 1.5
89
+ FONT_SCALE_DIVISOR = 10.0
90
+
88
91
  # When true, the Package will be generated with a shared string table. This may be required by some OOXML processors that do not
89
92
  # adhere to the ECMA specification that dictates string may be inline in the sheet.
90
93
  # Using this option will increase the time required to serialize the document as every string in every cell must be analzed and referenced.
@@ -184,6 +187,36 @@ require 'axlsx/workbook/worksheet/selection.rb'
184
187
  @styles
185
188
  end
186
189
 
190
+ # An array that holds all cells with styles
191
+ # @return Set
192
+ def styled_cells
193
+ @styled_cells ||= Set.new
194
+ end
195
+
196
+ # Are the styles added with workbook.add_styles applied yet
197
+ # @return Boolean
198
+ attr_accessor :styles_applied
199
+
200
+ # A helper to apply styles that were added using `worksheet.add_style`
201
+ # @return [Boolean]
202
+ def apply_styles
203
+ return false if !styled_cells
204
+
205
+ styled_cells.each do |cell|
206
+ current_style = styles.style_index[cell.style]
207
+
208
+ if current_style
209
+ new_style = Axlsx.hash_deep_merge(current_style, cell.raw_style)
210
+ else
211
+ new_style = cell.raw_style
212
+ end
213
+
214
+ cell.style = styles.add_style(new_style)
215
+ end
216
+
217
+ self.styles_applied = true
218
+ end
219
+
187
220
 
188
221
  # Indicates if the epoc date for serialization should be 1904. If false, 1900 is used.
189
222
  @@date1904 = false
@@ -213,6 +246,8 @@ require 'axlsx/workbook/worksheet/selection.rb'
213
246
 
214
247
 
215
248
  @use_autowidth = true
249
+ @bold_font_multiplier = BOLD_FONT_MULTIPLIER
250
+ @font_scale_divisor = FONT_SCALE_DIVISOR
216
251
 
217
252
  self.date1904= !options[:date1904].nil? && options[:date1904]
218
253
  yield self if block_given?
@@ -243,6 +278,26 @@ require 'axlsx/workbook/worksheet/selection.rb'
243
278
  # see @use_autowidth
244
279
  def use_autowidth=(v=true) Axlsx::validate_boolean v; @use_autowidth = v; end
245
280
 
281
+ # Font size of bold fonts is multiplied with this
282
+ # Used for automatic calculation of cell widths with bold text
283
+ # @return [Float]
284
+ attr_reader :bold_font_multiplier
285
+
286
+ def bold_font_multiplier=(v)
287
+ Axlsx::validate_float v
288
+ @bold_font_multiplier = v
289
+ end
290
+
291
+ # Font scale is calculated with this value (font_size / font_scale_divisor)
292
+ # Used for automatic calculation of cell widths
293
+ # @return [Float]
294
+ attr_reader :font_scale_divisor
295
+
296
+ def font_scale_divisor=(v)
297
+ Axlsx::validate_float v
298
+ @font_scale_divisor = v
299
+ end
300
+
246
301
  # inserts a worksheet into this workbook at the position specified.
247
302
  # It the index specified is out of range, the worksheet will be added to the end of the
248
303
  # worksheets collection
@@ -0,0 +1,76 @@
1
+ # encoding: UTF-8
2
+
3
+ module Axlsx
4
+ class BorderCreator
5
+ attr_reader :worksheet, :cells, :edges, :width, :color
6
+
7
+ def initialize(worksheet, cells, args)
8
+ @worksheet = worksheet
9
+ @cells = cells
10
+ if args.is_a?(Hash)
11
+ @edges = args[:edges] || Axlsx::Border::EDGES
12
+ @width = args[:style] || :thin
13
+ @color = args[:color] || '000000'
14
+ else
15
+ @edges = args || Axlsx::Border::Edges
16
+ @width = :thin
17
+ @color = '000000'
18
+ end
19
+
20
+ if @edges == :all
21
+ @edges = Axlsx::Border::EDGES
22
+ elsif @edges.is_a?(Array)
23
+ @edges = (@edges.map(&:to_sym).uniq & Axlsx::Border::EDGES)
24
+ else
25
+ @edges = []
26
+ end
27
+ end
28
+
29
+ def draw
30
+ @edges.each do |edge|
31
+ worksheet.add_style(
32
+ border_cells[edge],
33
+ {
34
+ border: {style: @width, color: @color, edges: [edge]}
35
+ }
36
+ )
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def border_cells
43
+ {
44
+ top: "#{first_cell}:#{last_col}#{first_row}",
45
+ right: "#{last_col}#{first_row}:#{last_cell}",
46
+ bottom: "#{first_col}#{last_row}:#{last_cell}",
47
+ left: "#{first_cell}:#{first_col}#{last_row}",
48
+ }
49
+ end
50
+
51
+ def first_cell
52
+ @first_cell ||= cells.first.r
53
+ end
54
+
55
+ def last_cell
56
+ @last_cell ||= cells.last.r
57
+ end
58
+
59
+ def first_row
60
+ @first_row ||= first_cell.scan(/\d+/).first
61
+ end
62
+
63
+ def first_col
64
+ @first_col ||= first_cell.scan(/\D+/).first
65
+ end
66
+
67
+ def last_row
68
+ @last_row ||= last_cell.scan(/\d+/).first
69
+ end
70
+
71
+ def last_col
72
+ @last_col ||= last_cell.scan(/\D+/).first
73
+ end
74
+
75
+ end
76
+ end
@@ -82,6 +82,32 @@ module Axlsx
82
82
  defined?(@style) ? @style : 0
83
83
  end
84
84
 
85
+ attr_accessor :raw_style
86
+
87
+ # The index of the cellXfs item to be applied to this cell.
88
+ # @param [Hash] styles
89
+ # @see Axlsx::Styles
90
+ def add_style(style)
91
+ self.raw_style ||= {}
92
+
93
+ new_style = Axlsx.hash_deep_merge(raw_style, style)
94
+
95
+ all_edges = [:top, :right, :bottom, :left]
96
+
97
+ if !raw_style[:border].nil? && !style[:border].nil?
98
+ border_at = (raw_style[:border][:edges] || all_edges) + (style[:border][:edges] || all_edges)
99
+ new_style[:border][:edges] = border_at.uniq.sort
100
+ elsif !style[:border].nil?
101
+ new_style[:border] = style[:border]
102
+ end
103
+
104
+ self.raw_style = new_style
105
+
106
+ wb = row.worksheet.workbook
107
+
108
+ wb.styled_cells << self
109
+ end
110
+
85
111
  # The row this cell belongs to.
86
112
  # @return [Row]
87
113
  attr_reader :row
@@ -409,7 +435,7 @@ module Axlsx
409
435
  # This is still not perfect...
410
436
  # - scaling is not linear as font sizes increase
411
437
  def string_width(string, font_size)
412
- font_scale = font_size / 10.0
438
+ font_scale = font_size / row.worksheet.workbook.font_scale_divisor
413
439
  (string.to_s.size + 3) * font_scale
414
440
  end
415
441
 
@@ -418,8 +444,9 @@ module Axlsx
418
444
  # imagemagick and loading metrics for every character.
419
445
  def font_size
420
446
  return sz if sz
447
+
421
448
  font = styles.fonts[styles.cellXfs[style].fontId] || styles.fonts[0]
422
- (font.b || (defined?(@b) && @b)) ? (font.sz * 1.5) : font.sz
449
+ font.b || (defined?(@b) && @b) ? (font.sz * row.worksheet.workbook.bold_font_multiplier) : font.sz
423
450
  end
424
451
 
425
452
  # Utility method for setting inline style attributes
@@ -22,7 +22,7 @@ module Axlsx
22
22
  def run_xml_string(cell, str = '')
23
23
  if cell.is_text_run?
24
24
  valid = RichTextRun::INLINE_STYLES - [:value, :type]
25
- data = Hash[cell.instance_values.map{ |k, v| [k.to_sym, v] }]
25
+ data = Hash[Axlsx.instance_values_for(cell).map{ |k, v| [k.to_sym, v] }]
26
26
  data = data.select { |key, value| valid.include?(key) && !value.nil? }
27
27
  RichText.new(cell.value.to_s, data).to_xml_string(str)
28
28
  elsif cell.contains_rich_text?
@@ -125,12 +125,12 @@ module Axlsx
125
125
  # to this value and the cell's attributes are ignored.
126
126
  # @param [Boolean] use_autowidth If this is false, the cell's
127
127
  # autowidth value will be ignored.
128
- def update_width(cell, fixed_width=nil, use_autowidth=true)
128
+ def update_width(cell, fixed_width = nil, use_autowidth = true)
129
129
  if fixed_width.is_a? Numeric
130
- self.width = fixed_width
130
+ self.width = fixed_width
131
131
  elsif use_autowidth
132
- cell_width = cell.autowidth
133
- self.width = cell_width unless (width || 0) > (cell_width || 0)
132
+ cell_width = cell.autowidth
133
+ self.width = cell_width unless (width || 0) > (cell_width || 0)
134
134
  end
135
135
  end
136
136
 
@@ -13,11 +13,12 @@ module Axlsx
13
13
  # @option options [Boolean] allowBlank - A boolean value indicating whether the data validation allows the use of empty or blank entries.
14
14
  # @option options [String] error - Message text of error alert.
15
15
  # @option options [Symbol] errorStyle - The style of error alert used for this data validation.
16
- # @option options [String] errorTitle - itle bar text of error alert.
16
+ # @option options [String] errorTitle - Title bar text of error alert.
17
17
  # @option options [Symbol] operator - The relational operator used with this data validation.
18
18
  # @option options [String] prompt - Message text of input prompt.
19
19
  # @option options [String] promptTitle - Title bar text of input prompt.
20
- # @option options [Boolean] showDropDown - A boolean value indicating whether to display a dropdown combo box for a list type data validation
20
+ # @option options [Boolean] showDropDown - A boolean value indicating whether to display a dropdown combo box for a list type data validation. Be careful: It has an inverted logic, false shows the dropdown list! You should use hideDropDown instead.
21
+ # @option options [Boolean] hideDropDown - A boolean value indicating whether to hide the dropdown combo box for a list type data validation. Defaults to `false` (meaning the dropdown is visible by default).
21
22
  # @option options [Boolean] showErrorMessage - A boolean value indicating whether to display the error alert message when an invalid value has been entered, according to the criteria specified.
22
23
  # @option options [Boolean] showInputMessage - A boolean value indicating whether to display the input prompt message.
23
24
  # @option options [String] sqref - Range over which data validation is applied, in "A1:B2" format.
@@ -121,13 +122,22 @@ module Axlsx
121
122
 
122
123
  # Show drop down
123
124
  # A boolean value indicating whether to display a dropdown combo box for a list type data
124
- # validation. Be careful: false shows the dropdown list!
125
+ # validation. Be careful: It has an inverted logic, false shows the dropdown list!
125
126
  # Available for type list
126
127
  # @see type
127
128
  # @return [Boolean]
128
129
  # default false
129
130
  attr_reader :showDropDown
130
131
 
132
+ # Hide drop down
133
+ # A boolean value indicating whether to hide a dropdown combo box for a list type data
134
+ # validation. Defaults to `false` (meaning the dropdown is visible by default).
135
+ # Available for type list
136
+ # @see type
137
+ # @return [Boolean]
138
+ # default false
139
+ alias :hideDropDown :showDropDown
140
+
131
141
  # Show error message
132
142
  # A boolean value indicating whether to display the error alert message when an invalid
133
143
  # value has been entered, according to the criteria specified.
@@ -195,7 +205,18 @@ module Axlsx
195
205
  def promptTitle=(v); Axlsx::validate_string(v); @promptTitle = v end
196
206
 
197
207
  # @see showDropDown
198
- def showDropDown=(v); Axlsx::validate_boolean(v); @showDropDown = v end
208
+ def showDropDown=(v)
209
+ warn 'The `showDropDown` has an inverted logic, false shows the dropdown list! You should use `hideDropDown` instead.'
210
+ Axlsx::validate_boolean(v)
211
+ @showDropDown = v
212
+ end
213
+
214
+ # @see hideDropDown
215
+ def hideDropDown=(v)
216
+ Axlsx::validate_boolean(v)
217
+ # It's just an alias for the showDropDown attribute, hideDropDown should set the value of the original showDropDown.
218
+ @showDropDown = v
219
+ end
199
220
 
200
221
  # @see showErrorMessage
201
222
  def showErrorMessage=(v); Axlsx::validate_boolean(v); @showErrorMessage = v end
@@ -216,7 +237,7 @@ module Axlsx
216
237
  valid_attributes = get_valid_attributes
217
238
 
218
239
  str << '<dataValidation '
219
- str << instance_values.map do |key, value|
240
+ str << Axlsx.instance_values_for(self).map do |key, value|
220
241
  '' << key << '="' << Axlsx.booleanize(value).to_s << '"' if (valid_attributes.include?(key.to_sym) && !CHILD_ELEMENTS.include?(key.to_sym))
221
242
  end.join(' ')
222
243
  str << '>'
@@ -26,17 +26,33 @@ module Axlsx
26
26
  @pages = []
27
27
  @subtotal = nil
28
28
  @no_subtotals_on_headers = []
29
+ @sort_on_headers = {}
29
30
  @style_info = {}
30
31
  parse_options options
31
32
  yield self if block_given?
32
33
  end
33
34
 
34
- # Defines the headers in which subtotals are not to be included
35
- # @return[Array]
35
+ # Defines the headers in which subtotals are not to be included.
36
+ # @return [Array]
36
37
  attr_accessor :no_subtotals_on_headers
37
38
 
39
+ # Defines the headers in which sort is applied.
40
+ # Can be an array of headers to sort ascending by default, or a hash for specific control
41
+ # (with headers as keys, `:ascending` or `:descending` as values).
42
+ #
43
+ # Examples: `["year", "month"]` or `{"year" => :descending, "month" => :descending}`
44
+ # @return [Hash]
45
+ attr_reader :sort_on_headers
46
+
47
+ # (see #sort_on_headers)
48
+ def sort_on_headers=(headers)
49
+ headers ||= {}
50
+ headers = Hash[*headers.map { |h| [h, :ascending] }.flatten] if headers.is_a?(Array)
51
+ @sort_on_headers = headers
52
+ end
53
+
38
54
  # Style info for the pivot table
39
- # @return[Hash]
55
+ # @return [Hash]
40
56
  attr_accessor :style_info
41
57
 
42
58
  # The reference to the table data
@@ -173,12 +189,18 @@ module Axlsx
173
189
  # @return [String]
174
190
  def to_xml_string(str = '')
175
191
  str << '<?xml version="1.0" encoding="UTF-8"?>'
176
- str << ('<pivotTableDefinition xmlns="' << XML_NS << '" name="' << name << '" cacheId="' << cache_definition.cache_id.to_s << '" dataOnRows="1" applyNumberFormats="0" applyBorderFormats="0" applyFontFormats="0" applyPatternFormats="0" applyAlignmentFormats="0" applyWidthHeightFormats="1" dataCaption="Data" showMultipleLabel="0" showMemberPropertyTips="0" useAutoFormatting="1" indent="0" compact="0" compactData="0" gridDropZones="1" multipleFieldFilters="0">')
192
+
193
+ str << ('<pivotTableDefinition xmlns="' << XML_NS << '" name="' << name << '" cacheId="' << cache_definition.cache_id.to_s << '"' << (data.size <= 1 ? ' dataOnRows="1"' : '') << ' applyNumberFormats="0" applyBorderFormats="0" applyFontFormats="0" applyPatternFormats="0" applyAlignmentFormats="0" applyWidthHeightFormats="1" dataCaption="Data" showMultipleLabel="0" showMemberPropertyTips="0" useAutoFormatting="1" indent="0" compact="0" compactData="0" gridDropZones="1" multipleFieldFilters="0">')
194
+
177
195
  str << ('<location firstDataCol="1" firstDataRow="1" firstHeaderRow="1" ref="' << ref << '"/>')
178
196
  str << ('<pivotFields count="' << header_cells_count.to_s << '">')
197
+
179
198
  header_cell_values.each do |cell_value|
180
- str << pivot_field_for(cell_value, !no_subtotals_on_headers.include?(cell_value))
199
+ subtotal = !no_subtotals_on_headers.include?(cell_value)
200
+ sorttype = sort_on_headers[cell_value]
201
+ str << pivot_field_for(cell_value, subtotal, sorttype)
181
202
  end
203
+
182
204
  str << '</pivotFields>'
183
205
  if rows.empty?
184
206
  str << '<rowFields count="1"><field x="-2"/></rowFields>'
@@ -196,7 +218,17 @@ module Axlsx
196
218
  str << '</rowItems>'
197
219
  end
198
220
  if columns.empty?
199
- str << '<colItems count="1"><i/></colItems>'
221
+ if data.size > 1
222
+ str << '<colFields count="1"><field x="-2"/></colFields>'
223
+ str << "<colItems count=\"#{data.size}\">"
224
+ str << '<i><x/></i>'
225
+ data[1..-1].each_with_index do |datum_value,i|
226
+ str << "<i i=\"#{i + 1}\"><x v=\"#{i + 1}\"/></i>"
227
+ end
228
+ str << '</colItems>'
229
+ else
230
+ str << '<colItems count="1"><i/></colItems>'
231
+ end
200
232
  else
201
233
  str << ('<colFields count="' << columns.size.to_s << '">')
202
234
  columns.each do |column_value|
@@ -265,22 +297,31 @@ module Axlsx
265
297
 
266
298
  private
267
299
 
268
- def pivot_field_for(cell_ref, subtotal=true)
300
+ def pivot_field_for(cell_ref, subtotal, sorttype)
301
+ attributes = %w[compact="0" outline="0" subtotalTop="0" showAll="0" includeNewItemsInFilter="1"]
302
+ items_tag = '<items count="1"><item t="default"/></items>'
303
+ include_items_tag = false
304
+
269
305
  if rows.include? cell_ref
306
+ attributes << 'axis="axisRow"'
307
+ attributes << "sortType=\"#{sorttype == :descending ? 'descending' : 'ascending'}\"" if sorttype
270
308
  if subtotal
271
- '<pivotField axis="axisRow" compact="0" outline="0" subtotalTop="0" showAll="0" includeNewItemsInFilter="1"><items count="1"><item t="default"/></items></pivotField>'
309
+ include_items_tag = true
272
310
  else
273
- '<pivotField axis="axisRow" compact="0" outline="0" subtotalTop="0" showAll="0" includeNewItemsInFilter="1" defaultSubtotal="0"></pivotField>'
311
+ attributes << 'defaultSubtotal="0"'
274
312
  end
275
313
  elsif columns.include? cell_ref
276
- '<pivotField axis="axisCol" compact="0" outline="0" subtotalTop="0" showAll="0" includeNewItemsInFilter="1"><items count="1"><item t="default"/></items></pivotField>'
314
+ attributes << 'axis="axisCol"'
315
+ attributes << "sortType=\"#{sorttype == :descending ? 'descending' : 'ascending'}\"" if sorttype
316
+ include_items_tag = true
277
317
  elsif pages.include? cell_ref
278
- '<pivotField axis="axisPage" compact="0" outline="0" subtotalTop="0" showAll="0" includeNewItemsInFilter="1"><items count="1"><item t="default"/></items></pivotField>'
318
+ attributes << 'axis="axisPage"'
319
+ include_items_tag = true
279
320
  elsif data_refs.include? cell_ref
280
- '<pivotField dataField="1" compact="0" outline="0" subtotalTop="0" showAll="0" includeNewItemsInFilter="1"></pivotField>'
281
- else
282
- '<pivotField compact="0" outline="0" subtotalTop="0" showAll="0" includeNewItemsInFilter="1"></pivotField>'
321
+ attributes << 'dataField="1"'
283
322
  end
323
+
324
+ "<pivotField #{attributes.join(' ')}>#{include_items_tag ? items_tag : nil}</pivotField>"
284
325
  end
285
326
 
286
327
  def data_refs
@@ -190,7 +190,7 @@ module Axlsx
190
190
  # @return [String]
191
191
  def to_xml_string(str = '')
192
192
  valid = RichTextRun::INLINE_STYLES
193
- data = Hash[self.instance_values.map{ |k, v| [k.to_sym, v] }]
193
+ data = Hash[Axlsx.instance_values_for(self).map{ |k, v| [k.to_sym, v] }]
194
194
  data = data.select { |key, value| valid.include?(key) && !value.nil? }
195
195
 
196
196
  str << '<r><rPr>'