use crate::address::CellAddress;
use crate::model::{CellChangeKind, CellDiff, Severity, SheetChange, WorkbookDiff};
#[derive(Clone, Debug)]
pub struct ViewFilter {
pub include_values: bool,
pub include_formulas: bool,
pub include_formatting: bool,
pub include_info_diagnostics: bool,
pub sheets: Option<Vec<usize>>,
}
impl Default for ViewFilter {
fn default() -> Self {
Self {
include_values: true,
include_formulas: true,
include_formatting: false,
include_info_diagnostics: false,
sheets: None,
}
}
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ChangeAnchor {
pub sheet_index: usize,
pub row: u32,
pub col: u32,
}
pub struct CellChangeRow<'a> {
pub anchor: ChangeAnchor,
pub sheet_name: &'a str,
pub address: &'a CellAddress,
pub change_kind: CellChangeKind,
pub old_display: String,
pub new_display: String,
pub formula_changed: bool,
pub old_formula: Option<&'a str>,
pub new_formula: Option<&'a str>,
pub max_severity: Option<Severity>,
}
impl<'a> CellChangeRow<'a> {
pub fn to_owned_row(&self) -> OwnedCellChangeRow {
OwnedCellChangeRow {
anchor: self.anchor.clone(),
sheet_name: self.sheet_name.to_owned(),
address: self.address.clone(),
change_kind: self.change_kind,
old_display: self.old_display.clone(),
new_display: self.new_display.clone(),
formula_changed: self.formula_changed,
old_formula: self.old_formula.map(|s| s.to_owned()),
new_formula: self.new_formula.map(|s| s.to_owned()),
max_severity: self.max_severity,
}
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct OwnedCellChangeRow {
pub anchor: ChangeAnchor,
pub sheet_name: String,
pub address: CellAddress,
pub change_kind: CellChangeKind,
pub old_display: String,
pub new_display: String,
pub formula_changed: bool,
pub old_formula: Option<String>,
pub new_formula: Option<String>,
pub max_severity: Option<Severity>,
}
pub struct SheetSummaryRow<'a> {
pub sheet_index: usize,
pub name: &'a str,
pub change: &'a SheetChange,
pub cells_changed: usize,
pub has_diagnostics: bool,
}
pub struct DiffView<'a> {
pub workbook: &'a WorkbookDiff,
}
impl<'a> DiffView<'a> {
pub fn new(workbook: &'a WorkbookDiff) -> Self {
Self { workbook }
}
pub fn sheets(&self) -> impl Iterator<Item = SheetSummaryRow<'a>> {
self.workbook
.sheets
.iter()
.enumerate()
.map(|(i, sd)| SheetSummaryRow {
sheet_index: i,
name: sd
.new_sheet
.as_ref()
.or(sd.old_sheet.as_ref())
.map(|s| s.name.as_str())
.unwrap_or("?"),
change: &sd.change,
cells_changed: sd.summary.cells_changed,
has_diagnostics: !sd.diagnostics.is_empty(),
})
}
pub fn rows(&'a self, filter: &ViewFilter) -> Vec<CellChangeRow<'a>> {
let mut out = Vec::new();
for (sheet_idx, sd) in self.workbook.sheets.iter().enumerate() {
if let Some(ref allowed) = filter.sheets
&& !allowed.contains(&sheet_idx)
{
continue;
}
let sheet_name = sd
.new_sheet
.as_ref()
.or(sd.old_sheet.as_ref())
.map(|s| s.name.as_str())
.unwrap_or("?");
for cd in &sd.cell_diffs {
if let Some(row) = cell_to_row(cd, sheet_idx, sheet_name, filter) {
out.push(row);
}
}
}
out
}
pub fn row_count(&self, filter: &ViewFilter) -> usize {
self.rows(filter).len()
}
pub fn first(&self, filter: &ViewFilter) -> Option<ChangeAnchor> {
self.rows(filter).into_iter().next().map(|r| r.anchor)
}
pub fn next_after(&self, current: &ChangeAnchor, filter: &ViewFilter) -> Option<ChangeAnchor> {
let mut past = false;
for row in self.rows(filter).into_iter() {
if past {
return Some(row.anchor);
}
if &row.anchor == current {
past = true;
}
}
None
}
pub fn previous_before(
&self,
current: &ChangeAnchor,
filter: &ViewFilter,
) -> Option<ChangeAnchor> {
let mut prev: Option<ChangeAnchor> = None;
for row in self.rows(filter).into_iter() {
if &row.anchor == current {
return prev;
}
prev = Some(row.anchor.clone());
}
None
}
pub fn sheet_rows(&'a self, sheet_index: usize, filter: &ViewFilter) -> Vec<CellChangeRow<'a>> {
let mut f = filter.clone();
f.sheets = Some(vec![sheet_index]);
self.rows(&f)
}
}
fn cell_to_row<'a>(
cd: &'a CellDiff,
sheet_index: usize,
sheet_name: &'a str,
filter: &ViewFilter,
) -> Option<CellChangeRow<'a>> {
let has_value = cd.value.is_some() && filter.include_values;
let has_formula = cd.formula.is_some() && filter.include_formulas;
if !has_value && !has_formula {
return None;
}
let old_display = cd
.value
.as_ref()
.map(|vc| vc.old.display_string())
.unwrap_or_default();
let new_display = cd
.value
.as_ref()
.map(|vc| vc.new.display_string())
.unwrap_or_default();
let max_severity = cd.diagnostics.iter().map(|d| d.severity).max();
let (old_formula, new_formula) = match &cd.formula {
Some(fc) => (
fc.old.as_ref().map(|t| t.raw.as_str()),
fc.new.as_ref().map(|t| t.raw.as_str()),
),
None => (None, None),
};
Some(CellChangeRow {
anchor: ChangeAnchor {
sheet_index,
row: cd.address.row,
col: cd.address.col,
},
sheet_name,
address: &cd.address,
change_kind: cd.change_kind(),
old_display,
new_display,
formula_changed: cd.formula.is_some(),
old_formula,
new_formula,
max_severity,
})
}