Skip to main content

sheets_diff/
objects.rs

1//! Non-cell workbook object detection and coverage diagnostics (RFC-023).
2//!
3//! calamine 0.35 does not expose object content (charts, images, comments,
4//! tables, pivot tables, hyperlinks, or data validation) through its public
5//! API. What it does expose is:
6//!   - `Sheet.typ: SheetType` — distinguishes WorkSheet, ChartSheet, MacroSheet, Vba
7//!   - `Sheet.visible: SheetVisible`
8//!
9//! The policy for v2.2 is `WarnIfPresent` for non-worksheet sheet types and
10//! a single coverage diagnostic explaining what is NOT compared. This prevents
11//! a misleading "no differences" result when meaningful objects are present.
12
13use calamine::{Reader, SheetType};
14
15use crate::model::{
16    Diagnostic, DiagnosticKind, DiagnosticLocation, DiffStage, Severity,
17};
18use crate::open::OpenedWorkbook;
19
20// ---------------------------------------------------------------------------
21// ObjectCompareMode (RFC-023 §6)
22// ---------------------------------------------------------------------------
23
24/// Controls how the presence of non-cell objects is handled.
25#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
26pub enum ObjectCompareMode {
27    /// Ignore objects entirely — no diagnostics.
28    Ignore,
29    /// Emit a coverage warning when non-worksheet sheets or any object
30    /// categories that cannot be compared are detected. Default.
31    #[default]
32    WarnIfPresent,
33    /// Compare what is available; emit diagnostics for the rest.
34    /// In v2.2 this behaves identically to `WarnIfPresent` because no
35    /// object content API is available in calamine 0.35.
36    CompareAvailable,
37}
38
39// ---------------------------------------------------------------------------
40// Public entry point
41// ---------------------------------------------------------------------------
42
43/// Detect non-cell objects on both workbook sides and emit coverage diagnostics.
44pub fn report_object_coverage(
45    old_wb: &mut OpenedWorkbook,
46    new_wb: &mut OpenedWorkbook,
47    mode: ObjectCompareMode,
48    diagnostics: &mut Vec<Diagnostic>,
49) {
50    if mode == ObjectCompareMode::Ignore {
51        return;
52    }
53
54    detect_non_worksheet_sheets(old_wb, diagnostics);
55    detect_non_worksheet_sheets(new_wb, diagnostics);
56
57    // Emit a single blanket coverage note so consumers know what was NOT compared.
58    emit_coverage_note(diagnostics);
59}
60
61// ---------------------------------------------------------------------------
62// Non-worksheet sheet detection
63// ---------------------------------------------------------------------------
64
65fn detect_non_worksheet_sheets(wb: &mut OpenedWorkbook, diagnostics: &mut Vec<Diagnostic>) {
66    for (index, sheet) in wb.reader.sheets_metadata().iter().enumerate() {
67        let kind = match sheet.typ {
68            SheetType::ChartSheet  => Some("chart sheet"),
69            SheetType::MacroSheet  => Some("macro sheet"),
70            SheetType::Vba         => Some("VBA module"),
71            SheetType::DialogSheet => Some("dialog sheet"),
72            SheetType::WorkSheet   => None, // ordinary — no warning needed
73        };
74        if let Some(kind_label) = kind {
75            diagnostics.push(Diagnostic {
76                severity: Severity::Warning,
77                kind: DiagnosticKind::UnsupportedWorkbookFeature {
78                    feature: kind_label.to_owned(),
79                },
80                location: DiagnosticLocation {
81                    stage: DiffStage::Metadata,
82                    sheet_order: Some(index),
83                    sheet_name: Some(sheet.name.clone()),
84                    address: None,
85                },
86                message: format!(
87                    "sheet '{}' is a {} — content not compared \
88                     (calamine 0.35 does not expose {} data)",
89                    sheet.name, kind_label, kind_label
90                ),
91            });
92        }
93    }
94}
95
96// ---------------------------------------------------------------------------
97// Blanket coverage note
98// ---------------------------------------------------------------------------
99
100fn emit_coverage_note(diagnostics: &mut Vec<Diagnostic>) {
101    diagnostics.push(Diagnostic {
102        severity: Severity::Info,
103        kind: DiagnosticKind::UnsupportedWorkbookFeature {
104            feature: "non-cell objects".to_owned(),
105        },
106        location: DiagnosticLocation {
107            stage: DiffStage::Metadata,
108            sheet_order: None,
109            sheet_name: None,
110            address: None,
111        },
112        message: "charts, images, comments, hyperlinks, tables, pivot tables, \
113                  data validation, and conditional formatting are not compared \
114                  in this version (calamine 0.35 does not expose object content)"
115            .into(),
116    });
117}