xlsxrb 0.1.2 → 0.1.3
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/README.md +57 -264
- data/Rakefile +137 -5
- data/benchmark.rb +23 -14
- data/docs/DEVELOPMENT.md +67 -0
- data/docs/coi-serviceworker.js +82 -0
- data/docs/office_thread.js +77 -0
- data/docs/preview.html +719 -0
- data/docs/visual/VisualGallery.md +0 -158
- data/docs/wasm/wasm_doc_helper.css +256 -94
- data/docs/wasm/wasm_doc_helper.js +261 -142
- data/docs/zeta.js +1107 -0
- data/lib/xlsxrb/ooxml/worksheet_parser.rb +178 -24
- data/lib/xlsxrb/ooxml.rb +1 -1
- data/lib/xlsxrb/version.rb +1 -1
- metadata +6 -1
|
@@ -25,27 +25,9 @@ module Xlsxrb
|
|
|
25
25
|
# Streaming parse: yields one raw row hash at a time.
|
|
26
26
|
def self.each_row(xml_string, shared_strings: [], part_name: "xl/worksheets/sheet1.xml", &block)
|
|
27
27
|
return enum_for(:each_row, xml_string, shared_strings: shared_strings, part_name: part_name) unless block
|
|
28
|
+
return if xml_string.nil? || xml_string.empty?
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
each_event(xml_string, shared_strings: shared_strings, part_name: part_name) do |event|
|
|
31
|
-
case event.type
|
|
32
|
-
when :row_start
|
|
33
|
-
index, attrs = event.args
|
|
34
|
-
current_row = { index: index, cells: [], attrs: attrs, unmapped: EMPTY_ARRAY, source: event.source }
|
|
35
|
-
when :cell
|
|
36
|
-
ref, type, style_index, value, formula = event.args
|
|
37
|
-
if current_row
|
|
38
|
-
cell = { ref: ref, type: type, style_index: style_index, value: value, source: event.source }
|
|
39
|
-
cell[:formula] = formula if formula
|
|
40
|
-
current_row[:cells] << cell
|
|
41
|
-
end
|
|
42
|
-
when :row_end
|
|
43
|
-
if current_row
|
|
44
|
-
block.call(current_row)
|
|
45
|
-
current_row = nil
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|
|
30
|
+
fast_scan_rows_direct(xml_string, shared_strings, part_name, &block)
|
|
49
31
|
end
|
|
50
32
|
|
|
51
33
|
# Parses column definitions (<cols>) from a worksheet.
|
|
@@ -187,7 +169,7 @@ module Xlsxrb
|
|
|
187
169
|
source: row_source
|
|
188
170
|
))
|
|
189
171
|
|
|
190
|
-
fast_scan_cells_events(xml, tag_end + 1, row_end, shared_strings,
|
|
172
|
+
fast_scan_cells_events(xml, tag_end + 1, row_end, shared_strings, row_source, &block)
|
|
191
173
|
|
|
192
174
|
block.call(Event.new(
|
|
193
175
|
type: :row_end,
|
|
@@ -245,7 +227,7 @@ module Xlsxrb
|
|
|
245
227
|
|
|
246
228
|
private_class_method :tag_attr
|
|
247
229
|
|
|
248
|
-
def self.fast_scan_cells_events(xml, from, to, shared_strings,
|
|
230
|
+
def self.fast_scan_cells_events(xml, from, to, shared_strings, row_source, &block)
|
|
249
231
|
pos = from
|
|
250
232
|
|
|
251
233
|
while pos < to
|
|
@@ -270,10 +252,12 @@ module Xlsxrb
|
|
|
270
252
|
|
|
271
253
|
# Self-closing <c ... />
|
|
272
254
|
if xml.getbyte(c_tag_end - 1) == 47
|
|
255
|
+
cell_source = row_source.dup
|
|
256
|
+
cell_source[:cell] = ref
|
|
273
257
|
block.call(Event.new(
|
|
274
258
|
type: :cell,
|
|
275
259
|
args: [ref, type, style_index, nil, nil],
|
|
276
|
-
source:
|
|
260
|
+
source: cell_source
|
|
277
261
|
))
|
|
278
262
|
pos = c_tag_end + 1
|
|
279
263
|
next
|
|
@@ -347,10 +331,12 @@ module Xlsxrb
|
|
|
347
331
|
end
|
|
348
332
|
|
|
349
333
|
val_to_use = inline_str || value
|
|
334
|
+
cell_source = row_source.dup
|
|
335
|
+
cell_source[:cell] = ref
|
|
350
336
|
block.call(Event.new(
|
|
351
337
|
type: :cell,
|
|
352
338
|
args: [ref, type, style_index, val_to_use, formula],
|
|
353
|
-
source:
|
|
339
|
+
source: cell_source
|
|
354
340
|
))
|
|
355
341
|
|
|
356
342
|
pos = c_end + 4
|
|
@@ -450,6 +436,174 @@ module Xlsxrb
|
|
|
450
436
|
|
|
451
437
|
def characters(_text); end
|
|
452
438
|
end
|
|
439
|
+
|
|
440
|
+
def self.fast_scan_rows_direct(xml_src, shared_strings, part_name, &block)
|
|
441
|
+
xml = xml_src.b # force ASCII-8BIT for O(1) byte indexing
|
|
442
|
+
|
|
443
|
+
sd_start = xml.index("<sheetData")
|
|
444
|
+
return unless sd_start
|
|
445
|
+
|
|
446
|
+
sd_open_end = xml.index(">", sd_start)
|
|
447
|
+
return unless sd_open_end
|
|
448
|
+
return if xml.getbyte(sd_open_end - 1) == 47 # self-closing <sheetData/>
|
|
449
|
+
|
|
450
|
+
sd_end = xml.index("</sheetData>", sd_open_end)
|
|
451
|
+
return unless sd_end
|
|
452
|
+
|
|
453
|
+
pos = sd_open_end + 1
|
|
454
|
+
|
|
455
|
+
while pos < sd_end
|
|
456
|
+
row_start = xml.index("<row", pos)
|
|
457
|
+
break unless row_start && row_start < sd_end
|
|
458
|
+
|
|
459
|
+
nb = xml.getbyte(row_start + 4)
|
|
460
|
+
unless [32, 62, 9, 10, 13, 47].include?(nb)
|
|
461
|
+
pos = row_start + 4
|
|
462
|
+
next
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
tag_end = xml.index(">", row_start + 4)
|
|
466
|
+
break unless tag_end
|
|
467
|
+
|
|
468
|
+
if xml.getbyte(tag_end - 1) == 47
|
|
469
|
+
pos = tag_end + 1
|
|
470
|
+
next
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
row_tag = xml.byteslice(row_start, tag_end - row_start)
|
|
474
|
+
row_index = 0
|
|
475
|
+
r_val = tag_attr(row_tag, ' r="')
|
|
476
|
+
row_index = r_val.to_i - 1 if r_val
|
|
477
|
+
|
|
478
|
+
attrs = extract_row_attrs(row_tag)
|
|
479
|
+
row_end = xml.index("</row>", tag_end + 1)
|
|
480
|
+
break unless row_end
|
|
481
|
+
|
|
482
|
+
row_source = { part: part_name, row: row_index }
|
|
483
|
+
cells = fast_parse_cells_direct(xml, tag_end + 1, row_end, shared_strings, row_source)
|
|
484
|
+
|
|
485
|
+
block.call({ index: row_index, cells: cells, attrs: attrs, unmapped: EMPTY_ARRAY, source: row_source })
|
|
486
|
+
|
|
487
|
+
pos = row_end + 6
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
private_class_method :fast_scan_rows_direct
|
|
492
|
+
|
|
493
|
+
def self.fast_parse_cells_direct(xml, from, to, shared_strings, row_source)
|
|
494
|
+
cells = []
|
|
495
|
+
pos = from
|
|
496
|
+
|
|
497
|
+
while pos < to
|
|
498
|
+
c_start = xml.index("<c", pos)
|
|
499
|
+
break unless c_start && c_start < to
|
|
500
|
+
|
|
501
|
+
nb = xml.getbyte(c_start + 2)
|
|
502
|
+
unless [32, 62, 9, 10, 13, 47].include?(nb)
|
|
503
|
+
pos = c_start + 2
|
|
504
|
+
next
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
c_tag_end = xml.index(">", c_start + 2)
|
|
508
|
+
break unless c_tag_end
|
|
509
|
+
|
|
510
|
+
# Extract tag substring for bounded attribute search
|
|
511
|
+
c_tag = xml.byteslice(c_start, c_tag_end - c_start)
|
|
512
|
+
ref = tag_attr(c_tag, ' r="')
|
|
513
|
+
type = tag_attr(c_tag, ' t="')
|
|
514
|
+
style_str = tag_attr(c_tag, ' s="')
|
|
515
|
+
style_index = style_str&.to_i
|
|
516
|
+
|
|
517
|
+
# Self-closing <c ... />
|
|
518
|
+
if xml.getbyte(c_tag_end - 1) == 47
|
|
519
|
+
cell_source = row_source.dup
|
|
520
|
+
cell_source[:cell] = ref
|
|
521
|
+
cells << { ref: ref, type: type, style_index: style_index, value: nil, source: cell_source }
|
|
522
|
+
pos = c_tag_end + 1
|
|
523
|
+
next
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
c_end = xml.index("</c>", c_tag_end + 1)
|
|
527
|
+
break unless c_end
|
|
528
|
+
|
|
529
|
+
# Parse cell content sequentially (bounded to c_end - avoid unbounded scans)
|
|
530
|
+
value = nil
|
|
531
|
+
formula = nil
|
|
532
|
+
inline_str = nil
|
|
533
|
+
cpos = c_tag_end + 1
|
|
534
|
+
while cpos < c_end
|
|
535
|
+
tag_pos = xml.index("<", cpos)
|
|
536
|
+
break unless tag_pos && tag_pos < c_end
|
|
537
|
+
|
|
538
|
+
tag_char = xml.getbyte(tag_pos + 1)
|
|
539
|
+
case tag_char
|
|
540
|
+
when 118 # 'v'
|
|
541
|
+
if xml.getbyte(tag_pos + 2) == 62 # <v>
|
|
542
|
+
v_val_start = tag_pos + 3
|
|
543
|
+
v_end = xml.index("</v>", v_val_start)
|
|
544
|
+
if v_end
|
|
545
|
+
raw_value = xml.byteslice(v_val_start, v_end - v_val_start)
|
|
546
|
+
value = resolve_fast_value(raw_value, type, shared_strings)
|
|
547
|
+
cpos = v_end + 4
|
|
548
|
+
else
|
|
549
|
+
cpos = tag_pos + 3
|
|
550
|
+
end
|
|
551
|
+
elsif xml.getbyte(tag_pos + 2) == 47 && xml.getbyte(tag_pos + 3) == 62 # <v/>
|
|
552
|
+
cpos = tag_pos + 4
|
|
553
|
+
else
|
|
554
|
+
cpos = tag_pos + 2
|
|
555
|
+
end
|
|
556
|
+
when 102 # 'f'
|
|
557
|
+
f_tag_end = xml.index(">", tag_pos + 2)
|
|
558
|
+
if f_tag_end && f_tag_end < c_end
|
|
559
|
+
if xml.getbyte(f_tag_end - 1) == 47 # self-closing <f ... />
|
|
560
|
+
cpos = f_tag_end + 1
|
|
561
|
+
else
|
|
562
|
+
f_end = xml.index("</f>", f_tag_end + 1)
|
|
563
|
+
if f_end && f_end <= c_end
|
|
564
|
+
formula = xml.byteslice(f_tag_end + 1, f_end - f_tag_end - 1).force_encoding("UTF-8")
|
|
565
|
+
formula = decode_xml_entities(formula) if formula.include?("&")
|
|
566
|
+
cpos = f_end + 4
|
|
567
|
+
else
|
|
568
|
+
cpos = f_tag_end + 1
|
|
569
|
+
end
|
|
570
|
+
end
|
|
571
|
+
else
|
|
572
|
+
cpos = tag_pos + 2
|
|
573
|
+
end
|
|
574
|
+
when 105 # 'i' - <is>
|
|
575
|
+
if xml.byteslice(tag_pos, 4) == "<is>"
|
|
576
|
+
is_end = xml.index("</is>", tag_pos + 4)
|
|
577
|
+
if is_end && is_end <= c_end
|
|
578
|
+
inline_str = extract_inline_text(xml, tag_pos + 4, is_end)
|
|
579
|
+
cpos = is_end + 5
|
|
580
|
+
else
|
|
581
|
+
cpos = tag_pos + 4
|
|
582
|
+
end
|
|
583
|
+
else
|
|
584
|
+
cpos = tag_pos + 2
|
|
585
|
+
end
|
|
586
|
+
else
|
|
587
|
+
# Skip unknown tag
|
|
588
|
+
close = xml.index(">", tag_pos + 1)
|
|
589
|
+
cpos = close ? close + 1 : c_end
|
|
590
|
+
end
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
val_to_use = inline_str || value
|
|
594
|
+
cell_source = row_source.dup
|
|
595
|
+
cell_source[:cell] = ref
|
|
596
|
+
cell = { ref: ref, type: type, style_index: style_index, value: val_to_use, source: cell_source }
|
|
597
|
+
cell[:formula] = formula if formula
|
|
598
|
+
cells << cell
|
|
599
|
+
|
|
600
|
+
pos = c_end + 4
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
cells
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
private_class_method :fast_parse_cells_direct
|
|
453
607
|
end
|
|
454
608
|
end
|
|
455
609
|
end
|
data/lib/xlsxrb/ooxml.rb
CHANGED
data/lib/xlsxrb/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: xlsxrb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- niku
|
|
@@ -82,10 +82,15 @@ files:
|
|
|
82
82
|
- Rakefile
|
|
83
83
|
- benchmark.rb
|
|
84
84
|
- docs/ARCHITECTURE.md
|
|
85
|
+
- docs/DEVELOPMENT.md
|
|
85
86
|
- docs/SPEC_SOURCES.md
|
|
87
|
+
- docs/coi-serviceworker.js
|
|
88
|
+
- docs/office_thread.js
|
|
89
|
+
- docs/preview.html
|
|
86
90
|
- docs/visual/VisualGallery.md
|
|
87
91
|
- docs/wasm/wasm_doc_helper.css
|
|
88
92
|
- docs/wasm/wasm_doc_helper.js
|
|
93
|
+
- docs/zeta.js
|
|
89
94
|
- lib/xlsxrb.rb
|
|
90
95
|
- lib/xlsxrb/elements.rb
|
|
91
96
|
- lib/xlsxrb/elements/cell.rb
|