xlsxrb 0.1.12 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +116 -42
- data/README.md +28 -22
- data/lib/xlsxrb/colors.rb +118 -0
- data/lib/xlsxrb/dsl_helpers.rb +59 -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 +96 -4
- data/lib/xlsxrb/elements/worksheet.rb +424 -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.rb +12 -14
- data/lib/xlsxrb/ooxml/shared_strings_parser.rb +10 -6
- data/lib/xlsxrb/ooxml/styles_parser.rb +39 -9
- data/lib/xlsxrb/ooxml/utils.rb +34 -18
- data/lib/xlsxrb/ooxml/workbook_parser.rb +53 -15
- data/lib/xlsxrb/ooxml/workbook_writer.rb +70 -21
- data/lib/xlsxrb/ooxml/worksheet_parser.rb +466 -63
- data/lib/xlsxrb/ooxml/worksheet_writer.rb +206 -61
- data/lib/xlsxrb/ooxml/writer/features_xml.rb +4 -2
- data/lib/xlsxrb/ooxml/writer.rb +16 -1
- data/lib/xlsxrb/ooxml/xml_builder.rb +20 -0
- data/lib/xlsxrb/ooxml/zip_reader.rb +452 -82
- data/lib/xlsxrb/ooxml/zip_writer.rb +32 -5
- data/lib/xlsxrb/stream_row.rb +243 -16
- data/lib/xlsxrb/stream_sheet.rb +504 -17
- data/lib/xlsxrb/stream_writer.rb +246 -83
- data/lib/xlsxrb/style_builder.rb +308 -22
- data/lib/xlsxrb/utils.rb +158 -0
- data/lib/xlsxrb/version.rb +1 -1
- data/lib/xlsxrb/workbook_builder.rb +74 -4
- data/lib/xlsxrb/worksheet_builder.rb +230 -47
- data/lib/xlsxrb.rb +1005 -229
- data/sig/generated/xlsxrb/colors.rbs +40 -0
- data/sig/generated/xlsxrb/dsl_helpers.rbs +32 -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 +58 -2
- data/sig/generated/xlsxrb/elements/worksheet.rbs +231 -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 -2
- 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/worksheet_parser.rbs +31 -8
- data/sig/generated/xlsxrb/ooxml/worksheet_writer.rbs +3 -1
- data/sig/generated/xlsxrb/ooxml/writer.rbs +2 -0
- data/sig/generated/xlsxrb/ooxml/xml_builder.rbs +11 -0
- data/sig/generated/xlsxrb/ooxml/zip_reader.rbs +106 -8
- data/sig/generated/xlsxrb/ooxml/zip_writer.rbs +5 -1
- data/sig/generated/xlsxrb/stream_row.rbs +100 -6
- data/sig/generated/xlsxrb/stream_sheet.rbs +288 -14
- data/sig/generated/xlsxrb/stream_writer.rbs +120 -14
- data/sig/generated/xlsxrb/style_builder.rbs +55 -18
- data/sig/generated/xlsxrb/utils.rbs +119 -0
- data/sig/generated/xlsxrb/workbook_builder.rbs +26 -0
- data/sig/generated/xlsxrb/worksheet_builder.rbs +107 -11
- data/sig/generated/xlsxrb.rbs +202 -36
- metadata +16 -146
- data/.devcontainer/Dockerfile +0 -65
- data/.devcontainer/devcontainer.json +0 -17
- data/.gem_rbs_collection/ast/2.4/.rbs_meta.yaml +0 -9
- data/.gem_rbs_collection/ast/2.4/ast.rbs +0 -73
- data/.gem_rbs_collection/concurrent-ruby/1.1/.rbs_meta.yaml +0 -9
- data/.gem_rbs_collection/concurrent-ruby/1.1/array.rbs +0 -4
- data/.gem_rbs_collection/concurrent-ruby/1.1/atomic_reference.rbs +0 -16
- data/.gem_rbs_collection/concurrent-ruby/1.1/executor.rbs +0 -96
- data/.gem_rbs_collection/concurrent-ruby/1.1/hash.rbs +0 -4
- data/.gem_rbs_collection/concurrent-ruby/1.1/map.rbs +0 -68
- data/.gem_rbs_collection/concurrent-ruby/1.1/promises.rbs +0 -249
- data/.gem_rbs_collection/concurrent-ruby/1.1/set.rbs +0 -4
- data/.gem_rbs_collection/concurrent-ruby/1.1/timer_task.rbs +0 -47
- data/.gem_rbs_collection/concurrent-ruby/1.1/utility/processor_counter.rbs +0 -5
- data/.gem_rbs_collection/csv/3.3/.rbs_meta.yaml +0 -9
- data/.gem_rbs_collection/csv/3.3/csv.rbs +0 -3871
- data/.gem_rbs_collection/csv/3.3/manifest.yaml +0 -3
- data/.gem_rbs_collection/lint_roller/1.1/.rbs_meta.yaml +0 -9
- data/.gem_rbs_collection/lint_roller/1.1/lint_roller.rbs +0 -48
- data/.gem_rbs_collection/listen/3.9/.rbs_meta.yaml +0 -9
- data/.gem_rbs_collection/listen/3.9/listen.rbs +0 -25
- data/.gem_rbs_collection/listen/3.9/listener.rbs +0 -24
- data/.gem_rbs_collection/logger/1.7/.rbs_meta.yaml +0 -9
- data/.gem_rbs_collection/logger/1.7/formatter.rbs +0 -45
- data/.gem_rbs_collection/logger/1.7/log_device.rbs +0 -100
- data/.gem_rbs_collection/logger/1.7/logger.rbs +0 -796
- data/.gem_rbs_collection/logger/1.7/manifest.yaml +0 -2
- data/.gem_rbs_collection/logger/1.7/period.rbs +0 -17
- data/.gem_rbs_collection/logger/1.7/severity.rbs +0 -34
- data/.gem_rbs_collection/nokogiri/1.11/.rbs_meta.yaml +0 -9
- data/.gem_rbs_collection/nokogiri/1.11/nokogiri.rbs +0 -2332
- data/.gem_rbs_collection/nokogiri/1.11/patch.rbs +0 -4
- data/.gem_rbs_collection/parallel/1.20/.rbs_meta.yaml +0 -9
- data/.gem_rbs_collection/parallel/1.20/parallel.rbs +0 -86
- data/.gem_rbs_collection/parser/3.2/.rbs_meta.yaml +0 -9
- data/.gem_rbs_collection/parser/3.2/manifest.yaml +0 -7
- data/.gem_rbs_collection/parser/3.2/parser.rbs +0 -194
- data/.gem_rbs_collection/parser/3.2/polyfill.rbs +0 -4
- data/.gem_rbs_collection/rainbow/3.0/.rbs_meta.yaml +0 -9
- data/.gem_rbs_collection/rainbow/3.0/global.rbs +0 -7
- data/.gem_rbs_collection/rainbow/3.0/presenter.rbs +0 -209
- data/.gem_rbs_collection/rainbow/3.0/rainbow.rbs +0 -5
- data/.gem_rbs_collection/rake/13.0/.rbs_meta.yaml +0 -9
- data/.gem_rbs_collection/rake/13.0/manifest.yaml +0 -2
- data/.gem_rbs_collection/rake/13.0/rake.rbs +0 -39
- data/.gem_rbs_collection/regexp_parser/2.8/.rbs_meta.yaml +0 -9
- data/.gem_rbs_collection/regexp_parser/2.8/regexp_parser.rbs +0 -17
- data/.gem_rbs_collection/rubocop/1.57/.rbs_meta.yaml +0 -9
- data/.gem_rbs_collection/rubocop/1.57/rubocop.rbs +0 -208
- data/.gem_rbs_collection/rubocop-ast/1.46/.rbs_meta.yaml +0 -9
- data/.gem_rbs_collection/rubocop-ast/1.46/rubocop-ast.rbs +0 -903
- data/.gem_rbs_collection/rubyzip/3.2/.rbs_meta.yaml +0 -9
- data/.gem_rbs_collection/rubyzip/3.2/manifest.yaml +0 -8
- data/.gem_rbs_collection/rubyzip/3.2/zip/central_directory.rbs +0 -42
- data/.gem_rbs_collection/rubyzip/3.2/zip/compressor.rbs +0 -5
- data/.gem_rbs_collection/rubyzip/3.2/zip/constants.rbs +0 -47
- data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/aes_encryption.rbs +0 -30
- data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/decrypted_io.rbs +0 -9
- data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/encryption.rbs +0 -7
- data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/null_encryption.rbs +0 -19
- data/.gem_rbs_collection/rubyzip/3.2/zip/crypto/traditional_encryption.rbs +0 -31
- data/.gem_rbs_collection/rubyzip/3.2/zip/decompressor.rbs +0 -18
- data/.gem_rbs_collection/rubyzip/3.2/zip/deflater.rbs +0 -12
- data/.gem_rbs_collection/rubyzip/3.2/zip/dirtyable.rbs +0 -11
- data/.gem_rbs_collection/rubyzip/3.2/zip/dos_time.rbs +0 -13
- data/.gem_rbs_collection/rubyzip/3.2/zip/entry.rbs +0 -95
- data/.gem_rbs_collection/rubyzip/3.2/zip/entry_set.rbs +0 -31
- data/.gem_rbs_collection/rubyzip/3.2/zip/errors.rbs +0 -58
- data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/aes.rbs +0 -24
- data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/generic.rbs +0 -17
- data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/ntfs.rbs +0 -23
- data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/old_unix.rbs +0 -22
- data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/universal_time.rbs +0 -30
- data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/unix.rbs +0 -20
- data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/unknown.rbs +0 -15
- data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field/zip64.rbs +0 -26
- data/.gem_rbs_collection/rubyzip/3.2/zip/extra_field.rbs +0 -21
- data/.gem_rbs_collection/rubyzip/3.2/zip/file.rbs +0 -131
- data/.gem_rbs_collection/rubyzip/3.2/zip/file_split.rbs +0 -14
- data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/dir.rbs +0 -33
- data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/directory_iterator.rbs +0 -21
- data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/file.rbs +0 -63
- data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/file_stat.rbs +0 -55
- data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem/zip_file_name_mapper.rbs +0 -35
- data/.gem_rbs_collection/rubyzip/3.2/zip/filesystem.rbs +0 -7
- data/.gem_rbs_collection/rubyzip/3.2/zip/inflater.rbs +0 -10
- data/.gem_rbs_collection/rubyzip/3.2/zip/input_stream.rbs +0 -22
- data/.gem_rbs_collection/rubyzip/3.2/zip/ioextras/abstract_input_stream.rbs +0 -29
- data/.gem_rbs_collection/rubyzip/3.2/zip/ioextras/abstract_output_stream.rbs +0 -17
- data/.gem_rbs_collection/rubyzip/3.2/zip/ioextras.rbs +0 -13
- data/.gem_rbs_collection/rubyzip/3.2/zip/null_compressor.rbs +0 -10
- data/.gem_rbs_collection/rubyzip/3.2/zip/null_decompressor.rbs +0 -8
- data/.gem_rbs_collection/rubyzip/3.2/zip/null_input_stream.rbs +0 -6
- data/.gem_rbs_collection/rubyzip/3.2/zip/output_stream.rbs +0 -30
- data/.gem_rbs_collection/rubyzip/3.2/zip/pass_thru_compressor.rbs +0 -10
- data/.gem_rbs_collection/rubyzip/3.2/zip/pass_thru_decompressor.rbs +0 -10
- data/.gem_rbs_collection/rubyzip/3.2/zip/streamable_directory.rbs +0 -5
- data/.gem_rbs_collection/rubyzip/3.2/zip/streamable_stream.rbs +0 -15
- data/.gem_rbs_collection/rubyzip/3.2/zip/version.rbs +0 -3
- data/.gem_rbs_collection/rubyzip/3.2/zip.rbs +0 -40
- data/.mutant.yml +0 -8
- data/Rakefile +0 -633
- data/Steepfile +0 -18
- data/benchmark.rb +0 -406
- data/docs/ARCHITECTURE.md +0 -514
- data/docs/DEVELOPMENT.md +0 -137
- data/docs/MUTATION_TESTING.md +0 -52
- data/docs/PEER_LIBRARIES.md +0 -121
- data/docs/QUALITY_ASSURANCE.md +0 -26
- data/docs/SPEC_SOURCES.md +0 -48
- data/docs/assets/benchmark_results.svg +0 -144
- data/docs/assets/lsp_autocompletion.png +0 -0
- data/docs/assets/playground_preview.png +0 -0
- data/docs/coi-serviceworker.js +0 -82
- data/docs/office_thread.js +0 -77
- data/docs/preview.html +0 -719
- data/docs/visual/VisualGallery.md +0 -4683
- data/docs/wasm/ruby.wasm +0 -0
- data/docs/wasm/wasm_doc_helper.css +0 -351
- data/docs/wasm/wasm_doc_helper.js +0 -439
- data/docs/zeta.js +0 -1107
- data/rbs_collection.lock.yaml +0 -252
- data/rbs_collection.yaml +0 -19
- data/vendor/sdk_runner/Program.cs +0 -93
- data/vendor/sdk_runner/sdk_runner.csproj +0 -13
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
# rbs_inline: enabled
|
|
4
4
|
|
|
5
5
|
require_relative "xml_parser"
|
|
6
|
+
require_relative "xml_builder"
|
|
6
7
|
|
|
7
8
|
module Xlsxrb
|
|
8
9
|
module Ooxml
|
|
@@ -12,45 +13,71 @@ module Xlsxrb
|
|
|
12
13
|
class WorksheetParser
|
|
13
14
|
EMPTY_ARRAY = [].freeze
|
|
14
15
|
EMPTY_HASH = {}.freeze
|
|
16
|
+
TAG_DELIM_BYTES = [32, 62, 9, 10, 13, 47].freeze
|
|
17
|
+
ROW_ATTR_START_BYTES = [104, 99, 111, 115].freeze
|
|
15
18
|
|
|
16
19
|
# Parses all rows from a worksheet XML string.
|
|
17
20
|
# Returns an Array of raw row hashes:
|
|
18
21
|
# { index:, cells: [{ ref:, type:, style_index:, value:, formula: }], attrs:, unmapped: }
|
|
19
|
-
def self.parse(xml_string, shared_strings: [])
|
|
22
|
+
def self.parse(xml_string, shared_strings: [], styles: nil)
|
|
20
23
|
return [] if xml_string.nil? || xml_string.empty?
|
|
21
24
|
|
|
22
25
|
rows = []
|
|
23
|
-
each_row(xml_string, shared_strings: shared_strings) { |row| rows << row }
|
|
26
|
+
each_row(xml_string, shared_strings: shared_strings, styles: styles) { |row| rows << row }
|
|
24
27
|
rows
|
|
25
28
|
end
|
|
26
29
|
|
|
27
30
|
# Streaming parse: yields one raw row hash at a time.
|
|
28
|
-
def self.each_row(
|
|
29
|
-
return enum_for(:each_row,
|
|
30
|
-
return if
|
|
31
|
+
def self.each_row(xml_source, shared_strings: [], part_name: "xl/worksheets/sheet1.xml", styles: nil, date1904: false, trim_empty_rows: false, pad_empty_rows: false, pad_empty_cells: false, &block)
|
|
32
|
+
return enum_for(:each_row, xml_source, shared_strings: shared_strings, part_name: part_name, styles: styles, date1904: date1904, trim_empty_rows: trim_empty_rows, pad_empty_rows: pad_empty_rows, pad_empty_cells: pad_empty_cells) unless block
|
|
33
|
+
return if xml_source.nil? || (xml_source.respond_to?(:empty?) && xml_source.empty?)
|
|
34
|
+
|
|
35
|
+
row_consumer = if trim_empty_rows
|
|
36
|
+
pending_empty = []
|
|
37
|
+
lambda do |row|
|
|
38
|
+
if row.empty?
|
|
39
|
+
pending_empty << row
|
|
40
|
+
else
|
|
41
|
+
pending_empty.each(&block)
|
|
42
|
+
pending_empty.clear
|
|
43
|
+
block.call(row)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
else
|
|
47
|
+
block
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
if pad_empty_rows
|
|
51
|
+
current_expected_idx = 0
|
|
52
|
+
inner_consumer = row_consumer
|
|
53
|
+
row_consumer = lambda do |row|
|
|
54
|
+
while current_expected_idx < row.index
|
|
55
|
+
empty_row = StreamRow.fast_create(
|
|
56
|
+
current_expected_idx, "".b, 0, 0, shared_strings, "", nil, false, false, nil, nil, styles, date1904, false, pad_empty_cells
|
|
57
|
+
)
|
|
58
|
+
inner_consumer.call(empty_row)
|
|
59
|
+
current_expected_idx += 1
|
|
60
|
+
end
|
|
61
|
+
inner_consumer.call(row)
|
|
62
|
+
current_expected_idx = row.index + 1
|
|
63
|
+
end
|
|
64
|
+
end
|
|
31
65
|
|
|
32
|
-
|
|
66
|
+
if xml_source.is_a?(String)
|
|
67
|
+
fast_scan_rows_direct(xml_source, shared_strings, part_name, styles, date1904, pad_empty_cells, &row_consumer)
|
|
68
|
+
else
|
|
69
|
+
scan_rows_stream(xml_source, shared_strings, part_name, styles, date1904, pad_empty_cells, &row_consumer)
|
|
70
|
+
end
|
|
33
71
|
end
|
|
34
72
|
|
|
35
73
|
# Parses column definitions (<cols>) from a worksheet.
|
|
36
|
-
def self.parse_columns(xml_string,
|
|
74
|
+
def self.parse_columns(xml_string, _part_name = "xl/worksheets/sheet1.xml")
|
|
37
75
|
return [] if xml_string.nil? || xml_string.empty?
|
|
76
|
+
return [] unless xml_string.include?("<cols")
|
|
38
77
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
min, max, width, hidden, custom_width, outline_level = event.args
|
|
44
|
-
columns << {
|
|
45
|
-
min: min,
|
|
46
|
-
max: max,
|
|
47
|
-
width: width,
|
|
48
|
-
hidden: hidden,
|
|
49
|
-
custom_width: custom_width,
|
|
50
|
-
outline_level: outline_level
|
|
51
|
-
}
|
|
52
|
-
end
|
|
53
|
-
columns
|
|
78
|
+
listener = ColumnsListener.new
|
|
79
|
+
XmlParser.parse(xml_string, listener)
|
|
80
|
+
listener.columns
|
|
54
81
|
end
|
|
55
82
|
|
|
56
83
|
# Yields Event objects for columns, rows, cells, and hyperlinks.
|
|
@@ -75,19 +102,37 @@ module Xlsxrb
|
|
|
75
102
|
fast_scan_events(xml_string, shared_strings, part_name, &block)
|
|
76
103
|
|
|
77
104
|
# 3. Parse and yield hyperlink events
|
|
78
|
-
|
|
105
|
+
parse_hyperlinks(xml_string).each do |hl|
|
|
106
|
+
block.call(Event.new(
|
|
107
|
+
type: :hyperlink,
|
|
108
|
+
args: [hl[:ref], hl[:rid], hl[:display], hl[:tooltip], hl[:location]],
|
|
109
|
+
source: { part: part_name, cell: hl[:ref] }
|
|
110
|
+
))
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Parses all hyperlink elements from worksheet XML.
|
|
115
|
+
#
|
|
116
|
+
# @param xml_string [String, nil]
|
|
117
|
+
# @return [Array<Hash[Symbol, untyped]>]
|
|
118
|
+
# @api public
|
|
119
|
+
#: (String?) -> Array[Hash[Symbol, untyped]]
|
|
120
|
+
def self.parse_hyperlinks(xml_string)
|
|
121
|
+
return [] if xml_string.nil? || xml_string.empty?
|
|
122
|
+
return [] unless xml_string.include?("hyperlink")
|
|
79
123
|
|
|
80
124
|
xml = xml_string.b
|
|
81
125
|
hpos_term = xml.index("hyperlinks")
|
|
82
|
-
return unless hpos_term
|
|
126
|
+
return [] unless hpos_term
|
|
83
127
|
|
|
84
128
|
hpos = xml.rindex("<", hpos_term)
|
|
85
|
-
return unless hpos
|
|
129
|
+
return [] unless hpos
|
|
86
130
|
|
|
87
131
|
h_end_term = xml.index("/hyperlinks", hpos)
|
|
88
132
|
h_end = h_end_term ? xml.index(">", h_end_term) : xml.size
|
|
89
133
|
h_end ||= xml.size
|
|
90
134
|
|
|
135
|
+
links = []
|
|
91
136
|
pos = hpos
|
|
92
137
|
while pos < h_end
|
|
93
138
|
hl_start_term = xml.index("hyperlink", pos)
|
|
@@ -101,20 +146,22 @@ module Xlsxrb
|
|
|
101
146
|
|
|
102
147
|
hl_tag = xml.byteslice(hl_start, hl_tag_end - hl_start)
|
|
103
148
|
ref = tag_attr(hl_tag, ' ref="')
|
|
104
|
-
rid = tag_attr(hl_tag, ' r:id="') || tag_attr(hl_tag, ' id="')
|
|
149
|
+
rid = tag_attr(hl_tag, ' r:id="') || tag_attr(hl_tag, ' id="') || hl_tag[/(?:[a-zA-Z0-9_]+:)?id="([^"]+)"/, 1]
|
|
105
150
|
display = tag_attr(hl_tag, ' display="')
|
|
106
151
|
tooltip = tag_attr(hl_tag, ' tooltip="')
|
|
107
152
|
location = tag_attr(hl_tag, ' location="')
|
|
108
153
|
|
|
109
154
|
if ref
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
155
|
+
entry = { ref: ref }
|
|
156
|
+
entry[:rid] = rid if rid
|
|
157
|
+
entry[:display] = display if display
|
|
158
|
+
entry[:tooltip] = tooltip if tooltip
|
|
159
|
+
entry[:location] = location if location
|
|
160
|
+
links << entry
|
|
115
161
|
end
|
|
116
162
|
pos = hl_tag_end + 1
|
|
117
163
|
end
|
|
164
|
+
links
|
|
118
165
|
end
|
|
119
166
|
|
|
120
167
|
# ---- Fast string-scanning event parser (byte-level) ----
|
|
@@ -152,13 +199,14 @@ module Xlsxrb
|
|
|
152
199
|
row_end_len = row_end_tag.bytesize
|
|
153
200
|
|
|
154
201
|
pos = sd_open_end + 1
|
|
202
|
+
last_row_index = -1
|
|
155
203
|
|
|
156
204
|
while pos < sd_end
|
|
157
205
|
row_start = xml.index(row_start_pattern, pos)
|
|
158
206
|
break unless row_start && row_start < sd_end
|
|
159
207
|
|
|
160
208
|
nb = xml.getbyte(row_start + row_start_len)
|
|
161
|
-
unless
|
|
209
|
+
unless TAG_DELIM_BYTES.include?(nb)
|
|
162
210
|
pos = row_start + row_start_len
|
|
163
211
|
next
|
|
164
212
|
end
|
|
@@ -173,9 +221,9 @@ module Xlsxrb
|
|
|
173
221
|
|
|
174
222
|
# Row index and attrs from tag substring (bounded search)
|
|
175
223
|
row_tag = xml.byteslice(row_start, tag_end - row_start)
|
|
176
|
-
row_index = 0
|
|
177
224
|
r_val = tag_attr(row_tag, ' r="')
|
|
178
|
-
row_index = r_val.to_i - 1
|
|
225
|
+
row_index = r_val ? r_val.to_i - 1 : last_row_index + 1
|
|
226
|
+
last_row_index = row_index
|
|
179
227
|
|
|
180
228
|
attrs = extract_row_attrs(row_tag)
|
|
181
229
|
|
|
@@ -212,22 +260,33 @@ module Xlsxrb
|
|
|
212
260
|
attrs[:height] = ht_val.to_f
|
|
213
261
|
end
|
|
214
262
|
|
|
215
|
-
if row_tag.include?('hidden="1"')
|
|
263
|
+
if row_tag.include?('hidden="1"') || row_tag.include?('hidden="true"')
|
|
216
264
|
attrs = {} if attrs.equal?(EMPTY_HASH)
|
|
217
265
|
attrs[:hidden] = true
|
|
218
266
|
end
|
|
219
267
|
|
|
220
|
-
if row_tag.include?('customHeight="1"')
|
|
268
|
+
if row_tag.include?('customHeight="1"') || row_tag.include?('customHeight="true"')
|
|
221
269
|
attrs = {} if attrs.equal?(EMPTY_HASH)
|
|
222
270
|
attrs[:custom_height] = true
|
|
223
271
|
end
|
|
224
272
|
|
|
273
|
+
if row_tag.include?('collapsed="1"') || row_tag.include?('collapsed="true"')
|
|
274
|
+
attrs = {} if attrs.equal?(EMPTY_HASH)
|
|
275
|
+
attrs[:collapsed] = true
|
|
276
|
+
end
|
|
277
|
+
|
|
225
278
|
ol_val = tag_attr(row_tag, ' outlineLevel="')
|
|
226
279
|
if ol_val
|
|
227
280
|
attrs = {} if attrs.equal?(EMPTY_HASH)
|
|
228
281
|
attrs[:outline_level] = ol_val.to_i
|
|
229
282
|
end
|
|
230
283
|
|
|
284
|
+
s_val = tag_attr(row_tag, ' s="')
|
|
285
|
+
if s_val
|
|
286
|
+
attrs = {} if attrs.equal?(EMPTY_HASH)
|
|
287
|
+
attrs[:style_index] = s_val.to_i
|
|
288
|
+
end
|
|
289
|
+
|
|
231
290
|
attrs
|
|
232
291
|
end
|
|
233
292
|
|
|
@@ -273,7 +332,7 @@ module Xlsxrb
|
|
|
273
332
|
break unless c_start && c_start < to
|
|
274
333
|
|
|
275
334
|
nb = xml.getbyte(c_start + c_start_len)
|
|
276
|
-
unless
|
|
335
|
+
unless TAG_DELIM_BYTES.include?(nb)
|
|
277
336
|
pos = c_start + c_start_len
|
|
278
337
|
next
|
|
279
338
|
end
|
|
@@ -396,8 +455,7 @@ module Xlsxrb
|
|
|
396
455
|
break unless t_end && t_end <= to
|
|
397
456
|
|
|
398
457
|
text_segment = xml.byteslice(t_tag_end + 1, t_close - t_tag_end - 1).force_encoding("UTF-8")
|
|
399
|
-
|
|
400
|
-
result << text_segment
|
|
458
|
+
result << normalize_text(text_segment)
|
|
401
459
|
|
|
402
460
|
pos = t_end + 1
|
|
403
461
|
end
|
|
@@ -413,8 +471,7 @@ module Xlsxrb
|
|
|
413
471
|
when "b"
|
|
414
472
|
raw == "1"
|
|
415
473
|
when "e", "str", "inlineStr"
|
|
416
|
-
|
|
417
|
-
val.include?("&") ? decode_xml_entities(val) : val
|
|
474
|
+
normalize_text(raw.force_encoding("UTF-8"))
|
|
418
475
|
else
|
|
419
476
|
return nil if raw.empty?
|
|
420
477
|
|
|
@@ -428,16 +485,19 @@ module Xlsxrb
|
|
|
428
485
|
|
|
429
486
|
private_class_method :resolve_fast_value
|
|
430
487
|
|
|
431
|
-
XML_ENTITIES = { "&" => "&", "<" => "<", ">" => ">", """ => '"', "'" => "'" }.freeze
|
|
432
|
-
|
|
433
488
|
# SECURITY NOTE: Decodes only standard predefined XML entities in a single pass.
|
|
434
489
|
# This completely prevents "Billion Laughs" attacks (exponential entity expansion)
|
|
435
490
|
# because it avoids recursive expansion and ignores custom entities entirely.
|
|
491
|
+
#: (String str) -> String
|
|
436
492
|
def self.decode_xml_entities(str)
|
|
437
|
-
|
|
493
|
+
XmlBuilder.unescape(str)
|
|
438
494
|
end
|
|
439
495
|
|
|
440
|
-
|
|
496
|
+
#: (String str) -> String
|
|
497
|
+
def self.normalize_text(str)
|
|
498
|
+
s = decode_xml_entities(str)
|
|
499
|
+
s.include?("\r") ? s.gsub("\r\n", "\n").tr("\r", "\n") : s
|
|
500
|
+
end
|
|
441
501
|
|
|
442
502
|
# Parses <cols> section for column definitions.
|
|
443
503
|
class ColumnsListener
|
|
@@ -463,7 +523,8 @@ module Xlsxrb
|
|
|
463
523
|
width: attrs["width"]&.to_f,
|
|
464
524
|
hidden: attrs["hidden"] == "1",
|
|
465
525
|
custom_width: attrs["customWidth"] == "1",
|
|
466
|
-
outline_level: attrs["outlineLevel"]&.to_i
|
|
526
|
+
outline_level: attrs["outlineLevel"]&.to_i,
|
|
527
|
+
style_index: attrs["style"]&.to_i
|
|
467
528
|
}
|
|
468
529
|
@columns << col
|
|
469
530
|
end
|
|
@@ -476,7 +537,9 @@ module Xlsxrb
|
|
|
476
537
|
def characters(_text); end
|
|
477
538
|
end
|
|
478
539
|
|
|
479
|
-
|
|
540
|
+
# rubocop:disable Style/OptionalBooleanParameter
|
|
541
|
+
def self.fast_scan_rows_direct(xml_src, shared_strings, _part_name, styles = nil, date1904 = false, pad_empty_cells = false, &block)
|
|
542
|
+
# rubocop:enable Style/OptionalBooleanParameter
|
|
480
543
|
xml = xml_src.b # force ASCII-8BIT for O(1) byte indexing
|
|
481
544
|
|
|
482
545
|
sd_term = xml.index("sheetData")
|
|
@@ -501,13 +564,14 @@ module Xlsxrb
|
|
|
501
564
|
row_end_len = row_end_tag.bytesize
|
|
502
565
|
|
|
503
566
|
pos = sd_open_end + 1
|
|
567
|
+
last_row_index = -1
|
|
504
568
|
|
|
505
569
|
while pos < sd_end
|
|
506
570
|
row_start = xml.index(row_start_pattern, pos)
|
|
507
571
|
break unless row_start && row_start < sd_end
|
|
508
572
|
|
|
509
573
|
nb = xml.getbyte(row_start + row_start_len)
|
|
510
|
-
unless
|
|
574
|
+
unless TAG_DELIM_BYTES.include?(nb)
|
|
511
575
|
pos = row_start + row_start_len
|
|
512
576
|
next
|
|
513
577
|
end
|
|
@@ -520,23 +584,23 @@ module Xlsxrb
|
|
|
520
584
|
next
|
|
521
585
|
end
|
|
522
586
|
|
|
523
|
-
row_index =
|
|
587
|
+
row_index = nil
|
|
524
588
|
has_custom_attrs = false
|
|
525
589
|
ri = row_start + row_start_len
|
|
526
590
|
while ri < tag_end
|
|
527
591
|
rb = xml.getbyte(ri)
|
|
528
592
|
if rb == 114 && xml.getbyte(ri + 1) == 61 && xml.getbyte(ri + 2) == 34 # r="
|
|
529
593
|
ri += 3
|
|
594
|
+
parsed_r = 0
|
|
530
595
|
while ri < tag_end
|
|
531
596
|
cb = xml.getbyte(ri)
|
|
532
597
|
break unless cb.between?(48, 57)
|
|
533
598
|
|
|
534
|
-
|
|
599
|
+
parsed_r = (parsed_r * 10) + (cb - 48)
|
|
535
600
|
ri += 1
|
|
536
|
-
|
|
537
601
|
end
|
|
538
|
-
row_index
|
|
539
|
-
elsif
|
|
602
|
+
row_index = parsed_r - 1
|
|
603
|
+
elsif ROW_ATTR_START_BYTES.include?(rb) # 'h', 'c', 'o', 's' for ht, customHeight, hidden, outlineLevel, s
|
|
540
604
|
has_custom_attrs = true
|
|
541
605
|
ri += 1
|
|
542
606
|
else
|
|
@@ -544,6 +608,9 @@ module Xlsxrb
|
|
|
544
608
|
end
|
|
545
609
|
end
|
|
546
610
|
|
|
611
|
+
row_index ||= last_row_index + 1
|
|
612
|
+
last_row_index = row_index
|
|
613
|
+
|
|
547
614
|
attrs = if has_custom_attrs
|
|
548
615
|
row_tag = xml.byteslice(row_start, tag_end - row_start)
|
|
549
616
|
extract_row_attrs(row_tag)
|
|
@@ -564,7 +631,12 @@ module Xlsxrb
|
|
|
564
631
|
attrs[:height],
|
|
565
632
|
attrs[:hidden] || false,
|
|
566
633
|
attrs[:custom_height] || false,
|
|
567
|
-
attrs[:outline_level]
|
|
634
|
+
attrs[:outline_level],
|
|
635
|
+
attrs[:style_index],
|
|
636
|
+
styles,
|
|
637
|
+
date1904,
|
|
638
|
+
attrs[:collapsed] || false,
|
|
639
|
+
pad_empty_cells
|
|
568
640
|
)
|
|
569
641
|
block.call(row_obj)
|
|
570
642
|
|
|
@@ -574,6 +646,191 @@ module Xlsxrb
|
|
|
574
646
|
|
|
575
647
|
private_class_method :fast_scan_rows_direct
|
|
576
648
|
|
|
649
|
+
# rubocop:disable Style/OptionalBooleanParameter
|
|
650
|
+
def self.scan_rows_stream(source, shared_strings, _part_name, styles = nil, date1904 = false, pad_empty_cells = false, &block)
|
|
651
|
+
# rubocop:enable Style/OptionalBooleanParameter
|
|
652
|
+
buffer = +""
|
|
653
|
+
buffer.force_encoding(Encoding::BINARY)
|
|
654
|
+
in_sheet_data = false
|
|
655
|
+
prefix = ""
|
|
656
|
+
row_start_pattern = "<row"
|
|
657
|
+
row_start_len = 4
|
|
658
|
+
row_end_tag = "</row>"
|
|
659
|
+
row_end_len = 6
|
|
660
|
+
sd_end_tag = "</sheetData>"
|
|
661
|
+
last_row_index = -1
|
|
662
|
+
|
|
663
|
+
push_chunk = lambda do |chunk|
|
|
664
|
+
return if in_sheet_data == :closed
|
|
665
|
+
return if chunk.nil? || chunk.empty?
|
|
666
|
+
|
|
667
|
+
buffer << chunk.b
|
|
668
|
+
|
|
669
|
+
unless in_sheet_data
|
|
670
|
+
sd_term = buffer.index("sheetData")
|
|
671
|
+
if sd_term.nil?
|
|
672
|
+
buffer.slice!(0...(buffer.bytesize - 32)) if buffer.bytesize > 64
|
|
673
|
+
return
|
|
674
|
+
end
|
|
675
|
+
|
|
676
|
+
sd_start = buffer.rindex("<", sd_term)
|
|
677
|
+
if sd_start.nil?
|
|
678
|
+
buffer.slice!(0...sd_term)
|
|
679
|
+
return
|
|
680
|
+
end
|
|
681
|
+
|
|
682
|
+
sd_open_end = buffer.index(">", sd_term)
|
|
683
|
+
return if sd_open_end.nil?
|
|
684
|
+
|
|
685
|
+
if buffer.getbyte(sd_open_end - 1) == 47
|
|
686
|
+
in_sheet_data = :closed
|
|
687
|
+
buffer.clear
|
|
688
|
+
return
|
|
689
|
+
end
|
|
690
|
+
|
|
691
|
+
prefix = buffer.byteslice(sd_start + 1, sd_term - (sd_start + 1))
|
|
692
|
+
row_start_pattern = "<#{prefix}row"
|
|
693
|
+
row_start_len = row_start_pattern.bytesize
|
|
694
|
+
row_end_tag = "</#{prefix}row>"
|
|
695
|
+
row_end_len = row_end_tag.bytesize
|
|
696
|
+
sd_end_tag = "</#{prefix}sheetData>"
|
|
697
|
+
|
|
698
|
+
buffer.slice!(0..sd_open_end)
|
|
699
|
+
in_sheet_data = true
|
|
700
|
+
end
|
|
701
|
+
|
|
702
|
+
pos = 0
|
|
703
|
+
last_consumed_pos = 0
|
|
704
|
+
sd_end_pos = buffer.index(sd_end_tag)
|
|
705
|
+
|
|
706
|
+
loop do
|
|
707
|
+
row_start = buffer.index(row_start_pattern, pos)
|
|
708
|
+
|
|
709
|
+
if sd_end_pos && (row_start.nil? || sd_end_pos < row_start)
|
|
710
|
+
in_sheet_data = :closed
|
|
711
|
+
buffer.clear
|
|
712
|
+
return
|
|
713
|
+
end
|
|
714
|
+
|
|
715
|
+
break if row_start.nil?
|
|
716
|
+
|
|
717
|
+
next_pos = row_start + row_start_len
|
|
718
|
+
break if next_pos >= buffer.bytesize
|
|
719
|
+
|
|
720
|
+
nb = buffer.getbyte(next_pos)
|
|
721
|
+
unless TAG_DELIM_BYTES.include?(nb)
|
|
722
|
+
pos = next_pos
|
|
723
|
+
next
|
|
724
|
+
end
|
|
725
|
+
|
|
726
|
+
tag_end = buffer.index(">", next_pos)
|
|
727
|
+
break if tag_end.nil?
|
|
728
|
+
|
|
729
|
+
if buffer.getbyte(tag_end - 1) == 47
|
|
730
|
+
pos = tag_end + 1
|
|
731
|
+
last_consumed_pos = pos
|
|
732
|
+
next
|
|
733
|
+
end
|
|
734
|
+
|
|
735
|
+
row_end = buffer.index(row_end_tag, tag_end + 1)
|
|
736
|
+
if row_end.nil?
|
|
737
|
+
raise ArgumentError, "Malformed XML: row size exceeds maximum buffer limit" if buffer.bytesize > 50 * 1024 * 1024
|
|
738
|
+
|
|
739
|
+
break
|
|
740
|
+
end
|
|
741
|
+
|
|
742
|
+
row_index = nil
|
|
743
|
+
has_custom_attrs = false
|
|
744
|
+
ri = row_start + row_start_len
|
|
745
|
+
while ri < tag_end
|
|
746
|
+
rb = buffer.getbyte(ri)
|
|
747
|
+
if rb == 114 && buffer.getbyte(ri + 1) == 61 && buffer.getbyte(ri + 2) == 34 # r="
|
|
748
|
+
ri += 3
|
|
749
|
+
parsed_r = 0
|
|
750
|
+
while ri < tag_end
|
|
751
|
+
cb = buffer.getbyte(ri)
|
|
752
|
+
break unless cb.between?(48, 57)
|
|
753
|
+
|
|
754
|
+
parsed_r = (parsed_r * 10) + (cb - 48)
|
|
755
|
+
ri += 1
|
|
756
|
+
end
|
|
757
|
+
row_index = parsed_r - 1
|
|
758
|
+
elsif ROW_ATTR_START_BYTES.include?(rb)
|
|
759
|
+
has_custom_attrs = true
|
|
760
|
+
ri += 1
|
|
761
|
+
else
|
|
762
|
+
ri += 1
|
|
763
|
+
end
|
|
764
|
+
end
|
|
765
|
+
|
|
766
|
+
row_index ||= last_row_index + 1
|
|
767
|
+
last_row_index = row_index
|
|
768
|
+
|
|
769
|
+
attrs = if has_custom_attrs
|
|
770
|
+
row_tag = buffer.byteslice(row_start, tag_end - row_start)
|
|
771
|
+
extract_row_attrs(row_tag)
|
|
772
|
+
else
|
|
773
|
+
EMPTY_HASH
|
|
774
|
+
end
|
|
775
|
+
|
|
776
|
+
cells_xml = buffer.byteslice(tag_end + 1, row_end - (tag_end + 1))
|
|
777
|
+
row_obj = StreamRow.fast_create(
|
|
778
|
+
row_index,
|
|
779
|
+
cells_xml,
|
|
780
|
+
0,
|
|
781
|
+
cells_xml.bytesize,
|
|
782
|
+
shared_strings,
|
|
783
|
+
prefix,
|
|
784
|
+
attrs[:height],
|
|
785
|
+
attrs[:hidden] || false,
|
|
786
|
+
attrs[:custom_height] || false,
|
|
787
|
+
attrs[:outline_level],
|
|
788
|
+
attrs[:style_index],
|
|
789
|
+
styles,
|
|
790
|
+
date1904,
|
|
791
|
+
attrs[:collapsed] || false,
|
|
792
|
+
pad_empty_cells
|
|
793
|
+
)
|
|
794
|
+
block.call(row_obj)
|
|
795
|
+
|
|
796
|
+
pos = row_end + row_end_len
|
|
797
|
+
last_consumed_pos = pos
|
|
798
|
+
end
|
|
799
|
+
|
|
800
|
+
buffer.slice!(0...last_consumed_pos) if last_consumed_pos.positive?
|
|
801
|
+
end
|
|
802
|
+
|
|
803
|
+
catch(:done) do
|
|
804
|
+
if source.respond_to?(:each_chunk)
|
|
805
|
+
source.each_chunk(65_536) do |chunk|
|
|
806
|
+
push_chunk.call(chunk)
|
|
807
|
+
throw :done if in_sheet_data == :closed
|
|
808
|
+
end
|
|
809
|
+
elsif source.respond_to?(:read)
|
|
810
|
+
while (chunk = source.read(65_536))
|
|
811
|
+
break if chunk.empty?
|
|
812
|
+
|
|
813
|
+
push_chunk.call(chunk)
|
|
814
|
+
break if in_sheet_data == :closed
|
|
815
|
+
end
|
|
816
|
+
elsif source.respond_to?(:call)
|
|
817
|
+
source.call do |chunk|
|
|
818
|
+
push_chunk.call(chunk)
|
|
819
|
+
throw :done if in_sheet_data == :closed
|
|
820
|
+
end
|
|
821
|
+
elsif source.respond_to?(:each)
|
|
822
|
+
source.each do |chunk|
|
|
823
|
+
push_chunk.call(chunk)
|
|
824
|
+
break if in_sheet_data == :closed
|
|
825
|
+
end
|
|
826
|
+
else
|
|
827
|
+
raise ArgumentError, "Unsupported stream source: #{source.class}"
|
|
828
|
+
end
|
|
829
|
+
end
|
|
830
|
+
end
|
|
831
|
+
|
|
832
|
+
private_class_method :scan_rows_stream
|
|
833
|
+
|
|
577
834
|
COL_LETTERS = (0...16_384).map do |index|
|
|
578
835
|
result = +""
|
|
579
836
|
i = index
|
|
@@ -589,8 +846,8 @@ module Xlsxrb
|
|
|
589
846
|
[COL_LETTERS[idx], idx]
|
|
590
847
|
end.freeze
|
|
591
848
|
|
|
592
|
-
CELL_FAST_RE = %r{<(?:[a-zA-Z0-9_]+:)?c\s+r="([A-Za-z0-9]+)"(?:[^>]*?s="(\d+)")?(?:[^>]*?t="([a-zA-Z]+)")?[^>]
|
|
593
|
-
CELL_GENERIC_RE = %r{<(?:[a-zA-Z0-9_]+:)?c\b([^>]*?)(
|
|
849
|
+
CELL_FAST_RE = %r{<(?:[a-zA-Z0-9_]+:)?c\s+r="([A-Za-z0-9]+)"(?:[^>]*?s="(\d+)")?(?:[^>]*?t="([a-zA-Z]+)")?[^>]*>\s*(?:<(?:[a-zA-Z0-9_]+:)?f\b[^>]*?(?:/>|>([^<]*)</(?:[a-zA-Z0-9_]+:)?f>)\s*)?(?:<(?:[a-zA-Z0-9_]+:)?v>([^<]*)</(?:[a-zA-Z0-9_]+:)?v>\s*)?(?:<(?:[a-zA-Z0-9_]+:)?is>(.*?)</(?:[a-zA-Z0-9_]+:)?is>\s*)?.*?</(?:[a-zA-Z0-9_]+:)?c>|<(?:[a-zA-Z0-9_]+:)?c\s+r="([A-Za-z0-9]+)"(?:[^>]*?s="(\d+)")?[^>]*/>}m
|
|
850
|
+
CELL_GENERIC_RE = %r{<(?:[a-zA-Z0-9_]+:)?c\b([^>]*?)(?:>\s*(?:<(?:[a-zA-Z0-9_]+:)?f\b[^>]*?(?:/>|>([^<]*)</(?:[a-zA-Z0-9_]+:)?f>)\s*)?(?:<(?:[a-zA-Z0-9_]+:)?v>([^<]*)</(?:[a-zA-Z0-9_]+:)?v>\s*)?(?:<(?:[a-zA-Z0-9_]+:)?is>(.*?)</(?:[a-zA-Z0-9_]+:)?is>\s*)?.*?</(?:[a-zA-Z0-9_]+:)?c>|/>)}m
|
|
594
851
|
ATTR_R = /r="([A-Za-z0-9]+)"/
|
|
595
852
|
ATTR_T = /t="([a-zA-Z]+)"/
|
|
596
853
|
ATTR_S = /s="(\d+)"/
|
|
@@ -601,7 +858,9 @@ module Xlsxrb
|
|
|
601
858
|
cells
|
|
602
859
|
end
|
|
603
860
|
|
|
604
|
-
|
|
861
|
+
# rubocop:disable Style/OptionalBooleanParameter
|
|
862
|
+
def self.fast_scan_cells_direct(xml, from, to, shared_strings, row_source, _prefix = "", styles = nil, date1904 = false, &block)
|
|
863
|
+
# rubocop:enable Style/OptionalBooleanParameter
|
|
605
864
|
chunk = xml.byteslice(from, to - from)
|
|
606
865
|
row_idx = row_source.is_a?(Hash) ? row_source[:row] : row_source
|
|
607
866
|
col_idx = 0
|
|
@@ -626,6 +885,8 @@ module Xlsxrb
|
|
|
626
885
|
col_idx
|
|
627
886
|
end
|
|
628
887
|
col_idx = c_idx + 1
|
|
888
|
+
v = v.strip if v
|
|
889
|
+
f = f.strip if f
|
|
629
890
|
|
|
630
891
|
val = if t == "s"
|
|
631
892
|
shared_strings[v.to_i] || ""
|
|
@@ -635,7 +896,7 @@ module Xlsxrb
|
|
|
635
896
|
if is
|
|
636
897
|
extract_inline_text(is, 0, is.bytesize)
|
|
637
898
|
elsif v
|
|
638
|
-
|
|
899
|
+
normalize_text(v)
|
|
639
900
|
else
|
|
640
901
|
""
|
|
641
902
|
end
|
|
@@ -643,8 +904,16 @@ module Xlsxrb
|
|
|
643
904
|
v.include?(".") ? v.to_f : v.to_i
|
|
644
905
|
end
|
|
645
906
|
|
|
646
|
-
|
|
647
|
-
|
|
907
|
+
raw_val = if is
|
|
908
|
+
extract_inline_text(is, 0, is.bytesize)
|
|
909
|
+
elsif v
|
|
910
|
+
normalize_text(v)
|
|
911
|
+
end
|
|
912
|
+
|
|
913
|
+
formula_expr = f ? normalize_text(f) : nil
|
|
914
|
+
formula_expr = nil if formula_expr && formula_expr.empty?
|
|
915
|
+
fmt_code = NumberFormatter.format_code_for(style_idx, styles) if styles && style_idx
|
|
916
|
+
cell = Elements::Cell.fast_create(row_idx, c_idx, val, style_idx, formula_expr, raw_val, fmt_code, date1904)
|
|
648
917
|
block.call(cell)
|
|
649
918
|
end
|
|
650
919
|
|
|
@@ -670,6 +939,8 @@ module Xlsxrb
|
|
|
670
939
|
col_idx
|
|
671
940
|
end
|
|
672
941
|
col_idx = c_idx + 1
|
|
942
|
+
v = v.strip if v
|
|
943
|
+
f = f.strip if f
|
|
673
944
|
|
|
674
945
|
val = if t == "s"
|
|
675
946
|
shared_strings[v.to_i] || ""
|
|
@@ -679,7 +950,7 @@ module Xlsxrb
|
|
|
679
950
|
if is
|
|
680
951
|
extract_inline_text(is, 0, is.bytesize)
|
|
681
952
|
elsif v
|
|
682
|
-
|
|
953
|
+
normalize_text(v)
|
|
683
954
|
else
|
|
684
955
|
""
|
|
685
956
|
end
|
|
@@ -687,13 +958,145 @@ module Xlsxrb
|
|
|
687
958
|
v.include?(".") ? v.to_f : v.to_i
|
|
688
959
|
end
|
|
689
960
|
|
|
961
|
+
raw_val = if is
|
|
962
|
+
extract_inline_text(is, 0, is.bytesize)
|
|
963
|
+
elsif v
|
|
964
|
+
normalize_text(v)
|
|
965
|
+
end
|
|
966
|
+
|
|
690
967
|
style_idx = s&.to_i
|
|
691
|
-
formula_expr = f
|
|
692
|
-
|
|
968
|
+
formula_expr = f ? normalize_text(f) : nil
|
|
969
|
+
formula_expr = nil if formula_expr && formula_expr.empty?
|
|
970
|
+
fmt_code = NumberFormatter.format_code_for(style_idx, styles) if styles && style_idx
|
|
971
|
+
cell = Elements::Cell.fast_create(row_idx, c_idx, val, style_idx, formula_expr, raw_val, fmt_code, date1904)
|
|
693
972
|
block.call(cell)
|
|
694
973
|
end
|
|
695
974
|
end
|
|
696
975
|
|
|
976
|
+
# rubocop:disable Style/OptionalBooleanParameter
|
|
977
|
+
#: (String xml, Integer from, Integer to, Array[String] shared_strings, ?untyped styles, ?bool date1904, ?type_cast: bool) -> Array[untyped]
|
|
978
|
+
def self.fast_scan_row_values(xml, from, to, shared_strings, styles = nil, date1904 = false, type_cast: false)
|
|
979
|
+
# rubocop:enable Style/OptionalBooleanParameter
|
|
980
|
+
chunk = xml.byteslice(from, to - from)
|
|
981
|
+
return [] if chunk.nil? || chunk.empty?
|
|
982
|
+
|
|
983
|
+
col_idx = 0
|
|
984
|
+
matched_any = false
|
|
985
|
+
row_values = []
|
|
986
|
+
|
|
987
|
+
chunk.scan(CELL_FAST_RE) do |r, s, t, _f, v, is, self_r, self_s|
|
|
988
|
+
matched_any = true
|
|
989
|
+
ref = r || self_r
|
|
990
|
+
s_val = s || self_s
|
|
991
|
+
c_idx = if ref
|
|
992
|
+
expected_letter = COL_LETTERS[col_idx]
|
|
993
|
+
if expected_letter && ref.start_with?(expected_letter)
|
|
994
|
+
col_idx
|
|
995
|
+
else
|
|
996
|
+
c_len = 0
|
|
997
|
+
c_len += 1 while (b = ref.getbyte(c_len)) && (b.between?(65, 90) || b.between?(97, 122))
|
|
998
|
+
col_letters = ref.byteslice(0, c_len).upcase
|
|
999
|
+
COL_MAP[col_letters] || col_idx
|
|
1000
|
+
end
|
|
1001
|
+
else
|
|
1002
|
+
col_idx
|
|
1003
|
+
end
|
|
1004
|
+
col_idx = c_idx + 1
|
|
1005
|
+
v = v.strip if v
|
|
1006
|
+
|
|
1007
|
+
val = if t == "s"
|
|
1008
|
+
shared_strings[v.to_i] || ""
|
|
1009
|
+
elsif t == "b"
|
|
1010
|
+
v == "1"
|
|
1011
|
+
elsif %w[inlineStr str e].include?(t)
|
|
1012
|
+
if is
|
|
1013
|
+
extract_inline_text(is, 0, is.bytesize)
|
|
1014
|
+
elsif v
|
|
1015
|
+
normalize_text(v)
|
|
1016
|
+
else
|
|
1017
|
+
""
|
|
1018
|
+
end
|
|
1019
|
+
elsif v
|
|
1020
|
+
v.include?(".") ? v.to_f : v.to_i
|
|
1021
|
+
end
|
|
1022
|
+
|
|
1023
|
+
if type_cast && val.is_a?(Numeric) && s_val && styles
|
|
1024
|
+
fmt_type = NumberFormatter.format_type_for(s_val.to_i, styles)
|
|
1025
|
+
begin
|
|
1026
|
+
case fmt_type
|
|
1027
|
+
when :date
|
|
1028
|
+
val = Utils.serial_to_date(val, date1904: date1904)
|
|
1029
|
+
when :datetime, :time
|
|
1030
|
+
val = Utils.serial_to_datetime(val, date1904: date1904)
|
|
1031
|
+
end
|
|
1032
|
+
rescue StandardError
|
|
1033
|
+
# Keep numeric value on conversion failure
|
|
1034
|
+
end
|
|
1035
|
+
end
|
|
1036
|
+
|
|
1037
|
+
row_values[c_idx] = val
|
|
1038
|
+
end
|
|
1039
|
+
|
|
1040
|
+
return row_values if matched_any || (!chunk.include?("<c") && !chunk.include?(":c"))
|
|
1041
|
+
|
|
1042
|
+
# Fallback for non-standard attribute ordering
|
|
1043
|
+
chunk.scan(CELL_GENERIC_RE) do |attrs, _f, v, is|
|
|
1044
|
+
r = attrs[ATTR_R, 1]
|
|
1045
|
+
t = attrs[ATTR_T, 1]
|
|
1046
|
+
s = attrs[ATTR_S, 1]
|
|
1047
|
+
|
|
1048
|
+
c_idx = if r
|
|
1049
|
+
expected_letter = COL_LETTERS[col_idx]
|
|
1050
|
+
if expected_letter && r.start_with?(expected_letter)
|
|
1051
|
+
col_idx
|
|
1052
|
+
else
|
|
1053
|
+
c_len = 0
|
|
1054
|
+
c_len += 1 while (b = r.getbyte(c_len)) && (b.between?(65, 90) || b.between?(97, 122))
|
|
1055
|
+
col_letters = r.byteslice(0, c_len).upcase
|
|
1056
|
+
COL_MAP[col_letters] || col_idx
|
|
1057
|
+
end
|
|
1058
|
+
else
|
|
1059
|
+
col_idx
|
|
1060
|
+
end
|
|
1061
|
+
col_idx = c_idx + 1
|
|
1062
|
+
v = v.strip if v
|
|
1063
|
+
|
|
1064
|
+
val = if t == "s"
|
|
1065
|
+
shared_strings[v.to_i] || ""
|
|
1066
|
+
elsif t == "b"
|
|
1067
|
+
v == "1"
|
|
1068
|
+
elsif %w[inlineStr str e].include?(t)
|
|
1069
|
+
if is
|
|
1070
|
+
extract_inline_text(is, 0, is.bytesize)
|
|
1071
|
+
elsif v
|
|
1072
|
+
normalize_text(v)
|
|
1073
|
+
else
|
|
1074
|
+
""
|
|
1075
|
+
end
|
|
1076
|
+
elsif v
|
|
1077
|
+
v.include?(".") ? v.to_f : v.to_i
|
|
1078
|
+
end
|
|
1079
|
+
|
|
1080
|
+
if type_cast && val.is_a?(Numeric) && s && styles
|
|
1081
|
+
fmt_type = NumberFormatter.format_type_for(s.to_i, styles)
|
|
1082
|
+
begin
|
|
1083
|
+
case fmt_type
|
|
1084
|
+
when :date
|
|
1085
|
+
val = Utils.serial_to_date(val, date1904: date1904)
|
|
1086
|
+
when :datetime, :time
|
|
1087
|
+
val = Utils.serial_to_datetime(val, date1904: date1904)
|
|
1088
|
+
end
|
|
1089
|
+
rescue StandardError
|
|
1090
|
+
# Keep numeric value on conversion failure
|
|
1091
|
+
end
|
|
1092
|
+
end
|
|
1093
|
+
|
|
1094
|
+
row_values[c_idx] = val
|
|
1095
|
+
end
|
|
1096
|
+
|
|
1097
|
+
row_values
|
|
1098
|
+
end
|
|
1099
|
+
|
|
697
1100
|
private_class_method :fast_parse_cells_direct
|
|
698
1101
|
end
|
|
699
1102
|
end
|