1use std::collections::{BTreeMap, HashMap};
14use std::io::{BufReader, Read, Seek};
15
16use log::warn;
17use quick_xml::events::attributes::Attributes;
18use quick_xml::events::Event;
19use quick_xml::name::QName;
20use quick_xml::Reader as XmlReader;
21use zip::read::{ZipArchive, ZipFile};
22use zip::result::ZipError;
23
24use crate::utils::unescape_entity_to_buffer;
25use crate::vba::VbaProject;
26use crate::{
27 Data, DataType, HeaderRow, Metadata, Range, Reader, Sheet, SheetType, SheetVisible, StyleRange,
28 WorksheetLayout,
29};
30use std::marker::PhantomData;
31
32const MIMETYPE: &[u8] = b"application/vnd.oasis.opendocument.spreadsheet";
33
34const MAX_ROWS: u32 = 1_048_576;
36
37const MAX_COLUMNS: u32 = 16_384;
39
40const MAX_CELLS: usize = 100_000_000;
42
43type OdsReader<'a, RS> = XmlReader<BufReader<ZipFile<'a, RS>>>;
44
45#[derive(Debug)]
47pub enum OdsError {
48 Io(std::io::Error),
50 Zip(zip::result::ZipError),
52 Xml(quick_xml::Error),
54 XmlAttr(quick_xml::events::attributes::AttrError),
56 Parse(std::string::ParseError),
58 ParseInt(std::num::ParseIntError),
60 ParseFloat(std::num::ParseFloatError),
62 ParseBool(std::str::ParseBoolError),
64
65 InvalidMime(Vec<u8>),
67 FileNotFound(&'static str),
69 Eof(&'static str),
71 Mismatch {
73 expected: &'static str,
75 found: String,
77 },
78 Password,
80 WorksheetNotFound(String),
82
83 AttrError(quick_xml::events::attributes::AttrError),
85 EncodingError(quick_xml::encoding::EncodingError),
87 CellLimitExceeded {
89 requested: usize,
91 max: usize,
93 },
94}
95
96#[derive(Debug, Default)]
98#[non_exhaustive]
99struct OdsOptions {
100 pub header_row: HeaderRow,
101}
102
103from_err!(std::io::Error, OdsError, Io);
104from_err!(zip::result::ZipError, OdsError, Zip);
105from_err!(quick_xml::Error, OdsError, Xml);
106from_err!(std::str::ParseBoolError, OdsError, ParseBool);
107from_err!(std::num::ParseFloatError, OdsError, ParseFloat);
108from_err!(std::num::ParseIntError, OdsError, ParseInt);
109from_err!(quick_xml::events::attributes::AttrError, OdsError, XmlAttr);
110from_err!(quick_xml::encoding::EncodingError, OdsError, Xml);
111
112impl std::fmt::Display for OdsError {
113 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114 match self {
115 OdsError::Io(e) => write!(f, "I/O error: {e}"),
116 OdsError::Zip(e) => write!(f, "Zip error: {e:?}"),
117 OdsError::Xml(e) => write!(f, "Xml error: {e}"),
118 OdsError::XmlAttr(e) => write!(f, "Xml attribute error: {e}"),
119 OdsError::Parse(e) => write!(f, "Parse string error: {e}"),
120 OdsError::ParseInt(e) => write!(f, "Parse integer error: {e}"),
121 OdsError::ParseFloat(e) => write!(f, "Parse float error: {e}"),
122 OdsError::ParseBool(e) => write!(f, "Parse bool error: {e}"),
123 OdsError::InvalidMime(mime) => write!(f, "Invalid MIME type: {mime:?}"),
124 OdsError::FileNotFound(file) => write!(f, "'{file}' file not found in archive"),
125 OdsError::Eof(node) => write!(f, "Expecting '{node}' node, found end of xml file"),
126 OdsError::Mismatch { expected, found } => {
127 write!(f, "Expecting '{expected}', found '{found}'")
128 }
129 OdsError::Password => write!(f, "Workbook is password protected"),
130 OdsError::WorksheetNotFound(name) => write!(f, "Worksheet '{name}' not found"),
131 OdsError::AttrError(e) => write!(f, "XML attribute Error: {e}"),
132 OdsError::EncodingError(e) => write!(f, "XML encoding Error: {e}"),
133 OdsError::CellLimitExceeded { requested, max } => {
134 write!(
135 f,
136 "Cell limit exceeded ({requested} cells requested, max {max})"
137 )
138 }
139 }
140 }
141}
142
143impl std::error::Error for OdsError {
144 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
145 match self {
146 OdsError::Io(e) => Some(e),
147 OdsError::Zip(e) => Some(e),
148 OdsError::Xml(e) => Some(e),
149 OdsError::Parse(e) => Some(e),
150 OdsError::ParseInt(e) => Some(e),
151 OdsError::ParseFloat(e) => Some(e),
152 OdsError::AttrError(e) => Some(e),
153 OdsError::EncodingError(e) => Some(e),
154 _ => None,
155 }
156 }
157}
158
159pub struct Ods<RS> {
168 sheets: BTreeMap<String, (Range<Data>, Range<String>)>,
169 metadata: Metadata,
170 marker: PhantomData<RS>,
171 #[cfg(feature = "picture")]
172 pictures: Option<Vec<(String, Vec<u8>)>>,
173 options: OdsOptions,
175}
176
177impl<RS> Reader<RS> for Ods<RS>
178where
179 RS: Read + Seek,
180{
181 type Error = OdsError;
182
183 fn new(reader: RS) -> Result<Self, OdsError> {
184 let mut zip = ZipArchive::new(reader)?;
185
186 match zip.by_name("mimetype") {
188 Ok(mut f) => {
189 let mut buf = [0u8; 46];
190 f.read_exact(&mut buf)?;
191 if &buf[..] != MIMETYPE {
192 return Err(OdsError::InvalidMime(buf.to_vec()));
193 }
194 }
195 Err(ZipError::FileNotFound) => return Err(OdsError::FileNotFound("mimetype")),
196 Err(e) => return Err(OdsError::Zip(e)),
197 }
198
199 check_for_password_protected(&mut zip)?;
200
201 #[cfg(feature = "picture")]
202 let pictures = read_pictures(&mut zip)?;
203
204 let Content {
205 sheets,
206 sheets_metadata,
207 defined_names,
208 } = parse_content(zip)?;
209 let metadata = Metadata {
210 sheets: sheets_metadata,
211 names: defined_names,
212 };
213
214 Ok(Ods {
215 marker: PhantomData,
216 metadata,
217 sheets,
218 #[cfg(feature = "picture")]
219 pictures,
220 options: OdsOptions::default(),
221 })
222 }
223
224 fn with_header_row(&mut self, header_row: HeaderRow) -> &mut Self {
225 self.options.header_row = header_row;
226 self
227 }
228
229 fn vba_project(&mut self) -> Result<Option<VbaProject>, OdsError> {
231 Ok(None)
232 }
233
234 fn metadata(&self) -> &Metadata {
236 &self.metadata
237 }
238
239 fn worksheet_range(&mut self, name: &str) -> Result<Range<Data>, OdsError> {
241 let sheet = self
242 .sheets
243 .get(name)
244 .ok_or_else(|| OdsError::WorksheetNotFound(name.into()))?
245 .0
246 .to_owned();
247
248 match self.options.header_row {
249 HeaderRow::FirstNonEmptyRow => Ok(sheet),
250 HeaderRow::Row(header_row_idx) => {
251 if let (Some(start), Some(end)) = (sheet.start(), sheet.end()) {
253 Ok(sheet.range((header_row_idx, start.1), end))
254 } else {
255 Ok(sheet)
256 }
257 }
258 }
259 }
260
261 fn worksheets(&mut self) -> Vec<(String, Range<Data>)> {
262 self.sheets
263 .iter()
264 .map(|(name, (range, _formula))| (name.to_owned(), range.clone()))
265 .collect()
266 }
267
268 fn worksheet_formula(&mut self, name: &str) -> Result<Range<String>, OdsError> {
270 self.sheets
271 .get(name)
272 .ok_or_else(|| OdsError::WorksheetNotFound(name.into()))
273 .map(|r| r.1.to_owned())
274 }
275
276 fn worksheet_style(&mut self, _name: &str) -> Result<StyleRange, OdsError> {
277 Ok(StyleRange::empty())
279 }
280
281 fn worksheet_layout(&mut self, _name: &str) -> Result<WorksheetLayout, OdsError> {
282 Ok(WorksheetLayout::new())
284 }
285
286 #[cfg(feature = "picture")]
287 fn pictures(&self) -> Option<Vec<(String, Vec<u8>)>> {
288 self.pictures.to_owned()
289 }
290}
291
292struct Content {
293 sheets: BTreeMap<String, (Range<Data>, Range<String>)>,
294 sheets_metadata: Vec<Sheet>,
295 defined_names: Vec<(String, String)>,
296}
297
298fn check_for_password_protected<RS: Read + Seek>(zip: &mut ZipArchive<RS>) -> Result<(), OdsError> {
300 let mut reader = match zip.by_name("META-INF/manifest.xml") {
301 Ok(f) => {
302 let mut r = XmlReader::from_reader(BufReader::new(f));
303 let config = r.config_mut();
304 config.check_end_names = false;
305 config.trim_text(false);
306 config.check_comments = false;
307 config.expand_empty_elements = true;
308 r
309 }
310 Err(ZipError::FileNotFound) => return Err(OdsError::FileNotFound("META-INF/manifest.xml")),
311 Err(e) => return Err(OdsError::Zip(e)),
312 };
313
314 let mut buf = Vec::new();
315 let mut inner = Vec::new();
316 loop {
317 match reader.read_event_into(&mut buf) {
318 Ok(Event::Start(e)) if e.name() == QName(b"manifest:file-entry") => {
319 loop {
320 match reader.read_event_into(&mut inner) {
321 Ok(Event::Start(e)) if e.name() == QName(b"manifest:encryption-data") => {
322 return Err(OdsError::Password)
323 }
324 Ok(Event::Eof) => break,
325 Err(e) => return Err(OdsError::Xml(e)),
326 _ => (),
327 }
328 }
329 inner.clear();
330 }
331 Ok(Event::Eof) => break,
332 Err(e) => return Err(OdsError::Xml(e)),
333 _ => (),
334 }
335 buf.clear();
336 }
337
338 Ok(())
339}
340
341fn parse_content<RS: Read + Seek>(mut zip: ZipArchive<RS>) -> Result<Content, OdsError> {
343 let mut reader = match zip.by_name("content.xml") {
344 Ok(f) => {
345 let mut r = XmlReader::from_reader(BufReader::new(f));
346 let config = r.config_mut();
347 config.check_end_names = false;
348 config.trim_text(false);
349 config.check_comments = false;
350 config.expand_empty_elements = true;
351 r
352 }
353 Err(ZipError::FileNotFound) => return Err(OdsError::FileNotFound("content.xml")),
354 Err(e) => return Err(OdsError::Zip(e)),
355 };
356 let mut buf = Vec::with_capacity(1024);
357 let mut sheets = BTreeMap::new();
358 let mut defined_names = Vec::new();
359 let mut sheets_metadata = Vec::new();
360 let mut styles = HashMap::new();
361 let mut style_name: Option<String> = None;
362 loop {
363 match reader.read_event_into(&mut buf) {
364 Ok(Event::Start(e)) if e.name() == QName(b"style:style") => {
365 style_name = e
366 .try_get_attribute(b"style:name")?
367 .map(|a| a.decode_and_unescape_value(reader.decoder()))
368 .transpose()?
369 .map(|x| x.to_string());
370 }
371 Ok(Event::Start(e))
372 if style_name.is_some() && e.name() == QName(b"style:table-properties") =>
373 {
374 let visible = match e.try_get_attribute(b"table:display")? {
375 Some(a) => {
376 if a.decode_and_unescape_value(reader.decoder())?.parse()? {
377 SheetVisible::Visible
378 } else {
379 SheetVisible::Hidden
380 }
381 }
382 None => SheetVisible::Visible,
383 };
384 styles.insert(style_name.clone(), visible);
385 }
386 Ok(Event::Start(e)) if e.name() == QName(b"table:table") => {
387 let visible = styles
388 .get(
389 &e.try_get_attribute(b"table:style-name")?
390 .map(|a| a.decode_and_unescape_value(reader.decoder()))
391 .transpose()?
392 .map(|x| x.to_string()),
393 )
394 .cloned()
395 .unwrap_or(SheetVisible::Visible);
396 if let Some(a) = e
397 .attributes()
398 .filter_map(|a| a.ok())
399 .find(|a| a.key == QName(b"table:name"))
400 {
401 let name = a.decode_and_unescape_value(reader.decoder())?.to_string();
402 let (range, formulas) = read_table(&mut reader)?;
403 sheets_metadata.push(Sheet {
404 name: name.clone(),
405 typ: SheetType::WorkSheet,
406 visible,
407 });
408 sheets.insert(name, (range, formulas));
409 }
410 }
411 Ok(Event::Start(e)) if e.name() == QName(b"table:named-expressions") => {
412 defined_names = read_named_expressions(&mut reader)?;
413 }
414 Ok(Event::Eof) => break,
415 Err(e) => return Err(OdsError::Xml(e)),
416 _ => (),
417 }
418 buf.clear();
419 }
420 Ok(Content {
421 sheets,
422 sheets_metadata,
423 defined_names,
424 })
425}
426
427fn read_table<RS>(reader: &mut OdsReader<'_, RS>) -> Result<(Range<Data>, Range<String>), OdsError>
428where
429 RS: Read + Seek,
430{
431 let mut cells = Vec::new();
432 let mut rows_repeats = Vec::new();
433 let mut formulas = Vec::new();
434 let mut cols = Vec::new();
435 let mut buf = Vec::with_capacity(1024);
436 let mut row_buf = Vec::with_capacity(1024);
437 let mut cell_buf = Vec::with_capacity(1024);
438 let mut total_rows = 0;
439 cols.push(0);
440 loop {
441 match reader.read_event_into(&mut buf) {
442 Ok(Event::Start(e)) if e.name() == QName(b"table:table-row") => {
443 let row_repeats = match e.try_get_attribute(b"table:number-rows-repeated")? {
444 Some(c) => c.decode_and_unescape_value(reader.decoder())?.parse()?,
445 None => 1,
446 };
447
448 let remaining_rows = (MAX_ROWS as usize).saturating_sub(total_rows);
450 let capped_repeats = row_repeats.min(remaining_rows);
451 if capped_repeats < row_repeats {
452 warn!(
453 "ods row repeat count capped ({row_repeats} -> {capped_repeats}, max rows {MAX_ROWS})"
454 );
455 }
456 total_rows = total_rows.saturating_add(capped_repeats);
457
458 read_row(
459 reader,
460 &mut row_buf,
461 &mut cell_buf,
462 &mut cells,
463 &mut formulas,
464 )?;
465 cols.push(cells.len());
466 rows_repeats.push(capped_repeats);
467 }
468 Ok(Event::End(e)) if e.name() == QName(b"table:table") => break,
469 Err(e) => return Err(OdsError::Xml(e)),
470 Ok(_) => (),
471 }
472 buf.clear();
473 }
474 Ok((
475 get_range(cells, &cols, &rows_repeats)?,
476 get_range(formulas, &cols, &rows_repeats)?,
477 ))
478}
479
480fn is_empty_row<T: Default + Clone + PartialEq>(row: &[T]) -> bool {
481 row.iter().all(|x| x == &T::default())
482}
483
484fn get_range<T: Default + Clone + PartialEq>(
485 mut cells: Vec<T>,
486 cols: &[usize],
487 rows_repeats: &[usize],
488) -> Result<Range<T>, OdsError> {
489 let mut row_min = None;
491 let mut row_max = 0;
492 let mut col_min = usize::MAX;
493 let mut col_max = 0;
494 let mut first_empty_rows_repeated = 0;
495 {
496 for (i, w) in cols.windows(2).enumerate() {
497 let row = &cells[w[0]..w[1]];
498 if let Some(p) = row.iter().position(|c| c != &T::default()) {
499 if row_min.is_none() {
500 row_min = Some(i);
501 first_empty_rows_repeated =
502 rows_repeats.iter().take(i).sum::<usize>().saturating_sub(i);
503 }
504 row_max = i;
505 if p < col_min {
506 col_min = p;
507 }
508 if let Some(p) = row.iter().rposition(|c| c != &T::default()) {
509 if p > col_max {
510 col_max = p;
511 }
512 }
513 }
514 }
515 }
516 let Some(row_min) = row_min else {
517 return Ok(Range::default());
518 };
519
520 let row_width = col_max + 1 - col_min;
522 let cells_len = (row_max + 1 - row_min) * row_width;
523 {
524 let mut new_cells = Vec::with_capacity(cells_len.min(MAX_CELLS));
525 let empty_cells = vec![T::default(); col_max + 1];
526 let mut empty_row_repeats = 0_usize;
527 let mut consecutive_empty_rows = 0_usize;
528 for (w, row_repeats) in cols
529 .windows(2)
530 .skip(row_min)
531 .take(row_max + 1)
532 .zip(rows_repeats.iter().skip(row_min).take(row_max + 1))
533 {
534 let row = &cells[w[0]..w[1]];
535 let row_repeats = *row_repeats;
536
537 if is_empty_row(row) {
538 empty_row_repeats = empty_row_repeats.saturating_add(row_repeats);
539 consecutive_empty_rows += 1;
540 continue;
541 }
542
543 if empty_row_repeats > 0 {
544 let cells_to_add = empty_row_repeats.saturating_mul(row_width);
546 if new_cells.len().saturating_add(cells_to_add) > MAX_CELLS {
547 return Err(OdsError::CellLimitExceeded {
548 requested: new_cells.len().saturating_add(cells_to_add),
549 max: MAX_CELLS,
550 });
551 }
552 row_max = row_max + empty_row_repeats - consecutive_empty_rows;
553 for _ in 0..empty_row_repeats {
554 new_cells.extend_from_slice(&empty_cells[col_min..]);
555 }
556 empty_row_repeats = 0;
557 consecutive_empty_rows = 0;
558 }
559
560 if row_repeats > 1 {
561 row_max = row_max + row_repeats - 1;
562 }
563
564 let cells_to_add = row_repeats.saturating_mul(row_width);
566 if new_cells.len().saturating_add(cells_to_add) > MAX_CELLS {
567 return Err(OdsError::CellLimitExceeded {
568 requested: new_cells.len().saturating_add(cells_to_add),
569 max: MAX_CELLS,
570 });
571 }
572
573 for _ in 0..row_repeats {
574 match row.len().cmp(&(col_max + 1)) {
575 std::cmp::Ordering::Less => {
576 new_cells.extend_from_slice(&row[col_min..]);
577 new_cells.extend_from_slice(&empty_cells[row.len()..]);
578 }
579 std::cmp::Ordering::Equal => {
580 new_cells.extend_from_slice(&row[col_min..]);
581 }
582 std::cmp::Ordering::Greater => {
583 new_cells.extend_from_slice(&row[col_min..=col_max]);
584 }
585 }
586 }
587 }
588 cells = new_cells;
589 }
590 let row_min = row_min + first_empty_rows_repeated;
591 let row_max = row_max + first_empty_rows_repeated;
592 Ok(Range {
593 start: (row_min as u32, col_min as u32),
594 end: (row_max as u32, col_max as u32),
595 inner: cells,
596 })
597}
598
599fn read_row<RS>(
600 reader: &mut OdsReader<'_, RS>,
601 row_buf: &mut Vec<u8>,
602 cell_buf: &mut Vec<u8>,
603 cells: &mut Vec<Data>,
604 formulas: &mut Vec<String>,
605) -> Result<(), OdsError>
606where
607 RS: Read + Seek,
608{
609 let mut empty_col_repeats = 0;
610 let row_start = cells.len();
611 loop {
612 row_buf.clear();
613 match reader.read_event_into(row_buf) {
614 Ok(Event::Start(e))
615 if e.name() == QName(b"table:table-cell")
616 || e.name() == QName(b"table:covered-table-cell") =>
617 {
618 let mut repeats = 1;
619 for a in e.attributes() {
620 let a = a?;
621 if a.key == QName(b"table:number-columns-repeated") {
622 repeats = reader.decoder().decode(&a.value)?.parse()?;
623 break;
624 }
625 }
626
627 let (value, formula, is_closed) = get_datatype(reader, e.attributes(), cell_buf)?;
628
629 let current_cols = cells.len() - row_start;
631 let remaining = (MAX_COLUMNS as usize).saturating_sub(current_cols);
632 let capped_empty = empty_col_repeats.min(remaining);
633 if capped_empty < empty_col_repeats {
634 warn!(
635 "ods column repeat count capped ({empty_col_repeats} -> {capped_empty}, max columns {MAX_COLUMNS})"
636 );
637 }
638
639 for _ in 0..capped_empty {
640 cells.push(Data::Empty);
641 formulas.push("".to_string());
642 }
643 empty_col_repeats = 0;
644
645 let current_cols = cells.len() - row_start;
647 let remaining = (MAX_COLUMNS as usize).saturating_sub(current_cols);
648 let capped_repeats = repeats.min(remaining);
649 if capped_repeats < repeats {
650 warn!(
651 "ods column repeat count capped ({repeats} -> {capped_repeats}, max columns {MAX_COLUMNS})"
652 );
653 }
654
655 if value.is_empty() && formula.is_empty() {
656 empty_col_repeats = capped_repeats;
657 } else {
658 for _ in 0..capped_repeats {
659 cells.push(value.clone());
660 formulas.push(formula.clone());
661 }
662 }
663 if !is_closed {
664 reader.read_to_end_into(e.name(), cell_buf)?;
665 }
666 }
667 Ok(Event::End(e)) if e.name() == QName(b"table:table-row") => break,
668 Err(e) => return Err(OdsError::Xml(e)),
669 Ok(e) => {
670 return Err(OdsError::Mismatch {
671 expected: "table-cell",
672 found: format!("{e:?}"),
673 });
674 }
675 }
676 }
677 Ok(())
678}
679
680fn get_datatype<RS>(
684 reader: &mut OdsReader<'_, RS>,
685 atts: Attributes<'_>,
686 buf: &mut Vec<u8>,
687) -> Result<(Data, String, bool), OdsError>
688where
689 RS: Read + Seek,
690{
691 let mut is_string = false;
692 let mut is_value_set = false;
693 let mut val = Data::Empty;
694 let mut formula = String::new();
695 for a in atts {
696 let a = a?;
697 match a.key {
698 QName(b"office:value") if !is_value_set => {
699 let v = reader.decoder().decode(&a.value)?;
700 val = Data::Float(v.parse()?);
701 is_value_set = true;
702 }
703 QName(b"office:string-value" | b"office:date-value" | b"office:time-value")
704 if !is_value_set =>
705 {
706 let attr = a.decode_and_unescape_value(reader.decoder())?.to_string();
707 val = match a.key {
708 QName(b"office:date-value") => Data::DateTimeIso(attr),
709 QName(b"office:time-value") => Data::DurationIso(attr),
710 _ => Data::String(attr),
711 };
712 is_value_set = true;
713 }
714 QName(b"office:boolean-value") if !is_value_set => {
715 let b = &*a.value == b"TRUE" || &*a.value == b"true";
716 val = Data::Bool(b);
717 is_value_set = true;
718 }
719 QName(b"office:value-type") if !is_value_set => is_string = &*a.value == b"string",
720 QName(b"table:formula") => {
721 formula = a.decode_and_unescape_value(reader.decoder())?.to_string();
722 }
723 _ => (),
724 }
725 }
726 if !is_value_set && is_string {
727 let mut s = String::new();
730 let mut first_paragraph = true;
731 loop {
732 buf.clear();
733 match reader.read_event_into(buf) {
734 Ok(Event::Text(t)) => {
735 s.push_str(&t.xml10_content()?);
736 }
737 Ok(Event::GeneralRef(e)) => {
738 unescape_entity_to_buffer(&e, &mut s)?;
739 }
740 Ok(Event::End(e))
741 if e.name() == QName(b"table:table-cell")
742 || e.name() == QName(b"table:covered-table-cell") =>
743 {
744 return Ok((Data::String(s), formula, true));
745 }
746 Ok(Event::Start(e)) if e.name() == QName(b"office:annotation") => loop {
747 match reader.read_event_into(buf) {
748 Ok(Event::End(e)) if e.name() == QName(b"office:annotation") => {
749 break;
750 }
751 Err(e) => return Err(OdsError::Xml(e)),
752 _ => (),
753 }
754 },
755 Ok(Event::Start(e)) if e.name() == QName(b"text:p") => {
756 if first_paragraph {
757 first_paragraph = false;
758 } else {
759 s.push('\n');
760 }
761 }
762 Ok(Event::Start(e)) if e.name() == QName(b"text:s") => {
763 let count = match e.try_get_attribute("text:c")? {
764 Some(c) => c.decode_and_unescape_value(reader.decoder())?.parse()?,
765 None => 1,
766 };
767 for _ in 0..count {
768 s.push(' ');
769 }
770 }
771 Err(e) => return Err(OdsError::Xml(e)),
772 Ok(Event::Eof) => return Err(OdsError::Eof("table:table-cell")),
773 _ => (),
774 }
775 }
776 } else {
777 Ok((val, formula, false))
778 }
779}
780
781fn read_named_expressions<RS>(
782 reader: &mut OdsReader<'_, RS>,
783) -> Result<Vec<(String, String)>, OdsError>
784where
785 RS: Read + Seek,
786{
787 let mut defined_names = Vec::new();
788 let mut buf = Vec::with_capacity(512);
789 loop {
790 buf.clear();
791 match reader.read_event_into(&mut buf) {
792 Ok(Event::Start(e))
793 if e.name() == QName(b"table:named-range")
794 || e.name() == QName(b"table:named-expression") =>
795 {
796 let mut name = String::new();
797 let mut formula = String::new();
798 for a in e.attributes() {
799 let a = a?;
800 match a.key {
801 QName(b"table:name") => {
802 name = a.decode_and_unescape_value(reader.decoder())?.to_string();
803 }
804 QName(b"table:cell-range-address" | b"table:expression") => {
805 formula = a.decode_and_unescape_value(reader.decoder())?.to_string();
806 }
807 _ => (),
808 }
809 }
810 defined_names.push((name, formula));
811 }
812 Ok(Event::End(e))
813 if e.name() == QName(b"table:named-range")
814 || e.name() == QName(b"table:named-expression") => {}
815 Ok(Event::End(e)) if e.name() == QName(b"table:named-expressions") => break,
816 Err(e) => return Err(OdsError::Xml(e)),
817 Ok(e) => {
818 return Err(OdsError::Mismatch {
819 expected: "table:named-expressions",
820 found: format!("{e:?}"),
821 });
822 }
823 }
824 }
825 Ok(defined_names)
826}
827
828#[cfg(feature = "picture")]
830#[allow(clippy::type_complexity)]
831fn read_pictures<RS: Read + Seek>(
832 zip: &mut ZipArchive<RS>,
833) -> Result<Option<Vec<(String, Vec<u8>)>>, OdsError> {
834 let mut pics = Vec::new();
835 for i in 0..zip.len() {
836 let mut zfile = zip.by_index(i)?;
837 let zname = zfile.name();
838 if zname.starts_with("Pictures") {
840 if let Some(ext) = zname.split('.').next_back() {
841 if [
842 "emf", "wmf", "pict", "jpeg", "jpg", "png", "dib", "gif", "tiff", "eps", "bmp",
843 "wpg",
844 ]
845 .contains(&ext)
846 {
847 let ext = ext.to_string();
848 let mut buf: Vec<u8> = Vec::new();
849 zfile.read_to_end(&mut buf)?;
850 pics.push((ext, buf));
851 }
852 }
853 }
854 }
855 if pics.is_empty() {
856 Ok(None)
857 } else {
858 Ok(Some(pics))
859 }
860}