xlsxrb 0.1.15 → 0.1.16
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +61 -0
- data/README.md +22 -3
- data/benchmark/micro_ips.rb +70 -0
- data/lib/xlsxrb/elements/styles.rb +427 -15
- data/lib/xlsxrb/elements/workbook.rb +31 -4
- data/lib/xlsxrb/elements/worksheet.rb +22 -7
- data/lib/xlsxrb/ooxml/cfb.rb +10 -0
- data/lib/xlsxrb/ooxml/reader/listeners/core_listeners.rb +56 -38
- data/lib/xlsxrb/ooxml/reader/listeners/feature_listeners.rb +7 -1
- data/lib/xlsxrb/ooxml/reader/listeners/style_listeners.rb +12 -7
- data/lib/xlsxrb/ooxml/reader.rb +165 -136
- data/lib/xlsxrb/ooxml/shared_strings_parser.rb +83 -28
- data/lib/xlsxrb/ooxml/styles_parser.rb +57 -14
- data/lib/xlsxrb/ooxml/utils.rb +59 -2
- data/lib/xlsxrb/ooxml/workbook_parser.rb +42 -3
- data/lib/xlsxrb/ooxml/workbook_writer.rb +215 -50
- data/lib/xlsxrb/ooxml/worksheet_parser.rb +192 -123
- data/lib/xlsxrb/ooxml/worksheet_writer.rb +238 -108
- data/lib/xlsxrb/ooxml/writer/drawing_xml.rb +16 -12
- data/lib/xlsxrb/ooxml/writer/styles_xml.rb +64 -15
- data/lib/xlsxrb/ooxml/writer.rb +31 -28
- data/lib/xlsxrb/ooxml/xml_builder.rb +12 -4
- data/lib/xlsxrb/ooxml/xml_parser.rb +45 -0
- data/lib/xlsxrb/ooxml/zip_reader.rb +48 -29
- data/lib/xlsxrb/stream_row.rb +4 -3
- data/lib/xlsxrb/stream_sheet.rb +26 -6
- data/lib/xlsxrb/stream_writer.rb +35 -14
- data/lib/xlsxrb/utils.rb +15 -0
- data/lib/xlsxrb/version.rb +1 -1
- data/lib/xlsxrb/workbook_builder.rb +110 -10
- data/lib/xlsxrb/worksheet_builder.rb +15 -7
- data/lib/xlsxrb.rb +416 -108
- data/sig/generated/xlsxrb/elements/styles.rbs +271 -5
- data/sig/generated/xlsxrb/elements/workbook.rbs +26 -3
- data/sig/generated/xlsxrb/elements/worksheet.rbs +12 -2
- data/sig/generated/xlsxrb/ooxml/reader/listeners/style_listeners.rbs +2 -0
- data/sig/generated/xlsxrb/ooxml/reader.rbs +12 -0
- data/sig/generated/xlsxrb/ooxml/shared_strings_parser.rbs +3 -1
- data/sig/generated/xlsxrb/ooxml/styles_parser.rbs +2 -0
- data/sig/generated/xlsxrb/ooxml/utils.rbs +22 -0
- data/sig/generated/xlsxrb/ooxml/workbook_parser.rbs +7 -0
- data/sig/generated/xlsxrb/ooxml/workbook_writer.rbs +12 -0
- data/sig/generated/xlsxrb/ooxml/worksheet_parser.rbs +18 -2
- data/sig/generated/xlsxrb/ooxml/worksheet_writer.rbs +3 -1
- data/sig/generated/xlsxrb/ooxml/writer/styles_xml.rbs +5 -2
- data/sig/generated/xlsxrb/ooxml/xml_builder.rbs +4 -1
- data/sig/generated/xlsxrb/ooxml/zip_reader.rbs +6 -6
- data/sig/generated/xlsxrb/stream_row.rbs +3 -2
- data/sig/generated/xlsxrb/stream_sheet.rbs +21 -5
- data/sig/generated/xlsxrb/stream_writer.rbs +18 -11
- data/sig/generated/xlsxrb/utils.rbs +13 -0
- data/sig/generated/xlsxrb/worksheet_builder.rbs +3 -2
- data/sig/generated/xlsxrb.rbs +47 -10
- metadata +2 -1
|
@@ -7,19 +7,20 @@ require_relative "../ooxml/utils"
|
|
|
7
7
|
|
|
8
8
|
module Xlsxrb
|
|
9
9
|
module Elements
|
|
10
|
-
# Represents the workbook stylesheet definitions (fonts, fills, borders, cellXfs,
|
|
11
|
-
#
|
|
12
|
-
# while providing precomputed O(1) number format classifications.
|
|
10
|
+
# Represents the workbook stylesheet definitions (fonts, fills, borders, cellXfs, numFmts, etc.).
|
|
11
|
+
# Encapsulates stylesheet collections, item lookups, and precomputed O(1) number format classifications.
|
|
13
12
|
#
|
|
14
13
|
# @api public
|
|
15
|
-
class Styles
|
|
16
|
-
|
|
14
|
+
class Styles
|
|
15
|
+
EMPTY_ARRAY = [].freeze
|
|
16
|
+
EMPTY_HASH = {}.freeze
|
|
17
|
+
|
|
18
|
+
# Initializes the styles container, optionally wrapping an existing styles dictionary.
|
|
17
19
|
#
|
|
18
20
|
# @param initial_hash [Hash, nil] Initial styles dictionary.
|
|
19
21
|
#: (?Hash[untyped, untyped]? initial_hash) -> void
|
|
20
22
|
def initialize(initial_hash = nil)
|
|
21
|
-
|
|
22
|
-
merge!(initial_hash) if initial_hash.is_a?(Hash)
|
|
23
|
+
@data = initial_hash.is_a?(Hash) ? initial_hash.dup : {}
|
|
23
24
|
@format_codes = nil
|
|
24
25
|
@format_types = nil
|
|
25
26
|
@date_flags = nil
|
|
@@ -27,6 +28,402 @@ module Xlsxrb
|
|
|
27
28
|
@time_flags = nil
|
|
28
29
|
end
|
|
29
30
|
|
|
31
|
+
# Returns array of font entries from the styles.
|
|
32
|
+
#
|
|
33
|
+
# @return [Array<Hash>]
|
|
34
|
+
# @api public
|
|
35
|
+
#: () -> Array[Hash[Symbol, untyped]]
|
|
36
|
+
def fonts
|
|
37
|
+
@data[:fonts] || EMPTY_ARRAY
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Returns array of fill entries from the styles.
|
|
41
|
+
#
|
|
42
|
+
# @return [Array<Hash>]
|
|
43
|
+
# @api public
|
|
44
|
+
#: () -> Array[Hash[Symbol, untyped]]
|
|
45
|
+
def fills
|
|
46
|
+
@data[:fills] || EMPTY_ARRAY
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Returns array of border entries from the styles.
|
|
50
|
+
#
|
|
51
|
+
# @return [Array<Hash>]
|
|
52
|
+
# @api public
|
|
53
|
+
#: () -> Array[Hash[Symbol, untyped]]
|
|
54
|
+
def borders
|
|
55
|
+
@data[:borders] || EMPTY_ARRAY
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Returns array of cell format (cellXfs) entries from the styles.
|
|
59
|
+
#
|
|
60
|
+
# @return [Array<Hash>]
|
|
61
|
+
# @api public
|
|
62
|
+
#: () -> Array[Hash[Symbol, untyped]]
|
|
63
|
+
def cell_xfs
|
|
64
|
+
@data[:cell_xfs] || EMPTY_ARRAY
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Returns custom number formats as { numFmtId => formatCode }.
|
|
68
|
+
#
|
|
69
|
+
# @return [Hash<Integer, String>]
|
|
70
|
+
# @api public
|
|
71
|
+
#: () -> Hash[Integer, String]
|
|
72
|
+
def num_fmts
|
|
73
|
+
@data[:num_fmts] || EMPTY_HASH
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Returns array of differential format (dxf) entries from the styles.
|
|
77
|
+
#
|
|
78
|
+
# @return [Array<Hash>]
|
|
79
|
+
# @api public
|
|
80
|
+
#: () -> Array[Hash[Symbol, untyped]]
|
|
81
|
+
def dxfs
|
|
82
|
+
@data[:dxfs] || EMPTY_ARRAY
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Returns array of cell style format (cellStyleXfs) entries from the styles.
|
|
86
|
+
#
|
|
87
|
+
# @return [Array<Hash>]
|
|
88
|
+
# @api public
|
|
89
|
+
#: () -> Array[Hash[Symbol, untyped]]
|
|
90
|
+
def cell_style_xfs
|
|
91
|
+
@data[:cell_style_xfs] || EMPTY_ARRAY
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Returns array of named cell styles from the styles.
|
|
95
|
+
#
|
|
96
|
+
# @return [Array<Hash>]
|
|
97
|
+
# @api public
|
|
98
|
+
#: () -> Array[Hash[Symbol, untyped]]
|
|
99
|
+
def cell_styles
|
|
100
|
+
@data[:cell_styles] || EMPTY_ARRAY
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Returns table styles configuration dictionary from the styles.
|
|
104
|
+
#
|
|
105
|
+
# @return [Hash, Array]
|
|
106
|
+
# @api public
|
|
107
|
+
#: () -> (Hash[Symbol, untyped] | Array[Hash[Symbol, untyped]])
|
|
108
|
+
def table_styles
|
|
109
|
+
@data[:table_styles] || EMPTY_HASH
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Returns array of indexed color strings (ARGB hex).
|
|
113
|
+
#
|
|
114
|
+
# @return [Array<String>]
|
|
115
|
+
# @api public
|
|
116
|
+
#: () -> Array[String]
|
|
117
|
+
def indexed_colors
|
|
118
|
+
@data[:indexed_colors] || EMPTY_ARRAY
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Returns array of MRU (most recently used) color dictionaries.
|
|
122
|
+
#
|
|
123
|
+
# @return [Array<Hash>]
|
|
124
|
+
# @api public
|
|
125
|
+
#: () -> Array[Hash[Symbol, untyped]]
|
|
126
|
+
def mru_colors
|
|
127
|
+
@data[:mru_colors] || EMPTY_ARRAY
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Returns colors dictionary or palette.
|
|
131
|
+
#
|
|
132
|
+
# @return [Hash, Array]
|
|
133
|
+
# @api public
|
|
134
|
+
#: () -> (Hash[Symbol, untyped] | Array[Hash[Symbol, untyped]])
|
|
135
|
+
def colors
|
|
136
|
+
@data[:colors] || EMPTY_HASH
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Looks up a font entry by 0-based index.
|
|
140
|
+
#
|
|
141
|
+
# @param id [Integer, nil]
|
|
142
|
+
# @return [Hash, nil]
|
|
143
|
+
# @api public
|
|
144
|
+
#: (Integer? id) -> Hash[Symbol, untyped]?
|
|
145
|
+
def font(id)
|
|
146
|
+
return nil unless id.is_a?(Integer) && id >= 0
|
|
147
|
+
|
|
148
|
+
@data[:fonts]&.at(id)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Looks up a fill entry by 0-based index.
|
|
152
|
+
#
|
|
153
|
+
# @param id [Integer, nil]
|
|
154
|
+
# @return [Hash, nil]
|
|
155
|
+
# @api public
|
|
156
|
+
#: (Integer? id) -> Hash[Symbol, untyped]?
|
|
157
|
+
def fill(id)
|
|
158
|
+
return nil unless id.is_a?(Integer) && id >= 0
|
|
159
|
+
|
|
160
|
+
@data[:fills]&.at(id)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Looks up a border entry by 0-based index.
|
|
164
|
+
#
|
|
165
|
+
# @param id [Integer, nil]
|
|
166
|
+
# @return [Hash, nil]
|
|
167
|
+
# @api public
|
|
168
|
+
#: (Integer? id) -> Hash[Symbol, untyped]?
|
|
169
|
+
def border(id)
|
|
170
|
+
return nil unless id.is_a?(Integer) && id >= 0
|
|
171
|
+
|
|
172
|
+
@data[:borders]&.at(id)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Looks up a cell format (cellXf) entry by 0-based index.
|
|
176
|
+
#
|
|
177
|
+
# @param id [Integer, nil]
|
|
178
|
+
# @return [Hash, nil]
|
|
179
|
+
# @api public
|
|
180
|
+
#: (Integer? id) -> Hash[Symbol, untyped]?
|
|
181
|
+
def cell_xf(id)
|
|
182
|
+
return nil unless id.is_a?(Integer) && id >= 0
|
|
183
|
+
|
|
184
|
+
@data[:cell_xfs]&.at(id)
|
|
185
|
+
end
|
|
186
|
+
alias xf cell_xf
|
|
187
|
+
|
|
188
|
+
# Looks up a custom format code by numFmtId.
|
|
189
|
+
#
|
|
190
|
+
# @param id [Integer, nil]
|
|
191
|
+
# @return [String, nil]
|
|
192
|
+
# @api public
|
|
193
|
+
#: (Integer? id) -> String?
|
|
194
|
+
def num_fmt(id)
|
|
195
|
+
return nil unless id.is_a?(Integer)
|
|
196
|
+
|
|
197
|
+
@data[:num_fmts]&.[](id)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Looks up a differential format (dxf) entry by 0-based index.
|
|
201
|
+
#
|
|
202
|
+
# @param id [Integer, nil]
|
|
203
|
+
# @return [Hash, nil]
|
|
204
|
+
# @api public
|
|
205
|
+
#: (Integer? id) -> Hash[Symbol, untyped]?
|
|
206
|
+
def dxf(id)
|
|
207
|
+
return nil unless id.is_a?(Integer) && id >= 0
|
|
208
|
+
|
|
209
|
+
@data[:dxfs]&.at(id)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Looks up a cell style format (cellStyleXf) entry by 0-based index.
|
|
213
|
+
#
|
|
214
|
+
# @param id [Integer, nil]
|
|
215
|
+
# @return [Hash, nil]
|
|
216
|
+
# @api public
|
|
217
|
+
#: (Integer? id) -> Hash[Symbol, untyped]?
|
|
218
|
+
def cell_style_xf(id)
|
|
219
|
+
return nil unless id.is_a?(Integer) && id >= 0
|
|
220
|
+
|
|
221
|
+
@data[:cell_style_xfs]&.at(id)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Looks up a named cell style entry by 0-based index.
|
|
225
|
+
#
|
|
226
|
+
# @param id [Integer, nil]
|
|
227
|
+
# @return [Hash, nil]
|
|
228
|
+
# @api public
|
|
229
|
+
#: (Integer? id) -> Hash[Symbol, untyped]?
|
|
230
|
+
def cell_style(id)
|
|
231
|
+
return nil unless id.is_a?(Integer) && id >= 0
|
|
232
|
+
|
|
233
|
+
@data[:cell_styles]&.at(id)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Appends a font entry to the stylesheet and returns its 0-based index.
|
|
237
|
+
#
|
|
238
|
+
# @param spec [Hash, nil]
|
|
239
|
+
# @return [Integer]
|
|
240
|
+
# @api public
|
|
241
|
+
#: (?Hash[Symbol, untyped]? spec, **untyped kwargs) -> Integer
|
|
242
|
+
def add_font(spec = nil, **kwargs)
|
|
243
|
+
entry = (spec || kwargs).dup
|
|
244
|
+
fonts_arr = (@data[:fonts] ||= [])
|
|
245
|
+
fonts_arr << entry
|
|
246
|
+
fonts_arr.size - 1
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Appends a fill entry to the stylesheet and returns its 0-based index.
|
|
250
|
+
#
|
|
251
|
+
# @param spec [Hash, nil]
|
|
252
|
+
# @return [Integer]
|
|
253
|
+
# @api public
|
|
254
|
+
#: (?Hash[Symbol, untyped]? spec, **untyped kwargs) -> Integer
|
|
255
|
+
def add_fill(spec = nil, **kwargs)
|
|
256
|
+
entry = (spec || kwargs).dup
|
|
257
|
+
fills_arr = (@data[:fills] ||= [])
|
|
258
|
+
fills_arr << entry
|
|
259
|
+
fills_arr.size - 1
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Appends a border entry to the stylesheet and returns its 0-based index.
|
|
263
|
+
#
|
|
264
|
+
# @param spec [Hash, nil]
|
|
265
|
+
# @return [Integer]
|
|
266
|
+
# @api public
|
|
267
|
+
#: (?Hash[Symbol, untyped]? spec, **untyped kwargs) -> Integer
|
|
268
|
+
def add_border(spec = nil, **kwargs)
|
|
269
|
+
entry = (spec || kwargs).dup
|
|
270
|
+
borders_arr = (@data[:borders] ||= [])
|
|
271
|
+
borders_arr << entry
|
|
272
|
+
borders_arr.size - 1
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# Sets a custom number format code and returns the num_fmt_id.
|
|
276
|
+
#
|
|
277
|
+
# @param id [Integer] Format ID (custom formats typically start at 164).
|
|
278
|
+
# @param code [String] Format code pattern.
|
|
279
|
+
# @return [Integer]
|
|
280
|
+
# @api public
|
|
281
|
+
#: (Integer id, String code) -> Integer
|
|
282
|
+
def add_num_fmt(id, code)
|
|
283
|
+
raise ArgumentError, "num_fmt_id must be an Integer" unless id.is_a?(Integer)
|
|
284
|
+
raise ArgumentError, "format_code must be a String" unless code.is_a?(String)
|
|
285
|
+
|
|
286
|
+
(@data[:num_fmts] ||= {})[id] = code
|
|
287
|
+
invalidate_cache!
|
|
288
|
+
id
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Appends a cell format (cellXf) entry and returns its 0-based index.
|
|
292
|
+
#
|
|
293
|
+
# @param spec [Hash, nil]
|
|
294
|
+
# @return [Integer]
|
|
295
|
+
# @api public
|
|
296
|
+
#: (?Hash[Symbol, untyped]? spec, **untyped kwargs) -> Integer
|
|
297
|
+
def add_xf(spec = nil, **kwargs)
|
|
298
|
+
entry = (spec || kwargs).dup
|
|
299
|
+
xfs_arr = (@data[:cell_xfs] ||= [])
|
|
300
|
+
xfs_arr << entry
|
|
301
|
+
invalidate_cache!
|
|
302
|
+
xfs_arr.size - 1
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# Appends a differential format (dxf) entry and returns its 0-based index.
|
|
306
|
+
#
|
|
307
|
+
# @param spec [Hash, nil]
|
|
308
|
+
# @return [Integer]
|
|
309
|
+
# @api public
|
|
310
|
+
#: (?Hash[Symbol, untyped]? spec, **untyped kwargs) -> Integer
|
|
311
|
+
def add_dxf(spec = nil, **kwargs)
|
|
312
|
+
entry = (spec || kwargs).dup
|
|
313
|
+
dxfs_arr = (@data[:dxfs] ||= [])
|
|
314
|
+
dxfs_arr << entry
|
|
315
|
+
dxfs_arr.size - 1
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# Appends a cell style format (cellStyleXf) entry and returns its 0-based index.
|
|
319
|
+
#
|
|
320
|
+
# @param spec [Hash, nil]
|
|
321
|
+
# @return [Integer]
|
|
322
|
+
# @api public
|
|
323
|
+
#: (?Hash[Symbol, untyped]? spec, **untyped kwargs) -> Integer
|
|
324
|
+
def add_cell_style_xf(spec = nil, **kwargs)
|
|
325
|
+
entry = (spec || kwargs).dup
|
|
326
|
+
xfs_arr = (@data[:cell_style_xfs] ||= [])
|
|
327
|
+
xfs_arr << entry
|
|
328
|
+
invalidate_cache!
|
|
329
|
+
xfs_arr.size - 1
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# Appends a named cell style entry and returns its 0-based index.
|
|
333
|
+
#
|
|
334
|
+
# @param spec [Hash, nil]
|
|
335
|
+
# @return [Integer]
|
|
336
|
+
# @api public
|
|
337
|
+
#: (?Hash[Symbol, untyped]? spec, **untyped kwargs) -> Integer
|
|
338
|
+
def add_cell_style(spec = nil, **kwargs)
|
|
339
|
+
entry = (spec || kwargs).dup
|
|
340
|
+
cs_arr = (@data[:cell_styles] ||= [])
|
|
341
|
+
cs_arr << entry
|
|
342
|
+
cs_arr.size - 1
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# High-level helper to compose and register a cell format (xf), adding subordinate
|
|
346
|
+
# font, fill, border, and number format definitions as needed. Returns the xf_id.
|
|
347
|
+
#
|
|
348
|
+
# @param spec [Hash, nil]
|
|
349
|
+
# @return [Integer]
|
|
350
|
+
# @api public
|
|
351
|
+
#: (?Hash[Symbol, untyped]? spec, **untyped kwargs) -> Integer
|
|
352
|
+
def add_style(spec = nil, **kwargs)
|
|
353
|
+
opts = (spec || kwargs).dup
|
|
354
|
+
font_id = opts[:font_id] || (opts[:font] ? add_font(opts[:font]) : 0)
|
|
355
|
+
fill_id = opts[:fill_id] || (opts[:fill] ? add_fill(opts[:fill]) : 0)
|
|
356
|
+
border_id = opts[:border_id] || (opts[:border] ? add_border(opts[:border]) : 0)
|
|
357
|
+
num_fmt_id = opts[:num_fmt_id] || 0
|
|
358
|
+
if opts[:num_fmt] || opts[:format_code]
|
|
359
|
+
code = (opts[:num_fmt] || opts[:format_code]).to_s
|
|
360
|
+
existing_id = num_fmts.key(code) || Ooxml::Utils::BUILTIN_NUM_FMT_CODES.key(code)
|
|
361
|
+
num_fmt_id = existing_id || add_num_fmt(next_num_fmt_id, code)
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
xf_spec = {
|
|
365
|
+
font_id: font_id,
|
|
366
|
+
fill_id: fill_id,
|
|
367
|
+
border_id: border_id,
|
|
368
|
+
num_fmt_id: num_fmt_id
|
|
369
|
+
}
|
|
370
|
+
xf_spec[:alignment] = opts[:alignment] if opts[:alignment]
|
|
371
|
+
xf_spec[:apply_alignment] = true if opts[:alignment] || opts[:apply_alignment]
|
|
372
|
+
xf_spec[:apply_fill] = true if opts[:fill] || opts[:apply_fill]
|
|
373
|
+
xf_spec[:apply_font] = true if opts[:font] || opts[:apply_font]
|
|
374
|
+
xf_spec[:apply_border] = true if opts[:border] || opts[:apply_border]
|
|
375
|
+
xf_spec[:apply_number_format] = true if opts[:num_fmt] || opts[:format_code] || opts[:apply_number_format]
|
|
376
|
+
xf_spec[:protection] = opts[:protection] if opts[:protection]
|
|
377
|
+
xf_spec[:apply_protection] = true if opts[:protection] || opts[:apply_protection]
|
|
378
|
+
|
|
379
|
+
add_xf(xf_spec)
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
# Returns true if the styles container has no registered definitions.
|
|
383
|
+
#
|
|
384
|
+
# @return [Boolean]
|
|
385
|
+
# @api public
|
|
386
|
+
#: () -> bool
|
|
387
|
+
def empty?
|
|
388
|
+
@data.empty?
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
# Converts the styles container to a standard Hash.
|
|
392
|
+
#
|
|
393
|
+
# @return [Hash]
|
|
394
|
+
# @api public
|
|
395
|
+
#: () -> Hash[Symbol, untyped]
|
|
396
|
+
def to_h
|
|
397
|
+
@data.dup
|
|
398
|
+
end
|
|
399
|
+
alias to_hash to_h
|
|
400
|
+
|
|
401
|
+
# Equality check against another Styles or Hash.
|
|
402
|
+
#
|
|
403
|
+
# @param other [Object]
|
|
404
|
+
# @return [Boolean]
|
|
405
|
+
# @api public
|
|
406
|
+
#: (untyped other) -> bool
|
|
407
|
+
def ==(other)
|
|
408
|
+
if other.is_a?(Styles)
|
|
409
|
+
@data == other.to_h
|
|
410
|
+
elsif other.is_a?(Hash)
|
|
411
|
+
@data == other
|
|
412
|
+
else
|
|
413
|
+
false
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
alias eql? ==
|
|
417
|
+
|
|
418
|
+
# Computes hash code based on underlying data.
|
|
419
|
+
#
|
|
420
|
+
# @return [Integer]
|
|
421
|
+
# @api public
|
|
422
|
+
#: () -> Integer
|
|
423
|
+
def hash
|
|
424
|
+
@data.hash
|
|
425
|
+
end
|
|
426
|
+
|
|
30
427
|
# Returns the format code string for a given style index.
|
|
31
428
|
#
|
|
32
429
|
# @param style_index [Integer, nil]
|
|
@@ -111,8 +508,8 @@ module Xlsxrb
|
|
|
111
508
|
# @api public
|
|
112
509
|
#: () -> self
|
|
113
510
|
def precompute!
|
|
114
|
-
|
|
115
|
-
size =
|
|
511
|
+
xfs = cell_xfs
|
|
512
|
+
size = xfs.is_a?(Array) ? xfs.size : 0
|
|
116
513
|
|
|
117
514
|
codes = Array.new(size)
|
|
118
515
|
types = Array.new(size)
|
|
@@ -121,20 +518,20 @@ module Xlsxrb
|
|
|
121
518
|
t_flags = Array.new(size, false)
|
|
122
519
|
|
|
123
520
|
if size.positive?
|
|
124
|
-
|
|
125
|
-
|
|
521
|
+
cs_xfs = cell_style_xfs
|
|
522
|
+
n_fmts = num_fmts
|
|
126
523
|
|
|
127
524
|
size.times do |i|
|
|
128
|
-
xf =
|
|
525
|
+
xf = xfs[i]
|
|
129
526
|
next unless xf.is_a?(Hash)
|
|
130
527
|
|
|
131
528
|
num_fmt_id = xf[:num_fmt_id]
|
|
132
|
-
if (num_fmt_id.nil? || num_fmt_id.zero?) && xf[:xf_id] &&
|
|
133
|
-
parent_xf =
|
|
529
|
+
if (num_fmt_id.nil? || num_fmt_id.zero?) && xf[:xf_id] && cs_xfs.is_a?(Array)
|
|
530
|
+
parent_xf = cs_xfs[xf[:xf_id]]
|
|
134
531
|
num_fmt_id = parent_xf[:num_fmt_id] if parent_xf.is_a?(Hash)
|
|
135
532
|
end
|
|
136
533
|
|
|
137
|
-
code = num_fmt_id ? (
|
|
534
|
+
code = num_fmt_id ? (n_fmts[num_fmt_id] || Ooxml::Utils::BUILTIN_NUM_FMT_CODES[num_fmt_id]) : nil
|
|
138
535
|
|
|
139
536
|
codes[i] = code
|
|
140
537
|
type = NumberFormatter.format_type(code)
|
|
@@ -152,6 +549,21 @@ module Xlsxrb
|
|
|
152
549
|
@time_flags = t_flags
|
|
153
550
|
self
|
|
154
551
|
end
|
|
552
|
+
|
|
553
|
+
private
|
|
554
|
+
|
|
555
|
+
def next_num_fmt_id
|
|
556
|
+
max_id = [163, *num_fmts.keys].max
|
|
557
|
+
max_id + 1
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
def invalidate_cache!
|
|
561
|
+
@format_codes = nil
|
|
562
|
+
@format_types = nil
|
|
563
|
+
@date_flags = nil
|
|
564
|
+
@datetime_flags = nil
|
|
565
|
+
@time_flags = nil
|
|
566
|
+
end
|
|
155
567
|
end
|
|
156
568
|
end
|
|
157
569
|
end
|
|
@@ -19,12 +19,12 @@ module Xlsxrb
|
|
|
19
19
|
|
|
20
20
|
# @param sheets [Array<Elements::Worksheet, StreamSheet>] Worksheets in the workbook.
|
|
21
21
|
# @param shared_strings [Array<String>] Shared strings table.
|
|
22
|
-
# @param styles [Hash] Styles definition.
|
|
22
|
+
# @param styles [Elements::Styles, Hash] Styles definition.
|
|
23
23
|
# @param unmapped_data [Hash] Additional metadata for round-tripping.
|
|
24
24
|
# @param errors [Array<String>, nil] Validation errors.
|
|
25
25
|
# @param defined_names [Array<Hash>, nil] Defined names list.
|
|
26
26
|
# @param date1904 [Boolean] Whether the workbook uses the 1904 date system.
|
|
27
|
-
#: (?sheets: Array[Elements::Worksheet | StreamSheet], ?shared_strings: Array[String], ?styles: Hash[untyped, untyped], ?unmapped_data: Hash[untyped, untyped], ?errors: Array[String]?, ?defined_names: Array[Hash[Symbol, untyped]]?, ?date1904: bool) -> void
|
|
27
|
+
#: (?sheets: Array[Elements::Worksheet | StreamSheet], ?shared_strings: Array[String], ?styles: Elements::Styles | Hash[untyped, untyped], ?unmapped_data: Hash[untyped, untyped], ?errors: Array[String]?, ?defined_names: Array[Hash[Symbol, untyped]]?, ?date1904: bool) -> void
|
|
28
28
|
def initialize(sheets: [], shared_strings: [], styles: {}, unmapped_data: {}, errors: nil, defined_names: nil, date1904: false)
|
|
29
29
|
dns = defined_names || unmapped_data[:defined_names] || unmapped_data.dig(:facade, :defined_names) || []
|
|
30
30
|
computed_errors = errors || self.class.validate(sheets)
|
|
@@ -62,15 +62,42 @@ module Xlsxrb
|
|
|
62
62
|
errors.empty?
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
+
# Returns workbook-level properties (e.g. date1904, active_tab, full_calc_on_load), or an empty hash.
|
|
66
|
+
#
|
|
67
|
+
# @return [Hash<Symbol, untyped>]
|
|
68
|
+
# @api public
|
|
69
|
+
#: () -> Hash[Symbol, untyped]
|
|
70
|
+
def workbook_properties
|
|
71
|
+
unmapped_data[:workbook_properties] || unmapped_data.dig(:facade, :workbook_properties) || {}
|
|
72
|
+
end
|
|
73
|
+
|
|
65
74
|
# Returns whether the workbook uses the 1904 date system.
|
|
66
75
|
#
|
|
67
76
|
# @return [Boolean]
|
|
68
77
|
# @api public
|
|
69
78
|
#: () -> bool
|
|
70
79
|
def date1904?
|
|
71
|
-
|
|
72
|
-
|
|
80
|
+
workbook_properties[:date1904] ? true : false
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Returns the 0-based active worksheet tab index from workbookView, or nil if not set.
|
|
84
|
+
#
|
|
85
|
+
# @return [Integer, nil]
|
|
86
|
+
# @api public
|
|
87
|
+
#: () -> Integer?
|
|
88
|
+
def active_tab
|
|
89
|
+
workbook_properties[:active_tab]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Returns whether Excel is instructed to perform a full recalculation when loading the workbook.
|
|
93
|
+
#
|
|
94
|
+
# @return [Boolean]
|
|
95
|
+
# @api public
|
|
96
|
+
#: () -> bool
|
|
97
|
+
def full_calc_on_load?
|
|
98
|
+
workbook_properties[:full_calc_on_load] ? true : false
|
|
73
99
|
end
|
|
100
|
+
alias full_calc_on_load full_calc_on_load?
|
|
74
101
|
|
|
75
102
|
# Returns core document properties (e.g. title, creator, created), or an empty hash.
|
|
76
103
|
#
|
|
@@ -17,7 +17,7 @@ module Xlsxrb
|
|
|
17
17
|
class Worksheet
|
|
18
18
|
[Enumerable, CoordinateAccess].each { |m| include m }
|
|
19
19
|
|
|
20
|
-
attr_reader :name, :rows, :columns, :charts, :conditional_formatting, :data_validations, :unmapped_data, :errors, :state, :hyperlinks, :comments, :images, :styles, :date1904, :print_area, :print_titles, :sparkline_groups, :merged_cells, :auto_filter, :tables, :shapes, :freeze_pane, :page_setup, :page_margins, :header_footer, :print_options, :sheet_protection, :sheet_view, :sheet_properties, :sheet_format, :row_breaks, :col_breaks, :protected_ranges, :cell_watches
|
|
20
|
+
attr_reader :name, :rows, :columns, :charts, :conditional_formatting, :data_validations, :unmapped_data, :errors, :state, :hyperlinks, :comments, :images, :styles, :date1904, :print_area, :print_titles, :sparkline_groups, :merged_cells, :auto_filter, :tables, :shapes, :freeze_pane, :page_setup, :page_margins, :header_footer, :print_options, :sheet_protection, :sheet_view, :sheet_properties, :sheet_format, :row_breaks, :col_breaks, :protected_ranges, :cell_watches, :selection
|
|
21
21
|
attr_writer :dimension
|
|
22
22
|
alias conditional_formats conditional_formatting
|
|
23
23
|
|
|
@@ -59,8 +59,9 @@ module Xlsxrb
|
|
|
59
59
|
# @param col_breaks [Array<Integer | Hash>] Explicit page column breaks.
|
|
60
60
|
# @param protected_ranges [Array<Hash>] Protected range definitions.
|
|
61
61
|
# @param cell_watches [Array<String>] Watched cell references.
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
# @param selection [Hash, Array<Hash>, nil] Active cell and selection settings.
|
|
63
|
+
#: (name: String?, ?rows: Array[Elements::Row], ?columns: Array[Elements::Column], ?charts: Array[Hash[Symbol, untyped]], ?conditional_formatting: Array[Hash[Symbol, untyped]]?, ?data_validations: Array[Hash[Symbol, untyped]], ?unmapped_data: Hash[untyped, untyped], ?errors: Array[String]?, ?conditional_formats: Array[Hash[Symbol, untyped]]?, ?state: Symbol, ?hyperlinks: Hash[String, untyped], ?comments: Array[Hash[Symbol, untyped]], ?images: Array[Elements::Image], ?styles: (Elements::Styles | Hash[untyped, untyped])?, ?date1904: bool, ?dimension: String?, ?trim_empty_rows: bool, ?pad_empty_rows: bool, ?pad_empty_cells: bool, ?print_area: String?, ?print_titles: Hash[Symbol, untyped]?, ?sparkline_groups: Array[Hash[Symbol, untyped]], ?merged_cells: Array[String], ?auto_filter: String?, ?tables: Array[Hash[Symbol, untyped]], ?shapes: Array[Hash[Symbol, untyped]], ?freeze_pane: Hash[Symbol, untyped]?, ?page_setup: Hash[Symbol, untyped], ?page_margins: Hash[Symbol, untyped]?, ?header_footer: Hash[Symbol, untyped], ?print_options: Hash[Symbol, untyped], ?sheet_protection: Hash[Symbol, untyped]?, ?sheet_view: Hash[Symbol, untyped], ?sheet_properties: Hash[Symbol, untyped], ?sheet_format: Hash[Symbol, untyped], ?row_breaks: Array[untyped], ?col_breaks: Array[untyped], ?protected_ranges: Array[Hash[Symbol, untyped]], ?cell_watches: Array[String], ?selection: (Hash[Symbol, untyped] | Array[Hash[Symbol, untyped]])?) -> void
|
|
64
|
+
def initialize(name:, rows: [], columns: [], charts: [], conditional_formatting: nil, data_validations: [], unmapped_data: {}, errors: nil, conditional_formats: nil, state: :visible, hyperlinks: {}, comments: [], images: [], styles: nil, date1904: false, dimension: nil, trim_empty_rows: false, pad_empty_rows: false, pad_empty_cells: false, print_area: nil, print_titles: nil, sparkline_groups: [], merged_cells: [], auto_filter: nil, tables: [], shapes: [], freeze_pane: nil, page_setup: {}, page_margins: nil, header_footer: {}, print_options: {}, sheet_protection: nil, sheet_view: {}, sheet_properties: {}, sheet_format: {}, row_breaks: [], col_breaks: [], protected_ranges: [], cell_watches: [], selection: nil)
|
|
64
65
|
@name = name
|
|
65
66
|
@rows = (rows || []).freeze
|
|
66
67
|
@columns = (columns || []).freeze
|
|
@@ -101,6 +102,7 @@ module Xlsxrb
|
|
|
101
102
|
@col_breaks = (col_breaks || []).freeze
|
|
102
103
|
@protected_ranges = (protected_ranges || unmapped_data[:protected_ranges] || unmapped_data.dig(:facade, :protected_ranges) || []).freeze
|
|
103
104
|
@cell_watches = (cell_watches || unmapped_data[:cell_watches] || unmapped_data.dig(:facade, :cell_watches) || []).freeze
|
|
105
|
+
@selection = selection&.freeze
|
|
104
106
|
end
|
|
105
107
|
|
|
106
108
|
# Returns whether trailing empty rows are omitted during iteration.
|
|
@@ -362,6 +364,15 @@ module Xlsxrb
|
|
|
362
364
|
@state == :visible
|
|
363
365
|
end
|
|
364
366
|
|
|
367
|
+
# Returns whether the worksheet is very hidden (:very_hidden).
|
|
368
|
+
#
|
|
369
|
+
# @return [Boolean]
|
|
370
|
+
# @api public
|
|
371
|
+
#: () -> bool
|
|
372
|
+
def very_hidden?
|
|
373
|
+
@state == :very_hidden
|
|
374
|
+
end
|
|
375
|
+
|
|
365
376
|
# Returns hyperlink metadata for the given cell reference, or nil.
|
|
366
377
|
#
|
|
367
378
|
# @param ref_or_row [String, Symbol, Integer] Cell reference (e.g. "A1") or 0-based row index.
|
|
@@ -539,6 +550,7 @@ module Xlsxrb
|
|
|
539
550
|
new_col_breaks = changes.key?(:col_breaks) ? changes[:col_breaks] : col_breaks
|
|
540
551
|
new_protected_ranges = changes.key?(:protected_ranges) ? changes[:protected_ranges] : protected_ranges
|
|
541
552
|
new_cell_watches = changes.key?(:cell_watches) ? changes[:cell_watches] : cell_watches
|
|
553
|
+
new_selection = changes.key?(:selection) ? changes[:selection] : selection
|
|
542
554
|
|
|
543
555
|
self.class.new(
|
|
544
556
|
name: new_name,
|
|
@@ -575,7 +587,8 @@ module Xlsxrb
|
|
|
575
587
|
row_breaks: new_row_breaks,
|
|
576
588
|
col_breaks: new_col_breaks,
|
|
577
589
|
protected_ranges: new_protected_ranges,
|
|
578
|
-
cell_watches: new_cell_watches
|
|
590
|
+
cell_watches: new_cell_watches,
|
|
591
|
+
selection: new_selection
|
|
579
592
|
)
|
|
580
593
|
end
|
|
581
594
|
|
|
@@ -616,7 +629,8 @@ module Xlsxrb
|
|
|
616
629
|
sheet_properties: sheet_properties,
|
|
617
630
|
sheet_format: sheet_format,
|
|
618
631
|
row_breaks: row_breaks,
|
|
619
|
-
col_breaks: col_breaks
|
|
632
|
+
col_breaks: col_breaks,
|
|
633
|
+
selection: selection
|
|
620
634
|
}
|
|
621
635
|
end
|
|
622
636
|
|
|
@@ -634,7 +648,8 @@ module Xlsxrb
|
|
|
634
648
|
header_footer == other.header_footer && print_options == other.print_options &&
|
|
635
649
|
sheet_protection == other.sheet_protection && sheet_view == other.sheet_view &&
|
|
636
650
|
sheet_properties == other.sheet_properties && sheet_format == other.sheet_format &&
|
|
637
|
-
row_breaks == other.row_breaks && col_breaks == other.col_breaks
|
|
651
|
+
row_breaks == other.row_breaks && col_breaks == other.col_breaks &&
|
|
652
|
+
selection == other.selection
|
|
638
653
|
end
|
|
639
654
|
alias eql? ==
|
|
640
655
|
|
|
@@ -644,7 +659,7 @@ module Xlsxrb
|
|
|
644
659
|
self.class, name, rows, columns, charts, conditional_formatting, data_validations,
|
|
645
660
|
state, hyperlinks, comments, merged_cells, auto_filter, tables, shapes, freeze_pane,
|
|
646
661
|
page_setup, page_margins, header_footer, print_options, sheet_protection, sheet_view,
|
|
647
|
-
sheet_properties, sheet_format, row_breaks, col_breaks
|
|
662
|
+
sheet_properties, sheet_format, row_breaks, col_breaks, selection
|
|
648
663
|
].hash
|
|
649
664
|
end
|
|
650
665
|
|
data/lib/xlsxrb/ooxml/cfb.rb
CHANGED
|
@@ -100,9 +100,16 @@ module Xlsxrb
|
|
|
100
100
|
raise Xlsxrb::Error, "CFB file is too small" if @data.bytesize < 512
|
|
101
101
|
|
|
102
102
|
_, major_ver = @data[0x18, 4].unpack("v2")
|
|
103
|
+
raise Xlsxrb::Error, "Unsupported CFB major version: #{major_ver}" unless [3, 4].include?(major_ver)
|
|
104
|
+
|
|
103
105
|
@major_version = major_ver
|
|
104
106
|
@sector_shift = @data[0x1E, 2].unpack1("v")
|
|
105
107
|
@mini_sector_shift = @data[0x20, 2].unpack1("v")
|
|
108
|
+
|
|
109
|
+
expected_shift = @major_version == 4 ? 12 : 9
|
|
110
|
+
raise Xlsxrb::Error, "Invalid CFB sector shift in header: #{@sector_shift}" unless @sector_shift == expected_shift
|
|
111
|
+
raise Xlsxrb::Error, "Invalid CFB mini sector shift in header: #{@mini_sector_shift}" unless @mini_sector_shift == 6
|
|
112
|
+
|
|
106
113
|
@sector_size = 1 << @sector_shift
|
|
107
114
|
@mini_sector_size = 1 << @mini_sector_shift
|
|
108
115
|
|
|
@@ -123,6 +130,9 @@ module Xlsxrb
|
|
|
123
130
|
end
|
|
124
131
|
|
|
125
132
|
def read_sector(sector_id)
|
|
133
|
+
max_sectors = (@data.bytesize - 512) / @sector_size
|
|
134
|
+
return "".b if sector_id.negative? || sector_id >= max_sectors
|
|
135
|
+
|
|
126
136
|
offset = sector_offset(sector_id)
|
|
127
137
|
@data[offset, @sector_size] || "".b
|
|
128
138
|
end
|