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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +36 -0
- data/Cargo.lock +283 -0
- data/Cargo.toml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +65 -0
- data/ext/calcsheet_native/Cargo.toml +14 -0
- data/ext/calcsheet_native/extconf.rb +6 -0
- data/ext/calcsheet_native/src/csv.rs +309 -0
- data/ext/calcsheet_native/src/gvl.rs +47 -0
- data/ext/calcsheet_native/src/lib.rs +470 -0
- data/ext/calcsheet_native/src/ods.rs +815 -0
- data/ext/calcsheet_native/src/xlsx.rs +814 -0
- data/ext/calcsheet_native/src/xml.rs +370 -0
- data/lib/calcsheet/native/csv_reader.rb +122 -0
- data/lib/calcsheet/native/ods_reader.rb +148 -0
- data/lib/calcsheet/native/version.rb +8 -0
- data/lib/calcsheet/native/xlsx_reader.rb +208 -0
- data/lib/calcsheet/native.rb +31 -0
- metadata +100 -0
|
@@ -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
|
+
}
|