pub mod audit;
pub mod calc;
pub mod cone;
pub mod cursor;
pub mod diff;
pub mod histogram;
pub mod odf;
pub mod ooxml;
pub mod print;
use std::collections::HashSet;
use crate::intrinsics::fold;
use crate::messages::Refusal;
use crate::refers::{self, Kind};
pub use vla_lang::rows::{sheet_prefix, RefersTo, Row, Sink, Value, Visibility};
pub const DISTINCT_CAP: usize = 1 << 20;
pub struct Discard;
impl Sink for Discard {
fn row(&mut self, _row: &Row<'_>) {}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct SheetStats {
pub cells: u64,
pub formulas: u64,
pub array_anchors: u64,
pub rows: u32,
pub columns: u32,
pub part_bytes: usize,
pub distinct_r1c1: u64,
pub unreadable: u64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SheetInfo {
pub name: String,
pub visibility: Visibility,
pub part: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NameInfo {
pub name: String,
pub refers_to: String,
pub sheet: Option<usize>,
pub hidden: bool,
pub placeholder: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TableInfo {
pub name: String,
pub sheet: usize,
pub range: String,
pub columns: Vec<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Summary {
pub names: usize,
pub placeholders: usize,
pub tables: usize,
pub external_books: usize,
pub strings: usize,
pub string_bytes: usize,
}
pub trait Source: std::fmt::Debug {
fn label(&self) -> &str;
fn sheets(&self) -> &[SheetInfo];
fn names(&self) -> &[NameInfo];
fn tables(&self) -> &[TableInfo];
fn external_books(&self) -> &[String];
fn summary(&self) -> Summary;
fn walk_sheet_with(
&self,
index: usize,
sink: &mut dyn Sink,
book_distinct: &mut HashSet<String>,
) -> Result<SheetStats, Refusal>;
fn walk_sheet(&self, index: usize, sink: &mut dyn Sink) -> Result<SheetStats, Refusal> {
self.walk_sheet_with(index, sink, &mut HashSet::new())
}
fn printed_name(&self, name: &NameInfo) -> String {
match name.sheet.and_then(|i| self.sheets().get(i)) {
Some(sheet) => format!("{}!{}", sheet_prefix(&sheet.name), name.name),
None => name.name.clone(),
}
}
fn header_rows(&self, sink: &mut dyn Sink) {
for sheet in self.sheets() {
sink.row(&Row::Sheet {
name: &sheet.name,
visibility: sheet.visibility,
});
}
let mut names: Vec<(String, &str)> = self
.names()
.iter()
.filter(|n| !n.placeholder)
.map(|n| (self.printed_name(n), n.refers_to.as_str()))
.collect();
names.sort_by(|a, b| fold(&a.0).cmp(&fold(&b.0)).then_with(|| a.0.cmp(&b.0)));
for (name, refers_to) in &names {
sink.row(&Row::Name { name, refers_to });
}
let mut tables: Vec<&TableInfo> = self.tables().iter().collect();
tables.sort_by(|a, b| {
a.sheet
.cmp(&b.sheet)
.then_with(|| fold(&a.name).cmp(&fold(&b.name)))
});
let sheets = self.sheets();
for table in tables {
let Some(sheet) = sheets.get(table.sheet) else {
continue;
};
sink.row(&Row::Table {
name: &table.name,
sheet: &sheet.name,
range: &table.range,
});
}
}
}
pub fn open<'a>(bytes: &'a [u8], label: &str) -> Result<Box<dyn Source + 'a>, Refusal> {
if odf::is_opendocument(bytes) {
Ok(Box::new(odf::Document::open(bytes, label)?))
} else {
Ok(Box::new(ooxml::Package::open(bytes, label)?))
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn emit_formula(
sink: &mut dyn Sink,
sheet: &str,
addr: &str,
text: &str,
row: u32,
col: u32,
stats: &mut SheetStats,
sheet_distinct: &mut HashSet<String>,
book_distinct: &mut HashSet<String>,
) {
stats.formulas += 1;
sink.row(&Row::Formula { sheet, addr, text });
let found = refers::scan(text);
stats.unreadable += found.iter().filter(|r| r.kind == Kind::Unreadable).count() as u64;
let mut targets: Vec<RefersTo> = found.iter().map(|r| RefersTo::of(r, sheet)).collect();
targets.sort_by_key(print::target);
targets.dedup();
for to in &targets {
sink.row(&Row::Refers { sheet, addr, to });
}
let rendered = refers::r1c1(text, row, col);
if sheet_distinct.len() < DISTINCT_CAP {
sheet_distinct.insert(rendered.clone());
}
if book_distinct.len() < DISTINCT_CAP {
book_distinct.insert(rendered);
}
}
pub fn reflect_text(bytes: &[u8], label: &str) -> Result<String, Refusal> {
let source = open(bytes, label)?;
let mut printer = print::Printer::default();
source.header_rows(&mut printer);
for i in 0..source.sheets().len() {
source.walk_sheet(i, &mut printer)?;
}
Ok(printer.out)
}