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
|
@@ -24,11 +24,14 @@ module Xlsxrb
|
|
|
24
24
|
result.freeze
|
|
25
25
|
end.freeze
|
|
26
26
|
|
|
27
|
-
INTEGER_STRINGS = (0..65_535).map
|
|
27
|
+
INTEGER_STRINGS = (0..65_535).map { |n| -n.to_s }.freeze
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
attr_accessor :escape_formulas
|
|
30
|
+
|
|
31
|
+
def initialize(io, date1904: false, escape_formulas: false)
|
|
30
32
|
@io = io
|
|
31
33
|
@date1904 = date1904 ? true : false
|
|
34
|
+
@escape_formulas = escape_formulas ? true : false
|
|
32
35
|
@builder = XmlBuilder.new(@io)
|
|
33
36
|
@row_buffer = String.new(capacity: 65_536)
|
|
34
37
|
@started = false
|
|
@@ -59,12 +62,12 @@ module Xlsxrb
|
|
|
59
62
|
columns.each do |col|
|
|
60
63
|
if col.is_a?(Elements::Column)
|
|
61
64
|
s = col.style_index
|
|
62
|
-
min = col.index
|
|
63
|
-
max = col.index
|
|
65
|
+
min = col.unmapped_data[:min] ? col.unmapped_data[:min] - 1 : col.index
|
|
66
|
+
max = col.unmapped_data[:max] ? col.unmapped_data[:max] - 1 : col.index
|
|
64
67
|
else
|
|
65
68
|
s = col[:style_index] || col[:style]
|
|
66
|
-
min = col[:
|
|
67
|
-
max = col[:
|
|
69
|
+
min = col[:min] ? col[:min] - 1 : (col[:index] || 0)
|
|
70
|
+
max = col[:max] ? col[:max] - 1 : (col[:index] || min)
|
|
68
71
|
end
|
|
69
72
|
next unless s
|
|
70
73
|
|
|
@@ -103,7 +106,7 @@ module Xlsxrb
|
|
|
103
106
|
cells.each do |cell|
|
|
104
107
|
if cell.is_a?(Elements::Cell)
|
|
105
108
|
value = cell.value
|
|
106
|
-
|
|
109
|
+
cell_style_id = cell.style_index
|
|
107
110
|
col_ref = cell.ref || "#{column_letter(cell.column_index)}#{row_num_str}"
|
|
108
111
|
col_idx = cell.column_index
|
|
109
112
|
formula = cell.formula
|
|
@@ -111,7 +114,7 @@ module Xlsxrb
|
|
|
111
114
|
cell_type_val = nil
|
|
112
115
|
elsif cell.is_a?(Hash)
|
|
113
116
|
value = cell[:value]
|
|
114
|
-
|
|
117
|
+
cell_style_id = cell[:style_index]
|
|
115
118
|
col_idx = cell[:column_index]
|
|
116
119
|
col_ref = cell[:ref] || (col_idx ? "#{column_letter(col_idx)}#{row_num_str}" : nil)
|
|
117
120
|
formula = cell[:formula]
|
|
@@ -119,7 +122,7 @@ module Xlsxrb
|
|
|
119
122
|
cell_type_val = cell[:type]
|
|
120
123
|
else
|
|
121
124
|
value = cell
|
|
122
|
-
|
|
125
|
+
cell_style_id = nil
|
|
123
126
|
col_idx = nil
|
|
124
127
|
col_ref = nil
|
|
125
128
|
formula = nil
|
|
@@ -128,7 +131,7 @@ module Xlsxrb
|
|
|
128
131
|
end
|
|
129
132
|
|
|
130
133
|
col_style = @column_styles && col_idx ? @column_styles[col_idx] : nil
|
|
131
|
-
style_id
|
|
134
|
+
style_id = cell_style_id || attrs[:style_index] || col_style
|
|
132
135
|
style_id = nil unless style_id.is_a?(Integer)
|
|
133
136
|
|
|
134
137
|
# Fast path for common unstyled cells
|
|
@@ -138,7 +141,7 @@ module Xlsxrb
|
|
|
138
141
|
buf << '<c r="' << col_ref << '"><v>' << value.to_s << "</v></c>"
|
|
139
142
|
next
|
|
140
143
|
when String
|
|
141
|
-
if
|
|
144
|
+
if sst_index && (idx = sst_index[value])
|
|
142
145
|
buf << '<c r="' << col_ref << '" t="s"><v>' << idx.to_s << "</v></c>"
|
|
143
146
|
next
|
|
144
147
|
end
|
|
@@ -159,17 +162,14 @@ module Xlsxrb
|
|
|
159
162
|
end
|
|
160
163
|
|
|
161
164
|
if value.nil? && formula.nil?
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
+
if cell_style_id.is_a?(Integer)
|
|
166
|
+
buf << '<c r="' << col_ref << '" s="' << cell_style_id.to_s << '"/>'
|
|
167
|
+
else
|
|
168
|
+
buf << '<c r="' << col_ref << '"/>'
|
|
169
|
+
end
|
|
165
170
|
next
|
|
166
171
|
end
|
|
167
172
|
|
|
168
|
-
if value.is_a?(String) && value.start_with?("=") && !formula
|
|
169
|
-
formula = value
|
|
170
|
-
value = nil
|
|
171
|
-
end
|
|
172
|
-
|
|
173
173
|
xml_val = value
|
|
174
174
|
type = cell_type_val
|
|
175
175
|
formula_type = nil
|
|
@@ -183,22 +183,40 @@ module Xlsxrb
|
|
|
183
183
|
formula_type = formula.type
|
|
184
184
|
formula_ref = formula.ref
|
|
185
185
|
formula_si = formula.shared_index
|
|
186
|
-
xml_val = formula.cached_value.nil? ?
|
|
186
|
+
xml_val = formula.cached_value.nil? ? value : formula.cached_value
|
|
187
187
|
elsif formula.is_a?(Hash)
|
|
188
188
|
formula_expr = formula[:formula] || formula[:expression]
|
|
189
189
|
formula_ca = formula[:calculate_always]
|
|
190
190
|
formula_type = formula[:type]
|
|
191
191
|
formula_ref = formula[:ref]
|
|
192
192
|
formula_si = formula[:si] || formula[:shared_index]
|
|
193
|
-
xml_val =
|
|
193
|
+
xml_val = if formula.key?(:value)
|
|
194
|
+
formula[:value]
|
|
195
|
+
elsif formula.key?(:cached_value)
|
|
196
|
+
formula[:cached_value]
|
|
197
|
+
else
|
|
198
|
+
value
|
|
199
|
+
end
|
|
194
200
|
else
|
|
195
201
|
formula_expr = formula
|
|
196
|
-
xml_val = value
|
|
202
|
+
xml_val = value
|
|
197
203
|
end
|
|
198
|
-
formula_expr = formula_expr[1..] if formula_expr.start_with?("=")
|
|
204
|
+
formula_expr = formula_expr[1..] if formula_expr.is_a?(String) && formula_expr.start_with?("=")
|
|
199
205
|
end
|
|
200
206
|
|
|
201
|
-
if
|
|
207
|
+
if formula_expr
|
|
208
|
+
case xml_val
|
|
209
|
+
when true
|
|
210
|
+
type = "b"
|
|
211
|
+
xml_val = "1"
|
|
212
|
+
when false
|
|
213
|
+
type = "b"
|
|
214
|
+
xml_val = "0"
|
|
215
|
+
when Xlsxrb::Elements::CellError
|
|
216
|
+
type = "e"
|
|
217
|
+
xml_val = xml_val.code
|
|
218
|
+
end
|
|
219
|
+
elsif !type
|
|
202
220
|
case value
|
|
203
221
|
when String
|
|
204
222
|
if sst_index && (idx = sst_index[value])
|
|
@@ -290,13 +308,18 @@ module Xlsxrb
|
|
|
290
308
|
end
|
|
291
309
|
f_attrs << ' ca="1"' if formula_ca
|
|
292
310
|
buf << "><f#{f_attrs}>" << escape_xml(formula_expr) << "</f>"
|
|
293
|
-
if xml_val
|
|
294
|
-
buf << "<v>" << xml_val.to_s << "</v></c>"
|
|
295
|
-
else
|
|
311
|
+
if xml_val.nil?
|
|
296
312
|
buf << "<v>0</v></c>"
|
|
313
|
+
else
|
|
314
|
+
v_str = xml_val.to_s
|
|
315
|
+
v_str = v_str.dup.force_encoding(Encoding::UTF_8) if v_str.encoding == Encoding::BINARY
|
|
316
|
+
v_str = escape_xml(v_str) if type == "str" || xml_val.is_a?(String)
|
|
317
|
+
buf << "<v>" << v_str << "</v></c>"
|
|
297
318
|
end
|
|
298
319
|
else
|
|
299
|
-
|
|
320
|
+
v_str = xml_val.to_s
|
|
321
|
+
v_str = v_str.dup.force_encoding(Encoding::UTF_8) if v_str.encoding == Encoding::BINARY
|
|
322
|
+
buf << "><v>" << v_str << "</v></c>"
|
|
300
323
|
end
|
|
301
324
|
end
|
|
302
325
|
|
|
@@ -318,7 +341,7 @@ module Xlsxrb
|
|
|
318
341
|
buf = @row_buffer ||= String.new(capacity: 65_536)
|
|
319
342
|
|
|
320
343
|
if styles.nil? && attrs.nil? && @column_styles.nil?
|
|
321
|
-
buf <<
|
|
344
|
+
buf << '<row r="' << row_num_str << '">'
|
|
322
345
|
col_index = 0
|
|
323
346
|
max_len = values.length
|
|
324
347
|
while col_index < max_len
|
|
@@ -331,27 +354,26 @@ module Xlsxrb
|
|
|
331
354
|
case value
|
|
332
355
|
when Integer
|
|
333
356
|
val_str = value >= 0 && value < 65_536 ? INTEGER_STRINGS[value] : value.to_s
|
|
334
|
-
buf <<
|
|
357
|
+
buf << '<c r="' << col_ref << row_num_str << '"><v>' << val_str << "</v></c>"
|
|
335
358
|
when Float
|
|
336
|
-
buf <<
|
|
359
|
+
buf << '<c r="' << col_ref << row_num_str << '"><v>' << value.to_s << "</v></c>"
|
|
337
360
|
when String
|
|
338
|
-
|
|
339
|
-
raise ArgumentError, "Cell text length #{value.length} exceeds Excel limit of 32,767 characters" if @strict_excel_mode && value.length > 32_767
|
|
361
|
+
raise ArgumentError, "Cell text length #{value.length} exceeds Excel limit of 32,767 characters" if @strict_excel_mode && value.length > 32_767
|
|
340
362
|
|
|
363
|
+
if sst_index
|
|
341
364
|
idx = (sst_index[value] ||= begin
|
|
342
365
|
sst << value
|
|
343
366
|
sst.size - 1
|
|
344
367
|
end)
|
|
345
368
|
idx_str = idx < 65_536 ? INTEGER_STRINGS[idx] : idx.to_s
|
|
346
|
-
buf <<
|
|
347
|
-
|
|
369
|
+
buf << '<c r="' << col_ref << row_num_str << '" t="s"><v>' << idx_str << "</v></c>"
|
|
370
|
+
else
|
|
371
|
+
buf << '<c r="' << col_ref << row_num_str << '" t="inlineStr"><is><t>' << escape_xml(value) << "</t></is></c>"
|
|
348
372
|
end
|
|
349
|
-
formula_expr = value[1..]
|
|
350
|
-
buf << "<c r=\"#{col_ref}#{row_num_str}\"><f>#{escape_xml(formula_expr)}</f><v>0</v></c>"
|
|
351
373
|
when true
|
|
352
|
-
buf <<
|
|
374
|
+
buf << '<c r="' << col_ref << row_num_str << '" t="b"><v>1</v></c>'
|
|
353
375
|
when false
|
|
354
|
-
buf <<
|
|
376
|
+
buf << '<c r="' << col_ref << row_num_str << '" t="b"><v>0</v></c>'
|
|
355
377
|
when Date
|
|
356
378
|
date_style_id = style_map ? style_map["__xlsxrb_date"] : nil
|
|
357
379
|
buf << if date_style_id
|
|
@@ -382,8 +404,23 @@ module Xlsxrb
|
|
|
382
404
|
end
|
|
383
405
|
end
|
|
384
406
|
f_attrs << ' ca="1"' if value.calculate_always
|
|
385
|
-
|
|
386
|
-
|
|
407
|
+
c_type = ""
|
|
408
|
+
case value.cached_value
|
|
409
|
+
when true
|
|
410
|
+
c_type = ' t="b"'
|
|
411
|
+
v_val = "1"
|
|
412
|
+
when false
|
|
413
|
+
c_type = ' t="b"'
|
|
414
|
+
v_val = "0"
|
|
415
|
+
when Xlsxrb::Elements::CellError
|
|
416
|
+
c_type = ' t="e"'
|
|
417
|
+
v_val = value.cached_value.code
|
|
418
|
+
when nil
|
|
419
|
+
v_val = 0
|
|
420
|
+
else
|
|
421
|
+
v_val = value.cached_value.is_a?(String) ? escape_xml(value.cached_value) : value.cached_value
|
|
422
|
+
end
|
|
423
|
+
buf << "<c r=\"#{col_ref}#{row_num_str}\"#{c_type}><f#{f_attrs}>#{escape_xml(formula_expr)}</f><v>#{v_val}</v></c>"
|
|
387
424
|
when Hash
|
|
388
425
|
if value.key?(:formula)
|
|
389
426
|
formula_expr = value[:formula] || value[:expression]
|
|
@@ -401,8 +438,27 @@ module Xlsxrb
|
|
|
401
438
|
end
|
|
402
439
|
end
|
|
403
440
|
f_attrs << ' ca="1"' if value[:calculate_always]
|
|
404
|
-
xml_val = value
|
|
405
|
-
|
|
441
|
+
xml_val = if value.key?(:value) && !value[:value].nil?
|
|
442
|
+
value[:value]
|
|
443
|
+
elsif value.key?(:cached_value) && !value[:cached_value].nil?
|
|
444
|
+
value[:cached_value]
|
|
445
|
+
else
|
|
446
|
+
0
|
|
447
|
+
end
|
|
448
|
+
c_type = ""
|
|
449
|
+
case xml_val
|
|
450
|
+
when true
|
|
451
|
+
c_type = ' t="b"'
|
|
452
|
+
xml_val = "1"
|
|
453
|
+
when false
|
|
454
|
+
c_type = ' t="b"'
|
|
455
|
+
xml_val = "0"
|
|
456
|
+
when Xlsxrb::Elements::CellError
|
|
457
|
+
c_type = ' t="e"'
|
|
458
|
+
xml_val = xml_val.code
|
|
459
|
+
end
|
|
460
|
+
xml_val = escape_xml(xml_val) if xml_val.is_a?(String)
|
|
461
|
+
buf << "<c r=\"#{col_ref}#{row_num_str}\"#{c_type}><f#{f_attrs}>#{escape_xml(formula_expr)}</f><v>#{xml_val}</v></c>"
|
|
406
462
|
end
|
|
407
463
|
when Xlsxrb::Elements::CellError
|
|
408
464
|
buf << "<c r=\"#{col_ref}#{row_num_str}\" t=\"e\"><v>#{value.code}</v></c>"
|
|
@@ -429,9 +485,17 @@ module Xlsxrb
|
|
|
429
485
|
is_styles_collection = styles && (styles.is_a?(Array) || styles.is_a?(Hash))
|
|
430
486
|
single_style_id = resolve_style_id(styles, style_map) unless is_styles_collection
|
|
431
487
|
|
|
488
|
+
row_style = attrs ? attrs[:style_index] : nil
|
|
489
|
+
row_style_id = resolve_style_id(row_style, style_map)
|
|
490
|
+
|
|
432
491
|
buf << '<row r="' << row_num_str << '"'
|
|
492
|
+
buf << ' s="' << row_style_id.to_s << '" customFormat="1"' if row_style_id.is_a?(Integer)
|
|
433
493
|
if attrs
|
|
434
|
-
|
|
494
|
+
if attrs[:height]
|
|
495
|
+
buf << ' ht="' << attrs[:height].to_s << '" customHeight="1"'
|
|
496
|
+
elsif attrs[:custom_height]
|
|
497
|
+
buf << ' customHeight="1"'
|
|
498
|
+
end
|
|
435
499
|
buf << ' hidden="1"' if attrs[:hidden]
|
|
436
500
|
buf << ' collapsed="1"' if attrs[:collapsed]
|
|
437
501
|
buf << ' outlineLevel="' << attrs[:outline_level].to_s << '"' if attrs[:outline_level]
|
|
@@ -444,32 +508,31 @@ module Xlsxrb
|
|
|
444
508
|
max_len = [max_len, styles_len].max
|
|
445
509
|
end
|
|
446
510
|
|
|
447
|
-
row_style = attrs ? attrs[:style_index] : nil
|
|
448
|
-
row_style_id = resolve_style_id(row_style, style_map)
|
|
449
|
-
|
|
450
511
|
col_index = 0
|
|
451
512
|
while col_index < max_len
|
|
452
513
|
value = col_index < values.length ? values[col_index] : nil
|
|
453
|
-
|
|
514
|
+
cell_style_id = single_style_id
|
|
454
515
|
if is_styles_collection
|
|
455
516
|
style_name = if styles.is_a?(Array)
|
|
456
517
|
col_index < styles.length ? styles[col_index] : nil
|
|
457
518
|
else
|
|
458
519
|
styles[col_index]
|
|
459
520
|
end
|
|
460
|
-
|
|
521
|
+
cell_style_id = resolve_style_id(style_name, style_map)
|
|
461
522
|
end
|
|
462
523
|
|
|
463
|
-
if
|
|
524
|
+
if cell_style_id.nil?
|
|
464
525
|
col_style = @column_styles ? @column_styles[col_index] : nil
|
|
465
526
|
col_style_id = resolve_style_id(col_style, style_map)
|
|
466
527
|
style_id = row_style_id || col_style_id
|
|
528
|
+
else
|
|
529
|
+
style_id = cell_style_id
|
|
467
530
|
end
|
|
468
531
|
|
|
469
532
|
col_ref = COLUMN_LETTERS[col_index] || column_letter(col_index)
|
|
470
533
|
|
|
471
534
|
if value.nil?
|
|
472
|
-
buf << '<c r="' << col_ref << row_num_str << '" s="' <<
|
|
535
|
+
buf << '<c r="' << col_ref << row_num_str << '" s="' << cell_style_id.to_s << '"/>' if cell_style_id
|
|
473
536
|
col_index += 1
|
|
474
537
|
next
|
|
475
538
|
end
|
|
@@ -487,7 +550,7 @@ module Xlsxrb
|
|
|
487
550
|
col_index += 1
|
|
488
551
|
next
|
|
489
552
|
when String
|
|
490
|
-
|
|
553
|
+
if sst_index
|
|
491
554
|
idx = sst_index[value]
|
|
492
555
|
unless idx
|
|
493
556
|
sst << value
|
|
@@ -496,9 +559,11 @@ module Xlsxrb
|
|
|
496
559
|
end
|
|
497
560
|
idx_str = INTEGER_STRINGS[idx] || idx.to_s
|
|
498
561
|
buf << '<c r="' << col_ref << row_num_str << '" t="s"><v>' << idx_str << "</v></c>"
|
|
499
|
-
|
|
500
|
-
|
|
562
|
+
else
|
|
563
|
+
buf << '<c r="' << col_ref << row_num_str << '" t="inlineStr"><is><t>' << escape_xml(value) << "</t></is></c>"
|
|
501
564
|
end
|
|
565
|
+
col_index += 1
|
|
566
|
+
next
|
|
502
567
|
when true
|
|
503
568
|
buf << '<c r="' << col_ref << row_num_str << '" t="b"><v>1</v></c>'
|
|
504
569
|
col_index += 1
|
|
@@ -544,6 +609,9 @@ module Xlsxrb
|
|
|
544
609
|
when false
|
|
545
610
|
type = "b"
|
|
546
611
|
xml_val = "0"
|
|
612
|
+
when Xlsxrb::Elements::CellError
|
|
613
|
+
type = "e"
|
|
614
|
+
xml_val = value.cached_value.code
|
|
547
615
|
end
|
|
548
616
|
when Hash
|
|
549
617
|
if value.key?(:formula)
|
|
@@ -552,7 +620,11 @@ module Xlsxrb
|
|
|
552
620
|
formula_type = value[:type]
|
|
553
621
|
formula_ref = value[:ref]
|
|
554
622
|
formula_si = value[:si] || value[:shared_index]
|
|
555
|
-
xml_val = value
|
|
623
|
+
xml_val = if value.key?(:value)
|
|
624
|
+
value[:value]
|
|
625
|
+
elsif value.key?(:cached_value)
|
|
626
|
+
value[:cached_value]
|
|
627
|
+
end
|
|
556
628
|
case xml_val
|
|
557
629
|
when String
|
|
558
630
|
type = "str"
|
|
@@ -562,26 +634,12 @@ module Xlsxrb
|
|
|
562
634
|
when false
|
|
563
635
|
type = "b"
|
|
564
636
|
xml_val = "0"
|
|
637
|
+
when Xlsxrb::Elements::CellError
|
|
638
|
+
type = "e"
|
|
639
|
+
xml_val = xml_val.code
|
|
565
640
|
end
|
|
566
641
|
end
|
|
567
|
-
when String
|
|
568
|
-
if value.start_with?("=") && value.length > 1
|
|
569
|
-
formula_expr = value
|
|
570
|
-
xml_val = nil
|
|
571
|
-
elsif sst_index
|
|
572
|
-
idx = sst_index[value]
|
|
573
|
-
unless idx
|
|
574
|
-
sst << value
|
|
575
|
-
idx = sst.size - 1
|
|
576
|
-
sst_index[value] = idx
|
|
577
|
-
end
|
|
578
|
-
xml_val = idx
|
|
579
|
-
type = "s"
|
|
580
|
-
else
|
|
581
|
-
xml_val = value
|
|
582
|
-
type = "inlineStr"
|
|
583
|
-
end
|
|
584
|
-
when Xlsxrb::Elements::RichText
|
|
642
|
+
when String, Xlsxrb::Elements::RichText
|
|
585
643
|
if sst_index
|
|
586
644
|
idx = sst_index[value]
|
|
587
645
|
unless idx
|
|
@@ -632,10 +690,12 @@ module Xlsxrb
|
|
|
632
690
|
end
|
|
633
691
|
f_attrs << ' ca="1"' if formula_ca
|
|
634
692
|
buf << "><f#{f_attrs}>" << escape_xml(formula_expr) << "</f>"
|
|
635
|
-
if xml_val
|
|
636
|
-
buf << "<v>" << xml_val.to_s << "</v></c>"
|
|
637
|
-
else
|
|
693
|
+
if xml_val.nil?
|
|
638
694
|
buf << "<v>0</v></c>"
|
|
695
|
+
else
|
|
696
|
+
v_str = xml_val.to_s
|
|
697
|
+
v_str = escape_xml(v_str) if type == "str" || xml_val.is_a?(String)
|
|
698
|
+
buf << "<v>" << v_str << "</v></c>"
|
|
639
699
|
end
|
|
640
700
|
elsif type == "inlineStr"
|
|
641
701
|
buf << "><is><t>" << escape_xml(xml_val.to_s) << "</t></is></c>"
|
|
@@ -733,12 +793,26 @@ module Xlsxrb
|
|
|
733
793
|
|
|
734
794
|
def write_sheet_views(freeze_pane: nil, split_pane: nil, selection: nil, sheet_view: nil)
|
|
735
795
|
@builder.open_tag("sheetViews")
|
|
736
|
-
sv_attrs = {
|
|
796
|
+
sv_attrs = { workbookViewId: "0" }
|
|
737
797
|
if sheet_view
|
|
738
|
-
sv_attrs[:
|
|
739
|
-
sv_attrs[:
|
|
740
|
-
sv_attrs[:
|
|
798
|
+
sv_attrs[:windowProtection] = sheet_view[:window_protection] ? "1" : "0" unless sheet_view[:window_protection].nil?
|
|
799
|
+
sv_attrs[:showFormulas] = sheet_view[:show_formulas] ? "1" : "0" unless sheet_view[:show_formulas].nil?
|
|
800
|
+
sv_attrs[:showGridLines] = sheet_view[:show_grid_lines] ? "1" : "0" unless sheet_view[:show_grid_lines].nil?
|
|
801
|
+
sv_attrs[:showRowColHeaders] = sheet_view[:show_row_col_headers] ? "1" : "0" unless sheet_view[:show_row_col_headers].nil?
|
|
802
|
+
sv_attrs[:showZeros] = sheet_view[:show_zeros] ? "1" : "0" unless sheet_view[:show_zeros].nil?
|
|
803
|
+
sv_attrs[:rightToLeft] = sheet_view[:right_to_left] ? "1" : "0" unless sheet_view[:right_to_left].nil?
|
|
804
|
+
sv_attrs[:tabSelected] = "1" if sheet_view[:tab_selected]
|
|
805
|
+
sv_attrs[:showRuler] = sheet_view[:show_ruler] ? "1" : "0" unless sheet_view[:show_ruler].nil?
|
|
806
|
+
sv_attrs[:showOutlineSymbols] = sheet_view[:show_outline_symbols] ? "1" : "0" unless sheet_view[:show_outline_symbols].nil?
|
|
807
|
+
sv_attrs[:defaultGridColor] = sheet_view[:default_grid_color] ? "1" : "0" unless sheet_view[:default_grid_color].nil?
|
|
808
|
+
sv_attrs[:showWhiteSpace] = sheet_view[:show_white_space] ? "1" : "0" unless sheet_view[:show_white_space].nil?
|
|
809
|
+
sv_attrs[:view] = sheet_view[:view].to_s if sheet_view[:view]
|
|
810
|
+
sv_attrs[:topLeftCell] = sheet_view[:top_left_cell].to_s if sheet_view[:top_left_cell]
|
|
811
|
+
sv_attrs[:colorId] = sheet_view[:color_id].to_s if sheet_view[:color_id]
|
|
741
812
|
sv_attrs[:zoomScale] = sheet_view[:zoom_scale].to_s if sheet_view[:zoom_scale]
|
|
813
|
+
sv_attrs[:zoomScaleNormal] = sheet_view[:zoom_scale_normal].to_s if sheet_view[:zoom_scale_normal]
|
|
814
|
+
sv_attrs[:zoomScaleSheetLayoutView] = sheet_view[:zoom_scale_sheet_layout_view].to_s if sheet_view[:zoom_scale_sheet_layout_view]
|
|
815
|
+
sv_attrs[:zoomScalePageLayoutView] = sheet_view[:zoom_scale_page_layout_view].to_s if sheet_view[:zoom_scale_page_layout_view]
|
|
742
816
|
end
|
|
743
817
|
@builder.open_tag("sheetView", sv_attrs)
|
|
744
818
|
if freeze_pane
|
|
@@ -747,30 +821,38 @@ module Xlsxrb
|
|
|
747
821
|
pane_attrs[:ySplit] = freeze_pane[:row].to_s if freeze_pane[:row]&.positive?
|
|
748
822
|
top_left_col = column_letter(freeze_pane[:col] || 0)
|
|
749
823
|
top_left_row = (freeze_pane[:row] || 0) + 1
|
|
750
|
-
pane_attrs[:topLeftCell] = "#{top_left_col}#{top_left_row}"
|
|
824
|
+
pane_attrs[:topLeftCell] = freeze_pane[:top_left_cell] || "#{top_left_col}#{top_left_row}"
|
|
751
825
|
pane_attrs[:state] = "frozen"
|
|
752
826
|
# Determine active pane
|
|
753
|
-
pane_attrs[:activePane] = if (freeze_pane[:col] || 0).positive? && (freeze_pane[:row] || 0).positive?
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
827
|
+
pane_attrs[:activePane] = freeze_pane[:active_pane] || if (freeze_pane[:col] || 0).positive? && (freeze_pane[:row] || 0).positive?
|
|
828
|
+
"bottomRight"
|
|
829
|
+
elsif (freeze_pane[:col] || 0).positive?
|
|
830
|
+
"topRight"
|
|
831
|
+
else
|
|
832
|
+
"bottomLeft"
|
|
833
|
+
end
|
|
760
834
|
@builder.empty_tag("pane", pane_attrs)
|
|
761
835
|
elsif split_pane
|
|
762
836
|
pane_attrs = {}
|
|
763
837
|
pane_attrs[:xSplit] = split_pane[:x_split].to_s if split_pane[:x_split]&.positive?
|
|
764
838
|
pane_attrs[:ySplit] = split_pane[:y_split].to_s if split_pane[:y_split]&.positive?
|
|
765
839
|
pane_attrs[:topLeftCell] = split_pane[:top_left_cell] if split_pane[:top_left_cell]
|
|
840
|
+
pane_attrs[:activePane] = split_pane[:active_pane] if split_pane[:active_pane]
|
|
841
|
+
pane_attrs[:state] = split_pane[:state].to_s if split_pane[:state]
|
|
766
842
|
@builder.empty_tag("pane", pane_attrs)
|
|
767
843
|
end
|
|
768
|
-
if
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
844
|
+
active_selection = selection || (sheet_view[:selection] if sheet_view)
|
|
845
|
+
if active_selection
|
|
846
|
+
selections = active_selection.is_a?(Array) ? active_selection : [active_selection]
|
|
847
|
+
selections.each do |sel|
|
|
848
|
+
sel_attrs = {}
|
|
849
|
+
sel_attrs[:pane] = sel[:pane].to_s if sel[:pane]
|
|
850
|
+
sel_attrs[:activeCell] = sel[:active_cell].to_s if sel[:active_cell]
|
|
851
|
+
sel_attrs[:activeCellId] = sel[:active_cell_id].to_s if sel[:active_cell_id]
|
|
852
|
+
sqref = sel[:sqref] || sel[:active_cell]
|
|
853
|
+
sel_attrs[:sqref] = sqref.to_s if sqref
|
|
854
|
+
@builder.empty_tag("selection", sel_attrs) unless sel_attrs.empty?
|
|
855
|
+
end
|
|
774
856
|
end
|
|
775
857
|
@builder.close_tag("sheetView")
|
|
776
858
|
@builder.close_tag("sheetViews")
|
|
@@ -1070,6 +1152,9 @@ module Xlsxrb
|
|
|
1070
1152
|
end
|
|
1071
1153
|
|
|
1072
1154
|
def write_data_validations(validations)
|
|
1155
|
+
validations = validations.reject { |dv| dv[:sqref].nil? || dv[:sqref].to_s.empty? }
|
|
1156
|
+
return if validations.empty?
|
|
1157
|
+
|
|
1073
1158
|
@builder.open_tag("dataValidations", { count: validations.size.to_s })
|
|
1074
1159
|
validations.each do |dv|
|
|
1075
1160
|
if dv[:in].is_a?(Array)
|
|
@@ -1089,6 +1174,8 @@ module Xlsxrb
|
|
|
1089
1174
|
dv_attrs[:error] = dv[:error] if dv[:error]
|
|
1090
1175
|
dv_attrs[:promptTitle] = dv[:prompt_title] if dv[:prompt_title]
|
|
1091
1176
|
dv_attrs[:prompt] = dv[:prompt] if dv[:prompt]
|
|
1177
|
+
dv_attrs[:showDropDown] = "1" if dv[:show_drop_down]
|
|
1178
|
+
dv_attrs[:imeMode] = dv[:ime_mode].to_s if dv[:ime_mode]
|
|
1092
1179
|
|
|
1093
1180
|
has_formulas = dv[:formula1] || dv[:formula2]
|
|
1094
1181
|
if has_formulas
|
|
@@ -1145,7 +1232,7 @@ module Xlsxrb
|
|
|
1145
1232
|
attrs[:scale] = opts[:scale].to_s if opts[:scale]
|
|
1146
1233
|
attrs[:fitToWidth] = opts[:fit_to_width].to_s if opts[:fit_to_width]
|
|
1147
1234
|
attrs[:fitToHeight] = opts[:fit_to_height].to_s if opts[:fit_to_height]
|
|
1148
|
-
attrs[:firstPageNumber] = opts[:first_page_number].to_s if opts[:first_page_number]
|
|
1235
|
+
attrs[:firstPageNumber] = opts[:first_page_number].to_s if opts[:first_page_number] && opts[:first_page_number].to_i >= 0
|
|
1149
1236
|
attrs[:pageOrder] = opts[:page_order].to_s if opts[:page_order]
|
|
1150
1237
|
attrs[:blackAndWhite] = "1" if opts[:black_and_white]
|
|
1151
1238
|
attrs[:draft] = "1" if opts[:draft]
|
|
@@ -1164,7 +1251,14 @@ module Xlsxrb
|
|
|
1164
1251
|
def write_row_breaks(breaks)
|
|
1165
1252
|
@builder.open_tag("rowBreaks", { count: breaks.size.to_s, manualBreakCount: breaks.size.to_s })
|
|
1166
1253
|
breaks.each do |brk|
|
|
1167
|
-
|
|
1254
|
+
attrs = if brk.is_a?(Hash)
|
|
1255
|
+
a = { id: (brk[:id] || brk[:row] || 0).to_s, max: (brk[:max] || 16_383).to_s, man: brk.fetch(:man, true) ? "1" : "0" }
|
|
1256
|
+
a[:min] = brk[:min].to_s if brk[:min]
|
|
1257
|
+
a
|
|
1258
|
+
else
|
|
1259
|
+
{ id: brk.to_s, max: "16383", man: "1" }
|
|
1260
|
+
end
|
|
1261
|
+
@builder.empty_tag("brk", attrs)
|
|
1168
1262
|
end
|
|
1169
1263
|
@builder.close_tag("rowBreaks")
|
|
1170
1264
|
end
|
|
@@ -1172,7 +1266,14 @@ module Xlsxrb
|
|
|
1172
1266
|
def write_col_breaks(breaks)
|
|
1173
1267
|
@builder.open_tag("colBreaks", { count: breaks.size.to_s, manualBreakCount: breaks.size.to_s })
|
|
1174
1268
|
breaks.each do |brk|
|
|
1175
|
-
|
|
1269
|
+
attrs = if brk.is_a?(Hash)
|
|
1270
|
+
a = { id: (brk[:id] || brk[:col] || 0).to_s, max: (brk[:max] || 1_048_575).to_s, man: brk.fetch(:man, true) ? "1" : "0" }
|
|
1271
|
+
a[:min] = brk[:min].to_s if brk[:min]
|
|
1272
|
+
a
|
|
1273
|
+
else
|
|
1274
|
+
{ id: brk.to_s, max: "1048575", man: "1" }
|
|
1275
|
+
end
|
|
1276
|
+
@builder.empty_tag("brk", attrs)
|
|
1176
1277
|
end
|
|
1177
1278
|
@builder.close_tag("colBreaks")
|
|
1178
1279
|
end
|
|
@@ -1268,16 +1369,32 @@ module Xlsxrb
|
|
|
1268
1369
|
def write_columns(columns)
|
|
1269
1370
|
@builder.open_tag("cols")
|
|
1270
1371
|
columns.each do |col|
|
|
1372
|
+
if col.is_a?(Elements::Column)
|
|
1373
|
+
min = col.unmapped_data[:min] || (col.index + 1)
|
|
1374
|
+
max = col.unmapped_data[:max] || (col.index + 1)
|
|
1375
|
+
width = col.width
|
|
1376
|
+
hidden = col.hidden
|
|
1377
|
+
custom_width = col.custom_width || col.width
|
|
1378
|
+
outline_level = col.outline_level
|
|
1379
|
+
style_id = col.style_index
|
|
1380
|
+
else
|
|
1381
|
+
min = col[:min] || ((col[:index] || 0) + 1)
|
|
1382
|
+
max = col[:max] || ((col[:index] || 0) + 1)
|
|
1383
|
+
width = col[:width]
|
|
1384
|
+
hidden = col[:hidden]
|
|
1385
|
+
custom_width = col[:custom_width] || col[:width]
|
|
1386
|
+
outline_level = col[:outline_level]
|
|
1387
|
+
style_id = col[:style_index] || col[:style]
|
|
1388
|
+
end
|
|
1271
1389
|
attrs = {
|
|
1272
|
-
min:
|
|
1273
|
-
max:
|
|
1390
|
+
min: min.to_s,
|
|
1391
|
+
max: max.to_s
|
|
1274
1392
|
}
|
|
1275
|
-
attrs[:width] =
|
|
1276
|
-
attrs[:hidden] = "1" if
|
|
1277
|
-
attrs[:customWidth] = "1" if
|
|
1278
|
-
attrs[:outlineLevel] =
|
|
1279
|
-
|
|
1280
|
-
attrs[:style] = style_id.to_s if style_id
|
|
1393
|
+
attrs[:width] = width.to_s if width
|
|
1394
|
+
attrs[:hidden] = "1" if hidden
|
|
1395
|
+
attrs[:customWidth] = "1" if custom_width
|
|
1396
|
+
attrs[:outlineLevel] = outline_level.to_s if outline_level
|
|
1397
|
+
attrs[:style] = style_id.to_s if style_id.is_a?(Integer)
|
|
1281
1398
|
@builder.empty_tag("col", attrs)
|
|
1282
1399
|
end
|
|
1283
1400
|
@builder.close_tag("cols")
|
|
@@ -1289,15 +1406,27 @@ module Xlsxrb
|
|
|
1289
1406
|
|
|
1290
1407
|
value = cell[:value]
|
|
1291
1408
|
formula = cell[:formula]
|
|
1292
|
-
# For formula cells with cached values,
|
|
1293
|
-
type =
|
|
1409
|
+
# For formula cells with cached values, infer type for booleans and strings
|
|
1410
|
+
type = cell[:type] || if formula
|
|
1411
|
+
case value
|
|
1412
|
+
when true, false then "b"
|
|
1413
|
+
end
|
|
1414
|
+
else
|
|
1415
|
+
cell_type(value)
|
|
1416
|
+
end
|
|
1294
1417
|
attrs[:t] = type if type
|
|
1295
1418
|
col_idx = cell[:column_index]
|
|
1296
1419
|
col_style = @column_styles && col_idx ? @column_styles[col_idx] : nil
|
|
1297
|
-
|
|
1420
|
+
cell_style_id = cell[:style_index]
|
|
1421
|
+
style_id = cell_style_id || col_style
|
|
1298
1422
|
attrs[:s] = style_id.to_s if style_id.is_a?(Integer)
|
|
1299
1423
|
|
|
1300
1424
|
if value.nil? && formula.nil?
|
|
1425
|
+
if cell_style_id.is_a?(Integer)
|
|
1426
|
+
attrs[:s] = cell_style_id.to_s
|
|
1427
|
+
else
|
|
1428
|
+
attrs.delete(:s)
|
|
1429
|
+
end
|
|
1301
1430
|
@builder.empty_tag("c", attrs)
|
|
1302
1431
|
return
|
|
1303
1432
|
end
|
|
@@ -1369,6 +1498,7 @@ module Xlsxrb
|
|
|
1369
1498
|
|
|
1370
1499
|
def escape_xml(value)
|
|
1371
1500
|
str = value.to_s
|
|
1501
|
+
str = str.dup.force_encoding(Encoding::UTF_8) if str.encoding == Encoding::BINARY
|
|
1372
1502
|
str = str.gsub(INVALID_XML_CHARS_RE, "") if str.match?(INVALID_XML_CHARS_RE)
|
|
1373
1503
|
str.match?(ESCAPE_RE) ? str.gsub(ESCAPE_RE, ESCAPE_MAP) : str
|
|
1374
1504
|
end
|