use crate::address::CellAddress;
use crate::model::{
CellChangeKind, CellDiff, SheetChange, Severity, 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)]
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 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 {
if !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();
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(),
max_severity,
})
}