logisheets_controller 1.16.1

Core spreadsheet engine for LogiSheets: formula evaluation, dependency tracking, and undo/redo.
Documentation
use super::utils::parse_cell;
use crate::calc_engine::calculator::calc_vertex::Value;
use crate::ext_book_manager::{ExtBook, ExtBooksManager};
use imbl::{HashMap, Vector};
use logisheets_base::{Addr, ExtBookId, SheetId, id_fetcher::SheetIdFetcherTrait};
use logisheets_parser::ast;
use logisheets_workbook::workbook::ExternalLink;

pub fn load_external_link<T>(
    manager: &mut ExtBooksManager,
    ext: &ExternalLink,
    fetcher: &mut T,
) -> ExtBookId
where
    T: SheetIdFetcherTrait,
{
    if let Some(id) = manager.book_id_manager.has(&ext.target) {
        return id;
    }
    // `<externalBook>` is only ONE of the things this part may hold — `<ddeLink>`
    // and `<oleLink>` are equally valid alternatives — so a workbook linked to a
    // DDE server or an OLE object has no external book here and used to crash
    // the whole load. Register the target so references to it resolve to a book
    // with no data, which is what an unreadable link is.
    let Some(ext_book) = ext.external_link_part.external_book.as_ref() else {
        let bid = manager.book_id_manager.get_or_register_id(&ext.target);
        manager.books.insert(
            bid,
            ExtBook {
                sheets: Vector::<SheetId>::new(),
                data_set: HashMap::new(),
            },
        );
        manager.orders.push_back(bid);
        return bid;
    };
    let sheets = if let Some(ext_sheet_names) = &ext_book.sheet_names {
        let names = ext_sheet_names
            .names
            .iter()
            .map(|v| fetcher.fetch_sheet_id(&v.val))
            .collect::<Vec<_>>();
        names.into()
    } else {
        Vector::<SheetId>::new()
    };
    let mut data_set = HashMap::<(SheetId, Addr), Value>::new();
    if let Some(ds) = &ext_book.sheet_data_set {
        ds.data.iter().enumerate().for_each(|(idx, sheet_data)| {
            // `<sheetDataSet>` and `<sheetNames>` are separate elements and
            // nothing in the file makes them agree, so a cached sheet with no
            // name is the file's business, not a reason to refuse it.
            let Some(sheet_id) = sheets.get(idx).cloned() else {
                return;
            };
            sheet_data.rows.iter().for_each(|r| {
                r.cells.iter().for_each(|ext_cell| {
                    if ext_cell.v.is_none() || ext_cell.r.is_none() {
                    } else {
                        let val_str = &ext_cell.v.as_ref().unwrap().value;
                        let cell_ref = ext_cell.r.as_ref().unwrap();
                        if let Some((r, c)) = parse_cell(cell_ref) {
                            use logisheets_workbook::prelude::StCellType;
                            let val = match &ext_cell.t {
                                StCellType::B => {
                                    let b = if val_str == "TRUE" || val_str == "1" {
                                        true
                                    } else {
                                        false
                                    };
                                    Value::Boolean(b)
                                }
                                StCellType::D => Value::Error(ast::Error::Unspecified), // TODO
                                StCellType::N => {
                                    let num = val_str.parse::<f64>();
                                    match num {
                                        Ok(n) => Value::Number(n),
                                        Err(_) => Value::Error(ast::Error::Unspecified),
                                    }
                                }
                                StCellType::E => Value::Error(ast::Error::from_err_str(&val_str)),
                                // A shared string cannot appear in a cached
                                // external value (there is no string table to
                                // point into), but refusing to load a whole
                                // workbook over a malformed one is not a
                                // reasonable trade either.
                                StCellType::S => Value::Text(val_str.clone()),
                                StCellType::Str => Value::Text(val_str.clone()),
                                // An inline string's text is in `<is>`, which
                                // this loader does not read; the value string
                                // is the best available and is usually empty.
                                StCellType::InlineStr => Value::Text(val_str.clone()),
                            };
                            data_set.insert((sheet_id, Addr { row: r, col: c }), val);
                        }
                    }
                })
            })
        })
    }
    let b = ExtBook { sheets, data_set };
    let bid = manager.book_id_manager.get_or_register_id(&ext.target);
    manager.books.insert(bid, b);
    manager.orders.push_back(bid);
    bid
}