xlsxrb 0.1.11 → 0.1.13
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 +86 -34
- data/README.md +26 -21
- data/lib/xlsxrb/chart_builder.rb +18 -18
- data/lib/xlsxrb/dsl_helpers.rb +175 -0
- data/lib/xlsxrb/elements/cell.rb +56 -22
- data/lib/xlsxrb/elements/column.rb +27 -3
- data/lib/xlsxrb/elements/row.rb +27 -8
- data/lib/xlsxrb/elements/workbook.rb +27 -14
- data/lib/xlsxrb/elements/worksheet.rb +17 -1
- data/lib/xlsxrb/ooxml/cfb.rb +9 -6
- data/lib/xlsxrb/ooxml/crypto/agile.rb +5 -5
- data/lib/xlsxrb/ooxml/reader/listeners/core_listeners.rb +784 -0
- data/lib/xlsxrb/ooxml/reader/listeners/drawing_listeners.rb +3478 -0
- data/lib/xlsxrb/ooxml/reader/listeners/feature_listeners.rb +1796 -0
- data/lib/xlsxrb/ooxml/reader/listeners/style_listeners.rb +428 -0
- data/lib/xlsxrb/ooxml/reader/listeners.rb +4 -6446
- data/lib/xlsxrb/ooxml/shared_strings_parser.rb +3 -5
- data/lib/xlsxrb/ooxml/utils.rb +5 -6
- data/lib/xlsxrb/ooxml/workbook_writer.rb +5 -7
- data/lib/xlsxrb/ooxml/worksheet_parser.rb +172 -9
- data/lib/xlsxrb/ooxml/writer/drawing_xml.rb +1602 -0
- data/lib/xlsxrb/ooxml/writer/features_xml.rb +731 -0
- data/lib/xlsxrb/ooxml/writer/styles_xml.rb +348 -0
- data/lib/xlsxrb/ooxml/writer.rb +9 -2646
- data/lib/xlsxrb/ooxml/xml_builder.rb +31 -2
- data/lib/xlsxrb/ooxml/zip_generator.rb +26 -5
- data/lib/xlsxrb/ooxml/zip_reader.rb +364 -78
- data/lib/xlsxrb/ooxml/zip_writer.rb +32 -5
- data/lib/xlsxrb/stream_sheet.rb +94 -6
- data/lib/xlsxrb/stream_writer.rb +48 -115
- data/lib/xlsxrb/style_builder.rb +10 -1
- data/lib/xlsxrb/version.rb +1 -1
- data/lib/xlsxrb/workbook_builder.rb +1 -1
- data/lib/xlsxrb/worksheet_builder.rb +19 -81
- data/lib/xlsxrb.rb +449 -171
- data/sig/generated/xlsxrb/chart_builder.rbs +36 -36
- data/sig/generated/xlsxrb/dsl_helpers.rbs +92 -0
- data/sig/generated/xlsxrb/elements/cell.rbs +29 -0
- data/sig/generated/xlsxrb/elements/column.rbs +32 -21
- data/sig/generated/xlsxrb/elements/row.rbs +94 -14
- data/sig/generated/xlsxrb/elements/workbook.rbs +102 -10
- data/sig/generated/xlsxrb/elements/worksheet.rbs +9 -0
- data/sig/generated/xlsxrb/ooxml/reader/listeners/core_listeners.rbs +173 -0
- data/sig/generated/xlsxrb/ooxml/reader/listeners/drawing_listeners.rbs +400 -0
- data/sig/generated/xlsxrb/ooxml/reader/listeners/feature_listeners.rbs +442 -0
- data/sig/generated/xlsxrb/ooxml/reader/listeners/style_listeners.rbs +80 -0
- data/sig/generated/xlsxrb/ooxml/reader/listeners.rbs +0 -1072
- data/sig/generated/xlsxrb/ooxml/shared_strings_parser.rbs +0 -2
- data/sig/generated/xlsxrb/ooxml/utils.rbs +2 -0
- data/sig/generated/xlsxrb/ooxml/worksheet_parser.rbs +5 -4
- data/sig/generated/xlsxrb/ooxml/writer/drawing_xml.rbs +79 -0
- data/sig/generated/xlsxrb/ooxml/writer/features_xml.rbs +83 -0
- data/sig/generated/xlsxrb/ooxml/writer/styles_xml.rbs +53 -0
- data/sig/generated/xlsxrb/ooxml/writer.rbs +6 -185
- data/sig/generated/xlsxrb/ooxml/xml_builder.rbs +18 -0
- data/sig/generated/xlsxrb/ooxml/zip_generator.rbs +15 -0
- data/sig/generated/xlsxrb/ooxml/zip_reader.rbs +96 -8
- data/sig/generated/xlsxrb/ooxml/zip_writer.rbs +5 -1
- data/sig/generated/xlsxrb/stream_sheet.rbs +43 -3
- data/sig/generated/xlsxrb/style_builder.rbs +7 -0
- data/sig/generated/xlsxrb.rbs +18 -11
- metadata +22 -144
- 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/Rakefile +0 -567
- data/Steepfile +0 -18
- data/benchmark.rb +0 -406
- data/docs/ARCHITECTURE.md +0 -514
- data/docs/DEVELOPMENT.md +0 -133
- data/docs/PEER_LIBRARIES.md +0 -121
- data/docs/QUALITY_ASSURANCE.md +0 -25
- 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 -4672
- 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,14 +3,13 @@
|
|
|
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
|
|
9
10
|
# SAX-based parser for xl/sharedStrings.xml.
|
|
10
11
|
# Returns an Array of strings (index = SST index).
|
|
11
12
|
class SharedStringsParser
|
|
12
|
-
XML_ENTITIES = { "&" => "&", "<" => "<", ">" => ">", """ => '"', "'" => "'" }.freeze
|
|
13
|
-
|
|
14
13
|
# Parses all shared strings and returns an Array of strings.
|
|
15
14
|
def self.parse(xml_string, _part_name: "xl/sharedStrings.xml")
|
|
16
15
|
return [] if xml_string.nil? || xml_string.empty?
|
|
@@ -89,7 +88,7 @@ module Xlsxrb
|
|
|
89
88
|
str = extract_multi_t(xml, si_open_end + 1, si_end)
|
|
90
89
|
else
|
|
91
90
|
raw_str = xml.byteslice(t_open_end + 1, t_end - t_open_end - 1).force_encoding("UTF-8")
|
|
92
|
-
str =
|
|
91
|
+
str = XmlBuilder.unescape(raw_str)
|
|
93
92
|
end
|
|
94
93
|
else
|
|
95
94
|
str = ""
|
|
@@ -128,8 +127,7 @@ module Xlsxrb
|
|
|
128
127
|
break unless t_end && t_end <= to
|
|
129
128
|
|
|
130
129
|
raw_chunk = xml.byteslice(t_open_end + 1, t_end - t_open_end - 1).force_encoding("UTF-8")
|
|
131
|
-
|
|
132
|
-
buf << raw_chunk
|
|
130
|
+
buf << XmlBuilder.unescape(raw_chunk)
|
|
133
131
|
pos = t_end + 4
|
|
134
132
|
end
|
|
135
133
|
buf
|
data/lib/xlsxrb/ooxml/utils.rb
CHANGED
|
@@ -12,6 +12,7 @@ module Xlsxrb
|
|
|
12
12
|
module Utils
|
|
13
13
|
# Excel 1900 date system epoch.
|
|
14
14
|
EPOCH_1900 = Date.new(1899, 12, 31) # serial 1 = Jan 1, 1900
|
|
15
|
+
EPOCH_1900_JD = EPOCH_1900.jd
|
|
15
16
|
|
|
16
17
|
# Built-in number format codes defined by SpreadsheetML.
|
|
17
18
|
BUILTIN_NUM_FMT_CODES = {
|
|
@@ -57,7 +58,7 @@ module Xlsxrb
|
|
|
57
58
|
class << self
|
|
58
59
|
# Converts a Date to an Excel serial number (1900 system).
|
|
59
60
|
def date_to_serial(date)
|
|
60
|
-
serial =
|
|
61
|
+
serial = date.jd - EPOCH_1900_JD
|
|
61
62
|
# Lotus 1-2-3 bug: serial 60 = Feb 29, 1900 (doesn't exist).
|
|
62
63
|
# Dates on or after Mar 1, 1900 (raw serial >= 60) need +1.
|
|
63
64
|
serial += 1 if serial >= 60
|
|
@@ -82,13 +83,11 @@ module Xlsxrb
|
|
|
82
83
|
|
|
83
84
|
# Converts a fractional Excel serial number to a Time (1900 system, UTC).
|
|
84
85
|
def serial_to_datetime(serial)
|
|
85
|
-
int_part = serial.
|
|
86
|
-
frac = serial - int_part
|
|
86
|
+
int_part, frac = serial.divmod(1)
|
|
87
87
|
date = serial_to_date(int_part)
|
|
88
88
|
total_seconds = (frac * 86_400).round
|
|
89
|
-
hours = total_seconds
|
|
90
|
-
minutes = (
|
|
91
|
-
seconds = total_seconds % 60
|
|
89
|
+
hours, remaining = total_seconds.divmod(3600)
|
|
90
|
+
minutes, seconds = remaining.divmod(60)
|
|
92
91
|
Time.utc(date.year, date.month, date.day, hours, minutes, seconds)
|
|
93
92
|
end
|
|
94
93
|
|
|
@@ -33,13 +33,11 @@ module Xlsxrb
|
|
|
33
33
|
def self.write(target, sheets:, shared_strings: [], shared_strings_index: nil, styles: nil,
|
|
34
34
|
defined_names: nil, core_properties: nil, app_properties: nil,
|
|
35
35
|
custom_properties: nil, workbook_protection: nil, workbook_properties: nil)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
writer.write_to(target)
|
|
42
|
-
end
|
|
36
|
+
writer = new(sheets: sheets, shared_strings: shared_strings, shared_strings_index: shared_strings_index, styles: styles,
|
|
37
|
+
defined_names: defined_names, core_properties: core_properties,
|
|
38
|
+
app_properties: app_properties, custom_properties: custom_properties,
|
|
39
|
+
workbook_protection: workbook_protection, workbook_properties: workbook_properties)
|
|
40
|
+
writer.write_to(target)
|
|
43
41
|
end
|
|
44
42
|
|
|
45
43
|
def initialize(sheets:, shared_strings: [], shared_strings_index: nil, styles: nil,
|
|
@@ -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
|
|
@@ -25,11 +26,15 @@ module Xlsxrb
|
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
# Streaming parse: yields one raw row hash at a time.
|
|
28
|
-
def self.each_row(
|
|
29
|
-
return enum_for(:each_row,
|
|
30
|
-
return if
|
|
29
|
+
def self.each_row(xml_source, shared_strings: [], part_name: "xl/worksheets/sheet1.xml", &block)
|
|
30
|
+
return enum_for(:each_row, xml_source, shared_strings: shared_strings, part_name: part_name) unless block
|
|
31
|
+
return if xml_source.nil? || (xml_source.respond_to?(:empty?) && xml_source.empty?)
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
if xml_source.is_a?(String)
|
|
34
|
+
fast_scan_rows_direct(xml_source, shared_strings, part_name, &block)
|
|
35
|
+
else
|
|
36
|
+
scan_rows_stream(xml_source, shared_strings, part_name, &block)
|
|
37
|
+
end
|
|
33
38
|
end
|
|
34
39
|
|
|
35
40
|
# Parses column definitions (<cols>) from a worksheet.
|
|
@@ -428,17 +433,14 @@ module Xlsxrb
|
|
|
428
433
|
|
|
429
434
|
private_class_method :resolve_fast_value
|
|
430
435
|
|
|
431
|
-
XML_ENTITIES = { "&" => "&", "<" => "<", ">" => ">", """ => '"', "'" => "'" }.freeze
|
|
432
|
-
|
|
433
436
|
# SECURITY NOTE: Decodes only standard predefined XML entities in a single pass.
|
|
434
437
|
# This completely prevents "Billion Laughs" attacks (exponential entity expansion)
|
|
435
438
|
# because it avoids recursive expansion and ignores custom entities entirely.
|
|
439
|
+
#: (String str) -> String
|
|
436
440
|
def self.decode_xml_entities(str)
|
|
437
|
-
|
|
441
|
+
XmlBuilder.unescape(str)
|
|
438
442
|
end
|
|
439
443
|
|
|
440
|
-
private_class_method :decode_xml_entities
|
|
441
|
-
|
|
442
444
|
# Parses <cols> section for column definitions.
|
|
443
445
|
class ColumnsListener
|
|
444
446
|
include REXML::SAX2Listener
|
|
@@ -574,6 +576,167 @@ module Xlsxrb
|
|
|
574
576
|
|
|
575
577
|
private_class_method :fast_scan_rows_direct
|
|
576
578
|
|
|
579
|
+
def self.scan_rows_stream(source, shared_strings, _part_name, &block)
|
|
580
|
+
buffer = +""
|
|
581
|
+
buffer.force_encoding(Encoding::BINARY)
|
|
582
|
+
in_sheet_data = false
|
|
583
|
+
prefix = ""
|
|
584
|
+
row_start_pattern = "<row"
|
|
585
|
+
row_start_len = 4
|
|
586
|
+
row_end_tag = "</row>"
|
|
587
|
+
row_end_len = 6
|
|
588
|
+
sd_end_tag = "</sheetData>"
|
|
589
|
+
|
|
590
|
+
push_chunk = lambda do |chunk|
|
|
591
|
+
return if in_sheet_data == :closed
|
|
592
|
+
return if chunk.nil? || chunk.empty?
|
|
593
|
+
|
|
594
|
+
buffer << chunk.b
|
|
595
|
+
|
|
596
|
+
unless in_sheet_data
|
|
597
|
+
sd_term = buffer.index("sheetData")
|
|
598
|
+
if sd_term.nil?
|
|
599
|
+
buffer.slice!(0...(buffer.bytesize - 32)) if buffer.bytesize > 64
|
|
600
|
+
return
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
sd_start = buffer.rindex("<", sd_term)
|
|
604
|
+
if sd_start.nil?
|
|
605
|
+
buffer.slice!(0...sd_term)
|
|
606
|
+
return
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
sd_open_end = buffer.index(">", sd_term)
|
|
610
|
+
return if sd_open_end.nil?
|
|
611
|
+
|
|
612
|
+
if buffer.getbyte(sd_open_end - 1) == 47
|
|
613
|
+
in_sheet_data = :closed
|
|
614
|
+
buffer.clear
|
|
615
|
+
return
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
prefix = buffer.byteslice(sd_start + 1, sd_term - (sd_start + 1))
|
|
619
|
+
row_start_pattern = "<#{prefix}row"
|
|
620
|
+
row_start_len = row_start_pattern.bytesize
|
|
621
|
+
row_end_tag = "</#{prefix}row>"
|
|
622
|
+
row_end_len = row_end_tag.bytesize
|
|
623
|
+
sd_end_tag = "</#{prefix}sheetData>"
|
|
624
|
+
|
|
625
|
+
buffer.slice!(0..sd_open_end)
|
|
626
|
+
in_sheet_data = true
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
pos = 0
|
|
630
|
+
last_consumed_pos = 0
|
|
631
|
+
|
|
632
|
+
loop do
|
|
633
|
+
sd_end_pos = buffer.index(sd_end_tag, pos)
|
|
634
|
+
row_start = buffer.index(row_start_pattern, pos)
|
|
635
|
+
|
|
636
|
+
if sd_end_pos && (row_start.nil? || sd_end_pos < row_start)
|
|
637
|
+
in_sheet_data = :closed
|
|
638
|
+
buffer.clear
|
|
639
|
+
return
|
|
640
|
+
end
|
|
641
|
+
|
|
642
|
+
break if row_start.nil?
|
|
643
|
+
|
|
644
|
+
next_pos = row_start + row_start_len
|
|
645
|
+
break if next_pos >= buffer.bytesize
|
|
646
|
+
|
|
647
|
+
nb = buffer.getbyte(next_pos)
|
|
648
|
+
unless [32, 62, 9, 10, 13, 47].include?(nb)
|
|
649
|
+
pos = next_pos
|
|
650
|
+
next
|
|
651
|
+
end
|
|
652
|
+
|
|
653
|
+
tag_end = buffer.index(">", next_pos)
|
|
654
|
+
break if tag_end.nil?
|
|
655
|
+
|
|
656
|
+
if buffer.getbyte(tag_end - 1) == 47
|
|
657
|
+
pos = tag_end + 1
|
|
658
|
+
last_consumed_pos = pos
|
|
659
|
+
next
|
|
660
|
+
end
|
|
661
|
+
|
|
662
|
+
row_end = buffer.index(row_end_tag, tag_end + 1)
|
|
663
|
+
if row_end.nil?
|
|
664
|
+
raise ArgumentError, "Malformed XML: row size exceeds maximum buffer limit" if buffer.bytesize > 50 * 1024 * 1024
|
|
665
|
+
|
|
666
|
+
break
|
|
667
|
+
end
|
|
668
|
+
|
|
669
|
+
row_index = 0
|
|
670
|
+
has_custom_attrs = false
|
|
671
|
+
ri = row_start + row_start_len
|
|
672
|
+
while ri < tag_end
|
|
673
|
+
rb = buffer.getbyte(ri)
|
|
674
|
+
if rb == 114 && buffer.getbyte(ri + 1) == 61 && buffer.getbyte(ri + 2) == 34 # r="
|
|
675
|
+
ri += 3
|
|
676
|
+
while ri < tag_end
|
|
677
|
+
cb = buffer.getbyte(ri)
|
|
678
|
+
break unless cb.between?(48, 57)
|
|
679
|
+
|
|
680
|
+
row_index = (row_index * 10) + (cb - 48)
|
|
681
|
+
ri += 1
|
|
682
|
+
end
|
|
683
|
+
row_index -= 1
|
|
684
|
+
elsif [104, 99, 111].include?(rb)
|
|
685
|
+
has_custom_attrs = true
|
|
686
|
+
ri += 1
|
|
687
|
+
else
|
|
688
|
+
ri += 1
|
|
689
|
+
end
|
|
690
|
+
end
|
|
691
|
+
|
|
692
|
+
attrs = if has_custom_attrs
|
|
693
|
+
row_tag = buffer.byteslice(row_start, tag_end - row_start)
|
|
694
|
+
extract_row_attrs(row_tag)
|
|
695
|
+
else
|
|
696
|
+
EMPTY_HASH
|
|
697
|
+
end
|
|
698
|
+
|
|
699
|
+
cells_xml = buffer.byteslice(tag_end + 1, row_end - (tag_end + 1))
|
|
700
|
+
row_obj = StreamRow.fast_create(
|
|
701
|
+
row_index,
|
|
702
|
+
cells_xml,
|
|
703
|
+
0,
|
|
704
|
+
cells_xml.bytesize,
|
|
705
|
+
shared_strings,
|
|
706
|
+
prefix,
|
|
707
|
+
attrs[:height],
|
|
708
|
+
attrs[:hidden] || false,
|
|
709
|
+
attrs[:custom_height] || false,
|
|
710
|
+
attrs[:outline_level]
|
|
711
|
+
)
|
|
712
|
+
block.call(row_obj)
|
|
713
|
+
|
|
714
|
+
pos = row_end + row_end_len
|
|
715
|
+
last_consumed_pos = pos
|
|
716
|
+
end
|
|
717
|
+
|
|
718
|
+
buffer.slice!(0...last_consumed_pos) if last_consumed_pos.positive?
|
|
719
|
+
end
|
|
720
|
+
|
|
721
|
+
if source.respond_to?(:each_chunk)
|
|
722
|
+
source.each_chunk(65_536, &push_chunk)
|
|
723
|
+
elsif source.respond_to?(:read)
|
|
724
|
+
while (chunk = source.read(65_536))
|
|
725
|
+
break if chunk.empty?
|
|
726
|
+
|
|
727
|
+
push_chunk.call(chunk)
|
|
728
|
+
end
|
|
729
|
+
elsif source.respond_to?(:call)
|
|
730
|
+
source.call(&push_chunk)
|
|
731
|
+
elsif source.respond_to?(:each)
|
|
732
|
+
source.each(&push_chunk)
|
|
733
|
+
else
|
|
734
|
+
raise ArgumentError, "Unsupported stream source: #{source.class}"
|
|
735
|
+
end
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
private_class_method :scan_rows_stream
|
|
739
|
+
|
|
577
740
|
COL_LETTERS = (0...16_384).map do |index|
|
|
578
741
|
result = +""
|
|
579
742
|
i = index
|