///|
/// Read-time package-level feature detection. Several source features
/// vanish before (or instead of) modeling — external-link parts, query
/// tables, connections, pivot caches without a modeled pivot table, VML,
/// and any part outside the known taxonomy — so a post-read model cannot
/// prove their absence. The read path captures these flags from the raw
/// part list BEFORE lossy modeling; callers with wrong-number safety
/// contracts (template repetition) refuse on them.
///|
/// Raw-package feature flags with the first triggering part path per
/// class (archive entry order, deterministic per package). API-built
/// workbooks carry `XlsxPackageFeatures::none()`.
pub struct XlsxPackageFeatures {
external_links : Bool
query_tables : Bool
connections : Bool
pivot_parts : Bool
vml_present : Bool
unknown_parts : Bool
first_external_link : String?
first_query_table : String?
first_connections : String?
first_pivot_part : String?
first_vml_part : String?
first_unknown_part : String?
} derive(Eq, @debug.Debug)
///|
pub fn XlsxPackageFeatures::none() -> XlsxPackageFeatures {
{
external_links: false,
query_tables: false,
connections: false,
pivot_parts: false,
vml_present: false,
unknown_parts: false,
first_external_link: None,
first_query_table: None,
first_connections: None,
first_pivot_part: None,
first_vml_part: None,
first_unknown_part: None,
}
}
///|
/// The package-part taxonomy: parts the reader models, preserves, or that
/// cannot carry spreadsheet-evaluable content (printer settings, media,
/// custom XML, OLE payloads). Anything outside it raises `unknown_parts` —
/// fail-closed, so a nonconforming producer hiding a formula-bearing part
/// at an unexpected path is caught by exclusion rather than enumeration.
fn known_inert_part(lower : String) -> Bool {
if lower == "[content_types].xml" ||
lower == "xl/workbook.xml" ||
lower == "xl/styles.xml" ||
lower == "xl/sharedstrings.xml" ||
lower == "xl/calcchain.xml" ||
lower == "xl/metadata.xml" ||
lower == "xl/cellimages.xml" ||
lower == "xl/vbaproject.bin" ||
lower == "docprops/core.xml" ||
lower == "docprops/app.xml" ||
lower == "docprops/custom.xml" {
return true
}
if lower.has_suffix(".rels") &&
(lower.has_prefix("_rels/") || lower.find("/_rels/") is Some(_)) {
return true
}
for
prefix in [
"docprops/thumbnail", "xl/theme/", "xl/worksheets/", "xl/chartsheets/", "xl/tables/",
"xl/media/", "xl/drawings/", "xl/charts/", "xl/richdata/", "xl/embeddings/",
"xl/printersettings/", "customxml/", "xl/comments", "xl/slicers/", "xl/slicercaches/",
"xl/preserveddrawingparts/",
] {
if lower.has_prefix(prefix) {
return true
}
}
false
}
///|
/// Classifies every physical archive part into the feature flags. The
/// DECLARED CONTENT TYPE is authoritative and checked first: a part's
/// path says where it lives, its manifest type says what it is, and a
/// producer can legally place an external-link part under an inert-
/// looking path (codex round 1 demonstrated exactly that fail-open).
/// Path patterns remain as the fallback for undeclared parts, and the
/// dangerous-but-unmodeled macrosheet types fold into unknown_parts.
fn detect_package_features(
part_names : Map[String, String],
content_type_of : (String) -> String?,
) -> XlsxPackageFeatures {
let mut external_links = false
let mut query_tables = false
let mut connections = false
let mut pivot_parts = false
let mut vml_present = false
let mut unknown_parts = false
let mut first_external_link : String? = None
let mut first_query_table : String? = None
let mut first_connections : String? = None
let mut first_pivot_part : String? = None
let mut first_vml_part : String? = None
let mut first_unknown_part : String? = None
for _, physical in part_names {
let lower = physical.to_lower()
let declared = match content_type_of(physical) {
Some(value) => value.to_lower()
None => ""
}
// exact OFFICIAL type identities only — substring matching classified
// a vnd.example ...externallink-wrapper alias as an external link
// (codex round 2); unrecognized declared types fall through to the
// inert/unknown classification, never to the flag heuristics
if declared ==
"application/vnd.openxmlformats-officedocument.spreadsheetml.externallink+xml" {
if !external_links {
external_links = true
first_external_link = Some(physical)
}
continue
}
if declared ==
"application/vnd.openxmlformats-officedocument.spreadsheetml.querytable+xml" {
if !query_tables {
query_tables = true
first_query_table = Some(physical)
}
continue
}
if declared ==
"application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml" {
if !connections {
connections = true
first_connections = Some(physical)
}
continue
}
if declared ==
"application/vnd.openxmlformats-officedocument.spreadsheetml.pivottable+xml" ||
declared ==
"application/vnd.openxmlformats-officedocument.spreadsheetml.pivotcachedefinition+xml" ||
declared ==
"application/vnd.openxmlformats-officedocument.spreadsheetml.pivotcacherecords+xml" {
if !pivot_parts {
pivot_parts = true
first_pivot_part = Some(physical)
}
continue
}
if declared == "application/vnd.openxmlformats-officedocument.vmldrawing" {
if !vml_present {
vml_present = true
first_vml_part = Some(physical)
}
continue
}
// formula-capable classes the model never represents: fail closed
if declared == "application/vnd.ms-excel.macrosheet+xml" ||
declared == "application/vnd.ms-excel.intlmacrosheet+xml" {
if !unknown_parts {
unknown_parts = true
first_unknown_part = Some(physical)
}
continue
}
// path-based FLAG heuristics apply only to parts with no declared
// type; a declared non-matching type under xl/externalLinks/ is an
// unknown part, not an external link
if declared == "" && lower.has_suffix(".vml") {
if !vml_present {
vml_present = true
first_vml_part = Some(physical)
}
continue
}
if declared == "" && lower.has_prefix("xl/externallinks/") {
if !external_links {
external_links = true
first_external_link = Some(physical)
}
continue
}
if declared == "" && lower.has_prefix("xl/querytables/") {
if !query_tables {
query_tables = true
first_query_table = Some(physical)
}
continue
}
if declared == "" && lower == "xl/connections.xml" {
if !connections {
connections = true
first_connections = Some(physical)
}
continue
}
if declared == "" &&
(
lower.has_prefix("xl/pivottables/") ||
lower.has_prefix("xl/pivotcache/")
) {
if !pivot_parts {
pivot_parts = true
first_pivot_part = Some(physical)
}
continue
}
if known_inert_part(lower) {
continue
}
if !unknown_parts {
unknown_parts = true
first_unknown_part = Some(physical)
}
}
{
external_links,
query_tables,
connections,
pivot_parts,
vml_present,
unknown_parts,
first_external_link,
first_query_table,
first_connections,
first_pivot_part,
first_vml_part,
first_unknown_part,
}
}
///|
/// The package-level feature flags captured when this workbook was read;
/// `XlsxPackageFeatures::none()` for API-built workbooks.
pub fn Workbook::package_features(self : Workbook) -> XlsxPackageFeatures {
self.package_features
}
///|
/// The first table (sheet name, table name) whose SOURCE part carried
/// calculated-column or totals-row formula markup — content the model
/// drops and the writer regenerates without. None when no table does.
pub fn Workbook::first_table_with_source_formulas(
self : Workbook,
) -> (String, String)? {
for sheet in self.sheets {
for table in sheet.tables() {
if table.carries_source_formulas {
return Some((sheet.name(), table.name))
}
}
}
None
}