xlsxrb 0.1.13 → 0.1.15
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 +104 -0
- data/README.md +17 -14
- data/lib/xlsxrb/colors.rb +118 -0
- data/lib/xlsxrb/elements/cell.rb +205 -16
- data/lib/xlsxrb/elements/column.rb +6 -4
- data/lib/xlsxrb/elements/coordinate_access.rb +32 -0
- data/lib/xlsxrb/elements/image.rb +128 -0
- data/lib/xlsxrb/elements/row.rb +206 -9
- data/lib/xlsxrb/elements/styles.rb +157 -0
- data/lib/xlsxrb/elements/types.rb +95 -1
- data/lib/xlsxrb/elements/workbook.rb +121 -4
- data/lib/xlsxrb/elements/worksheet.rb +519 -29
- data/lib/xlsxrb/elements.rb +2 -0
- data/lib/xlsxrb/number_formatter.rb +429 -0
- data/lib/xlsxrb/ooxml/cfb.rb +1 -1
- data/lib/xlsxrb/ooxml/reader/listeners/core_listeners.rb +7 -5
- data/lib/xlsxrb/ooxml/reader/listeners/drawing_listeners.rb +11 -4
- data/lib/xlsxrb/ooxml/reader/listeners/feature_listeners.rb +7 -1
- data/lib/xlsxrb/ooxml/reader.rb +14 -17
- data/lib/xlsxrb/ooxml/shared_strings_parser.rb +7 -1
- data/lib/xlsxrb/ooxml/styles_parser.rb +39 -9
- data/lib/xlsxrb/ooxml/utils.rb +34 -18
- data/lib/xlsxrb/ooxml/workbook_parser.rb +74 -16
- data/lib/xlsxrb/ooxml/workbook_writer.rb +82 -23
- data/lib/xlsxrb/ooxml/worksheet_parser.rb +319 -79
- data/lib/xlsxrb/ooxml/worksheet_writer.rb +263 -66
- data/lib/xlsxrb/ooxml/writer/features_xml.rb +5 -3
- data/lib/xlsxrb/ooxml/writer.rb +14 -0
- data/lib/xlsxrb/ooxml/xml_builder.rb +14 -2
- data/lib/xlsxrb/ooxml/zip_reader.rb +144 -28
- data/lib/xlsxrb/stream_row.rb +243 -16
- data/lib/xlsxrb/stream_sheet.rb +647 -16
- data/lib/xlsxrb/stream_writer.rb +375 -34
- data/lib/xlsxrb/style_builder.rb +308 -22
- data/lib/xlsxrb/utils.rb +211 -0
- data/lib/xlsxrb/version.rb +1 -1
- data/lib/xlsxrb/workbook_builder.rb +72 -11
- data/lib/xlsxrb/worksheet_builder.rb +738 -24
- data/lib/xlsxrb.rb +1073 -83
- data/sig/generated/xlsxrb/colors.rbs +40 -0
- data/sig/generated/xlsxrb/elements/cell.rbs +113 -8
- data/sig/generated/xlsxrb/elements/column.rbs +3 -2
- data/sig/generated/xlsxrb/elements/coordinate_access.rbs +18 -0
- data/sig/generated/xlsxrb/elements/image.rbs +82 -0
- data/sig/generated/xlsxrb/elements/row.rbs +83 -4
- data/sig/generated/xlsxrb/elements/styles.rbs +79 -0
- data/sig/generated/xlsxrb/elements/types.rbs +43 -0
- data/sig/generated/xlsxrb/elements/workbook.rbs +79 -2
- data/sig/generated/xlsxrb/elements/worksheet.rbs +282 -18
- data/sig/generated/xlsxrb/number_formatter.rbs +132 -0
- data/sig/generated/xlsxrb/ooxml/reader.rbs +6 -0
- data/sig/generated/xlsxrb/ooxml/shared_strings_parser.rbs +2 -0
- data/sig/generated/xlsxrb/ooxml/styles_parser.rbs +5 -2
- data/sig/generated/xlsxrb/ooxml/utils.rbs +17 -8
- data/sig/generated/xlsxrb/ooxml/workbook_parser.rbs +9 -3
- data/sig/generated/xlsxrb/ooxml/workbook_writer.rbs +1 -1
- data/sig/generated/xlsxrb/ooxml/worksheet_parser.rbs +28 -6
- data/sig/generated/xlsxrb/ooxml/worksheet_writer.rbs +13 -3
- data/sig/generated/xlsxrb/ooxml/writer.rbs +2 -0
- data/sig/generated/xlsxrb/ooxml/xml_builder.rbs +3 -1
- data/sig/generated/xlsxrb/ooxml/zip_reader.rbs +13 -0
- data/sig/generated/xlsxrb/stream_row.rbs +100 -6
- data/sig/generated/xlsxrb/stream_sheet.rbs +358 -14
- data/sig/generated/xlsxrb/stream_writer.rbs +220 -30
- data/sig/generated/xlsxrb/style_builder.rbs +55 -18
- data/sig/generated/xlsxrb/utils.rbs +151 -0
- data/sig/generated/xlsxrb/workbook_builder.rbs +26 -0
- data/sig/generated/xlsxrb/worksheet_builder.rbs +356 -18
- data/sig/generated/xlsxrb.rbs +223 -27
- metadata +11 -1
|
@@ -26,14 +26,20 @@ module Xlsxrb
|
|
|
26
26
|
|
|
27
27
|
INTEGER_STRINGS = (0..65_535).map(&:to_s).freeze
|
|
28
28
|
|
|
29
|
-
def initialize(io)
|
|
29
|
+
def initialize(io, date1904: false)
|
|
30
30
|
@io = io
|
|
31
|
+
@date1904 = date1904 ? true : false
|
|
31
32
|
@builder = XmlBuilder.new(@io)
|
|
32
33
|
@row_buffer = String.new(capacity: 65_536)
|
|
33
34
|
@started = false
|
|
34
35
|
@finished = false
|
|
35
36
|
end
|
|
36
37
|
|
|
38
|
+
def set_column_style(col_index, style_id)
|
|
39
|
+
@column_styles ||= {}
|
|
40
|
+
@column_styles[col_index] = style_id
|
|
41
|
+
end
|
|
42
|
+
|
|
37
43
|
# Write the worksheet header. Call once before writing rows.
|
|
38
44
|
# Options for pre-sheetData elements:
|
|
39
45
|
# sheet_properties: Hash of sheet-level properties (:tab_color, etc.)
|
|
@@ -41,15 +47,35 @@ module Xlsxrb
|
|
|
41
47
|
# split_pane: { x_split:, y_split:, top_left_cell: }
|
|
42
48
|
# selection: { active_cell:, sqref:, pane: }
|
|
43
49
|
# sheet_view: Hash of sheet view properties
|
|
44
|
-
def start(columns: [], sheet_properties: nil, freeze_pane: nil, split_pane: nil, selection: nil, sheet_view: nil)
|
|
50
|
+
def start(columns: [], sheet_properties: nil, freeze_pane: nil, split_pane: nil, selection: nil, sheet_view: nil, sheet_format: nil)
|
|
45
51
|
return if @started
|
|
46
52
|
|
|
47
53
|
@started = true
|
|
48
54
|
@builder.declaration
|
|
49
55
|
@builder.open_tag("worksheet", { xmlns: SSML_NS, "xmlns:r": DOC_REL_NS })
|
|
50
56
|
|
|
57
|
+
@column_styles ||= {}
|
|
58
|
+
if columns && !columns.empty?
|
|
59
|
+
columns.each do |col|
|
|
60
|
+
if col.is_a?(Elements::Column)
|
|
61
|
+
s = col.style_index
|
|
62
|
+
min = col.index
|
|
63
|
+
max = col.index
|
|
64
|
+
else
|
|
65
|
+
s = col[:style_index] || col[:style]
|
|
66
|
+
min = col[:index] || col[:min] || 0
|
|
67
|
+
max = col[:index] || col[:max] || col[:min] || 0
|
|
68
|
+
end
|
|
69
|
+
next unless s
|
|
70
|
+
|
|
71
|
+
(min..max).each { |i| @column_styles[i] = s }
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
@column_styles = nil if @column_styles.empty?
|
|
75
|
+
|
|
51
76
|
write_sheet_properties(sheet_properties) if sheet_properties && !sheet_properties.empty?
|
|
52
77
|
write_sheet_views(freeze_pane: freeze_pane, split_pane: split_pane, selection: selection, sheet_view: sheet_view) if freeze_pane || split_pane || selection || (sheet_view && !sheet_view.empty?)
|
|
78
|
+
write_sheet_format_pr(sheet_format) if sheet_format && !sheet_format.empty?
|
|
53
79
|
write_columns(columns) unless columns.empty?
|
|
54
80
|
|
|
55
81
|
@builder.open_tag("sheetData")
|
|
@@ -63,12 +89,14 @@ module Xlsxrb
|
|
|
63
89
|
row_num_str = row_num.to_s
|
|
64
90
|
buf = @row_buffer ||= String.new(capacity: 65_536)
|
|
65
91
|
buf << '<row r="' << row_num_str << '"'
|
|
92
|
+
buf << ' s="' << attrs[:style_index].to_s << '" customFormat="1"' if attrs[:style_index].is_a?(Integer)
|
|
66
93
|
if attrs[:height]
|
|
67
94
|
buf << ' ht="' << attrs[:height].to_s << '" customHeight="1"'
|
|
68
95
|
elsif attrs[:custom_height]
|
|
69
96
|
buf << ' customHeight="1"'
|
|
70
97
|
end
|
|
71
98
|
buf << ' hidden="1"' if attrs[:hidden]
|
|
99
|
+
buf << ' collapsed="1"' if attrs[:collapsed]
|
|
72
100
|
buf << ' outlineLevel="' << attrs[:outline_level].to_s << '"' if attrs[:outline_level]
|
|
73
101
|
buf << ">"
|
|
74
102
|
|
|
@@ -77,25 +105,32 @@ module Xlsxrb
|
|
|
77
105
|
value = cell.value
|
|
78
106
|
style_id = cell.style_index
|
|
79
107
|
col_ref = cell.ref || "#{column_letter(cell.column_index)}#{row_num_str}"
|
|
108
|
+
col_idx = cell.column_index
|
|
80
109
|
formula = cell.formula
|
|
81
110
|
formula_ca = false
|
|
82
111
|
cell_type_val = nil
|
|
83
112
|
elsif cell.is_a?(Hash)
|
|
84
113
|
value = cell[:value]
|
|
85
114
|
style_id = cell[:style_index]
|
|
86
|
-
|
|
115
|
+
col_idx = cell[:column_index]
|
|
116
|
+
col_ref = cell[:ref] || (col_idx ? "#{column_letter(col_idx)}#{row_num_str}" : nil)
|
|
87
117
|
formula = cell[:formula]
|
|
88
118
|
formula_ca = cell[:formula_ca]
|
|
89
119
|
cell_type_val = cell[:type]
|
|
90
120
|
else
|
|
91
121
|
value = cell
|
|
92
122
|
style_id = nil
|
|
123
|
+
col_idx = nil
|
|
93
124
|
col_ref = nil
|
|
94
125
|
formula = nil
|
|
95
126
|
formula_ca = false
|
|
96
127
|
cell_type_val = nil
|
|
97
128
|
end
|
|
98
129
|
|
|
130
|
+
col_style = @column_styles && col_idx ? @column_styles[col_idx] : nil
|
|
131
|
+
style_id ||= attrs[:style_index] || col_style
|
|
132
|
+
style_id = nil unless style_id.is_a?(Integer)
|
|
133
|
+
|
|
99
134
|
# Fast path for common unstyled cells
|
|
100
135
|
if !formula && !style_id && !cell_type_val
|
|
101
136
|
case value
|
|
@@ -117,7 +152,7 @@ module Xlsxrb
|
|
|
117
152
|
buf << '<c r="' << col_ref << '"/>'
|
|
118
153
|
next
|
|
119
154
|
when Date
|
|
120
|
-
serial = Xlsxrb::Ooxml::Utils.date_to_serial(value)
|
|
155
|
+
serial = Xlsxrb::Ooxml::Utils.date_to_serial(value, date1904: @date1904)
|
|
121
156
|
buf << '<c r="' << col_ref << '"><v>' << serial.to_s << "</v></c>"
|
|
122
157
|
next
|
|
123
158
|
end
|
|
@@ -137,13 +172,25 @@ module Xlsxrb
|
|
|
137
172
|
|
|
138
173
|
xml_val = value
|
|
139
174
|
type = cell_type_val
|
|
140
|
-
|
|
175
|
+
formula_type = nil
|
|
176
|
+
formula_ref = nil
|
|
177
|
+
formula_si = nil
|
|
141
178
|
|
|
142
179
|
if formula
|
|
143
180
|
if formula.is_a?(Xlsxrb::Elements::Formula)
|
|
144
181
|
formula_expr = formula.expression
|
|
145
182
|
formula_ca = formula.calculate_always
|
|
146
|
-
|
|
183
|
+
formula_type = formula.type
|
|
184
|
+
formula_ref = formula.ref
|
|
185
|
+
formula_si = formula.shared_index
|
|
186
|
+
xml_val = formula.cached_value.nil? ? (value || nil) : formula.cached_value
|
|
187
|
+
elsif formula.is_a?(Hash)
|
|
188
|
+
formula_expr = formula[:formula] || formula[:expression]
|
|
189
|
+
formula_ca = formula[:calculate_always]
|
|
190
|
+
formula_type = formula[:type]
|
|
191
|
+
formula_ref = formula[:ref]
|
|
192
|
+
formula_si = formula[:si] || formula[:shared_index]
|
|
193
|
+
xml_val = formula[:value] || formula[:cached_value] || value || nil
|
|
147
194
|
else
|
|
148
195
|
formula_expr = formula
|
|
149
196
|
xml_val = value || nil
|
|
@@ -175,9 +222,9 @@ module Xlsxrb
|
|
|
175
222
|
xml_val = "0"
|
|
176
223
|
type = "b"
|
|
177
224
|
when Date
|
|
178
|
-
xml_val = Xlsxrb::Ooxml::Utils.date_to_serial(value)
|
|
225
|
+
xml_val = Xlsxrb::Ooxml::Utils.date_to_serial(value, date1904: @date1904)
|
|
179
226
|
when Time
|
|
180
|
-
xml_val = Xlsxrb::Ooxml::Utils.datetime_to_serial(value)
|
|
227
|
+
xml_val = Xlsxrb::Ooxml::Utils.datetime_to_serial(value, date1904: @date1904)
|
|
181
228
|
when BigDecimal
|
|
182
229
|
xml_val = value.to_s("F")
|
|
183
230
|
when Xlsxrb::Elements::CellError
|
|
@@ -229,16 +276,24 @@ module Xlsxrb
|
|
|
229
276
|
buf << "><is><t>" << escape_xml(xml_val.to_s) << "</t></is></c>"
|
|
230
277
|
end
|
|
231
278
|
elsif formula_expr
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
279
|
+
f_attrs = +""
|
|
280
|
+
if formula_type
|
|
281
|
+
case formula_type.to_sym
|
|
282
|
+
when :array
|
|
283
|
+
f_attrs << ' t="array"'
|
|
284
|
+
f_attrs << %( ref="#{formula_ref}") if formula_ref
|
|
285
|
+
when :shared
|
|
286
|
+
f_attrs << ' t="shared"'
|
|
287
|
+
f_attrs << %( si="#{formula_si}") if formula_si
|
|
288
|
+
f_attrs << %( ref="#{formula_ref}") if formula_ref
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
f_attrs << ' ca="1"' if formula_ca
|
|
292
|
+
buf << "><f#{f_attrs}>" << escape_xml(formula_expr) << "</f>"
|
|
238
293
|
if xml_val
|
|
239
294
|
buf << "<v>" << xml_val.to_s << "</v></c>"
|
|
240
295
|
else
|
|
241
|
-
buf << "</c>"
|
|
296
|
+
buf << "<v>0</v></c>"
|
|
242
297
|
end
|
|
243
298
|
else
|
|
244
299
|
buf << "><v>" << xml_val.to_s << "</v></c>"
|
|
@@ -262,7 +317,7 @@ module Xlsxrb
|
|
|
262
317
|
row_num_str = row_num < 65_536 ? INTEGER_STRINGS[row_num] : row_num.to_s
|
|
263
318
|
buf = @row_buffer ||= String.new(capacity: 65_536)
|
|
264
319
|
|
|
265
|
-
if styles.nil? && attrs.nil?
|
|
320
|
+
if styles.nil? && attrs.nil? && @column_styles.nil?
|
|
266
321
|
buf << "<row r=\"#{row_num_str}\">"
|
|
267
322
|
col_index = 0
|
|
268
323
|
max_len = values.length
|
|
@@ -292,7 +347,7 @@ module Xlsxrb
|
|
|
292
347
|
next
|
|
293
348
|
end
|
|
294
349
|
formula_expr = value[1..]
|
|
295
|
-
buf << "<c r=\"#{col_ref}#{row_num_str}\"><f>#{escape_xml(formula_expr)}</f></c>"
|
|
350
|
+
buf << "<c r=\"#{col_ref}#{row_num_str}\"><f>#{escape_xml(formula_expr)}</f><v>0</v></c>"
|
|
296
351
|
when true
|
|
297
352
|
buf << "<c r=\"#{col_ref}#{row_num_str}\" t=\"b\"><v>1</v></c>"
|
|
298
353
|
when false
|
|
@@ -300,35 +355,54 @@ module Xlsxrb
|
|
|
300
355
|
when Date
|
|
301
356
|
date_style_id = style_map ? style_map["__xlsxrb_date"] : nil
|
|
302
357
|
buf << if date_style_id
|
|
303
|
-
"<c r=\"#{col_ref}#{row_num_str}\" s=\"#{date_style_id}\"><v>#{Xlsxrb::Ooxml::Utils.date_to_serial(value)}</v></c>"
|
|
358
|
+
"<c r=\"#{col_ref}#{row_num_str}\" s=\"#{date_style_id}\"><v>#{Xlsxrb::Ooxml::Utils.date_to_serial(value, date1904: @date1904)}</v></c>"
|
|
304
359
|
else
|
|
305
|
-
"<c r=\"#{col_ref}#{row_num_str}\"><v>#{Xlsxrb::Ooxml::Utils.date_to_serial(value)}</v></c>"
|
|
360
|
+
"<c r=\"#{col_ref}#{row_num_str}\"><v>#{Xlsxrb::Ooxml::Utils.date_to_serial(value, date1904: @date1904)}</v></c>"
|
|
306
361
|
end
|
|
307
362
|
when Time
|
|
308
363
|
time_style_id = style_map ? style_map["__xlsxrb_time"] : nil
|
|
309
364
|
buf << if time_style_id
|
|
310
|
-
"<c r=\"#{col_ref}#{row_num_str}\" s=\"#{time_style_id}\"><v>#{Xlsxrb::Ooxml::Utils.datetime_to_serial(value)}</v></c>"
|
|
365
|
+
"<c r=\"#{col_ref}#{row_num_str}\" s=\"#{time_style_id}\"><v>#{Xlsxrb::Ooxml::Utils.datetime_to_serial(value, date1904: @date1904)}</v></c>"
|
|
311
366
|
else
|
|
312
|
-
"<c r=\"#{col_ref}#{row_num_str}\"><v>#{Xlsxrb::Ooxml::Utils.datetime_to_serial(value)}</v></c>"
|
|
367
|
+
"<c r=\"#{col_ref}#{row_num_str}\"><v>#{Xlsxrb::Ooxml::Utils.datetime_to_serial(value, date1904: @date1904)}</v></c>"
|
|
313
368
|
end
|
|
314
369
|
when Xlsxrb::Elements::Formula
|
|
315
370
|
formula_expr = value.expression
|
|
316
371
|
formula_expr = formula_expr[1..] if formula_expr.start_with?("=")
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
372
|
+
f_attrs = +""
|
|
373
|
+
if value.type
|
|
374
|
+
case value.type.to_sym
|
|
375
|
+
when :array
|
|
376
|
+
f_attrs << ' t="array"'
|
|
377
|
+
f_attrs << %( ref="#{value.ref}") if value.ref
|
|
378
|
+
when :shared
|
|
379
|
+
f_attrs << ' t="shared"'
|
|
380
|
+
f_attrs << %( si="#{value.shared_index}") if value.shared_index
|
|
381
|
+
f_attrs << %( ref="#{value.ref}") if value.ref
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
f_attrs << ' ca="1"' if value.calculate_always
|
|
385
|
+
v_val = value.cached_value.nil? ? 0 : value.cached_value
|
|
386
|
+
buf << "<c r=\"#{col_ref}#{row_num_str}\"><f#{f_attrs}>#{escape_xml(formula_expr)}</f><v>#{v_val}</v></c>"
|
|
322
387
|
when Hash
|
|
323
388
|
if value.key?(:formula)
|
|
324
|
-
formula_expr = value[:formula]
|
|
389
|
+
formula_expr = value[:formula] || value[:expression]
|
|
325
390
|
formula_expr = formula_expr[1..] if formula_expr.start_with?("=")
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
391
|
+
f_attrs = +""
|
|
392
|
+
if value[:type]
|
|
393
|
+
case value[:type].to_sym
|
|
394
|
+
when :array
|
|
395
|
+
f_attrs << ' t="array"'
|
|
396
|
+
f_attrs << %( ref="#{value[:ref]}") if value[:ref]
|
|
397
|
+
when :shared
|
|
398
|
+
f_attrs << ' t="shared"'
|
|
399
|
+
f_attrs << %( si="#{value[:si] || value[:shared_index]}") if value[:si] || value[:shared_index]
|
|
400
|
+
f_attrs << %( ref="#{value[:ref]}") if value[:ref]
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
f_attrs << ' ca="1"' if value[:calculate_always]
|
|
404
|
+
xml_val = value[:value] || value[:cached_value] || 0
|
|
405
|
+
buf << "<c r=\"#{col_ref}#{row_num_str}\"><f#{f_attrs}>#{escape_xml(formula_expr)}</f><v>#{xml_val}</v></c>"
|
|
332
406
|
end
|
|
333
407
|
when Xlsxrb::Elements::CellError
|
|
334
408
|
buf << "<c r=\"#{col_ref}#{row_num_str}\" t=\"e\"><v>#{value.code}</v></c>"
|
|
@@ -353,13 +427,13 @@ module Xlsxrb
|
|
|
353
427
|
end
|
|
354
428
|
|
|
355
429
|
is_styles_collection = styles && (styles.is_a?(Array) || styles.is_a?(Hash))
|
|
356
|
-
single_style_id =
|
|
357
|
-
single_style_id = style_map[styles] if styles && style_map && !is_styles_collection
|
|
430
|
+
single_style_id = resolve_style_id(styles, style_map) unless is_styles_collection
|
|
358
431
|
|
|
359
432
|
buf << '<row r="' << row_num_str << '"'
|
|
360
433
|
if attrs
|
|
361
434
|
buf << ' ht="' << attrs[:height].to_s << '" customHeight="1"' if attrs[:height]
|
|
362
435
|
buf << ' hidden="1"' if attrs[:hidden]
|
|
436
|
+
buf << ' collapsed="1"' if attrs[:collapsed]
|
|
363
437
|
buf << ' outlineLevel="' << attrs[:outline_level].to_s << '"' if attrs[:outline_level]
|
|
364
438
|
end
|
|
365
439
|
buf << ">"
|
|
@@ -370,17 +444,26 @@ module Xlsxrb
|
|
|
370
444
|
max_len = [max_len, styles_len].max
|
|
371
445
|
end
|
|
372
446
|
|
|
447
|
+
row_style = attrs ? attrs[:style_index] : nil
|
|
448
|
+
row_style_id = resolve_style_id(row_style, style_map)
|
|
449
|
+
|
|
373
450
|
col_index = 0
|
|
374
451
|
while col_index < max_len
|
|
375
452
|
value = col_index < values.length ? values[col_index] : nil
|
|
376
453
|
style_id = single_style_id
|
|
377
|
-
if is_styles_collection
|
|
454
|
+
if is_styles_collection
|
|
378
455
|
style_name = if styles.is_a?(Array)
|
|
379
456
|
col_index < styles.length ? styles[col_index] : nil
|
|
380
457
|
else
|
|
381
458
|
styles[col_index]
|
|
382
459
|
end
|
|
383
|
-
style_id =
|
|
460
|
+
style_id = resolve_style_id(style_name, style_map)
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
if style_id.nil?
|
|
464
|
+
col_style = @column_styles ? @column_styles[col_index] : nil
|
|
465
|
+
col_style_id = resolve_style_id(col_style, style_map)
|
|
466
|
+
style_id = row_style_id || col_style_id
|
|
384
467
|
end
|
|
385
468
|
|
|
386
469
|
col_ref = COLUMN_LETTERS[col_index] || column_letter(col_index)
|
|
@@ -425,11 +508,11 @@ module Xlsxrb
|
|
|
425
508
|
col_index += 1
|
|
426
509
|
next
|
|
427
510
|
when Date
|
|
428
|
-
buf << '<c r="' << col_ref << row_num_str << '"><v>' << Xlsxrb::Ooxml::Utils.date_to_serial(value).to_s << "</v></c>"
|
|
511
|
+
buf << '<c r="' << col_ref << row_num_str << '"><v>' << Xlsxrb::Ooxml::Utils.date_to_serial(value, date1904: @date1904).to_s << "</v></c>"
|
|
429
512
|
col_index += 1
|
|
430
513
|
next
|
|
431
514
|
when Time
|
|
432
|
-
buf << '<c r="' << col_ref << row_num_str << '"><v>' << Xlsxrb::Ooxml::Utils.datetime_to_serial(value).to_s << "</v></c>"
|
|
515
|
+
buf << '<c r="' << col_ref << row_num_str << '"><v>' << Xlsxrb::Ooxml::Utils.datetime_to_serial(value, date1904: @date1904).to_s << "</v></c>"
|
|
433
516
|
col_index += 1
|
|
434
517
|
next
|
|
435
518
|
end
|
|
@@ -438,6 +521,9 @@ module Xlsxrb
|
|
|
438
521
|
# General path: styled, formula, rich text, or complex cell
|
|
439
522
|
formula_expr = nil
|
|
440
523
|
formula_ca = false
|
|
524
|
+
formula_type = nil
|
|
525
|
+
formula_ref = nil
|
|
526
|
+
formula_si = nil
|
|
441
527
|
xml_val = value
|
|
442
528
|
type = nil
|
|
443
529
|
|
|
@@ -445,6 +531,9 @@ module Xlsxrb
|
|
|
445
531
|
when Xlsxrb::Elements::Formula
|
|
446
532
|
formula_expr = value.expression
|
|
447
533
|
formula_ca = value.calculate_always
|
|
534
|
+
formula_type = value.type
|
|
535
|
+
formula_ref = value.ref
|
|
536
|
+
formula_si = value.shared_index
|
|
448
537
|
xml_val = value.cached_value
|
|
449
538
|
case value.cached_value
|
|
450
539
|
when String
|
|
@@ -458,9 +547,12 @@ module Xlsxrb
|
|
|
458
547
|
end
|
|
459
548
|
when Hash
|
|
460
549
|
if value.key?(:formula)
|
|
461
|
-
formula_expr = value[:formula]
|
|
550
|
+
formula_expr = value[:formula] || value[:expression]
|
|
462
551
|
formula_ca = value[:calculate_always] || false
|
|
463
|
-
|
|
552
|
+
formula_type = value[:type]
|
|
553
|
+
formula_ref = value[:ref]
|
|
554
|
+
formula_si = value[:si] || value[:shared_index]
|
|
555
|
+
xml_val = value[:value] || value[:cached_value]
|
|
464
556
|
case xml_val
|
|
465
557
|
when String
|
|
466
558
|
type = "str"
|
|
@@ -476,7 +568,7 @@ module Xlsxrb
|
|
|
476
568
|
if value.start_with?("=") && value.length > 1
|
|
477
569
|
formula_expr = value
|
|
478
570
|
xml_val = nil
|
|
479
|
-
|
|
571
|
+
elsif sst_index
|
|
480
572
|
idx = sst_index[value]
|
|
481
573
|
unless idx
|
|
482
574
|
sst << value
|
|
@@ -485,16 +577,24 @@ module Xlsxrb
|
|
|
485
577
|
end
|
|
486
578
|
xml_val = idx
|
|
487
579
|
type = "s"
|
|
580
|
+
else
|
|
581
|
+
xml_val = value
|
|
582
|
+
type = "inlineStr"
|
|
488
583
|
end
|
|
489
584
|
when Xlsxrb::Elements::RichText
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
585
|
+
if sst_index
|
|
586
|
+
idx = sst_index[value]
|
|
587
|
+
unless idx
|
|
588
|
+
sst << value
|
|
589
|
+
idx = sst.size - 1
|
|
590
|
+
sst_index[value] = idx
|
|
591
|
+
end
|
|
592
|
+
xml_val = idx
|
|
593
|
+
type = "s"
|
|
594
|
+
else
|
|
595
|
+
xml_val = value
|
|
596
|
+
type = "inlineStr"
|
|
495
597
|
end
|
|
496
|
-
xml_val = idx
|
|
497
|
-
type = "s"
|
|
498
598
|
when true
|
|
499
599
|
xml_val = "1"
|
|
500
600
|
type = "b"
|
|
@@ -502,9 +602,9 @@ module Xlsxrb
|
|
|
502
602
|
xml_val = "0"
|
|
503
603
|
type = "b"
|
|
504
604
|
when Date
|
|
505
|
-
xml_val = Xlsxrb::Ooxml::Utils.date_to_serial(value)
|
|
605
|
+
xml_val = Xlsxrb::Ooxml::Utils.date_to_serial(value, date1904: @date1904)
|
|
506
606
|
when Time
|
|
507
|
-
xml_val = Xlsxrb::Ooxml::Utils.datetime_to_serial(value)
|
|
607
|
+
xml_val = Xlsxrb::Ooxml::Utils.datetime_to_serial(value, date1904: @date1904)
|
|
508
608
|
when BigDecimal
|
|
509
609
|
xml_val = value.to_s("F")
|
|
510
610
|
when Xlsxrb::Elements::CellError
|
|
@@ -518,17 +618,27 @@ module Xlsxrb
|
|
|
518
618
|
buf << ' s="' << style_id.to_s << '"' if style_id
|
|
519
619
|
buf << ' t="' << type << '"' if type
|
|
520
620
|
if formula_expr
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
621
|
+
f_attrs = +""
|
|
622
|
+
if formula_type
|
|
623
|
+
case formula_type.to_sym
|
|
624
|
+
when :array
|
|
625
|
+
f_attrs << ' t="array"'
|
|
626
|
+
f_attrs << %( ref="#{formula_ref}") if formula_ref
|
|
627
|
+
when :shared
|
|
628
|
+
f_attrs << ' t="shared"'
|
|
629
|
+
f_attrs << %( si="#{formula_si}") if formula_si
|
|
630
|
+
f_attrs << %( ref="#{formula_ref}") if formula_ref
|
|
631
|
+
end
|
|
632
|
+
end
|
|
633
|
+
f_attrs << ' ca="1"' if formula_ca
|
|
634
|
+
buf << "><f#{f_attrs}>" << escape_xml(formula_expr) << "</f>"
|
|
527
635
|
if xml_val
|
|
528
636
|
buf << "<v>" << xml_val.to_s << "</v></c>"
|
|
529
637
|
else
|
|
530
|
-
buf << "</c>"
|
|
638
|
+
buf << "<v>0</v></c>"
|
|
531
639
|
end
|
|
640
|
+
elsif type == "inlineStr"
|
|
641
|
+
buf << "><is><t>" << escape_xml(xml_val.to_s) << "</t></is></c>"
|
|
532
642
|
else
|
|
533
643
|
buf << "><v>" << xml_val.to_s << "</v></c>"
|
|
534
644
|
end
|
|
@@ -545,12 +655,12 @@ module Xlsxrb
|
|
|
545
655
|
|
|
546
656
|
# Write the worksheet footer. Call once after all rows.
|
|
547
657
|
# Options for post-sheetData elements (in OOXML order):
|
|
548
|
-
def finish(drawing_rid: nil, sheet_protection: nil, auto_filter: nil,
|
|
658
|
+
def finish(drawing_rid: nil, sheet_protection: nil, protected_ranges: nil, auto_filter: nil,
|
|
549
659
|
filter_columns: nil, sort_state: nil, merge_cells: nil,
|
|
550
660
|
conditional_formats: nil, data_validations: nil,
|
|
551
661
|
hyperlinks: nil, print_options: nil, page_margins: nil,
|
|
552
662
|
page_setup: nil, header_footer: nil, row_breaks: nil,
|
|
553
|
-
col_breaks: nil, tables: nil, table_start_rid: nil,
|
|
663
|
+
col_breaks: nil, cell_watches: nil, tables: nil, table_start_rid: nil,
|
|
554
664
|
legacy_drawing_rid: nil, sparkline_groups: nil)
|
|
555
665
|
return if @finished
|
|
556
666
|
|
|
@@ -564,6 +674,7 @@ module Xlsxrb
|
|
|
564
674
|
|
|
565
675
|
# Elements must appear in OOXML specification order after sheetData
|
|
566
676
|
write_sheet_protection(sheet_protection) if sheet_protection
|
|
677
|
+
write_protected_ranges(protected_ranges) if protected_ranges && !protected_ranges.empty?
|
|
567
678
|
write_auto_filter(auto_filter, filter_columns, sort_state) if auto_filter
|
|
568
679
|
write_merge_cells(merge_cells) if merge_cells && !merge_cells.empty?
|
|
569
680
|
write_conditional_formatting(conditional_formats) if conditional_formats && !conditional_formats.empty?
|
|
@@ -575,6 +686,7 @@ module Xlsxrb
|
|
|
575
686
|
write_header_footer(header_footer) if header_footer && !header_footer.empty?
|
|
576
687
|
write_row_breaks(row_breaks) if row_breaks && !row_breaks.empty?
|
|
577
688
|
write_col_breaks(col_breaks) if col_breaks && !col_breaks.empty?
|
|
689
|
+
write_cell_watches(cell_watches) if cell_watches && !cell_watches.empty?
|
|
578
690
|
@builder.empty_tag("drawing", { "r:id": drawing_rid }) if drawing_rid
|
|
579
691
|
@builder.empty_tag("legacyDrawing", { "r:id": legacy_drawing_rid }) if legacy_drawing_rid
|
|
580
692
|
write_table_parts(tables, table_start_rid) if tables && !tables.empty?
|
|
@@ -584,6 +696,17 @@ module Xlsxrb
|
|
|
584
696
|
|
|
585
697
|
private
|
|
586
698
|
|
|
699
|
+
def resolve_style_id(style, style_map)
|
|
700
|
+
return nil if style.nil?
|
|
701
|
+
|
|
702
|
+
if style_map
|
|
703
|
+
mapped = style_map[style] || style_map[style.to_s]
|
|
704
|
+
return mapped if mapped
|
|
705
|
+
end
|
|
706
|
+
|
|
707
|
+
style.is_a?(Integer) ? style : nil
|
|
708
|
+
end
|
|
709
|
+
|
|
587
710
|
# --- Pre-sheetData elements ---
|
|
588
711
|
|
|
589
712
|
def write_sheet_properties(props)
|
|
@@ -653,6 +776,22 @@ module Xlsxrb
|
|
|
653
776
|
@builder.close_tag("sheetViews")
|
|
654
777
|
end
|
|
655
778
|
|
|
779
|
+
def write_sheet_format_pr(fmt)
|
|
780
|
+
return if fmt.nil? || fmt.empty?
|
|
781
|
+
|
|
782
|
+
attrs = {}
|
|
783
|
+
attrs[:defaultRowHeight] = fmt[:default_row_height].to_s if fmt[:default_row_height]
|
|
784
|
+
attrs[:defaultColWidth] = fmt[:default_col_width].to_s if fmt[:default_col_width]
|
|
785
|
+
attrs[:baseColWidth] = fmt[:base_col_width].to_s if fmt[:base_col_width]
|
|
786
|
+
attrs[:outlineLevelRow] = fmt[:outline_level_row].to_s if fmt[:outline_level_row]
|
|
787
|
+
attrs[:outlineLevelCol] = fmt[:outline_level_col].to_s if fmt[:outline_level_col]
|
|
788
|
+
attrs[:customHeight] = "1" if fmt[:custom_height]
|
|
789
|
+
attrs[:zeroHeight] = "1" if fmt[:zero_height]
|
|
790
|
+
attrs[:thickTop] = "1" if fmt[:thick_top]
|
|
791
|
+
attrs[:thickBottom] = "1" if fmt[:thick_bottom]
|
|
792
|
+
@builder.empty_tag("sheetFormatPr", attrs) unless attrs.empty?
|
|
793
|
+
end
|
|
794
|
+
|
|
656
795
|
# --- Post-sheetData elements (in OOXML order) ---
|
|
657
796
|
|
|
658
797
|
def write_sheet_protection(opts)
|
|
@@ -681,6 +820,28 @@ module Xlsxrb
|
|
|
681
820
|
@builder.empty_tag("sheetProtection", attrs)
|
|
682
821
|
end
|
|
683
822
|
|
|
823
|
+
def write_protected_ranges(ranges)
|
|
824
|
+
@builder.open_tag("protectedRanges")
|
|
825
|
+
ranges.each do |pr|
|
|
826
|
+
attrs = { sqref: pr[:sqref].to_s, name: pr[:name].to_s }
|
|
827
|
+
attrs[:password] = pr[:password].to_s if pr[:password]
|
|
828
|
+
attrs[:algorithmName] = pr[:algorithm_name].to_s if pr[:algorithm_name]
|
|
829
|
+
attrs[:hashValue] = pr[:hash_value].to_s if pr[:hash_value]
|
|
830
|
+
attrs[:saltValue] = pr[:salt_value].to_s if pr[:salt_value]
|
|
831
|
+
attrs[:spinCount] = pr[:spin_count].to_s if pr[:spin_count]
|
|
832
|
+
attrs[:securityDescriptor] = pr[:security_descriptor].to_s if pr[:security_descriptor]
|
|
833
|
+
sds = pr[:security_descriptors] || []
|
|
834
|
+
if sds.empty?
|
|
835
|
+
@builder.empty_tag("protectedRange", attrs)
|
|
836
|
+
else
|
|
837
|
+
@builder.open_tag("protectedRange", attrs)
|
|
838
|
+
sds.each { |sd| @builder.tag("securityDescriptor") { @builder.text(sd.to_s) } }
|
|
839
|
+
@builder.close_tag("protectedRange")
|
|
840
|
+
end
|
|
841
|
+
end
|
|
842
|
+
@builder.close_tag("protectedRanges")
|
|
843
|
+
end
|
|
844
|
+
|
|
684
845
|
def write_auto_filter(range, filter_columns, sort_state)
|
|
685
846
|
if (filter_columns && !filter_columns.empty?) || sort_state
|
|
686
847
|
@builder.open_tag("autoFilter", { ref: range })
|
|
@@ -945,7 +1106,8 @@ module Xlsxrb
|
|
|
945
1106
|
def write_hyperlinks(links)
|
|
946
1107
|
@builder.open_tag("hyperlinks")
|
|
947
1108
|
links.each_with_index do |link, idx|
|
|
948
|
-
|
|
1109
|
+
cell_ref = link[:cell] || link[:ref]
|
|
1110
|
+
h_attrs = { ref: cell_ref }
|
|
949
1111
|
h_attrs[:"r:id"] = "rId#{link[:_rid] || (idx + 1)}" if link[:url]
|
|
950
1112
|
h_attrs[:location] = link[:location] if link[:location]
|
|
951
1113
|
h_attrs[:display] = link[:display] if link[:display]
|
|
@@ -1015,6 +1177,14 @@ module Xlsxrb
|
|
|
1015
1177
|
@builder.close_tag("colBreaks")
|
|
1016
1178
|
end
|
|
1017
1179
|
|
|
1180
|
+
def write_cell_watches(watches)
|
|
1181
|
+
@builder.open_tag("cellWatches")
|
|
1182
|
+
watches.each do |w|
|
|
1183
|
+
@builder.empty_tag("cellWatch", { r: w.to_s })
|
|
1184
|
+
end
|
|
1185
|
+
@builder.close_tag("cellWatches")
|
|
1186
|
+
end
|
|
1187
|
+
|
|
1018
1188
|
def write_table_parts(tables, start_rid)
|
|
1019
1189
|
@builder.open_tag("tableParts", { count: tables.size.to_s })
|
|
1020
1190
|
tables.each_with_index do |_tbl, idx|
|
|
@@ -1106,6 +1276,8 @@ module Xlsxrb
|
|
|
1106
1276
|
attrs[:hidden] = "1" if col[:hidden]
|
|
1107
1277
|
attrs[:customWidth] = "1" if col[:custom_width] || col[:width]
|
|
1108
1278
|
attrs[:outlineLevel] = col[:outline_level].to_s if col[:outline_level]
|
|
1279
|
+
style_id = col[:style_index] || col[:style]
|
|
1280
|
+
attrs[:style] = style_id.to_s if style_id
|
|
1109
1281
|
@builder.empty_tag("col", attrs)
|
|
1110
1282
|
end
|
|
1111
1283
|
@builder.close_tag("cols")
|
|
@@ -1120,7 +1292,10 @@ module Xlsxrb
|
|
|
1120
1292
|
# For formula cells with cached values, don't infer type from value
|
|
1121
1293
|
type = formula ? cell[:type] : (cell[:type] || cell_type(value))
|
|
1122
1294
|
attrs[:t] = type if type
|
|
1123
|
-
|
|
1295
|
+
col_idx = cell[:column_index]
|
|
1296
|
+
col_style = @column_styles && col_idx ? @column_styles[col_idx] : nil
|
|
1297
|
+
style_id = cell[:style_index] || col_style
|
|
1298
|
+
attrs[:s] = style_id.to_s if style_id.is_a?(Integer)
|
|
1124
1299
|
|
|
1125
1300
|
if value.nil? && formula.nil?
|
|
1126
1301
|
@builder.empty_tag("c", attrs)
|
|
@@ -1130,14 +1305,36 @@ module Xlsxrb
|
|
|
1130
1305
|
@builder.open_tag("c", attrs)
|
|
1131
1306
|
if formula
|
|
1132
1307
|
f_attrs = {}
|
|
1133
|
-
|
|
1308
|
+
formula_text = formula
|
|
1309
|
+
if formula.is_a?(Xlsxrb::Elements::Formula)
|
|
1310
|
+
formula_text = formula.expression
|
|
1311
|
+
formula_text = formula_text[1..] if formula_text.start_with?("=")
|
|
1312
|
+
f_attrs[:t] = formula.type.to_s if formula.type
|
|
1313
|
+
f_attrs[:ref] = formula.ref if formula.ref
|
|
1314
|
+
f_attrs[:si] = formula.shared_index.to_s if formula.shared_index
|
|
1315
|
+
f_attrs[:ca] = "1" if formula.calculate_always
|
|
1316
|
+
else
|
|
1317
|
+
formula_text = formula_text[1..] if formula_text.is_a?(String) && formula_text.start_with?("=")
|
|
1318
|
+
f_type = cell[:formula_type] || cell[:type]
|
|
1319
|
+
if f_type && %i[array shared].include?(f_type.to_sym)
|
|
1320
|
+
f_attrs[:t] = f_type.to_s
|
|
1321
|
+
f_attrs[:ref] = cell[:formula_ref] || cell[:ref] if cell[:formula_ref] || (f_type.to_s == "array" && cell[:ref])
|
|
1322
|
+
f_attrs[:si] = cell[:formula_si].to_s if cell[:formula_si]
|
|
1323
|
+
end
|
|
1324
|
+
f_attrs[:ca] = "1" if cell[:formula_ca]
|
|
1325
|
+
end
|
|
1134
1326
|
if f_attrs.empty?
|
|
1135
|
-
@builder.tag("f") { |b| b.text(
|
|
1327
|
+
@builder.tag("f") { |b| b.text(formula_text) }
|
|
1136
1328
|
else
|
|
1137
|
-
@builder.tag("f", f_attrs) { |b| b.text(
|
|
1329
|
+
@builder.tag("f", f_attrs) { |b| b.text(formula_text) }
|
|
1138
1330
|
end
|
|
1139
1331
|
end
|
|
1140
|
-
|
|
1332
|
+
v_val = if value.nil?
|
|
1333
|
+
formula ? "0" : nil
|
|
1334
|
+
else
|
|
1335
|
+
xml_cell_value(value, type)
|
|
1336
|
+
end
|
|
1337
|
+
@builder.tag("v") { |b| b.text(v_val) } unless v_val.nil?
|
|
1141
1338
|
@builder.close_tag("c")
|
|
1142
1339
|
end
|
|
1143
1340
|
|
|
@@ -393,11 +393,13 @@ module Xlsxrb
|
|
|
393
393
|
when true, false
|
|
394
394
|
%(<c r="#{cell_ref}" t="b"#{s_attr}#{ph_attr}><v>#{value ? 1 : 0}</v></c>)
|
|
395
395
|
when Time
|
|
396
|
-
|
|
396
|
+
date1904 = @workbook_properties&.[](:date1904) ? true : false
|
|
397
|
+
serial = Xlsxrb::Ooxml::Utils.datetime_to_serial(value, date1904: date1904)
|
|
397
398
|
dt_attr = s_attr.empty? && (dt_style = resolve_style_index(datetime_num_fmt_id)) ? %( s="#{dt_style}") : s_attr
|
|
398
399
|
%(<c r="#{cell_ref}"#{dt_attr}#{ph_attr}><v>#{serial}</v></c>)
|
|
399
400
|
when Date
|
|
400
|
-
|
|
401
|
+
date1904 = @workbook_properties&.[](:date1904) ? true : false
|
|
402
|
+
serial = Xlsxrb::Ooxml::Utils.date_to_serial(value, date1904: date1904)
|
|
401
403
|
ds_attr = s_attr.empty? && (date_style = resolve_style_index(date_num_fmt_id)) ? %( s="#{date_style}") : s_attr
|
|
402
404
|
%(<c r="#{cell_ref}"#{ds_attr}#{ph_attr}><v>#{serial}</v></c>)
|
|
403
405
|
when Numeric
|
|
@@ -713,7 +715,7 @@ module Xlsxrb
|
|
|
713
715
|
"<vt:i4>#{prop[:value]}</vt:i4>"
|
|
714
716
|
when :float, :r8
|
|
715
717
|
"<vt:r8>#{prop[:value]}</vt:r8>"
|
|
716
|
-
when :bool
|
|
718
|
+
when :bool, :boolean
|
|
717
719
|
"<vt:bool>#{prop[:value] ? "true" : "false"}</vt:bool>"
|
|
718
720
|
when :date, :filetime
|
|
719
721
|
"<vt:filetime>#{prop[:value]}</vt:filetime>"
|