xlsxrb 0.1.12 → 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 +59 -42
- data/README.md +26 -22
- data/lib/xlsxrb/dsl_helpers.rb +59 -0
- data/lib/xlsxrb/elements/workbook.rb +11 -0
- data/lib/xlsxrb/ooxml/shared_strings_parser.rb +3 -5
- data/lib/xlsxrb/ooxml/workbook_writer.rb +5 -7
- data/lib/xlsxrb/ooxml/worksheet_parser.rb +172 -9
- data/lib/xlsxrb/ooxml/writer.rb +2 -1
- data/lib/xlsxrb/ooxml/xml_builder.rb +20 -0
- 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 +33 -66
- data/lib/xlsxrb/version.rb +1 -1
- data/lib/xlsxrb/workbook_builder.rb +1 -1
- data/lib/xlsxrb/worksheet_builder.rb +3 -31
- data/lib/xlsxrb.rb +448 -171
- data/sig/generated/xlsxrb/dsl_helpers.rbs +32 -0
- data/sig/generated/xlsxrb/elements/workbook.rbs +7 -0
- data/sig/generated/xlsxrb/ooxml/shared_strings_parser.rbs +0 -2
- data/sig/generated/xlsxrb/ooxml/worksheet_parser.rbs +5 -4
- data/sig/generated/xlsxrb/ooxml/xml_builder.rbs +11 -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.rbs +18 -11
- metadata +6 -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
|
|
@@ -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
|
data/lib/xlsxrb/ooxml/writer.rb
CHANGED
|
@@ -6,6 +6,7 @@ require_relative "zip_generator"
|
|
|
6
6
|
require_relative "writer/drawing_xml"
|
|
7
7
|
require_relative "writer/features_xml"
|
|
8
8
|
require_relative "writer/styles_xml"
|
|
9
|
+
require_relative "../dsl_helpers"
|
|
9
10
|
|
|
10
11
|
module Xlsxrb
|
|
11
12
|
module Ooxml
|
|
@@ -3008,7 +3009,7 @@ module Xlsxrb
|
|
|
3008
3009
|
# Converts a range like "A1:D20" to absolute "$A$1:$D$20".
|
|
3009
3010
|
# : (untyped range) -> untyped
|
|
3010
3011
|
def absolute_range(range)
|
|
3011
|
-
|
|
3012
|
+
DslHelpers.absolute_range(range)
|
|
3012
3013
|
end
|
|
3013
3014
|
|
|
3014
3015
|
# Converts "A1" to "$A$1".
|
|
@@ -98,6 +98,16 @@ module Xlsxrb
|
|
|
98
98
|
@io.is_a?(StringIO) ? @io.string : @io.to_s
|
|
99
99
|
end
|
|
100
100
|
|
|
101
|
+
UNESCAPE_MAP = {
|
|
102
|
+
"&" => "&",
|
|
103
|
+
"<" => "<",
|
|
104
|
+
">" => ">",
|
|
105
|
+
""" => '"',
|
|
106
|
+
"'" => "'"
|
|
107
|
+
}.freeze
|
|
108
|
+
|
|
109
|
+
UNESCAPE_RE = /&(?:amp|lt|gt|quot|apos);/
|
|
110
|
+
|
|
101
111
|
# Escapes XML special characters in a string.
|
|
102
112
|
#
|
|
103
113
|
# @param value [Object]
|
|
@@ -108,6 +118,16 @@ module Xlsxrb
|
|
|
108
118
|
str.match?(ESCAPE_RE) ? str.gsub(ESCAPE_RE, ESCAPE_MAP) : str
|
|
109
119
|
end
|
|
110
120
|
|
|
121
|
+
# Unescapes predefined XML entities in a string in a single pass.
|
|
122
|
+
#
|
|
123
|
+
# @param value [Object]
|
|
124
|
+
# @return [String]
|
|
125
|
+
#: (untyped value) -> String
|
|
126
|
+
def self.unescape(value)
|
|
127
|
+
str = value.to_s
|
|
128
|
+
str.include?("&") ? str.gsub(UNESCAPE_RE, UNESCAPE_MAP) : str
|
|
129
|
+
end
|
|
130
|
+
|
|
111
131
|
private
|
|
112
132
|
|
|
113
133
|
def write_attrs(attrs)
|