xlsxrb 0.1.9 → 0.1.11

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -0
  3. data/README.md +71 -253
  4. data/Rakefile +2 -1
  5. data/benchmark.rb +3 -1
  6. data/docs/PEER_LIBRARIES.md +121 -0
  7. data/docs/assets/benchmark_results.svg +144 -0
  8. data/docs/assets/lsp_autocompletion.png +0 -0
  9. data/docs/assets/playground_preview.png +0 -0
  10. data/docs/visual/VisualGallery.md +183 -0
  11. data/docs/wasm/ruby.wasm +0 -0
  12. data/lib/xlsxrb/chart_builder.rb +333 -0
  13. data/lib/xlsxrb/elements/cell.rb +66 -12
  14. data/lib/xlsxrb/elements/workbook.rb +2 -2
  15. data/lib/xlsxrb/ooxml/reader/listeners.rb +6450 -0
  16. data/lib/xlsxrb/ooxml/reader.rb +2 -6438
  17. data/lib/xlsxrb/ooxml/workbook_writer.rb +297 -236
  18. data/lib/xlsxrb/ooxml/worksheet_parser.rb +218 -266
  19. data/lib/xlsxrb/ooxml/worksheet_writer.rb +127 -10
  20. data/lib/xlsxrb/ooxml/zip_writer.rb +31 -38
  21. data/lib/xlsxrb/stream_row.rb +34 -8
  22. data/lib/xlsxrb/stream_sheet.rb +119 -0
  23. data/lib/xlsxrb/stream_writer.rb +1733 -0
  24. data/lib/xlsxrb/version.rb +1 -1
  25. data/lib/xlsxrb/workbook_builder.rb +309 -0
  26. data/lib/xlsxrb/worksheet_builder.rb +691 -0
  27. data/lib/xlsxrb.rb +238 -2968
  28. data/sig/generated/xlsxrb/chart_builder.rbs +269 -0
  29. data/sig/generated/xlsxrb/elements/cell.rbs +137 -12
  30. data/sig/generated/xlsxrb/ooxml/reader/listeners.rbs +1074 -0
  31. data/sig/generated/xlsxrb/ooxml/reader.rbs +0 -1067
  32. data/sig/generated/xlsxrb/ooxml/workbook_writer.rbs +17 -1
  33. data/sig/generated/xlsxrb/ooxml/worksheet_parser.rbs +18 -4
  34. data/sig/generated/xlsxrb/ooxml/worksheet_writer.rbs +4 -0
  35. data/sig/generated/xlsxrb/stream_row.rbs +6 -2
  36. data/sig/generated/xlsxrb/stream_sheet.rbs +100 -0
  37. data/sig/generated/xlsxrb/stream_writer.rbs +960 -0
  38. data/sig/generated/xlsxrb/workbook_builder.rbs +146 -0
  39. data/sig/generated/xlsxrb/worksheet_builder.rbs +349 -0
  40. data/sig/generated/xlsxrb.rbs +103 -1563
  41. metadata +17 -1
@@ -0,0 +1,333 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rbs_inline: enabled
4
+
5
+ module Xlsxrb
6
+ # Builder for block-style chart definitions.
7
+ #
8
+ # @example Create a chart definition
9
+ # builder = ChartBuilder.new
10
+ # builder.type(:bar)
11
+ # builder.title("Quarterly Sales")
12
+ # builder.series do |s|
13
+ # s.name("2026")
14
+ # s.categories("Sheet1!$A$2:$A$5")
15
+ # s.values("Sheet1!$B$2:$B$5")
16
+ # end
17
+ #
18
+ # @api public
19
+ class ChartBuilder
20
+ # @return [Hash{Symbol => Object}] The configured chart options.
21
+ #: () -> void
22
+ def initialize
23
+ @options = {}
24
+ end
25
+
26
+ # @return [Hash{Symbol => Object}]
27
+ #: Hash[Symbol, untyped]
28
+ attr_reader :options
29
+
30
+ # Sets the chart type (e.g. :line, :bar, :pie, :area, :radar, :scatter).
31
+ #
32
+ # @param value [Symbol, String] Chart type.
33
+ # @return [Symbol, String]
34
+ # @api public
35
+ #: (Symbol | String value) -> (Symbol | String)
36
+ def type(value)
37
+ @options[:type] = value
38
+ end
39
+
40
+ # Sets the chart title.
41
+ #
42
+ # @param value [String, Hash, nil] Chart title text or options hash.
43
+ # @return [String, Hash, nil]
44
+ # @api public
45
+ #: (String | Hash[Symbol, untyped] | nil value) -> (String | Hash[Symbol, untyped] | nil)
46
+ def title(value)
47
+ @options[:title] = value
48
+ end
49
+
50
+ # Adds a data series to the chart.
51
+ #
52
+ # @overload series(value)
53
+ # Adds a pre-built series options hash.
54
+ # @param value [Hash{Symbol => Object}] Series options hash.
55
+ # @return [Array<Hash{Symbol => Object}>]
56
+ #
57
+ # @overload series(&block)
58
+ # Configures a series using a block.
59
+ # @yield [series_builder]
60
+ # @yieldparam series_builder [SeriesBuilder]
61
+ # @return [Array<Hash{Symbol => Object}>]
62
+ #
63
+ # @api public
64
+ #: (?Hash[Symbol, untyped]? value) ?{ (SeriesBuilder) -> void } -> Array[Hash[Symbol, untyped]]
65
+ def series(value = nil)
66
+ @options[:series] ||= []
67
+ if block_given?
68
+ sb = SeriesBuilder.new
69
+ yield sb
70
+ @options[:series] << sb.options
71
+ elsif value
72
+ @options[:series] << value
73
+ end
74
+ @options[:series]
75
+ end
76
+
77
+ # Configures the legend property for this chart.
78
+ #
79
+ # @param args [Array] Positional arguments (e.g. position string).
80
+ # @param kwargs [Hash] Keyword arguments for legend styling/layout.
81
+ # @return [Object] The configured legend property.
82
+ # @api public
83
+ #: (*(Hash[Symbol, String | Integer | bool | nil] | String) args, **String | Integer | bool | nil kwargs) -> (Hash[Symbol, String | Integer | bool | nil] | String)
84
+ def legend(*args, **kwargs)
85
+ @options[:legend] = kwargs.empty? ? args.first : kwargs
86
+ end
87
+
88
+ # Configures the plot_area property for this chart.
89
+ #
90
+ # @param args [Array] Positional arguments.
91
+ # @param kwargs [Hash] Keyword arguments for plot area configuration.
92
+ # @return [Object] The configured plot_area property.
93
+ # @api public
94
+ #: (*(Hash[Symbol, String | Integer | bool | nil] | String) args, **String | Integer | bool | nil kwargs) -> (Hash[Symbol, String | Integer | bool | nil] | String)
95
+ def plot_area(*args, **kwargs)
96
+ @options[:plot_area] = kwargs.empty? ? args.first : kwargs
97
+ end
98
+
99
+ # Configures the chart_space property for this chart.
100
+ #
101
+ # @param args [Array] Positional arguments.
102
+ # @param kwargs [Hash] Keyword arguments.
103
+ # @return [Object] The configured chart_space property.
104
+ # @api public
105
+ #: (*(Hash[Symbol, String | Integer | bool | nil] | String) args, **String | Integer | bool | nil kwargs) -> (Hash[Symbol, String | Integer | bool | nil] | String)
106
+ def chart_space(*args, **kwargs)
107
+ @options[:chart_space] = kwargs.empty? ? args.first : kwargs
108
+ end
109
+
110
+ # Configures the style index/id for this chart.
111
+ #
112
+ # @param args [Array] Positional arguments.
113
+ # @param kwargs [Hash] Keyword arguments.
114
+ # @return [Object] The configured style property.
115
+ # @api public
116
+ #: (*(Hash[Symbol, String | Integer | bool | nil] | String | Integer) args, **String | Integer | bool | nil kwargs) -> (Hash[Symbol, String | Integer | bool | nil] | String | Integer)
117
+ def style(*args, **kwargs)
118
+ @options[:style] = kwargs.empty? ? args.first : kwargs
119
+ end
120
+
121
+ # Configures data labels for this chart.
122
+ #
123
+ # @param args [Array] Positional arguments.
124
+ # @param kwargs [Hash] Keyword arguments.
125
+ # @return [Object] The configured data_labels property.
126
+ # @api public
127
+ #: (*(Hash[Symbol, String | Integer | bool | nil] | String) args, **String | Integer | bool | nil kwargs) -> (Hash[Symbol, String | Integer | bool | nil] | String)
128
+ def data_labels(*args, **kwargs)
129
+ @options[:data_labels] = kwargs.empty? ? args.first : kwargs
130
+ end
131
+
132
+ # Configures whether to plot visible cells only.
133
+ #
134
+ # @param args [Array] Positional arguments.
135
+ # @param kwargs [Hash] Keyword arguments.
136
+ # @return [Boolean, String]
137
+ # @api public
138
+ #: (*(bool | String) args, **String | Integer | bool | nil kwargs) -> (bool | String)
139
+ def plot_visible_only(*args, **kwargs)
140
+ @options[:plot_visible_only] = kwargs.empty? ? args.first : kwargs
141
+ end
142
+
143
+ # Configures how blank cells are displayed in the chart.
144
+ #
145
+ # @param args [Array] Positional arguments.
146
+ # @param kwargs [Hash] Keyword arguments.
147
+ # @return [String]
148
+ # @api public
149
+ #: (*(String) args, **String | Integer | bool | nil kwargs) -> String
150
+ def display_blanks_as(*args, **kwargs)
151
+ @options[:display_blanks_as] = kwargs.empty? ? args.first : kwargs
152
+ end
153
+
154
+ # Configures 3D view properties for 3D charts.
155
+ #
156
+ # @param args [Array] Positional arguments.
157
+ # @param kwargs [Hash] Keyword arguments.
158
+ # @return [Object]
159
+ # @api public
160
+ #: (*(Hash[Symbol, String | Integer | bool | nil] | String) args, **String | Integer | bool | nil kwargs) -> (Hash[Symbol, String | Integer | bool | nil] | String)
161
+ def view3d(*args, **kwargs)
162
+ @options[:view3d] = kwargs.empty? ? args.first : kwargs
163
+ end
164
+
165
+ # Configures the category axis properties for this chart.
166
+ #
167
+ # @param args [Array] Positional arguments.
168
+ # @param kwargs [Hash] Keyword arguments.
169
+ # @return [Object]
170
+ # @api public
171
+ #: (*(Hash[Symbol, String | Integer | bool | nil] | String) args, **String | Integer | bool | nil kwargs) -> (Hash[Symbol, String | Integer | bool | nil] | String)
172
+ def category_axis(*args, **kwargs)
173
+ @options[:category_axis] = kwargs.empty? ? args.first : kwargs
174
+ end
175
+
176
+ # Configures the value axis properties for this chart.
177
+ #
178
+ # @param args [Array] Positional arguments.
179
+ # @param kwargs [Hash] Keyword arguments.
180
+ # @return [Object]
181
+ # @api public
182
+ #: (*(Hash[Symbol, String | Integer | bool | nil] | String) args, **String | Integer | bool | nil kwargs) -> (Hash[Symbol, String | Integer | bool | nil] | String)
183
+ def value_axis(*args, **kwargs)
184
+ @options[:value_axis] = kwargs.empty? ? args.first : kwargs
185
+ end
186
+
187
+ # Configures whether to show the legend key in data labels.
188
+ #
189
+ # @param args [Array] Positional arguments.
190
+ # @param kwargs [Hash] Keyword arguments.
191
+ # @return [Boolean, String]
192
+ # @api public
193
+ #: (*(bool | String) args, **String | Integer | bool | nil kwargs) -> (bool | String)
194
+ def show_legend_key(*args, **kwargs)
195
+ @options[:show_legend_key] = kwargs.empty? ? args.first : kwargs
196
+ end
197
+
198
+ # Builder for a single series entry in block-style chart definitions.
199
+ #
200
+ # @api public
201
+ class SeriesBuilder
202
+ #: () -> void
203
+ def initialize
204
+ @options = {}
205
+ end
206
+
207
+ # @return [Hash{Symbol => Object}]
208
+ #: Hash[Symbol, untyped]
209
+ attr_reader :options
210
+
211
+ # Configures categories reference for this series (e.g. "Sheet1!$A$2:$A$10").
212
+ #
213
+ # @param args [Array] Positional arguments.
214
+ # @param kwargs [Hash] Keyword arguments.
215
+ # @return [Object]
216
+ # @api public
217
+ #: (*untyped args, **untyped kwargs) -> untyped
218
+ def categories(*args, **kwargs)
219
+ @options[:categories] = kwargs.empty? ? args.first : kwargs
220
+ end
221
+
222
+ # Configures values reference for this series (e.g. "Sheet1!$B$2:$B$10").
223
+ #
224
+ # @param args [Array] Positional arguments.
225
+ # @param kwargs [Hash] Keyword arguments.
226
+ # @return [Object]
227
+ # @api public
228
+ #: (*untyped args, **untyped kwargs) -> untyped
229
+ def values(*args, **kwargs)
230
+ @options[:values] = kwargs.empty? ? args.first : kwargs
231
+ end
232
+
233
+ # Configures the name for this series (e.g. "Total Sales" or cell reference "Sheet1!$B$1").
234
+ #
235
+ # @param args [Array] Positional arguments.
236
+ # @param kwargs [Hash] Keyword arguments.
237
+ # @return [Object]
238
+ # @api public
239
+ #: (*untyped args, **untyped kwargs) -> untyped
240
+ def name(*args, **kwargs)
241
+ @options[:name] = kwargs.empty? ? args.first : kwargs
242
+ end
243
+
244
+ # Configures data markers for this series.
245
+ #
246
+ # @param args [Array] Positional arguments.
247
+ # @param kwargs [Hash] Keyword arguments.
248
+ # @return [Object]
249
+ # @api public
250
+ #: (*untyped args, **untyped kwargs) -> untyped
251
+ def marker(*args, **kwargs)
252
+ @options[:marker] = kwargs.empty? ? args.first : kwargs
253
+ end
254
+
255
+ # Configures fill color/pattern for this series.
256
+ #
257
+ # @param args [Array] Positional arguments.
258
+ # @param kwargs [Hash] Keyword arguments.
259
+ # @return [Object]
260
+ # @api public
261
+ #: (*(Hash[Symbol, String | Integer | bool | nil] | String) args, **String | Integer | bool | nil kwargs) -> (Hash[Symbol, String | Integer | bool | nil] | String)
262
+ def fill(*args, **kwargs)
263
+ @options[:fill] = kwargs.empty? ? args.first : kwargs
264
+ end
265
+
266
+ # Configures line color/thickness for this series.
267
+ #
268
+ # @param args [Array] Positional arguments.
269
+ # @param kwargs [Hash] Keyword arguments.
270
+ # @return [Object]
271
+ # @api public
272
+ #: (*(Hash[Symbol, String | Integer | bool | nil] | String) args, **String | Integer | bool | nil kwargs) -> (Hash[Symbol, String | Integer | bool | nil] | String)
273
+ def line(*args, **kwargs)
274
+ @options[:line] = kwargs.empty? ? args.first : kwargs
275
+ end
276
+
277
+ # Configures trendline for this series.
278
+ #
279
+ # @param args [Array] Positional arguments.
280
+ # @param kwargs [Hash] Keyword arguments.
281
+ # @return [Object]
282
+ # @api public
283
+ #: (*(Hash[Symbol, String | Integer | bool | nil] | String) args, **String | Integer | bool | nil kwargs) -> (Hash[Symbol, String | Integer | bool | nil] | String)
284
+ def trendline(*args, **kwargs)
285
+ @options[:trendline] = kwargs.empty? ? args.first : kwargs
286
+ end
287
+
288
+ # Configures data labels for this series.
289
+ #
290
+ # @param args [Array] Positional arguments.
291
+ # @param kwargs [Hash] Keyword arguments.
292
+ # @return [Object]
293
+ # @api public
294
+ #: (*(Hash[Symbol, String | Integer | bool | nil] | String) args, **String | Integer | bool | nil kwargs) -> (Hash[Symbol, String | Integer | bool | nil] | String)
295
+ def data_labels(*args, **kwargs)
296
+ @options[:data_labels] = kwargs.empty? ? args.first : kwargs
297
+ end
298
+
299
+ # Configures line smoothing for this series.
300
+ #
301
+ # @param args [Array] Positional arguments.
302
+ # @param kwargs [Hash] Keyword arguments.
303
+ # @return [Boolean, String]
304
+ # @api public
305
+ #: (*(bool | String) args, **String | Integer | bool | nil kwargs) -> (bool | String)
306
+ def smooth(*args, **kwargs)
307
+ @options[:smooth] = kwargs.empty? ? args.first : kwargs
308
+ end
309
+
310
+ # Configures 3D bar/column shape (e.g. "cylinder", "cone", "pyramid").
311
+ #
312
+ # @param args [Array] Positional arguments.
313
+ # @param kwargs [Hash] Keyword arguments.
314
+ # @return [String]
315
+ # @api public
316
+ #: (*(String) args, **String | Integer | bool | nil kwargs) -> String
317
+ def shape(*args, **kwargs)
318
+ @options[:shape] = kwargs.empty? ? args.first : kwargs
319
+ end
320
+
321
+ # Configures individual series chart type override in combo charts.
322
+ #
323
+ # @param args [Array] Positional arguments.
324
+ # @param kwargs [Hash] Keyword arguments.
325
+ # @return [String]
326
+ # @api public
327
+ #: (*(String) args, **String | Integer | bool | nil kwargs) -> String
328
+ def type(*args, **kwargs)
329
+ @options[:type] = kwargs.empty? ? args.first : kwargs
330
+ end
331
+ end
332
+ end
333
+ end
@@ -15,7 +15,9 @@ module Xlsxrb
15
15
  # cell.to_date # Date value
16
16
  #
17
17
  # @api public
18
- Cell = Data.define(:row_index, :column_index, :value, :formula, :style_index, :unmapped_data, :errors) do
18
+ class Cell
19
+ attr_reader :row_index, :column_index, :value, :formula, :style_index, :unmapped_data, :errors
20
+
19
21
  # @param row_index [Integer] 0-based row index.
20
22
  # @param column_index [Integer] 0-based column index.
21
23
  # @param value [Object, nil] The cell's value.
@@ -23,12 +25,66 @@ module Xlsxrb
23
25
  # @param style_index [Integer, String, nil] Style identifier.
24
26
  # @param unmapped_data [Hash] Additional metadata.
25
27
  # @param errors [Array<String>, nil] Validation errors.
26
- #: (row_index: Integer, column_index: Integer, ?value: untyped, ?formula: Elements::Formula?, ?style_index: Integer | String | nil, ?unmapped_data: Hash[untyped, untyped], ?errors: Array[String]?) -> void
28
+ #: (row_index: untyped, column_index: untyped, ?value: untyped, ?formula: (Elements::Formula | String)?, ?style_index: (Integer | String)?, ?unmapped_data: Hash[untyped, untyped], ?errors: Array[String]?) -> void
27
29
  def initialize(row_index:, column_index:, value: nil, formula: nil, style_index: nil, unmapped_data: EMPTY_HASH, errors: nil)
30
+ @row_index = row_index
31
+ @column_index = column_index
32
+ @value = value
33
+ @formula = formula
34
+ @style_index = style_index
35
+ @unmapped_data = unmapped_data || EMPTY_HASH
28
36
  computed_errors = errors || self.class.validate(row_index, column_index, value)
29
- computed_errors = computed_errors.freeze unless computed_errors.frozen?
30
- super(row_index: row_index, column_index: column_index, value: value, formula: formula,
31
- style_index: style_index, unmapped_data: unmapped_data, errors: computed_errors)
37
+ @errors = computed_errors.frozen? ? computed_errors : computed_errors.freeze
38
+ end
39
+
40
+ def self.fast_create(row_index, column_index, value, style_index = nil, formula = nil)
41
+ inst = allocate
42
+ inst.instance_variable_set(:@row_index, row_index)
43
+ inst.instance_variable_set(:@column_index, column_index)
44
+ inst.instance_variable_set(:@value, value)
45
+ inst.instance_variable_set(:@formula, formula)
46
+ inst.instance_variable_set(:@style_index, style_index)
47
+ inst.instance_variable_set(:@unmapped_data, EMPTY_HASH)
48
+ inst.instance_variable_set(:@errors, EMPTY_ERRORS)
49
+ inst
50
+ end
51
+
52
+ def ==(other)
53
+ other.is_a?(Cell) &&
54
+ row_index == other.row_index &&
55
+ column_index == other.column_index &&
56
+ value == other.value &&
57
+ formula == other.formula &&
58
+ style_index == other.style_index &&
59
+ unmapped_data == other.unmapped_data &&
60
+ errors == other.errors
61
+ end
62
+ alias eql? ==
63
+
64
+ def hash
65
+ [row_index, column_index, value, formula, style_index, unmapped_data, errors].hash
66
+ end
67
+
68
+ def deconstruct
69
+ [row_index, column_index, value, formula, style_index, unmapped_data, errors]
70
+ end
71
+
72
+ def deconstruct_keys(keys)
73
+ h = { row_index: row_index, column_index: column_index, value: value, formula: formula,
74
+ style_index: style_index, unmapped_data: unmapped_data, errors: errors }
75
+ keys ? h.slice(*keys) : h
76
+ end
77
+
78
+ def with(**changes)
79
+ self.class.new(
80
+ row_index: changes.fetch(:row_index, row_index),
81
+ column_index: changes.fetch(:column_index, column_index),
82
+ value: changes.fetch(:value, value),
83
+ formula: changes.fetch(:formula, formula),
84
+ style_index: changes.fetch(:style_index, style_index),
85
+ unmapped_data: changes.fetch(:unmapped_data, unmapped_data),
86
+ errors: changes.fetch(:errors, errors)
87
+ )
32
88
  end
33
89
 
34
90
  # Returns whether the cell is valid according to OOXML specifications.
@@ -57,7 +113,8 @@ module Xlsxrb
57
113
  def [](key)
58
114
  case key
59
115
  when :value then value
60
- when :formula then formula
116
+ when :formula
117
+ formula.is_a?(Formula) ? formula.expression : formula
61
118
  when :style_index then style_index
62
119
  when :ref then ref
63
120
  when :column_index then column_index
@@ -114,9 +171,8 @@ module Xlsxrb
114
171
  def to_date
115
172
  return value if value.is_a?(Date)
116
173
 
117
- # Excel epoch is 1899-12-30
118
174
  if value.is_a?(Numeric)
119
- Date.new(1899, 12, 30) + value.to_i
175
+ Ooxml::Utils.serial_to_date(value)
120
176
  else
121
177
  begin
122
178
  Date.parse(value.to_s)
@@ -135,9 +191,7 @@ module Xlsxrb
135
191
  return value if value.is_a?(Time)
136
192
 
137
193
  if value.is_a?(Numeric)
138
- days = value.to_f
139
- base_time = Time.utc(1899, 12, 30)
140
- base_time + (days * 86_400)
194
+ Ooxml::Utils.serial_to_datetime(value)
141
195
  else
142
196
  begin
143
197
  Time.parse(value.to_s)
@@ -168,7 +222,7 @@ module Xlsxrb
168
222
  # @param index [Integer] 0-based column index.
169
223
  # @return [String] Excel column letter.
170
224
  # @api public
171
- #: (Integer index) -> String
225
+ #: (untyped index) -> String
172
226
  def self.column_letter(index)
173
227
  raise ArgumentError, "Column index must be a non-negative Integer, got #{index.inspect}" unless index.is_a?(Integer) && index >= 0
174
228
 
@@ -131,10 +131,10 @@ module Xlsxrb
131
131
  # @example
132
132
  # wb.save("output.xlsx")
133
133
  #
134
- # @param filepath [String, IO] Destination file path or IO stream.
134
+ # @param filepath [String, IO, StringIO] Destination file path or IO stream.
135
135
  # @return [void]
136
136
  # @api public
137
- #: (untyped filepath) -> void
137
+ #: (String | IO | StringIO filepath) -> void
138
138
  def save(filepath)
139
139
  Xlsxrb.write(filepath, self)
140
140
  end