calcsheet-native 0.1.0

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.
@@ -0,0 +1,814 @@
1
+ //! The cells of an XLSX sheet part, read from its XML as calcsheet's `Xlsx::SheetParser` reads them through Ox, for
2
+ //! XML that keeps to a plain subset: elements, attributes and text, a leading XML declaration, well formed. Anything
3
+ //! else (a comment, CDATA, a DOCTYPE, a reference in an attribute, an odd name, a value calcsheet would refuse...)
4
+ //! makes the sheet `Unsupported`: the Ruby side then reads it with calcsheet's own parser, which gives its exact
5
+ //! result or error. The state machine follows `SheetParser`'s callbacks one for one.
6
+ //!
7
+ //! The texts of the cells are copied once, into `out`, which the rows of a batch point into.
8
+
9
+ use std::sync::Arc;
10
+
11
+ use memchr::memchr;
12
+
13
+ use crate::xml::{Item, Lexer, Result, Unsupported, decode, line_ends, local, utf8};
14
+
15
+ /// The rows and columns of calcsheet's grid.
16
+ const MAX_ROWS: u32 = 1_048_576;
17
+ const MAX_COLUMNS: u32 = 16_384;
18
+ const LAST_COLUMN_INDEX: u32 = MAX_COLUMNS - 1;
19
+ /// Cast::MAX_NUMBER_BYTES: the longest value of a number, date, boolean, error or shared string index.
20
+ const MAX_NUMBER_BYTES: usize = 1_024;
21
+
22
+ /// The type of a cell, as `SheetParser#finish_cell` dispatches it.
23
+ #[derive(Clone, Copy, PartialEq, Eq)]
24
+ pub enum Kind {
25
+ Number = 0,
26
+ Shared = 1,
27
+ Inline = 2,
28
+ Boolean = 3,
29
+ Error = 4,
30
+ Date = 5,
31
+ Other = 6,
32
+ }
33
+
34
+ /// A cell with a text: its column, kind (for `Shared`, the text is the shared string), the type text for `Other`, its style (`None` without one, `Some(-1)` for
35
+ /// one that is not one to ten digits), its text in `out` and whether that text still needs its line ends and
36
+ /// references read (a single piece holding "\r" or "&").
37
+ pub struct Cell {
38
+ pub column: u32,
39
+ pub kind: Kind,
40
+ pub type_text: Option<Vec<u8>>,
41
+ pub style: Option<i64>,
42
+ pub start: usize,
43
+ pub end: usize,
44
+ pub raw: bool,
45
+ }
46
+
47
+ pub struct Row {
48
+ pub number: u32,
49
+ pub cells: Vec<Cell>,
50
+ }
51
+
52
+ pub enum Stop {
53
+ NeedMore,
54
+ /// The end of sheetData, or of a document without it.
55
+ End,
56
+ /// The Ruby parser must read this sheet.
57
+ Unsupported,
58
+ /// A cell needs the shared strings, which calcsheet loads when the first cell needs them: hand them over with
59
+ /// #set_strings and ask again.
60
+ NeedStrings,
61
+ Full,
62
+ }
63
+
64
+ /// The shared strings table as calcsheet keeps it: the bytes of all strings, and the end of each (offset `i + 1`)
65
+ /// after a leading 0. One copy serves the sheet readers and the Ruby side.
66
+ pub struct Strings {
67
+ pub buffer: Vec<u8>,
68
+ pub offsets: Vec<u64>,
69
+ }
70
+
71
+ impl Strings {
72
+ pub fn size(&self) -> usize {
73
+ self.offsets.len() - 1
74
+ }
75
+
76
+ /// The string at `index`, 0 to `size - 1`.
77
+ pub fn get(&self, index: usize) -> &[u8] {
78
+ &self.buffer[self.offsets[index] as usize..self.offsets[index + 1] as usize]
79
+ }
80
+ }
81
+
82
+ /// The elements SheetParser reads, by local name.
83
+ #[derive(Clone, Copy, PartialEq, Eq)]
84
+ enum Element {
85
+ C,
86
+ V,
87
+ T,
88
+ Row,
89
+ Is,
90
+ RPh,
91
+ SheetData,
92
+ Other,
93
+ }
94
+
95
+ /// The `t` of a cell.
96
+ #[derive(Clone, Copy, PartialEq, Eq)]
97
+ enum CellType {
98
+ Absent,
99
+ N,
100
+ S,
101
+ Inline,
102
+ B,
103
+ E,
104
+ D,
105
+ Other,
106
+ }
107
+
108
+ pub struct SheetReader {
109
+ max_cell_bytes: Option<usize>,
110
+ lexer: Lexer,
111
+ done: bool,
112
+ unsupported: bool,
113
+ // The last item was a start tag that is not empty: Ox hands over an empty text when its end tag follows.
114
+ just_started: bool,
115
+ strings: Option<Arc<Strings>>,
116
+ need_strings: bool,
117
+ // The texts of the rows handed out, and the bytes of them the next batch drops.
118
+ out: Vec<u8>,
119
+ consumed: usize,
120
+ row_start: usize,
121
+ // SheetParser's state.
122
+ last_number: u32,
123
+ in_row: bool,
124
+ row_number: Option<u32>,
125
+ next_column: u32,
126
+ in_cell: bool,
127
+ column: Option<u32>,
128
+ cell_type: CellType,
129
+ other_type: Vec<u8>,
130
+ style: Option<i64>,
131
+ text: Option<usize>,
132
+ pieces: usize,
133
+ special: bool,
134
+ capture: bool,
135
+ inline: bool,
136
+ phonetic: u32,
137
+ text_limit: Option<usize>,
138
+ cells: Vec<Cell>,
139
+ }
140
+
141
+ impl SheetReader {
142
+ pub fn new(max_cell_bytes: Option<usize>) -> Self {
143
+ SheetReader {
144
+ max_cell_bytes,
145
+ lexer: Lexer::new(),
146
+ done: false,
147
+ unsupported: false,
148
+ just_started: false,
149
+ strings: None,
150
+ need_strings: false,
151
+ out: Vec::new(),
152
+ consumed: 0,
153
+ row_start: 0,
154
+ last_number: 0,
155
+ in_row: false,
156
+ row_number: None,
157
+ next_column: 0,
158
+ in_cell: false,
159
+ column: None,
160
+ cell_type: CellType::Absent,
161
+ other_type: Vec::new(),
162
+ style: None,
163
+ text: None,
164
+ pieces: 0,
165
+ special: false,
166
+ capture: false,
167
+ inline: false,
168
+ phonetic: 0,
169
+ text_limit: None,
170
+ cells: Vec::new(),
171
+ }
172
+ }
173
+
174
+ pub fn feed(&mut self, chunk: &[u8]) {
175
+ self.lexer.feed(chunk);
176
+ }
177
+
178
+ pub fn finish(&mut self) {
179
+ self.lexer.eof = true;
180
+ }
181
+
182
+ /// The shared strings table: `offsets` holds native-endian 64-bit ends after a leading 0, as calcsheet's
183
+ /// SharedStrings packs them.
184
+ pub fn set_strings(&mut self, buffer: &[u8], offsets: &[u8]) {
185
+ let offsets = offsets
186
+ .as_chunks::<8>()
187
+ .0
188
+ .iter()
189
+ .map(|bytes| u64::from_ne_bytes(*bytes))
190
+ .collect();
191
+ self.set_table(Arc::new(Strings {
192
+ buffer: buffer.to_vec(),
193
+ offsets,
194
+ }));
195
+ }
196
+
197
+ /// The shared strings table read by a StringsReader, shared.
198
+ pub fn set_table(&mut self, strings: Arc<Strings>) {
199
+ self.strings = Some(strings);
200
+ self.need_strings = false;
201
+ }
202
+
203
+ /// The text of a cell of the last batch.
204
+ pub fn text(&self, cell: &Cell) -> &[u8] {
205
+ &self.out[cell.start..cell.end]
206
+ }
207
+
208
+ /// Reads items until `max` rows are complete, the text fed so far ends, or the sheet ends.
209
+ pub fn rows(&mut self, max: usize) -> (Vec<Row>, Stop) {
210
+ self.drop_consumed();
211
+ let mut rows = Vec::new();
212
+ let stop = if self.unsupported {
213
+ Stop::Unsupported
214
+ } else if self.done {
215
+ Stop::End
216
+ } else {
217
+ self.read(max, &mut rows)
218
+ };
219
+ if matches!(stop, Stop::Unsupported) {
220
+ self.unsupported = true;
221
+ }
222
+ self.consumed = if self.in_row {
223
+ self.row_start
224
+ } else {
225
+ self.out.len()
226
+ };
227
+ (rows, stop)
228
+ }
229
+
230
+ fn read(&mut self, max: usize, rows: &mut Vec<Row>) -> Stop {
231
+ while rows.len() < max {
232
+ let result = match self.lexer.item() {
233
+ Ok(Some(item)) => self.handle(item, rows),
234
+ Ok(None) if self.lexer.eof => {
235
+ // The text ends: a complete document without sheetData ends as Ox's parse does.
236
+ if self.lexer.complete() {
237
+ self.done = true;
238
+ return Stop::End;
239
+ }
240
+ return Stop::Unsupported;
241
+ }
242
+ Ok(None) => return Stop::NeedMore,
243
+ Err(Unsupported) => return Stop::Unsupported,
244
+ };
245
+ match result {
246
+ Ok(true) => {
247
+ self.done = true;
248
+ return Stop::End;
249
+ }
250
+ Ok(false) if self.need_strings => return Stop::NeedStrings,
251
+ Ok(false) => {}
252
+ Err(Unsupported) => return Stop::Unsupported,
253
+ }
254
+ }
255
+ Stop::Full
256
+ }
257
+
258
+ /// Drops the texts of the rows handed out, keeping those of the row being read.
259
+ fn drop_consumed(&mut self) {
260
+ let consumed = self.consumed;
261
+ if consumed == 0 {
262
+ return;
263
+ }
264
+ self.out.drain(..consumed);
265
+ for cell in &mut self.cells {
266
+ cell.start -= consumed;
267
+ cell.end -= consumed;
268
+ }
269
+ if let Some(text) = self.text.as_mut() {
270
+ *text -= consumed;
271
+ }
272
+ self.row_start = self.row_start.saturating_sub(consumed);
273
+ self.consumed = 0;
274
+ }
275
+
276
+ // ---- SheetParser's callbacks ----
277
+
278
+ /// Handles an item; true at the end of sheetData.
279
+ fn handle(&mut self, item: Item, rows: &mut Vec<Row>) -> Result<bool> {
280
+ let just_started = std::mem::replace(&mut self.just_started, false);
281
+ match item {
282
+ Item::Text(start, end) => {
283
+ if self.capture {
284
+ self.text_piece(start, end)?;
285
+ }
286
+ Ok(false)
287
+ }
288
+ Item::Start(name_start, name_end, empty) => {
289
+ let element = element(local(&self.lexer.buf[name_start..name_end]));
290
+ self.start(element)?;
291
+ if empty {
292
+ self.end(element, rows)
293
+ } else {
294
+ self.just_started = true;
295
+ Ok(false)
296
+ }
297
+ }
298
+ Item::End(name_start, name_end) => {
299
+ let element = element(local(&self.lexer.buf[name_start..name_end]));
300
+ // calcsheet loads the shared strings at the first cell that needs them: the end tag is read again
301
+ // once they are here.
302
+ if element == Element::C
303
+ && self.in_cell
304
+ && self.cell_type == CellType::S
305
+ && self.text.is_some()
306
+ && self.strings.is_none()
307
+ {
308
+ self.lexer.unread_end();
309
+ self.just_started = just_started;
310
+ self.need_strings = true;
311
+ return Ok(false);
312
+ }
313
+ // "<v></v>": Ox hands over an empty text ("<v/>" has none).
314
+ if just_started && self.capture {
315
+ self.text_piece(self.lexer.pos, self.lexer.pos)?;
316
+ }
317
+ self.end(element, rows)
318
+ }
319
+ }
320
+ }
321
+
322
+ fn start(&mut self, element: Element) -> Result<()> {
323
+ // An element inside a captured text would split it in pieces that Ox may hand over differently.
324
+ if self.capture {
325
+ return Err(Unsupported);
326
+ }
327
+ match element {
328
+ Element::C => {
329
+ if self.in_cell || !self.in_row {
330
+ return Err(Unsupported);
331
+ }
332
+ self.in_cell = true;
333
+ self.column = None;
334
+ self.cell_type = CellType::Absent;
335
+ self.style = None;
336
+ self.text = None;
337
+ self.pieces = 0;
338
+ self.special = false;
339
+ self.inline = false;
340
+ self.check_plain()?;
341
+ for index in 0..self.lexer.attrs.len() {
342
+ let (name_start, name_end, value_start, value_end) = self.lexer.attrs[index];
343
+ let value = &self.lexer.buf[value_start..value_end];
344
+ match &self.lexer.buf[name_start..name_end] {
345
+ b"r" => self.column = column_of(value)?,
346
+ b"t" => {
347
+ utf8(value)?;
348
+ self.cell_type = match value {
349
+ b"n" => CellType::N,
350
+ b"s" => CellType::S,
351
+ b"inlineStr" | b"str" => CellType::Inline,
352
+ b"b" => CellType::B,
353
+ b"e" => CellType::E,
354
+ b"d" => CellType::D,
355
+ other => {
356
+ self.other_type.clear();
357
+ self.other_type.extend_from_slice(other);
358
+ CellType::Other
359
+ }
360
+ };
361
+ }
362
+ b"s" => self.style = Some(style_index(value)),
363
+ _ => {}
364
+ }
365
+ }
366
+ }
367
+ Element::V => {
368
+ self.capture = self.in_cell;
369
+ let bounded = matches!(
370
+ self.cell_type,
371
+ CellType::Absent
372
+ | CellType::N
373
+ | CellType::S
374
+ | CellType::B
375
+ | CellType::E
376
+ | CellType::D
377
+ );
378
+ self.text_limit = if bounded {
379
+ Some(MAX_NUMBER_BYTES)
380
+ } else {
381
+ self.max_cell_bytes
382
+ };
383
+ }
384
+ Element::T => {
385
+ self.capture = self.inline && self.phonetic == 0;
386
+ self.text_limit = self.max_cell_bytes;
387
+ }
388
+ Element::Row => {
389
+ if self.in_row {
390
+ return Err(Unsupported);
391
+ }
392
+ self.in_row = true;
393
+ self.row_number = None;
394
+ self.next_column = 0;
395
+ self.row_start = self.out.len();
396
+ self.check_plain()?;
397
+ for index in 0..self.lexer.attrs.len() {
398
+ let (name_start, name_end, value_start, value_end) = self.lexer.attrs[index];
399
+ if &self.lexer.buf[name_start..name_end] == b"r" {
400
+ self.row_number = Some(self.row_number_of(value_start, value_end)?);
401
+ }
402
+ }
403
+ }
404
+ Element::Is => self.inline = self.in_cell,
405
+ Element::RPh => self.phonetic += 1,
406
+ Element::SheetData | Element::Other => {}
407
+ }
408
+ Ok(())
409
+ }
410
+
411
+ fn end(&mut self, element: Element, rows: &mut Vec<Row>) -> Result<bool> {
412
+ match element {
413
+ Element::V | Element::T => self.capture = false,
414
+ Element::C => {
415
+ if self.in_cell {
416
+ self.finish_cell()?;
417
+ }
418
+ self.text = None;
419
+ self.capture = false;
420
+ self.inline = false;
421
+ }
422
+ Element::Row => {
423
+ if self.in_row {
424
+ self.finish_row(rows)?;
425
+ }
426
+ }
427
+ Element::Is => self.inline = false,
428
+ Element::RPh => self.phonetic = self.phonetic.saturating_sub(1),
429
+ Element::SheetData => return Ok(true),
430
+ Element::Other => {}
431
+ }
432
+ Ok(false)
433
+ }
434
+
435
+ /// The attributes of a cell or row, refused when one is prefixed or repeated (Ox would read them its own way).
436
+ fn check_plain(&self) -> Result<()> {
437
+ let lexer = &self.lexer;
438
+ if lexer
439
+ .attrs
440
+ .iter()
441
+ .any(|&(start, end, _, _)| memchr(b':', &lexer.buf[start..end]).is_some())
442
+ {
443
+ return Err(Unsupported);
444
+ }
445
+ lexer.check_unique()
446
+ }
447
+
448
+ /// A piece of the cell's text. A text of several pieces is joined here only when no piece needs its line ends or
449
+ /// references read, which calcsheet does per piece; a text too long, or not UTF-8, is left to calcsheet.
450
+ fn text_piece(&mut self, start: usize, end: usize) -> Result<()> {
451
+ let piece = &self.lexer.buf[start..end];
452
+ let special = memchr(b'&', piece).is_some() || memchr(b'\r', piece).is_some();
453
+ self.pieces += 1;
454
+ if self.pieces > 1 && (special || self.special) {
455
+ return Err(Unsupported);
456
+ }
457
+ self.special |= special;
458
+ let text_start = *self.text.get_or_insert(self.out.len());
459
+ let length = self.out.len() - text_start + piece.len();
460
+ if self.text_limit.is_some_and(|limit| length > limit) {
461
+ return Err(Unsupported);
462
+ }
463
+ utf8(piece)?;
464
+ self.out.extend_from_slice(piece);
465
+ Ok(())
466
+ }
467
+
468
+ fn finish_cell(&mut self) -> Result<()> {
469
+ self.in_cell = false;
470
+ let column = self.column.unwrap_or(self.next_column);
471
+ if column > LAST_COLUMN_INDEX {
472
+ return Err(Unsupported);
473
+ }
474
+ self.next_column = column + 1;
475
+ let Some(start) = self.text.take() else {
476
+ return Ok(());
477
+ };
478
+ let (kind, type_text) = match self.cell_type {
479
+ CellType::Absent | CellType::N => (Kind::Number, None),
480
+ CellType::S => {
481
+ self.shared_string(start)?;
482
+ (Kind::Shared, None)
483
+ }
484
+ CellType::Inline => (Kind::Inline, None),
485
+ CellType::B => (Kind::Boolean, None),
486
+ CellType::E => (Kind::Error, None),
487
+ CellType::D => (Kind::Date, None),
488
+ CellType::Other => (Kind::Other, Some(self.other_type.clone())),
489
+ };
490
+ self.cells.push(Cell {
491
+ column,
492
+ kind,
493
+ type_text,
494
+ style: self.style,
495
+ start,
496
+ end: self.out.len(),
497
+ raw: self.special,
498
+ });
499
+ Ok(())
500
+ }
501
+
502
+ /// SheetParser#shared_string: the text from `start` in `out` is the index, replaced by the string it names. An
503
+ /// index that is not plain digits, outside the table, and a string that is not UTF-8 are left to calcsheet.
504
+ fn shared_string(&mut self, start: usize) -> Result<()> {
505
+ let index = &self.out[start..];
506
+ if self.special
507
+ || index.is_empty()
508
+ || index.len() > 18
509
+ || !index.iter().all(u8::is_ascii_digit)
510
+ {
511
+ return Err(Unsupported);
512
+ }
513
+ let index = index.iter().fold(0usize, |index, &digit| {
514
+ index * 10 + usize::from(digit - b'0')
515
+ });
516
+ let strings = self.strings.as_ref().ok_or(Unsupported)?;
517
+ if index >= strings.size() {
518
+ return Err(Unsupported);
519
+ }
520
+ let text = strings.get(index);
521
+ utf8(text)?;
522
+ self.out.truncate(start);
523
+ self.out.extend_from_slice(text);
524
+ Ok(())
525
+ }
526
+
527
+ fn finish_row(&mut self, rows: &mut Vec<Row>) -> Result<()> {
528
+ self.in_row = false;
529
+ let number = match self.row_number {
530
+ Some(number) => number,
531
+ None if self.last_number < MAX_ROWS => self.last_number + 1,
532
+ None => return Err(Unsupported),
533
+ };
534
+ self.last_number = number;
535
+ if !self.cells.is_empty() {
536
+ let cells = std::mem::take(&mut self.cells);
537
+ rows.push(Row { number, cells });
538
+ }
539
+ Ok(())
540
+ }
541
+
542
+ /// SheetParser#row_number: digits without a leading zero, at most seven, 1 to MAX_ROWS, after the last row.
543
+ fn row_number_of(&self, start: usize, end: usize) -> Result<u32> {
544
+ let value = &self.lexer.buf[start..end];
545
+ let canonical = (1..=7).contains(&value.len())
546
+ && value[0] != b'0'
547
+ && value.iter().all(u8::is_ascii_digit);
548
+ if !canonical {
549
+ return Err(Unsupported);
550
+ }
551
+ let number = value
552
+ .iter()
553
+ .fold(0u32, |number, &digit| number * 10 + u32::from(digit - b'0'));
554
+ if number > MAX_ROWS || number <= self.last_number {
555
+ return Err(Unsupported);
556
+ }
557
+ Ok(number)
558
+ }
559
+ }
560
+
561
+ fn element(name: &[u8]) -> Element {
562
+ match name {
563
+ b"c" => Element::C,
564
+ b"v" => Element::V,
565
+ b"t" => Element::T,
566
+ b"row" => Element::Row,
567
+ b"is" => Element::Is,
568
+ b"rPh" => Element::RPh,
569
+ b"sheetData" => Element::SheetData,
570
+ _ => Element::Other,
571
+ }
572
+ }
573
+
574
+ /// SheetParser#column_of: the 0-based column of the letters of a reference, `None` without letters.
575
+ fn column_of(value: &[u8]) -> Result<Option<u32>> {
576
+ utf8(value)?;
577
+ // The common case: one capital letter and the row digits.
578
+ if let (Some(&first), Some(&second)) = (value.first(), value.get(1))
579
+ && second < b'A'
580
+ && first.is_ascii_uppercase()
581
+ {
582
+ return Ok(Some(u32::from(first - b'A')));
583
+ }
584
+ let mut index: u32 = 0;
585
+ let mut position = 0;
586
+ for &byte in value {
587
+ let byte = if byte > b'Z' { byte - 32 } else { byte };
588
+ if !byte.is_ascii_uppercase() {
589
+ break;
590
+ }
591
+ if position == 3 {
592
+ return Err(Unsupported);
593
+ }
594
+ index = index * 26 + u32::from(byte - 64);
595
+ position += 1;
596
+ }
597
+ if position == 0 {
598
+ return Ok(None);
599
+ }
600
+ if index > MAX_COLUMNS {
601
+ return Err(Unsupported);
602
+ }
603
+ Ok(Some(index - 1))
604
+ }
605
+
606
+ /// A style index of one to ten digits; -1 for any other text, which calcsheet refuses where the style is read.
607
+ fn style_index(value: &[u8]) -> i64 {
608
+ if (1..=10).contains(&value.len()) && value.iter().all(u8::is_ascii_digit) {
609
+ value
610
+ .iter()
611
+ .fold(0i64, |index, &digit| index * 10 + i64::from(digit - b'0'))
612
+ } else {
613
+ -1
614
+ }
615
+ }
616
+
617
+ /// The shared strings of an XLSX file (`xl/sharedStrings.xml`), read as calcsheet's `SharedStrings::Builder` reads them
618
+ /// through Ox: rich text runs joined, phonetic runs skipped, line ends and references read per text, the `_xHHHH_`
619
+ /// escapes decoded. Anything else is `Unsupported`, and calcsheet loads the table itself.
620
+ pub struct StringsReader {
621
+ lexer: Lexer,
622
+ max_cell_bytes: Option<usize>,
623
+ item: Option<Vec<u8>>,
624
+ in_text: bool,
625
+ phonetic: u32,
626
+ any_element: bool,
627
+ pub buffer: Vec<u8>,
628
+ pub offsets: Vec<u64>,
629
+ }
630
+
631
+ pub enum StringsStop {
632
+ NeedMore,
633
+ Done,
634
+ Unsupported,
635
+ }
636
+
637
+ impl StringsReader {
638
+ pub fn new(max_cell_bytes: Option<usize>) -> Self {
639
+ StringsReader {
640
+ lexer: Lexer::new(),
641
+ max_cell_bytes,
642
+ item: None,
643
+ in_text: false,
644
+ phonetic: 0,
645
+ any_element: false,
646
+ buffer: Vec::new(),
647
+ offsets: vec![0],
648
+ }
649
+ }
650
+
651
+ pub fn feed(&mut self, chunk: &[u8]) {
652
+ self.lexer.feed(chunk);
653
+ }
654
+
655
+ pub fn finish(&mut self) {
656
+ self.lexer.eof = true;
657
+ }
658
+
659
+ pub fn read(&mut self) -> StringsStop {
660
+ match self.items() {
661
+ Ok(stop) => stop,
662
+ Err(Unsupported) => StringsStop::Unsupported,
663
+ }
664
+ }
665
+
666
+ fn items(&mut self) -> Result<StringsStop> {
667
+ loop {
668
+ let Some(item) = self.lexer.item()? else {
669
+ if !self.lexer.eof {
670
+ return Ok(StringsStop::NeedMore);
671
+ }
672
+ return if self.lexer.complete() && self.any_element {
673
+ Ok(StringsStop::Done)
674
+ } else {
675
+ Err(Unsupported)
676
+ };
677
+ };
678
+ match item {
679
+ Item::Start(name_start, name_end, empty) => {
680
+ // An element inside a text would let Ox drop white space that is kept here.
681
+ if self.in_text {
682
+ return Err(Unsupported);
683
+ }
684
+ self.any_element = true;
685
+ let name = local(&self.lexer.buf[name_start..name_end]).to_vec();
686
+ self.start(&name);
687
+ if empty {
688
+ self.end(&name)?;
689
+ }
690
+ }
691
+ Item::End(name_start, name_end) => {
692
+ let name = local(&self.lexer.buf[name_start..name_end]).to_vec();
693
+ self.end(&name)?;
694
+ }
695
+ Item::Text(start, end) => {
696
+ if self.in_text {
697
+ self.text(start, end)?;
698
+ }
699
+ }
700
+ }
701
+ }
702
+ }
703
+
704
+ fn start(&mut self, name: &[u8]) {
705
+ match name {
706
+ b"si" => self.item = Some(Vec::new()),
707
+ b"rPh" if self.item.is_some() => self.phonetic += 1,
708
+ b"t" if self.item.is_some() && self.phonetic == 0 => self.in_text = true,
709
+ _ => {}
710
+ }
711
+ }
712
+
713
+ fn end(&mut self, name: &[u8]) -> Result<()> {
714
+ match name {
715
+ b"si" => {
716
+ if let Some(item) = self.item.take() {
717
+ self.finish_item(item)?;
718
+ }
719
+ }
720
+ b"rPh" if self.item.is_some() && self.phonetic > 0 => self.phonetic -= 1,
721
+ b"t" => self.in_text = false,
722
+ _ => {}
723
+ }
724
+ Ok(())
725
+ }
726
+
727
+ fn text(&mut self, start: usize, end: usize) -> Result<()> {
728
+ let mut piece = self.lexer.buf[start..end].to_vec();
729
+ if memchr(b'\r', &piece).is_some() {
730
+ piece = line_ends(&piece);
731
+ }
732
+ if memchr(b'&', &piece).is_some() {
733
+ piece = decode(&piece)?;
734
+ }
735
+ let item = self.item.as_mut().ok_or(Unsupported)?;
736
+ if self
737
+ .max_cell_bytes
738
+ .is_some_and(|max| item.len() + piece.len() > max)
739
+ {
740
+ return Err(Unsupported);
741
+ }
742
+ item.extend_from_slice(&piece);
743
+ Ok(())
744
+ }
745
+
746
+ fn finish_item(&mut self, item: Vec<u8>) -> Result<()> {
747
+ let item = if memchr::memmem::find(&item, b"_x").is_some() {
748
+ escapes(utf8(&item)?)
749
+ } else {
750
+ item
751
+ };
752
+ self.buffer.extend_from_slice(&item);
753
+ self.offsets.push(self.buffer.len() as u64);
754
+ self.in_text = false;
755
+ self.phonetic = 0;
756
+ Ok(())
757
+ }
758
+ }
759
+
760
+ /// Escapes.decode!: the `_xHHHH_` escapes of OOXML, a surrogate pair of them as one character; a lone surrogate stays
761
+ /// as text.
762
+ fn escapes(text: &[u8]) -> Vec<u8> {
763
+ let hex = |bytes: &[u8]| -> Option<u32> {
764
+ if bytes.iter().all(u8::is_ascii_hexdigit) {
765
+ u32::from_str_radix(std::str::from_utf8(bytes).ok()?, 16).ok()
766
+ } else {
767
+ None
768
+ }
769
+ };
770
+ let mut decoded = Vec::with_capacity(text.len());
771
+ let mut at = 0;
772
+ while at < text.len() {
773
+ let rest = &text[at..];
774
+ // _xD[89AB]hh__xD[C-F]hh_
775
+ if rest.len() >= 14
776
+ && rest.starts_with(b"_xD")
777
+ && matches!(rest[3], b'8' | b'9' | b'A' | b'B')
778
+ && rest[6..9] == *b"__x"
779
+ && rest[9] == b'D'
780
+ && matches!(rest[10], b'C'..=b'F')
781
+ && rest[13] == b'_'
782
+ && let (Some(high), Some(low)) = (hex(&rest[2..6]), hex(&rest[9..13]))
783
+ {
784
+ let code = (high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000;
785
+ let mut bytes = [0; 4];
786
+ decoded.extend_from_slice(
787
+ char::from_u32(code)
788
+ .expect("a pair")
789
+ .encode_utf8(&mut bytes)
790
+ .as_bytes(),
791
+ );
792
+ at += 14;
793
+ continue;
794
+ }
795
+ if rest.len() >= 7
796
+ && rest.starts_with(b"_x")
797
+ && rest[6] == b'_'
798
+ && let Some(code) = hex(&rest[2..6])
799
+ {
800
+ match char::from_u32(code) {
801
+ Some(character) => {
802
+ let mut bytes = [0; 4];
803
+ decoded.extend_from_slice(character.encode_utf8(&mut bytes).as_bytes());
804
+ }
805
+ None => decoded.extend_from_slice(&rest[..7]),
806
+ }
807
+ at += 7;
808
+ continue;
809
+ }
810
+ decoded.push(text[at]);
811
+ at += 1;
812
+ }
813
+ decoded
814
+ }