rxlsb 0.1.0

Pure Rust XLSB (Excel Binary Workbook) reader/writer library
Documentation

#[derive(Debug, Clone)]
pub enum CellValue {
    Text(String),
    Number(f64),
    Bool(bool),
    #[allow(dead_code)]
    Blank,
}

#[derive(Debug, Clone)]
pub struct CellInfo {
    pub row: u32,
    pub col: u32,
    pub value: CellValue,
    pub style_index: u32,
}

#[derive(Debug, Clone)]
pub struct MergeCell {
    pub row_first: u32,
    pub row_last: u32,
    pub col_first: u32,
    pub col_last: u32,
}

pub struct SheetParser {
    pub cells: Vec<CellInfo>,
    pub merges: Vec<MergeCell>,
    pub max_row: u32,
    pub max_col: u32,
}

impl SheetParser {
    pub fn parse(data: &[u8], sst_strings: &[&str]) -> Self {
        let mut cells = Vec::with_capacity(256);
        let mut merges = Vec::with_capacity(16);
        let mut max_row = 0u32;
        let mut max_col = 0u32;
        let mut current_row = 0u32;
        let mut pos = 0;
        
        while pos + 2 < data.len() {
            let record_type = if data[pos] >= 128 {
                pos += 2;
                ((data[pos-2] & 0x7F) as u32) | ((data[pos-1] & 0x7F) << 7) as u32
            } else {
                pos += 1;
                data[pos-1] as u32
            };
            
            let record_size = if data[pos] >= 128 {
                pos += 2;
                ((data[pos-2] & 0x7F) as u32) | ((data[pos-1] as u32) << 7)
            } else {
                pos += 1;
                data[pos-1] as u32
            };
            
            if pos + record_size as usize > data.len() { break; }
            
            match record_type {
                0 => {
                    current_row = u32::from_le_bytes([
                        data[pos], data[pos+1], data[pos+2], data[pos+3]
                    ]);
                    if current_row > max_row { max_row = current_row; }
                }
                7 => {
                    let col = u32::from_le_bytes([data[pos], data[pos+1], data[pos+2], data[pos+3]]);
                    let style_idx = (data[pos+4] as u32) | ((data[pos+5] as u32) << 8) | ((data[pos+6] as u32) << 16);
                    let sst_idx = u32::from_le_bytes([data[pos+8], data[pos+9], data[pos+10], data[pos+11]]);
                    if (sst_idx as usize) < sst_strings.len() {
                        cells.push(CellInfo {
                            row: current_row, col,
                            value: CellValue::Text(sst_strings[sst_idx as usize].to_string()),
                            style_index: style_idx,
                        });
                        if col > max_col { max_col = col; }
                    }
                }
                5 => {
                    let col = u32::from_le_bytes([data[pos], data[pos+1], data[pos+2], data[pos+3]]);
                    let style_idx = (data[pos+4] as u32) | ((data[pos+5] as u32) << 8) | ((data[pos+6] as u32) << 16);
                    let val_bytes = [data[pos+8], data[pos+9], data[pos+10], data[pos+11],
                                    data[pos+12], data[pos+13], data[pos+14], data[pos+15]];
                    let value = f64::from_bits(u64::from_le_bytes(val_bytes));
                    cells.push(CellInfo {
                        row: current_row, col, value: CellValue::Number(value), style_index: style_idx,
                    });
                    if col > max_col { max_col = col; }
                }
                4 => {
                    let col = u32::from_le_bytes([data[pos], data[pos+1], data[pos+2], data[pos+3]]);
                    let style_idx = (data[pos+4] as u32) | ((data[pos+5] as u32) << 8) | ((data[pos+6] as u32) << 16);
                    let value = data[pos+8] != 0;
                    cells.push(CellInfo {
                        row: current_row, col, value: CellValue::Bool(value), style_index: style_idx,
                    });
                    if col > max_col { max_col = col; }
                }
                176 => {
                    let rf = u32::from_le_bytes([data[pos], data[pos+1], data[pos+2], data[pos+3]]);
                    let rl = u32::from_le_bytes([data[pos+4], data[pos+5], data[pos+6], data[pos+7]]);
                    let cf = u32::from_le_bytes([data[pos+8], data[pos+9], data[pos+10], data[pos+11]]);
                    let cl = u32::from_le_bytes([data[pos+12], data[pos+13], data[pos+14], data[pos+15]]);
                    merges.push(MergeCell { row_first: rf, row_last: rl, col_first: cf, col_last: cl });
                    if rl > max_row { max_row = rl; }
                    if cl > max_col { max_col = cl; }
                }
                _ => {}
            }
            
            pos += record_size as usize;
        }
        
        SheetParser { cells, merges, max_row, max_col }
    }
    
    pub fn find_text_cell(&self, marker: &str) -> Option<(u32, u32)> {
        for cell in &self.cells {
            if let CellValue::Text(text) = &cell.value {
                if text == marker {
                    return Some((cell.row, cell.col));
                }
            }
        }
        None
    }
}