1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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
}