use crate::{
CalculationCellId, CellAddress, DocumentPresentation, SheetId, TableId, ValidationError,
WorkbookSnapshot, XlsxDocument,
};
use std::collections::{BTreeMap, BTreeSet};
use std::sync::Arc;
mod batch;
mod metadata;
mod presentation;
mod single_edit;
pub(crate) use batch::{
BatchExecutionError, TableMaterializationBudget, TableMaterializationError,
rewrite_limit_detail,
};
pub use batch::{EditBatch, EditReceipt, WorkbookChange};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum DraftCellMutation {
Upsert { number_format_changed: bool },
Remove,
}
const DRAFT_MUTATION_ROW_CHUNK_SIZE: u32 = 256;
#[derive(Debug, Clone, Default)]
pub(crate) struct DraftCellMutationStore {
chunks: BTreeMap<(SheetId, u32), Arc<BTreeMap<CalculationCellId, DraftCellMutation>>>,
len: usize,
}
impl DraftCellMutationStore {
fn chunk(id: CalculationCellId) -> (SheetId, u32) {
(
id.sheet_id(),
(id.address().row().get() - 1) / DRAFT_MUTATION_ROW_CHUNK_SIZE,
)
}
pub(crate) fn get(&self, id: &CalculationCellId) -> Option<&DraftCellMutation> {
self.chunks
.get(&Self::chunk(*id))
.and_then(|chunk| chunk.get(id))
}
pub(crate) fn insert(
&mut self,
id: CalculationCellId,
mutation: DraftCellMutation,
) -> Option<DraftCellMutation> {
let chunk = self
.chunks
.entry(Self::chunk(id))
.or_insert_with(|| Arc::new(BTreeMap::new()));
let previous = Arc::make_mut(chunk).insert(id, mutation);
if previous.is_none() {
self.len += 1;
}
previous
}
pub(crate) fn iter(&self) -> impl Iterator<Item = (&CalculationCellId, &DraftCellMutation)> {
self.chunks.values().flat_map(|chunk| chunk.iter())
}
pub(crate) fn keys(&self) -> impl Iterator<Item = &CalculationCellId> {
self.iter().map(|(id, _)| id)
}
pub(crate) const fn is_empty(&self) -> bool {
self.len == 0
}
}
#[derive(Debug, Clone)]
pub struct WorkbookDraft {
workbook: Arc<WorkbookSnapshot>,
presentation: DocumentPresentation,
source_document: Option<Arc<XlsxDocument>>,
cell_mutations: DraftCellMutationStore,
presentation_cell_mutations: BTreeSet<CalculationCellId>,
presentation_sheet_mutations: BTreeSet<SheetId>,
added_sheets: BTreeSet<SheetId>,
changed_table_ids: BTreeSet<TableId>,
workbook_changed: bool,
}
impl WorkbookDraft {
pub fn new() -> Self {
Self {
workbook: Arc::new(WorkbookSnapshot::new_draft()),
presentation: DocumentPresentation::default(),
source_document: None,
cell_mutations: DraftCellMutationStore::default(),
presentation_cell_mutations: BTreeSet::new(),
presentation_sheet_mutations: BTreeSet::new(),
added_sheets: BTreeSet::new(),
changed_table_ids: BTreeSet::new(),
workbook_changed: true,
}
}
pub fn from_document(document: &XlsxDocument) -> Self {
Self {
workbook: Arc::new(document.workbook().clone()),
presentation: document.presentation().clone(),
source_document: Some(Arc::new(document.clone())),
cell_mutations: DraftCellMutationStore::default(),
presentation_cell_mutations: BTreeSet::new(),
presentation_sheet_mutations: BTreeSet::new(),
added_sheets: BTreeSet::new(),
changed_table_ids: BTreeSet::new(),
workbook_changed: false,
}
}
pub fn workbook(&self) -> &WorkbookSnapshot {
self.workbook.as_ref()
}
pub(crate) fn shared_workbook(&self) -> Arc<WorkbookSnapshot> {
Arc::clone(&self.workbook)
}
pub const fn presentation(&self) -> &DocumentPresentation {
&self.presentation
}
pub fn semantic_revision(&self) -> u64 {
self.workbook.semantic_revision()
}
pub const fn presentation_revision(&self) -> u64 {
self.presentation.revision()
}
pub const fn is_document_backed(&self) -> bool {
self.source_document.is_some()
}
pub fn document_kind(&self) -> Option<crate::XlsxDocumentKind> {
self.source_document.as_deref().map(XlsxDocument::kind)
}
pub(crate) fn clone_cancellable(&self, cancelled: &impl Fn() -> bool) -> Result<Self, ()> {
let workbook = Arc::clone(&self.workbook);
let presentation = self.presentation.clone_cancellable(cancelled)?;
if cancelled() {
return Err(());
}
let source_document = self.source_document.clone();
Ok(Self {
workbook,
presentation,
source_document,
cell_mutations: self.cell_mutations.clone(),
presentation_cell_mutations: clone_set_cancellable(
&self.presentation_cell_mutations,
cancelled,
)?,
presentation_sheet_mutations: clone_set_cancellable(
&self.presentation_sheet_mutations,
cancelled,
)?,
added_sheets: clone_set_cancellable(&self.added_sheets, cancelled)?,
changed_table_ids: clone_set_cancellable(&self.changed_table_ids, cancelled)?,
workbook_changed: self.workbook_changed,
})
}
#[cfg(test)]
pub(crate) fn from_snapshot_for_test(workbook: WorkbookSnapshot) -> Self {
Self {
workbook: Arc::new(workbook),
presentation: DocumentPresentation::default(),
source_document: None,
cell_mutations: DraftCellMutationStore::default(),
presentation_cell_mutations: BTreeSet::new(),
presentation_sheet_mutations: BTreeSet::new(),
added_sheets: BTreeSet::new(),
changed_table_ids: BTreeSet::new(),
workbook_changed: true,
}
}
}
fn annotated_text_replacement_required(sheet_id: SheetId, address: CellAddress) -> ValidationError {
ValidationError::AnnotatedTextReplacementRequired {
sheet_id: sheet_id.get(),
row: address.row().get(),
column: address.column().get(),
}
}
impl Default for WorkbookDraft {
fn default() -> Self {
Self::new()
}
}
fn next_revision(revision: u64) -> Result<u64, ValidationError> {
revision
.checked_add(1)
.ok_or(ValidationError::SemanticRevisionExhausted)
}
fn case_insensitive_key(value: &str) -> String {
value.chars().flat_map(char::to_lowercase).collect()
}
fn clone_set_cancellable<T>(
source: &BTreeSet<T>,
cancelled: &impl Fn() -> bool,
) -> Result<BTreeSet<T>, ()>
where
T: Clone + Ord,
{
let mut cloned = BTreeSet::new();
for value in source {
if cancelled() {
return Err(());
}
cloned.insert(value.clone());
}
Ok(cloned)
}
#[cfg(test)]
#[path = "draft_tests.rs"]
mod tests;