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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +108 -0
- data/README.md +37 -17
- data/benchmark/micro_ips.rb +70 -0
- data/lib/xlsxrb/elements/styles.rb +427 -15
- data/lib/xlsxrb/elements/workbook.rb +67 -4
- data/lib/xlsxrb/elements/worksheet.rb +117 -7
- data/lib/xlsxrb/ooxml/cfb.rb +10 -0
- data/lib/xlsxrb/ooxml/reader/listeners/core_listeners.rb +56 -38
- data/lib/xlsxrb/ooxml/reader/listeners/feature_listeners.rb +14 -2
- data/lib/xlsxrb/ooxml/reader/listeners/style_listeners.rb +12 -7
- data/lib/xlsxrb/ooxml/reader.rb +167 -139
- data/lib/xlsxrb/ooxml/shared_strings_parser.rb +83 -28
- data/lib/xlsxrb/ooxml/styles_parser.rb +57 -14
- data/lib/xlsxrb/ooxml/utils.rb +59 -2
- data/lib/xlsxrb/ooxml/workbook_parser.rb +63 -4
- data/lib/xlsxrb/ooxml/workbook_writer.rb +232 -59
- data/lib/xlsxrb/ooxml/worksheet_parser.rb +192 -123
- data/lib/xlsxrb/ooxml/worksheet_writer.rb +307 -125
- data/lib/xlsxrb/ooxml/writer/drawing_xml.rb +16 -12
- data/lib/xlsxrb/ooxml/writer/features_xml.rb +1 -1
- data/lib/xlsxrb/ooxml/writer/styles_xml.rb +64 -15
- data/lib/xlsxrb/ooxml/writer.rb +31 -28
- data/lib/xlsxrb/ooxml/xml_builder.rb +22 -2
- data/lib/xlsxrb/ooxml/xml_parser.rb +45 -0
- data/lib/xlsxrb/ooxml/zip_reader.rb +83 -32
- data/lib/xlsxrb/stream_row.rb +4 -3
- data/lib/xlsxrb/stream_sheet.rb +259 -7
- data/lib/xlsxrb/stream_writer.rb +197 -31
- data/lib/xlsxrb/utils.rb +68 -0
- data/lib/xlsxrb/version.rb +1 -1
- data/lib/xlsxrb/workbook_builder.rb +110 -19
- data/lib/xlsxrb/worksheet_builder.rb +526 -15
- data/lib/xlsxrb.rb +889 -90
- data/sig/generated/xlsxrb/elements/styles.rbs +271 -5
- data/sig/generated/xlsxrb/elements/workbook.rbs +54 -3
- data/sig/generated/xlsxrb/elements/worksheet.rbs +63 -2
- data/sig/generated/xlsxrb/ooxml/reader/listeners/style_listeners.rbs +2 -0
- data/sig/generated/xlsxrb/ooxml/reader.rbs +12 -0
- data/sig/generated/xlsxrb/ooxml/shared_strings_parser.rbs +3 -1
- data/sig/generated/xlsxrb/ooxml/styles_parser.rbs +2 -0
- data/sig/generated/xlsxrb/ooxml/utils.rbs +22 -0
- data/sig/generated/xlsxrb/ooxml/workbook_parser.rbs +7 -0
- data/sig/generated/xlsxrb/ooxml/workbook_writer.rbs +13 -1
- data/sig/generated/xlsxrb/ooxml/worksheet_parser.rbs +18 -2
- data/sig/generated/xlsxrb/ooxml/worksheet_writer.rbs +13 -3
- data/sig/generated/xlsxrb/ooxml/writer/styles_xml.rbs +5 -2
- data/sig/generated/xlsxrb/ooxml/xml_builder.rbs +6 -1
- data/sig/generated/xlsxrb/ooxml/zip_reader.rbs +9 -6
- data/sig/generated/xlsxrb/stream_row.rbs +3 -2
- data/sig/generated/xlsxrb/stream_sheet.rbs +132 -6
- data/sig/generated/xlsxrb/stream_writer.rbs +118 -27
- data/sig/generated/xlsxrb/utils.rbs +45 -0
- data/sig/generated/xlsxrb/worksheet_builder.rbs +252 -9
- data/sig/generated/xlsxrb.rbs +84 -10
- metadata +2 -1
|
@@ -13,6 +13,7 @@ module Xlsxrb
|
|
|
13
13
|
class WorksheetParser
|
|
14
14
|
EMPTY_ARRAY = [].freeze
|
|
15
15
|
EMPTY_HASH = {}.freeze
|
|
16
|
+
EMPTY_BINARY = "".b.freeze
|
|
16
17
|
TAG_DELIM_BYTES = [32, 62, 9, 10, 13, 47].freeze
|
|
17
18
|
ROW_ATTR_START_BYTES = [104, 99, 111, 115].freeze
|
|
18
19
|
|
|
@@ -214,10 +215,7 @@ module Xlsxrb
|
|
|
214
215
|
tag_end = xml.index(">", row_start + row_start_len)
|
|
215
216
|
break unless tag_end
|
|
216
217
|
|
|
217
|
-
|
|
218
|
-
pos = tag_end + 1
|
|
219
|
-
next
|
|
220
|
-
end
|
|
218
|
+
is_self_closing = xml.getbyte(tag_end - 1) == 47
|
|
221
219
|
|
|
222
220
|
# Row index and attrs from tag substring (bounded search)
|
|
223
221
|
row_tag = xml.byteslice(row_start, tag_end - row_start)
|
|
@@ -227,9 +225,6 @@ module Xlsxrb
|
|
|
227
225
|
|
|
228
226
|
attrs = extract_row_attrs(row_tag)
|
|
229
227
|
|
|
230
|
-
row_end = xml.index(row_end_tag, tag_end + 1)
|
|
231
|
-
break unless row_end
|
|
232
|
-
|
|
233
228
|
row_source = { part: part_name, row: row_index }
|
|
234
229
|
block.call(Event.new(
|
|
235
230
|
type: :row_start,
|
|
@@ -237,6 +232,19 @@ module Xlsxrb
|
|
|
237
232
|
source: row_source
|
|
238
233
|
))
|
|
239
234
|
|
|
235
|
+
if is_self_closing
|
|
236
|
+
block.call(Event.new(
|
|
237
|
+
type: :row_end,
|
|
238
|
+
args: [],
|
|
239
|
+
source: row_source
|
|
240
|
+
))
|
|
241
|
+
pos = tag_end + 1
|
|
242
|
+
next
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
row_end = xml.index(row_end_tag, tag_end + 1)
|
|
246
|
+
break unless row_end
|
|
247
|
+
|
|
240
248
|
fast_scan_cells_events(xml, tag_end + 1, row_end, shared_strings, row_source, prefix, &block)
|
|
241
249
|
|
|
242
250
|
block.call(Event.new(
|
|
@@ -431,60 +439,128 @@ module Xlsxrb
|
|
|
431
439
|
private_class_method :fast_scan_cells_events
|
|
432
440
|
|
|
433
441
|
def self.extract_inline_text(xml, from, to)
|
|
442
|
+
xml = xml.b
|
|
434
443
|
result = +""
|
|
435
444
|
pos = from
|
|
436
445
|
while pos < to
|
|
437
|
-
|
|
438
|
-
break unless
|
|
446
|
+
tag_start = xml.index("<", pos)
|
|
447
|
+
break unless tag_start && tag_start < to
|
|
448
|
+
|
|
449
|
+
# Check self-closing tag or closing tag
|
|
450
|
+
next_byte = xml.getbyte(tag_start + 1)
|
|
451
|
+
if next_byte == 47 # </...
|
|
452
|
+
tag_end = xml.index(">", tag_start + 2)
|
|
453
|
+
break unless tag_end
|
|
439
454
|
|
|
440
|
-
|
|
441
|
-
|
|
455
|
+
pos = tag_end + 1
|
|
456
|
+
next
|
|
457
|
+
end
|
|
442
458
|
|
|
443
|
-
|
|
444
|
-
break unless
|
|
459
|
+
tag_end = xml.index(">", tag_start + 1)
|
|
460
|
+
break unless tag_end && tag_end <= to
|
|
445
461
|
|
|
446
|
-
|
|
447
|
-
|
|
462
|
+
# Skip self-closing tags (<.../>)
|
|
463
|
+
if xml.getbyte(tag_end - 1) == 47
|
|
464
|
+
pos = tag_end + 1
|
|
448
465
|
next
|
|
449
466
|
end
|
|
450
467
|
|
|
451
|
-
|
|
452
|
-
|
|
468
|
+
tag_header = xml.byteslice(tag_start + 1, tag_end - tag_start - 1)
|
|
469
|
+
tag_name = tag_header.split(/[ \t\r\n]/, 2).first || ""
|
|
470
|
+
tag_localname = tag_name.include?(":") ? tag_name.split(":", 2).last : tag_name
|
|
471
|
+
|
|
472
|
+
if tag_localname == "rPh"
|
|
473
|
+
# Skip phonetic readings
|
|
474
|
+
close_rph = xml.index("</#{tag_name}>", tag_end + 1) || xml.index("</rPh>", tag_end + 1)
|
|
475
|
+
break unless close_rph && close_rph < to
|
|
453
476
|
|
|
454
|
-
|
|
455
|
-
|
|
477
|
+
close_end = xml.index(">", close_rph)
|
|
478
|
+
break unless close_end
|
|
456
479
|
|
|
457
|
-
|
|
458
|
-
|
|
480
|
+
pos = close_end + 1
|
|
481
|
+
next
|
|
482
|
+
elsif tag_localname == "t"
|
|
483
|
+
close_t = xml.index("</#{tag_name}>", tag_end + 1) || xml.index("</t>", tag_end + 1)
|
|
484
|
+
break unless close_t && close_t <= to
|
|
459
485
|
|
|
460
|
-
|
|
486
|
+
close_end = xml.index(">", close_t)
|
|
487
|
+
break unless close_end
|
|
488
|
+
|
|
489
|
+
text_segment = xml.byteslice(tag_end + 1, close_t - tag_end - 1).force_encoding("UTF-8")
|
|
490
|
+
result << normalize_text(text_segment)
|
|
491
|
+
pos = close_end + 1
|
|
492
|
+
next
|
|
493
|
+
else
|
|
494
|
+
pos = tag_end + 1
|
|
495
|
+
end
|
|
461
496
|
end
|
|
462
497
|
result
|
|
463
498
|
end
|
|
464
499
|
|
|
465
500
|
private_class_method :extract_inline_text
|
|
466
501
|
|
|
467
|
-
|
|
502
|
+
STRICT_NUMERIC_RE = /\A[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?\z/
|
|
503
|
+
private_constant :STRICT_NUMERIC_RE
|
|
504
|
+
|
|
505
|
+
# Resolves a cell value from its type attribute and parsed XML content (<v> or <is>).
|
|
506
|
+
# Consolidates parsing across fast-scan direct, row values, events, and listener paths.
|
|
507
|
+
#: (String? type, String? raw_value, String? is_xml, Array[String | Elements::RichText | untyped] shared_strings) -> untyped
|
|
508
|
+
def self.resolve_cell_value(type, raw_value, is_xml, shared_strings)
|
|
468
509
|
case type
|
|
469
510
|
when "s"
|
|
470
|
-
shared_strings[
|
|
511
|
+
raw_value ? (shared_strings[raw_value.to_i] || "") : nil
|
|
471
512
|
when "b"
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
513
|
+
if raw_value
|
|
514
|
+
v = raw_value.strip
|
|
515
|
+
v == "1" || v.casecmp?("true")
|
|
516
|
+
end
|
|
517
|
+
when "inlineStr", "str", "e"
|
|
518
|
+
if is_xml
|
|
519
|
+
extract_inline_text(is_xml, 0, is_xml.bytesize)
|
|
520
|
+
elsif raw_value
|
|
521
|
+
normalize_text(raw_value)
|
|
522
|
+
end
|
|
523
|
+
when "d"
|
|
524
|
+
raw_value && normalize_text(raw_value)
|
|
525
|
+
when "n"
|
|
526
|
+
raw_value ? numeric_cell_value(raw_value) : nil
|
|
475
527
|
else
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
528
|
+
if raw_value
|
|
529
|
+
text = raw_value.strip
|
|
530
|
+
return nil if text.empty?
|
|
531
|
+
|
|
532
|
+
if text.match?(STRICT_NUMERIC_RE)
|
|
533
|
+
Integer(text, 10, exception: false) ||
|
|
534
|
+
Float(text, exception: false) ||
|
|
535
|
+
normalize_text(raw_value)
|
|
536
|
+
else
|
|
537
|
+
normalize_text(raw_value)
|
|
538
|
+
end
|
|
482
539
|
end
|
|
483
540
|
end
|
|
484
541
|
end
|
|
485
542
|
|
|
543
|
+
def self.resolve_fast_value(raw, type, shared_strings)
|
|
544
|
+
resolve_cell_value(type, raw, nil, shared_strings)
|
|
545
|
+
end
|
|
546
|
+
|
|
486
547
|
private_class_method :resolve_fast_value
|
|
487
548
|
|
|
549
|
+
# Converts the text of a numeric cell (<c t="n">) into an Integer or Float.
|
|
550
|
+
# An empty <v></v> carries no value and yields nil. Text that is not an xsd:double
|
|
551
|
+
# (e.g. "abc") cannot come from a conforming producer and raises ParseError
|
|
552
|
+
# instead of silently becoming 0.
|
|
553
|
+
#: (String raw) -> (Integer | Float)?
|
|
554
|
+
def self.numeric_cell_value(raw)
|
|
555
|
+
text = raw.strip
|
|
556
|
+
return nil if text.empty?
|
|
557
|
+
|
|
558
|
+
raise(ParseError, "invalid numeric cell value: #{text.inspect}") unless text.match?(STRICT_NUMERIC_RE)
|
|
559
|
+
|
|
560
|
+
Integer(text, 10, exception: false) || Float(text, exception: false) ||
|
|
561
|
+
raise(ParseError, "invalid numeric cell value: #{text.inspect}")
|
|
562
|
+
end
|
|
563
|
+
|
|
488
564
|
# SECURITY NOTE: Decodes only standard predefined XML entities in a single pass.
|
|
489
565
|
# This completely prevents "Billion Laughs" attacks (exponential entity expansion)
|
|
490
566
|
# because it avoids recursive expansion and ignores custom entities entirely.
|
|
@@ -496,7 +572,8 @@ module Xlsxrb
|
|
|
496
572
|
#: (String str) -> String
|
|
497
573
|
def self.normalize_text(str)
|
|
498
574
|
s = decode_xml_entities(str)
|
|
499
|
-
s
|
|
575
|
+
s = s.gsub("\r\n", "\n").tr("\r", "\n") if s.include?("\r")
|
|
576
|
+
s.encoding == Encoding::BINARY ? s.dup.force_encoding(Encoding::UTF_8) : s
|
|
500
577
|
end
|
|
501
578
|
|
|
502
579
|
# Parses <cols> section for column definitions.
|
|
@@ -579,10 +656,7 @@ module Xlsxrb
|
|
|
579
656
|
tag_end = xml.index(">", row_start + row_start_len)
|
|
580
657
|
break unless tag_end
|
|
581
658
|
|
|
582
|
-
|
|
583
|
-
pos = tag_end + 1
|
|
584
|
-
next
|
|
585
|
-
end
|
|
659
|
+
is_self_closing = xml.getbyte(tag_end - 1) == 47
|
|
586
660
|
|
|
587
661
|
row_index = nil
|
|
588
662
|
has_custom_attrs = false
|
|
@@ -618,6 +692,29 @@ module Xlsxrb
|
|
|
618
692
|
EMPTY_HASH
|
|
619
693
|
end
|
|
620
694
|
|
|
695
|
+
if is_self_closing
|
|
696
|
+
row_obj = StreamRow.fast_create(
|
|
697
|
+
row_index,
|
|
698
|
+
EMPTY_BINARY,
|
|
699
|
+
0,
|
|
700
|
+
0,
|
|
701
|
+
shared_strings,
|
|
702
|
+
prefix,
|
|
703
|
+
attrs[:height],
|
|
704
|
+
attrs[:hidden] || false,
|
|
705
|
+
attrs[:custom_height] || false,
|
|
706
|
+
attrs[:outline_level],
|
|
707
|
+
attrs[:style_index],
|
|
708
|
+
styles,
|
|
709
|
+
date1904,
|
|
710
|
+
attrs[:collapsed] || false,
|
|
711
|
+
pad_empty_cells
|
|
712
|
+
)
|
|
713
|
+
block.call(row_obj)
|
|
714
|
+
pos = tag_end + 1
|
|
715
|
+
next
|
|
716
|
+
end
|
|
717
|
+
|
|
621
718
|
row_end = xml.index(row_end_tag, tag_end + 1)
|
|
622
719
|
break unless row_end
|
|
623
720
|
|
|
@@ -726,17 +823,15 @@ module Xlsxrb
|
|
|
726
823
|
tag_end = buffer.index(">", next_pos)
|
|
727
824
|
break if tag_end.nil?
|
|
728
825
|
|
|
729
|
-
|
|
730
|
-
pos = tag_end + 1
|
|
731
|
-
last_consumed_pos = pos
|
|
732
|
-
next
|
|
733
|
-
end
|
|
826
|
+
is_self_closing = buffer.getbyte(tag_end - 1) == 47
|
|
734
827
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
828
|
+
unless is_self_closing
|
|
829
|
+
row_end = buffer.index(row_end_tag, tag_end + 1)
|
|
830
|
+
if row_end.nil?
|
|
831
|
+
raise ArgumentError, "Malformed XML: row size exceeds maximum buffer limit" if buffer.bytesize > 50 * 1024 * 1024
|
|
738
832
|
|
|
739
|
-
|
|
833
|
+
break
|
|
834
|
+
end
|
|
740
835
|
end
|
|
741
836
|
|
|
742
837
|
row_index = nil
|
|
@@ -773,6 +868,30 @@ module Xlsxrb
|
|
|
773
868
|
EMPTY_HASH
|
|
774
869
|
end
|
|
775
870
|
|
|
871
|
+
if is_self_closing
|
|
872
|
+
row_obj = StreamRow.fast_create(
|
|
873
|
+
row_index,
|
|
874
|
+
EMPTY_BINARY,
|
|
875
|
+
0,
|
|
876
|
+
0,
|
|
877
|
+
shared_strings,
|
|
878
|
+
prefix,
|
|
879
|
+
attrs[:height],
|
|
880
|
+
attrs[:hidden] || false,
|
|
881
|
+
attrs[:custom_height] || false,
|
|
882
|
+
attrs[:outline_level],
|
|
883
|
+
attrs[:style_index],
|
|
884
|
+
styles,
|
|
885
|
+
date1904,
|
|
886
|
+
attrs[:collapsed] || false,
|
|
887
|
+
pad_empty_cells
|
|
888
|
+
)
|
|
889
|
+
block.call(row_obj)
|
|
890
|
+
pos = tag_end + 1
|
|
891
|
+
last_consumed_pos = pos
|
|
892
|
+
next
|
|
893
|
+
end
|
|
894
|
+
|
|
776
895
|
cells_xml = buffer.byteslice(tag_end + 1, row_end - (tag_end + 1))
|
|
777
896
|
row_obj = StreamRow.fast_create(
|
|
778
897
|
row_index,
|
|
@@ -846,11 +965,11 @@ module Xlsxrb
|
|
|
846
965
|
[COL_LETTERS[idx], idx]
|
|
847
966
|
end.freeze
|
|
848
967
|
|
|
849
|
-
CELL_FAST_RE = %r{<(?:[a-zA-Z0-9_]+:)?c\
|
|
968
|
+
CELL_FAST_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>|<(?:[a-zA-Z0-9_]+:)?c\b([^>]*)/?>}m
|
|
850
969
|
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
|
|
851
|
-
ATTR_R =
|
|
852
|
-
ATTR_T =
|
|
853
|
-
ATTR_S =
|
|
970
|
+
ATTR_R = /\br="([A-Za-z0-9]+)"/
|
|
971
|
+
ATTR_T = /\bt="([a-zA-Z]+)"/
|
|
972
|
+
ATTR_S = /\bs="(\d+)"/
|
|
854
973
|
|
|
855
974
|
def self.fast_parse_cells_direct(xml, from, to, shared_strings, row_source, prefix = "")
|
|
856
975
|
cells = []
|
|
@@ -866,14 +985,17 @@ module Xlsxrb
|
|
|
866
985
|
col_idx = 0
|
|
867
986
|
matched_any = false
|
|
868
987
|
|
|
869
|
-
chunk.scan(CELL_FAST_RE) do |
|
|
988
|
+
chunk.scan(CELL_FAST_RE) do |attrs_str, f, v, is, self_attrs|
|
|
870
989
|
matched_any = true
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
990
|
+
raw_attrs = attrs_str || self_attrs || ""
|
|
991
|
+
r = raw_attrs[ATTR_R, 1]
|
|
992
|
+
s = raw_attrs.include?('s="') ? raw_attrs[ATTR_S, 1] : nil
|
|
993
|
+
t = raw_attrs.include?('t="') ? raw_attrs[ATTR_T, 1] : nil
|
|
994
|
+
style_idx = s&.to_i
|
|
995
|
+
ref = r
|
|
874
996
|
c_idx = if ref
|
|
875
997
|
expected_letter = COL_LETTERS[col_idx]
|
|
876
|
-
if expected_letter && ref.start_with?(expected_letter)
|
|
998
|
+
if expected_letter && ref.start_with?(expected_letter) && ref.getbyte(expected_letter.bytesize)&.between?(48, 57)
|
|
877
999
|
col_idx
|
|
878
1000
|
else
|
|
879
1001
|
c_len = 0
|
|
@@ -888,21 +1010,7 @@ module Xlsxrb
|
|
|
888
1010
|
v = v.strip if v
|
|
889
1011
|
f = f.strip if f
|
|
890
1012
|
|
|
891
|
-
val =
|
|
892
|
-
shared_strings[v.to_i] || ""
|
|
893
|
-
elsif t == "b"
|
|
894
|
-
v == "1"
|
|
895
|
-
elsif %w[inlineStr str e].include?(t)
|
|
896
|
-
if is
|
|
897
|
-
extract_inline_text(is, 0, is.bytesize)
|
|
898
|
-
elsif v
|
|
899
|
-
normalize_text(v)
|
|
900
|
-
else
|
|
901
|
-
""
|
|
902
|
-
end
|
|
903
|
-
elsif v
|
|
904
|
-
v.include?(".") ? v.to_f : v.to_i
|
|
905
|
-
end
|
|
1013
|
+
val = resolve_cell_value(t, v, is, shared_strings)
|
|
906
1014
|
|
|
907
1015
|
raw_val = if is
|
|
908
1016
|
extract_inline_text(is, 0, is.bytesize)
|
|
@@ -927,7 +1035,7 @@ module Xlsxrb
|
|
|
927
1035
|
|
|
928
1036
|
c_idx = if r
|
|
929
1037
|
expected_letter = COL_LETTERS[col_idx]
|
|
930
|
-
if expected_letter && r.start_with?(expected_letter)
|
|
1038
|
+
if expected_letter && r.start_with?(expected_letter) && r.getbyte(expected_letter.bytesize)&.between?(48, 57)
|
|
931
1039
|
col_idx
|
|
932
1040
|
else
|
|
933
1041
|
c_len = 0
|
|
@@ -942,21 +1050,7 @@ module Xlsxrb
|
|
|
942
1050
|
v = v.strip if v
|
|
943
1051
|
f = f.strip if f
|
|
944
1052
|
|
|
945
|
-
val =
|
|
946
|
-
shared_strings[v.to_i] || ""
|
|
947
|
-
elsif t == "b"
|
|
948
|
-
v == "1"
|
|
949
|
-
elsif %w[inlineStr str e].include?(t)
|
|
950
|
-
if is
|
|
951
|
-
extract_inline_text(is, 0, is.bytesize)
|
|
952
|
-
elsif v
|
|
953
|
-
normalize_text(v)
|
|
954
|
-
else
|
|
955
|
-
""
|
|
956
|
-
end
|
|
957
|
-
elsif v
|
|
958
|
-
v.include?(".") ? v.to_f : v.to_i
|
|
959
|
-
end
|
|
1053
|
+
val = resolve_cell_value(t, v, is, shared_strings)
|
|
960
1054
|
|
|
961
1055
|
raw_val = if is
|
|
962
1056
|
extract_inline_text(is, 0, is.bytesize)
|
|
@@ -974,7 +1068,7 @@ module Xlsxrb
|
|
|
974
1068
|
end
|
|
975
1069
|
|
|
976
1070
|
# rubocop:disable Style/OptionalBooleanParameter
|
|
977
|
-
#: (String xml, Integer from, Integer to, Array[String] shared_strings, ?untyped styles, ?bool date1904, ?type_cast: bool) -> Array[untyped]
|
|
1071
|
+
#: (String xml, Integer from, Integer to, Array[String | Elements::RichText | untyped] shared_strings, ?untyped styles, ?bool date1904, ?type_cast: bool) -> Array[untyped]
|
|
978
1072
|
def self.fast_scan_row_values(xml, from, to, shared_strings, styles = nil, date1904 = false, type_cast: false)
|
|
979
1073
|
# rubocop:enable Style/OptionalBooleanParameter
|
|
980
1074
|
chunk = xml.byteslice(from, to - from)
|
|
@@ -984,13 +1078,16 @@ module Xlsxrb
|
|
|
984
1078
|
matched_any = false
|
|
985
1079
|
row_values = []
|
|
986
1080
|
|
|
987
|
-
chunk.scan(CELL_FAST_RE) do |
|
|
1081
|
+
chunk.scan(CELL_FAST_RE) do |attrs_str, _f, v, is, self_attrs|
|
|
988
1082
|
matched_any = true
|
|
989
|
-
|
|
990
|
-
|
|
1083
|
+
raw_attrs = attrs_str || self_attrs || ""
|
|
1084
|
+
r = raw_attrs[ATTR_R, 1]
|
|
1085
|
+
s_val = raw_attrs.include?('s="') ? raw_attrs[ATTR_S, 1] : nil
|
|
1086
|
+
t = raw_attrs.include?('t="') ? raw_attrs[ATTR_T, 1] : nil
|
|
1087
|
+
ref = r
|
|
991
1088
|
c_idx = if ref
|
|
992
1089
|
expected_letter = COL_LETTERS[col_idx]
|
|
993
|
-
if expected_letter && ref.start_with?(expected_letter)
|
|
1090
|
+
if expected_letter && ref.start_with?(expected_letter) && ref.getbyte(expected_letter.bytesize)&.between?(48, 57)
|
|
994
1091
|
col_idx
|
|
995
1092
|
else
|
|
996
1093
|
c_len = 0
|
|
@@ -1004,21 +1101,7 @@ module Xlsxrb
|
|
|
1004
1101
|
col_idx = c_idx + 1
|
|
1005
1102
|
v = v.strip if v
|
|
1006
1103
|
|
|
1007
|
-
val =
|
|
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
|
|
1104
|
+
val = resolve_cell_value(t, v, is, shared_strings)
|
|
1022
1105
|
|
|
1023
1106
|
if type_cast && val.is_a?(Numeric) && s_val && styles
|
|
1024
1107
|
fmt_type = NumberFormatter.format_type_for(s_val.to_i, styles)
|
|
@@ -1047,7 +1130,7 @@ module Xlsxrb
|
|
|
1047
1130
|
|
|
1048
1131
|
c_idx = if r
|
|
1049
1132
|
expected_letter = COL_LETTERS[col_idx]
|
|
1050
|
-
if expected_letter && r.start_with?(expected_letter)
|
|
1133
|
+
if expected_letter && r.start_with?(expected_letter) && r.getbyte(expected_letter.bytesize)&.between?(48, 57)
|
|
1051
1134
|
col_idx
|
|
1052
1135
|
else
|
|
1053
1136
|
c_len = 0
|
|
@@ -1061,21 +1144,7 @@ module Xlsxrb
|
|
|
1061
1144
|
col_idx = c_idx + 1
|
|
1062
1145
|
v = v.strip if v
|
|
1063
1146
|
|
|
1064
|
-
val =
|
|
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
|
|
1147
|
+
val = resolve_cell_value(t, v, is, shared_strings)
|
|
1079
1148
|
|
|
1080
1149
|
if type_cast && val.is_a?(Numeric) && s && styles
|
|
1081
1150
|
fmt_type = NumberFormatter.format_type_for(s.to_i, styles)
|