Skip to main content

calamine_styles/xlsb/
mod.rs

1// SPDX-License-Identifier: MIT
2//
3// Copyright 2016-2025, Johann Tuffe.
4
5mod cells_reader;
6
7pub use cells_reader::XlsbCellsReader;
8
9use std::borrow::Cow;
10use std::collections::BTreeMap;
11use std::io::{BufReader, Read, Seek};
12
13use log::debug;
14
15use encoding_rs::UTF_16LE;
16use quick_xml::events::attributes::Attribute;
17use quick_xml::events::Event;
18use quick_xml::name::QName;
19use quick_xml::Reader as XmlReader;
20use zip::read::{ZipArchive, ZipFile};
21use zip::result::ZipError;
22
23use crate::datatype::DataRef;
24use crate::formats::{builtin_format_by_code, detect_custom_number_format, CellFormat};
25use crate::utils::{push_column, read_f64, read_i32, read_u16, read_u32, read_usize};
26use crate::vba::VbaProject;
27use crate::{
28    Cell, Data, HeaderRow, Metadata, Range, Reader, ReaderRef, Sheet, SheetType, SheetVisible,
29    StyleRange, WorksheetLayout,
30};
31
32/// A Xlsb specific error
33#[derive(Debug)]
34pub enum XlsbError {
35    /// Io error
36    Io(std::io::Error),
37    /// Zip error
38    Zip(zip::result::ZipError),
39    /// Xml error
40    Xml(quick_xml::Error),
41    /// Xml attribute error
42    XmlAttr(quick_xml::events::attributes::AttrError),
43    /// Vba error
44    Vba(crate::vba::VbaError),
45
46    /// Mismatch value
47    Mismatch {
48        /// expected
49        expected: &'static str,
50        /// found
51        found: u16,
52    },
53    /// File not found
54    FileNotFound(String),
55    /// Invalid formula, stack length too short
56    StackLen,
57
58    /// Unsupported type
59    UnsupportedType(u16),
60    /// Unsupported etpg
61    Etpg(u8),
62    /// Unsupported iftab
63    IfTab(usize),
64    /// Unsupported `BErr`
65    BErr(u8),
66    /// Unsupported Ptg
67    Ptg(u8),
68    /// Unsupported cell error code
69    CellError(u8),
70    /// Wide str length too long
71    WideStr {
72        /// wide str length
73        ws_len: usize,
74        /// buffer length
75        buf_len: usize,
76    },
77    /// Unrecognized data
78    Unrecognized {
79        /// data type
80        typ: &'static str,
81        /// value found
82        val: String,
83    },
84    /// Workbook is password protected
85    Password,
86    /// Worksheet not found
87    WorksheetNotFound(String),
88    /// XML Encoding error
89    Encoding(quick_xml::encoding::EncodingError),
90}
91
92from_err!(std::io::Error, XlsbError, Io);
93from_err!(zip::result::ZipError, XlsbError, Zip);
94from_err!(quick_xml::Error, XlsbError, Xml);
95from_err!(quick_xml::events::attributes::AttrError, XlsbError, XmlAttr);
96from_err!(quick_xml::encoding::EncodingError, XlsbError, Encoding);
97from_err!(crate::vba::VbaError, XlsbError, Vba);
98
99impl std::fmt::Display for XlsbError {
100    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101        match self {
102            XlsbError::Io(e) => write!(f, "I/O error: {e}"),
103            XlsbError::Zip(e) => write!(f, "Zip error: {e}"),
104            XlsbError::Xml(e) => write!(f, "Xml error: {e}"),
105            XlsbError::XmlAttr(e) => write!(f, "Xml attribute error: {e}"),
106            XlsbError::Vba(e) => write!(f, "Vba error: {e}"),
107            XlsbError::Mismatch { expected, found } => {
108                write!(f, "Expecting {expected}, got {found:X}")
109            }
110            XlsbError::FileNotFound(file) => write!(f, "File not found: '{file}'"),
111            XlsbError::StackLen => write!(f, "Invalid stack length"),
112            XlsbError::UnsupportedType(t) => write!(f, "Unsupported type {t:X}"),
113            XlsbError::Etpg(t) => write!(f, "Unsupported etpg {t:X}"),
114            XlsbError::IfTab(t) => write!(f, "Unsupported iftab {t:X}"),
115            XlsbError::BErr(t) => write!(f, "Unsupported BErr {t:X}"),
116            XlsbError::Ptg(t) => write!(f, "Unsupported Ptf {t:X}"),
117            XlsbError::CellError(t) => write!(f, "Unsupported Cell Error code {t:X}"),
118            XlsbError::WideStr { ws_len, buf_len } => write!(
119                f,
120                "Wide str length exceeds buffer length ({ws_len} > {buf_len})",
121            ),
122            XlsbError::Unrecognized { typ, val } => {
123                write!(f, "Unrecognized {typ}: {val}")
124            }
125            XlsbError::Password => write!(f, "Workbook is password protected"),
126            XlsbError::WorksheetNotFound(name) => write!(f, "Worksheet '{name}' not found"),
127            XlsbError::Encoding(e) => write!(f, "XML encoding error: {e}"),
128        }
129    }
130}
131
132impl std::error::Error for XlsbError {
133    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
134        match self {
135            XlsbError::Io(e) => Some(e),
136            XlsbError::Zip(e) => Some(e),
137            XlsbError::Xml(e) => Some(e),
138            XlsbError::Vba(e) => Some(e),
139            _ => None,
140        }
141    }
142}
143
144/// Xlsb reader options
145#[derive(Debug, Default)]
146#[non_exhaustive]
147struct XlsbOptions {
148    pub header_row: HeaderRow,
149}
150
151/// A Xlsb reader
152pub struct Xlsb<RS> {
153    zip: ZipArchive<RS>,
154    extern_sheets: Vec<String>,
155    sheets: Vec<(String, String)>,
156    strings: Vec<String>,
157    /// Cell (number) formats
158    formats: Vec<CellFormat>,
159    is_1904: bool,
160    metadata: Metadata,
161    #[cfg(feature = "picture")]
162    pictures: Option<Vec<(String, Vec<u8>)>>,
163    options: XlsbOptions,
164}
165
166impl<RS: Read + Seek> Xlsb<RS> {
167    /// MS-XLSB
168    fn read_relationships(&mut self) -> Result<BTreeMap<Vec<u8>, String>, XlsbError> {
169        let mut relationships = BTreeMap::new();
170        match self.zip.by_name("xl/_rels/workbook.bin.rels") {
171            Ok(f) => {
172                let mut xml = XmlReader::from_reader(BufReader::new(f));
173                let config = xml.config_mut();
174                config.check_end_names = false;
175                config.trim_text(false);
176                config.check_comments = false;
177                config.expand_empty_elements = true;
178                let mut buf: Vec<u8> = Vec::with_capacity(64);
179
180                loop {
181                    match xml.read_event_into(&mut buf) {
182                        Ok(Event::Start(e)) if e.name() == QName(b"Relationship") => {
183                            let mut id = None;
184                            let mut target = None;
185                            for a in e.attributes() {
186                                match a? {
187                                    Attribute {
188                                        key: QName(b"Id"),
189                                        value: v,
190                                    } => {
191                                        id = Some(v.to_vec());
192                                    }
193                                    Attribute {
194                                        key: QName(b"Target"),
195                                        value: v,
196                                    } => {
197                                        target = Some(
198                                            xml.decoder()
199                                                .decode(&v)
200                                                .map_err(XlsbError::Encoding)?
201                                                .into_owned(),
202                                        );
203                                    }
204                                    _ => (),
205                                }
206                            }
207                            if let (Some(id), Some(target)) = (id, target) {
208                                relationships.insert(id, target);
209                            }
210                        }
211                        Ok(Event::Eof) => break,
212                        Err(e) => return Err(XlsbError::Xml(e)),
213                        _ => (),
214                    }
215                    buf.clear();
216                }
217            }
218            Err(ZipError::FileNotFound) => (),
219            Err(e) => return Err(XlsbError::Zip(e)),
220        }
221        Ok(relationships)
222    }
223
224    /// MS-XLSB 2.1.7.50 Styles
225    fn read_styles(&mut self) -> Result<(), XlsbError> {
226        let mut iter = match RecordIter::from_zip(&mut self.zip, "xl/styles.bin") {
227            Ok(iter) => iter,
228            Err(_) => return Ok(()), // it is fine if path does not exists
229        };
230        let mut buf = Vec::with_capacity(1024);
231        let mut number_formats = BTreeMap::new();
232
233        loop {
234            match iter.read_type()? {
235                0x0267 => {
236                    // BrtBeginFmts
237                    let _len = iter.fill_buffer(&mut buf)?;
238                    let len = read_usize(&buf);
239
240                    for _ in 0..len {
241                        let _ = iter.next_skip_blocks(0x002C, &[], &mut buf)?; // BrtFmt
242                        let fmt_code = read_u16(&buf);
243                        let fmt_str = wide_str(&buf[2..], &mut 0)?;
244                        number_formats
245                            .insert(fmt_code, detect_custom_number_format(fmt_str.as_ref()));
246                    }
247                }
248                0x0269 => {
249                    // BrtBeginCellXFs
250                    let _len = iter.fill_buffer(&mut buf)?;
251                    let len = read_usize(&buf);
252                    for _ in 0..len {
253                        let _ = iter.next_skip_blocks(0x002F, &[], &mut buf)?; // BrtXF
254                        let fmt_code = read_u16(&buf[2..4]);
255                        match builtin_format_by_code(fmt_code) {
256                            CellFormat::DateTime => self.formats.push(CellFormat::DateTime),
257                            CellFormat::TimeDelta => self.formats.push(CellFormat::TimeDelta),
258                            CellFormat::Other => {
259                                self.formats.push(
260                                    number_formats
261                                        .get(&fmt_code)
262                                        .copied()
263                                        .unwrap_or(CellFormat::Other),
264                                );
265                            }
266                        }
267                    }
268                    // BrtBeginCellXFs is always present and always after BrtBeginFmts
269                    break;
270                }
271                _ => (),
272            }
273            buf.clear();
274        }
275
276        Ok(())
277    }
278
279    /// MS-XLSB 2.1.7.45
280    fn read_shared_strings(&mut self) -> Result<(), XlsbError> {
281        let mut iter = match RecordIter::from_zip(&mut self.zip, "xl/sharedStrings.bin") {
282            Ok(iter) => iter,
283            Err(_) => return Ok(()), // it is fine if path does not exists
284        };
285        let mut buf = Vec::with_capacity(1024);
286
287        let _ = iter.next_skip_blocks(0x009F, &[], &mut buf)?; // BrtBeginSst
288        let len = read_usize(&buf[4..8]);
289
290        // BrtSSTItems
291        for _ in 0..len {
292            let _ = iter.next_skip_blocks(
293                0x0013,
294                &[
295                    (0x0023, Some(0x0024)), // future
296                ],
297                &mut buf,
298            )?; // BrtSSTItem
299            self.strings.push(wide_str(&buf[1..], &mut 0)?.into_owned());
300        }
301        Ok(())
302    }
303
304    /// MS-XLSB 2.1.7.61
305    fn read_workbook(
306        &mut self,
307        relationships: &BTreeMap<Vec<u8>, String>,
308    ) -> Result<(), XlsbError> {
309        let mut iter = RecordIter::from_zip(&mut self.zip, "xl/workbook.bin")?;
310        let mut buf = Vec::with_capacity(1024);
311
312        loop {
313            match iter.read_type()? {
314                0x0099 => {
315                    let _ = iter.fill_buffer(&mut buf)?;
316                    self.is_1904 = &buf[0] & 0x1 != 0;
317                } // BrtWbProp
318                0x009C => {
319                    // BrtBundleSh
320                    let len = iter.fill_buffer(&mut buf)?;
321                    let rel_len = read_u32(&buf[8..len]);
322                    if rel_len != 0xFFFF_FFFF {
323                        let rel_len = rel_len as usize * 2;
324                        let relid = &buf[12..12 + rel_len];
325                        // converts utf16le to utf8 for BTreeMap search
326                        let relid = UTF_16LE.decode(relid).0;
327                        let path = format!("xl/{}", relationships[relid.as_bytes()]);
328                        // ST_SheetState
329                        let visible = match read_u32(&buf) {
330                            0 => SheetVisible::Visible,
331                            1 => SheetVisible::Hidden,
332                            2 => SheetVisible::VeryHidden,
333                            v => {
334                                return Err(XlsbError::Unrecognized {
335                                    typ: "BoundSheet8:hsState",
336                                    val: v.to_string(),
337                                })
338                            }
339                        };
340                        let typ = match path.split('/').nth(1) {
341                            Some("worksheets") => SheetType::WorkSheet,
342                            Some("chartsheets") => SheetType::ChartSheet,
343                            Some("dialogsheets") => SheetType::DialogSheet,
344                            _ => {
345                                return Err(XlsbError::Unrecognized {
346                                    typ: "BoundSheet8:dt",
347                                    val: path.to_string(),
348                                })
349                            }
350                        };
351                        let name = wide_str(&buf[12 + rel_len..len], &mut 0)?;
352                        self.metadata.sheets.push(Sheet {
353                            name: name.to_string(),
354                            typ,
355                            visible,
356                        });
357                        self.sheets.push((name.into_owned(), path));
358                    }
359                }
360                0x0090 => break, // BrtEndBundleShs
361                _ => (),
362            }
363            buf.clear();
364        }
365
366        // BrtName
367        let mut defined_names = Vec::new();
368        loop {
369            let typ = iter.read_type()?;
370            match typ {
371                0x016A => {
372                    // BrtExternSheet
373                    let _len = iter.fill_buffer(&mut buf)?;
374                    let cxti = read_u32(&buf[..4]) as usize;
375                    if cxti < 1_000_000 {
376                        self.extern_sheets.reserve(cxti);
377                    }
378                    let sheets = &self.sheets;
379                    let extern_sheets = buf[4..]
380                        .chunks(12)
381                        .map(|xti| {
382                            match read_i32(&xti[4..8]) {
383                                -2 => "#ThisWorkbook",
384                                -1 => "#InvalidWorkSheet",
385                                p if p >= 0 && (p as usize) < sheets.len() => &sheets[p as usize].0,
386                                _ => "#Unknown",
387                            }
388                            .to_string()
389                        })
390                        .take(cxti)
391                        .collect();
392                    self.extern_sheets = extern_sheets;
393                }
394                0x0027 => {
395                    // BrtName
396                    let len = iter.fill_buffer(&mut buf)?;
397                    let mut str_len = 0;
398                    let name = wide_str(&buf[9..len], &mut str_len)?.into_owned();
399                    let rgce_len = read_u32(&buf[9 + str_len..]) as usize;
400                    let rgce = &buf[13 + str_len..13 + str_len + rgce_len];
401                    let formula = parse_formula(rgce, &self.extern_sheets, &defined_names)?;
402                    defined_names.push((name, formula));
403                }
404                0x009D | 0x0225 | 0x018D | 0x0180 | 0x009A | 0x0252 | 0x0229 | 0x009B | 0x0084 => {
405                    // record supposed to happen AFTER BrtNames
406                    self.metadata.names = defined_names;
407                    return Ok(());
408                }
409                _ => debug!("Unsupported type {typ:X}"),
410            }
411        }
412    }
413
414    /// Get a cells reader for a given worksheet
415    pub fn worksheet_cells_reader<'a>(
416        &'a mut self,
417        name: &str,
418    ) -> Result<XlsbCellsReader<'a, RS>, XlsbError> {
419        let path = match self.sheets.iter().find(|&(n, _)| n == name) {
420            Some((_, path)) => path.clone(),
421            None => return Err(XlsbError::WorksheetNotFound(name.into())),
422        };
423        let iter = RecordIter::from_zip(&mut self.zip, &path)?;
424        XlsbCellsReader::new(
425            iter,
426            &self.formats,
427            &self.strings,
428            &self.extern_sheets,
429            &self.metadata.names,
430            self.is_1904,
431        )
432    }
433
434    #[cfg(feature = "picture")]
435    fn read_pictures(&mut self) -> Result<(), XlsbError> {
436        let mut pics = Vec::new();
437        for i in 0..self.zip.len() {
438            let mut zfile = self.zip.by_index(i)?;
439            let zname = zfile.name();
440            if zname.starts_with("xl/media") {
441                if let Some(ext) = zname.split('.').next_back() {
442                    if [
443                        "emf", "wmf", "pict", "jpeg", "jpg", "png", "dib", "gif", "tiff", "eps",
444                        "bmp", "wpg",
445                    ]
446                    .contains(&ext)
447                    {
448                        let ext = ext.to_string();
449                        let mut buf: Vec<u8> = Vec::new();
450                        zfile.read_to_end(&mut buf)?;
451                        pics.push((ext, buf));
452                    }
453                }
454            }
455        }
456        if !pics.is_empty() {
457            self.pictures = Some(pics);
458        }
459        Ok(())
460    }
461}
462
463impl<RS: Read + Seek> Reader<RS> for Xlsb<RS> {
464    type Error = XlsbError;
465
466    fn new(mut reader: RS) -> Result<Self, XlsbError> {
467        check_for_password_protected(&mut reader)?;
468
469        let mut xlsb = Xlsb {
470            zip: ZipArchive::new(reader)?,
471            sheets: Vec::new(),
472            strings: Vec::new(),
473            extern_sheets: Vec::new(),
474            formats: Vec::new(),
475            is_1904: false,
476            metadata: Metadata::default(),
477            #[cfg(feature = "picture")]
478            pictures: None,
479            options: XlsbOptions::default(),
480        };
481        xlsb.read_shared_strings()?;
482        xlsb.read_styles()?;
483        let relationships = xlsb.read_relationships()?;
484        xlsb.read_workbook(&relationships)?;
485        #[cfg(feature = "picture")]
486        xlsb.read_pictures()?;
487
488        Ok(xlsb)
489    }
490
491    fn with_header_row(&mut self, header_row: HeaderRow) -> &mut Self {
492        self.options.header_row = header_row;
493        self
494    }
495
496    fn vba_project(&mut self) -> Result<Option<VbaProject>, XlsbError> {
497        let Some(mut f) = self.zip.by_name("xl/vbaProject.bin").ok() else {
498            return Ok(None);
499        };
500        let len = f.size() as usize;
501        let vba = VbaProject::new(&mut f, len)?;
502        Ok(Some(vba))
503    }
504
505    fn metadata(&self) -> &Metadata {
506        &self.metadata
507    }
508
509    /// MS-XLSB 2.1.7.62
510    fn worksheet_range(&mut self, name: &str) -> Result<Range<Data>, XlsbError> {
511        let rge = self.worksheet_range_ref(name)?;
512        let inner = rge.inner.into_iter().map(|v| v.into()).collect();
513        Ok(Range {
514            start: rge.start,
515            end: rge.end,
516            inner,
517        })
518    }
519
520    /// MS-XLSB 2.1.7.62
521    fn worksheet_formula(&mut self, name: &str) -> Result<Range<String>, XlsbError> {
522        let mut cells_reader = self.worksheet_cells_reader(name)?;
523        let mut cells = Vec::with_capacity(cells_reader.dimensions().len().min(1_000_000) as _);
524        while let Some(cell) = cells_reader.next_formula()? {
525            if !cell.val.is_empty() {
526                cells.push(cell);
527            }
528        }
529        Ok(Range::from_sparse(cells))
530    }
531
532    fn worksheet_style(&mut self, _name: &str) -> Result<StyleRange, XlsbError> {
533        // TODO: Implement XLSB style parsing
534        Ok(StyleRange::empty())
535    }
536
537    fn worksheet_layout(&mut self, _name: &str) -> Result<WorksheetLayout, XlsbError> {
538        // XLSB doesn't support column width/row height information in the same way as XLSX yet
539        Ok(WorksheetLayout::new())
540    }
541
542    /// MS-XLSB 2.1.7.62
543    fn worksheets(&mut self) -> Vec<(String, Range<Data>)> {
544        let sheets = self
545            .sheets
546            .iter()
547            .map(|(name, _)| name.clone())
548            .collect::<Vec<_>>();
549        sheets
550            .into_iter()
551            .filter_map(|name| {
552                let ws = self.worksheet_range(&name).ok()?;
553                Some((name, ws))
554            })
555            .collect()
556    }
557
558    #[cfg(feature = "picture")]
559    fn pictures(&self) -> Option<Vec<(String, Vec<u8>)>> {
560        self.pictures.to_owned()
561    }
562}
563
564impl<RS: Read + Seek> ReaderRef<RS> for Xlsb<RS> {
565    fn worksheet_range_ref<'a>(&'a mut self, name: &str) -> Result<Range<DataRef<'a>>, XlsbError> {
566        let header_row = self.options.header_row;
567        let mut cell_reader = self.worksheet_cells_reader(name)?;
568        let len = cell_reader.dimensions().len();
569        let mut cells = Vec::new();
570        if len < 100_000 {
571            cells.reserve(len as usize);
572        }
573
574        match header_row {
575            HeaderRow::FirstNonEmptyRow => {
576                // the header row is the row of the first non-empty cell
577                loop {
578                    match cell_reader.next_cell() {
579                        Ok(Some(Cell {
580                            val: DataRef::Empty,
581                            ..
582                        })) => (),
583                        Ok(Some(cell)) => cells.push(cell),
584                        Ok(None) => break,
585                        Err(e) => return Err(e),
586                    }
587                }
588            }
589            HeaderRow::Row(header_row_idx) => {
590                // If `header_row` is a row index, we only add non-empty cells after this index.
591                loop {
592                    match cell_reader.next_cell() {
593                        Ok(Some(Cell {
594                            val: DataRef::Empty,
595                            ..
596                        })) => (),
597                        Ok(Some(cell)) => {
598                            if cell.pos.0 >= header_row_idx {
599                                cells.push(cell);
600                            }
601                        }
602                        Ok(None) => break,
603                        Err(e) => return Err(e),
604                    }
605                }
606
607                // If `header_row` is set and the first non-empty cell is not at the `header_row`, we add
608                // an empty cell at the beginning with row `header_row` and same column as the first non-empty cell.
609                if cells.first().is_some_and(|c| c.pos.0 != header_row_idx) {
610                    cells.insert(
611                        0,
612                        Cell {
613                            pos: (
614                                header_row_idx,
615                                cells.first().expect("cells should not be empty").pos.1,
616                            ),
617                            val: DataRef::Empty,
618                            style: None,
619                        },
620                    );
621                }
622            }
623        }
624
625        Ok(Range::from_sparse(cells))
626    }
627}
628
629pub(crate) struct RecordIter<'a, RS>
630where
631    RS: Read + Seek,
632{
633    b: [u8; 1],
634    r: BufReader<ZipFile<'a, RS>>,
635}
636
637impl<'a, RS> RecordIter<'a, RS>
638where
639    RS: Read + Seek,
640{
641    fn from_zip(zip: &'a mut ZipArchive<RS>, path: &str) -> Result<RecordIter<'a, RS>, XlsbError> {
642        let zip_path = crate::xlsx::path_to_zip_path(zip, path);
643
644        match zip.by_name(&zip_path) {
645            Ok(f) => Ok(RecordIter {
646                r: BufReader::new(f),
647                b: [0],
648            }),
649            Err(ZipError::FileNotFound) => Err(XlsbError::FileNotFound(path.into())),
650            Err(e) => Err(XlsbError::Zip(e)),
651        }
652    }
653
654    fn read_u8(&mut self) -> Result<u8, std::io::Error> {
655        self.r.read_exact(&mut self.b)?;
656        Ok(self.b[0])
657    }
658
659    /// Read next type, until we have no future record
660    fn read_type(&mut self) -> Result<u16, std::io::Error> {
661        let b = self.read_u8()?;
662        let typ = if (b & 0x80) == 0x80 {
663            (b & 0x7F) as u16 + (((self.read_u8()? & 0x7F) as u16) << 7)
664        } else {
665            b as u16
666        };
667        Ok(typ)
668    }
669
670    fn fill_buffer(&mut self, buf: &mut Vec<u8>) -> Result<usize, std::io::Error> {
671        let mut b = self.read_u8()?;
672        let mut len = (b & 0x7F) as usize;
673        for i in 1..4 {
674            if (b & 0x80) == 0 {
675                break;
676            }
677            b = self.read_u8()?;
678            len += ((b & 0x7F) as usize) << (7 * i);
679        }
680        if buf.len() < len {
681            *buf = vec![0; len];
682        }
683
684        self.r.read_exact(&mut buf[..len])?;
685        Ok(len)
686    }
687
688    /// Reads next type, and discard blocks between `start` and `end`
689    fn next_skip_blocks(
690        &mut self,
691        record_type: u16,
692        bounds: &[(u16, Option<u16>)],
693        buf: &mut Vec<u8>,
694    ) -> Result<usize, XlsbError> {
695        loop {
696            let typ = self.read_type()?;
697            let len = self.fill_buffer(buf)?;
698            if typ == record_type {
699                return Ok(len);
700            }
701            if let Some(end) = bounds.iter().find(|b| b.0 == typ).and_then(|b| b.1) {
702                while self.read_type()? != end {
703                    let _ = self.fill_buffer(buf)?;
704                }
705                let _ = self.fill_buffer(buf)?;
706            }
707        }
708    }
709}
710
711fn wide_str<'a>(buf: &'a [u8], str_len: &mut usize) -> Result<Cow<'a, str>, XlsbError> {
712    let len = read_u32(buf) as usize;
713    if buf.len() < 4 + len * 2 {
714        return Err(XlsbError::WideStr {
715            ws_len: 4 + len * 2,
716            buf_len: buf.len(),
717        });
718    }
719    *str_len = 4 + len * 2;
720    let s = &buf[4..*str_len];
721    Ok(UTF_16LE.decode(s).0)
722}
723
724/// Formula parsing
725///
726/// [MS-XLSB 2.2.2]
727/// [MS-XLSB 2.5.97]
728///
729/// See Ptg [2.5.97.16]
730fn parse_formula(
731    mut rgce: &[u8],
732    sheets: &[String],
733    names: &[(String, String)],
734) -> Result<String, XlsbError> {
735    if rgce.is_empty() {
736        return Ok(String::new());
737    }
738
739    let mut stack = Vec::new();
740    let mut formula = String::with_capacity(rgce.len());
741    while !rgce.is_empty() {
742        let ptg = rgce[0];
743        rgce = &rgce[1..];
744        match ptg {
745            0x3a | 0x5a | 0x7a => {
746                // PtgRef3d
747                let ixti = read_u16(&rgce[0..2]);
748                stack.push(formula.len());
749                formula.push_str(&sheets[ixti as usize]);
750                formula.push('!');
751                // TODO: check with relative columns
752                formula.push('$');
753                push_column(read_u16(&rgce[6..8]) as u32, &mut formula);
754                formula.push('$');
755                formula.push_str(&format!("{}", read_u32(&rgce[2..6]) + 1));
756                rgce = &rgce[8..];
757            }
758            0x3b | 0x5b | 0x7b => {
759                // PtgArea3d
760                let ixti = read_u16(&rgce[0..2]);
761                stack.push(formula.len());
762                formula.push_str(&sheets[ixti as usize]);
763                formula.push('!');
764                // TODO: check with relative columns
765                formula.push('$');
766                push_column(read_u16(&rgce[10..12]) as u32, &mut formula);
767                formula.push('$');
768                formula.push_str(&format!("{}", read_u32(&rgce[2..6]) + 1));
769                formula.push(':');
770                formula.push('$');
771                push_column(read_u16(&rgce[12..14]) as u32, &mut formula);
772                formula.push('$');
773                formula.push_str(&format!("{}", read_u32(&rgce[6..10]) + 1));
774                rgce = &rgce[14..];
775            }
776            0x3c | 0x5c | 0x7c => {
777                // PtfRefErr3d
778                let ixti = read_u16(&rgce[0..2]);
779                stack.push(formula.len());
780                formula.push_str(&sheets[ixti as usize]);
781                formula.push('!');
782                formula.push_str("#REF!");
783                rgce = &rgce[8..];
784            }
785            0x3d | 0x5d | 0x7d => {
786                // PtgAreaErr3d
787                let ixti = read_u16(&rgce[0..2]);
788                stack.push(formula.len());
789                formula.push_str(&sheets[ixti as usize]);
790                formula.push('!');
791                formula.push_str("#REF!");
792                rgce = &rgce[14..];
793            }
794            0x01 => {
795                // PtgExp: array/shared formula, ignore
796                debug!("ignoring PtgExp array/shared formula");
797                stack.push(formula.len());
798                rgce = &rgce[4..];
799            }
800            0x03..=0x11 => {
801                // binary operation
802                let e2 = stack.pop().ok_or(XlsbError::StackLen)?;
803                let e2 = formula.split_off(e2);
804                // imaginary 'e1' will actually already be the start of the binary op
805                let op = match ptg {
806                    0x03 => "+",
807                    0x04 => "-",
808                    0x05 => "*",
809                    0x06 => "/",
810                    0x07 => "^",
811                    0x08 => "&",
812                    0x09 => "<",
813                    0x0A => "<=",
814                    0x0B => "=",
815                    0x0C => ">",
816                    0x0D => ">=",
817                    0x0E => "<>",
818                    0x0F => " ",
819                    0x10 => ",",
820                    0x11 => ":",
821                    _ => unreachable!(),
822                };
823                formula.push_str(op);
824                formula.push_str(&e2);
825            }
826            0x12 => {
827                let e = stack.last().ok_or(XlsbError::StackLen)?;
828                formula.insert(*e, '+');
829            }
830            0x13 => {
831                let e = stack.last().ok_or(XlsbError::StackLen)?;
832                formula.insert(*e, '-');
833            }
834            0x14 => {
835                formula.push('%');
836            }
837            0x15 => {
838                let e = stack.last().ok_or(XlsbError::StackLen)?;
839                formula.insert(*e, '(');
840                formula.push(')');
841            }
842            0x16 => {
843                stack.push(formula.len());
844            }
845            0x17 => {
846                stack.push(formula.len());
847                formula.push('\"');
848                let cch = read_u16(&rgce[0..2]) as usize;
849                formula.push_str(&UTF_16LE.decode(&rgce[2..2 + 2 * cch]).0);
850                formula.push('\"');
851                rgce = &rgce[2 + 2 * cch..];
852            }
853            0x18 => {
854                stack.push(formula.len());
855                let eptg = rgce[0];
856                rgce = &rgce[1..];
857                match eptg {
858                    0x19 => rgce = &rgce[12..],
859                    0x1D => rgce = &rgce[4..],
860                    e => return Err(XlsbError::Etpg(e)),
861                }
862            }
863            0x19 => {
864                let eptg = rgce[0];
865                rgce = &rgce[1..];
866                match eptg {
867                    0x01 | 0x02 | 0x08 | 0x20 | 0x21 | 0x40 | 0x41 | 0x80 => rgce = &rgce[2..],
868                    0x04 => rgce = &rgce[10..],
869                    0x10 => {
870                        rgce = &rgce[2..];
871                        let e = stack.last().ok_or(XlsbError::StackLen)?;
872                        let e = formula.split_off(*e);
873                        formula.push_str("SUM(");
874                        formula.push_str(&e);
875                        formula.push(')');
876                    }
877                    e => return Err(XlsbError::Etpg(e)),
878                }
879            }
880            0x1C => {
881                stack.push(formula.len());
882                let err = rgce[0];
883                rgce = &rgce[1..];
884                match err {
885                    0x00 => formula.push_str("#NULL!"),
886                    0x07 => formula.push_str("#DIV/0!"),
887                    0x0F => formula.push_str("#VALUE!"),
888                    0x17 => formula.push_str("#REF!"),
889                    0x1D => formula.push_str("#NAME?"),
890                    0x24 => formula.push_str("#NUM!"),
891                    0x2A => formula.push_str("#N/A"),
892                    0x2B => formula.push_str("#GETTING_DATA"),
893                    e => return Err(XlsbError::BErr(e)),
894                }
895            }
896            0x1D => {
897                stack.push(formula.len());
898                formula.push_str(if rgce[0] == 0 { "FALSE" } else { "TRUE" });
899                rgce = &rgce[1..];
900            }
901            0x1E => {
902                stack.push(formula.len());
903                formula.push_str(&format!("{}", read_u16(rgce)));
904                rgce = &rgce[2..];
905            }
906            0x1F => {
907                stack.push(formula.len());
908                formula.push_str(&format!("{}", read_f64(rgce)));
909                rgce = &rgce[8..];
910            }
911            0x20 | 0x40 | 0x60 => {
912                // PtgArray: ignore
913                stack.push(formula.len());
914                rgce = &rgce[14..];
915            }
916            0x21 | 0x22 | 0x41 | 0x42 | 0x61 | 0x62 => {
917                let (iftab, argc) = match ptg {
918                    0x22 | 0x42 | 0x62 => {
919                        let iftab = read_u16(&rgce[1..]) as usize;
920                        let argc = rgce[0] as usize;
921                        rgce = &rgce[3..];
922                        (iftab, argc)
923                    }
924                    _ => {
925                        let iftab = read_u16(rgce) as usize;
926                        if iftab > crate::utils::FTAB_LEN {
927                            return Err(XlsbError::IfTab(iftab));
928                        }
929                        rgce = &rgce[2..];
930                        let argc = crate::utils::FTAB_ARGC[iftab] as usize;
931                        (iftab, argc)
932                    }
933                };
934                if stack.len() < argc {
935                    return Err(XlsbError::StackLen);
936                }
937                if argc > 0 {
938                    let args_start = stack.len() - argc;
939                    let mut args = stack.split_off(args_start);
940                    let start = args[0];
941                    for s in &mut args {
942                        *s -= start;
943                    }
944                    let fargs = formula.split_off(start);
945                    stack.push(formula.len());
946                    args.push(fargs.len());
947                    formula.push_str(crate::utils::FTAB[iftab]);
948                    formula.push('(');
949                    for w in args.windows(2) {
950                        formula.push_str(&fargs[w[0]..w[1]]);
951                        formula.push(',');
952                    }
953                    formula.pop();
954                    formula.push(')');
955                } else {
956                    stack.push(formula.len());
957                    formula.push_str(crate::utils::FTAB[iftab]);
958                    formula.push_str("()");
959                }
960            }
961            0x23 | 0x43 | 0x63 => {
962                let iname = read_u32(rgce) as usize - 1; // one-based
963                stack.push(formula.len());
964                if let Some(name) = names.get(iname) {
965                    formula.push_str(&name.0);
966                }
967                rgce = &rgce[4..];
968            }
969            0x24 | 0x44 | 0x64 => {
970                let row = read_u32(rgce) + 1;
971                let col = [rgce[4], rgce[5] & 0x3F];
972                let col = read_u16(&col);
973                stack.push(formula.len());
974                if rgce[5] & 0x80 != 0x80 {
975                    formula.push('$');
976                }
977                push_column(col as u32, &mut formula);
978                if rgce[5] & 0x40 != 0x40 {
979                    formula.push('$');
980                }
981                formula.push_str(&format!("{row}"));
982                rgce = &rgce[6..];
983            }
984            0x25 | 0x45 | 0x65 => {
985                stack.push(formula.len());
986                formula.push('$');
987                push_column(read_u16(&rgce[8..10]) as u32, &mut formula);
988                formula.push('$');
989                formula.push_str(&format!("{}", read_u32(&rgce[0..4]) + 1));
990                formula.push(':');
991                formula.push('$');
992                push_column(read_u16(&rgce[10..12]) as u32, &mut formula);
993                formula.push('$');
994                formula.push_str(&format!("{}", read_u32(&rgce[4..8]) + 1));
995                rgce = &rgce[12..];
996            }
997            0x2A | 0x4A | 0x6A => {
998                stack.push(formula.len());
999                formula.push_str("#REF!");
1000                rgce = &rgce[6..];
1001            }
1002            0x2B | 0x4B | 0x6B => {
1003                stack.push(formula.len());
1004                formula.push_str("#REF!");
1005                rgce = &rgce[12..];
1006            }
1007            0x29 | 0x49 | 0x69 => {
1008                let cce = read_u16(rgce) as usize;
1009                rgce = &rgce[2..];
1010                let f = parse_formula(&rgce[..cce], sheets, names)?;
1011                stack.push(formula.len());
1012                formula.push_str(&f);
1013                rgce = &rgce[cce..];
1014            }
1015            0x39 | 0x59 | 0x79 => {
1016                // TODO: external workbook ... ignore this formula ...
1017                stack.push(formula.len());
1018                formula.push_str("EXTERNAL_WB_NAME");
1019                rgce = &rgce[6..];
1020            }
1021            _ => return Err(XlsbError::Ptg(ptg)),
1022        }
1023    }
1024
1025    if stack.len() == 1 {
1026        Ok(formula)
1027    } else {
1028        Err(XlsbError::StackLen)
1029    }
1030}
1031
1032fn cell_format<'a>(formats: &'a [CellFormat], buf: &[u8]) -> Option<&'a CellFormat> {
1033    // Parses a Cell (MS-XLSB 2.5.9) and determines if it references a Date format
1034
1035    // iStyleRef is stored as a 24bit integer starting at the fifth byte
1036    let style_ref = u32::from_le_bytes([buf[4], buf[5], buf[6], 0]);
1037
1038    formats.get(style_ref as usize)
1039}
1040
1041fn check_for_password_protected<RS: Read + Seek>(reader: &mut RS) -> Result<(), XlsbError> {
1042    let offset_end = reader.seek(std::io::SeekFrom::End(0))? as usize;
1043    reader.seek(std::io::SeekFrom::Start(0))?;
1044
1045    if let Ok(cfb) = crate::cfb::Cfb::new(reader, offset_end) {
1046        if cfb.has_directory("EncryptedPackage") {
1047            return Err(XlsbError::Password);
1048        }
1049    }
1050
1051    Ok(())
1052}