caxlsx 3.0.2 → 3.1.1

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