calamine/
xls.rs

1use std::borrow::Cow;
2use std::cmp::min;
3use std::collections::BTreeMap;
4use std::fmt::Write;
5use std::io::{Read, Seek, SeekFrom};
6use std::marker::PhantomData;
7
8use log::debug;
9
10use crate::cfb::{Cfb, XlsEncoding};
11use crate::formats::{
12    builtin_format_by_code, detect_custom_number_format, format_excel_f64, format_excel_i64,
13    CellFormat,
14};
15#[cfg(feature = "picture")]
16use crate::utils::read_usize;
17use crate::utils::{push_column, read_f64, read_i16, read_i32, read_u16, read_u32};
18use crate::vba::VbaProject;
19use crate::{
20    Cell, CellErrorType, Data, Dimensions, HeaderRow, Metadata, Range, Reader, Sheet, SheetType,
21    SheetVisible,
22};
23
24#[derive(Debug)]
25/// An enum to handle Xls specific errors
26pub enum XlsError {
27    /// Io error
28    Io(std::io::Error),
29    /// Cfb error
30    Cfb(crate::cfb::CfbError),
31    /// Vba error
32    Vba(crate::vba::VbaError),
33
34    /// Cannot parse formula, stack is too short
35    StackLen,
36    /// Unrecognized data
37    Unrecognized {
38        /// data type
39        typ: &'static str,
40        /// value found
41        val: u8,
42    },
43    /// Workbook is password protected
44    Password,
45    /// Invalid length
46    Len {
47        /// expected length
48        expected: usize,
49        /// found length
50        found: usize,
51        /// length type
52        typ: &'static str,
53    },
54    /// Continue Record is too short
55    ContinueRecordTooShort,
56    /// End of stream
57    EoStream(&'static str),
58
59    /// Invalid Formula
60    InvalidFormula {
61        /// stack size
62        stack_size: usize,
63    },
64    /// Invalid or unknown iftab
65    IfTab(usize),
66    /// Invalid etpg
67    Etpg(u8),
68    /// No vba project
69    NoVba,
70    /// Invalid OfficeArt Record
71    #[cfg(feature = "picture")]
72    Art(&'static str),
73    /// Worksheet not found
74    WorksheetNotFound(String),
75}
76
77from_err!(std::io::Error, XlsError, Io);
78from_err!(crate::cfb::CfbError, XlsError, Cfb);
79from_err!(crate::vba::VbaError, XlsError, Vba);
80
81impl std::fmt::Display for XlsError {
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        match self {
84            XlsError::Io(e) => write!(f, "I/O error: {e}"),
85            XlsError::Cfb(e) => write!(f, "Cfb error: {e}"),
86            XlsError::Vba(e) => write!(f, "Vba error: {e}"),
87            XlsError::StackLen => write!(f, "Invalid stack length"),
88            XlsError::Unrecognized { typ, val } => write!(f, "Unrecognized {typ}: 0x{val:0X}"),
89            XlsError::Password => write!(f, "Workbook is password protected"),
90            XlsError::Len {
91                expected,
92                found,
93                typ,
94            } => write!(
95                f,
96                "Invalid {typ} length, expected {expected} maximum, found {found}",
97            ),
98            XlsError::ContinueRecordTooShort => write!(
99                f,
100                "Continued record too short while reading extended string"
101            ),
102            XlsError::EoStream(s) => write!(f, "End of stream '{s}'"),
103            XlsError::InvalidFormula { stack_size } => {
104                write!(f, "Invalid formula (stack size: {stack_size})")
105            }
106            XlsError::IfTab(iftab) => write!(f, "Invalid iftab {iftab:X}"),
107            XlsError::Etpg(etpg) => write!(f, "Invalid etpg {etpg:X}"),
108            XlsError::NoVba => write!(f, "No VBA project"),
109            #[cfg(feature = "picture")]
110            XlsError::Art(s) => write!(f, "Invalid art record '{s}'"),
111            XlsError::WorksheetNotFound(name) => write!(f, "Worksheet '{name}' not found"),
112        }
113    }
114}
115
116impl std::error::Error for XlsError {
117    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
118        match self {
119            XlsError::Io(e) => Some(e),
120            XlsError::Cfb(e) => Some(e),
121            XlsError::Vba(e) => Some(e),
122            _ => None,
123        }
124    }
125}
126
127/// Options to perform specialized parsing.
128#[derive(Debug, Clone, Default)]
129#[non_exhaustive]
130pub struct XlsOptions {
131    /// Force a spreadsheet to be interpreted using a particular code page.
132    ///
133    /// XLS files can contain [code page] identifiers. If this identifier is missing or incorrect,
134    /// strings in the parsed spreadsheet may be decoded incorrectly. Setting this field causes
135    /// `calamine::Xls` to interpret strings using the specified code page, which may allow such
136    /// spreadsheets to be decoded properly.
137    ///
138    /// [code page]: https://docs.microsoft.com/en-us/windows/win32/intl/code-page-identifiers
139    pub force_codepage: Option<u16>,
140    /// Row to use as header
141    pub header_row: HeaderRow,
142}
143
144struct SheetData {
145    range: Range<Data>,
146    formula: Range<String>,
147    merge_cells: Vec<Dimensions>,
148}
149
150/// A struct representing an old xls format file (CFB)
151pub struct Xls<RS> {
152    sheets: BTreeMap<String, SheetData>,
153    vba: Option<VbaProject>,
154    metadata: Metadata,
155    marker: PhantomData<RS>,
156    options: XlsOptions,
157    formats: Vec<CellFormat>,
158    is_1904: bool,
159    #[cfg(feature = "picture")]
160    pictures: Option<Vec<(String, Vec<u8>)>>,
161}
162
163impl<RS: Read + Seek> Xls<RS> {
164    /// Creates a new instance using `Options` to inform parsing.
165    ///
166    /// ```
167    /// use calamine::{Xls,XlsOptions};
168    /// # use std::io::Cursor;
169    /// # const BYTES: &'static [u8] = b"";
170    ///
171    /// # fn run() -> Result<Xls<Cursor<&'static [u8]>>, calamine::XlsError> {
172    /// # let reader = std::io::Cursor::new(BYTES);
173    /// let mut options = XlsOptions::default();
174    /// // ...set options...
175    /// let workbook = Xls::new_with_options(reader, options)?;
176    /// # Ok(workbook) }
177    /// # fn main() { assert!(run().is_err()); }
178    /// ```
179    pub fn new_with_options(mut reader: RS, options: XlsOptions) -> Result<Self, XlsError> {
180        let mut cfb = {
181            let offset_end = reader.seek(SeekFrom::End(0))? as usize;
182            reader.seek(SeekFrom::Start(0))?;
183            Cfb::new(&mut reader, offset_end)?
184        };
185
186        debug!("cfb loaded");
187
188        // Reads vba once for all (better than reading all worksheets once for all)
189        let vba = if cfb.has_directory("_VBA_PROJECT_CUR") {
190            Some(VbaProject::from_cfb(&mut reader, &mut cfb)?)
191        } else {
192            None
193        };
194
195        debug!("vba ok");
196
197        let mut xls = Xls {
198            sheets: BTreeMap::new(),
199            vba,
200            marker: PhantomData,
201            metadata: Metadata::default(),
202            options,
203            is_1904: false,
204            formats: Vec::new(),
205            #[cfg(feature = "picture")]
206            pictures: None,
207        };
208
209        xls.parse_workbook(reader, cfb)?;
210
211        debug!("xls parsed");
212
213        Ok(xls)
214    }
215
216    /// Gets the worksheet merge cell dimensions
217    pub fn worksheet_merge_cells(&self, name: &str) -> Option<Vec<Dimensions>> {
218        self.sheets.get(name).map(|r| r.merge_cells.clone())
219    }
220
221    /// Get the nth worksheet. Shortcut for getting the nth
222    /// sheet_name, then the corresponding worksheet.
223    pub fn worksheet_merge_cells_at(&self, n: usize) -> Option<Vec<Dimensions>> {
224        let sheet = self.metadata().sheets.get(n)?;
225
226        self.worksheet_merge_cells(&sheet.name)
227    }
228}
229
230impl<RS: Read + Seek> Reader<RS> for Xls<RS> {
231    type Error = XlsError;
232
233    fn new(reader: RS) -> Result<Self, XlsError> {
234        Self::new_with_options(reader, XlsOptions::default())
235    }
236
237    fn with_header_row(&mut self, header_row: HeaderRow) -> &mut Self {
238        self.options.header_row = header_row;
239        self
240    }
241
242    fn vba_project(&mut self) -> Option<Result<Cow<'_, VbaProject>, XlsError>> {
243        self.vba.as_ref().map(|vba| Ok(Cow::Borrowed(vba)))
244    }
245
246    /// Parses Workbook stream, no need for the relationships variable
247    fn metadata(&self) -> &Metadata {
248        &self.metadata
249    }
250
251    fn worksheet_range(&mut self, name: &str) -> Result<Range<Data>, XlsError> {
252        let sheet = self
253            .sheets
254            .get(name)
255            .map(|r| r.range.clone())
256            .ok_or_else(|| XlsError::WorksheetNotFound(name.into()))?;
257
258        match self.options.header_row {
259            HeaderRow::FirstNonEmptyRow => Ok(sheet),
260            HeaderRow::Row(header_row_idx) => {
261                // If `header_row` is a row index, adjust the range
262                if let (Some(start), Some(end)) = (sheet.start(), sheet.end()) {
263                    Ok(sheet.range((header_row_idx, start.1), end))
264                } else {
265                    Ok(sheet)
266                }
267            }
268        }
269    }
270
271    fn worksheets(&mut self) -> Vec<(String, Range<Data>)> {
272        self.sheets
273            .iter()
274            .map(|(name, sheet)| (name.to_owned(), sheet.range.clone()))
275            .collect()
276    }
277
278    fn worksheet_formula(&mut self, name: &str) -> Result<Range<String>, XlsError> {
279        self.sheets
280            .get(name)
281            .ok_or_else(|| XlsError::WorksheetNotFound(name.into()))
282            .map(|r| r.formula.clone())
283    }
284
285    #[cfg(feature = "picture")]
286    fn pictures(&self) -> Option<Vec<(String, Vec<u8>)>> {
287        self.pictures.to_owned()
288    }
289}
290
291#[derive(Debug, Clone, Copy)]
292struct Xti {
293    _isup_book: u16,
294    itab_first: i16,
295    _itab_last: i16,
296}
297
298impl<RS: Read + Seek> Xls<RS> {
299    fn parse_workbook(&mut self, mut reader: RS, mut cfb: Cfb) -> Result<(), XlsError> {
300        // gets workbook and worksheets stream, or early exit
301        let stream = cfb
302            .get_stream("Workbook", &mut reader)
303            .or_else(|_| cfb.get_stream("Book", &mut reader))?;
304
305        let mut sheet_names = Vec::new();
306        let mut strings = Vec::new();
307        let mut defined_names = Vec::new();
308        let mut xtis = Vec::new();
309        let mut formats = BTreeMap::new();
310        let mut xfs = Vec::new();
311        let mut biff = Biff::Biff8; // Binary Interchange File Format (BIFF) version
312        let codepage = self.options.force_codepage.unwrap_or(1200);
313        let mut encoding = XlsEncoding::from_codepage(codepage)?;
314        #[cfg(feature = "picture")]
315        let mut draw_group: Vec<u8> = Vec::new();
316        {
317            let wb = &stream;
318            let records = RecordIter { stream: wb };
319            for record in records {
320                let mut r = record?;
321                match r.typ {
322                    // 2.4.117 FilePass
323                    0x002F if read_u16(r.data) != 0 => return Err(XlsError::Password),
324                    // CodePage
325                    0x0042 => {
326                        if self.options.force_codepage.is_none() {
327                            encoding = XlsEncoding::from_codepage(read_u16(r.data))?
328                        }
329                    }
330                    0x013D => {
331                        let sheet_len = r.data.len() / 2;
332                        sheet_names.reserve(sheet_len);
333                        self.metadata.sheets.reserve(sheet_len);
334                    }
335                    // Date1904
336                    0x0022 => {
337                        if read_u16(r.data) == 1 {
338                            self.is_1904 = true
339                        }
340                    }
341                    // FORMATTING
342                    0x041E => {
343                        let (idx, format) = parse_format(&mut r, &encoding)?;
344                        formats.insert(idx, format);
345                    }
346                    // XFS
347                    0x00E0 => {
348                        xfs.push(parse_xf(&r)?);
349                    }
350                    // RRTabId
351                    0x0085 => {
352                        let (pos, sheet) = parse_sheet_metadata(&mut r, &encoding, biff)?;
353                        self.metadata.sheets.push(sheet.clone());
354                        sheet_names.push((pos, sheet.name)); // BoundSheet8
355                    }
356                    // BOF
357                    0x0809 => {
358                        let bof = parse_bof(&mut r)?;
359                        biff = bof.biff;
360                    }
361                    0x0018 => {
362                        // Lbl for defined_names
363                        let cch = r.data[3] as usize;
364                        let cce = read_u16(&r.data[4..]) as usize;
365                        let mut name = String::new();
366                        read_unicode_string_no_cch(&encoding, &r.data[14..], &cch, &mut name);
367                        let rgce = &r.data[r.data.len() - cce..];
368                        let formula = parse_defined_names(rgce)?;
369                        defined_names.push((name, formula));
370                    }
371                    0x0017 => {
372                        // ExternSheet
373                        let cxti = read_u16(r.data) as usize;
374                        xtis.extend(r.data[2..].chunks(6).take(cxti).map(|xti| Xti {
375                            _isup_book: read_u16(&xti[..2]),
376                            itab_first: read_i16(&xti[2..4]),
377                            _itab_last: read_i16(&xti[4..]),
378                        }));
379                    }
380                    0x00FC => strings = parse_sst(&mut r, &encoding)?, // SST
381                    #[cfg(feature = "picture")]
382                    0x00EB => {
383                        // MsoDrawingGroup
384                        draw_group.extend(r.data);
385                        if let Some(cont) = r.cont {
386                            draw_group.extend(cont.iter().flat_map(|v| *v));
387                        }
388                    }
389                    0x000A => break, // EOF,
390                    _ => (),
391                }
392            }
393        }
394
395        self.formats = xfs
396            .into_iter()
397            .map(|fmt| match formats.get(&fmt) {
398                Some(s) => *s,
399                _ => builtin_format_by_code(fmt),
400            })
401            .collect();
402
403        debug!("formats: {:?}", self.formats);
404
405        let defined_names = defined_names
406            .into_iter()
407            .map(|(name, (i, mut f))| {
408                if let Some(i) = i {
409                    let sh = xtis
410                        .get(i)
411                        .and_then(|xti| sheet_names.get(xti.itab_first as usize))
412                        .map_or("#REF", |sh| &sh.1);
413                    f = format!("{sh}!{f}");
414                }
415                (name, f)
416            })
417            .collect::<Vec<_>>();
418
419        debug!("defined_names: {:?}", defined_names);
420
421        let mut sheets = BTreeMap::new();
422        let fmla_sheet_names = sheet_names
423            .iter()
424            .map(|(_, n)| n.clone())
425            .collect::<Vec<_>>();
426        for (pos, name) in sheet_names {
427            let sh = &stream[pos..];
428            let records = RecordIter { stream: sh };
429            let mut cells = Vec::new();
430            let mut formulas = Vec::new();
431            let mut fmla_pos = (0, 0);
432            let mut merge_cells = Vec::new();
433            for record in records {
434                let r = record?;
435                match r.typ {
436                    // 512: Dimensions
437                    0x0200 => {
438                        let Dimensions { start, end } = parse_dimensions(r.data)?;
439                        let rows = (end.0 - start.0 + 1) as usize;
440                        let cols = (end.1 - start.1 + 1) as usize;
441                        cells.reserve(rows.saturating_mul(cols));
442                    }
443                    //0x0201 => cells.push(parse_blank(r.data)?), // 513: Blank
444                    0x0203 => cells.push(parse_number(r.data, &self.formats, self.is_1904)?), // 515: Number
445                    0x0204 => cells.extend(parse_label(r.data, &encoding, biff)?), // 516: Label [MS-XLS 2.4.148]
446                    0x0205 => cells.push(parse_bool_err(r.data)?),                 // 517: BoolErr
447                    0x0207 => {
448                        // 519 String (formula value)
449                        let val = Data::String(parse_string(r.data, &encoding, biff)?);
450                        cells.push(Cell::new(fmla_pos, val))
451                    }
452                    0x027E => cells.push(parse_rk(r.data, &self.formats, self.is_1904)?), // 638: Rk
453                    0x00FD => cells.extend(parse_label_sst(r.data, &strings)?), // LabelSst
454                    0x00BD => parse_mul_rk(r.data, &mut cells, &self.formats, self.is_1904)?, // 189: MulRk
455                    0x00E5 => parse_merge_cells(r.data, &mut merge_cells)?, // 229: Merge Cells
456                    0x000A => break,                                        // 10: EOF,
457                    0x0006 => {
458                        // 6: Formula
459                        if r.data.len() < 20 {
460                            return Err(XlsError::Len {
461                                expected: 20,
462                                found: r.data.len(),
463                                typ: "Formula",
464                            });
465                        }
466                        let row = read_u16(r.data);
467                        let col = read_u16(&r.data[2..]);
468                        fmla_pos = (row as u32, col as u32);
469                        if let Some(val) = parse_formula_value(&r.data[6..14])? {
470                            // If the value is a string
471                            // it will appear in 0x0207 record coming next
472                            cells.push(Cell::new(fmla_pos, val));
473                        }
474                        let fmla = parse_formula(
475                            &r.data[20..],
476                            &fmla_sheet_names,
477                            &defined_names,
478                            &xtis,
479                            &encoding,
480                        )
481                        .unwrap_or_else(|e| {
482                            debug!("{}", e);
483                            format!(
484                                "Unrecognised formula \
485                                 for cell ({}, {}): {:?}",
486                                row, col, e
487                            )
488                        });
489                        formulas.push(Cell::new(fmla_pos, fmla));
490                    }
491                    _ => (),
492                }
493            }
494            let range = Range::from_sparse(cells);
495            let formula = Range::from_sparse(formulas);
496            sheets.insert(
497                name,
498                SheetData {
499                    range,
500                    formula,
501                    merge_cells,
502                },
503            );
504        }
505
506        self.sheets = sheets;
507        self.metadata.names = defined_names;
508
509        #[cfg(feature = "picture")]
510        if !draw_group.is_empty() {
511            let pics = parse_pictures(&draw_group)?;
512            if !pics.is_empty() {
513                self.pictures = Some(pics);
514            }
515        }
516
517        Ok(())
518    }
519}
520
521/// https://learn.microsoft.com/en-us/openspecs/office_file_formats/ms-xls/4d6a3d1e-d7c5-405f-bbae-d01e9cb79366
522struct Bof {
523    /// Binary Interchange File Format
524    biff: Biff,
525}
526
527/// https://www.loc.gov/preservation/digital/formats/fdd/fdd000510.shtml#notes
528#[derive(Clone, Copy)]
529enum Biff {
530    Biff2,
531    Biff3,
532    Biff4,
533    Biff5,
534    Biff8,
535    // Used by MS-XLSB Workbook(2.1.7.61) or Worksheet(2.1.7.61) which are not supported yet.
536    // Biff12,
537}
538
539/// BOF [MS-XLS] 2.4.21
540fn parse_bof(r: &mut Record<'_>) -> Result<Bof, XlsError> {
541    let mut dt = 0;
542    let biff_version = read_u16(&r.data[..2]);
543
544    if r.data.len() >= 4 {
545        dt = read_u16(&r.data[2..]);
546    };
547
548    let biff = match biff_version {
549        0x0200 | 0x0002 | 0x0007 => Biff::Biff2,
550        0x0300 => Biff::Biff3,
551        0x0400 => Biff::Biff4,
552        0x0500 => Biff::Biff5,
553        0x0600 => Biff::Biff8,
554        0 => {
555            if dt == 0x1000 {
556                Biff::Biff5
557            } else {
558                Biff::Biff8
559            }
560        }
561        _ => Biff::Biff8,
562    };
563
564    Ok(Bof { biff })
565}
566
567/// BoundSheet8 [MS-XLS 2.4.28]
568fn parse_sheet_metadata(
569    r: &mut Record<'_>,
570    encoding: &XlsEncoding,
571    biff: Biff,
572) -> Result<(usize, Sheet), XlsError> {
573    let pos = read_u32(r.data) as usize;
574    let visible = match r.data[4] & 0b0011_1111 {
575        0x00 => SheetVisible::Visible,
576        0x01 => SheetVisible::Hidden,
577        0x02 => SheetVisible::VeryHidden,
578        e => {
579            return Err(XlsError::Unrecognized {
580                typ: "BoundSheet8:hsState",
581                val: e,
582            });
583        }
584    };
585    let typ = match r.data[5] {
586        0x00 => SheetType::WorkSheet,
587        0x01 => SheetType::MacroSheet,
588        0x02 => SheetType::ChartSheet,
589        0x06 => SheetType::Vba,
590        e => {
591            return Err(XlsError::Unrecognized {
592                typ: "BoundSheet8:dt",
593                val: e,
594            });
595        }
596    };
597    r.data = &r.data[6..];
598    let mut name = parse_short_string(r, encoding, biff)?;
599    name.retain(|c| c != '\0');
600    Ok((pos, Sheet { name, visible, typ }))
601}
602
603fn parse_number(r: &[u8], formats: &[CellFormat], is_1904: bool) -> Result<Cell<Data>, XlsError> {
604    if r.len() < 14 {
605        return Err(XlsError::Len {
606            typ: "number",
607            expected: 14,
608            found: r.len(),
609        });
610    }
611    let row = read_u16(r) as u32;
612    let col = read_u16(&r[2..]) as u32;
613    let v = read_f64(&r[6..]);
614    let format = formats.get(read_u16(&r[4..]) as usize);
615
616    Ok(Cell::new((row, col), format_excel_f64(v, format, is_1904)))
617}
618
619fn parse_bool_err(r: &[u8]) -> Result<Cell<Data>, XlsError> {
620    if r.len() < 8 {
621        return Err(XlsError::Len {
622            typ: "BoolErr",
623            expected: 8,
624            found: r.len(),
625        });
626    }
627    let row = read_u16(r);
628    let col = read_u16(&r[2..]);
629    let pos = (row as u32, col as u32);
630    match r[7] {
631        0x00 => Ok(Cell::new(pos, Data::Bool(r[6] != 0))),
632        0x01 => Ok(Cell::new(pos, parse_err(r[6])?)),
633        e => Err(XlsError::Unrecognized {
634            typ: "fError",
635            val: e,
636        }),
637    }
638}
639
640fn parse_err(e: u8) -> Result<Data, XlsError> {
641    match e {
642        0x00 => Ok(Data::Error(CellErrorType::Null)),
643        0x07 => Ok(Data::Error(CellErrorType::Div0)),
644        0x0F => Ok(Data::Error(CellErrorType::Value)),
645        0x17 => Ok(Data::Error(CellErrorType::Ref)),
646        0x1D => Ok(Data::Error(CellErrorType::Name)),
647        0x24 => Ok(Data::Error(CellErrorType::Num)),
648        0x2A => Ok(Data::Error(CellErrorType::NA)),
649        0x2B => Ok(Data::Error(CellErrorType::GettingData)),
650        e => Err(XlsError::Unrecognized {
651            typ: "error",
652            val: e,
653        }),
654    }
655}
656
657fn parse_rk(r: &[u8], formats: &[CellFormat], is_1904: bool) -> Result<Cell<Data>, XlsError> {
658    if r.len() < 10 {
659        return Err(XlsError::Len {
660            typ: "rk",
661            expected: 10,
662            found: r.len(),
663        });
664    }
665    let row = read_u16(r);
666    let col = read_u16(&r[2..]);
667
668    Ok(Cell::new(
669        (row as u32, col as u32),
670        rk_num(&r[4..10], formats, is_1904),
671    ))
672}
673
674fn parse_merge_cells(r: &[u8], merge_cells: &mut Vec<Dimensions>) -> Result<(), XlsError> {
675    let count = read_u16(r);
676
677    for i in 0..count {
678        let offset: usize = (2 + i * 8).into();
679
680        let rf = read_u16(&r[offset..]);
681        let rl = read_u16(&r[offset + 2..]);
682        let cf = read_u16(&r[offset + 4..]);
683        let cl = read_u16(&r[offset + 6..]);
684
685        merge_cells.push(Dimensions {
686            start: (rf.into(), cf.into()),
687            end: (rl.into(), cl.into()),
688        })
689    }
690
691    Ok(())
692}
693
694fn parse_mul_rk(
695    r: &[u8],
696    cells: &mut Vec<Cell<Data>>,
697    formats: &[CellFormat],
698    is_1904: bool,
699) -> Result<(), XlsError> {
700    if r.len() < 6 {
701        return Err(XlsError::Len {
702            typ: "rk",
703            expected: 6,
704            found: r.len(),
705        });
706    }
707
708    let row = read_u16(r);
709    let col_first = read_u16(&r[2..]);
710    let col_last = read_u16(&r[r.len() - 2..]);
711
712    if r.len() != 6 + 6 * (col_last - col_first + 1) as usize {
713        return Err(XlsError::Len {
714            typ: "rk",
715            expected: 6 + 6 * (col_last - col_first + 1) as usize,
716            found: r.len(),
717        });
718    }
719
720    let mut col = col_first as u32;
721
722    for rk in r[4..r.len() - 2].chunks(6) {
723        cells.push(Cell::new((row as u32, col), rk_num(rk, formats, is_1904)));
724        col += 1;
725    }
726    Ok(())
727}
728
729fn rk_num(rk: &[u8], formats: &[CellFormat], is_1904: bool) -> Data {
730    let d100 = (rk[2] & 1) != 0;
731    let is_int = (rk[2] & 2) != 0;
732    let format = formats.get(read_u16(rk) as usize);
733
734    let mut v = [0u8; 8];
735    v[4..].copy_from_slice(&rk[2..]);
736    v[4] &= 0xFC;
737    if is_int {
738        let v = (read_i32(&v[4..8]) >> 2) as i64;
739        if d100 && v % 100 != 0 {
740            format_excel_f64(v as f64 / 100.0, format, is_1904)
741        } else {
742            format_excel_i64(if d100 { v / 100 } else { v }, format, is_1904)
743        }
744    } else {
745        let v = read_f64(&v);
746        format_excel_f64(if d100 { v / 100.0 } else { v }, format, is_1904)
747    }
748}
749
750/// ShortXLUnicodeString [MS-XLS 2.5.240]
751fn parse_short_string(
752    r: &mut Record<'_>,
753    encoding: &XlsEncoding,
754    biff: Biff,
755) -> Result<String, XlsError> {
756    if r.data.len() < 2 {
757        return Err(XlsError::Len {
758            typ: "short string",
759            expected: 2,
760            found: r.data.len(),
761        });
762    }
763
764    let cch = r.data[0] as usize;
765    r.data = &r.data[1..];
766    let mut high_byte = None;
767
768    if matches!(biff, Biff::Biff8) {
769        high_byte = Some(r.data[0] & 0x1 != 0);
770        r.data = &r.data[1..];
771    }
772
773    let mut s = String::with_capacity(cch);
774    let _ = encoding.decode_to(r.data, cch, &mut s, high_byte);
775    Ok(s)
776}
777
778/// XLUnicodeString [MS-XLS 2.5.294]
779fn parse_string(r: &[u8], encoding: &XlsEncoding, biff: Biff) -> Result<String, XlsError> {
780    if r.len() < 4 {
781        return Err(XlsError::Len {
782            typ: "string",
783            expected: 4,
784            found: r.len(),
785        });
786    }
787    let cch = read_u16(r) as usize;
788
789    let (high_byte, start) = match biff {
790        Biff::Biff2 | Biff::Biff3 | Biff::Biff4 | Biff::Biff5 => (None, 2),
791        _ => (Some(r[2] & 0x1 != 0), 3),
792    };
793
794    let mut s = String::with_capacity(cch);
795    let _ = encoding.decode_to(&r[start..], cch, &mut s, high_byte);
796    Ok(s)
797}
798
799fn parse_label(
800    r: &[u8],
801    encoding: &XlsEncoding,
802    biff: Biff,
803) -> Result<Option<Cell<Data>>, XlsError> {
804    if r.len() < 6 {
805        return Err(XlsError::Len {
806            typ: "label",
807            expected: 6,
808            found: r.len(),
809        });
810    }
811    let row = read_u16(r);
812    let col = read_u16(&r[2..]);
813    let _ixfe = read_u16(&r[4..]);
814    Ok(Some(Cell::new(
815        (row as u32, col as u32),
816        Data::String(parse_string(&r[6..], encoding, biff)?),
817    )))
818}
819
820fn parse_label_sst(r: &[u8], strings: &[String]) -> Result<Option<Cell<Data>>, XlsError> {
821    if r.len() < 10 {
822        return Err(XlsError::Len {
823            typ: "label sst",
824            expected: 10,
825            found: r.len(),
826        });
827    }
828    let row = read_u16(r);
829    let col = read_u16(&r[2..]);
830    let i = read_u32(&r[6..]) as usize;
831    if let Some(s) = strings.get(i) {
832        if !s.is_empty() {
833            return Ok(Some(Cell::new(
834                (row as u32, col as u32),
835                Data::String(s.clone()),
836            )));
837        }
838    }
839    Ok(None)
840}
841
842fn parse_dimensions(r: &[u8]) -> Result<Dimensions, XlsError> {
843    let (rf, rl, cf, cl) = match r.len() {
844        10 => (
845            read_u16(&r[0..2]) as u32,
846            read_u16(&r[2..4]) as u32,
847            read_u16(&r[4..6]) as u32,
848            read_u16(&r[6..8]) as u32,
849        ),
850        14 => (
851            read_u32(&r[0..4]),
852            read_u32(&r[4..8]),
853            read_u16(&r[8..10]) as u32,
854            read_u16(&r[10..12]) as u32,
855        ),
856        _ => {
857            return Err(XlsError::Len {
858                typ: "dimensions",
859                expected: 14,
860                found: r.len(),
861            });
862        }
863    };
864    if 1 <= rl && 1 <= cl {
865        Ok(Dimensions {
866            start: (rf, cf),
867            end: (rl - 1, cl - 1),
868        })
869    } else {
870        Ok(Dimensions {
871            start: (rf, cf),
872            end: (rf, cf),
873        })
874    }
875}
876
877fn parse_sst(r: &mut Record<'_>, encoding: &XlsEncoding) -> Result<Vec<String>, XlsError> {
878    if r.data.len() < 8 {
879        return Err(XlsError::Len {
880            typ: "sst",
881            expected: 8,
882            found: r.data.len(),
883        });
884    }
885    let len: usize = read_i32(&r.data[4..8]).try_into().unwrap();
886    let mut sst = Vec::with_capacity(len);
887    r.data = &r.data[8..];
888
889    for _ in 0..len {
890        sst.push(read_rich_extended_string(r, encoding)?);
891    }
892    Ok(sst)
893}
894
895/// Decode XF (extract only ifmt - Format identifier)
896///
897/// See: https://learn.microsoft.com/ru-ru/openspecs/office_file_formats/ms-xls/993d15c4-ec04-43e9-ba36-594dfb336c6d
898fn parse_xf(r: &Record<'_>) -> Result<u16, XlsError> {
899    if r.data.len() < 4 {
900        return Err(XlsError::Len {
901            typ: "xf",
902            expected: 4,
903            found: r.data.len(),
904        });
905    }
906
907    Ok(read_u16(&r.data[2..]))
908}
909
910/// Decode Format
911///
912/// See: https://learn.microsoft.com/ru-ru/openspecs/office_file_formats/ms-xls/300280fd-e4fe-4675-a924-4d383af48d3b
913fn parse_format(r: &mut Record<'_>, encoding: &XlsEncoding) -> Result<(u16, CellFormat), XlsError> {
914    if r.data.len() < 4 {
915        return Err(XlsError::Len {
916            typ: "format",
917            expected: 4,
918            found: r.data.len(),
919        });
920    }
921
922    let idx = read_u16(r.data);
923
924    let cch = read_u16(&r.data[2..]) as usize;
925    let high_byte = r.data[4] & 0x1 != 0;
926    r.data = &r.data[5..];
927    let mut s = String::with_capacity(cch);
928    encoding.decode_to(r.data, cch, &mut s, Some(high_byte));
929
930    Ok((idx, detect_custom_number_format(&s)))
931}
932
933/// Decode XLUnicodeRichExtendedString.
934///
935/// See: <https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-xls/173d9f51-e5d3-43da-8de2-be7f22e119b9>
936fn read_rich_extended_string(
937    r: &mut Record<'_>,
938    encoding: &XlsEncoding,
939) -> Result<String, XlsError> {
940    if r.data.is_empty() && !r.continue_record() || r.data.len() < 3 {
941        return Err(XlsError::Len {
942            typ: "rich extended string",
943            expected: 3,
944            found: r.data.len(),
945        });
946    }
947
948    let cch = read_u16(r.data) as usize;
949    let flags = r.data[2];
950
951    r.data = &r.data[3..];
952
953    let high_byte = flags & 0x1 != 0;
954
955    // how many FormatRun in rgRun data block
956    let mut c_run = 0;
957
958    // how many bytes in ExtRst data block
959    let mut cb_ext_rst = 0;
960
961    // if flag fRichSt exists, read cRun and forward.
962    if flags & 0x8 != 0 {
963        c_run = read_u16(r.data) as usize;
964        r.data = &r.data[2..];
965    }
966
967    // if flag fExtSt exists, read cbExtRst and forward.
968    if flags & 0x4 != 0 {
969        cb_ext_rst = read_i32(r.data) as usize;
970        r.data = &r.data[4..];
971    }
972
973    // read rgb data block for the string we want
974    let s = read_dbcs(encoding, cch, r, high_byte)?;
975
976    // skip rgRun data block. Note: each FormatRun contain 4 bytes.
977    r.skip(c_run * 4)?;
978
979    // skip ExtRst data block.
980    r.skip(cb_ext_rst)?;
981
982    Ok(s)
983}
984
985fn read_dbcs(
986    encoding: &XlsEncoding,
987    mut len: usize,
988    r: &mut Record<'_>,
989    mut high_byte: bool,
990) -> Result<String, XlsError> {
991    let mut s = String::with_capacity(len);
992    while len > 0 {
993        let (l, at) = encoding.decode_to(r.data, len, &mut s, Some(high_byte));
994        r.data = &r.data[at..];
995        len -= l;
996        if len > 0 {
997            if r.continue_record() {
998                high_byte = r.data[0] & 0x1 != 0;
999                r.data = &r.data[1..];
1000            } else {
1001                return Err(XlsError::EoStream("dbcs"));
1002            }
1003        }
1004    }
1005    Ok(s)
1006}
1007
1008fn read_unicode_string_no_cch(encoding: &XlsEncoding, buf: &[u8], len: &usize, s: &mut String) {
1009    encoding.decode_to(&buf[1..=*len], *len, s, Some(buf[0] & 0x1 != 0));
1010}
1011
1012struct Record<'a> {
1013    typ: u16,
1014    data: &'a [u8],
1015    cont: Option<Vec<&'a [u8]>>,
1016}
1017
1018impl<'a> Record<'a> {
1019    fn continue_record(&mut self) -> bool {
1020        match self.cont {
1021            None => false,
1022            Some(ref mut v) => {
1023                if v.is_empty() {
1024                    false
1025                } else {
1026                    self.data = v.remove(0);
1027                    true
1028                }
1029            }
1030        }
1031    }
1032
1033    fn skip(&mut self, mut len: usize) -> Result<(), XlsError> {
1034        while len > 0 {
1035            if self.data.is_empty() && !self.continue_record() {
1036                return Err(XlsError::ContinueRecordTooShort);
1037            }
1038            let l = min(len, self.data.len());
1039            let (_, next) = self.data.split_at(l);
1040            self.data = next;
1041            len -= l;
1042        }
1043        Ok(())
1044    }
1045}
1046
1047struct RecordIter<'a> {
1048    stream: &'a [u8],
1049}
1050
1051impl<'a> Iterator for RecordIter<'a> {
1052    type Item = Result<Record<'a>, XlsError>;
1053    fn next(&mut self) -> Option<Self::Item> {
1054        if self.stream.len() < 4 {
1055            return if self.stream.is_empty() {
1056                None
1057            } else {
1058                Some(Err(XlsError::EoStream("record type and length")))
1059            };
1060        }
1061        let t = read_u16(self.stream);
1062        let mut len = read_u16(&self.stream[2..]) as usize;
1063        if self.stream.len() < len + 4 {
1064            return Some(Err(XlsError::EoStream("record length")));
1065        }
1066        let (data, next) = self.stream.split_at(len + 4);
1067        self.stream = next;
1068        let d = &data[4..];
1069
1070        // Append next record data if it is a Continue record
1071        let cont = if next.len() > 4 && read_u16(next) == 0x003C {
1072            let mut cont = Vec::new();
1073            while self.stream.len() > 4 && read_u16(self.stream) == 0x003C {
1074                len = read_u16(&self.stream[2..]) as usize;
1075                if self.stream.len() < len + 4 {
1076                    return Some(Err(XlsError::EoStream("continue record length")));
1077                }
1078                let sp = self.stream.split_at(len + 4);
1079                cont.push(&sp.0[4..]);
1080                self.stream = sp.1;
1081            }
1082            Some(cont)
1083        } else {
1084            None
1085        };
1086
1087        Some(Ok(Record {
1088            typ: t,
1089            data: d,
1090            cont,
1091        }))
1092    }
1093}
1094
1095/// Formula parsing
1096///
1097/// Does not implement ALL possibilities, only Area are parsed
1098fn parse_defined_names(rgce: &[u8]) -> Result<(Option<usize>, String), XlsError> {
1099    if rgce.is_empty() {
1100        // TODO: do something better here ...
1101        return Ok((None, "empty rgce".to_string()));
1102    }
1103    let ptg = rgce[0];
1104    let res = match ptg {
1105        0x3a | 0x5a | 0x7a => {
1106            // PtgRef3d
1107            let ixti = read_u16(&rgce[1..3]) as usize;
1108            let mut f = String::new();
1109            // TODO: check with relative columns
1110            f.push('$');
1111            push_column(read_u16(&rgce[5..7]) as u32, &mut f);
1112            f.push('$');
1113            f.push_str(&format!("{}", read_u16(&rgce[3..5]) as u32 + 1));
1114            (Some(ixti), f)
1115        }
1116        0x3b | 0x5b | 0x7b => {
1117            // PtgArea3d
1118            let ixti = read_u16(&rgce[1..3]) as usize;
1119            let mut f = String::new();
1120            // TODO: check with relative columns
1121            f.push('$');
1122            push_column(read_u16(&rgce[7..9]) as u32, &mut f);
1123            f.push('$');
1124            f.push_str(&format!("{}", read_u16(&rgce[3..5]) as u32 + 1));
1125            f.push(':');
1126            f.push('$');
1127            push_column(read_u16(&rgce[9..11]) as u32, &mut f);
1128            f.push('$');
1129            f.push_str(&format!("{}", read_u16(&rgce[5..7]) as u32 + 1));
1130            (Some(ixti), f)
1131        }
1132        0x3c | 0x5c | 0x7c | 0x3d | 0x5d | 0x7d => {
1133            // PtgAreaErr3d or PtfRefErr3d
1134            let ixti = read_u16(&rgce[1..3]) as usize;
1135            (Some(ixti), "#REF!".to_string())
1136        }
1137        _ => (None, format!("Unsupported ptg: {:x}", ptg)),
1138    };
1139    Ok(res)
1140}
1141
1142/// Formula parsing
1143///
1144/// CellParsedFormula [MS-XLS 2.5.198.3]
1145fn parse_formula(
1146    mut rgce: &[u8],
1147    sheets: &[String],
1148    names: &[(String, String)],
1149    xtis: &[Xti],
1150    encoding: &XlsEncoding,
1151) -> Result<String, XlsError> {
1152    let mut stack = Vec::new();
1153    let mut formula = String::with_capacity(rgce.len());
1154    let cce = read_u16(rgce) as usize;
1155    rgce = &rgce[2..2 + cce];
1156    while !rgce.is_empty() {
1157        let ptg = rgce[0];
1158        rgce = &rgce[1..];
1159        match ptg {
1160            0x3a | 0x5a | 0x7a => {
1161                // PtgRef3d
1162                let ixti = read_u16(&rgce[0..2]);
1163                let rowu = read_u16(&rgce[2..]);
1164                let colu = read_u16(&rgce[4..]);
1165                let sh = xtis
1166                    .get(ixti as usize)
1167                    .and_then(|xti| sheets.get(xti.itab_first as usize))
1168                    .map_or("#REF", |sh| sh);
1169                stack.push(formula.len());
1170                formula.push_str(sh);
1171                formula.push('!');
1172                let col = colu << 2; // first 14 bits only
1173                if colu & 2 != 0 {
1174                    formula.push('$');
1175                }
1176                push_column(col as u32, &mut formula);
1177                if colu & 1 != 0 {
1178                    formula.push('$');
1179                }
1180                write!(&mut formula, "{}", rowu + 1).unwrap();
1181                rgce = &rgce[6..];
1182            }
1183            0x3b | 0x5b | 0x7b => {
1184                // PtgArea3d
1185                let ixti = read_u16(&rgce[0..2]);
1186                stack.push(formula.len());
1187                formula.push_str(sheets.get(ixti as usize).map_or("#REF", |s| &**s));
1188                formula.push('!');
1189                // TODO: check with relative columns
1190                formula.push('$');
1191                push_column(read_u16(&rgce[6..8]) as u32, &mut formula);
1192                write!(&mut formula, "${}:$", read_u16(&rgce[2..4]) as u32 + 1).unwrap();
1193                push_column(read_u16(&rgce[8..10]) as u32, &mut formula);
1194                write!(&mut formula, "${}", read_u16(&rgce[4..6]) as u32 + 1).unwrap();
1195                rgce = &rgce[10..];
1196            }
1197            0x3c | 0x5c | 0x7c => {
1198                // PtfRefErr3d
1199                let ixti = read_u16(&rgce[0..2]);
1200                stack.push(formula.len());
1201                formula.push_str(sheets.get(ixti as usize).map_or("#REF", |s| &**s));
1202                formula.push('!');
1203                formula.push_str("#REF!");
1204                rgce = &rgce[6..];
1205            }
1206            0x3d | 0x5d | 0x7d => {
1207                // PtgAreaErr3d
1208                let ixti = read_u16(&rgce[0..2]);
1209                stack.push(formula.len());
1210                formula.push_str(sheets.get(ixti as usize).map_or("#REF", |s| &**s));
1211                formula.push('!');
1212                formula.push_str("#REF!");
1213                rgce = &rgce[10..];
1214            }
1215            0x01 => {
1216                // PtgExp: array/shared formula, ignore
1217                debug!("ignoring PtgExp array/shared formula");
1218                stack.push(formula.len());
1219                rgce = &rgce[4..];
1220            }
1221            0x03..=0x11 => {
1222                // binary operation
1223                let e2 = stack.pop().ok_or(XlsError::StackLen)?;
1224                // imaginary 'e1' will actually already be the start of the binary op
1225                let op = match ptg {
1226                    0x03 => "+",
1227                    0x04 => "-",
1228                    0x05 => "*",
1229                    0x06 => "/",
1230                    0x07 => "^",
1231                    0x08 => "&",
1232                    0x09 => "<",
1233                    0x0A => "<=",
1234                    0x0B => "=",
1235                    0x0C => ">",
1236                    0x0D => ">=",
1237                    0x0E => "<>",
1238                    0x0F => " ",
1239                    0x10 => ",",
1240                    0x11 => ":",
1241                    _ => unreachable!(),
1242                };
1243                let e2 = formula.split_off(e2);
1244                write!(&mut formula, "{}{}", op, e2).unwrap();
1245            }
1246            0x12 => {
1247                let e = stack.last().ok_or(XlsError::StackLen)?;
1248                formula.insert(*e, '+');
1249            }
1250            0x13 => {
1251                let e = stack.last().ok_or(XlsError::StackLen)?;
1252                formula.insert(*e, '-');
1253            }
1254            0x14 => {
1255                formula.push('%');
1256            }
1257            0x15 => {
1258                let e = stack.last().ok_or(XlsError::StackLen)?;
1259                formula.insert(*e, '(');
1260                formula.push(')');
1261            }
1262            0x16 => {
1263                stack.push(formula.len());
1264            }
1265            0x17 => {
1266                stack.push(formula.len());
1267                formula.push('\"');
1268                let cch = rgce[0] as usize;
1269                read_unicode_string_no_cch(encoding, &rgce[1..], &cch, &mut formula);
1270                formula.push('\"');
1271                rgce = &rgce[2 + cch..];
1272            }
1273            0x18 => {
1274                rgce = &rgce[5..];
1275            }
1276            0x19 => {
1277                let etpg = rgce[0];
1278                rgce = &rgce[1..];
1279                match etpg {
1280                    0x01 | 0x02 | 0x08 | 0x20 | 0x21 => rgce = &rgce[2..],
1281                    0x04 => {
1282                        // PtgAttrChoose
1283                        let n = read_u16(&rgce[..2]) as usize + 1;
1284                        rgce = &rgce[2 + 2 * n..]; // ignore
1285                    }
1286                    0x10 => {
1287                        rgce = &rgce[2..];
1288                        let e = *stack.last().ok_or(XlsError::StackLen)?;
1289                        let e = formula.split_off(e);
1290                        write!(&mut formula, "SUM({})", e).unwrap();
1291                    }
1292                    0x40 | 0x41 => {
1293                        // PtfAttrSpace
1294                        let e = *stack.last().ok_or(XlsError::StackLen)?;
1295                        let space = match rgce[0] {
1296                            0x00 | 0x02 | 0x04 | 0x06 => ' ',
1297                            0x01 | 0x03 | 0x05 => '\r',
1298                            val => {
1299                                return Err(XlsError::Unrecognized {
1300                                    typ: "PtgAttrSpaceType",
1301                                    val,
1302                                });
1303                            }
1304                        };
1305                        let cch = rgce[1];
1306                        for _ in 0..cch {
1307                            formula.insert(e, space);
1308                        }
1309                        rgce = &rgce[2..];
1310                    }
1311                    e => return Err(XlsError::Etpg(e)),
1312                }
1313            }
1314            0x1C => {
1315                stack.push(formula.len());
1316                let err = rgce[0];
1317                rgce = &rgce[1..];
1318                match err {
1319                    0x00 => formula.push_str("#NULL!"),
1320                    0x07 => formula.push_str("#DIV/0!"),
1321                    0x0F => formula.push_str("#VALUE!"),
1322                    0x17 => formula.push_str("#REF!"),
1323                    0x1D => formula.push_str("#NAME?"),
1324                    0x24 => formula.push_str("#NUM!"),
1325                    0x2A => formula.push_str("#N/A"),
1326                    0x2B => formula.push_str("#GETTING_DATA"),
1327                    e => {
1328                        return Err(XlsError::Unrecognized {
1329                            typ: "BErr",
1330                            val: e,
1331                        });
1332                    }
1333                }
1334            }
1335            0x1D => {
1336                stack.push(formula.len());
1337                formula.push_str(if rgce[0] == 0 { "FALSE" } else { "TRUE" });
1338                rgce = &rgce[1..];
1339            }
1340            0x1E => {
1341                stack.push(formula.len());
1342                write!(&mut formula, "{}", read_u16(rgce)).unwrap();
1343                rgce = &rgce[2..];
1344            }
1345            0x1F => {
1346                stack.push(formula.len());
1347                write!(&mut formula, "{}", read_f64(rgce)).unwrap();
1348                rgce = &rgce[8..];
1349            }
1350            0x20 | 0x40 | 0x60 => {
1351                // PtgArray: ignore
1352                stack.push(formula.len());
1353                formula.push_str("{PtgArray}");
1354                rgce = &rgce[7..];
1355            }
1356            0x21 | 0x22 | 0x41 | 0x42 | 0x61 | 0x62 => {
1357                let (iftab, argc) = match ptg {
1358                    0x22 | 0x42 | 0x62 => {
1359                        let iftab = read_u16(&rgce[1..]) as usize;
1360                        let argc = rgce[0] as usize;
1361                        rgce = &rgce[3..];
1362                        (iftab, argc)
1363                    }
1364                    _ => {
1365                        let iftab = read_u16(rgce) as usize;
1366                        if iftab > crate::utils::FTAB_LEN {
1367                            return Err(XlsError::IfTab(iftab));
1368                        }
1369                        rgce = &rgce[2..];
1370                        let argc = crate::utils::FTAB_ARGC[iftab] as usize;
1371                        (iftab, argc)
1372                    }
1373                };
1374                if stack.len() < argc {
1375                    return Err(XlsError::StackLen);
1376                }
1377                if argc > 0 {
1378                    let args_start = stack.len() - argc;
1379                    let mut args = stack.split_off(args_start);
1380                    let start = args[0];
1381                    for s in &mut args {
1382                        *s -= start;
1383                    }
1384                    let fargs = formula.split_off(start);
1385                    stack.push(formula.len());
1386                    args.push(fargs.len());
1387                    formula.push_str(
1388                        crate::utils::FTAB
1389                            .get(iftab)
1390                            .ok_or(XlsError::IfTab(iftab))?,
1391                    );
1392                    formula.push('(');
1393                    for w in args.windows(2) {
1394                        formula.push_str(&fargs[w[0]..w[1]]);
1395                        formula.push(',');
1396                    }
1397                    formula.pop();
1398                    formula.push(')');
1399                } else {
1400                    stack.push(formula.len());
1401                    formula.push_str(crate::utils::FTAB[iftab]);
1402                    formula.push_str("()");
1403                }
1404            }
1405            0x23 | 0x43 | 0x63 => {
1406                let iname = read_u32(rgce) as usize - 1; // one-based
1407                stack.push(formula.len());
1408                formula.push_str(names.get(iname).map_or("#REF!", |n| &*n.0));
1409                rgce = &rgce[4..];
1410            }
1411            0x24 | 0x44 | 0x64 => {
1412                stack.push(formula.len());
1413                let row = read_u16(rgce) + 1;
1414                let col = read_u16(&[rgce[2], rgce[3] & 0x3F]);
1415                if rgce[3] & 0x80 != 0x80 {
1416                    formula.push('$');
1417                }
1418                push_column(col as u32, &mut formula);
1419                if rgce[3] & 0x40 != 0x40 {
1420                    formula.push('$');
1421                }
1422                formula.push_str(&format!("{}", row));
1423                rgce = &rgce[4..];
1424            }
1425            0x25 | 0x45 | 0x65 => {
1426                stack.push(formula.len());
1427                formula.push('$');
1428                push_column(read_u16(&rgce[4..6]) as u32, &mut formula);
1429                write!(&mut formula, "${}:$", read_u16(&rgce[0..2]) as u32 + 1).unwrap();
1430                push_column(read_u16(&rgce[6..8]) as u32, &mut formula);
1431                write!(&mut formula, "${}", read_u16(&rgce[2..4]) as u32 + 1).unwrap();
1432                rgce = &rgce[8..];
1433            }
1434            0x2A | 0x4A | 0x6A => {
1435                stack.push(formula.len());
1436                formula.push_str("#REF!");
1437                rgce = &rgce[4..];
1438            }
1439            0x2B | 0x4B | 0x6B => {
1440                stack.push(formula.len());
1441                formula.push_str("#REF!");
1442                rgce = &rgce[8..];
1443            }
1444            0x39 | 0x59 => {
1445                // PfgNameX
1446                stack.push(formula.len());
1447                formula.push_str("[PtgNameX]");
1448                rgce = &rgce[6..];
1449            }
1450            _ => {
1451                return Err(XlsError::Unrecognized {
1452                    typ: "ptg",
1453                    val: ptg,
1454                });
1455            }
1456        }
1457    }
1458    if stack.len() == 1 {
1459        Ok(formula)
1460    } else {
1461        Err(XlsError::InvalidFormula {
1462            stack_size: stack.len(),
1463        })
1464    }
1465}
1466
1467/// FormulaValue [MS-XLS 2.5.133]
1468fn parse_formula_value(r: &[u8]) -> Result<Option<Data>, XlsError> {
1469    match *r {
1470        // String, value should be in next record
1471        [0x00, .., 0xFF, 0xFF] => Ok(None),
1472        [0x01, _, b, .., 0xFF, 0xFF] => Ok(Some(Data::Bool(b != 0))),
1473        [0x02, _, e, .., 0xFF, 0xFF] => parse_err(e).map(Some),
1474        // ignore, return blank string value
1475        [0x03, _, .., 0xFF, 0xFF] => Ok(Some(Data::String("".to_string()))),
1476        [e, .., 0xFF, 0xFF] => Err(XlsError::Unrecognized {
1477            typ: "error",
1478            val: e,
1479        }),
1480        _ => Ok(Some(Data::Float(read_f64(r)))),
1481    }
1482}
1483
1484/// OfficeArtRecord [MS-ODRAW 1.3.1]
1485#[cfg(feature = "picture")]
1486struct ArtRecord<'a> {
1487    instance: u16,
1488    typ: u16,
1489    data: &'a [u8],
1490}
1491
1492#[cfg(feature = "picture")]
1493struct ArtRecordIter<'a> {
1494    stream: &'a [u8],
1495}
1496
1497#[cfg(feature = "picture")]
1498impl<'a> Iterator for ArtRecordIter<'a> {
1499    type Item = Result<ArtRecord<'a>, XlsError>;
1500    fn next(&mut self) -> Option<Self::Item> {
1501        if self.stream.len() < 8 {
1502            return if self.stream.is_empty() {
1503                None
1504            } else {
1505                Some(Err(XlsError::EoStream("art record header")))
1506            };
1507        }
1508        let ver_ins = read_u16(self.stream);
1509        let instance = ver_ins >> 4;
1510        let typ = read_u16(&self.stream[2..]);
1511        if typ < 0xF000 {
1512            return Some(Err(XlsError::Art("type range 0xF000 - 0xFFFF")));
1513        }
1514        let len = read_usize(&self.stream[4..]);
1515        if self.stream.len() < len + 8 {
1516            return Some(Err(XlsError::EoStream("art record length")));
1517        }
1518        let (d, next) = self.stream.split_at(len + 8);
1519        self.stream = next;
1520        let data = &d[8..];
1521
1522        Some(Ok(ArtRecord {
1523            instance,
1524            typ,
1525            data,
1526        }))
1527    }
1528}
1529
1530/// Parsing pictures
1531#[cfg(feature = "picture")]
1532fn parse_pictures(stream: &[u8]) -> Result<Vec<(String, Vec<u8>)>, XlsError> {
1533    let mut pics = Vec::new();
1534    let records = ArtRecordIter { stream };
1535    for record in records {
1536        let r = record?;
1537        match r.typ {
1538            // OfficeArtDggContainer [MS-ODRAW 2.2.12]
1539            // OfficeArtBStoreContainer [MS-ODRAW 2.2.20]
1540            0xF000 | 0xF001 => pics.extend(parse_pictures(r.data)?),
1541            // OfficeArtFBSE [MS-ODRAW 2.2.32]
1542            0xF007 => {
1543                let skip = 36 + r.data[33] as usize;
1544                pics.extend(parse_pictures(&r.data[skip..])?);
1545            }
1546            // OfficeArtBlip [MS-ODRAW 2.2.23]
1547            0xF01A | 0xF01B | 0xF01C | 0xF01D | 0xF01E | 0xF01F | 0xF029 | 0xF02A => {
1548                let ext_skip = match r.typ {
1549                    // OfficeArtBlipEMF [MS-ODRAW 2.2.24]
1550                    0xF01A => {
1551                        let skip = match r.instance {
1552                            0x3D4 => 50usize,
1553                            0x3D5 => 66,
1554                            _ => unreachable!(),
1555                        };
1556                        Ok(("emf", skip))
1557                    }
1558                    // OfficeArtBlipWMF [MS-ODRAW 2.2.25]
1559                    0xF01B => {
1560                        let skip = match r.instance {
1561                            0x216 => 50usize,
1562                            0x217 => 66,
1563                            _ => unreachable!(),
1564                        };
1565                        Ok(("wmf", skip))
1566                    }
1567                    // OfficeArtBlipPICT [MS-ODRAW 2.2.26]
1568                    0xF01C => {
1569                        let skip = match r.instance {
1570                            0x542 => 50usize,
1571                            0x543 => 66,
1572                            _ => unreachable!(),
1573                        };
1574                        Ok(("pict", skip))
1575                    }
1576                    // OfficeArtBlipJPEG [MS-ODRAW 2.2.27]
1577                    0xF01D | 0xF02A => {
1578                        let skip = match r.instance {
1579                            0x46A | 0x6E2 => 17usize,
1580                            0x46B | 0x6E3 => 33,
1581                            _ => unreachable!(),
1582                        };
1583                        Ok(("jpg", skip))
1584                    }
1585                    // OfficeArtBlipPNG [MS-ODRAW 2.2.28]
1586                    0xF01E => {
1587                        let skip = match r.instance {
1588                            0x6E0 => 17usize,
1589                            0x6E1 => 33,
1590                            _ => unreachable!(),
1591                        };
1592                        Ok(("png", skip))
1593                    }
1594                    // OfficeArtBlipDIB [MS-ODRAW 2.2.29]
1595                    0xF01F => {
1596                        let skip = match r.instance {
1597                            0x7A8 => 17usize,
1598                            0x7A9 => 33,
1599                            _ => unreachable!(),
1600                        };
1601                        Ok(("dib", skip))
1602                    }
1603                    // OfficeArtBlipTIFF [MS-ODRAW 2.2.30]
1604                    0xF029 => {
1605                        let skip = match r.instance {
1606                            0x6E4 => 17usize,
1607                            0x6E5 => 33,
1608                            _ => unreachable!(),
1609                        };
1610                        Ok(("tiff", skip))
1611                    }
1612                    _ => Err(XlsError::Art("picture type not support")),
1613                };
1614                let ext_skip = ext_skip?;
1615                pics.push((ext_skip.0.to_string(), Vec::from(&r.data[ext_skip.1..])));
1616            }
1617            _ => {}
1618        }
1619    }
1620    Ok(pics)
1621}