xlsxrb 0.1.14 → 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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +108 -0
  3. data/README.md +37 -17
  4. data/benchmark/micro_ips.rb +70 -0
  5. data/lib/xlsxrb/elements/styles.rb +427 -15
  6. data/lib/xlsxrb/elements/workbook.rb +67 -4
  7. data/lib/xlsxrb/elements/worksheet.rb +117 -7
  8. data/lib/xlsxrb/ooxml/cfb.rb +10 -0
  9. data/lib/xlsxrb/ooxml/reader/listeners/core_listeners.rb +56 -38
  10. data/lib/xlsxrb/ooxml/reader/listeners/feature_listeners.rb +14 -2
  11. data/lib/xlsxrb/ooxml/reader/listeners/style_listeners.rb +12 -7
  12. data/lib/xlsxrb/ooxml/reader.rb +167 -139
  13. data/lib/xlsxrb/ooxml/shared_strings_parser.rb +83 -28
  14. data/lib/xlsxrb/ooxml/styles_parser.rb +57 -14
  15. data/lib/xlsxrb/ooxml/utils.rb +59 -2
  16. data/lib/xlsxrb/ooxml/workbook_parser.rb +63 -4
  17. data/lib/xlsxrb/ooxml/workbook_writer.rb +232 -59
  18. data/lib/xlsxrb/ooxml/worksheet_parser.rb +192 -123
  19. data/lib/xlsxrb/ooxml/worksheet_writer.rb +307 -125
  20. data/lib/xlsxrb/ooxml/writer/drawing_xml.rb +16 -12
  21. data/lib/xlsxrb/ooxml/writer/features_xml.rb +1 -1
  22. data/lib/xlsxrb/ooxml/writer/styles_xml.rb +64 -15
  23. data/lib/xlsxrb/ooxml/writer.rb +31 -28
  24. data/lib/xlsxrb/ooxml/xml_builder.rb +22 -2
  25. data/lib/xlsxrb/ooxml/xml_parser.rb +45 -0
  26. data/lib/xlsxrb/ooxml/zip_reader.rb +83 -32
  27. data/lib/xlsxrb/stream_row.rb +4 -3
  28. data/lib/xlsxrb/stream_sheet.rb +259 -7
  29. data/lib/xlsxrb/stream_writer.rb +197 -31
  30. data/lib/xlsxrb/utils.rb +68 -0
  31. data/lib/xlsxrb/version.rb +1 -1
  32. data/lib/xlsxrb/workbook_builder.rb +110 -19
  33. data/lib/xlsxrb/worksheet_builder.rb +526 -15
  34. data/lib/xlsxrb.rb +889 -90
  35. data/sig/generated/xlsxrb/elements/styles.rbs +271 -5
  36. data/sig/generated/xlsxrb/elements/workbook.rbs +54 -3
  37. data/sig/generated/xlsxrb/elements/worksheet.rbs +63 -2
  38. data/sig/generated/xlsxrb/ooxml/reader/listeners/style_listeners.rbs +2 -0
  39. data/sig/generated/xlsxrb/ooxml/reader.rbs +12 -0
  40. data/sig/generated/xlsxrb/ooxml/shared_strings_parser.rbs +3 -1
  41. data/sig/generated/xlsxrb/ooxml/styles_parser.rbs +2 -0
  42. data/sig/generated/xlsxrb/ooxml/utils.rbs +22 -0
  43. data/sig/generated/xlsxrb/ooxml/workbook_parser.rbs +7 -0
  44. data/sig/generated/xlsxrb/ooxml/workbook_writer.rbs +13 -1
  45. data/sig/generated/xlsxrb/ooxml/worksheet_parser.rbs +18 -2
  46. data/sig/generated/xlsxrb/ooxml/worksheet_writer.rbs +13 -3
  47. data/sig/generated/xlsxrb/ooxml/writer/styles_xml.rbs +5 -2
  48. data/sig/generated/xlsxrb/ooxml/xml_builder.rbs +6 -1
  49. data/sig/generated/xlsxrb/ooxml/zip_reader.rbs +9 -6
  50. data/sig/generated/xlsxrb/stream_row.rbs +3 -2
  51. data/sig/generated/xlsxrb/stream_sheet.rbs +132 -6
  52. data/sig/generated/xlsxrb/stream_writer.rbs +118 -27
  53. data/sig/generated/xlsxrb/utils.rbs +45 -0
  54. data/sig/generated/xlsxrb/worksheet_builder.rbs +252 -9
  55. data/sig/generated/xlsxrb.rbs +84 -10
  56. metadata +2 -1
@@ -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
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
 
@@ -42,8 +42,26 @@ module Xlsxrb
42
42
  # @param print_area [String, nil] Sheet print area range (e.g. "A1:H50").
43
43
  # @param print_titles [Hash, nil] Repeating print title rows/cols (e.g. { rows: "1:2", cols: "A:B" }).
44
44
  # @param sparkline_groups [Array<Hash>] Sparkline groups in the sheet.
45
- #: (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, Hash[Symbol, untyped]], ?comments: Array[Hash[Symbol, untyped]], ?images: Array[Elements::Image], ?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]]) -> void
46
- 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: [])
45
+ # @param merged_cells [Array<String>] Merged cell ranges (e.g. ["A1:B2"]).
46
+ # @param auto_filter [String, nil] AutoFilter range (e.g. "A1:E10").
47
+ # @param tables [Array<Hash>] Table definitions in the sheet.
48
+ # @param shapes [Array<Hash>] Shape definitions in the sheet.
49
+ # @param freeze_pane [Hash, nil] Freeze pane settings (e.g. { row: 1, col: 0 }).
50
+ # @param page_setup [Hash] Page setup properties (e.g. orientation, paper size).
51
+ # @param page_margins [Hash, nil] Page margins settings.
52
+ # @param header_footer [Hash] Header and footer configurations.
53
+ # @param print_options [Hash] Print options (e.g. gridlines, headings).
54
+ # @param sheet_protection [Hash, nil] Sheet protection settings.
55
+ # @param sheet_view [Hash] Sheet view configurations (e.g. zoom, gridlines).
56
+ # @param sheet_properties [Hash] Sheet properties (e.g. tabColor, outlinePr).
57
+ # @param sheet_format [Hash] Sheet format properties (e.g. defaultRowHeight, defaultColWidth).
58
+ # @param row_breaks [Array<Integer | Hash>] Explicit page row breaks.
59
+ # @param col_breaks [Array<Integer | Hash>] Explicit page column breaks.
60
+ # @param protected_ranges [Array<Hash>] Protected range definitions.
61
+ # @param cell_watches [Array<String>] Watched cell references.
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)
47
65
  @name = name
48
66
  @rows = (rows || []).freeze
49
67
  @columns = (columns || []).freeze
@@ -67,6 +85,24 @@ module Xlsxrb
67
85
  @trim_empty_rows = trim_empty_rows ? true : false
68
86
  @pad_empty_rows = pad_empty_rows ? true : false
69
87
  @pad_empty_cells = pad_empty_cells ? true : false
88
+ @merged_cells = (merged_cells || []).freeze
89
+ @auto_filter = auto_filter
90
+ @tables = (tables || []).freeze
91
+ @shapes = (shapes || []).freeze
92
+ @freeze_pane = freeze_pane&.freeze
93
+ @page_setup = (page_setup || {}).freeze
94
+ @page_margins = page_margins&.freeze
95
+ @header_footer = (header_footer || {}).freeze
96
+ @print_options = (print_options || {}).freeze
97
+ @sheet_protection = sheet_protection&.freeze
98
+ @sheet_view = (sheet_view || {}).freeze
99
+ @sheet_properties = (sheet_properties || {}).freeze
100
+ @sheet_format = (sheet_format || unmapped_data[:sheet_format] || unmapped_data.dig(:facade, :sheet_format) || {}).freeze
101
+ @row_breaks = (row_breaks || []).freeze
102
+ @col_breaks = (col_breaks || []).freeze
103
+ @protected_ranges = (protected_ranges || unmapped_data[:protected_ranges] || unmapped_data.dig(:facade, :protected_ranges) || []).freeze
104
+ @cell_watches = (cell_watches || unmapped_data[:cell_watches] || unmapped_data.dig(:facade, :cell_watches) || []).freeze
105
+ @selection = selection&.freeze
70
106
  end
71
107
 
72
108
  # Returns whether trailing empty rows are omitted during iteration.
@@ -328,6 +364,15 @@ module Xlsxrb
328
364
  @state == :visible
329
365
  end
330
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
+
331
376
  # Returns hyperlink metadata for the given cell reference, or nil.
332
377
  #
333
378
  # @param ref_or_row [String, Symbol, Integer] Cell reference (e.g. "A1") or 0-based row index.
@@ -488,6 +533,24 @@ module Xlsxrb
488
533
  new_print_titles = changes.key?(:print_titles) ? changes[:print_titles] : print_titles
489
534
  new_sparklines = changes.key?(:sparkline_groups) ? changes[:sparkline_groups] : sparkline_groups
490
535
  new_trim = changes.key?(:trim_empty_rows) ? changes[:trim_empty_rows] : @trim_empty_rows
536
+ new_merged = changes.key?(:merged_cells) ? changes[:merged_cells] : merged_cells
537
+ new_auto_filter = changes.key?(:auto_filter) ? changes[:auto_filter] : auto_filter
538
+ new_tables = changes.key?(:tables) ? changes[:tables] : tables
539
+ new_shapes = changes.key?(:shapes) ? changes[:shapes] : shapes
540
+ new_freeze_pane = changes.key?(:freeze_pane) ? changes[:freeze_pane] : freeze_pane
541
+ new_page_setup = changes.key?(:page_setup) ? changes[:page_setup] : page_setup
542
+ new_page_margins = changes.key?(:page_margins) ? changes[:page_margins] : page_margins
543
+ new_header_footer = changes.key?(:header_footer) ? changes[:header_footer] : header_footer
544
+ new_print_options = changes.key?(:print_options) ? changes[:print_options] : print_options
545
+ new_sheet_protection = changes.key?(:sheet_protection) ? changes[:sheet_protection] : sheet_protection
546
+ new_sheet_view = changes.key?(:sheet_view) ? changes[:sheet_view] : sheet_view
547
+ new_sheet_properties = changes.key?(:sheet_properties) ? changes[:sheet_properties] : sheet_properties
548
+ new_sheet_format = changes.key?(:sheet_format) ? changes[:sheet_format] : sheet_format
549
+ new_row_breaks = changes.key?(:row_breaks) ? changes[:row_breaks] : row_breaks
550
+ new_col_breaks = changes.key?(:col_breaks) ? changes[:col_breaks] : col_breaks
551
+ new_protected_ranges = changes.key?(:protected_ranges) ? changes[:protected_ranges] : protected_ranges
552
+ new_cell_watches = changes.key?(:cell_watches) ? changes[:cell_watches] : cell_watches
553
+ new_selection = changes.key?(:selection) ? changes[:selection] : selection
491
554
 
492
555
  self.class.new(
493
556
  name: new_name,
@@ -507,7 +570,25 @@ module Xlsxrb
507
570
  print_area: new_print_area,
508
571
  print_titles: new_print_titles,
509
572
  sparkline_groups: new_sparklines,
510
- trim_empty_rows: new_trim
573
+ trim_empty_rows: new_trim,
574
+ merged_cells: new_merged,
575
+ auto_filter: new_auto_filter,
576
+ tables: new_tables,
577
+ shapes: new_shapes,
578
+ freeze_pane: new_freeze_pane,
579
+ page_setup: new_page_setup,
580
+ page_margins: new_page_margins,
581
+ header_footer: new_header_footer,
582
+ print_options: new_print_options,
583
+ sheet_protection: new_sheet_protection,
584
+ sheet_view: new_sheet_view,
585
+ sheet_properties: new_sheet_properties,
586
+ sheet_format: new_sheet_format,
587
+ row_breaks: new_row_breaks,
588
+ col_breaks: new_col_breaks,
589
+ protected_ranges: new_protected_ranges,
590
+ cell_watches: new_cell_watches,
591
+ selection: new_selection
511
592
  )
512
593
  end
513
594
 
@@ -533,7 +614,23 @@ module Xlsxrb
533
614
  print_area: print_area,
534
615
  print_titles: print_titles,
535
616
  sparkline_groups: sparkline_groups,
536
- trim_empty_rows: @trim_empty_rows
617
+ trim_empty_rows: @trim_empty_rows,
618
+ merged_cells: merged_cells,
619
+ auto_filter: auto_filter,
620
+ tables: tables,
621
+ shapes: shapes,
622
+ freeze_pane: freeze_pane,
623
+ page_setup: page_setup,
624
+ page_margins: page_margins,
625
+ header_footer: header_footer,
626
+ print_options: print_options,
627
+ sheet_protection: sheet_protection,
628
+ sheet_view: sheet_view,
629
+ sheet_properties: sheet_properties,
630
+ sheet_format: sheet_format,
631
+ row_breaks: row_breaks,
632
+ col_breaks: col_breaks,
633
+ selection: selection
537
634
  }
538
635
  end
539
636
 
@@ -544,13 +641,26 @@ module Xlsxrb
544
641
 
545
642
  name == other.name && rows == other.rows && columns == other.columns && charts == other.charts &&
546
643
  conditional_formatting == other.conditional_formatting && data_validations == other.data_validations &&
547
- state == other.state && hyperlinks == other.hyperlinks && comments == other.comments
644
+ state == other.state && hyperlinks == other.hyperlinks && comments == other.comments &&
645
+ merged_cells == other.merged_cells && auto_filter == other.auto_filter &&
646
+ tables == other.tables && shapes == other.shapes && freeze_pane == other.freeze_pane &&
647
+ page_setup == other.page_setup && page_margins == other.page_margins &&
648
+ header_footer == other.header_footer && print_options == other.print_options &&
649
+ sheet_protection == other.sheet_protection && sheet_view == other.sheet_view &&
650
+ sheet_properties == other.sheet_properties && sheet_format == other.sheet_format &&
651
+ row_breaks == other.row_breaks && col_breaks == other.col_breaks &&
652
+ selection == other.selection
548
653
  end
549
654
  alias eql? ==
550
655
 
551
656
  #: () -> Integer
552
657
  def hash
553
- [self.class, name, rows, columns, charts, conditional_formatting, data_validations, state, hyperlinks, comments].hash
658
+ [
659
+ self.class, name, rows, columns, charts, conditional_formatting, data_validations,
660
+ state, hyperlinks, comments, merged_cells, auto_filter, tables, shapes, freeze_pane,
661
+ page_setup, page_margins, header_footer, print_options, sheet_protection, sheet_view,
662
+ sheet_properties, sheet_format, row_breaks, col_breaks, selection
663
+ ].hash
554
664
  end
555
665
 
556
666
  # Returns self when load is called on an already in-memory Worksheet.
@@ -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
@@ -24,12 +24,15 @@ module Xlsxrb
24
24
  @runs = []
25
25
  @current_font = {}
26
26
  @has_runs = false
27
+ @inside_rph = false
27
28
  end
28
29
 
29
30
  def start_element(_uri, local_name, qname, attributes)
30
31
  name = element_name(local_name, qname)
31
32
 
32
33
  case name
34
+ when "rPh"
35
+ @inside_rph = true
33
36
  when "si"
34
37
  @inside_si = true
35
38
  @text_buffer = +""
@@ -74,7 +77,7 @@ module Xlsxrb
74
77
  when "scheme"
75
78
  @current_font[:scheme] = attributes["val"] if @inside_rpr
76
79
  when "t"
77
- @inside_t = @inside_si
80
+ @inside_t = @inside_si && !@inside_rph
78
81
  @text_buffer = +"" if @inside_r
79
82
  end
80
83
  end
@@ -87,12 +90,16 @@ module Xlsxrb
87
90
  name = element_name(local_name, qname)
88
91
 
89
92
  case name
93
+ when "rPh"
94
+ @inside_rph = false
90
95
  when "t"
91
96
  @inside_t = false
92
97
  when "rPr"
93
98
  @inside_rpr = false
94
99
  when "r"
95
- run = { text: @text_buffer.dup }
100
+ decoded_text = XmlBuilder.unescape(@text_buffer)
101
+ decoded_text = decoded_text.gsub("\r\n", "\n").tr("\r", "\n") if decoded_text.include?("\r")
102
+ run = { text: decoded_text }
96
103
  run[:font] = @current_font.dup unless @current_font.empty?
97
104
  @runs << run
98
105
  @inside_r = false
@@ -100,7 +107,9 @@ module Xlsxrb
100
107
  @strings << if @has_runs
101
108
  Xlsxrb::Elements::RichText.new(runs: @runs)
102
109
  else
103
- @text_buffer.dup
110
+ decoded_text = XmlBuilder.unescape(@text_buffer)
111
+ decoded_text = decoded_text.gsub("\r\n", "\n").tr("\r", "\n") if decoded_text.include?("\r")
112
+ decoded_text
104
113
  end
105
114
  @inside_si = false
106
115
  @text_buffer = +""
@@ -145,18 +154,23 @@ module Xlsxrb
145
154
  @is_has_runs = false
146
155
  @is_current_font = {}
147
156
  @is_run_text = +""
157
+ @inside_rph = false
158
+ @has_inline_text = false
148
159
  end
149
160
 
150
161
  def start_element(_uri, local_name, qname, attributes)
151
162
  name = element_name(local_name, qname)
152
163
 
153
164
  case name
165
+ when "rPh"
166
+ @inside_rph = true
154
167
  when "row"
155
168
  parse_row_attributes(attributes)
156
169
  when "c"
157
170
  @current_cell_ref = attributes["r"]
158
171
  @current_cell_type = attributes["t"]
159
172
  @current_cell_ph = %w[1 true].include?(attributes["ph"])
173
+ @has_inline_text = false
160
174
  @value_buffer = +""
161
175
  @inline_text_buffer = +""
162
176
  @formula_buffer = +""
@@ -188,6 +202,7 @@ module Xlsxrb
188
202
  @formula_r1 = attributes["r1"] if attributes["r1"]
189
203
  @formula_r2 = attributes["r2"] if attributes["r2"]
190
204
  when "is"
205
+ @has_inline_text = true
191
206
  @inside_is = true if @current_cell_type == "inlineStr"
192
207
  when "r"
193
208
  if @inside_is
@@ -231,11 +246,15 @@ module Xlsxrb
231
246
  when "scheme"
232
247
  @is_current_font[:scheme] = attributes["val"] if @inside_is_rpr
233
248
  when "t"
234
- if @inside_is_r
235
- @is_run_text = +""
236
- @inside_inline_text = true
237
- elsif @inside_is || (@current_cell_type == "inlineStr" && !@current_cell_ref.nil?)
238
- @inside_inline_text = true
249
+ unless @inside_rph
250
+ if @inside_is_r
251
+ @is_run_text = +""
252
+ @inside_inline_text = true
253
+ @has_inline_text = true
254
+ elsif @inside_is || (@current_cell_type == "inlineStr" && !@current_cell_ref.nil?)
255
+ @inside_inline_text = true
256
+ @has_inline_text = true
257
+ end
239
258
  end
240
259
  end
241
260
  end
@@ -256,6 +275,8 @@ module Xlsxrb
256
275
  name = element_name(local_name, qname)
257
276
 
258
277
  case name
278
+ when "rPh"
279
+ @inside_rph = false
259
280
  when "v"
260
281
  @inside_value = false
261
282
  when "f"
@@ -309,7 +330,14 @@ module Xlsxrb
309
330
  return if @current_cell_ref.nil?
310
331
 
311
332
  unless @formula_buffer.empty? && @formula_type.nil?
312
- cached = @value_buffer.empty? ? nil : @value_buffer.dup
333
+ cached = if @value_buffer.empty?
334
+ nil
335
+ elsif @current_cell_type == "b"
336
+ v = @value_buffer.strip
337
+ v == "1" || v.casecmp?("true")
338
+ else
339
+ @value_buffer.dup
340
+ end
313
341
  f_type = { "shared" => :shared, "array" => :array, "dataTable" => :data_table }[@formula_type]
314
342
  @cells[@current_cell_ref] = Xlsxrb::Elements::Formula.new(
315
343
  expression: @formula_buffer.dup,
@@ -328,39 +356,29 @@ module Xlsxrb
328
356
  return
329
357
  end
330
358
 
331
- case @current_cell_type
332
- when "inlineStr"
333
- @cells[@current_cell_ref] = if @is_has_runs
334
- Xlsxrb::Elements::RichText.new(runs: @is_runs.map(&:dup))
335
- else
336
- @inline_text_buffer.dup
337
- end
338
- when "s"
339
- index = @value_buffer.to_i
340
- @cells[@current_cell_ref] = @shared_strings[index] || ""
341
- when "e"
342
- code = @value_buffer.dup
343
- @cells[@current_cell_ref] = if Xlsxrb::Elements::VALID_ERROR_CODES.include?(code)
344
- Xlsxrb::Elements::CellError.new(code:)
345
- else
346
- code
347
- end
348
- when "b"
349
- @cells[@current_cell_ref] = @value_buffer.strip == "1"
350
- when nil, "", "n"
351
- return if @value_buffer.empty?
352
-
353
- raw = @value_buffer.dup
354
- @cells[@current_cell_ref] = numeric_value(raw)
359
+ if @current_cell_type == "inlineStr" && @is_has_runs
360
+ @cells[@current_cell_ref] = Xlsxrb::Elements::RichText.new(runs: @is_runs.map(&:dup))
361
+ return
355
362
  end
363
+
364
+ v = if @current_cell_type == "inlineStr"
365
+ @has_inline_text ? @inline_text_buffer : nil
366
+ else
367
+ @value_buffer.empty? ? nil : @value_buffer
368
+ end
369
+
370
+ val = Xlsxrb::Ooxml::WorksheetParser.resolve_cell_value(@current_cell_type, v, nil, @shared_strings)
371
+ return if val.nil? && (@current_cell_type.nil? || @current_cell_type == "" || @current_cell_type == "n" || @current_cell_type == "s" || @current_cell_type == "inlineStr")
372
+
373
+ @cells[@current_cell_ref] = if @current_cell_type == "e" && val && Xlsxrb::Elements::VALID_ERROR_CODES.include?(val)
374
+ Xlsxrb::Elements::CellError.new(code: val)
375
+ else
376
+ val
377
+ end
356
378
  end
357
379
 
358
380
  def numeric_value(raw)
359
- if raw.include?(".")
360
- raw.to_f
361
- else
362
- raw.to_i
363
- end
381
+ Xlsxrb::Ooxml::WorksheetParser.numeric_cell_value(raw)
364
382
  end
365
383
 
366
384
  def element_name(local_name, qname)
@@ -204,7 +204,7 @@ module Xlsxrb
204
204
  def start_element(_uri, local_name, qname, _attributes)
205
205
  name = element_name(local_name, qname)
206
206
  case name
207
- when "Application", "AppVersion"
207
+ when "Application", "AppVersion", "Company", "Manager"
208
208
  @current_field = name
209
209
  @text_buffer = +""
210
210
  when "HeadingPairs"
@@ -232,6 +232,12 @@ module Xlsxrb
232
232
  when "AppVersion"
233
233
  @properties[:app_version] = @text_buffer.dup
234
234
  @current_field = nil
235
+ when "Company"
236
+ @properties[:company] = @text_buffer.dup
237
+ @current_field = nil
238
+ when "Manager"
239
+ @properties[:manager] = @text_buffer.dup
240
+ @current_field = nil
235
241
  when "lpstr"
236
242
  if @inside_titles_of_parts
237
243
  @titles_of_parts << @text_buffer.dup
@@ -956,11 +962,14 @@ module Xlsxrb
956
962
  when "formula2"
957
963
  @inside_formula2 = true
958
964
  @text_buffer = +""
965
+ when "sqref"
966
+ @inside_sqref = true
967
+ @text_buffer = +""
959
968
  end
960
969
  end
961
970
 
962
971
  def characters(text)
963
- @text_buffer << text if @inside_formula1 || @inside_formula2
972
+ @text_buffer << text if @inside_formula1 || @inside_formula2 || @inside_sqref
964
973
  end
965
974
 
966
975
  def end_element(_uri, local_name, qname)
@@ -972,6 +981,9 @@ module Xlsxrb
972
981
  when "formula2"
973
982
  @current_dv[:formula2] = @text_buffer.dup if @current_dv
974
983
  @inside_formula2 = false
984
+ when "sqref"
985
+ @current_dv[:sqref] = @text_buffer.strip if @current_dv && (@current_dv[:sqref].nil? || @current_dv[:sqref].empty?)
986
+ @inside_sqref = false
975
987
  when "dataValidation"
976
988
  @validations << @current_dv if @current_dv
977
989
  @current_dv = nil
@@ -135,19 +135,19 @@ module Xlsxrb
135
135
  when "font"
136
136
  @current_font = {} if @inside_fonts || @inside_dxfs
137
137
  when "b"
138
- @current_font[:bold] = true if @current_font
138
+ @current_font[:bold] = truthy_val?(attributes) if @current_font
139
139
  when "i"
140
- @current_font[:italic] = true if @current_font
140
+ @current_font[:italic] = truthy_val?(attributes) if @current_font
141
141
  when "strike"
142
- @current_font[:strike] = true if @current_font
142
+ @current_font[:strike] = truthy_val?(attributes) if @current_font
143
143
  when "shadow"
144
- @current_font[:shadow] = true if @current_font
144
+ @current_font[:shadow] = truthy_val?(attributes) if @current_font
145
145
  when "outline"
146
- @current_font[:outline] = true if @current_font
146
+ @current_font[:outline] = truthy_val?(attributes) if @current_font
147
147
  when "condense"
148
- @current_font[:condense] = true if @current_font
148
+ @current_font[:condense] = truthy_val?(attributes) if @current_font
149
149
  when "extend"
150
- @current_font[:extend] = true if @current_font
150
+ @current_font[:extend] = truthy_val?(attributes) if @current_font
151
151
  when "u"
152
152
  if @current_font
153
153
  val = attributes["val"]
@@ -303,6 +303,11 @@ module Xlsxrb
303
303
 
304
304
  private
305
305
 
306
+ def truthy_val?(attributes)
307
+ val = attributes["val"]
308
+ val.nil? || (val != "0" && val.downcase != "false")
309
+ end
310
+
306
311
  def parse_color(attributes)
307
312
  if @inside_mru_colors
308
313
  c = {}