xlsxrb 0.1.13 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +57 -0
  3. data/README.md +2 -0
  4. data/lib/xlsxrb/colors.rb +118 -0
  5. data/lib/xlsxrb/elements/cell.rb +205 -16
  6. data/lib/xlsxrb/elements/column.rb +6 -4
  7. data/lib/xlsxrb/elements/coordinate_access.rb +32 -0
  8. data/lib/xlsxrb/elements/image.rb +128 -0
  9. data/lib/xlsxrb/elements/row.rb +206 -9
  10. data/lib/xlsxrb/elements/styles.rb +157 -0
  11. data/lib/xlsxrb/elements/types.rb +95 -1
  12. data/lib/xlsxrb/elements/workbook.rb +85 -4
  13. data/lib/xlsxrb/elements/worksheet.rb +424 -29
  14. data/lib/xlsxrb/elements.rb +2 -0
  15. data/lib/xlsxrb/number_formatter.rb +429 -0
  16. data/lib/xlsxrb/ooxml/cfb.rb +1 -1
  17. data/lib/xlsxrb/ooxml/reader/listeners/core_listeners.rb +7 -5
  18. data/lib/xlsxrb/ooxml/reader/listeners/drawing_listeners.rb +11 -4
  19. data/lib/xlsxrb/ooxml/reader.rb +12 -14
  20. data/lib/xlsxrb/ooxml/shared_strings_parser.rb +7 -1
  21. data/lib/xlsxrb/ooxml/styles_parser.rb +39 -9
  22. data/lib/xlsxrb/ooxml/utils.rb +34 -18
  23. data/lib/xlsxrb/ooxml/workbook_parser.rb +53 -15
  24. data/lib/xlsxrb/ooxml/workbook_writer.rb +65 -14
  25. data/lib/xlsxrb/ooxml/worksheet_parser.rb +319 -79
  26. data/lib/xlsxrb/ooxml/worksheet_writer.rb +206 -61
  27. data/lib/xlsxrb/ooxml/writer/features_xml.rb +4 -2
  28. data/lib/xlsxrb/ooxml/writer.rb +14 -0
  29. data/lib/xlsxrb/ooxml/zip_reader.rb +109 -25
  30. data/lib/xlsxrb/stream_row.rb +243 -16
  31. data/lib/xlsxrb/stream_sheet.rb +414 -15
  32. data/lib/xlsxrb/stream_writer.rb +213 -17
  33. data/lib/xlsxrb/style_builder.rb +308 -22
  34. data/lib/xlsxrb/utils.rb +158 -0
  35. data/lib/xlsxrb/version.rb +1 -1
  36. data/lib/xlsxrb/workbook_builder.rb +73 -3
  37. data/lib/xlsxrb/worksheet_builder.rb +227 -16
  38. data/lib/xlsxrb.rb +578 -79
  39. data/sig/generated/xlsxrb/colors.rbs +40 -0
  40. data/sig/generated/xlsxrb/elements/cell.rbs +113 -8
  41. data/sig/generated/xlsxrb/elements/column.rbs +3 -2
  42. data/sig/generated/xlsxrb/elements/coordinate_access.rbs +18 -0
  43. data/sig/generated/xlsxrb/elements/image.rbs +82 -0
  44. data/sig/generated/xlsxrb/elements/row.rbs +83 -4
  45. data/sig/generated/xlsxrb/elements/styles.rbs +79 -0
  46. data/sig/generated/xlsxrb/elements/types.rbs +43 -0
  47. data/sig/generated/xlsxrb/elements/workbook.rbs +51 -2
  48. data/sig/generated/xlsxrb/elements/worksheet.rbs +231 -18
  49. data/sig/generated/xlsxrb/number_formatter.rbs +132 -0
  50. data/sig/generated/xlsxrb/ooxml/reader.rbs +6 -0
  51. data/sig/generated/xlsxrb/ooxml/shared_strings_parser.rbs +2 -0
  52. data/sig/generated/xlsxrb/ooxml/styles_parser.rbs +5 -2
  53. data/sig/generated/xlsxrb/ooxml/utils.rbs +17 -8
  54. data/sig/generated/xlsxrb/ooxml/workbook_parser.rbs +9 -3
  55. data/sig/generated/xlsxrb/ooxml/worksheet_parser.rbs +28 -6
  56. data/sig/generated/xlsxrb/ooxml/worksheet_writer.rbs +3 -1
  57. data/sig/generated/xlsxrb/ooxml/writer.rbs +2 -0
  58. data/sig/generated/xlsxrb/ooxml/zip_reader.rbs +10 -0
  59. data/sig/generated/xlsxrb/stream_row.rbs +100 -6
  60. data/sig/generated/xlsxrb/stream_sheet.rbs +247 -13
  61. data/sig/generated/xlsxrb/stream_writer.rbs +120 -14
  62. data/sig/generated/xlsxrb/style_builder.rbs +55 -18
  63. data/sig/generated/xlsxrb/utils.rbs +119 -0
  64. data/sig/generated/xlsxrb/workbook_builder.rbs +26 -0
  65. data/sig/generated/xlsxrb/worksheet_builder.rbs +107 -11
  66. data/sig/generated/xlsxrb.rbs +186 -27
  67. 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.)
@@ -48,6 +54,25 @@ module Xlsxrb
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?)
53
78
  write_columns(columns) unless columns.empty?
@@ -63,12 +88,14 @@ module Xlsxrb
63
88
  row_num_str = row_num.to_s
64
89
  buf = @row_buffer ||= String.new(capacity: 65_536)
65
90
  buf << '<row r="' << row_num_str << '"'
91
+ buf << ' s="' << attrs[:style_index].to_s << '" customFormat="1"' if attrs[:style_index]
66
92
  if attrs[:height]
67
93
  buf << ' ht="' << attrs[:height].to_s << '" customHeight="1"'
68
94
  elsif attrs[:custom_height]
69
95
  buf << ' customHeight="1"'
70
96
  end
71
97
  buf << ' hidden="1"' if attrs[:hidden]
98
+ buf << ' collapsed="1"' if attrs[:collapsed]
72
99
  buf << ' outlineLevel="' << attrs[:outline_level].to_s << '"' if attrs[:outline_level]
73
100
  buf << ">"
74
101
 
@@ -77,25 +104,31 @@ module Xlsxrb
77
104
  value = cell.value
78
105
  style_id = cell.style_index
79
106
  col_ref = cell.ref || "#{column_letter(cell.column_index)}#{row_num_str}"
107
+ col_idx = cell.column_index
80
108
  formula = cell.formula
81
109
  formula_ca = false
82
110
  cell_type_val = nil
83
111
  elsif cell.is_a?(Hash)
84
112
  value = cell[:value]
85
113
  style_id = cell[:style_index]
86
- col_ref = cell[:ref] || "#{column_letter(cell[:column_index])}#{row_num_str}"
114
+ col_idx = cell[:column_index]
115
+ col_ref = cell[:ref] || (col_idx ? "#{column_letter(col_idx)}#{row_num_str}" : nil)
87
116
  formula = cell[:formula]
88
117
  formula_ca = cell[:formula_ca]
89
118
  cell_type_val = cell[:type]
90
119
  else
91
120
  value = cell
92
121
  style_id = nil
122
+ col_idx = nil
93
123
  col_ref = nil
94
124
  formula = nil
95
125
  formula_ca = false
96
126
  cell_type_val = nil
97
127
  end
98
128
 
129
+ col_style = @column_styles && col_idx ? @column_styles[col_idx] : nil
130
+ style_id ||= attrs[:style_index] || col_style
131
+
99
132
  # Fast path for common unstyled cells
100
133
  if !formula && !style_id && !cell_type_val
101
134
  case value
@@ -117,7 +150,7 @@ module Xlsxrb
117
150
  buf << '<c r="' << col_ref << '"/>'
118
151
  next
119
152
  when Date
120
- serial = Xlsxrb::Ooxml::Utils.date_to_serial(value)
153
+ serial = Xlsxrb::Ooxml::Utils.date_to_serial(value, date1904: @date1904)
121
154
  buf << '<c r="' << col_ref << '"><v>' << serial.to_s << "</v></c>"
122
155
  next
123
156
  end
@@ -137,13 +170,25 @@ module Xlsxrb
137
170
 
138
171
  xml_val = value
139
172
  type = cell_type_val
140
- formula_expr = nil
173
+ formula_type = nil
174
+ formula_ref = nil
175
+ formula_si = nil
141
176
 
142
177
  if formula
143
178
  if formula.is_a?(Xlsxrb::Elements::Formula)
144
179
  formula_expr = formula.expression
145
180
  formula_ca = formula.calculate_always
146
- xml_val = formula.cached_value || value || nil
181
+ formula_type = formula.type
182
+ formula_ref = formula.ref
183
+ formula_si = formula.shared_index
184
+ xml_val = formula.cached_value.nil? ? (value || nil) : formula.cached_value
185
+ elsif formula.is_a?(Hash)
186
+ formula_expr = formula[:formula] || formula[:expression]
187
+ formula_ca = formula[:calculate_always]
188
+ formula_type = formula[:type]
189
+ formula_ref = formula[:ref]
190
+ formula_si = formula[:si] || formula[:shared_index]
191
+ xml_val = formula[:value] || formula[:cached_value] || value || nil
147
192
  else
148
193
  formula_expr = formula
149
194
  xml_val = value || nil
@@ -175,9 +220,9 @@ module Xlsxrb
175
220
  xml_val = "0"
176
221
  type = "b"
177
222
  when Date
178
- xml_val = Xlsxrb::Ooxml::Utils.date_to_serial(value)
223
+ xml_val = Xlsxrb::Ooxml::Utils.date_to_serial(value, date1904: @date1904)
179
224
  when Time
180
- xml_val = Xlsxrb::Ooxml::Utils.datetime_to_serial(value)
225
+ xml_val = Xlsxrb::Ooxml::Utils.datetime_to_serial(value, date1904: @date1904)
181
226
  when BigDecimal
182
227
  xml_val = value.to_s("F")
183
228
  when Xlsxrb::Elements::CellError
@@ -229,16 +274,24 @@ module Xlsxrb
229
274
  buf << "><is><t>" << escape_xml(xml_val.to_s) << "</t></is></c>"
230
275
  end
231
276
  elsif formula_expr
232
- buf << if formula_ca
233
- '><f ca="1">'
234
- else
235
- "><f>"
236
- end
237
- buf << escape_xml(formula_expr) << "</f>"
277
+ f_attrs = +""
278
+ if formula_type
279
+ case formula_type.to_sym
280
+ when :array
281
+ f_attrs << ' t="array"'
282
+ f_attrs << %( ref="#{formula_ref}") if formula_ref
283
+ when :shared
284
+ f_attrs << ' t="shared"'
285
+ f_attrs << %( si="#{formula_si}") if formula_si
286
+ f_attrs << %( ref="#{formula_ref}") if formula_ref
287
+ end
288
+ end
289
+ f_attrs << ' ca="1"' if formula_ca
290
+ buf << "><f#{f_attrs}>" << escape_xml(formula_expr) << "</f>"
238
291
  if xml_val
239
292
  buf << "<v>" << xml_val.to_s << "</v></c>"
240
293
  else
241
- buf << "</c>"
294
+ buf << "<v>0</v></c>"
242
295
  end
243
296
  else
244
297
  buf << "><v>" << xml_val.to_s << "</v></c>"
@@ -262,7 +315,7 @@ module Xlsxrb
262
315
  row_num_str = row_num < 65_536 ? INTEGER_STRINGS[row_num] : row_num.to_s
263
316
  buf = @row_buffer ||= String.new(capacity: 65_536)
264
317
 
265
- if styles.nil? && attrs.nil?
318
+ if styles.nil? && attrs.nil? && @column_styles.nil?
266
319
  buf << "<row r=\"#{row_num_str}\">"
267
320
  col_index = 0
268
321
  max_len = values.length
@@ -292,7 +345,7 @@ module Xlsxrb
292
345
  next
293
346
  end
294
347
  formula_expr = value[1..]
295
- buf << "<c r=\"#{col_ref}#{row_num_str}\"><f>#{escape_xml(formula_expr)}</f></c>"
348
+ buf << "<c r=\"#{col_ref}#{row_num_str}\"><f>#{escape_xml(formula_expr)}</f><v>0</v></c>"
296
349
  when true
297
350
  buf << "<c r=\"#{col_ref}#{row_num_str}\" t=\"b\"><v>1</v></c>"
298
351
  when false
@@ -300,35 +353,54 @@ module Xlsxrb
300
353
  when Date
301
354
  date_style_id = style_map ? style_map["__xlsxrb_date"] : nil
302
355
  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>"
356
+ "<c r=\"#{col_ref}#{row_num_str}\" s=\"#{date_style_id}\"><v>#{Xlsxrb::Ooxml::Utils.date_to_serial(value, date1904: @date1904)}</v></c>"
304
357
  else
305
- "<c r=\"#{col_ref}#{row_num_str}\"><v>#{Xlsxrb::Ooxml::Utils.date_to_serial(value)}</v></c>"
358
+ "<c r=\"#{col_ref}#{row_num_str}\"><v>#{Xlsxrb::Ooxml::Utils.date_to_serial(value, date1904: @date1904)}</v></c>"
306
359
  end
307
360
  when Time
308
361
  time_style_id = style_map ? style_map["__xlsxrb_time"] : nil
309
362
  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>"
363
+ "<c r=\"#{col_ref}#{row_num_str}\" s=\"#{time_style_id}\"><v>#{Xlsxrb::Ooxml::Utils.datetime_to_serial(value, date1904: @date1904)}</v></c>"
311
364
  else
312
- "<c r=\"#{col_ref}#{row_num_str}\"><v>#{Xlsxrb::Ooxml::Utils.datetime_to_serial(value)}</v></c>"
365
+ "<c r=\"#{col_ref}#{row_num_str}\"><v>#{Xlsxrb::Ooxml::Utils.datetime_to_serial(value, date1904: @date1904)}</v></c>"
313
366
  end
314
367
  when Xlsxrb::Elements::Formula
315
368
  formula_expr = value.expression
316
369
  formula_expr = formula_expr[1..] if formula_expr.start_with?("=")
317
- buf << if value.cached_value
318
- "<c r=\"#{col_ref}#{row_num_str}\"><f>#{escape_xml(formula_expr)}</f><v>#{value.cached_value}</v></c>"
319
- else
320
- "<c r=\"#{col_ref}#{row_num_str}\"><f>#{escape_xml(formula_expr)}</f></c>"
321
- end
370
+ f_attrs = +""
371
+ if value.type
372
+ case value.type.to_sym
373
+ when :array
374
+ f_attrs << ' t="array"'
375
+ f_attrs << %( ref="#{value.ref}") if value.ref
376
+ when :shared
377
+ f_attrs << ' t="shared"'
378
+ f_attrs << %( si="#{value.shared_index}") if value.shared_index
379
+ f_attrs << %( ref="#{value.ref}") if value.ref
380
+ end
381
+ end
382
+ f_attrs << ' ca="1"' if value.calculate_always
383
+ v_val = value.cached_value.nil? ? 0 : value.cached_value
384
+ buf << "<c r=\"#{col_ref}#{row_num_str}\"><f#{f_attrs}>#{escape_xml(formula_expr)}</f><v>#{v_val}</v></c>"
322
385
  when Hash
323
386
  if value.key?(:formula)
324
- formula_expr = value[:formula]
387
+ formula_expr = value[:formula] || value[:expression]
325
388
  formula_expr = formula_expr[1..] if formula_expr.start_with?("=")
326
- xml_val = value[:value]
327
- buf << if xml_val
328
- "<c r=\"#{col_ref}#{row_num_str}\"><f>#{escape_xml(formula_expr)}</f><v>#{xml_val}</v></c>"
329
- else
330
- "<c r=\"#{col_ref}#{row_num_str}\"><f>#{escape_xml(formula_expr)}</f></c>"
331
- end
389
+ f_attrs = +""
390
+ if value[:type]
391
+ case value[:type].to_sym
392
+ when :array
393
+ f_attrs << ' t="array"'
394
+ f_attrs << %( ref="#{value[:ref]}") if value[:ref]
395
+ when :shared
396
+ f_attrs << ' t="shared"'
397
+ f_attrs << %( si="#{value[:si] || value[:shared_index]}") if value[:si] || value[:shared_index]
398
+ f_attrs << %( ref="#{value[:ref]}") if value[:ref]
399
+ end
400
+ end
401
+ f_attrs << ' ca="1"' if value[:calculate_always]
402
+ xml_val = value[:value] || value[:cached_value] || 0
403
+ buf << "<c r=\"#{col_ref}#{row_num_str}\"><f#{f_attrs}>#{escape_xml(formula_expr)}</f><v>#{xml_val}</v></c>"
332
404
  end
333
405
  when Xlsxrb::Elements::CellError
334
406
  buf << "<c r=\"#{col_ref}#{row_num_str}\" t=\"e\"><v>#{value.code}</v></c>"
@@ -360,6 +432,7 @@ module Xlsxrb
360
432
  if attrs
361
433
  buf << ' ht="' << attrs[:height].to_s << '" customHeight="1"' if attrs[:height]
362
434
  buf << ' hidden="1"' if attrs[:hidden]
435
+ buf << ' collapsed="1"' if attrs[:collapsed]
363
436
  buf << ' outlineLevel="' << attrs[:outline_level].to_s << '"' if attrs[:outline_level]
364
437
  end
365
438
  buf << ">"
@@ -370,17 +443,34 @@ module Xlsxrb
370
443
  max_len = [max_len, styles_len].max
371
444
  end
372
445
 
446
+ row_style = attrs ? attrs[:style_index] : nil
447
+ row_style_id = row_style && style_map ? (style_map[row_style] || style_map[row_style.to_s] || row_style) : row_style
448
+
373
449
  col_index = 0
374
450
  while col_index < max_len
375
451
  value = col_index < values.length ? values[col_index] : nil
376
452
  style_id = single_style_id
377
- if is_styles_collection && style_map
453
+ if is_styles_collection
378
454
  style_name = if styles.is_a?(Array)
379
455
  col_index < styles.length ? styles[col_index] : nil
380
456
  else
381
457
  styles[col_index]
382
458
  end
383
- style_id = style_map[style_name] if style_name
459
+ style_id = if style_map && style_name
460
+ style_map[style_name] || style_map[style_name.to_s] || style_name
461
+ else
462
+ style_name
463
+ end
464
+ end
465
+
466
+ if style_id.nil?
467
+ col_style = @column_styles ? @column_styles[col_index] : nil
468
+ col_style_id = if col_style && style_map
469
+ style_map[col_style] || style_map[col_style.to_s] || col_style
470
+ else
471
+ col_style
472
+ end
473
+ style_id = row_style_id || col_style_id
384
474
  end
385
475
 
386
476
  col_ref = COLUMN_LETTERS[col_index] || column_letter(col_index)
@@ -425,11 +515,11 @@ module Xlsxrb
425
515
  col_index += 1
426
516
  next
427
517
  when Date
428
- buf << '<c r="' << col_ref << row_num_str << '"><v>' << Xlsxrb::Ooxml::Utils.date_to_serial(value).to_s << "</v></c>"
518
+ buf << '<c r="' << col_ref << row_num_str << '"><v>' << Xlsxrb::Ooxml::Utils.date_to_serial(value, date1904: @date1904).to_s << "</v></c>"
429
519
  col_index += 1
430
520
  next
431
521
  when Time
432
- buf << '<c r="' << col_ref << row_num_str << '"><v>' << Xlsxrb::Ooxml::Utils.datetime_to_serial(value).to_s << "</v></c>"
522
+ buf << '<c r="' << col_ref << row_num_str << '"><v>' << Xlsxrb::Ooxml::Utils.datetime_to_serial(value, date1904: @date1904).to_s << "</v></c>"
433
523
  col_index += 1
434
524
  next
435
525
  end
@@ -438,6 +528,9 @@ module Xlsxrb
438
528
  # General path: styled, formula, rich text, or complex cell
439
529
  formula_expr = nil
440
530
  formula_ca = false
531
+ formula_type = nil
532
+ formula_ref = nil
533
+ formula_si = nil
441
534
  xml_val = value
442
535
  type = nil
443
536
 
@@ -445,6 +538,9 @@ module Xlsxrb
445
538
  when Xlsxrb::Elements::Formula
446
539
  formula_expr = value.expression
447
540
  formula_ca = value.calculate_always
541
+ formula_type = value.type
542
+ formula_ref = value.ref
543
+ formula_si = value.shared_index
448
544
  xml_val = value.cached_value
449
545
  case value.cached_value
450
546
  when String
@@ -458,9 +554,12 @@ module Xlsxrb
458
554
  end
459
555
  when Hash
460
556
  if value.key?(:formula)
461
- formula_expr = value[:formula]
557
+ formula_expr = value[:formula] || value[:expression]
462
558
  formula_ca = value[:calculate_always] || false
463
- xml_val = value[:value]
559
+ formula_type = value[:type]
560
+ formula_ref = value[:ref]
561
+ formula_si = value[:si] || value[:shared_index]
562
+ xml_val = value[:value] || value[:cached_value]
464
563
  case xml_val
465
564
  when String
466
565
  type = "str"
@@ -476,7 +575,7 @@ module Xlsxrb
476
575
  if value.start_with?("=") && value.length > 1
477
576
  formula_expr = value
478
577
  xml_val = nil
479
- else
578
+ elsif sst_index
480
579
  idx = sst_index[value]
481
580
  unless idx
482
581
  sst << value
@@ -485,16 +584,24 @@ module Xlsxrb
485
584
  end
486
585
  xml_val = idx
487
586
  type = "s"
587
+ else
588
+ xml_val = value
589
+ type = "inlineStr"
488
590
  end
489
591
  when Xlsxrb::Elements::RichText
490
- idx = sst_index[value]
491
- unless idx
492
- sst << value
493
- idx = sst.size - 1
494
- sst_index[value] = idx
592
+ if sst_index
593
+ idx = sst_index[value]
594
+ unless idx
595
+ sst << value
596
+ idx = sst.size - 1
597
+ sst_index[value] = idx
598
+ end
599
+ xml_val = idx
600
+ type = "s"
601
+ else
602
+ xml_val = value
603
+ type = "inlineStr"
495
604
  end
496
- xml_val = idx
497
- type = "s"
498
605
  when true
499
606
  xml_val = "1"
500
607
  type = "b"
@@ -502,9 +609,9 @@ module Xlsxrb
502
609
  xml_val = "0"
503
610
  type = "b"
504
611
  when Date
505
- xml_val = Xlsxrb::Ooxml::Utils.date_to_serial(value)
612
+ xml_val = Xlsxrb::Ooxml::Utils.date_to_serial(value, date1904: @date1904)
506
613
  when Time
507
- xml_val = Xlsxrb::Ooxml::Utils.datetime_to_serial(value)
614
+ xml_val = Xlsxrb::Ooxml::Utils.datetime_to_serial(value, date1904: @date1904)
508
615
  when BigDecimal
509
616
  xml_val = value.to_s("F")
510
617
  when Xlsxrb::Elements::CellError
@@ -518,17 +625,27 @@ module Xlsxrb
518
625
  buf << ' s="' << style_id.to_s << '"' if style_id
519
626
  buf << ' t="' << type << '"' if type
520
627
  if formula_expr
521
- buf << if formula_ca
522
- '><f ca="1">'
523
- else
524
- "><f>"
525
- end
526
- buf << escape_xml(formula_expr) << "</f>"
628
+ f_attrs = +""
629
+ if formula_type
630
+ case formula_type.to_sym
631
+ when :array
632
+ f_attrs << ' t="array"'
633
+ f_attrs << %( ref="#{formula_ref}") if formula_ref
634
+ when :shared
635
+ f_attrs << ' t="shared"'
636
+ f_attrs << %( si="#{formula_si}") if formula_si
637
+ f_attrs << %( ref="#{formula_ref}") if formula_ref
638
+ end
639
+ end
640
+ f_attrs << ' ca="1"' if formula_ca
641
+ buf << "<f#{f_attrs}>" << escape_xml(formula_expr) << "</f>"
527
642
  if xml_val
528
643
  buf << "<v>" << xml_val.to_s << "</v></c>"
529
644
  else
530
- buf << "</c>"
645
+ buf << "<v>0</v></c>"
531
646
  end
647
+ elsif type == "inlineStr"
648
+ buf << "><is><t>" << escape_xml(xml_val.to_s) << "</t></is></c>"
532
649
  else
533
650
  buf << "><v>" << xml_val.to_s << "</v></c>"
534
651
  end
@@ -945,7 +1062,8 @@ module Xlsxrb
945
1062
  def write_hyperlinks(links)
946
1063
  @builder.open_tag("hyperlinks")
947
1064
  links.each_with_index do |link, idx|
948
- h_attrs = { ref: link[:cell] }
1065
+ cell_ref = link[:cell] || link[:ref]
1066
+ h_attrs = { ref: cell_ref }
949
1067
  h_attrs[:"r:id"] = "rId#{link[:_rid] || (idx + 1)}" if link[:url]
950
1068
  h_attrs[:location] = link[:location] if link[:location]
951
1069
  h_attrs[:display] = link[:display] if link[:display]
@@ -1106,6 +1224,8 @@ module Xlsxrb
1106
1224
  attrs[:hidden] = "1" if col[:hidden]
1107
1225
  attrs[:customWidth] = "1" if col[:custom_width] || col[:width]
1108
1226
  attrs[:outlineLevel] = col[:outline_level].to_s if col[:outline_level]
1227
+ style_id = col[:style_index] || col[:style]
1228
+ attrs[:style] = style_id.to_s if style_id
1109
1229
  @builder.empty_tag("col", attrs)
1110
1230
  end
1111
1231
  @builder.close_tag("cols")
@@ -1120,7 +1240,10 @@ module Xlsxrb
1120
1240
  # For formula cells with cached values, don't infer type from value
1121
1241
  type = formula ? cell[:type] : (cell[:type] || cell_type(value))
1122
1242
  attrs[:t] = type if type
1123
- attrs[:s] = cell[:style_index].to_s if cell[:style_index]
1243
+ col_idx = cell[:column_index]
1244
+ col_style = @column_styles && col_idx ? @column_styles[col_idx] : nil
1245
+ style_id = cell[:style_index] || col_style
1246
+ attrs[:s] = style_id.to_s if style_id
1124
1247
 
1125
1248
  if value.nil? && formula.nil?
1126
1249
  @builder.empty_tag("c", attrs)
@@ -1130,14 +1253,36 @@ module Xlsxrb
1130
1253
  @builder.open_tag("c", attrs)
1131
1254
  if formula
1132
1255
  f_attrs = {}
1133
- f_attrs[:ca] = "1" if cell[:formula_ca]
1256
+ formula_text = formula
1257
+ if formula.is_a?(Xlsxrb::Elements::Formula)
1258
+ formula_text = formula.expression
1259
+ formula_text = formula_text[1..] if formula_text.start_with?("=")
1260
+ f_attrs[:t] = formula.type.to_s if formula.type
1261
+ f_attrs[:ref] = formula.ref if formula.ref
1262
+ f_attrs[:si] = formula.shared_index.to_s if formula.shared_index
1263
+ f_attrs[:ca] = "1" if formula.calculate_always
1264
+ else
1265
+ formula_text = formula_text[1..] if formula_text.is_a?(String) && formula_text.start_with?("=")
1266
+ f_type = cell[:formula_type] || cell[:type]
1267
+ if f_type && %i[array shared].include?(f_type.to_sym)
1268
+ f_attrs[:t] = f_type.to_s
1269
+ f_attrs[:ref] = cell[:formula_ref] || cell[:ref] if cell[:formula_ref] || (f_type.to_s == "array" && cell[:ref])
1270
+ f_attrs[:si] = cell[:formula_si].to_s if cell[:formula_si]
1271
+ end
1272
+ f_attrs[:ca] = "1" if cell[:formula_ca]
1273
+ end
1134
1274
  if f_attrs.empty?
1135
- @builder.tag("f") { |b| b.text(formula) }
1275
+ @builder.tag("f") { |b| b.text(formula_text) }
1136
1276
  else
1137
- @builder.tag("f", f_attrs) { |b| b.text(formula) }
1277
+ @builder.tag("f", f_attrs) { |b| b.text(formula_text) }
1138
1278
  end
1139
1279
  end
1140
- @builder.tag("v") { |b| b.text(xml_cell_value(value, type)) } unless value.nil?
1280
+ v_val = if value.nil?
1281
+ formula ? "0" : nil
1282
+ else
1283
+ xml_cell_value(value, type)
1284
+ end
1285
+ @builder.tag("v") { |b| b.text(v_val) } unless v_val.nil?
1141
1286
  @builder.close_tag("c")
1142
1287
  end
1143
1288
 
@@ -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
- serial = Xlsxrb::Ooxml::Utils.datetime_to_serial(value)
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
- serial = Xlsxrb::Ooxml::Utils.date_to_serial(value)
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
@@ -330,6 +330,19 @@ module Xlsxrb
330
330
  validate_cell_address!(cell_address)
331
331
  sheet_name = sheet || @sheet_order.first
332
332
  raise ArgumentError, "unknown sheet: #{sheet_name}" unless @hyperlinks.key?(sheet_name)
333
+
334
+ if url.is_a?(Elements::Hyperlink)
335
+ link = {}
336
+ link[:url] = url.url if url.url
337
+ link[:display] = url.display || display if url.display || display
338
+ link[:tooltip] = url.tooltip || tooltip if url.tooltip || tooltip
339
+ link[:location] = url.location || location if url.location || location
340
+ raise ArgumentError, "url or location required" if link[:url].nil? && link[:location].nil?
341
+
342
+ @hyperlinks[sheet_name][cell_address] = link
343
+ return
344
+ end
345
+
333
346
  raise ArgumentError, "url or location required" if url.nil? && location.nil?
334
347
 
335
348
  link = {}
@@ -997,6 +1010,7 @@ module Xlsxrb
997
1010
  def set_workbook_property(name, value)
998
1011
  @workbook_properties[name] = value
999
1012
  end
1013
+ alias workbook_property set_workbook_property
1000
1014
 
1001
1015
  # Returns workbook properties hash.
1002
1016
  # : () -> untyped