xlsxrb 0.1.9 → 0.1.11
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 +40 -0
- data/README.md +71 -253
- data/Rakefile +2 -1
- data/benchmark.rb +3 -1
- data/docs/PEER_LIBRARIES.md +121 -0
- data/docs/assets/benchmark_results.svg +144 -0
- data/docs/assets/lsp_autocompletion.png +0 -0
- data/docs/assets/playground_preview.png +0 -0
- data/docs/visual/VisualGallery.md +183 -0
- data/docs/wasm/ruby.wasm +0 -0
- data/lib/xlsxrb/chart_builder.rb +333 -0
- data/lib/xlsxrb/elements/cell.rb +66 -12
- data/lib/xlsxrb/elements/workbook.rb +2 -2
- data/lib/xlsxrb/ooxml/reader/listeners.rb +6450 -0
- data/lib/xlsxrb/ooxml/reader.rb +2 -6438
- data/lib/xlsxrb/ooxml/workbook_writer.rb +297 -236
- data/lib/xlsxrb/ooxml/worksheet_parser.rb +218 -266
- data/lib/xlsxrb/ooxml/worksheet_writer.rb +127 -10
- data/lib/xlsxrb/ooxml/zip_writer.rb +31 -38
- data/lib/xlsxrb/stream_row.rb +34 -8
- data/lib/xlsxrb/stream_sheet.rb +119 -0
- data/lib/xlsxrb/stream_writer.rb +1733 -0
- data/lib/xlsxrb/version.rb +1 -1
- data/lib/xlsxrb/workbook_builder.rb +309 -0
- data/lib/xlsxrb/worksheet_builder.rb +691 -0
- data/lib/xlsxrb.rb +238 -2968
- data/sig/generated/xlsxrb/chart_builder.rbs +269 -0
- data/sig/generated/xlsxrb/elements/cell.rbs +137 -12
- data/sig/generated/xlsxrb/ooxml/reader/listeners.rbs +1074 -0
- data/sig/generated/xlsxrb/ooxml/reader.rbs +0 -1067
- data/sig/generated/xlsxrb/ooxml/workbook_writer.rbs +17 -1
- data/sig/generated/xlsxrb/ooxml/worksheet_parser.rbs +18 -4
- data/sig/generated/xlsxrb/ooxml/worksheet_writer.rbs +4 -0
- data/sig/generated/xlsxrb/stream_row.rbs +6 -2
- data/sig/generated/xlsxrb/stream_sheet.rbs +100 -0
- data/sig/generated/xlsxrb/stream_writer.rbs +960 -0
- data/sig/generated/xlsxrb/workbook_builder.rbs +146 -0
- data/sig/generated/xlsxrb/worksheet_builder.rbs +349 -0
- data/sig/generated/xlsxrb.rbs +103 -1563
- metadata +17 -1
|
@@ -129,30 +129,41 @@ module Xlsxrb
|
|
|
129
129
|
def self.fast_scan_events(xml_src, shared_strings, part_name, &block)
|
|
130
130
|
xml = xml_src.b # force ASCII-8BIT for O(1) byte indexing
|
|
131
131
|
|
|
132
|
-
|
|
132
|
+
sd_term = xml.index("sheetData")
|
|
133
|
+
return unless sd_term
|
|
134
|
+
|
|
135
|
+
sd_start = xml.rindex("<", sd_term)
|
|
133
136
|
return unless sd_start
|
|
134
137
|
|
|
138
|
+
prefix = xml.byteslice(sd_start + 1, sd_term - (sd_start + 1))
|
|
139
|
+
|
|
135
140
|
sd_open_end = xml.index(">", sd_start)
|
|
136
141
|
return unless sd_open_end
|
|
137
142
|
|
|
138
143
|
return if xml.getbyte(sd_open_end - 1) == 47 # self-closing <sheetData/>
|
|
139
144
|
|
|
140
|
-
|
|
145
|
+
sd_end_tag = "</#{prefix}sheetData>"
|
|
146
|
+
sd_end = xml.index(sd_end_tag, sd_open_end)
|
|
141
147
|
return unless sd_end
|
|
142
148
|
|
|
149
|
+
row_start_pattern = "<#{prefix}row"
|
|
150
|
+
row_start_len = row_start_pattern.bytesize
|
|
151
|
+
row_end_tag = "</#{prefix}row>"
|
|
152
|
+
row_end_len = row_end_tag.bytesize
|
|
153
|
+
|
|
143
154
|
pos = sd_open_end + 1
|
|
144
155
|
|
|
145
156
|
while pos < sd_end
|
|
146
|
-
row_start = xml.index(
|
|
157
|
+
row_start = xml.index(row_start_pattern, pos)
|
|
147
158
|
break unless row_start && row_start < sd_end
|
|
148
159
|
|
|
149
|
-
nb = xml.getbyte(row_start +
|
|
160
|
+
nb = xml.getbyte(row_start + row_start_len)
|
|
150
161
|
unless [32, 62, 9, 10, 13, 47].include?(nb)
|
|
151
|
-
pos = row_start +
|
|
162
|
+
pos = row_start + row_start_len
|
|
152
163
|
next
|
|
153
164
|
end
|
|
154
165
|
|
|
155
|
-
tag_end = xml.index(">", row_start +
|
|
166
|
+
tag_end = xml.index(">", row_start + row_start_len)
|
|
156
167
|
break unless tag_end
|
|
157
168
|
|
|
158
169
|
if xml.getbyte(tag_end - 1) == 47
|
|
@@ -168,7 +179,7 @@ module Xlsxrb
|
|
|
168
179
|
|
|
169
180
|
attrs = extract_row_attrs(row_tag)
|
|
170
181
|
|
|
171
|
-
row_end = xml.index(
|
|
182
|
+
row_end = xml.index(row_end_tag, tag_end + 1)
|
|
172
183
|
break unless row_end
|
|
173
184
|
|
|
174
185
|
row_source = { part: part_name, row: row_index }
|
|
@@ -178,7 +189,7 @@ module Xlsxrb
|
|
|
178
189
|
source: row_source
|
|
179
190
|
))
|
|
180
191
|
|
|
181
|
-
fast_scan_cells_events(xml, tag_end + 1, row_end, shared_strings, row_source, &block)
|
|
192
|
+
fast_scan_cells_events(xml, tag_end + 1, row_end, shared_strings, row_source, prefix, &block)
|
|
182
193
|
|
|
183
194
|
block.call(Event.new(
|
|
184
195
|
type: :row_end,
|
|
@@ -186,7 +197,7 @@ module Xlsxrb
|
|
|
186
197
|
source: row_source
|
|
187
198
|
))
|
|
188
199
|
|
|
189
|
-
pos = row_end +
|
|
200
|
+
pos = row_end + row_end_len
|
|
190
201
|
end
|
|
191
202
|
end
|
|
192
203
|
|
|
@@ -236,20 +247,38 @@ module Xlsxrb
|
|
|
236
247
|
|
|
237
248
|
private_class_method :tag_attr
|
|
238
249
|
|
|
239
|
-
def self.fast_scan_cells_events(xml, from, to, shared_strings, row_source, &block)
|
|
250
|
+
def self.fast_scan_cells_events(xml, from, to, shared_strings, row_source, prefix = "", &block)
|
|
240
251
|
pos = from
|
|
252
|
+
c_start_pattern = "<#{prefix}c"
|
|
253
|
+
c_start_len = c_start_pattern.bytesize
|
|
254
|
+
c_end_tag = "</#{prefix}c>"
|
|
255
|
+
c_end_len = c_end_tag.bytesize
|
|
256
|
+
|
|
257
|
+
v_tag_start_str = "<#{prefix}v>"
|
|
258
|
+
v_tag_start_len = v_tag_start_str.bytesize
|
|
259
|
+
v_tag_end_str = "</#{prefix}v>"
|
|
260
|
+
v_tag_end_len = v_tag_end_str.bytesize
|
|
261
|
+
|
|
262
|
+
is_tag_start_str = "<#{prefix}is>"
|
|
263
|
+
is_tag_start_len = is_tag_start_str.bytesize
|
|
264
|
+
is_tag_end_str = "</#{prefix}is>"
|
|
265
|
+
is_tag_end_len = is_tag_end_str.bytesize
|
|
266
|
+
|
|
267
|
+
f_tag_start_str = "<#{prefix}f"
|
|
268
|
+
f_tag_end_str = "</#{prefix}f>"
|
|
269
|
+
f_tag_end_len = f_tag_end_str.bytesize
|
|
241
270
|
|
|
242
271
|
while pos < to
|
|
243
|
-
c_start = xml.index(
|
|
272
|
+
c_start = xml.index(c_start_pattern, pos)
|
|
244
273
|
break unless c_start && c_start < to
|
|
245
274
|
|
|
246
|
-
nb = xml.getbyte(c_start +
|
|
275
|
+
nb = xml.getbyte(c_start + c_start_len)
|
|
247
276
|
unless [32, 62, 9, 10, 13, 47].include?(nb)
|
|
248
|
-
pos = c_start +
|
|
277
|
+
pos = c_start + c_start_len
|
|
249
278
|
next
|
|
250
279
|
end
|
|
251
280
|
|
|
252
|
-
c_tag_end = xml.index(">", c_start +
|
|
281
|
+
c_tag_end = xml.index(">", c_start + c_start_len)
|
|
253
282
|
break unless c_tag_end
|
|
254
283
|
|
|
255
284
|
# Extract tag substring for bounded attribute search
|
|
@@ -272,7 +301,7 @@ module Xlsxrb
|
|
|
272
301
|
next
|
|
273
302
|
end
|
|
274
303
|
|
|
275
|
-
c_end = xml.index(
|
|
304
|
+
c_end = xml.index(c_end_tag, c_tag_end + 1)
|
|
276
305
|
break unless c_end
|
|
277
306
|
|
|
278
307
|
# Parse cell content sequentially (bounded to c_end - avoid unbounded scans)
|
|
@@ -284,35 +313,27 @@ module Xlsxrb
|
|
|
284
313
|
tag_pos = xml.index("<", cpos)
|
|
285
314
|
break unless tag_pos && tag_pos < c_end
|
|
286
315
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
if
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
raw_value = xml.byteslice(v_val_start, v_end - v_val_start)
|
|
295
|
-
value = resolve_fast_value(raw_value, type, shared_strings)
|
|
296
|
-
cpos = v_end + 4
|
|
297
|
-
else
|
|
298
|
-
cpos = tag_pos + 3
|
|
299
|
-
end
|
|
300
|
-
elsif xml.getbyte(tag_pos + 2) == 47 && xml.getbyte(tag_pos + 3) == 62 # <v/>
|
|
301
|
-
cpos = tag_pos + 4
|
|
316
|
+
if xml.byteslice(tag_pos, v_tag_start_len) == v_tag_start_str
|
|
317
|
+
v_val_start = tag_pos + v_tag_start_len
|
|
318
|
+
v_end = xml.index(v_tag_end_str, v_val_start)
|
|
319
|
+
if v_end
|
|
320
|
+
raw_value = xml.byteslice(v_val_start, v_end - v_val_start)
|
|
321
|
+
value = resolve_fast_value(raw_value, type, shared_strings)
|
|
322
|
+
cpos = v_end + v_tag_end_len
|
|
302
323
|
else
|
|
303
|
-
cpos = tag_pos +
|
|
324
|
+
cpos = tag_pos + v_tag_start_len
|
|
304
325
|
end
|
|
305
|
-
|
|
306
|
-
f_tag_end = xml.index(">", tag_pos +
|
|
326
|
+
elsif xml.byteslice(tag_pos, f_tag_start_str.bytesize) == f_tag_start_str
|
|
327
|
+
f_tag_end = xml.index(">", tag_pos + f_tag_start_str.bytesize)
|
|
307
328
|
if f_tag_end && f_tag_end < c_end
|
|
308
329
|
if xml.getbyte(f_tag_end - 1) == 47 # self-closing <f ... />
|
|
309
330
|
cpos = f_tag_end + 1
|
|
310
331
|
else
|
|
311
|
-
f_end = xml.index(
|
|
332
|
+
f_end = xml.index(f_tag_end_str, f_tag_end + 1)
|
|
312
333
|
if f_end && f_end <= c_end
|
|
313
334
|
formula = xml.byteslice(f_tag_end + 1, f_end - f_tag_end - 1).force_encoding("UTF-8")
|
|
314
335
|
formula = decode_xml_entities(formula) if formula.include?("&")
|
|
315
|
-
cpos = f_end +
|
|
336
|
+
cpos = f_end + f_tag_end_len
|
|
316
337
|
else
|
|
317
338
|
cpos = f_tag_end + 1
|
|
318
339
|
end
|
|
@@ -320,17 +341,13 @@ module Xlsxrb
|
|
|
320
341
|
else
|
|
321
342
|
cpos = tag_pos + 2
|
|
322
343
|
end
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
cpos = is_end + 5
|
|
329
|
-
else
|
|
330
|
-
cpos = tag_pos + 4
|
|
331
|
-
end
|
|
344
|
+
elsif xml.byteslice(tag_pos, is_tag_start_len) == is_tag_start_str
|
|
345
|
+
is_end = xml.index(is_tag_end_str, tag_pos + is_tag_start_len)
|
|
346
|
+
if is_end && is_end <= c_end
|
|
347
|
+
inline_str = extract_inline_text(xml, tag_pos + is_tag_start_len, is_end)
|
|
348
|
+
cpos = is_end + is_tag_end_len
|
|
332
349
|
else
|
|
333
|
-
cpos = tag_pos +
|
|
350
|
+
cpos = tag_pos + is_tag_start_len
|
|
334
351
|
end
|
|
335
352
|
else
|
|
336
353
|
# Skip unknown tag
|
|
@@ -348,7 +365,7 @@ module Xlsxrb
|
|
|
348
365
|
source: cell_source
|
|
349
366
|
))
|
|
350
367
|
|
|
351
|
-
pos = c_end +
|
|
368
|
+
pos = c_end + c_end_len
|
|
352
369
|
end
|
|
353
370
|
end
|
|
354
371
|
|
|
@@ -358,21 +375,32 @@ module Xlsxrb
|
|
|
358
375
|
result = +""
|
|
359
376
|
pos = from
|
|
360
377
|
while pos < to
|
|
361
|
-
t_start = xml.index("<
|
|
378
|
+
t_start = xml.index("<", pos)
|
|
362
379
|
break unless t_start && t_start < to
|
|
363
380
|
|
|
364
|
-
|
|
365
|
-
break unless
|
|
366
|
-
|
|
381
|
+
t_term = xml.index("t", t_start)
|
|
382
|
+
break unless t_term && t_term < to
|
|
383
|
+
|
|
384
|
+
t_tag_end = xml.index(">", t_term)
|
|
385
|
+
break unless t_tag_end && t_tag_end < to
|
|
367
386
|
|
|
368
|
-
|
|
387
|
+
if xml.getbyte(t_tag_end - 1) == 47 # <t ... />
|
|
388
|
+
pos = t_tag_end + 1
|
|
389
|
+
next
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
t_close = xml.index("</", t_tag_end + 1)
|
|
393
|
+
break unless t_close && t_close <= to
|
|
394
|
+
|
|
395
|
+
t_end = xml.index(">", t_close + 2)
|
|
369
396
|
break unless t_end && t_end <= to
|
|
370
397
|
|
|
371
|
-
|
|
372
|
-
|
|
398
|
+
text_segment = xml.byteslice(t_tag_end + 1, t_close - t_tag_end - 1).force_encoding("UTF-8")
|
|
399
|
+
text_segment = decode_xml_entities(text_segment) if text_segment.include?("&")
|
|
400
|
+
result << text_segment
|
|
401
|
+
|
|
402
|
+
pos = t_end + 1
|
|
373
403
|
end
|
|
374
|
-
result.force_encoding("UTF-8")
|
|
375
|
-
result = decode_xml_entities(result) if result.include?("&")
|
|
376
404
|
result
|
|
377
405
|
end
|
|
378
406
|
|
|
@@ -448,32 +476,43 @@ module Xlsxrb
|
|
|
448
476
|
def characters(_text); end
|
|
449
477
|
end
|
|
450
478
|
|
|
451
|
-
def self.fast_scan_rows_direct(xml_src, shared_strings,
|
|
479
|
+
def self.fast_scan_rows_direct(xml_src, shared_strings, _part_name, &block)
|
|
452
480
|
xml = xml_src.b # force ASCII-8BIT for O(1) byte indexing
|
|
453
481
|
|
|
454
|
-
|
|
482
|
+
sd_term = xml.index("sheetData")
|
|
483
|
+
return unless sd_term
|
|
484
|
+
|
|
485
|
+
sd_start = xml.rindex("<", sd_term)
|
|
455
486
|
return unless sd_start
|
|
456
487
|
|
|
488
|
+
prefix = xml.byteslice(sd_start + 1, sd_term - (sd_start + 1))
|
|
489
|
+
|
|
457
490
|
sd_open_end = xml.index(">", sd_start)
|
|
458
491
|
return unless sd_open_end
|
|
459
492
|
return if xml.getbyte(sd_open_end - 1) == 47 # self-closing <sheetData/>
|
|
460
493
|
|
|
461
|
-
|
|
494
|
+
sd_end_tag = "</#{prefix}sheetData>"
|
|
495
|
+
sd_end = xml.index(sd_end_tag, sd_open_end)
|
|
462
496
|
return unless sd_end
|
|
463
497
|
|
|
498
|
+
row_start_pattern = "<#{prefix}row"
|
|
499
|
+
row_start_len = row_start_pattern.bytesize
|
|
500
|
+
row_end_tag = "</#{prefix}row>"
|
|
501
|
+
row_end_len = row_end_tag.bytesize
|
|
502
|
+
|
|
464
503
|
pos = sd_open_end + 1
|
|
465
504
|
|
|
466
505
|
while pos < sd_end
|
|
467
|
-
row_start = xml.index(
|
|
506
|
+
row_start = xml.index(row_start_pattern, pos)
|
|
468
507
|
break unless row_start && row_start < sd_end
|
|
469
508
|
|
|
470
|
-
nb = xml.getbyte(row_start +
|
|
509
|
+
nb = xml.getbyte(row_start + row_start_len)
|
|
471
510
|
unless [32, 62, 9, 10, 13, 47].include?(nb)
|
|
472
|
-
pos = row_start +
|
|
511
|
+
pos = row_start + row_start_len
|
|
473
512
|
next
|
|
474
513
|
end
|
|
475
514
|
|
|
476
|
-
tag_end = xml.index(">", row_start +
|
|
515
|
+
tag_end = xml.index(">", row_start + row_start_len)
|
|
477
516
|
break unless tag_end
|
|
478
517
|
|
|
479
518
|
if xml.getbyte(tag_end - 1) == 47
|
|
@@ -483,7 +522,7 @@ module Xlsxrb
|
|
|
483
522
|
|
|
484
523
|
row_index = 0
|
|
485
524
|
has_custom_attrs = false
|
|
486
|
-
ri = row_start +
|
|
525
|
+
ri = row_start + row_start_len
|
|
487
526
|
while ri < tag_end
|
|
488
527
|
rb = xml.getbyte(ri)
|
|
489
528
|
if rb == 114 && xml.getbyte(ri + 1) == 61 && xml.getbyte(ri + 2) == 34 # r="
|
|
@@ -512,233 +551,146 @@ module Xlsxrb
|
|
|
512
551
|
EMPTY_HASH
|
|
513
552
|
end
|
|
514
553
|
|
|
515
|
-
row_end = xml.index(
|
|
554
|
+
row_end = xml.index(row_end_tag, tag_end + 1)
|
|
516
555
|
break unless row_end
|
|
517
556
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
557
|
+
row_obj = StreamRow.fast_create(
|
|
558
|
+
row_index,
|
|
559
|
+
xml,
|
|
560
|
+
tag_end + 1,
|
|
561
|
+
row_end,
|
|
562
|
+
shared_strings,
|
|
563
|
+
prefix,
|
|
564
|
+
attrs[:height],
|
|
565
|
+
attrs[:hidden] || false,
|
|
566
|
+
attrs[:custom_height] || false,
|
|
567
|
+
attrs[:outline_level]
|
|
529
568
|
)
|
|
530
569
|
block.call(row_obj)
|
|
531
570
|
|
|
532
|
-
pos = row_end +
|
|
571
|
+
pos = row_end + row_end_len
|
|
533
572
|
end
|
|
534
573
|
end
|
|
535
574
|
|
|
536
575
|
private_class_method :fast_scan_rows_direct
|
|
537
576
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
while pos < to
|
|
549
|
-
c_start = xml.index("<c", pos)
|
|
550
|
-
break unless c_start && c_start < to
|
|
577
|
+
COL_LETTERS = (0...16_384).map do |index|
|
|
578
|
+
result = +""
|
|
579
|
+
i = index
|
|
580
|
+
loop do
|
|
581
|
+
result.prepend(("A".ord + (i % 26)).chr)
|
|
582
|
+
i = (i / 26) - 1
|
|
583
|
+
break if i.negative?
|
|
584
|
+
end
|
|
585
|
+
result.freeze
|
|
586
|
+
end.freeze
|
|
551
587
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
next
|
|
556
|
-
end
|
|
588
|
+
COL_MAP = (0...16_384).to_h do |idx|
|
|
589
|
+
[COL_LETTERS[idx], idx]
|
|
590
|
+
end.freeze
|
|
557
591
|
|
|
558
|
-
|
|
559
|
-
|
|
592
|
+
CELL_FAST_RE = %r{<(?:[a-zA-Z0-9_]+:)?c\s+r="([A-Za-z0-9]+)"(?:[^>]*?s="(\d+)")?(?:[^>]*?t="([a-zA-Z]+)")?[^>]*>(?:<(?:[a-zA-Z0-9_]+:)?f[^>]*>([^<]*)</(?:[a-zA-Z0-9_]+:)?f>)?(?:<(?:[a-zA-Z0-9_]+:)?v>([^<]*)</(?:[a-zA-Z0-9_]+:)?v>)?(?:<(?:[a-zA-Z0-9_]+:)?is>(.*?)</(?:[a-zA-Z0-9_]+:)?is>)?</(?:[a-zA-Z0-9_]+:)?c>|<(?:[a-zA-Z0-9_]+:)?c\s+r="([A-Za-z0-9]+)"(?:[^>]*?s="(\d+)")?[^>]*/>}
|
|
593
|
+
CELL_GENERIC_RE = %r{<(?:[a-zA-Z0-9_]+:)?c\b([^>]*?)(?:>(?:<(?:[a-zA-Z0-9_]+:)?f[^>]*>([^<]*)</(?:[a-zA-Z0-9_]+:)?f>)?(?:<(?:[a-zA-Z0-9_]+:)?v>([^<]*)</(?:[a-zA-Z0-9_]+:)?v>)?(?:<(?:[a-zA-Z0-9_]+:)?is>(.*?)</(?:[a-zA-Z0-9_]+:)?is>)?</(?:[a-zA-Z0-9_]+:)?c>|/>)}m
|
|
594
|
+
ATTR_R = /r="([A-Za-z0-9]+)"/
|
|
595
|
+
ATTR_T = /t="([a-zA-Z]+)"/
|
|
596
|
+
ATTR_S = /s="(\d+)"/
|
|
560
597
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
ai = c_start + 2
|
|
568
|
-
while ai < c_tag_end
|
|
569
|
-
b = xml.getbyte(ai)
|
|
570
|
-
if [32, 9, 10, 13].include?(b)
|
|
571
|
-
ai += 1
|
|
572
|
-
next
|
|
573
|
-
end
|
|
598
|
+
def self.fast_parse_cells_direct(xml, from, to, shared_strings, row_source, prefix = "")
|
|
599
|
+
cells = []
|
|
600
|
+
fast_scan_cells_direct(xml, from, to, shared_strings, row_source, prefix) { |c| cells << c }
|
|
601
|
+
cells
|
|
602
|
+
end
|
|
574
603
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
604
|
+
def self.fast_scan_cells_direct(xml, from, to, shared_strings, row_source, _prefix = "", &block)
|
|
605
|
+
chunk = xml.byteslice(from, to - from)
|
|
606
|
+
row_idx = row_source.is_a?(Hash) ? row_source[:row] : row_source
|
|
607
|
+
col_idx = 0
|
|
608
|
+
matched_any = false
|
|
609
|
+
|
|
610
|
+
chunk.scan(CELL_FAST_RE) do |r, s, t, f, v, is, self_r, self_s|
|
|
611
|
+
matched_any = true
|
|
612
|
+
ref = r || self_r
|
|
613
|
+
s_val = s || self_s
|
|
614
|
+
style_idx = s_val&.to_i
|
|
615
|
+
c_idx = if ref
|
|
616
|
+
expected_letter = COL_LETTERS[col_idx]
|
|
617
|
+
if expected_letter && ref.start_with?(expected_letter)
|
|
618
|
+
col_idx
|
|
619
|
+
else
|
|
620
|
+
c_len = 0
|
|
621
|
+
c_len += 1 while (b = ref.getbyte(c_len)) && (b.between?(65, 90) || b.between?(97, 122))
|
|
622
|
+
col_letters = ref.byteslice(0, c_len).upcase
|
|
623
|
+
COL_MAP[col_letters] || col_idx
|
|
624
|
+
end
|
|
625
|
+
else
|
|
626
|
+
col_idx
|
|
627
|
+
end
|
|
628
|
+
col_idx = c_idx + 1
|
|
629
|
+
|
|
630
|
+
val = if t == "s"
|
|
631
|
+
shared_strings[v.to_i] || ""
|
|
632
|
+
elsif t == "b"
|
|
633
|
+
v == "1"
|
|
634
|
+
elsif %w[inlineStr str e].include?(t)
|
|
635
|
+
if is
|
|
636
|
+
extract_inline_text(is, 0, is.bytesize)
|
|
637
|
+
elsif v
|
|
638
|
+
v.include?("&") ? decode_xml_entities(v) : v
|
|
639
|
+
else
|
|
640
|
+
""
|
|
641
|
+
end
|
|
642
|
+
elsif v
|
|
643
|
+
v.include?(".") ? v.to_f : v.to_i
|
|
588
644
|
end
|
|
589
|
-
end
|
|
590
|
-
col_idx -= 1
|
|
591
|
-
row_idx = 0
|
|
592
|
-
while ai < c_tag_end
|
|
593
|
-
cb = xml.getbyte(ai)
|
|
594
|
-
break unless cb.between?(48, 57)
|
|
595
|
-
|
|
596
|
-
row_idx = (row_idx * 10) + (cb - 48)
|
|
597
|
-
ai += 1
|
|
598
|
-
end
|
|
599
|
-
row_idx -= 1
|
|
600
|
-
ai += 1 if xml.getbyte(ai) == 34
|
|
601
|
-
elsif b == 116 && xml.getbyte(ai + 1) == 61 && xml.getbyte(ai + 2) == 34 # t="
|
|
602
|
-
ai += 3
|
|
603
|
-
type_b = xml.getbyte(ai)
|
|
604
|
-
if type_b == 115 && xml.getbyte(ai + 1) == 34 # "s"
|
|
605
|
-
type = "s"
|
|
606
|
-
ai += 2
|
|
607
|
-
elsif type_b == 98 && xml.getbyte(ai + 1) == 34 # "b"
|
|
608
|
-
type = "b"
|
|
609
|
-
ai += 2
|
|
610
|
-
elsif type_b == 101 && xml.getbyte(ai + 1) == 34 # "e"
|
|
611
|
-
type = "e"
|
|
612
|
-
ai += 2
|
|
613
|
-
else
|
|
614
|
-
t_end = xml.index('"', ai)
|
|
615
|
-
type = xml.byteslice(ai, t_end - ai) if t_end
|
|
616
|
-
ai = t_end ? t_end + 1 : c_tag_end
|
|
617
|
-
end
|
|
618
|
-
elsif b == 115 && xml.getbyte(ai + 1) == 61 && xml.getbyte(ai + 2) == 34 # s="
|
|
619
|
-
ai += 3
|
|
620
|
-
style_index = 0
|
|
621
|
-
while ai < c_tag_end
|
|
622
|
-
cb = xml.getbyte(ai)
|
|
623
|
-
break unless cb.between?(48, 57)
|
|
624
645
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
else
|
|
630
|
-
ai += 1
|
|
631
|
-
end
|
|
632
|
-
end
|
|
633
|
-
|
|
634
|
-
# Self-closing <c ... />
|
|
635
|
-
if xml.getbyte(c_tag_end - 1) == 47
|
|
636
|
-
cell_obj = Elements::Cell.new(
|
|
637
|
-
row_index: row_idx || row_source[:row],
|
|
638
|
-
column_index: col_idx || cell_count,
|
|
639
|
-
value: nil,
|
|
640
|
-
style_index: style_index,
|
|
641
|
-
errors: Elements::EMPTY_ERRORS
|
|
642
|
-
)
|
|
643
|
-
cell_count += 1
|
|
644
|
-
block.call(cell_obj)
|
|
645
|
-
pos = c_tag_end + 1
|
|
646
|
-
next
|
|
647
|
-
end
|
|
648
|
-
|
|
649
|
-
c_end = xml.index("</c>", c_tag_end + 1)
|
|
650
|
-
break unless c_end
|
|
651
|
-
|
|
652
|
-
# Parse cell content sequentially (bounded to c_end - avoid unbounded scans)
|
|
653
|
-
value = nil
|
|
654
|
-
formula = nil
|
|
655
|
-
inline_str = nil
|
|
656
|
-
cpos = c_tag_end + 1
|
|
657
|
-
while cpos < c_end
|
|
658
|
-
tag_pos = xml.index("<", cpos)
|
|
659
|
-
break unless tag_pos && tag_pos < c_end
|
|
646
|
+
formula_expr = f&.include?("&") ? decode_xml_entities(f) : f
|
|
647
|
+
cell = Elements::Cell.fast_create(row_idx, c_idx, val, style_idx, formula_expr)
|
|
648
|
+
block.call(cell)
|
|
649
|
+
end
|
|
660
650
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
651
|
+
return if matched_any || (!chunk.include?("<c") && !chunk.include?(":c"))
|
|
652
|
+
|
|
653
|
+
# Fallback for non-standard attribute ordering
|
|
654
|
+
chunk.scan(CELL_GENERIC_RE) do |attrs, f, v, is|
|
|
655
|
+
r = attrs[ATTR_R, 1]
|
|
656
|
+
t = attrs[ATTR_T, 1]
|
|
657
|
+
s = attrs[ATTR_S, 1]
|
|
658
|
+
|
|
659
|
+
c_idx = if r
|
|
660
|
+
expected_letter = COL_LETTERS[col_idx]
|
|
661
|
+
if expected_letter && r.start_with?(expected_letter)
|
|
662
|
+
col_idx
|
|
663
|
+
else
|
|
664
|
+
c_len = 0
|
|
665
|
+
c_len += 1 while (b = r.getbyte(c_len)) && (b.between?(65, 90) || b.between?(97, 122))
|
|
666
|
+
col_letters = r.byteslice(0, c_len).upcase
|
|
667
|
+
COL_MAP[col_letters] || col_idx
|
|
675
668
|
end
|
|
676
|
-
value = shared_strings[s_idx] || ""
|
|
677
|
-
elsif type == "b"
|
|
678
|
-
value = xml.getbyte(v_val_start) == 49 # '1'
|
|
679
669
|
else
|
|
680
|
-
|
|
681
|
-
value = resolve_fast_value(raw_value, type, shared_strings)
|
|
670
|
+
col_idx
|
|
682
671
|
end
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
if f_tag_end && f_tag_end < c_end
|
|
695
|
-
if xml.getbyte(f_tag_end - 1) == 47 # self-closing <f ... />
|
|
696
|
-
cpos = f_tag_end + 1
|
|
697
|
-
else
|
|
698
|
-
f_end = xml.index("</f>", f_tag_end + 1)
|
|
699
|
-
if f_end && f_end <= c_end
|
|
700
|
-
formula = xml.byteslice(f_tag_end + 1, f_end - f_tag_end - 1).force_encoding("UTF-8")
|
|
701
|
-
formula = decode_xml_entities(formula) if formula.include?("&")
|
|
702
|
-
cpos = f_end + 4
|
|
672
|
+
col_idx = c_idx + 1
|
|
673
|
+
|
|
674
|
+
val = if t == "s"
|
|
675
|
+
shared_strings[v.to_i] || ""
|
|
676
|
+
elsif t == "b"
|
|
677
|
+
v == "1"
|
|
678
|
+
elsif %w[inlineStr str e].include?(t)
|
|
679
|
+
if is
|
|
680
|
+
extract_inline_text(is, 0, is.bytesize)
|
|
681
|
+
elsif v
|
|
682
|
+
v.include?("&") ? decode_xml_entities(v) : v
|
|
703
683
|
else
|
|
704
|
-
|
|
684
|
+
""
|
|
705
685
|
end
|
|
686
|
+
elsif v
|
|
687
|
+
v.include?(".") ? v.to_f : v.to_i
|
|
706
688
|
end
|
|
707
|
-
else
|
|
708
|
-
cpos = tag_pos + 2
|
|
709
|
-
end
|
|
710
|
-
when 105 # 'i' - <is>
|
|
711
|
-
if xml.byteslice(tag_pos, 4) == "<is>"
|
|
712
|
-
is_end = xml.index("</is>", tag_pos + 4)
|
|
713
|
-
if is_end && is_end <= c_end
|
|
714
|
-
inline_str = extract_inline_text(xml, tag_pos + 4, is_end)
|
|
715
|
-
cpos = is_end + 5
|
|
716
|
-
else
|
|
717
|
-
cpos = tag_pos + 4
|
|
718
|
-
end
|
|
719
|
-
else
|
|
720
|
-
cpos = tag_pos + 2
|
|
721
|
-
end
|
|
722
|
-
else
|
|
723
|
-
# Skip unknown tag
|
|
724
|
-
close = xml.index(">", tag_pos + 1)
|
|
725
|
-
cpos = close ? close + 1 : c_end
|
|
726
|
-
end
|
|
727
|
-
end
|
|
728
|
-
|
|
729
|
-
val_to_use = inline_str || value
|
|
730
|
-
cell_obj = Elements::Cell.new(
|
|
731
|
-
row_index: row_idx || row_source[:row],
|
|
732
|
-
column_index: col_idx || cell_count,
|
|
733
|
-
value: val_to_use,
|
|
734
|
-
formula: formula,
|
|
735
|
-
style_index: style_index,
|
|
736
|
-
errors: Elements::EMPTY_ERRORS
|
|
737
|
-
)
|
|
738
|
-
cell_count += 1
|
|
739
|
-
block.call(cell_obj)
|
|
740
689
|
|
|
741
|
-
|
|
690
|
+
style_idx = s&.to_i
|
|
691
|
+
formula_expr = f&.include?("&") ? decode_xml_entities(f) : f
|
|
692
|
+
cell = Elements::Cell.fast_create(row_idx, c_idx, val, style_idx, formula_expr)
|
|
693
|
+
block.call(cell)
|
|
742
694
|
end
|
|
743
695
|
end
|
|
744
696
|
|