///|
/// The engine-owned row-addressed state inventory: one authoritative
/// enumeration of every worksheet feature that lives at (or spans) row
/// positions, with how row INSERTION treats it. Consumers that must not
/// move content wrongly (template repetition preflight) read this instead
/// of maintaining a parallel hand-list that drifts from the writer.
///|
/// How `insert_rows`/`duplicate_row_to` treat a class today.
pub(all) enum RowStateHandling {
/// Insertion adjusts the stored positions (staged, atomic — #136).
ShiftedByInsert
/// Written back unchanged; positions do NOT follow an insertion.
StaticOnWrite
/// Read into the model but never emitted by the writer.
DroppedByWriter
} derive(Eq, @debug.Debug)
///|
/// The row extent a class occupies.
pub(all) enum RowStateExtent {
/// Parsed positions; the highest 1-based row addressed.
RowsUpTo(Int)
/// Present but not row-interpretable (opaque markup, unparsable refs) —
/// treat as touching every row.
WholeSheet
/// Present but carries no row semantics (view / pane presentation state).
Positionless
} derive(Eq, @debug.Debug)
///|
pub(all) enum RowStateClass {
Cells
RowDimensions
MergedRanges
ConditionalFormats
DataValidations
HyperlinkAnchors
/// Hyperlinks whose `location` names an internal destination — the
/// anchor shifts but the TARGET text never does, so insertion can
/// silently retarget them.
InternalLinkTargets
AutoFilterRange
TableRanges
SparklineAnchors
ImageAnchors
ChartAnchors
ShapeAnchors
FormControlAnchors
SlicerAnchors
CommentAnchors
IgnoredErrorRanges
RowPageBreaks
CellValueMetadata
DimensionRef
PreservedDrawingAnchors
VmlContent
UnknownExtensions
ViewState
/// Workbook-level entry: defined names (ANY scope) whose refers_to
/// carries references QUALIFIED to this sheet — exactly the refs the
/// insertion staging rewrites. Appended by
/// `Workbook::sheet_row_state_inventory`, never by the worksheet.
DefinedNameReferences
/// Workbook-level entry: names SCOPED to this sheet whose refers_to
/// carries no qualified reference to it — the adjuster leaves their
/// text untouched, so an insertion silently changes what unqualified
/// context resolves them to.
SheetScopedNamesUnadjusted
} derive(Eq, @debug.Debug)
///|
pub struct RowStateEntry {
class : RowStateClass
extent : RowStateExtent
handling : RowStateHandling
} derive(Eq, @debug.Debug)
///|
/// Highest 1-based row of a cell or range reference, None when unparsable.
fn reference_max_row(reference : String) -> Int? {
match (try? parse_range_ref(reference)) {
Ok((_, _, max_row, _)) => Some(max_row)
Err(_) => None
}
}
///|
/// Highest row across a space-separated sqref list; None on any
/// unparsable component (callers degrade to WholeSheet).
fn sqref_max_row(sqref : String) -> Int? {
let mut best = 0
for token in sqref.split(" ") {
if token.length() == 0 {
continue
}
match reference_max_row(token.to_owned()) {
Some(row) => if row > best { best = row }
None => return None
}
}
if best == 0 {
None
} else {
Some(best)
}
}
///|
/// Folds one anchor reference into a running (present, parsed-max,
/// any-unparsable) accumulator.
priv struct ExtentAccumulator {
mut present : Bool
mut max_row : Int
mut opaque : Bool
}
///|
fn ExtentAccumulator::new() -> ExtentAccumulator {
{ present: false, max_row: 0, opaque: false }
}
///|
fn ExtentAccumulator::add_reference(
self : ExtentAccumulator,
reference : String,
) -> Unit {
self.present = true
match reference_max_row(reference) {
Some(row) => if row > self.max_row { self.max_row = row }
None => self.opaque = true
}
}
///|
fn ExtentAccumulator::add_sqref(
self : ExtentAccumulator,
sqref : String,
) -> Unit {
self.present = true
match sqref_max_row(sqref) {
Some(row) => if row > self.max_row { self.max_row = row }
None => self.opaque = true
}
}
///|
fn ExtentAccumulator::entry(
self : ExtentAccumulator,
class : RowStateClass,
handling : RowStateHandling,
into : Array[RowStateEntry],
) -> Unit {
if !self.present {
return
}
let extent = if self.opaque || self.max_row == 0 {
WholeSheet
} else {
RowsUpTo(self.max_row)
}
into.push({ class, extent, handling })
}
///|
/// The value of an attribute in a raw feature XML string, or None. Used
/// for sqref extraction from stored conditional-format / data-validation
/// markup without materializing the full parse.
fn raw_attr_sqref(xml : String) -> String? {
match (try? attr_value(xml, "sqref")) {
Ok(Some(value)) => Some(unescape_xml_text(value))
_ => None
}
}
///|
/// Every row-addressed feature PRESENT on this worksheet, with extent and
/// insertion handling. Absent classes are omitted. Sheet-scoped defined
/// names live on the workbook — use `Workbook::sheet_row_state_inventory`
/// for the complete picture.
pub fn Worksheet::row_state_inventory(self : Worksheet) -> Array[RowStateEntry] {
let entries : Array[RowStateEntry] = []
if self.cells.length() > 0 {
let mut max_row = 0
for cell in self.cells {
if cell.row > max_row {
max_row = cell.row
}
}
entries.push({
class: Cells,
extent: RowsUpTo(max_row),
handling: ShiftedByInsert,
})
}
if self.row_dimensions.size() > 0 {
let mut max_row = 0
for row, _ in self.row_dimensions {
if row > max_row {
max_row = row
}
}
entries.push({
class: RowDimensions,
extent: RowsUpTo(max_row),
handling: ShiftedByInsert,
})
}
let merges = ExtentAccumulator::new()
for range in self.merged_cells {
merges.add_reference(range)
}
merges.entry(MergedRanges, ShiftedByInsert, entries)
let cf = ExtentAccumulator::new()
for xml in self.conditional_formats {
cf.present = true
match raw_attr_sqref(xml) {
Some(sqref) => cf.add_sqref(sqref)
None => cf.opaque = true
}
}
cf.entry(ConditionalFormats, ShiftedByInsert, entries)
let dv = ExtentAccumulator::new()
for xml in self.data_validations {
dv.present = true
match raw_attr_sqref(xml) {
Some(sqref) => dv.add_sqref(sqref)
None => dv.opaque = true
}
}
dv.entry(DataValidations, ShiftedByInsert, entries)
let links = ExtentAccumulator::new()
let mut internal_targets = false
for link in self.hyperlinks {
links.add_reference(link.reference)
if link.location is Some(_) {
internal_targets = true
}
}
links.entry(HyperlinkAnchors, ShiftedByInsert, entries)
if internal_targets {
entries.push({
class: InternalLinkTargets,
extent: WholeSheet,
handling: StaticOnWrite,
})
}
match self.auto_filter {
Some(filter) => {
let acc = ExtentAccumulator::new()
acc.add_reference(filter.range_ref)
acc.entry(AutoFilterRange, ShiftedByInsert, entries)
}
None => ()
}
let tables = ExtentAccumulator::new()
for table in self.tables {
tables.add_reference(table.range_ref)
}
tables.entry(TableRanges, ShiftedByInsert, entries)
let sparks = ExtentAccumulator::new()
for group in self.sparkline_groups {
for sparkline in group.sparklines {
sparks.add_reference(sparkline.location)
// insertion shifts the source range too (worksheet.mbt:5678);
// sheet-qualified or otherwise unparsable sources degrade the
// class to WholeSheet via the accumulator
sparks.add_reference(sparkline.range_ref)
}
}
sparks.entry(SparklineAnchors, ShiftedByInsert, entries)
let images = ExtentAccumulator::new()
for image in self.images {
images.add_reference(image.reference)
}
images.entry(ImageAnchors, ShiftedByInsert, entries)
let charts = ExtentAccumulator::new()
for chart in self.charts {
charts.add_reference(chart.reference)
}
charts.entry(ChartAnchors, ShiftedByInsert, entries)
let shapes = ExtentAccumulator::new()
for shape in self.shapes {
shapes.add_reference(shape.cell)
}
shapes.entry(ShapeAnchors, StaticOnWrite, entries)
let controls = ExtentAccumulator::new()
for control in self.form_controls {
controls.add_reference(control.cell)
}
controls.entry(FormControlAnchors, StaticOnWrite, entries)
let slicers = ExtentAccumulator::new()
for slicer in self.slicers {
slicers.add_reference(slicer.cell)
}
slicers.entry(SlicerAnchors, StaticOnWrite, entries)
let comments = ExtentAccumulator::new()
for comment in self.comments {
comments.add_reference(comment.cell)
}
comments.entry(CommentAnchors, StaticOnWrite, entries)
let ignored = ExtentAccumulator::new()
for item in self.ignored_errors {
ignored.add_sqref(item.sqref)
}
ignored.entry(IgnoredErrorRanges, StaticOnWrite, entries)
if self.row_breaks.length() > 0 {
// row-break ids are 0-based (id = row - 1, #136); report the 1-based
// row below the break, capped to the grid
let mut max_row = 0
for brk in self.row_breaks {
let row = if brk.id >= cell_ref_max_rows {
cell_ref_max_rows
} else {
brk.id + 1
}
if row > max_row {
max_row = row
}
}
entries.push({
class: RowPageBreaks,
extent: RowsUpTo(max_row),
handling: ShiftedByInsert,
})
}
let vm = ExtentAccumulator::new()
for reference, _ in self.cell_vm {
vm.add_reference(reference)
}
vm.entry(CellValueMetadata, DroppedByWriter, entries)
match self.dimension_ref {
Some(reference) => {
let acc = ExtentAccumulator::new()
acc.add_reference(reference)
acc.entry(DimensionRef, StaticOnWrite, entries)
}
None => ()
}
if self.preserved_drawing_anchors.length() > 0 ||
self.preserved_drawing_parts.length() > 0 {
entries.push({
class: PreservedDrawingAnchors,
extent: WholeSheet,
handling: StaticOnWrite,
})
}
if self.vml_drawing_xml is Some(_) || self.vml_drawing_hf_xml is Some(_) {
entries.push({
class: VmlContent,
extent: WholeSheet,
handling: StaticOnWrite,
})
}
if self.unknown_ext_blocks.length() > 0 {
entries.push({
class: UnknownExtensions,
extent: WholeSheet,
handling: StaticOnWrite,
})
}
if self.sheet_views.length() > 0 {
entries.push({
class: ViewState,
extent: Positionless,
handling: StaticOnWrite,
})
}
entries
}
///|
/// Row bound of one defined-name reference token (the text after a
/// `Sheet!` qualifier, `$` markers included): a parsed max row, no row
/// bound at all (column refs span every row but cannot overflow a row
/// insertion), or opaque.
priv enum DefinedRefRowBound {
BoundedRows(Int)
NoRowBound
OpaqueRef
}
///|
/// Mirrors adjust_ref_after_row_insert EXACTLY: the adjuster shifts only
/// cell:cell ranges, row:row ranges, single cells, and single rows;
/// column refs pass through unchanged and carry no row semantics; every
/// other shape (heterogeneous ranges like A5:B, arbitrary text) passes
/// through unchanged while still LOOKING row-positioned — those are
/// opaque, never bounded-shifted (codex round 2).
fn defined_ref_row_bound(text : String) -> DefinedRefRowBound {
match text.find(":") {
Some(pos) => {
let left = text.substring(start=0, end=pos)
let right = text.substring(start=pos + 1)
match (parse_cell_ref(left), parse_cell_ref(right)) {
(Some((r1, _, _, _)), Some((r2, _, _, _))) =>
return BoundedRows(if r1 > r2 { r1 } else { r2 })
_ => ()
}
match (parse_row_ref(left), parse_row_ref(right)) {
(Some((r1, _)), Some((r2, _))) =>
return BoundedRows(if r1 > r2 { r1 } else { r2 })
_ => ()
}
match (parse_column_ref(left), parse_column_ref(right)) {
(Some(_), Some(_)) => return NoRowBound
_ => ()
}
OpaqueRef
}
None => {
match parse_cell_ref(text) {
Some((row, _, _, _)) => return BoundedRows(row)
None => ()
}
match parse_row_ref(text) {
Some((row, _)) => return BoundedRows(row)
None => ()
}
match parse_column_ref(text) {
Some(_) => return NoRowBound
None => ()
}
OpaqueRef
}
}
}
///|
/// The complete row-addressed inventory for one sheet. Defined names are
/// classified by MIRRORING THE ADJUSTER, not by scope (codex round 1):
/// the probe runs the same qualified-reference scan the insertion
/// staging runs, so a workbook-scoped `Data!$A$1048576` lands in Data's
/// `DefinedNameReferences` (it IS rewritten and can overflow), while a
/// sheet-scoped `$A$9` with no qualifier lands in
/// `SheetScopedNamesUnadjusted` (its text never moves).
pub fn Workbook::sheet_row_state_inventory(
self : Workbook,
sheet_name : StringView,
) -> Array[RowStateEntry] raise XlsxError {
let sheet = self.require_sheet(sheet_name)
let entries = sheet.row_state_inventory()
let scope_key = defined_name_scope_key(sheet.name())
let adjusted = ExtentAccumulator::new()
let mut unadjusted_scoped = false
for name in self.defined_names {
let visited = Ref(0)
let probe = fn(ref_text : StringView) -> String? raise XlsxError {
visited.val = visited.val + 1
let owned = ref_text.to_owned()
adjusted.present = true
match defined_ref_row_bound(owned) {
BoundedRows(row) => if row > adjusted.max_row { adjusted.max_row = row }
NoRowBound => ()
OpaqueRef => adjusted.opaque = true
}
Some(owned)
}
let scan = try? adjust_defined_name_refers_to_for_sheet(
name.refers_to,
sheet.name(),
probe,
)
if scan is Err(_) {
adjusted.present = true
adjusted.opaque = true
}
ignore(visited.val)
// a sheet-scoped name is static-hazardous whenever ANY of its text
// sits outside the qualified refs the adjuster rewrites — a partially
// qualified "Data!$A$5+$B$1048576" keeps its unqualified half static
// even though the probe visited the first half (codex round 2)
if defined_name_scope_key(name.scope) == scope_key &&
name_has_unqualified_content(name.refers_to, sheet.name()) {
unadjusted_scoped = true
}
}
adjusted.entry(DefinedNameReferences, ShiftedByInsert, entries)
if unadjusted_scoped {
entries.push({
class: SheetScopedNamesUnadjusted,
extent: WholeSheet,
handling: StaticOnWrite,
})
}
entries
}
///|
/// Whether any of a name's refers_to text sits OUTSIDE the qualified
/// references the adjuster rewrites for `sheet_name`. Runs the same scan
/// with an empty-substitution probe, strips every re-emitted qualifier,
/// and looks for leftover reference-shaped characters. Conservative:
/// scan errors and constant values (a bare "42") count as unqualified
/// content.
fn name_has_unqualified_content(
refers_to : String,
sheet_name : String,
) -> Bool {
let marked = try? adjust_defined_name_refers_to_for_sheet(
refers_to,
sheet_name,
_ => Some(""),
)
guard marked is Ok(scanned) else { return true }
let plain_prefix = sheet_name + "!"
let quoted_prefix = "'" + escape_sheet_name(sheet_name) + "'!"
let mut leftover = scanned
for prefix in [quoted_prefix, plain_prefix] {
let pieces : Array[String] = []
for piece in leftover.split(prefix) {
pieces.push(piece.to_owned())
}
leftover = pieces.join("")
}
for unit in leftover {
// quote characters count: a string literal in refers_to is static
// text by definition, even when the (literal-unaware, engine-
// faithful) scan consumed a qualifier spelled inside it — the
// "Data!" constant shape (codex round 3)
if unit is ('A'..='Z' | 'a'..='z' | '0'..='9' | '$' | '"' | '\'') {
return true
}
}
false
}
///|
/// The first stored cell on this sheet carrying formula state — the
/// STORED presence test (`formula is Some(_)`, empty text included, so
/// shared-formula followers and array members count). `formula_refs()`
/// excludes empty formulas and must not be used for wrong-number gates.
pub fn Worksheet::first_stored_formula_cell(self : Worksheet) -> String? {
for cell in self.cells {
if cell.formula is Some(_) {
return Some(cell.reference)
}
}
None
}
///|
/// Clears the stored `` so the writer recomputes it from live
/// geometry (cells, merges, tables, auto-filter). A stored value is
/// otherwise echoed verbatim and goes stale after row insertion.
pub fn Worksheet::clear_dimension_ref(self : Worksheet) -> Unit {
self.dimension_ref = None
}
///|
/// Whether this sheet's conditional formatting is PROVABLY free of
/// formula criteria. False whenever proof is impossible: a raw rule
/// carries a formula-typed cfvo (icon sets expose only three of their
/// thresholds through the options surface), x14 extension state exists
/// (its cfvos are not modeled), the parsed rule count disagrees with the
/// raw count (the reader silently skips schema-exotic rule
/// types), or the parsed reader raises. Literal operands inside
/// elements (a cellIs against 5) do NOT disprove literal-ness —
/// callers judge those through the parsed options.
pub fn Worksheet::conditional_formats_provably_literal(
self : Worksheet,
) -> Bool {
let mut raw_rules = 0
for xml in self.conditional_formats {
if xml.find("type=\"formula\"") is Some(_) {
return false
}
let mut search = 0
while search < xml.length() {
match xml.substring(start=search).find(xml_probe_cfrule) {
Some(offset) => {
raw_rules += 1
search = search + offset + 1
}
None => break
}
}
}
if self.x14_data_bars.size() > 0 {
return false
}
match (try? self.get_conditional_formats()) {
Ok(parsed) => {
let mut modeled = 0
for _, options in parsed {
modeled += options.length()
}
modeled == raw_rules
}
Err(_) => false
}
}
///|
let xml_probe_cfrule : String = " Int? {
let mut index = from
let mut quote : UInt16 = ' '
let mut quoted = false
while index < xml.length() {
let unit = xml[index]
if quoted {
if unit == quote {
quoted = false
}
} else if unit == '"' || unit == '\'' {
quoted = true
quote = unit
} else if unit == '>' {
return Some(index + 1)
}
index += 1
}
None
}
///|
/// Whether the code unit terminates an element NAME (so
/// " Bool {
unit is (' ' | '\t' | '\r' | '\n' | '>' | '/')
}
///|
/// Text content of the DIRECT `element` child of the root whose content
/// begins at `content_start`. Depth-aware: a nested descendant carrying
/// the same element name never satisfies the lookup (a child's quoted
/// formula1 masked the root's reference formula and bypassed the
/// refusal policy — codex round 5). Some("") for absent or self-closing
/// elements; None when the structure resists the walk (callers classify
/// the whole entry opaque).
fn raw_direct_child_text(
xml : String,
content_start : Int,
element : String,
) -> String? {
let close = "" + element + ">"
let mut index = content_start
let mut depth = 0
while index < xml.length() {
let offset = match xml.substring(start=index).find("<") {
Some(pos) => pos
None => return Some("")
}
let start = index + offset
if start + 1 >= xml.length() {
return None
}
let marker = xml[start + 1]
if marker == '!' || marker == '?' {
// comments, declarations, PIs: outside the shapes the reader's
// canonical store produces — refuse to guess
return None
}
if marker == '/' {
if depth == 0 {
// the root's own close: the element is absent
return Some("")
}
depth -= 1
match raw_tag_end(xml, start) {
Some(next) => index = next
None => return None
}
continue
}
guard raw_tag_end(xml, start) is Some(tag_end) else { return None }
let self_closing = tag_end >= 2 && xml[tag_end - 2] == '/'
let name_end = start + 1 + element.length()
let is_match = depth == 0 &&
name_end < xml.length() &&
xml.substring(start=start + 1, end=name_end) == element &&
raw_name_boundary(xml[name_end])
if is_match {
if self_closing {
return Some("")
}
match xml.substring(start=tag_end).find(close) {
Some(rel) =>
return Some(
unescape_xml_text(xml.substring(start=tag_end, end=tag_end + rel)),
)
None => return None
}
}
if !self_closing {
depth += 1
}
index = tag_end
}
Some("")
}
///|
/// The raw criteria of every stored data validation, x14 and other
/// uninterpretable entries included as "opaque". XML entity escapes are
/// decoded; formula quote doubling is NOT collapsed.
pub fn Worksheet::raw_data_validation_criteria(
self : Worksheet,
) -> Array[RawDataValidationCriteria] {
let out : Array[RawDataValidationCriteria] = []
for xml in self.data_validations {
let trimmed = xml.trim(chars=" \t\r\n").to_owned()
let opaque : RawDataValidationCriteria = {
validation_type: "opaque",
sqref: "",
formula1: "",
formula2: "",
}
// the root must be EXACTLY dataValidation (not dataValidations),
// and type/sqref must come from the ROOT TAG alone — a nested
// element's attributes must not impersonate the root's (codex
// round 4)
let name_end = "= trimmed.length() ||
!raw_name_boundary(trimmed[name_end]) {
out.push(opaque)
continue
}
guard raw_tag_end(trimmed, 0) is Some(root_end) else {
out.push(opaque)
continue
}
let root_tag = trimmed.substring(start=0, end=root_end)
let validation_type = match (try? attr_value(root_tag, "type")) {
Ok(Some(value)) => unescape_xml_text(value)
Ok(None) => "none"
Err(_) => {
out.push(opaque)
continue
}
}
let sqref = match raw_attr_sqref(root_tag) {
Some(value) => value
None => ""
}
if sqref == "" {
out.push(opaque)
continue
}
if root_end >= 2 && trimmed[root_end - 2] == '/' {
// a self-closing root has no formula children at all
out.push({ validation_type, sqref, formula1: "", formula2: "" })
continue
}
let formula1 = raw_direct_child_text(trimmed, root_end, "formula1")
let formula2 = raw_direct_child_text(trimmed, root_end, "formula2")
match (formula1, formula2) {
(Some(first), Some(second)) =>
out.push({ validation_type, sqref, formula1: first, formula2: second })
_ => out.push(opaque)
}
}
out
}