///|
/// Creates a new worksheet named `name`, appends it after the existing sheets,
/// and returns it for immediate use. Names must be unique and follow Excel's
/// rules: 1–31 characters, may not start or end with a single quote (`'`), and
/// may not contain any of `[ ] : * ? / \`.
///
/// Raises `XlsxError` if `name` is empty, too long, starts/ends with `'`,
/// contains an invalid character, or duplicates an existing sheet.
pub fn Workbook::add_sheet(
self : Workbook,
name : String,
) -> Worksheet raise XlsxError {
check_sheet_name(name)
match self.sheet_entry(name) {
Some(_) => raise SheetAlreadyExists(name~)
None => ()
}
let sheet = Worksheet::new(name)
sheet.workbook_owner_token = Some(self.worksheet_owner_token)
self.sheets.push(sheet)
self.sheet_order.push(Worksheet(self.sheets.length() - 1))
if self.sheet_order.length() == 1 {
self.active_sheet_index = 0
}
sheet
}
///|
pub fn Workbook::add_chart_sheet(
self : Workbook,
name : String,
chart_xml : String,
) -> ChartSheet raise XlsxError {
check_sheet_name(name)
match self.sheet_entry(name) {
Some(_) => raise SheetAlreadyExists(name~)
None => ()
}
let sheet = ChartSheet::new(name, chart_xml)
self.chart_sheets.push(sheet)
self.sheet_order.push(ChartSheet(self.chart_sheets.length() - 1))
if self.sheet_order.length() == 1 {
self.active_sheet_index = 0
}
sheet
}
///|
pub fn Workbook::add_chart_sheet_with_options(
self : Workbook,
name : String,
opts : ChartOptions,
) -> ChartSheet raise XlsxError {
let xml = chart_options_to_xml(opts)
self.add_chart_sheet(name, xml)
}
///|
pub fn Workbook::new_stream_writer(
self : Workbook,
sheet_name : StringView,
) -> StreamWriter raise XlsxError {
check_sheet_name(sheet_name)
let sheet_id = match self.sheet_entry_index(sheet_name) {
Some(value) => value + 1
None => raise SheetNotFound(name=sheet_name.to_owned())
}
let sheet = self.require_sheet(sheet_name)
if sheet.stream_state != Idle {
raise StreamModeConflict(msg="stream writer already active")
}
if sheet.cells().length() > 0 {
raise StreamModeConflict(msg="worksheet already has data")
}
sheet.stream_state = Writing
let date1904_now = () => {
match self.get_workbook_props().date_1904 {
Some(flag) => flag
None => false
}
}
StreamWriter::new(sheet, sheet_id, self.styles, date1904_now~)
}
///|
/// Returns the worksheet named `name`, or `None` if there is no worksheet with
/// that name. A chart sheet of the same name is not returned by this method.
/// Use `add_sheet` to create one, `get_sheet_list` to enumerate names, or
/// `sheets` to iterate over every worksheet.
pub fn Workbook::sheet(self : Workbook, name : StringView) -> Worksheet? {
let needle = normalize_sheet_name(name)
for sheet in self.sheets {
if normalize_sheet_name(sheet.name()) == needle {
return Some(sheet)
}
}
None
}
///|
fn Workbook::require_sheet(
self : Workbook,
sheet_name : StringView,
) -> Worksheet raise XlsxError {
check_sheet_name(sheet_name)
match self.sheet(sheet_name) {
Some(value) => value
None => raise SheetNotFound(name=sheet_name.to_owned())
}
}
///|
/// Rejects detached or foreign worksheet handles before workbook-owned styles,
/// options, or date-system settings are applied to their cells.
fn Workbook::require_owned_worksheet(
self : Workbook,
worksheet : Worksheet,
) -> Unit raise XlsxError {
match worksheet.workbook_owner_token {
Some(owner) if physical_equal(owner, self.worksheet_owner_token) => ()
_ =>
raise InvalidSheetOperation(
msg="worksheet does not belong to this workbook",
)
}
}
///|
pub fn Workbook::chart_sheet(self : Workbook, name : StringView) -> ChartSheet? {
let needle = normalize_sheet_name(name)
for sheet in self.chart_sheets {
if normalize_sheet_name(sheet.name()) == needle {
return Some(sheet)
}
}
None
}
///|
pub fn Workbook::sheet_name(self : Workbook, index : Int) -> String? {
if index < 0 || index >= self.sheet_order.length() {
return None
}
Some(self.sheet_entry_name(self.sheet_order[index]))
}
///|
/// Returns the names of all sheets — worksheets and chart sheets — in tab
/// order.
pub fn Workbook::get_sheet_list(self : Workbook) -> Array[String] {
let names : Array[String] = []
for entry in self.sheet_order {
names.push(self.sheet_entry_name(entry))
}
names
}
///|
pub fn Workbook::get_sheet_map(self : Workbook) -> Map[Int, String] {
let mapping : Map[Int, String] = Map([])
for index, entry in self.sheet_order {
mapping[index + 1] = self.sheet_entry_name(entry)
}
mapping
}
///|
pub fn Workbook::sheet_index(
self : Workbook,
name : StringView,
) -> Int raise XlsxError {
match self.sheet_entry_index(name) {
Some(value) => value
None => raise SheetNotFound(name=name.to_owned())
}
}
///|
/// Returns every worksheet as a view, for iteration, in the order the sheets
/// were created (not necessarily tab order, and excluding chart sheets). Use
/// `sheet` to look one up by name, or `get_sheet_list` for names in tab order.
pub fn Workbook::sheets(self : Workbook) -> ArrayView[Worksheet] {
self.sheets
}
///|
/// Returns the number of concrete worksheet cell records currently retained
/// by the workbook. Range metadata such as merges and validations is excluded.
pub fn Workbook::materialized_cell_count(self : Workbook) -> Int {
let mut count = 0
for sheet in self.sheets {
count = count + sheet.cells().length()
}
count
}
///|
/// Returns the retained row and column dimension records across all worksheets.
pub fn Workbook::materialized_row_column_dimension_count(
self : Workbook,
) -> Int {
let mut count = 0
for sheet in self.sheets {
count = count +
sheet.row_dimensions.length() +
sheet.col_dimensions.length()
}
count
}
///|
pub fn Workbook::chart_sheets(self : Workbook) -> ArrayView[ChartSheet] {
self.chart_sheets
}
///|
pub fn Workbook::styles(self : Workbook) -> ArrayView[Style] {
self.styles
}
///|
fn Workbook::conditional_styles(self : Workbook) -> ArrayView[Style] {
self.conditional_styles
}
///|
pub fn Workbook::defined_names(self : Workbook) -> ArrayView[DefinedName] {
self.defined_names
}
///|
pub fn Workbook::core_properties(self : Workbook) -> CoreProperties {
self.core_properties
}
///|
pub fn Workbook::set_core_properties(
self : Workbook,
props : CoreProperties,
) -> Unit {
self.core_properties = props
}
///|
pub fn Workbook::doc_properties(self : Workbook) -> CoreProperties {
self.core_properties
}
///|
pub fn Workbook::set_doc_properties(
self : Workbook,
props : CoreProperties,
) -> Unit {
self.core_properties = props
}
///|
pub fn Workbook::get_doc_props(self : Workbook) -> CoreProperties {
self.core_properties
}
///|
pub fn Workbook::set_doc_props(self : Workbook, props : CoreProperties) -> Unit {
self.core_properties = props
}
///|
pub fn Workbook::app_properties(self : Workbook) -> AppProperties {
self.app_properties
}
///|
pub fn Workbook::set_app_properties(
self : Workbook,
props : AppProperties,
) -> Unit {
self.app_properties = props
}
///|
pub fn Workbook::app_props(self : Workbook) -> AppProperties {
self.app_properties
}
///|
pub fn Workbook::set_app_props(self : Workbook, props : AppProperties) -> Unit {
self.app_properties = props
}
///|
pub fn Workbook::get_app_props(self : Workbook) -> AppProperties {
self.app_properties
}
///|
pub fn Workbook::custom_properties(self : Workbook) -> Array[CustomProperty] {
let props : Array[CustomProperty] = []
for prop in self.custom_properties {
props.push(prop)
}
props
}
///|
pub fn Workbook::get_custom_props(self : Workbook) -> Array[CustomProperty] {
self.custom_properties()
}
///|
pub fn Workbook::set_custom_props(
self : Workbook,
prop : CustomProperty,
) -> Unit raise XlsxError {
if prop.name == "" {
raise InvalidWorkbookProperty(msg="custom property name missing")
}
let mut index : Int? = None
for i, existing in self.custom_properties {
if existing.name == prop.name {
index = Some(i)
break
}
}
match prop.value {
None =>
match index {
Some(i) => {
ignore(self.custom_properties.remove(i))
()
}
None => ()
}
Some(_) =>
match index {
Some(i) => self.custom_properties[i] = prop
None => self.custom_properties.push(prop)
}
}
}
///|
pub fn Workbook::add_vba_project(
self : Workbook,
data : Bytes,
) -> Unit raise XlsxError {
if data.find(ole_identifier_bytes) is None {
raise InvalidVBAProject(msg="unsupported vba project")
}
self.vba_project = Some(data)
}
///|
fn Workbook::workbook_props(self : Workbook) -> WorkbookPropsOptions {
self.workbook_props
}
///|
pub fn Workbook::get_workbook_props(self : Workbook) -> WorkbookPropsOptions {
self.workbook_props
}
///|
pub fn Workbook::set_workbook_props(
self : Workbook,
options : WorkbookPropsOptions?,
) -> Unit {
self.workbook_props = match options {
Some(value) => value
None => WorkbookPropsOptions::new()
}
}
///|
fn Workbook::calc_props(self : Workbook) -> CalcPropsOptions {
self.calc_props
}
///|
pub fn Workbook::get_calc_props(self : Workbook) -> CalcPropsOptions {
self.calc_props
}
///|
pub fn Workbook::set_calc_props(
self : Workbook,
options : CalcPropsOptions?,
) -> Unit raise XlsxError {
let value = match options {
Some(opts) => normalize_calc_props(opts)
None => CalcPropsOptions::new()
}
self.calc_props = value
}
///|
fn Workbook::default_font(self : Workbook) -> String {
self.default_font
}
///|
pub fn Workbook::get_default_font(self : Workbook) -> String {
self.default_font
}
///|
pub fn Workbook::set_default_font(self : Workbook, font_name : String) -> Unit {
self.default_font = font_name
}
///|
pub fn Workbook::set_zip_writer(
self : Workbook,
writer : (@zip.Archive) -> Bytes raise,
) -> Unit {
let io_context = workbook_io_context(self)
self.set_io_context({
file_path: io_context.file_path,
zip_writer: Some(writer),
charset_transcoder: io_context.charset_transcoder,
})
}
///|
pub fn Workbook::protect_workbook(
self : Workbook,
options? : WorkbookProtectionOptions = WorkbookProtectionOptions::new(),
) -> Unit raise XlsxError {
self.workbook_protection = Some(workbook_protection_from_options(options))
}
///|
pub fn Workbook::unprotect_workbook(
self : Workbook,
password? : String = "",
) -> Unit raise XlsxError {
let protection = match self.workbook_protection {
Some(value) => value
None => raise InvalidWorkbookProtection(msg="workbook not protected")
}
if password != "" {
verify_workbook_protection_password(protection, password)
}
self.workbook_protection = None
}
///|
pub fn Workbook::active_sheet_index(self : Workbook) -> Int {
self.active_sheet_index
}
///|
/// Marks the sheet at `index` (0-based, in tab order across worksheets and
/// chart sheets) as the active sheet — the one Excel selects and shows when the
/// workbook is opened. An out-of-range `index` falls back to the first sheet.
pub fn Workbook::set_active_sheet(self : Workbook, index : Int) -> Unit {
if self.sheet_order.length() == 0 {
self.active_sheet_index = 0
return
}
if index < 0 || index >= self.sheet_order.length() {
self.active_sheet_index = 0
} else {
self.active_sheet_index = index
}
}
///|
pub fn Workbook::set_sheet_name(
self : Workbook,
source : StringView,
target : String,
) -> Unit raise XlsxError {
check_sheet_name(source)
check_sheet_name(target)
if source.to_owned() == target {
return
}
let entry = match self.sheet_entry(source) {
Some(value) => value
None => raise SheetNotFound(name=source.to_owned())
}
let old_name = self.sheet_entry_name(entry)
if !sheet_name_equal(old_name, target) {
match self.sheet_entry(target) {
Some(_) => raise SheetAlreadyExists(name=target)
None => ()
}
}
match entry {
Worksheet(idx) => self.sheets[idx].name = target
ChartSheet(idx) => self.chart_sheets[idx].name = target
}
if self.defined_names.length() > 0 {
let updated : Array[DefinedName] = []
for dn in self.defined_names {
let scope = if sheet_name_equal(dn.scope, old_name) {
target
} else {
dn.scope
}
let refers_to = replace_sheet_name_in_refers_to(
dn.refers_to,
old_name,
target,
)
updated.push({ name: dn.name, refers_to, scope, comment: dn.comment })
}
self.defined_names.clear()
self.defined_names.append(updated)
}
}
///|
/// Removes the sheet named `name` — a worksheet or a chart sheet — from the
/// workbook, together with its cells and sheet-scoped settings. Does nothing if
/// no sheet has that name, or if it is the workbook's only remaining sheet (a
/// workbook must always keep at least one sheet).
///
/// Raises `XlsxError` if `name` is not a valid sheet name.
pub fn Workbook::delete_sheet(
self : Workbook,
name : StringView,
) -> Unit raise XlsxError {
check_sheet_name(name)
let order_index = match self.sheet_entry_index(name) {
Some(value) => value
None => return ()
}
let entry = self.sheet_order[order_index]
let deleted_name = self.sheet_entry_name(entry)
if self.sheet_order.length() <= 1 {
return ()
}
ignore(self.sheet_order.remove(order_index))
match entry {
Worksheet(idx) => {
self.sheets[idx].workbook_owner_token = None
ignore(self.sheets.remove(idx))
let updated : Array[SheetEntry] = []
for item in self.sheet_order {
match item {
Worksheet(value) =>
if value > idx {
updated.push(Worksheet(value - 1))
} else {
updated.push(item)
}
ChartSheet(_) => updated.push(item)
}
}
self.sheet_order.clear()
self.sheet_order.append(updated)
}
ChartSheet(idx) => {
ignore(self.chart_sheets.remove(idx))
let updated : Array[SheetEntry] = []
for item in self.sheet_order {
match item {
ChartSheet(value) =>
if value > idx {
updated.push(ChartSheet(value - 1))
} else {
updated.push(item)
}
Worksheet(_) => updated.push(item)
}
}
self.sheet_order.clear()
self.sheet_order.append(updated)
}
}
if self.defined_names.length() > 0 {
let deleted_key = defined_name_scope_key(deleted_name)
let updated : Array[DefinedName] = []
for dn in self.defined_names {
if defined_name_scope_key(dn.scope) == deleted_key {
continue
}
updated.push(dn)
}
self.defined_names.clear()
self.defined_names.append(updated)
}
if self.active_sheet_index == order_index {
let fallback = if order_index >= self.sheet_order.length() {
self.sheet_order.length() - 1
} else {
order_index
}
self.active_sheet_index = fallback
} else if self.active_sheet_index > order_index {
self.active_sheet_index = self.active_sheet_index - 1
}
}
///|
pub fn Workbook::move_sheet(
self : Workbook,
source : StringView,
target : StringView,
) -> Unit raise XlsxError {
check_sheet_name(source)
check_sheet_name(target)
if sheet_name_equal(source, target) {
return
}
let source_index = self.sheet_index(source)
let target_index = self.sheet_index(target)
let entry = self.sheet_order.remove(source_index)
let insert_index = if target_index > source_index {
target_index - 1
} else {
target_index
}
self.sheet_order.insert(insert_index, entry)
if self.active_sheet_index == source_index {
self.active_sheet_index = insert_index
} else if source_index < self.active_sheet_index &&
insert_index >= self.active_sheet_index {
self.active_sheet_index = self.active_sheet_index - 1
} else if source_index > self.active_sheet_index &&
insert_index <= self.active_sheet_index {
self.active_sheet_index = self.active_sheet_index + 1
}
}
///|
pub fn Workbook::copy_sheet(
self : Workbook,
from_index : Int,
to_index : Int,
) -> Unit raise XlsxError {
if from_index < 0 ||
to_index < 0 ||
from_index >= self.sheet_order.length() ||
to_index >= self.sheet_order.length() ||
from_index == to_index {
raise InvalidSheetIndex(index=to_index)
}
let from_entry = self.sheet_order[from_index]
let to_entry = self.sheet_order[to_index]
match (from_entry, to_entry) {
(Worksheet(from_sheet_idx), Worksheet(to_sheet_idx)) => {
let from_sheet = self.sheets[from_sheet_idx]
let to_sheet = self.sheets[to_sheet_idx]
let cloned = clone_worksheet(
from_sheet,
to_sheet.name(),
to_sheet.state(),
)
to_sheet.workbook_owner_token = None
cloned.workbook_owner_token = Some(self.worksheet_owner_token)
self.sheets[to_sheet_idx] = cloned
}
_ => raise InvalidSheetIndex(index=to_index)
}
}
///|
pub fn Workbook::set_sheet_visible(
self : Workbook,
name : StringView,
visible : Bool,
very_hidden? : Bool = false,
) -> Unit raise XlsxError {
check_sheet_name(name)
let index = self.sheet_index(name)
let entry = self.sheet_order[index]
let state = if visible {
Visible
} else if very_hidden {
VeryHidden
} else {
Hidden
}
if state != Visible {
let mut visible_count = 0
for i, order_entry in self.sheet_order {
let sheet_state = if i == index {
state
} else {
self.sheet_entry_state(order_entry)
}
if sheet_state == Visible {
visible_count = visible_count + 1
}
}
if visible_count == 0 {
raise InvalidSheetOperation(
msg="workbook must contain at least one visible sheet",
)
}
}
self.set_sheet_entry_state(entry, state)
}
///|
pub fn Workbook::sheet_visible(
self : Workbook,
name : StringView,
) -> Bool raise XlsxError {
check_sheet_name(name)
match self.sheet_entry(name) {
Some(entry) => self.sheet_entry_state(entry) == Visible
None => raise SheetNotFound(name=name.to_owned())
}
}
///|
pub fn Workbook::set_page_margins(
self : Workbook,
sheet_name : StringView,
options : PageLayoutMarginsOptions?,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.set_page_margins(options)
}
///|
pub fn Workbook::get_page_margins(
self : Workbook,
sheet_name : StringView,
) -> PageLayoutMarginsOptions raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.page_margins()
}
///|
pub fn Workbook::set_page_layout(
self : Workbook,
sheet_name : StringView,
options : PageLayoutOptions?,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.set_page_layout(options)
}
///|
pub fn Workbook::get_page_layout(
self : Workbook,
sheet_name : StringView,
) -> PageLayoutOptions raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.page_layout()
}
///|
pub fn Workbook::set_header_footer(
self : Workbook,
sheet_name : StringView,
options : HeaderFooterOptions?,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.set_header_footer(options)
}
///|
pub fn Workbook::set_sheet_props(
self : Workbook,
sheet_name : StringView,
options : SheetPropsOptions?,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.set_sheet_props(options)
}
///|
pub fn Workbook::get_header_footer(
self : Workbook,
sheet_name : StringView,
) -> HeaderFooterOptions? raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.header_footer()
}
///|
pub fn Workbook::get_header_footer_images(
self : Workbook,
sheet_name : StringView,
) -> Array[HeaderFooterImageOptions] raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.get_header_footer_images()
}
///|
pub fn Workbook::get_sheet_props(
self : Workbook,
sheet_name : StringView,
) -> SheetPropsOptions raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.sheet_props()
}
///|
pub fn Workbook::set_sheet_view(
self : Workbook,
sheet_name : StringView,
view_index : Int,
options : SheetViewOptions,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.set_sheet_view(view_index, options)
}
///|
pub fn Workbook::get_sheet_view(
self : Workbook,
sheet_name : StringView,
view_index : Int,
) -> SheetViewOptions raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.get_sheet_view(view_index)
}
///|
pub fn Workbook::set_panes(
self : Workbook,
sheet_name : StringView,
panes : Panes,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.set_panes(panes)
}
///|
pub fn Workbook::get_panes(
self : Workbook,
sheet_name : StringView,
) -> Panes raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.get_panes()
}
///|
pub fn Workbook::set_sheet_dimension(
self : Workbook,
sheet_name : StringView,
range_ref : StringView,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.set_sheet_dimension(range_ref)
}
///|
pub fn Workbook::get_sheet_dimension(
self : Workbook,
sheet_name : StringView,
) -> String raise XlsxError {
let sheet = self.require_sheet(sheet_name)
match sheet.sheet_dimension() {
Some(value) => value
None => ""
}
}
///|
pub fn Workbook::protect_sheet(
self : Workbook,
sheet_name : StringView,
options : SheetProtectionOptions,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.protect_sheet(options)
}
///|
pub fn Workbook::unprotect_sheet(
self : Workbook,
sheet_name : StringView,
password? : String = "",
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.unprotect_sheet(password~)
}
///|
pub fn Workbook::get_sheet_protection(
self : Workbook,
sheet_name : StringView,
) -> SheetProtectionOptions raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.get_sheet_protection()
}
///|
pub fn Workbook::set_sheet_background(
self : Workbook,
sheet_name : StringView,
data : Bytes,
extension : String,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.set_sheet_background(data, extension)
}
///|
pub fn Workbook::clear_sheet_background(
self : Workbook,
sheet_name : StringView,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.clear_sheet_background()
}
///|
pub fn Workbook::insert_page_break(
self : Workbook,
sheet_name : StringView,
cell : StringView,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.insert_page_break(cell)
}
///|
pub fn Workbook::remove_page_break(
self : Workbook,
sheet_name : StringView,
cell : StringView,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.remove_page_break(cell)
}
///|
pub fn Workbook::set_defined_name(
self : Workbook,
defined_name : DefinedName,
) -> Unit raise XlsxError {
if defined_name.name == "" || defined_name.refers_to == "" {
raise InvalidDefinedName(msg="defined name missing")
}
if !is_builtin_defined_name(defined_name.name) {
check_defined_name(defined_name.name)
}
let scope = normalize_defined_name_scope(defined_name.scope)
let normalized_scope = if scope != "Workbook" {
match self.sheet(scope) {
Some(_) => scope
None => "Workbook"
}
} else {
scope
}
let scope_key = defined_name_scope_key(normalized_scope)
for existing in self.defined_names {
if existing.name == defined_name.name &&
defined_name_scope_key(existing.scope) == scope_key {
raise DefinedNameDuplicate(name=defined_name.name)
}
}
self.defined_names.push({
name: defined_name.name,
refers_to: defined_name.refers_to,
scope: normalized_scope,
comment: defined_name.comment,
})
}
///|
pub fn Workbook::delete_defined_name(
self : Workbook,
defined_name : DefinedName,
) -> Unit raise XlsxError {
let scope_key = defined_name_scope_key(defined_name.scope)
let updated : Array[DefinedName] = []
let mut removed = false
for dn in self.defined_names {
if dn.name == defined_name.name &&
defined_name_scope_key(dn.scope) == scope_key {
removed = true
continue
}
updated.push(dn)
}
if !removed {
raise DefinedNameScope(name=defined_name.name)
}
self.defined_names.clear()
self.defined_names.append(updated)
}
///|
pub fn Workbook::get_defined_names(self : Workbook) -> Array[DefinedName] {
let names : Array[DefinedName] = []
for dn in self.defined_names {
names.push(dn)
}
names
}
///|
pub fn Workbook::group_sheets(
self : Workbook,
sheets : Array[String],
) -> Unit raise XlsxError {
let active_index = self.active_sheet_index
let active_name = if active_index >= 0 &&
active_index < self.sheet_order.length() {
self.sheet_entry_name(self.sheet_order[active_index])
} else {
""
}
let mut contains_active = false
for name in sheets {
check_sheet_name(name)
if sheet_name_equal(name, active_name) {
contains_active = true
}
}
if !contains_active {
raise InvalidSheetOperation(
msg="group worksheet must contain an active worksheet",
)
}
for name in sheets {
let sheet = match self.sheet(name) {
Some(value) => value
None => raise SheetNotFound(name~)
}
sheet.ensure_stream_idle()
if sheet.sheet_views.length() == 0 {
sheet.sheet_views.push(SheetView::new())
}
for view in sheet.sheet_views {
view.tab_selected = true
}
}
}
///|
pub fn Workbook::ungroup_sheets(self : Workbook) -> Unit raise XlsxError {
let active_index = self.active_sheet_index
for idx, entry in self.sheet_order {
if idx == active_index {
continue
}
match entry {
Worksheet(sheet_idx) => {
let sheet = self.sheets[sheet_idx]
if sheet.sheet_views.length() == 0 {
continue
}
sheet.ensure_stream_idle()
for view in sheet.sheet_views {
view.tab_selected = false
}
}
ChartSheet(_) => ()
}
}
}
///|
/// Computes the sheet's shifted defined names WITHOUT committing them, so a
/// caller can validate the shift (the adjuster raises if a ref leaves the grid)
/// before mutating anything else. `None` means there is nothing to change.
fn Workbook::stage_adjusted_defined_names_for_sheet(
self : Workbook,
sheet_name : String,
adjust_fn : (StringView) -> String? raise XlsxError,
cancelled? : () -> Bool = () => false,
) -> Array[DefinedName]? raise XlsxError {
check_read_cancelled(cancelled)
if self.defined_names.length() == 0 {
return None
}
let updated : Array[DefinedName] = []
for index, dn in self.defined_names {
if (index & 4095) == 0 {
check_read_cancelled(cancelled)
}
let refers_to = adjust_defined_name_refers_to_for_sheet(
dn.refers_to,
sheet_name,
adjust_fn,
cancelled~,
)
updated.push({
name: dn.name,
refers_to,
scope: dn.scope,
comment: dn.comment,
})
}
check_read_cancelled(cancelled)
Some(updated)
}
///|
/// Installs a staged defined-name array (from
/// `stage_adjusted_defined_names_for_sheet`). Infallible — call it only after
/// every other fallible step of the operation has succeeded.
fn Workbook::commit_adjusted_defined_names(
self : Workbook,
staged : Array[DefinedName]?,
) -> Unit {
match staged {
Some(updated) => {
self.defined_names.clear()
self.defined_names.append(updated)
}
None => ()
}
}
///|
fn clone_strings(values : ArrayView[String]) -> Array[String] {
let out : Array[String] = []
for value in values {
out.push(value)
}
out
}
///|
fn clone_row_dimensions(
values : Map[Int, RowDimension],
) -> Map[Int, RowDimension] {
let out : Map[Int, RowDimension] = Map([])
for row, dim in values {
out[row] = dim
}
out
}
///|
fn clone_col_dimensions(
values : Map[Int, ColDimension],
) -> Map[Int, ColDimension] {
let out : Map[Int, ColDimension] = Map([])
for col, dim in values {
out[col] = dim
}
out
}
///|
fn clone_cells(values : ArrayView[Cell]) -> Array[Cell] {
let out : Array[Cell] = []
for value in values {
out.push(value)
}
out
}
///|
fn clone_hyperlinks(values : ArrayView[Hyperlink]) -> Array[Hyperlink] {
let out : Array[Hyperlink] = []
for value in values {
out.push(value)
}
out
}
///|
fn clone_comments(values : ArrayView[Comment]) -> Array[Comment] {
let out : Array[Comment] = []
for value in values {
out.push(clone_comment(value))
}
out
}
///|
fn clone_tables(values : ArrayView[Table]) -> Array[Table] {
let out : Array[Table] = []
for value in values {
let columns : Array[String] = []
for column in value.columns {
columns.push(column)
}
out.push({
id: 0,
name: value.name,
display_name: value.display_name,
range: value.range_ref,
range_ref: value.range_ref,
columns,
style_name: value.style_name,
show_first_column: value.show_first_column,
show_last_column: value.show_last_column,
show_row_stripes: value.show_row_stripes,
show_column_stripes: value.show_column_stripes,
show_header_row: value.show_header_row,
carries_source_formulas: value.carries_source_formulas,
})
}
out
}
///|
fn clone_sparkline_groups(
values : ArrayView[SparklineGroup],
) -> Array[SparklineGroup] {
let out : Array[SparklineGroup] = []
for value in values {
let sparklines : Array[Sparkline] = []
for sparkline in value.sparklines {
sparklines.push(sparkline)
}
out.push({
sparkline_type: value.sparkline_type,
sparklines,
options: value.options,
})
}
out
}
///|
fn clone_pivot_tables(values : ArrayView[PivotTable]) -> Array[PivotTable] {
let out : Array[PivotTable] = []
for value in values {
out.push({
name: value.name,
table_id: 0,
cache_id: 0,
table_xml: value.table_xml,
cache_definition_xml: value.cache_definition_xml,
cache_records_xml: value.cache_records_xml,
})
}
out
}
///|
fn clone_images(values : ArrayView[Image]) -> Array[Image] {
let out : Array[Image] = []
for value in values {
out.push(value)
}
out
}
///|
fn clone_header_footer_images(
values : ArrayView[HeaderFooterImage],
) -> Array[HeaderFooterImage] {
let out : Array[HeaderFooterImage] = []
for value in values {
out.push(value)
}
out
}
///|
fn clone_charts(values : ArrayView[Chart]) -> Array[Chart] {
let out : Array[Chart] = []
for value in values {
out.push(value)
}
out
}
///|
fn clone_shapes(values : ArrayView[Shape]) -> Array[Shape] {
let out : Array[Shape] = []
for value in values {
out.push(value)
}
out
}
///|
fn clone_form_controls(values : ArrayView[FormControl]) -> Array[FormControl] {
let out : Array[FormControl] = []
for value in values {
out.push(value)
}
out
}
///|
fn clone_slicers(values : ArrayView[Slicer]) -> Array[Slicer] {
let out : Array[Slicer] = []
for value in values {
out.push(value)
}
out
}
///|
fn clone_preserved_drawing_relationships(
values : ArrayView[PreservedDrawingRelationship],
) -> Array[PreservedDrawingRelationship] {
let out : Array[PreservedDrawingRelationship] = []
for value in values {
out.push(value)
}
out
}
///|
fn clone_preserved_drawing_parts(
values : ArrayView[PreservedDrawingPart],
) -> Array[PreservedDrawingPart] {
let out : Array[PreservedDrawingPart] = []
for value in values {
out.push({
..value,
relationships: clone_preserved_drawing_relationships(value.relationships),
})
}
out
}
///|
fn clone_ignored_errors(
values : ArrayView[IgnoredError],
) -> Array[IgnoredError] {
let out : Array[IgnoredError] = []
for value in values {
out.push(value)
}
out
}
///|
fn clone_x14_data_bars(
values : Map[String, X14DataBarProps],
) -> Map[String, X14DataBarProps] {
let out : Map[String, X14DataBarProps] = Map([])
for key, value in values {
out[key] = value
}
out
}
///|
fn clone_sheet_views(values : ArrayView[SheetView]) -> Array[SheetView] {
let out : Array[SheetView] = []
for view in values {
let selection : Array[Selection] = []
for item in view.selection {
selection.push(item)
}
let pane = match view.pane {
Some(value) =>
Some({
active_pane: value.active_pane,
state: value.state,
top_left_cell: value.top_left_cell,
x_split: value.x_split,
y_split: value.y_split,
})
None => None
}
out.push({
default_grid_color: view.default_grid_color,
right_to_left: view.right_to_left,
show_formulas: view.show_formulas,
show_grid_lines: view.show_grid_lines,
show_row_col_headers: view.show_row_col_headers,
show_ruler: view.show_ruler,
show_zeros: view.show_zeros,
top_left_cell: view.top_left_cell,
view: view.view,
zoom_scale: view.zoom_scale,
tab_selected: view.tab_selected,
workbook_view_id: view.workbook_view_id,
pane,
selection,
})
}
out
}
///|
fn clone_worksheet(
sheet : Worksheet,
name : String,
state : SheetState,
) -> Worksheet {
let cells = clone_cells(sheet.cells)
{
workbook_owner_token: None,
name,
sheet_views: clone_sheet_views(sheet.sheet_views),
dimension_ref: sheet.dimension_ref,
cells,
cell_index: build_worksheet_cell_index(cells),
cell_index_valid: true,
shared_formula_masters_index: clone_shared_formula_masters_index(
sheet.shared_formula_masters_index,
),
shared_formula_masters_index_valid: sheet.shared_formula_masters_index_valid,
merged_cells: clone_strings(sheet.merged_cells),
hyperlinks: clone_hyperlinks(sheet.hyperlinks),
tables: clone_tables(sheet.tables),
sparkline_groups: clone_sparkline_groups(sheet.sparkline_groups),
pivot_tables: clone_pivot_tables(sheet.pivot_tables),
images: clone_images(sheet.images),
header_footer_images: clone_header_footer_images(sheet.header_footer_images),
charts: clone_charts(sheet.charts),
shapes: clone_shapes(sheet.shapes),
form_controls: clone_form_controls(sheet.form_controls),
slicers: clone_slicers(sheet.slicers),
data_validations: clone_strings(sheet.data_validations),
conditional_formats: clone_strings(sheet.conditional_formats),
x14_data_bars: clone_x14_data_bars(sheet.x14_data_bars),
unknown_ext_blocks: clone_strings(sheet.unknown_ext_blocks),
x14_cf_rule_id_counter: sheet.x14_cf_rule_id_counter,
ignored_errors: clone_ignored_errors(sheet.ignored_errors),
comments: clone_comments(sheet.comments),
auto_filter: match sheet.auto_filter {
Some(filter) => Some(clone_auto_filter(filter))
None => None
},
page_margins: sheet.page_margins,
page_layout: sheet.page_layout,
header_footer: sheet.header_footer,
sheet_protection: sheet.sheet_protection,
sheet_props: sheet.sheet_props,
sheet_background: sheet.sheet_background,
row_breaks: {
let breaks : Array[PageBreak] = []
for brk in sheet.row_breaks {
breaks.push(brk)
}
breaks
},
col_breaks: {
let breaks : Array[PageBreak] = []
for brk in sheet.col_breaks {
breaks.push(brk)
}
breaks
},
row_dimensions: clone_row_dimensions(sheet.row_dimensions),
col_dimensions: clone_col_dimensions(sheet.col_dimensions),
state,
stream_state: Idle,
vml_drawing_xml: sheet.vml_drawing_xml,
vml_drawing_hf_xml: sheet.vml_drawing_hf_xml,
next_drawing_order: sheet.next_drawing_order,
preserved_drawing_anchors: sheet.preserved_drawing_anchors.copy(),
preserved_drawing_relationships: clone_preserved_drawing_relationships(
sheet.preserved_drawing_relationships,
),
preserved_drawing_parts: clone_preserved_drawing_parts(
sheet.preserved_drawing_parts,
),
cell_vm: {
let copy : Map[String, Int] = Map([])
for reference, vm in sheet.cell_vm {
copy[reference] = vm
}
copy
},
}
}
///|
pub fn Workbook::row_stream(
self : Workbook,
sheet_name : StringView,
) -> RowStream raise XlsxError {
let sheet = self.require_sheet(sheet_name)
RowStream::new(sheet)
}
///|
pub fn Workbook::add_style(self : Workbook, style : Style) -> Int {
self.styles.push(style)
self.styles.length() - 1
}
///|
/// Registers `style` with the workbook and returns its integer style id. Pass
/// that id to the styling APIs — e.g. `Worksheet::set_cell_style`,
/// `Workbook::set_col_style`, or the `style_id` field of `RowOpts` — to apply
/// the formatting to cells, columns, or rows.
pub fn Workbook::new_style(self : Workbook, style : Style) -> Int {
self.add_style(style)
}
///|
fn Workbook::check_style_id(
self : Workbook,
style_id : Int,
) -> Unit raise XlsxError {
if style_id < 0 || style_id >= self.styles.length() {
raise InvalidStyleId(index=style_id)
}
}
///|
fn Workbook::check_conditional_style_id(
self : Workbook,
style_id : Int,
) -> Unit raise XlsxError {
if style_id < 0 || style_id >= self.conditional_styles.length() {
raise InvalidStyleId(index=style_id)
}
}
///|
pub fn Workbook::get_style(
self : Workbook,
style_id : Int,
) -> Style raise XlsxError {
self.check_style_id(style_id)
self.styles[style_id]
}
///|
pub fn Workbook::new_conditional_style(self : Workbook, style : Style) -> Int {
self.conditional_styles.push(style)
self.conditional_styles.length() - 1
}
///|
pub fn Workbook::get_conditional_style(
self : Workbook,
style_id : Int,
) -> Style raise XlsxError {
self.check_conditional_style_id(style_id)
self.conditional_styles[style_id]
}
///|
/// Formats a stored cell's value using an ARBITRARY style id instead of
/// the cell's own — the display semantic for row/column-inherited styles
/// (the cell attribute wins when set; `GetCellValue` parity keeps using
/// the cell's own style). Returns `None` for an absent cell.
pub fn Workbook::get_cell_value_styled(
self : Workbook,
sheet_name : StringView,
reference : StringView,
style_id : Int,
options? : Options,
) -> String? raise XlsxError {
check_sheet_name(sheet_name)
let resolved_options = match options {
Some(value) => value
None => self.options
}
let sheet = match self.sheet(sheet_name) {
Some(value) => value
None => raise SheetNotFound(name=sheet_name.to_owned())
}
if style_id < 0 || style_id >= self.styles.length() {
raise InvalidStyleId(index=style_id)
}
let (row, col) = cell_ref_to_rc(reference)
match sheet.cell_index_of(row, col) {
Some(index) => {
let cell = sheet.cells[index]
// Honor raw like get_cell_value: raw mode returns the stored value
// unformatted regardless of the style.
if resolved_options.raw_cell_value {
return Some(cell.value)
}
Some(
format_cell_value(
cell.value_type,
cell.value,
Some(self.styles[style_id]),
resolved_options,
use_1904_format=self.uses_1904_date_system(),
),
)
}
None => None
}
}
///|
fn format_cell_value_for_workbook(
styles : ArrayView[Style],
cell : Cell,
raw : Bool,
options : Options,
use_1904_format? : Bool = false,
) -> String raise XlsxError {
if raw || options.raw_cell_value {
return cell.value
}
if cell.style_id < 0 || cell.style_id >= styles.length() {
raise InvalidStyleId(index=cell.style_id)
}
format_cell_value(
cell.value_type,
cell.value,
Some(styles[cell.style_id]),
options,
use_1904_format~,
)
}
///|
fn Workbook::uses_1904_date_system(self : Workbook) -> Bool {
self.workbook_props.date_1904.unwrap_or(false)
}
///|
fn normalize_cell_float_value(
value : Double,
precision : Int,
bit_size : Int,
) -> Double {
let normalized = if bit_size == 32 {
Float::from_double(value).to_double()
} else {
value
}
if precision < 0 {
normalized
} else {
let factor = @math.pow(10.0, Double::from_int(precision))
(normalized * factor).round() / factor
}
}
///|
pub fn Workbook::set_cell(
self : Workbook,
sheet_name : StringView,
reference : String,
value : String,
) -> Unit raise XlsxError {
check_sheet_name(sheet_name)
let sheet = match self.sheet(sheet_name) {
Some(value) => value
None =>
match self.sheet_entry(sheet_name) {
Some(ChartSheet(_)) =>
raise InvalidSheetOperation(
msg="sheet \{sheet_name.to_owned()} is not a worksheet",
)
_ => raise SheetNotFound(name=sheet_name.to_owned())
}
}
sheet.set_cell(reference, value)
}
///|
pub fn Workbook::set_cell_value(
self : Workbook,
sheet_name : StringView,
reference : String,
value : CellValue,
) -> Unit raise XlsxError {
check_sheet_name(sheet_name)
let sheet = match self.sheet(sheet_name) {
Some(value) => value
None =>
match self.sheet_entry(sheet_name) {
Some(ChartSheet(_)) =>
raise InvalidSheetOperation(
msg="sheet \{sheet_name.to_owned()} is not a worksheet",
)
_ => raise SheetNotFound(name=sheet_name.to_owned())
}
}
sheet.set_cell_value(reference, value)
}
///|
pub fn Workbook::set_cell_bool(
self : Workbook,
sheet_name : StringView,
reference : String,
value : Bool,
) -> Unit raise XlsxError {
self.set_cell_value(sheet_name, reference, Bool(value))
}
///|
pub fn Workbook::set_cell_int(
self : Workbook,
sheet_name : StringView,
reference : String,
value : Int,
) -> Unit raise XlsxError {
self.set_cell_value(sheet_name, reference, Numeric(Double::from_int(value)))
}
///|
pub fn Workbook::set_cell_uint(
self : Workbook,
sheet_name : StringView,
reference : String,
value : UInt,
) -> Unit raise XlsxError {
self.set_cell_value(
sheet_name,
reference,
Numeric(Double::convert_uint(value)),
)
}
///|
pub fn Workbook::set_cell_float(
self : Workbook,
sheet_name : StringView,
reference : String,
value : Double,
precision? : Int = -1,
bit_size? : Int = 64,
) -> Unit raise XlsxError {
let normalized = normalize_cell_float_value(value, precision, bit_size)
self.set_cell_value(sheet_name, reference, Numeric(normalized))
}
///|
pub fn Workbook::set_cell_str(
self : Workbook,
sheet_name : StringView,
reference : String,
value : String,
) -> Unit raise XlsxError {
self.set_cell(sheet_name, reference, value)
}
///|
pub fn Workbook::set_cell_default(
self : Workbook,
sheet_name : StringView,
reference : String,
value : String,
) -> Unit raise XlsxError {
self.set_cell(sheet_name, reference, value)
}
///|
pub fn Workbook::set_cell_rich_text(
self : Workbook,
sheet_name : StringView,
reference : String,
runs : ArrayView[RichTextRun],
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.set_cell_rich_text(reference, runs)
}
///|
pub fn Workbook::set_cell_hyperlink(
self : Workbook,
sheet_name : StringView,
reference : String,
target : String,
link_type : HyperlinkType,
display? : String = "",
tooltip? : String = "",
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.set_cell_hyperlink(reference, target, link_type, display~, tooltip~)
}
///|
pub fn Workbook::set_cell_hyperlink_opts(
self : Workbook,
sheet_name : StringView,
reference : String,
target : String,
link_type : HyperlinkType,
opts? : HyperlinkOpts = HyperlinkOpts::new(),
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.set_cell_hyperlink_opts(reference, target, link_type, opts~)
}
///|
pub fn Workbook::set_cell_hyper_link(
self : Workbook,
sheet_name : StringView,
reference : String,
target : String,
link_type : HyperlinkType,
display? : String = "",
tooltip? : String = "",
) -> Unit raise XlsxError {
self.set_cell_hyperlink(
sheet_name,
reference,
target,
link_type,
display~,
tooltip~,
)
}
///|
pub fn Workbook::remove_cell_hyperlink(
self : Workbook,
sheet_name : StringView,
reference : StringView,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.remove_cell_hyperlink(reference)
}
///|
pub fn Workbook::get_cell(
self : Workbook,
sheet_name : StringView,
reference : StringView,
) -> String? raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.get_cell(reference)
}
///|
pub fn Workbook::get_cell_rich_text(
self : Workbook,
sheet_name : StringView,
reference : StringView,
) -> Array[RichTextRun]? raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.get_cell_rich_text(reference)
}
///|
pub fn Workbook::get_cell_hyperlink(
self : Workbook,
sheet_name : StringView,
reference : StringView,
) -> Hyperlink? raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.get_cell_hyperlink(reference)
}
///|
pub fn Workbook::get_cell_hyper_link(
self : Workbook,
sheet_name : StringView,
reference : StringView,
) -> Hyperlink? raise XlsxError {
self.get_cell_hyperlink(sheet_name, reference)
}
///|
/// Returns full hyperlink records for `sheet_name` in worksheet XML/insertion
/// order. See `Worksheet::get_hyperlinks` for filter semantics.
pub fn Workbook::get_hyperlinks(
self : Workbook,
sheet_name : StringView,
link_type? : HyperlinkType,
) -> Array[Hyperlink] raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.get_hyperlinks(link_type?)
}
///|
pub fn Workbook::get_hyperlink_cells(
self : Workbook,
sheet_name : StringView,
link_type? : HyperlinkType,
) -> Array[String] raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.get_hyperlink_cells(link_type?)
}
///|
pub fn Workbook::get_hyper_link_cells(
self : Workbook,
sheet_name : StringView,
link_type? : HyperlinkType,
) -> Array[String] raise XlsxError {
self.get_hyperlink_cells(sheet_name, link_type?)
}
///|
pub fn Workbook::get_cell_value_raw(
self : Workbook,
sheet_name : StringView,
reference : StringView,
) -> CellValue? raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.get_cell_value_raw(reference)
}
///|
pub fn Workbook::get_cell_type(
self : Workbook,
sheet_name : StringView,
reference : StringView,
) -> CellValueType? raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.get_cell_type(reference)
}
///|
pub fn Workbook::get_cell_value(
self : Workbook,
sheet_name : StringView,
reference : StringView,
raw? : Bool = false,
options? : Options,
) -> String? raise XlsxError {
check_sheet_name(sheet_name)
let resolved_options = match options {
Some(value) => value
None => self.options
}
let sheet = match self.sheet(sheet_name) {
Some(value) => value
None => raise SheetNotFound(name=sheet_name.to_owned())
}
let (row, col) = cell_ref_to_rc(reference)
// O(1) via the worksheet's lazily-built cell index — a linear scan here
// makes bulk per-cell reads (e.g. a 100k-cell `get --json`) quadratic.
match sheet.cell_index_of(row, col) {
Some(index) =>
Some(
format_cell_value_for_workbook(
self.styles,
sheet.cells[index],
raw,
resolved_options,
use_1904_format=self.uses_1904_date_system(),
),
)
None => None
}
}
///|
/// Formats one cell from a worksheet that has already been resolved from this
/// workbook. This is the bulk-read counterpart to `get_cell_value`: callers
/// can resolve a worksheet once, then read many coordinates without repeating
/// the workbook's name lookup for every cell. The cell's own style and the
/// workbook's formatting options have the same semantics as
/// `get_cell_value`.
///
/// `worksheet` must be a live entry from this workbook's `sheets()` view;
/// detached, deleted, replaced, or foreign handles raise
/// `InvalidSheetOperation`. The row and column are 1-based and must fit the
/// XLSX grid.
pub fn Workbook::get_cell_value_from_worksheet_rc(
self : Workbook,
worksheet : Worksheet,
row : Int,
col : Int,
raw? : Bool = false,
options? : Options,
) -> String? raise XlsxError {
self.require_owned_worksheet(worksheet)
ignore(cell_ref_from(row, col))
let resolved_options = match options {
Some(value) => value
None => self.options
}
match worksheet.cell_index_of(row, col) {
Some(index) =>
Some(
format_cell_value_for_workbook(
self.styles,
worksheet.cells[index],
raw,
resolved_options,
use_1904_format=self.uses_1904_date_system(),
),
)
None => None
}
}
///|
/// Formats one cell from an already-resolved worksheet using an explicit
/// style id. This is the bulk-read counterpart to `get_cell_value_styled` and
/// lets callers apply row/column-inherited styles without repeating a sheet
/// name lookup for every coordinate. Returns `None` for an absent cell.
///
/// `worksheet` must be a live entry from this workbook's `sheets()` view;
/// detached, deleted, replaced, or foreign handles raise
/// `InvalidSheetOperation`. The row and column are 1-based and `style_id` must
/// belong to this workbook.
pub fn Workbook::get_cell_value_styled_from_worksheet_rc(
self : Workbook,
worksheet : Worksheet,
row : Int,
col : Int,
style_id : Int,
options? : Options,
max_output_chars? : Int,
) -> String? raise XlsxError {
self.require_owned_worksheet(worksheet)
ignore(cell_ref_from(row, col))
self.check_style_id(style_id)
let resolved_options = match options {
Some(value) => value
None => self.options
}
match worksheet.cell_index_of(row, col) {
Some(index) => {
let cell = worksheet.cells[index]
if resolved_options.raw_cell_value {
return Some(cell.value)
}
Some(
format_cell_value_limited(
cell.value_type,
cell.value,
Some(self.styles[style_id]),
resolved_options,
use_1904_format=self.uses_1904_date_system(),
max_output_chars?,
),
)
}
None => None
}
}
///|
/// Calculates and formats one cell. Shared-formula followers resolved during
/// the calculation share `formula_limits`; `cancelled` is polled while their
/// masters are validated and translated.
pub fn Workbook::calc_cell_value(
self : Workbook,
sheet_name : StringView,
reference : StringView,
raw? : Bool = false,
options? : Options,
formula_limits? : SharedFormulaLimits = SharedFormulaLimits::new(),
cancelled? : () -> Bool = () => false,
) -> String raise XlsxError {
check_sheet_name(sheet_name)
let resolved_options = match options {
Some(value) => value
None => self.options
}
let sheet = match self.sheet(sheet_name) {
Some(value) => value
None => raise SheetNotFound(name=sheet_name.to_owned())
}
let (row, col) = cell_ref_to_rc(reference)
let canonical = cell_ref_from(row, col)
let ctx = CalcContext::new(
use_1904_dates=self.uses_1904_date_system(),
formula_limits~,
cancelled~,
)
let value = resolve_cell_value(self, sheet.name(), canonical, ctx)
let (value_type, raw_value) = formula_value_to_cell(value)
if raw || resolved_options.raw_cell_value {
return raw_value
}
let mut style_id = 0
for cell in sheet.cells() {
if cell.row == row && cell.col == col {
style_id = cell.style_id
break
}
}
if style_id < 0 || style_id >= self.styles.length() {
raise InvalidStyleId(index=style_id)
}
format_cell_value(
value_type,
raw_value,
Some(self.styles[style_id]),
resolved_options,
use_1904_format=self.uses_1904_date_system(),
)
}
///|
pub fn Workbook::calc_cell_value_rc(
self : Workbook,
sheet_name : StringView,
row : Int,
col : Int,
raw? : Bool = false,
options? : Options,
formula_limits? : SharedFormulaLimits = SharedFormulaLimits::new(),
cancelled? : () -> Bool = () => false,
) -> String raise XlsxError {
let reference = cell_ref_from(row, col)
match options {
Some(value) =>
self.calc_cell_value(
sheet_name,
reference,
raw~,
options=value,
formula_limits~,
cancelled~,
)
None =>
self.calc_cell_value(
sheet_name,
reference,
raw~,
formula_limits~,
cancelled~,
)
}
}
///|
/// The typed computed value of a cell — the same evaluation as
/// `calc_cell_value`, but the result is returned as a `CellValue` instead of
/// being stringified and number-formatted (mirroring `get_cell_value_raw`,
/// which returns the *stored* value the same way). `None` means an empty
/// result; `Some(Numeric/String/Bool/Error)` is the typed value. A genuine
/// formula error surfaces as `Some(Error("#…"))` (Excel stores those); only
/// structural problems (missing sheet, invalid reference) raise `XlsxError`.
/// Like `calc_cell_value`, this recomputes on every call with no memoization.
/// Shared-formula resolution uses the cumulative `formula_limits` policy and
/// polls `cancelled` during validation and translation.
pub fn Workbook::calc_cell_typed(
self : Workbook,
sheet_name : StringView,
reference : StringView,
formula_limits? : SharedFormulaLimits = SharedFormulaLimits::new(),
cancelled? : () -> Bool = () => false,
) -> CellValue? raise XlsxError {
check_sheet_name(sheet_name)
let sheet = match self.sheet(sheet_name) {
Some(value) => value
None => raise SheetNotFound(name=sheet_name.to_owned())
}
let (row, col) = cell_ref_to_rc(reference)
let canonical = cell_ref_from(row, col)
let ctx = CalcContext::new(
use_1904_dates=self.uses_1904_date_system(),
formula_limits~,
cancelled~,
)
formula_value_to_cell_value(
resolve_cell_value(self, sheet.name(), canonical, ctx),
)
}
///|
pub fn Workbook::calc_cell_typed_rc(
self : Workbook,
sheet_name : StringView,
row : Int,
col : Int,
formula_limits? : SharedFormulaLimits = SharedFormulaLimits::new(),
cancelled? : () -> Bool = () => false,
) -> CellValue? raise XlsxError {
self.calc_cell_typed(
sheet_name,
cell_ref_from(row, col),
formula_limits~,
cancelled~,
)
}
///|
pub fn Workbook::set_cell_formula(
self : Workbook,
sheet_name : StringView,
reference : String,
formula : String,
value? : String = "",
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.set_cell_formula(reference, formula, value~)
}
///|
pub fn Workbook::set_cell_formula_opts(
self : Workbook,
sheet_name : StringView,
reference : String,
formula : String,
opts? : FormulaOpts = FormulaOpts::new(),
value? : String = "",
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.set_cell_formula_opts(reference, formula, opts~, value~)
}
///|
pub fn Workbook::get_cell_formula(
self : Workbook,
sheet_name : StringView,
reference : StringView,
) -> String? raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.get_cell_formula(reference)
}
///|
pub fn Workbook::set_cell_style(
self : Workbook,
sheet_name : StringView,
reference : String,
style_id : Int,
) -> Unit raise XlsxError {
check_sheet_name(sheet_name)
self.check_style_id(style_id)
let sheet = self.require_sheet(sheet_name)
sheet.set_cell_style(reference, style_id)
}
///|
pub fn Workbook::get_cell_style(
self : Workbook,
sheet_name : StringView,
reference : StringView,
) -> Int? raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.get_cell_style(reference)
}
///|
pub fn Workbook::add_image(
self : Workbook,
sheet_name : StringView,
reference : String,
data : Bytes,
extension : String,
content_type : String,
offset_x? : Int = 0,
offset_y? : Int = 0,
scale_x? : Double = 1.0,
scale_y? : Double = 1.0,
hyperlink? : String = "",
hyperlink_type? : HyperlinkType = Unset,
name? : String = "",
alt_text? : String = "",
lock_aspect_ratio? : Bool = false,
auto_fit? : Bool = false,
auto_fit_ignore_aspect? : Bool = false,
print_object? : Bool = true,
locked? : Bool = true,
positioning? : PicturePositioning = OneCell,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.add_image(
reference,
data,
extension,
content_type,
offset_x~,
offset_y~,
scale_x~,
scale_y~,
hyperlink~,
hyperlink_type~,
name~,
alt_text~,
lock_aspect_ratio~,
auto_fit~,
auto_fit_ignore_aspect~,
print_object~,
locked~,
positioning~,
)
}
///|
pub fn Workbook::add_image_with_options(
self : Workbook,
sheet_name : StringView,
reference : String,
data : Bytes,
extension : String,
content_type : String,
options : GraphicOptions,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
let offset_x = options.offset_x.unwrap_or(0)
let offset_y = options.offset_y.unwrap_or(0)
let scale_x = options.scale_x.unwrap_or(1.0)
let scale_y = options.scale_y.unwrap_or(1.0)
let hyperlink = options.hyperlink.unwrap_or("")
let hyperlink_type = options.hyperlink_type.unwrap_or(Unset)
let name = options.name.unwrap_or("")
let alt_text = options.alt_text.unwrap_or("")
let lock_aspect_ratio = options.lock_aspect_ratio.unwrap_or(false)
let auto_fit = options.auto_fit.unwrap_or(false)
let auto_fit_ignore_aspect = options.auto_fit_ignore_aspect.unwrap_or(false)
let print_object = options.print_object.unwrap_or(true)
let locked = options.locked.unwrap_or(true)
let positioning = options.positioning.unwrap_or(OneCell)
sheet.add_image(
reference,
data,
extension,
content_type,
offset_x~,
offset_y~,
scale_x~,
scale_y~,
hyperlink~,
hyperlink_type~,
name~,
alt_text~,
lock_aspect_ratio~,
auto_fit~,
auto_fit_ignore_aspect~,
print_object~,
locked~,
positioning~,
)
}
///|
#cfg(target="native")
pub async fn Workbook::add_picture(
self : Workbook,
sheet_name : StringView,
reference : String,
path : String,
offset_x? : Int = 0,
offset_y? : Int = 0,
scale_x? : Double = 1.0,
scale_y? : Double = 1.0,
hyperlink? : String = "",
hyperlink_type? : HyperlinkType = Unset,
name? : String = "",
alt_text? : String = "",
lock_aspect_ratio? : Bool = false,
auto_fit? : Bool = false,
auto_fit_ignore_aspect? : Bool = false,
print_object? : Bool = true,
locked? : Bool = true,
positioning? : PicturePositioning = OneCell,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.add_picture(
reference,
path,
offset_x~,
offset_y~,
scale_x~,
scale_y~,
hyperlink~,
hyperlink_type~,
name~,
alt_text~,
lock_aspect_ratio~,
auto_fit~,
auto_fit_ignore_aspect~,
print_object~,
locked~,
positioning~,
)
}
///|
#cfg(target="native")
pub async fn Workbook::add_picture_with_options(
self : Workbook,
sheet_name : StringView,
reference : String,
path : String,
options : GraphicOptions,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.add_picture_with_options(reference, path, options)
}
///|
pub fn Workbook::add_picture_from_bytes(
self : Workbook,
sheet_name : StringView,
reference : String,
data : Bytes,
extension : String,
offset_x? : Int = 0,
offset_y? : Int = 0,
scale_x? : Double = 1.0,
scale_y? : Double = 1.0,
hyperlink? : String = "",
hyperlink_type? : HyperlinkType = Unset,
name? : String = "",
alt_text? : String = "",
lock_aspect_ratio? : Bool = false,
auto_fit? : Bool = false,
auto_fit_ignore_aspect? : Bool = false,
print_object? : Bool = true,
locked? : Bool = true,
positioning? : PicturePositioning = OneCell,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.add_picture_from_bytes(
reference,
data,
extension,
offset_x~,
offset_y~,
scale_x~,
scale_y~,
hyperlink~,
hyperlink_type~,
name~,
alt_text~,
lock_aspect_ratio~,
auto_fit~,
auto_fit_ignore_aspect~,
print_object~,
locked~,
positioning~,
)
}
///|
pub fn Workbook::add_picture_from_bytes_with_options(
self : Workbook,
sheet_name : StringView,
reference : String,
data : Bytes,
extension : String,
options : GraphicOptions,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.add_picture_from_bytes_with_options(reference, data, extension, options)
}
///|
pub fn Workbook::add_header_footer_image(
self : Workbook,
sheet_name : StringView,
options : HeaderFooterImageOptions,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.add_header_footer_image(options)
}
///|
#cfg(target="native")
pub async fn Workbook::add_header_footer_image_from_file(
self : Workbook,
sheet_name : StringView,
options : HeaderFooterImageOptions,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.add_header_footer_image_from_file(options)
}
///|
pub fn Workbook::get_pictures(
self : Workbook,
sheet_name : StringView,
cell : StringView,
) -> Array[Image] raise XlsxError {
let sheet = self.require_sheet(sheet_name)
// Excelize's GetPictures returns embedded cell images (getCellImages)
// before the drawing-object pictures. getCellImages resolves the WPS
// DISPIMG images first, then the modern rich-value "Place in cell"
// image, then the drawing objects are appended.
let images = self.disp_images_for_cell(sheet, cell)
match self.rich_value_image_for_cell(sheet, cell) {
Some(image) => images.push(image)
None => ()
}
for image in sheet.get_pictures(cell) {
images.push(image)
}
images
}
///|
pub fn Workbook::get_picture_cells(
self : Workbook,
sheet_name : StringView,
) -> Array[String] raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.get_picture_cells()
}
///|
pub fn Workbook::delete_picture(
self : Workbook,
sheet_name : StringView,
cell : StringView,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.delete_picture(cell)
}
///|
pub fn Workbook::add_chart(
self : Workbook,
sheet_name : StringView,
reference : String,
xml : String,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.add_chart(reference, xml)
}
///|
pub fn Workbook::add_chart_with_options(
self : Workbook,
sheet_name : StringView,
reference : String,
opts : ChartOptions,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.add_chart_with_options(reference, opts)
}
///|
pub fn Workbook::delete_chart(
self : Workbook,
sheet_name : StringView,
cell : StringView,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.delete_chart(cell)
}
///|
pub fn Workbook::add_shape(
self : Workbook,
sheet_name : StringView,
shape : Shape,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.add_shape(shape)
}
///|
pub fn Workbook::add_vml_drawing_xml(
self : Workbook,
sheet_name : StringView,
xml : String,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.add_vml_drawing_xml(xml)
}
///|
pub fn Workbook::add_vml_drawing_hf_xml(
self : Workbook,
sheet_name : StringView,
xml : String,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.add_vml_drawing_hf_xml(xml)
}
///|
pub fn Workbook::add_form_control(
self : Workbook,
sheet_name : StringView,
control : FormControl,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.add_form_control(control)
}
///|
pub fn Workbook::get_form_controls(
self : Workbook,
sheet_name : StringView,
) -> Array[FormControl] raise XlsxError {
let sheet = self.require_sheet(sheet_name)
let out : Array[FormControl] = []
for control in sheet.get_form_controls() {
out.push(control)
}
out
}
///|
pub fn Workbook::delete_form_control(
self : Workbook,
sheet_name : StringView,
cell : StringView,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.delete_form_control(cell)
}
///|
pub fn Workbook::add_slicer(
self : Workbook,
sheet_name : StringView,
opts : SlicerOptions,
) -> Unit raise XlsxError {
fn slicer_cache_stub(value : StringView) -> String {
let name = value.to_owned()
let sb = StringBuilder::new()
for i, ch in name {
let code = ch.to_int()
let is_letter = (code >= 65 && code <= 90) || (code >= 97 && code <= 122)
let is_digit = code >= 48 && code <= 57
if is_letter {
sb.write_view(ch.to_string())
} else if i > 0 && (is_digit || ch == '.') {
sb.write_view(ch.to_string())
} else {
sb.write_view("_")
}
}
sb.to_string()
}
fn gen_slicer_cache_name(
workbook : Workbook,
field_name : StringView,
) -> String {
let base = "Slicer_" + slicer_cache_stub(field_name)
let existing : Array[String] = []
for dn in workbook.defined_names {
if normalize_defined_name_scope(dn.scope) == "Workbook" {
existing.push(dn.name.to_lower())
}
}
let mut cnt = 0
while cnt < 1000000 {
let candidate = if cnt == 0 { base } else { "\{base}\{cnt}" }
if !existing.contains(candidate.to_lower()) {
return candidate
}
cnt = cnt + 1
}
base
}
fn gen_slicer_name(workbook : Workbook, base_name : StringView) -> String {
let base = base_name.to_owned()
let existing : Array[String] = []
for sheet in workbook.sheets {
for slicer in sheet.slicers() {
existing.push(slicer.name.to_lower())
}
}
let mut cnt = 0
while cnt < 1000000 {
let candidate = if cnt == 0 { base } else { "\{base} \{cnt}" }
if !existing.contains(candidate.to_lower()) {
return candidate
}
cnt = cnt + 1
}
base
}
fn pivot_cache_field_names(
cache_definition_xml : StringView,
) -> Array[String] {
let xml = cache_definition_xml.to_owned()
let out : Array[String] = []
let needle = " v
None => break
}
let after = pos + rel + needle.length()
let rest = xml.unsafe_substring(start=after, end=xml.length())
let end = match rest.find("\"") {
Some(v) => v
None => break
}
let name = rest.unsafe_substring(start=0, end~)
out.push(name.to_string())
pos = after + end + 1
}
out
}
let sheet = self.require_sheet(sheet_name)
ignore(check_sheet_name(opts.table_sheet))
match self.sheet(opts.table_sheet) {
Some(_) => ()
None => raise SheetNotFound(name=opts.table_sheet)
}
let mut has_source = false
let mut is_table_slicer = false
let tables = self.get_tables(opts.table_sheet)
for table in tables {
if table.name == opts.table_name {
has_source = true
is_table_slicer = true
if !table.columns.contains(opts.name) {
raise InvalidSheetOperation(msg="slicer field not found in table")
}
break
}
}
if !has_source {
let pivots = self.get_pivot_tables(opts.table_sheet)
for pivot in pivots {
if pivot.name == opts.table_name {
has_source = true
let fields = pivot_cache_field_names(pivot.cache_definition_xml)
let mut found = false
for field in fields {
if field.to_lower() == opts.name.to_lower() {
found = true
break
}
}
if !found {
raise InvalidSheetOperation(
msg="slicer field not found in pivot cache",
)
}
break
}
}
}
if !has_source {
raise TableNotFound(name=opts.table_name)
}
let mut cache_name = ""
for ws in self.sheets {
for existing in ws.slicers() {
if existing.table_sheet == opts.table_sheet &&
existing.table_name == opts.table_name &&
(if is_table_slicer {
existing.source_name.to_lower() == opts.name.to_lower()
} else {
true
}) {
cache_name = existing.cache
break
}
}
if cache_name != "" {
break
}
}
if cache_name == "" {
cache_name = gen_slicer_cache_name(self, opts.name)
self.set_defined_name(DefinedName::new(cache_name, "#N/A"))
}
let slicer_name = gen_slicer_name(self, opts.name)
let format = opts.format
if format.positioning is None {
format.positioning = Some(TwoCell)
}
if format.print_object is None {
format.print_object = Some(true)
}
if format.locked is None {
format.locked = Some(true)
}
let slicer = {
name: slicer_name,
cache: cache_name,
source_name: opts.name,
cell: opts.cell,
table_sheet: opts.table_sheet,
table_name: opts.table_name,
caption: opts.caption,
macro_name: opts.macro_name,
width: opts.width,
height: opts.height,
display_header: opts.display_header,
item_desc: opts.item_desc,
format,
drawing_offset_x_emu: None,
drawing_offset_y_emu: None,
drawing_width_emu: None,
drawing_height_emu: None,
drawing_order: None,
}
sheet.add_slicer(slicer)
}
///|
pub fn Workbook::add_slicer_with_options(
self : Workbook,
sheet_name : StringView,
opts : SlicerOptions,
) -> Unit raise XlsxError {
self.add_slicer(sheet_name, opts)
}
///|
pub fn Workbook::add_slicer_raw(
self : Workbook,
sheet_name : StringView,
slicer : Slicer,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.add_slicer(slicer)
}
///|
pub fn Workbook::get_slicers(
self : Workbook,
sheet_name : StringView,
) -> Array[Slicer] raise XlsxError {
let sheet = self.require_sheet(sheet_name)
let out : Array[Slicer] = []
for slicer in sheet.get_slicers() {
out.push(slicer)
}
out
}
///|
pub fn Workbook::delete_slicer(
self : Workbook,
name : StringView,
) -> Unit raise XlsxError {
let target = name.to_owned()
let mut removed = false
for sheet in self.sheets {
let mut found = false
for slicer in sheet.get_slicers() {
if slicer.name == target {
found = true
break
}
}
if found {
sheet.delete_slicer(target)
removed = true
break
}
}
if !removed {
raise InvalidSheetOperation(msg="slicer not found")
}
}
///|
pub fn Workbook::add_comment(
self : Workbook,
sheet_name : StringView,
comment : Comment,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.add_comment(comment)
}
///|
/// Replaces the existing classic comment at `comment.cell` on `sheet_name`.
pub fn Workbook::replace_comment(
self : Workbook,
sheet_name : StringView,
comment : Comment,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.replace_comment(comment)
}
///|
/// Returns the classic comment anchored at `cell` on `sheet_name`, if present.
pub fn Workbook::get_comment(
self : Workbook,
sheet_name : StringView,
cell : StringView,
) -> Comment? raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.get_comment(cell)
}
///|
pub fn Workbook::delete_comment(
self : Workbook,
sheet_name : StringView,
cell : StringView,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.delete_comment(cell)
}
///|
pub fn Workbook::get_comments(
self : Workbook,
sheet_name : StringView,
) -> Array[Comment] raise XlsxError {
let sheet = self.require_sheet(sheet_name)
clone_comments(sheet.comments)
}
///|
pub fn Workbook::set_auto_filter(
self : Workbook,
sheet_name : StringView,
range_ref : String,
options : ArrayView[AutoFilterOption],
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.set_auto_filter(range_ref, options)
let abs_range = auto_filter_abs_range_ref(range_ref)
let refers_to = "'\{escape_sheet_name(sheet_name)}'!\{abs_range}"
upsert_defined_name_for_scope(
self, "_xlnm._FilterDatabase", sheet_name, refers_to,
)
}
///|
pub fn Workbook::clear_auto_filter(
self : Workbook,
sheet_name : StringView,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.clear_auto_filter()
remove_defined_name_for_scope(self, "_xlnm._FilterDatabase", sheet_name)
}
///|
pub fn Workbook::get_auto_filter(
self : Workbook,
sheet_name : StringView,
) -> AutoFilter? raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.auto_filter()
}
///|
fn abs_cell_ref_from_rc(row : Int, col : Int) -> String raise XlsxError {
let base = cell_ref_from(row, col)
let chars = base.to_array()
let mut split = 0
while split < chars.length() && chars[split].is_ascii_alphabetic() {
split = split + 1
}
let col_text = base.unsafe_substring(start=0, end=split)
let row_text = base.unsafe_substring(start=split, end=base.length())
"$\{col_text}$\{row_text}"
}
///|
fn auto_filter_abs_range_ref(range_ref : StringView) -> String raise XlsxError {
let normalized = normalize_range_ref(range_ref)
let (min_row, min_col, max_row, max_col) = parse_range_ref(normalized)
let start_ref = abs_cell_ref_from_rc(min_row, min_col)
let end_ref = abs_cell_ref_from_rc(max_row, max_col)
"\{start_ref}:\{end_ref}"
}
///|
fn upsert_defined_name_for_scope(
workbook : Workbook,
name : String,
scope : StringView,
refers_to : String,
) -> Unit {
let normalized_scope = normalize_defined_name_scope(scope)
let scope_key = defined_name_scope_key(normalized_scope)
for i, dn in workbook.defined_names {
if dn.name == name && defined_name_scope_key(dn.scope) == scope_key {
workbook.defined_names[i] = {
name,
refers_to,
scope: normalized_scope,
comment: dn.comment,
}
return
}
}
workbook.defined_names.push({
name,
refers_to,
scope: normalized_scope,
comment: "",
})
}
///|
fn remove_defined_name_for_scope(
workbook : Workbook,
name : String,
scope : StringView,
) -> Unit {
if workbook.defined_names.length() == 0 {
return
}
let normalized_scope = normalize_defined_name_scope(scope)
let scope_key = defined_name_scope_key(normalized_scope)
let updated : Array[DefinedName] = []
for dn in workbook.defined_names {
if dn.name == name && defined_name_scope_key(dn.scope) == scope_key {
continue
}
updated.push(dn)
}
workbook.defined_names.clear()
workbook.defined_names.append(updated)
}
///|
pub fn Workbook::add_data_validation_xml(
self : Workbook,
sheet_name : StringView,
xml : String,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.add_data_validation_xml(xml)
}
///|
pub fn Workbook::add_data_validation(
self : Workbook,
sheet_name : StringView,
dv : DataValidation,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.add_data_validation(dv)
}
///|
pub fn Workbook::add_data_validation_list(
self : Workbook,
sheet_name : StringView,
range_ref : String,
values : ArrayView[String],
allow_blank? : Bool = true,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.add_data_validation_list(range_ref, values, allow_blank~)
}
///|
pub fn Workbook::get_data_validations(
self : Workbook,
sheet_name : StringView,
) -> Array[DataValidation] raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.get_data_validations()
}
///|
pub fn Workbook::delete_data_validation(
self : Workbook,
sheet_name : StringView,
sqrefs? : Array[String] = [],
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.delete_data_validation(sqrefs~)
}
///|
pub fn Workbook::add_ignored_errors(
self : Workbook,
sheet_name : StringView,
range_ref : String,
error_type : IgnoredErrorType,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.add_ignored_errors(range_ref, error_type)
}
///|
pub fn Workbook::add_conditional_format_xml(
self : Workbook,
sheet_name : StringView,
xml : String,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.add_conditional_format_xml(xml)
}
///|
pub fn Workbook::set_conditional_format(
self : Workbook,
sheet_name : StringView,
range_ref : String,
options : ArrayView[ConditionalFormatOptions],
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.set_conditional_format(range_ref, options)
}
///|
pub fn Workbook::get_conditional_formats(
self : Workbook,
sheet_name : StringView,
) -> Map[String, Array[ConditionalFormatOptions]] raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.get_conditional_formats()
}
///|
pub fn Workbook::unset_conditional_format(
self : Workbook,
sheet_name : StringView,
range_ref : String,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.unset_conditional_format(range_ref)
}
///|
pub fn Workbook::merge_cells(
self : Workbook,
sheet_name : StringView,
range_ref : String,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.merge_cells(range_ref)
}
///|
pub fn Workbook::merge_cell(
self : Workbook,
sheet_name : StringView,
top_left : StringView,
bottom_right : StringView,
) -> Unit raise XlsxError {
self.merge_cells(
sheet_name,
"\{top_left.to_owned()}:\{bottom_right.to_owned()}",
)
}
///|
pub fn Workbook::unmerge_cells(
self : Workbook,
sheet_name : StringView,
range_ref : String,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.unmerge_cells(range_ref)
}
///|
pub fn Workbook::unmerge_cell(
self : Workbook,
sheet_name : StringView,
top_left : StringView,
bottom_right : StringView,
) -> Unit raise XlsxError {
self.unmerge_cells(
sheet_name,
"\{top_left.to_owned()}:\{bottom_right.to_owned()}",
)
}
///|
pub fn Workbook::get_merge_cells(
self : Workbook,
sheet_name : StringView,
) -> Array[String] raise XlsxError {
let sheet = self.require_sheet(sheet_name)
let merged : Array[String] = []
for range_ref in sheet.merged_cells() {
merged.push(range_ref)
}
merged
}
///|
pub fn Workbook::get_merge_cells_info(
self : Workbook,
sheet_name : StringView,
without_values? : Bool = false,
options? : Options,
) -> Array[MergeCell] raise XlsxError {
check_sheet_name(sheet_name)
let resolved_options = match options {
Some(value) => value
None => self.options
}
let sheet = self.require_sheet(sheet_name)
let merged_ranges = merge_overlap_ranges(sheet.merged_cells())
let results : Array[MergeCell] = []
for range_ref in merged_ranges {
let value = if without_values {
""
} else {
let (min_row, min_col, _, _) = parse_range_ref(range_ref)
let start_axis = cell_ref_from(min_row, min_col)
match
self.get_cell_value(sheet_name, start_axis, options=resolved_options) {
Some(text) => text
None => ""
}
}
results.push(MergeCell::new(range_ref, value))
}
results
}
///|
pub fn Workbook::add_table(
self : Workbook,
sheet_name : StringView,
options : TableOptions,
) -> Table raise XlsxError {
fn table_name_exists(workbook : Workbook, name : StringView) -> Bool {
let target = name.to_owned().to_lower()
for sheet in workbook.sheets {
for table in sheet.tables {
if table.name.to_lower() == target {
return true
}
}
}
false
}
fn next_table_name(workbook : Workbook) -> String {
let mut max_id = 0
for sheet in workbook.sheets {
for table in sheet.tables {
let name = table.name
if !name.has_prefix("Table") {
continue
}
let suffix = name["Table".length():]
if suffix == "" {
continue
}
let id = @string.parse_int(suffix, base=10) catch { _ => continue }
if id > max_id {
max_id = id
}
}
}
"Table\{max_id + 1}"
}
fn intersects_existing_tables(
sheet : Worksheet,
min_row : Int,
min_col : Int,
max_row : Int,
max_col : Int,
) -> Bool raise XlsxError {
for table in sheet.tables() {
let (t_min_row, t_min_col, t_max_row, t_max_col) = parse_range_ref(
table.range_ref,
)
let disjoint = max_row < t_min_row ||
min_row > t_max_row ||
max_col < t_min_col ||
min_col > t_max_col
if !disjoint {
return true
}
}
false
}
let sheet = self.require_sheet(sheet_name)
let show_header_row = match options.show_header_row {
Some(v) => v
None => true
}
let show_row_stripes = match options.show_row_stripes {
Some(v) => v
None => true
}
let style_name = if options.style_name == "" {
"TableStyleMedium9"
} else {
options.style_name
}
let normalized = normalize_range_ref(options.range_ref)
let (min_row0, min_col, max_row0, max_col) = parse_range_ref(normalized)
let max_row = if min_row0 == max_row0 { max_row0 + 1 } else { max_row0 }
if intersects_existing_tables(sheet, min_row0, min_col, max_row, max_col) {
raise InvalidTable(msg="table range intersects existing table")
}
let range_ref = normalize_range_ref(
"\{cell_ref_from(min_row0, min_col)}:\{cell_ref_from(max_row, max_col)}",
)
let header_row = if show_header_row { min_row0 } else { min_row0 + 1 }
let column_names : Array[String] = []
let seen : Array[String] = []
let mut idx = 0
let mut col = min_col
while col <= max_col {
idx = idx + 1
let cell = cell_ref_from(header_row, col)
let raw = match self.get_cell_value(sheet_name, cell, raw=true) {
Some(v) => v
None => ""
}
let mut name = raw
if name == "" || seen.contains(name.to_lower()) {
name = "Column\{idx}"
if show_header_row {
self.set_cell(sheet_name, cell, name)
}
}
seen.push(name.to_lower())
column_names.push(name)
col = col + 1
}
let mut table_name = options.name
if table_name == "" {
table_name = next_table_name(self)
} else {
check_defined_name(table_name) catch {
_ => raise InvalidTable(msg="table name invalid")
}
}
if table_name_exists(self, table_name) {
raise InvalidTable(msg="table name exists")
}
sheet.add_table(
range_ref,
table_name,
column_names,
display_name=table_name,
style_name~,
show_first_column=options.show_first_column,
show_last_column=options.show_last_column,
show_row_stripes~,
show_column_stripes=options.show_column_stripes,
show_header_row~,
)
}
///|
pub fn Workbook::add_table_with_columns(
self : Workbook,
sheet_name : StringView,
range_ref : String,
name : String,
columns : ArrayView[String],
display_name? : String = "",
style_name? : String = "TableStyleMedium9",
show_first_column? : Bool = false,
show_last_column? : Bool = false,
show_row_stripes? : Bool = true,
show_column_stripes? : Bool = false,
show_header_row? : Bool = true,
) -> Table raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.add_table(
range_ref,
name,
columns,
display_name~,
style_name~,
show_first_column~,
show_last_column~,
show_row_stripes~,
show_column_stripes~,
show_header_row~,
)
}
///|
pub fn Workbook::get_tables(
self : Workbook,
sheet_name : StringView,
) -> Array[Table] raise XlsxError {
let sheet = self.require_sheet(sheet_name)
let tables : Array[Table] = []
for table in sheet.tables() {
tables.push(table)
}
tables
}
///|
pub fn Workbook::delete_table(
self : Workbook,
name : StringView,
) -> Unit raise XlsxError {
let target = name.to_owned()
for sheet in self.sheets {
let mut found = false
for table in sheet.tables {
if table.name == target || table.display_name == target {
found = true
break
}
}
if found {
sheet.delete_table(target)
return
}
}
raise TableNotFound(name=target)
}
///|
pub fn Workbook::add_sparkline(
self : Workbook,
sheet_name : StringView,
options : SparklineOptions,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.add_sparkline_options(options)
}
///|
pub fn Workbook::add_sparkline_group(
self : Workbook,
sheet_name : StringView,
locations : ArrayView[String],
ranges : ArrayView[String],
sparkline_type? : SparklineType = Line,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.add_sparkline_group(locations, ranges, sparkline_type~)
}
///|
pub fn Workbook::add_sparkline_options(
self : Workbook,
sheet_name : StringView,
options : SparklineOptions,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.add_sparkline_options(options)
}
///|
pub fn Workbook::add_sparkline_basic(
self : Workbook,
sheet_name : StringView,
location : String,
range_ref : String,
sparkline_type? : SparklineType = Line,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.add_sparkline(location, range_ref, sparkline_type~)
}
///|
pub fn Workbook::add_pivot_table_xml(
self : Workbook,
sheet_name : StringView,
table_xml : String,
cache_definition_xml : String,
cache_records_xml? : String = "",
name? : String = "",
) -> PivotTable raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.add_pivot_table_xml(
table_xml,
cache_definition_xml,
cache_records_xml~,
name~,
)
}
///|
pub fn Workbook::add_pivot_table(
self : Workbook,
opts : PivotTableOptions,
) -> PivotTable raise XlsxError {
self.add_pivot_table_with_options(opts)
}
///|
fn Workbook::next_pivot_table_name(self : Workbook) -> String {
let mut max_id = 0
for sheet in self.sheets {
for pivot in sheet.pivot_tables {
let name = pivot.name
if !name.has_prefix("PivotTable") {
continue
}
let suffix = name["PivotTable".length():]
if suffix == "" {
continue
}
let id = @string.parse_int(suffix, base=10) catch { _ => continue }
if id > max_id {
max_id = id
}
}
}
"PivotTable\{max_id + 1}"
}
///|
fn Workbook::add_pivot_table_with_options(
self : Workbook,
opts : PivotTableOptions,
) -> PivotTable raise XlsxError {
if opts.compact_data && opts.classic_layout {
raise InvalidPivotTable(
msg="pivot compactData is incompatible with classic layout",
)
}
let (data_sheet, data_ref) = split_sheet_range(opts.data_range)
let (pivot_sheet, pivot_ref) = split_sheet_range(opts.pivot_table_range)
let (header_row, min_col, max_row, max_col) = parse_range_ref(data_ref)
if max_row <= header_row || max_col <= min_col {
raise InvalidPivotTable(msg="pivot data range invalid")
}
let field_names : Array[String] = []
let mut col = min_col
while col <= max_col {
let axis = cell_ref_from(header_row, col)
let value = match
self.get_cell_value(data_sheet, axis, options=self.options) {
Some(text) => text
None => ""
}
if value == "" {
raise InvalidPivotTable(msg="pivot field name empty")
}
field_names.push(value)
col = col + 1
}
let field_index : Map[String, Int] = Map([])
for i, name in field_names {
field_index[name] = i
}
fn resolve_field_index(
field_index : Map[String, Int],
name : String,
) -> Int raise XlsxError {
match field_index.get(name) {
Some(idx) => idx
None => raise InvalidPivotTable(msg="pivot field not found: \{name}")
}
}
let row_fields : Array[(Int, PivotTableField)] = []
for field in opts.rows {
row_fields.push((resolve_field_index(field_index, field.data), field))
}
let col_fields : Array[(Int, PivotTableField)] = []
for field in opts.columns {
col_fields.push((resolve_field_index(field_index, field.data), field))
}
let page_fields : Array[(Int, PivotTableField)] = []
for field in opts.filter {
page_fields.push((resolve_field_index(field_index, field.data), field))
}
let data_fields : Array[(Int, PivotTableField)] = []
for field in opts.data {
let idx = resolve_field_index(field_index, field.data)
data_fields.push((idx, field))
}
let mut pivot_name = opts.name
if pivot_name == "" {
pivot_name = self.next_pivot_table_name()
}
if pivot_name.length() > 255 {
raise InvalidPivotTable(msg="pivot table name too long")
}
let cache_id_guess = 1
let cache_xml = write_pivot_cache_definition_xml(
data_sheet, data_ref, field_names,
)
let table_xml = write_pivot_table_definition_xml(
pivot_name, cache_id_guess, pivot_ref, field_names, row_fields, col_fields, page_fields,
data_fields, opts,
)
self.add_pivot_table_xml(
pivot_sheet,
table_xml,
cache_xml,
cache_records_xml="",
name=pivot_name,
)
}
///|
pub fn Workbook::get_pivot_tables(
self : Workbook,
sheet_name : StringView,
) -> Array[PivotTable] raise XlsxError {
let sheet = self.require_sheet(sheet_name)
clone_pivot_tables(sheet.pivot_tables())
}
///|
pub fn Workbook::delete_pivot_table(
self : Workbook,
sheet_name : StringView,
name : StringView,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.delete_pivot_table(name)
}
///|
pub fn Workbook::set_cell_rc(
self : Workbook,
sheet_name : StringView,
row : Int,
col : Int,
value : String,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.set_cell_rc(row, col, value)
}
///|
pub fn Workbook::set_cell_value_rc(
self : Workbook,
sheet_name : StringView,
row : Int,
col : Int,
value : CellValue,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.set_cell_value_rc(row, col, value)
}
///|
pub fn Workbook::get_cell_rc(
self : Workbook,
sheet_name : StringView,
row : Int,
col : Int,
) -> String? raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.get_cell_rc(row, col)
}
///|
pub fn Workbook::get_cell_value_raw_rc(
self : Workbook,
sheet_name : StringView,
row : Int,
col : Int,
) -> CellValue? raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.get_cell_value_raw_rc(row, col)
}
///|
pub fn Workbook::get_cell_value_rc(
self : Workbook,
sheet_name : StringView,
row : Int,
col : Int,
raw? : Bool = false,
options? : Options,
) -> String? raise XlsxError {
check_sheet_name(sheet_name)
let resolved_options = match options {
Some(value) => value
None => self.options
}
let sheet = self.require_sheet(sheet_name)
for cell in sheet.cells() {
if cell.row == row && cell.col == col {
return Some(
format_cell_value_for_workbook(
self.styles,
cell,
raw,
resolved_options,
use_1904_format=self.uses_1904_date_system(),
),
)
}
}
None
}
///|
pub fn Workbook::set_cell_style_rc(
self : Workbook,
sheet_name : StringView,
row : Int,
col : Int,
style_id : Int,
) -> Unit raise XlsxError {
check_sheet_name(sheet_name)
self.check_style_id(style_id)
let sheet = self.require_sheet(sheet_name)
sheet.set_cell_style_rc(row, col, style_id)
}
///|
pub fn Workbook::get_cell_style_rc(
self : Workbook,
sheet_name : StringView,
row : Int,
col : Int,
) -> Int? raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.get_cell_style_rc(row, col)
}
///|
pub fn Workbook::set_cell_formula_rc(
self : Workbook,
sheet_name : StringView,
row : Int,
col : Int,
formula : String,
value? : String = "",
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.set_cell_formula_rc(row, col, formula, value~)
}
///|
/// Writes `values` across row `row` (1-based) starting at column A, one raw
/// string per cell, on sheet `sheet_name`. Cells beyond `values` are left
/// unchanged.
///
/// Raises `XlsxError` if the sheet name is invalid or no matching sheet exists,
/// if `row` is less than 1, or — when `values` is non-empty —
/// `StreamModeConflict` if the sheet is in stream-writer mode.
pub fn Workbook::set_row(
self : Workbook,
sheet_name : StringView,
row : Int,
values : ArrayView[String],
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.set_row(row, values)
}
///|
/// Returns the raw string values of row `row` (1-based) on `sheet_name`, from
/// column A through the last populated cell; empty cells within that span are
/// returned as empty strings. Returns an empty array for a row with no cells.
///
/// Raises `XlsxError` if the sheet name is invalid, no matching sheet exists, or
/// `row` is less than 1.
pub fn Workbook::get_row(
self : Workbook,
sheet_name : StringView,
row : Int,
) -> Array[String] raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.get_row(row)
}
///|
/// Writes `values` down column `col` (1-based) starting at row 1, one raw string
/// per cell, on `sheet_name`. Cells beyond `values` are left unchanged.
///
/// Raises `XlsxError` if the sheet name is invalid or no matching sheet exists,
/// if `col` is less than 1, or — when `values` is non-empty —
/// `StreamModeConflict` if the sheet is in stream-writer mode.
pub fn Workbook::set_col(
self : Workbook,
sheet_name : StringView,
col : Int,
values : ArrayView[String],
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.set_col(col, values)
}
///|
/// Returns the raw string values of column `col` (1-based) on `sheet_name`, from
/// row 1 through the last populated cell; empty cells within that span are
/// returned as empty strings. Returns an empty array for a column with no cells.
///
/// Raises `XlsxError` if the sheet name is invalid, no matching sheet exists, or
/// `col` is less than 1.
pub fn Workbook::get_col(
self : Workbook,
sheet_name : StringView,
col : Int,
) -> Array[String] raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.get_col(col)
}
///|
/// Sets the height (in points) of row `row` (1-based) on `sheet_name`.
///
/// Raises `XlsxError` if the sheet name is invalid or no matching sheet exists,
/// `StreamModeConflict` if the sheet is in stream-writer mode, if `row` is less
/// than 1, or if `height` is negative.
pub fn Workbook::set_row_height(
self : Workbook,
sheet_name : StringView,
row : Int,
height : Double,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.set_row_height(row, height)
}
///|
/// Returns the explicitly-set height (in points) of row `row` (1-based) on
/// `sheet_name`, or `None` if the row uses the sheet default.
///
/// Raises `XlsxError` if the sheet name is invalid, no matching sheet exists, or
/// `row` is less than 1.
pub fn Workbook::get_row_height(
self : Workbook,
sheet_name : StringView,
row : Int,
) -> Double? raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.get_row_height(row)
}
///|
/// Shows (`visible` true) or hides (`visible` false) row `row` (1-based) on
/// `sheet_name`.
///
/// Raises `XlsxError` if the sheet name is invalid or no matching sheet exists,
/// `StreamModeConflict` if the sheet is in stream-writer mode, or if `row` is
/// less than 1.
pub fn Workbook::set_row_visible(
self : Workbook,
sheet_name : StringView,
row : Int,
visible : Bool,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.set_row_visible(row, visible)
}
///|
/// Reports whether row `row` (1-based) on `sheet_name` is visible. Rows with no
/// explicit dimension are visible by default.
///
/// Raises `XlsxError` if the sheet name is invalid, no matching sheet exists, or
/// `row` is less than 1.
pub fn Workbook::row_visible(
self : Workbook,
sheet_name : StringView,
row : Int,
) -> Bool raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.row_visible(row)
}
///|
/// Sets the outline (grouping) level of row `row` (1-based) on `sheet_name`.
/// `level` must be between 0 and 7 inclusive.
///
/// Raises `XlsxError` if the sheet name is invalid or no matching sheet exists,
/// `StreamModeConflict` if the sheet is in stream-writer mode, if `row` is less
/// than 1, or if `level` is outside 0..=7.
pub fn Workbook::set_row_outline_level(
self : Workbook,
sheet_name : StringView,
row : Int,
level : Int,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.set_row_outline_level(row, level)
}
///|
/// Returns the outline (grouping) level of row `row` (1-based) on `sheet_name`,
/// or 0 if the row has no explicit level.
///
/// Raises `XlsxError` if the sheet name is invalid, no matching sheet exists, or
/// `row` is less than 1.
pub fn Workbook::row_outline_level(
self : Workbook,
sheet_name : StringView,
row : Int,
) -> Int raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.row_outline_level(row)
}
///|
/// Sets the row-level style (applied to the whole row) of row `row` (1-based) on
/// `sheet_name`. A `style_id` of 0 clears the row style. Convenience wrapper
/// over `set_row_style_range` for a single row.
///
/// Raises `XlsxError` if the sheet name is invalid or no matching sheet exists,
/// `InvalidStyleId` if `style_id` is out of range, `StreamModeConflict` if the
/// sheet is in stream-writer mode, or if `row` is less than 1.
pub fn Workbook::set_row_style(
self : Workbook,
sheet_name : StringView,
row : Int,
style_id : Int,
) -> Unit raise XlsxError {
self.set_row_style_range(sheet_name, row, row, style_id)
}
///|
/// Sets the row-level style of every row from `start` to `end` (inclusive,
/// 1-based) on `sheet_name`. A `style_id` of 0 clears the style for those rows.
///
/// Raises `XlsxError` if the sheet name is invalid or no matching sheet exists,
/// `InvalidStyleId` if `style_id` is out of range, `StreamModeConflict` if the
/// sheet is in stream-writer mode, if `start` or `end` is less than 1, or if
/// `end` is less than `start`.
pub fn Workbook::set_row_style_range(
self : Workbook,
sheet_name : StringView,
start : Int,
end : Int,
style_id : Int,
) -> Unit raise XlsxError {
check_sheet_name(sheet_name)
self.check_style_id(style_id)
let sheet = self.require_sheet(sheet_name)
sheet.set_row_style_range(start, end, style_id)
}
///|
/// Returns the row-level style id of row `row` (1-based) on `sheet_name`, or
/// `None` if the row has no explicit style.
///
/// Raises `XlsxError` if the sheet name is invalid, no matching sheet exists, or
/// `row` is less than 1.
pub fn Workbook::get_row_style(
self : Workbook,
sheet_name : StringView,
row : Int,
) -> Int? raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.get_row_style(row)
}
///|
/// Sets the width (in character units) of column `col` (1-based) on
/// `sheet_name`.
///
/// Raises `XlsxError` if the sheet name is invalid or no matching sheet exists,
/// `StreamModeConflict` if the sheet is in stream-writer mode, if `col` is less
/// than 1, or if `width` is negative.
pub fn Workbook::set_col_width(
self : Workbook,
sheet_name : StringView,
col : Int,
width : Double,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.set_col_width(col, width)
}
///|
/// Returns the explicitly-set width (in character units) of column `col`
/// (1-based) on `sheet_name`, or `None` if the column uses the sheet default.
///
/// Raises `XlsxError` if the sheet name is invalid, no matching sheet exists, or
/// `col` is less than 1.
pub fn Workbook::get_col_width(
self : Workbook,
sheet_name : StringView,
col : Int,
) -> Double? raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.get_col_width(col)
}
///|
/// Shows (`visible` true) or hides (`visible` false) column `col` (1-based) on
/// `sheet_name`.
///
/// Raises `XlsxError` if the sheet name is invalid or no matching sheet exists,
/// `StreamModeConflict` if the sheet is in stream-writer mode, or if `col` is
/// less than 1.
pub fn Workbook::set_col_visible(
self : Workbook,
sheet_name : StringView,
col : Int,
visible : Bool,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.set_col_visible(col, visible)
}
///|
/// Reports whether column `col` (1-based) on `sheet_name` is visible. Columns
/// with no explicit dimension are visible by default.
///
/// Raises `XlsxError` if the sheet name is invalid, no matching sheet exists, or
/// `col` is less than 1.
pub fn Workbook::col_visible(
self : Workbook,
sheet_name : StringView,
col : Int,
) -> Bool raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.col_visible(col)
}
///|
/// Sets the outline (grouping) level of column `col` (1-based) on `sheet_name`.
/// `level` must be between 0 and 7 inclusive.
///
/// Raises `XlsxError` if the sheet name is invalid or no matching sheet exists,
/// `StreamModeConflict` if the sheet is in stream-writer mode, if `col` is less
/// than 1, or if `level` is outside 0..=7.
pub fn Workbook::set_col_outline_level(
self : Workbook,
sheet_name : StringView,
col : Int,
level : Int,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.set_col_outline_level(col, level)
}
///|
/// Returns the outline (grouping) level of column `col` (1-based) on
/// `sheet_name`, or 0 if the column has no explicit level.
///
/// Raises `XlsxError` if the sheet name is invalid, no matching sheet exists, or
/// `col` is less than 1.
pub fn Workbook::col_outline_level(
self : Workbook,
sheet_name : StringView,
col : Int,
) -> Int raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.col_outline_level(col)
}
///|
/// Sets the column-level style (applied to the whole column) of column `col`
/// (1-based) on `sheet_name`. A `style_id` of 0 clears the column style.
///
/// Raises `XlsxError` if the sheet name is invalid or no matching sheet exists,
/// `InvalidStyleId` if `style_id` is out of range, `StreamModeConflict` if the
/// sheet is in stream-writer mode, or if `col` is less than 1.
pub fn Workbook::set_col_style(
self : Workbook,
sheet_name : StringView,
col : Int,
style_id : Int,
) -> Unit raise XlsxError {
check_sheet_name(sheet_name)
self.check_style_id(style_id)
let sheet = self.require_sheet(sheet_name)
sheet.set_col_style(col, style_id)
}
///|
/// Returns the column-level style id of column `col` (1-based) on `sheet_name`,
/// or `None` if the column has no explicit style.
///
/// Raises `XlsxError` if the sheet name is invalid, no matching sheet exists, or
/// `col` is less than 1.
pub fn Workbook::get_col_style(
self : Workbook,
sheet_name : StringView,
col : Int,
) -> Int? raise XlsxError {
let sheet = self.require_sheet(sheet_name)
sheet.get_col_style(col)
}
///|
/// Inserts `count` blank rows before row `row` (1-based) on `sheet_name`,
/// shifting existing rows down along with their cells, row dimensions, merged
/// ranges, hyperlinks, auto filter, tables, sparklines, images, charts, and
/// page breaks. Defined names referring to the sheet are adjusted to follow the
/// shift.
///
/// Shared formulas are materialized transactionally under one cumulative
/// `formula_limits` budget; `cancelled` is polled before and during fan-out.
///
/// Raises `XlsxError` if the sheet name is invalid or no matching sheet exists,
/// `StreamModeConflict` if the sheet is in stream-writer mode, if `row` is less
/// than 1, or if `count` is not positive.
pub fn Workbook::insert_rows(
self : Workbook,
sheet_name : StringView,
row : Int,
count : Int,
formula_limits? : SharedFormulaLimits = SharedFormulaLimits::new(),
cancelled? : () -> Bool = () => false,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
if count > cell_ref_max_rows {
// Bound the count before the defined-name shift so `ref_row + count` cannot
// wrap `Int` (the worksheet insert enforces the same bound for its own
// arithmetic).
raise InvalidSheetOperation(
msg="row count \{count} exceeds the sheet's \{cell_ref_max_rows} rows",
)
}
// Stage (and grid-validate) the defined-name shift before touching the sheet,
// then commit it only after the sheet insert also succeeds. Both the sheet
// insert and the defined-name adjuster raise on an out-of-grid shift; ordering
// the two commits last makes the whole operation all-or-nothing.
let staged_names = self.stage_adjusted_defined_names_for_sheet(
sheet.name(),
fn(ref_text : StringView) -> String? raise XlsxError {
adjust_ref_after_row_insert(ref_text, row, count)
},
cancelled~,
)
sheet.insert_rows(row, count, formula_limits~, cancelled~)
self.commit_adjusted_defined_names(staged_names)
}
///|
/// Removes `count` rows starting at row `row` (1-based) on `sheet_name`,
/// shifting the rows below up and adjusting row dimensions, merged ranges,
/// hyperlinks, auto filter, tables, sparklines, images, charts, page breaks,
/// and defined names accordingly.
/// Shared formulas that survive the removal are materialized under one
/// cumulative `formula_limits` budget; removed cells are never translated.
///
/// Raises `XlsxError` if the sheet name is invalid or no matching sheet exists,
/// `StreamModeConflict` if the sheet is in stream-writer mode, if `row` is less
/// than 1, or if `count` is not positive.
pub fn Workbook::remove_rows(
self : Workbook,
sheet_name : StringView,
row : Int,
count : Int,
formula_limits? : SharedFormulaLimits = SharedFormulaLimits::new(),
cancelled? : () -> Bool = () => false,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
// Stage the defined-name adjustment before mutating the sheet, then commit it
// last — so if adjustment raises (e.g. a pre-existing out-of-grid name) the
// removal is all-or-nothing, matching insert.
let staged_names = self.stage_adjusted_defined_names_for_sheet(
sheet.name(),
fn(ref_text : StringView) -> String? raise XlsxError {
adjust_ref_after_row_remove(ref_text, row, count)
},
cancelled~,
)
sheet.remove_rows(row, count, formula_limits~, cancelled~)
self.commit_adjusted_defined_names(staged_names)
}
///|
/// Remove a single row (1-based) and shift rows upward.
pub fn Workbook::remove_row(
self : Workbook,
sheet_name : StringView,
row : Int,
formula_limits? : SharedFormulaLimits = SharedFormulaLimits::new(),
cancelled? : () -> Bool = () => false,
) -> Unit raise XlsxError {
self.remove_rows(sheet_name, row, 1, formula_limits~, cancelled~)
}
///|
/// Inserts `count` blank columns before column `col` (1-based) on `sheet_name`,
/// shifting existing columns right along with their cells and associated
/// features. Defined names referring to the sheet are adjusted to follow the
/// shift.
/// Shared-formula materialization is bounded by `formula_limits` and polls
/// `cancelled` before and during fan-out.
///
/// Raises `XlsxError` if the sheet name is invalid or no matching sheet exists,
/// `StreamModeConflict` if the sheet is in stream-writer mode, if `col` is less
/// than 1, or if `count` is not positive.
pub fn Workbook::insert_cols(
self : Workbook,
sheet_name : StringView,
col : Int,
count : Int,
formula_limits? : SharedFormulaLimits = SharedFormulaLimits::new(),
cancelled? : () -> Bool = () => false,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
if count > cell_ref_max_cols {
// Bound the count before the defined-name shift (see insert_rows).
raise InvalidSheetOperation(
msg="column count \{count} exceeds the sheet's \{cell_ref_max_cols} columns",
)
}
// See insert_rows: stage the defined-name shift, mutate the sheet, then commit
// the names last so an out-of-grid overflow on either side is all-or-nothing.
let staged_names = self.stage_adjusted_defined_names_for_sheet(
sheet.name(),
fn(ref_text : StringView) -> String? raise XlsxError {
adjust_ref_after_col_insert(ref_text, col, count)
},
cancelled~,
)
sheet.insert_cols(col, count, formula_limits~, cancelled~)
self.commit_adjusted_defined_names(staged_names)
}
///|
/// Removes `count` columns starting at column `col` (1-based) on `sheet_name`,
/// shifting the columns to its right leftward and adjusting associated features
/// and defined names accordingly.
/// Shared formulas that survive the removal are bounded by `formula_limits`;
/// removed cells are never translated, and `cancelled` is polled during work.
///
/// Raises `XlsxError` if the sheet name is invalid or no matching sheet exists,
/// `StreamModeConflict` if the sheet is in stream-writer mode, if `col` is less
/// than 1, or if `count` is not positive.
pub fn Workbook::remove_cols(
self : Workbook,
sheet_name : StringView,
col : Int,
count : Int,
formula_limits? : SharedFormulaLimits = SharedFormulaLimits::new(),
cancelled? : () -> Bool = () => false,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
// Stage names before mutating the sheet (see remove_rows).
let staged_names = self.stage_adjusted_defined_names_for_sheet(
sheet.name(),
fn(ref_text : StringView) -> String? raise XlsxError {
adjust_ref_after_col_remove(ref_text, col, count)
},
cancelled~,
)
sheet.remove_cols(col, count, formula_limits~, cancelled~)
self.commit_adjusted_defined_names(staged_names)
}
///|
/// Remove a single column (1-based) and shift columns left.
pub fn Workbook::remove_col(
self : Workbook,
sheet_name : StringView,
col : Int,
formula_limits? : SharedFormulaLimits = SharedFormulaLimits::new(),
cancelled? : () -> Bool = () => false,
) -> Unit raise XlsxError {
self.remove_cols(sheet_name, col, 1, formula_limits~, cancelled~)
}
///|
/// Duplicates row `row` (1-based) on `sheet_name`, inserting the copy directly
/// below as the new row `row + 1` and shifting subsequent rows down.
/// Convenience wrapper over `duplicate_row_to`.
///
/// Raises `XlsxError` if the sheet name is invalid or no matching sheet exists,
/// `StreamModeConflict` if the sheet is in stream-writer mode, or if `row` is
/// less than 1.
pub fn Workbook::duplicate_row(
self : Workbook,
sheet_name : StringView,
row : Int,
formula_limits? : SharedFormulaLimits = SharedFormulaLimits::new(),
cancelled? : () -> Bool = () => false,
) -> Unit raise XlsxError {
// Bound `row` before forming `row + 1`, which would otherwise wrap Int for
// `row == Int::max_value` (duplicate_row_to re-validates both rows too).
if row < 1 || row > cell_ref_max_rows {
raise InvalidCellRef(value="\{0}:\{row}")
}
self.duplicate_row_to(sheet_name, row, row + 1, formula_limits~, cancelled~)
}
///|
/// Duplicates row `row` (1-based) on `sheet_name` into `target_row`: it inserts
/// a new row at `target_row` (shifting existing rows there down) and copies the
/// source row's cells and its full row dimension (height, visibility, outline
/// level, style). Conditional formats, data validations, and single-row merged
/// ranges scoped to the source row are also duplicated onto `target_row`, though
/// merged-range duplication is skipped if `target_row` falls inside an existing
/// merge. Defined names are adjusted for the insert.
/// Source-row copying and the nested insertion share one `formula_limits`
/// budget and cancellation callback.
///
/// Raises `XlsxError` if the sheet name is invalid or no matching sheet exists,
/// `StreamModeConflict` if the sheet is in stream-writer mode, if `row` is less
/// than 1, or if `target_row` is less than 1 or equal to `row`.
pub fn Workbook::duplicate_row_to(
self : Workbook,
sheet_name : StringView,
row : Int,
target_row : Int,
formula_limits? : SharedFormulaLimits = SharedFormulaLimits::new(),
cancelled? : () -> Bool = () => false,
) -> Unit raise XlsxError {
let sheet = self.require_sheet(sheet_name)
// Stage the defined-name shift (which raises if a name would leave the grid)
// before duplicating the row, then commit the names last — otherwise an
// overflowing name would raise after the worksheet had already been mutated.
let staged_names = self.stage_adjusted_defined_names_for_sheet(
sheet.name(),
fn(ref_text : StringView) -> String? raise XlsxError {
adjust_ref_after_row_insert(ref_text, target_row, 1)
},
cancelled~,
)
sheet.duplicate_row_to(row, target_row, formula_limits~, cancelled~)
self.commit_adjusted_defined_names(staged_names)
}