caxlsx 3.0.2 → 3.1.1

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 (73) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +31 -8
  3. data/README.md +4 -0
  4. data/examples/{image1.jpeg → assets/image1.jpeg} +0 -0
  5. data/examples/generate.rb +15 -0
  6. data/lib/axlsx/drawing/bar_3D_chart.rb +5 -8
  7. data/lib/axlsx/drawing/bar_chart.rb +13 -18
  8. data/lib/axlsx/drawing/bar_series.rb +18 -1
  9. data/lib/axlsx/drawing/pie_series.rb +1 -1
  10. data/lib/axlsx/package.rb +44 -6
  11. data/lib/axlsx/util/constants.rb +2 -1
  12. data/lib/axlsx/util/mime_type_utils.rb +1 -1
  13. data/lib/axlsx/util/validators.rb +1 -1
  14. data/lib/axlsx/util/zip_command.rb +73 -0
  15. data/lib/axlsx/version.rb +1 -1
  16. data/lib/axlsx/workbook/worksheet/cell.rb +9 -3
  17. data/lib/axlsx/workbook/worksheet/data_validation.rb +4 -4
  18. data/lib/axlsx/workbook/worksheet/pivot_table.rb +7 -2
  19. data/lib/axlsx/workbook/worksheet/pivot_table_cache_definition.rb +1 -1
  20. data/lib/axlsx/workbook/worksheet/row.rb +6 -4
  21. data/lib/axlsx/workbook/worksheet/table.rb +1 -1
  22. data/lib/axlsx/workbook/worksheet/worksheet.rb +7 -1
  23. data/lib/axlsx.rb +7 -5
  24. data/test/drawing/tc_bar_3D_chart.rb +26 -11
  25. data/test/drawing/tc_bar_chart.rb +26 -11
  26. data/test/drawing/tc_bar_series.rb +10 -1
  27. data/test/drawing/tc_drawing.rb +2 -2
  28. data/test/drawing/tc_hyperlink.rb +1 -1
  29. data/test/drawing/tc_one_cell_anchor.rb +1 -1
  30. data/test/drawing/tc_pic.rb +4 -4
  31. data/test/drawing/tc_pie_series.rb +2 -1
  32. data/test/fixtures/image1.gif +0 -0
  33. data/test/fixtures/image1.jpeg +0 -0
  34. data/test/fixtures/image1.jpg +0 -0
  35. data/test/fixtures/image1.png +0 -0
  36. data/test/fixtures/image1_fake.jpg +0 -0
  37. data/test/tc_helper.rb +0 -2
  38. data/test/tc_package.rb +80 -13
  39. data/test/util/tc_mime_type_utils.rb +1 -1
  40. data/test/util/tc_validators.rb +1 -1
  41. data/test/workbook/worksheet/tc_cell.rb +38 -0
  42. data/test/workbook/worksheet/tc_pivot_table.rb +8 -0
  43. data/test/workbook/worksheet/tc_pivot_table_cache_definition.rb +8 -0
  44. data/test/workbook/worksheet/tc_row.rb +21 -0
  45. data/test/workbook/worksheet/tc_table.rb +10 -0
  46. data/test/workbook/worksheet/tc_worksheet.rb +16 -18
  47. metadata +116 -137
  48. data/examples/2010_comments.rb +0 -17
  49. data/examples/anchor_swapping.rb +0 -28
  50. data/examples/auto_filter.rb +0 -25
  51. data/examples/basic_charts.rb +0 -58
  52. data/examples/chart_colors.rb +0 -88
  53. data/examples/colored_links.rb +0 -45
  54. data/examples/conditional_formatting/example_conditional_formatting.rb +0 -89
  55. data/examples/conditional_formatting/getting_barred.rb +0 -37
  56. data/examples/conditional_formatting/hitting_the_high_notes.rb +0 -37
  57. data/examples/conditional_formatting/scaled_colors.rb +0 -39
  58. data/examples/conditional_formatting/stop_and_go.rb +0 -37
  59. data/examples/data_validation.rb +0 -67
  60. data/examples/example.rb +0 -900
  61. data/examples/extractive.rb +0 -45
  62. data/examples/ios_preview.rb +0 -14
  63. data/examples/merge_cells.rb +0 -17
  64. data/examples/no_grid_with_borders.rb +0 -18
  65. data/examples/page_setup.rb +0 -11
  66. data/examples/pivot_table.rb +0 -39
  67. data/examples/pivot_test.rb +0 -63
  68. data/examples/sheet_protection.rb +0 -10
  69. data/examples/skydrive/real_example.rb +0 -63
  70. data/examples/split.rb +0 -16
  71. data/examples/styles.rb +0 -66
  72. data/examples/underline.rb +0 -13
  73. data/examples/wrap_text.rb +0 -21
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: d1084b09a06fdcca750fa734735fe339c527327f
4
- data.tar.gz: cb0849f0edbe032dc82e3d6766245e7a42ebf81f
2
+ SHA256:
3
+ metadata.gz: ef44b04d373e873848cd74d5bebae4d327cd0d9cba06ee74de6d0aac35b51ab9
4
+ data.tar.gz: b54abdd82f277253216da4ad9d938c2508c9f1ed54abd080d4437e5523c470e1
5
5
  SHA512:
6
- metadata.gz: 736a38bbbb7bebebc204e3e81778e84d7ee45f9794e21b0bfab39d42ed1afe1e1e2cc905345b32889fb8cf3ae3b11a3ac7656173f991016296064a5ca82e98a7
7
- data.tar.gz: 4532801d92486550f8db5ab177acb8a72de3f0e209f2483db9e13df11a7e05ef3a63f97c9062099d478ca0469c86f1ebe7a7bc5cc8d5be8018a8a3abf49b8fe4
6
+ metadata.gz: bce9d28013a2ec3ffec4be335a2b933ba38fb508dbc4f486a3df135dc81122a236ff9439792ac8bc64c4150e523af261deea02c5a86b9f3838a38b2a26e0d537
7
+ data.tar.gz: 1bca0d82c582818a822feefbb499caef3b622f8d4203543a1c6d11f57876944df01db0bafb30d6238f9a268d731a0a78de0ed4efa97a205db43465d73dab7d74
data/CHANGELOG.md CHANGED
@@ -1,6 +1,29 @@
1
1
  CHANGELOG
2
2
  ---------
3
3
 
4
+ - **September.22.21**: 3.1.1
5
+ - [PR #107](https://github.com/caxlsx/caxlsx/pull/107) - Add overlap to bar charts
6
+ - [PR #108](https://github.com/caxlsx/caxlsx/pull/108) - Fix gap depth and gap depth validators for bar charts and 3D bar charts
7
+
8
+ - **March.27.21**: 3.1.0
9
+ - [PR #95](https://github.com/caxlsx/caxlsx/pull/95) - Replace mimemagic with marcel
10
+ - [PR #87](https://github.com/caxlsx/caxlsx/pull/87) - Implement :offset option for worksheet#add_row
11
+ - [PR #79](https://github.com/caxlsx/caxlsx/pull/79) - Add support for format in pivot tables
12
+ - [PR #77](https://github.com/caxlsx/caxlsx/pull/77) - Fix special characters in table header
13
+ - [PR #57](https://github.com/caxlsx/caxlsx/pull/57) - Deprecate using #serialize with boolean argument: Calls like `Package#serialize("name.xlsx", false)` should be replaced with `Package#serialize("name.xlsx", confirm_valid: false)`.
14
+
15
+ - **January.5.21**: 3.0.4
16
+ - [PR #72](https://github.com/caxlsx/caxlsx/pull/72) - Relax Ruby dependency to allow for Ruby 3. This required Travis to be upgraded from Ubuntu Trusty to Ubuntu Bionic. rbx-3 was dropped.
17
+ - [PR #71](https://github.com/caxlsx/caxlsx/pull/71) - Adds date type to validator so sheet.add_data_validation works with date type. Addresses [I #26](https://github.com/caxlsx/caxlsx/issues/26) - Date Data Validation not working
18
+ - [PR #70](https://github.com/caxlsx/caxlsx/pull/70) - Fix worksheet title length enforcement caused by switching from size to bytesize. Addresses [I #67](https://github.com/caxlsx/caxlsx/issues/67) - character length error in worksheet name when using Japanese, which was introduced by addressing [I #588](https://github.com/randym/axlsx/issues/588) in the old Axlsx repo.
19
+
20
+
21
+ - **December.7.20**: 3.0.3
22
+ - [PR #62](https://github.com/caxlsx/caxlsx/pull/62) - Fix edge cases in format detection for objects whose string representation made them look like numbers but the object didn’t respond to `#to_i` or `#to_f`.
23
+ - [PR #56](https://github.com/caxlsx/caxlsx/pull/56) - Add `zip_command` option to `#serialize` for faster serialization of large Excel files by using a zip binary
24
+ - [PR #54](https://github.com/caxlsx/caxlsx/pull/54) - Fix type detection for floats with out-of-rage exponents
25
+ - [I #67](https://github.com/caxlsx/caxlsx/issues/67) - Fix regression in worksheet name length enforcement: Some unicode characters were counted incorrectly, so that names that previously worked fine now stopped working. (This was introduced in 3.0.2)
26
+
4
27
  - **July.16.20**: 3.0.2
5
28
  - [I #51](https://github.com/caxlsx/caxlsx/issues/51) - Images do not import on Windows. IO read set explicitly to binary mode.
6
29
  - [PR #53](https://github.com/caxlsx/caxlsx/pull/53) - Limit column width to 255. Maximum column width limit in MS Excel is 255 characters, see https://support.microsoft.com/en-us/office/excel-specifications-and-limits-1672b34d-7043-467e-8e27-269d656771c3
@@ -133,7 +156,7 @@ CHANGELOG
133
156
  - added in interop requirements so that charts are properly exported
134
157
  to PDF from Libra Office
135
158
  - various readability improvements and work standardizing attribute
136
- names to snake_case. Aliases are provided for backward compatiblity
159
+ names to snake_case. Aliases are provided for backward compatiblity
137
160
 
138
161
  - **June.11.12**: 1.1.7
139
162
  - fix chart rendering issue when label offset is specified as a
@@ -174,23 +197,23 @@ in value caches
174
197
  - Added support for specifying the color of data series in charts.
175
198
  - bugfix using add_cell on row mismanaged calls to update_column_info.
176
199
 
177
- - ** April.25.12:**: 1.1.3
200
+ - **April.25.12:**: 1.1.3
178
201
  - Primarily because I am stupid.....Updates to readme to properly report version, add in missing docs and restructure example directory.
179
202
 
180
- - ** April.25.12:**: 1.1.2
203
+ - **April.25.12:**: 1.1.2
181
204
  - Conditional Formatting completely implemented.
182
205
  - refactoring / documentation for Style#add_style
183
206
  - added in label rotation for chart axis labels
184
207
  - bugfix to properly assign style and type info to cells when only partial information is provided in the types/style option
185
208
 
186
- - ** April.18.12**: 1.1.1
209
+ - **April.18.12**: 1.1.1
187
210
  - bugfix for autowidth calculations across multiple rows
188
211
  - bugfix for dimension calculations with nil cells.
189
212
  - REMOVED RMAGICK dependency WOOT!
190
213
  - Update readme to show screenshot of gem output.
191
214
  - Cleanup benchmark and add benchmark rake task
192
215
 
193
- - ** April.3.12**: 1.1.0
216
+ - **April.3.12**: 1.1.0
194
217
  - bugfix patch name_to_indecies to properly handle extended ranges.
195
218
  - bugfix properly serialize chart title.
196
219
  - lower rake minimum requirement for 1.8.7 apps that don't want to move on to 0.9 NOTE this will be reverted for 2.0.0 with workbook parsing!
@@ -205,7 +228,7 @@ in value caches
205
228
  - Major (like 7x faster!) performance updates.
206
229
  - Gem now supports for JRuby 1.6.7, as well as experimental support for Rubinius
207
230
 
208
- - ** March.5.12**: 1.0.18
231
+ - **March.5.12**: 1.0.18
209
232
  https://github.com/randym/axlsx/compare/1.0.17...1.0.18
210
233
  - bugfix custom borders are not properly applied when using styles.add_style
211
234
  - interop worksheet names must be 31 characters or less or some versions of office complain about repairs
@@ -215,14 +238,14 @@ in value caches
215
238
  - added << alias for add_row
216
239
  - removed presetting of date1904 based on authoring platform. Now defaults to use 1900 epoch (date1904 = false)
217
240
 
218
- - ** February.14.12**: 1.0.17
241
+ - **February.14.12**: 1.0.17
219
242
  https://github.com/randym/axlsx/compare/1.0.16...1.0.17
220
243
  - Added in support for serializing to StringIO
221
244
  - Added in support for using shared strings table. This makes most of the features in axlsx interoperable with iWorks Numbers
222
245
  - Added in support for fixed column_widths
223
246
  - Removed unneeded dependencies on active-support and i18n
224
247
 
225
- - ** February.2.12**: 1.0.16
248
+ - **February.2.12**: 1.0.16
226
249
  https://github.com/randym/axlsx/compare/1.0.15...1.0.16
227
250
  - Bug fix for schema file locations when validating in rails
228
251
  - Added hyperlink to images
data/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Caxlsx (Community Continued Version)
2
2
  [![Build Status](https://travis-ci.com/caxlsx/caxlsx.svg?branch=master)](https://travis-ci.com/caxlsx/caxlsx)
3
+ [![Gem
4
+ Version](https://badge.fury.io/rb/caxlsx.svg)](http://badge.fury.io/rb/caxlsx)
5
+ ![Total downloads](http://ruby-gem-downloads-badge.herokuapp.com/caxlsx?type=total)
6
+ ![Downloads for 3.1.1 (latest)](http://ruby-gem-downloads-badge.herokuapp.com/caxlsx/3.1.1?label=downloads%203.1.1)
3
7
 
4
8
  ## Notice: Community Axlsx Organization
5
9
 
File without changes
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ files = if !ARGV.empty?
4
+ ARGV.select { |file| File.exist?(file) }
5
+ else
6
+ Dir['*_example.md']
7
+ end
8
+
9
+ files.each do |file|
10
+ puts "Executing #{file.split('.')[0].tr('_', ' ')}"
11
+ code = File.read(file).match(/```ruby(?<code>.+)```/m)[:code]
12
+ unless code.nil?
13
+ eval(['$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib"', code].join("\n"))
14
+ end
15
+ end
@@ -31,17 +31,17 @@ module Axlsx
31
31
  alias :barDir :bar_dir
32
32
 
33
33
  # space between bar or column clusters, as a percentage of the bar or column width.
34
- # @return [String]
34
+ # @return [Integer]
35
35
  attr_reader :gap_depth
36
36
  alias :gapDepth :gap_depth
37
37
 
38
38
  # space between bar or column clusters, as a percentage of the bar or column width.
39
- # @return [String]
39
+ # @return [Integer]
40
40
  def gap_width
41
41
  @gap_width ||= 150
42
42
  end
43
43
  alias :gapWidth :gap_width
44
-
44
+
45
45
  #grouping for a column, line, or area chart.
46
46
  # must be one of [:percentStacked, :clustered, :standard, :stacked]
47
47
  # @return [Symbol]
@@ -56,9 +56,6 @@ module Axlsx
56
56
  @shape ||= :box
57
57
  end
58
58
 
59
- # validation regex for gap amount percent
60
- GAP_AMOUNT_PERCENT = /0*(([0-9])|([1-9][0-9])|([1-4][0-9][0-9])|500)%/
61
-
62
59
  # Creates a new bar chart object
63
60
  # @param [GraphicFrame] frame The workbook that owns this chart.
64
61
  # @option options [Cell, String] title
@@ -102,14 +99,14 @@ module Axlsx
102
99
 
103
100
  # space between bar or column clusters, as a percentage of the bar or column width.
104
101
  def gap_width=(v)
105
- RegexValidator.validate "Bar3DChart.gap_width", GAP_AMOUNT_PERCENT, v
102
+ RangeValidator.validate "Bar3DChart.gap_width", 0, 500, v
106
103
  @gap_width=(v)
107
104
  end
108
105
  alias :gapWidth= :gap_width=
109
106
 
110
107
  # space between bar or column clusters, as a percentage of the bar or column width.
111
108
  def gap_depth=(v)
112
- RegexValidator.validate "Bar3DChart.gap_didth", GAP_AMOUNT_PERCENT, v
109
+ RangeValidator.validate "Bar3DChart.gap_depth", 0, 500, v
113
110
  @gap_depth=(v)
114
111
  end
115
112
  alias :gapDepth= :gap_depth=
@@ -31,12 +31,7 @@ module Axlsx
31
31
  alias :barDir :bar_dir
32
32
 
33
33
  # space between bar or column clusters, as a percentage of the bar or column width.
34
- # @return [String]
35
- attr_reader :gap_depth
36
- alias :gapDepth :gap_depth
37
-
38
- # space between bar or column clusters, as a percentage of the bar or column width.
39
- # @return [String]
34
+ # @return [Integer]
40
35
  def gap_width
41
36
  @gap_width ||= 150
42
37
  end
@@ -49,6 +44,12 @@ module Axlsx
49
44
  @grouping ||= :clustered
50
45
  end
51
46
 
47
+ # Overlap between series
48
+ # @return [Integer]
49
+ def overlap
50
+ @overlap ||= 0
51
+ end
52
+
52
53
  # The shape of the bars or columns
53
54
  # must be one of [:cone, :coneToMax, :box, :cylinder, :pyramid, :pyramidToMax]
54
55
  # @return [Symbol]
@@ -56,9 +57,6 @@ module Axlsx
56
57
  @shape ||= :box
57
58
  end
58
59
 
59
- # validation regex for gap amount percent
60
- GAP_AMOUNT_PERCENT = /0*(([0-9])|([1-9][0-9])|([1-4][0-9][0-9])|500)%/
61
-
62
60
  # Creates a new bar chart object
63
61
  # @param [GraphicFrame] frame The workbook that owns this chart.
64
62
  # @option options [Cell, String] title
@@ -66,12 +64,11 @@ module Axlsx
66
64
  # @option options [Symbol] bar_dir
67
65
  # @option options [Symbol] grouping
68
66
  # @option options [String] gap_width
69
- # @option options [String] gap_depth
70
67
  # @option options [Symbol] shape
71
68
  # @see Chart
72
69
  def initialize(frame, options={})
73
70
  @vary_colors = true
74
- @gap_width, @gap_depth, @shape = nil, nil, nil
71
+ @gap_width, @overlap, @shape = nil, nil, nil
75
72
  super(frame, options)
76
73
  @series_type = BarSeries
77
74
  @d_lbls = nil
@@ -94,17 +91,15 @@ module Axlsx
94
91
 
95
92
  # space between bar or column clusters, as a percentage of the bar or column width.
96
93
  def gap_width=(v)
97
- RegexValidator.validate "BarChart.gap_width", GAP_AMOUNT_PERCENT, v
94
+ RangeValidator.validate "BarChart.gap_width", 0, 500, v
98
95
  @gap_width=(v)
99
96
  end
100
97
  alias :gapWidth= :gap_width=
101
98
 
102
- # space between bar or column clusters, as a percentage of the bar or column width.
103
- def gap_depth=(v)
104
- RegexValidator.validate "BarChart.gap_didth", GAP_AMOUNT_PERCENT, v
105
- @gap_depth=(v)
99
+ def overlap=(v)
100
+ RangeValidator.validate "BarChart.overlap", -100, 100, v
101
+ @overlap=(v)
106
102
  end
107
- alias :gapDepth= :gap_depth=
108
103
 
109
104
  # The shape of the bars or columns
110
105
  # must be one of [:cone, :coneToMax, :box, :cylinder, :pyramid, :pyramidToMax]
@@ -124,8 +119,8 @@ module Axlsx
124
119
  str << ('<c:varyColors val="' << vary_colors.to_s << '"/>')
125
120
  @series.each { |ser| ser.to_xml_string(str) }
126
121
  @d_lbls.to_xml_string(str) if @d_lbls
122
+ str << ('<c:overlap val="' << @overlap.to_s << '"/>') unless @overlap.nil?
127
123
  str << ('<c:gapWidth val="' << @gap_width.to_s << '"/>') unless @gap_width.nil?
128
- str << ('<c:gapDepth val="' << @gap_depth.to_s << '"/>') unless @gap_depth.nil?
129
124
  str << ('<c:shape val="' << @shape.to_s << '"/>') unless @shape.nil?
130
125
  axes.to_xml_string(str, :ids => true)
131
126
  str << '</c:barChart>'
@@ -22,12 +22,18 @@ module Axlsx
22
22
  # An array of rgb colors to apply to your bar chart.
23
23
  attr_reader :colors
24
24
 
25
+ # The fill color for this series.
26
+ # Red, green, and blue is expressed as sequence of hex digits, RRGGBB.
27
+ # @return [String]
28
+ attr_reader :series_color
29
+
25
30
  # Creates a new series
26
31
  # @option options [Array, SimpleTypedList] data
27
32
  # @option options [Array, SimpleTypedList] labels
28
33
  # @option options [String] title
29
34
  # @option options [String] shape
30
35
  # @option options [String] colors an array of colors to use when rendering each data point
36
+ # @option options [String] series_color a color to use when rendering series
31
37
  # @param [Chart] chart
32
38
  def initialize(chart, options={})
33
39
  @shape = :box
@@ -40,6 +46,10 @@ module Axlsx
40
46
  # @see colors
41
47
  def colors=(v) DataTypeValidator.validate "BarSeries.colors", [Array], v; @colors = v end
42
48
 
49
+ def series_color=(v)
50
+ @series_color = v
51
+ end
52
+
43
53
  # @see shape
44
54
  def shape=(v)
45
55
  RestrictionValidator.validate "BarSeries.shape", [:cone, :coneToMax, :box, :cylinder, :pyramid, :pyramidToMax], v
@@ -60,9 +70,16 @@ module Axlsx
60
70
  str << '</a:solidFill></c:spPr></c:dPt>'
61
71
  end
62
72
 
73
+ if series_color
74
+ str << '<c:spPr><a:solidFill>'
75
+ str << ('<a:srgbClr val="' << series_color << '"/>')
76
+ str << '</a:solidFill>'
77
+ str << '</c:spPr>'
78
+ end
79
+
63
80
  @labels.to_xml_string(str) unless @labels.nil?
64
81
  @data.to_xml_string(str) unless @data.nil?
65
- # this is actually only required for shapes other than box
82
+ # this is actually only required for shapes other than box
66
83
  str << ('<c:shape val="' << shape.to_s << '"></c:shape>')
67
84
  end
68
85
  end
@@ -47,7 +47,7 @@ module Axlsx
47
47
  # @return [String]
48
48
  def to_xml_string(str = '')
49
49
  super(str) do
50
- str << '<c:explosion val="' + @explosion + '"/>' unless @explosion.nil?
50
+ str << '<c:explosion val="' + @explosion.to_s + '"/>' unless @explosion.nil?
51
51
  colors.each_with_index do |c, index|
52
52
  str << '<c:dPt>'
53
53
  str << ('<c:idx val="' << index.to_s << '"/>')
data/lib/axlsx/package.rb CHANGED
@@ -74,10 +74,14 @@ module Axlsx
74
74
  # Serialize your workbook to disk as an xlsx document.
75
75
  #
76
76
  # @param [String] output The name of the file you want to serialize your package to
77
- # @param [Boolean] confirm_valid Validate the package prior to serialization.
77
+ # @param [Hash] options
78
+ # @option options [Boolean] :confirm_valid Validate the package prior to serialization.
79
+ # @option options [String] :zip_command When `nil`, `#serialize` with RubyZip to
80
+ # zip the XLSX file contents. When a String, the provided zip command (e.g.,
81
+ # "zip") is used to zip the file contents (may be faster for large files)
78
82
  # @return [Boolean] False if confirm_valid and validation errors exist. True if the package was serialized
79
83
  # @note A tremendous amount of effort has gone into ensuring that you cannot create invalid xlsx documents.
80
- # confirm_valid should be used in the rare case that you cannot open the serialized file.
84
+ # options[:confirm_valid] should be used in the rare case that you cannot open the serialized file.
81
85
  # @see Package#validate
82
86
  # @example
83
87
  # # This is how easy it is to create a valid xlsx file. Of course you might want to add a sheet or two, and maybe some data, styles and charts.
@@ -88,13 +92,24 @@ module Axlsx
88
92
  # # ......add cool stuff to your workbook......
89
93
  # p.serialize("example.xlsx")
90
94
  #
95
+ # # Serialize to a file, using a system zip binary
96
+ # p.serialize("example.xlsx", zip_command: "zip", confirm_valid: false)
97
+ # p.serialize("example.xlsx", zip_command: "/path/to/zip")
98
+ # p.serialize("example.xlsx", zip_command: "zip -1")
99
+ #
91
100
  # # Serialize to a stream
92
101
  # s = p.to_stream()
93
102
  # File.open('example_streamed.xlsx', 'w') { |f| f.write(s.read) }
94
- def serialize(output, confirm_valid=false)
103
+ def serialize(output, options = {}, secondary_options = nil)
104
+ confirm_valid, zip_command = parse_serialize_options(options, secondary_options)
95
105
  return false unless !confirm_valid || self.validate.empty?
106
+ zip_provider = if zip_command
107
+ ZipCommand.new(zip_command)
108
+ else
109
+ Zip::OutputStream
110
+ end
96
111
  Relationship.initialize_ids_cache
97
- Zip::OutputStream.open(output) do |zip|
112
+ zip_provider.open(output) do |zip|
98
113
  write_parts(zip)
99
114
  end
100
115
  true
@@ -153,8 +168,8 @@ module Axlsx
153
168
  private
154
169
 
155
170
  # Writes the package parts to a zip archive.
156
- # @param [Zip::OutputStream] zip
157
- # @return [Zip::OutputStream]
171
+ # @param [Zip::OutputStream, ZipCommand] zip
172
+ # @return [Zip::OutputStream, ZipCommand]
158
173
  def write_parts(zip)
159
174
  p = parts
160
175
  p.each do |part|
@@ -346,5 +361,28 @@ module Axlsx
346
361
  rels.lock
347
362
  rels
348
363
  end
364
+
365
+ # Parse the arguments of `#serialize`
366
+ # @return [Boolean, (String or nil)] Returns an array where the first value is
367
+ # `confirm_valid` and the second is the `zip_command`.
368
+ # @private
369
+ def parse_serialize_options(options, secondary_options)
370
+ if secondary_options
371
+ warn "[DEPRECATION] Axlsx::Package#serialize with 3 arguments is deprecated. " +
372
+ "Use keyword args instead e.g., package.serialize(output, confirm_valid: false, zip_command: 'zip')"
373
+ end
374
+ if options.is_a?(Hash)
375
+ options.merge!(secondary_options || {})
376
+ invalid_keys = options.keys - [:confirm_valid, :zip_command]
377
+ if invalid_keys.any?
378
+ raise ArgumentError.new("Invalid keyword arguments: #{invalid_keys}")
379
+ end
380
+ [options.fetch(:confirm_valid, false), options.fetch(:zip_command, nil)]
381
+ else
382
+ warn "[DEPRECATION] Axlsx::Package#serialize with confirm_valid as a boolean is deprecated. " +
383
+ "Use keyword args instead e.g., package.serialize(output, confirm_valid: false)"
384
+ parse_serialize_options((secondary_options || {}).merge(confirm_valid: options), nil)
385
+ end
386
+ end
349
387
  end
350
388
  end
@@ -393,7 +393,8 @@ module Axlsx
393
393
  ISO_8601_REGEX = /\A(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[0-1]|0[1-9]|[1-2][0-9])T(2[0-3]|[0-1][0-9]):([0-5][0-9]):([0-5][0-9])(\.[0-9]+)?(Z|[+-](?:2[0-3]|[0-1][0-9]):[0-5][0-9])?\Z/.freeze
394
394
 
395
395
  # FLOAT recognition
396
- FLOAT_REGEX = /\A[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?\Z/.freeze
396
+ SAFE_FLOAT_REGEX = /\A[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]{1,2})?\Z/.freeze
397
+ MAYBE_FLOAT_REGEX = /\A[-+]?[0-9]*\.?[0-9]+[eE](?<exp>[-+]?[0-9]{3})\Z/.freeze
397
398
 
398
399
  # Numeric recognition
399
400
  NUMERIC_REGEX = /\A[+-]?\d+?\Z/.freeze
@@ -5,7 +5,7 @@ module Axlsx
5
5
  # @param [String] v File path
6
6
  # @return [String] File mime type
7
7
  def self.get_mime_type(v)
8
- MimeMagic.by_magic(File.open(v)).to_s
8
+ Marcel::MimeType.for(Pathname.new(v))
9
9
  end
10
10
  end
11
11
  end
@@ -269,7 +269,7 @@ module Axlsx
269
269
  # valid types must be one of custom, data, decimal, list, none, textLength, time, whole
270
270
  # @param [Any] v The value validated
271
271
  def self.validate_data_validation_type(v)
272
- RestrictionValidator.validate :data_validation_type, [:custom, :data, :decimal, :list, :none, :textLength, :time, :whole], v
272
+ RestrictionValidator.validate :data_validation_type, [:custom, :data, :decimal, :list, :none, :textLength, :date, :time, :whole], v
273
273
  end
274
274
 
275
275
  # Requires that the value is a valid sheet view type.
@@ -0,0 +1,73 @@
1
+ # encoding: UTF-8
2
+ require 'open3'
3
+ require 'shellwords'
4
+
5
+ module Axlsx
6
+
7
+ # The ZipCommand class supports zipping the Excel file contents using
8
+ # a binary zip program instead of RubyZip's `Zip::OutputStream`.
9
+ #
10
+ # The methods provided here mimic `Zip::OutputStream` so that `ZipCommand` can
11
+ # be used as a drop-in replacement. Note that method signatures are not
12
+ # identical to `Zip::OutputStream`, they are only sufficiently close so that
13
+ # `ZipCommand` and `Zip::OutputStream` can be interchangeably used within
14
+ # `caxlsx`.
15
+ class ZipCommand
16
+ # Raised when the zip command exits with a non-zero status.
17
+ class ZipError < StandardError; end
18
+
19
+ def initialize(zip_command)
20
+ @current_file = nil
21
+ @files = []
22
+ @zip_command = zip_command
23
+ end
24
+
25
+ # Create a temporary directory for writing files to.
26
+ #
27
+ # The directory and its contents are removed at the end of the block.
28
+ def open(output, &block)
29
+ Dir.mktmpdir do |dir|
30
+ @dir = dir
31
+ block.call(self)
32
+ write_file
33
+ zip_parts(output)
34
+ end
35
+ end
36
+
37
+ # Closes the current entry and opens a new for writing.
38
+ def put_next_entry(entry)
39
+ write_file
40
+ @current_file = "#{@dir}/#{entry.name}"
41
+ @files << entry.name
42
+ FileUtils.mkdir_p(File.dirname(@current_file))
43
+ end
44
+
45
+ # Write to a buffer that will be written to the current entry
46
+ def write(content)
47
+ @buffer << content
48
+ end
49
+ alias << write
50
+
51
+ private
52
+
53
+ def write_file
54
+ if @current_file
55
+ @buffer.rewind
56
+ File.open(@current_file, "wb") { |f| f.write @buffer.read }
57
+ end
58
+ @current_file = nil
59
+ @buffer = StringIO.new
60
+ end
61
+
62
+ def zip_parts(output)
63
+ output = Shellwords.shellescape(File.absolute_path(output))
64
+ inputs = Shellwords.shelljoin(@files)
65
+ escaped_dir = Shellwords.shellescape(@dir)
66
+ command = "cd #{escaped_dir} && #{@zip_command} #{output} #{inputs}"
67
+ stdout_and_stderr, status = Open3.capture2e(command)
68
+ if !status.success?
69
+ raise(ZipError.new(stdout_and_stderr))
70
+ end
71
+ end
72
+ end
73
+ end
data/lib/axlsx/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Axlsx
2
2
 
3
3
  # The current version
4
- VERSION = "3.0.2"
4
+ VERSION = "3.1.1"
5
5
  end
@@ -451,9 +451,11 @@ module Axlsx
451
451
  :time
452
452
  elsif v.is_a?(TrueClass) || v.is_a?(FalseClass)
453
453
  :boolean
454
- elsif v.to_s =~ Axlsx::NUMERIC_REGEX
454
+ elsif v.to_s =~ Axlsx::NUMERIC_REGEX && v.respond_to?(:to_i)
455
455
  :integer
456
- elsif v.to_s =~ Axlsx::FLOAT_REGEX
456
+ elsif v.to_s =~ Axlsx::SAFE_FLOAT_REGEX && v.respond_to?(:to_f)
457
+ :float
458
+ elsif (matchdata = v.to_s.match(MAYBE_FLOAT_REGEX)) && (Float::MIN_10_EXP..Float::MAX_10_EXP).cover?(matchdata[:exp].to_i) && v.respond_to?(:to_f)
457
459
  :float
458
460
  elsif v.to_s =~ Axlsx::ISO_8601_REGEX
459
461
  :iso_8601
@@ -473,7 +475,11 @@ module Axlsx
473
475
  case type
474
476
  when :date
475
477
  self.style = STYLE_DATE if self.style == 0
476
- v
478
+ if !v.is_a?(Date) && v.respond_to?(:to_date)
479
+ v.to_date
480
+ else
481
+ v
482
+ end
477
483
  when :time
478
484
  self.style = STYLE_DATE if self.style == 0
479
485
  if !v.is_a?(Time) && v.respond_to?(:to_time)
@@ -171,7 +171,7 @@ module Axlsx
171
171
  def formula1=(v); Axlsx::validate_string(v); @formula1 = v end
172
172
 
173
173
  # @see formula2
174
- def formula2=(v); Axlsx::validate_string(v); @formula2 = v end
174
+ def formula2=(v); Axlsx::validate_string(v); @formula2 = v end
175
175
 
176
176
  # @see allowBlank
177
177
  def allowBlank=(v); Axlsx::validate_boolean(v); @allowBlank = v end
@@ -216,8 +216,8 @@ module Axlsx
216
216
  valid_attributes = get_valid_attributes
217
217
 
218
218
  str << '<dataValidation '
219
- str << instance_values.map do |key, value|
220
- '' << key << '="' << Axlsx.booleanize(value).to_s << '"' if (valid_attributes.include?(key.to_sym) && !CHILD_ELEMENTS.include?(key.to_sym))
219
+ str << instance_values.map do |key, value|
220
+ '' << key << '="' << Axlsx.booleanize(value).to_s << '"' if (valid_attributes.include?(key.to_sym) && !CHILD_ELEMENTS.include?(key.to_sym))
221
221
  end.join(' ')
222
222
  str << '>'
223
223
  str << ('<formula1>' << self.formula1 << '</formula1>') if @formula1 and valid_attributes.include?(:formula1)
@@ -229,7 +229,7 @@ module Axlsx
229
229
  def get_valid_attributes
230
230
  attributes = [:allowBlank, :error, :errorStyle, :errorTitle, :prompt, :promptTitle, :showErrorMessage, :showInputMessage, :sqref, :type ]
231
231
 
232
- if [:whole, :decimal, :data, :time, :textLength].include?(@type)
232
+ if [:whole, :decimal, :data, :time, :date, :textLength].include?(@type)
233
233
  attributes << [:operator, :formula1]
234
234
  attributes << [:formula2] if [:between, :notBetween].include?(@operator)
235
235
  elsif @type == :list
@@ -111,8 +111,12 @@ module Axlsx
111
111
  if data_field.is_a? String
112
112
  data_field = {:ref => data_field}
113
113
  end
114
- data_field.values.each do |value|
115
- DataTypeValidator.validate "#{self.class}.data[]", [String], value
114
+ data_field.each do |key, value|
115
+ if key == :num_fmt
116
+ DataTypeValidator.validate "#{self.class}.data[]", [Integer], value
117
+ else
118
+ DataTypeValidator.validate "#{self.class}.data[]", [String], value
119
+ end
116
120
  end
117
121
  @data << data_field
118
122
  end
@@ -212,6 +216,7 @@ module Axlsx
212
216
  data.each do |datum_value|
213
217
  # The correct name prefix in ["Sum","Average", etc...]
214
218
  str << "<dataField name='#{(datum_value[:subtotal]||'')} of #{datum_value[:ref]}' fld='#{header_index_of(datum_value[:ref])}' baseField='0' baseItem='0'"
219
+ str << " numFmtId='#{datum_value[:num_fmt]}'" if datum_value[:num_fmt]
215
220
  str << " subtotal='#{datum_value[:subtotal]}' " if datum_value[:subtotal]
216
221
  str << "/>"
217
222
  end
@@ -53,7 +53,7 @@ module Axlsx
53
53
  str << '</cacheSource>'
54
54
  str << ( '<cacheFields count="' << pivot_table.header_cells_count.to_s << '">')
55
55
  pivot_table.header_cells.each do |cell|
56
- str << ( '<cacheField name="' << cell.value << '" numFmtId="0">')
56
+ str << ( '<cacheField name="' << cell.clean_value << '" numFmtId="0">')
57
57
  str << '<sharedItems count="0">'
58
58
  str << '</sharedItems>'
59
59
  str << '</cacheField>'
@@ -25,11 +25,12 @@ module Axlsx
25
25
  # @option options [Array, Symbol] types
26
26
  # @option options [Array, Integer] style
27
27
  # @option options [Float] height the row's height (in points)
28
+ # @option options [Integer] offset - add empty columns before values
28
29
  # @see Row#array_to_cells
29
30
  # @see Cell
30
31
  def initialize(worksheet, values=[], options={})
31
32
  self.worksheet = worksheet
32
- super(Cell, nil, values.size)
33
+ super(Cell, nil, values.size + options[:offset].to_i)
33
34
  self.height = options.delete(:height)
34
35
  worksheet.rows << self
35
36
  array_to_cells(values, options)
@@ -56,7 +57,7 @@ module Axlsx
56
57
  attr_reader :outline_level
57
58
  alias :outlineLevel :outline_level
58
59
 
59
- # The style applied ot the row. This affects the entire row.
60
+ # The style applied to the row. This affects the entire row.
60
61
  # @return [Integer]
61
62
  attr_reader :s
62
63
 
@@ -147,14 +148,15 @@ module Axlsx
147
148
  # @option options [Array, Integer] style
148
149
  def array_to_cells(values, options={})
149
150
  DataTypeValidator.validate :array_to_cells, Array, values
150
- types, style, formula_values, escape_formulas = options.delete(:types), options.delete(:style), options.delete(:formula_values), options.delete(:escape_formulas)
151
+ types, style, formula_values, escape_formulas, offset = options.delete(:types), options.delete(:style), options.delete(:formula_values), options.delete(:escape_formulas), options.delete(:offset)
152
+ offset.to_i.times { |index| self[index] = Cell.new(self) } if offset
151
153
  values.each_with_index do |value, index|
152
154
  options[:style] = style.is_a?(Array) ? style[index] : style if style
153
155
  options[:type] = types.is_a?(Array) ? types[index] : types if types
154
156
  options[:escape_formulas] = escape_formulas.is_a?(Array) ? escape_formulas[index] : escape_formulas if escape_formulas
155
157
  options[:formula_value] = formula_values[index] if formula_values.is_a?(Array)
156
158
 
157
- self[index] = Cell.new(self, value, options)
159
+ self[index + offset.to_i] = Cell.new(self, value, options)
158
160
  end
159
161
  end
160
162
  end