///|
fn column_name_from_number(col : Int) -> String raise XlsxError {
column_number_to_name(col)
}
///|
fn parse_column_ref(text : StringView) -> (Int, Bool)? {
let mut idx = 0
let mut abs_col = false
if idx < text.length() && text[idx] == '$' {
abs_col = true
idx = idx + 1
}
match try_parse_column_name(text[idx:]) {
Some(col) => Some((col, abs_col))
None => None
}
}
///|
fn parse_row_ref(text : StringView) -> (Int, Bool)? {
let mut idx = 0
let mut abs_row = false
if idx < text.length() && text[idx] == '$' {
abs_row = true
idx = idx + 1
}
match try_parse_row_number(text[idx:]) {
Some(row) => Some((row, abs_row))
None => None
}
}
///|
fn parse_cell_ref(text : StringView) -> (Int, Int, Bool, Bool)? {
match try_parse_cell_ref_parts(text) {
Some((row, col, abs_col, abs_row)) => Some((row, col, abs_row, abs_col))
None => None
}
}
///|
fn format_column_ref(col : Int, abs_col : Bool) -> String raise XlsxError {
if col < 1 || col > cell_ref_max_cols {
raise InvalidCellRef(value="\{col}")
}
let prefix = if abs_col { "$" } else { "" }
let name = column_name_from_number(col)
"\{prefix}\{name}"
}
///|
// Raises when a shift pushes the row past the grid, so an out-of-grid defined
// name (e.g. `Sheet1!$A$1048577`) is rejected instead of silently serialized.
fn format_row_ref(row : Int, abs_row : Bool) -> String raise XlsxError {
if row < 1 || row > cell_ref_max_rows {
raise InvalidCellRef(value="\{row}")
}
let prefix = if abs_row { "$" } else { "" }
"\{prefix}\{row}"
}
///|
fn format_cell_ref(
row : Int,
col : Int,
abs_row : Bool,
abs_col : Bool,
) -> String raise XlsxError {
if row < 1 || row > cell_ref_max_rows || col < 1 || col > cell_ref_max_cols {
raise InvalidCellRef(value="\{col}:\{row}")
}
let col_prefix = if abs_col { "$" } else { "" }
let row_prefix = if abs_row { "$" } else { "" }
let col_name = column_name_from_number(col)
"\{col_prefix}\{col_name}\{row_prefix}\{row}"
}
///|
fn is_ref_char(ch : Char) -> Bool {
ch.is_ascii_alphabetic() || ch.is_ascii_digit() || ch == '$' || ch == ':'
}
///|
fn adjust_range_rows_after_insert(
min_row : Int,
max_row : Int,
row : Int,
count : Int,
) -> (Int, Int) {
adjust_bounds_for_insert(min_row, max_row, row, count)
}
///|
fn adjust_range_rows_after_remove(
min_row : Int,
max_row : Int,
row : Int,
count : Int,
) -> (Int, Int)? {
adjust_bounds_for_remove(min_row, max_row, row, count)
}
///|
fn adjust_range_cols_after_insert(
min_col : Int,
max_col : Int,
col : Int,
count : Int,
) -> (Int, Int) {
adjust_bounds_for_insert(min_col, max_col, col, count)
}
///|
fn adjust_range_cols_after_remove(
min_col : Int,
max_col : Int,
col : Int,
count : Int,
) -> (Int, Int)? {
adjust_bounds_for_remove(min_col, max_col, col, count)
}
///|
fn adjust_ref_after_row_insert(
ref_text : StringView,
row : Int,
count : Int,
) -> String? raise XlsxError {
let text = ref_text.to_owned()
match text.find(":") {
Some(pos) => {
let left = text[:pos]
let right = text[pos + 1:]
match (parse_cell_ref(left), parse_cell_ref(right)) {
(Some((r1, c1, ar1, ac1)), Some((r2, c2, ar2, ac2))) => {
let (new_min, new_max) = adjust_range_rows_after_insert(
r1, r2, row, count,
)
let start = format_cell_ref(new_min, c1, ar1, ac1)
let end = format_cell_ref(new_max, c2, ar2, ac2)
Some("\{start}:\{end}")
}
_ =>
match (parse_row_ref(left), parse_row_ref(right)) {
(Some((r1, ar1)), Some((r2, ar2))) => {
let (new_min, new_max) = adjust_range_rows_after_insert(
r1, r2, row, count,
)
let start = format_row_ref(new_min, ar1)
let end = format_row_ref(new_max, ar2)
Some("\{start}:\{end}")
}
_ => Some(text)
}
}
}
None =>
match parse_cell_ref(text) {
Some((r1, c1, ar1, ac1)) => {
let new_row = if r1 >= row { r1 + count } else { r1 }
Some(format_cell_ref(new_row, c1, ar1, ac1))
}
None =>
match parse_row_ref(text) {
Some((r1, ar1)) => {
let new_row = if r1 >= row { r1 + count } else { r1 }
Some(format_row_ref(new_row, ar1))
}
None => Some(text)
}
}
}
}
///|
fn adjust_ref_after_row_remove(
ref_text : StringView,
row : Int,
count : Int,
) -> String? raise XlsxError {
let text = ref_text.to_owned()
match text.find(":") {
Some(pos) => {
let left = text[:pos]
let right = text[pos + 1:]
match (parse_cell_ref(left), parse_cell_ref(right)) {
(Some((r1, c1, ar1, ac1)), Some((r2, c2, ar2, ac2))) =>
match adjust_range_rows_after_remove(r1, r2, row, count) {
Some((new_min, new_max)) => {
let start = format_cell_ref(new_min, c1, ar1, ac1)
let end = format_cell_ref(new_max, c2, ar2, ac2)
Some("\{start}:\{end}")
}
None => None
}
_ =>
match (parse_row_ref(left), parse_row_ref(right)) {
(Some((r1, ar1)), Some((r2, ar2))) =>
match adjust_range_rows_after_remove(r1, r2, row, count) {
Some((new_min, new_max)) => {
let start = format_row_ref(new_min, ar1)
let end = format_row_ref(new_max, ar2)
Some("\{start}:\{end}")
}
None => None
}
_ => Some(text)
}
}
}
None =>
match parse_cell_ref(text) {
Some((r1, c1, ar1, ac1)) =>
match adjust_range_rows_after_remove(r1, r1, row, count) {
Some((new_row, _)) => Some(format_cell_ref(new_row, c1, ar1, ac1))
None => None
}
None =>
match parse_row_ref(text) {
Some((r1, ar1)) =>
match adjust_range_rows_after_remove(r1, r1, row, count) {
Some((new_row, _)) => Some(format_row_ref(new_row, ar1))
None => None
}
None => Some(text)
}
}
}
}
///|
fn adjust_ref_after_col_insert(
ref_text : StringView,
col : Int,
count : Int,
) -> String? raise XlsxError {
let text = ref_text.to_owned()
match text.find(":") {
Some(pos) => {
let left = text[:pos]
let right = text[pos + 1:]
match (parse_cell_ref(left), parse_cell_ref(right)) {
(Some((r1, c1, ar1, ac1)), Some((r2, c2, ar2, ac2))) => {
let (new_min, new_max) = adjust_range_cols_after_insert(
c1, c2, col, count,
)
let start = format_cell_ref(r1, new_min, ar1, ac1)
let end = format_cell_ref(r2, new_max, ar2, ac2)
Some("\{start}:\{end}")
}
_ =>
match (parse_column_ref(left), parse_column_ref(right)) {
(Some((c1, ac1)), Some((c2, ac2))) => {
let (new_min, new_max) = adjust_range_cols_after_insert(
c1, c2, col, count,
)
let start = format_column_ref(new_min, ac1)
let end = format_column_ref(new_max, ac2)
Some("\{start}:\{end}")
}
_ => Some(text)
}
}
}
None =>
match parse_cell_ref(text) {
Some((r1, c1, ar1, ac1)) => {
let new_col = if c1 >= col { c1 + count } else { c1 }
Some(format_cell_ref(r1, new_col, ar1, ac1))
}
None =>
match parse_column_ref(text) {
Some((c1, ac1)) => {
let new_col = if c1 >= col { c1 + count } else { c1 }
Some(format_column_ref(new_col, ac1))
}
None => Some(text)
}
}
}
}
///|
fn adjust_ref_after_col_remove(
ref_text : StringView,
col : Int,
count : Int,
) -> String? raise XlsxError {
let text = ref_text.to_owned()
match text.find(":") {
Some(pos) => {
let left = text[:pos]
let right = text[pos + 1:]
match (parse_cell_ref(left), parse_cell_ref(right)) {
(Some((r1, c1, ar1, ac1)), Some((r2, c2, ar2, ac2))) =>
match adjust_range_cols_after_remove(c1, c2, col, count) {
Some((new_min, new_max)) => {
let start = format_cell_ref(r1, new_min, ar1, ac1)
let end = format_cell_ref(r2, new_max, ar2, ac2)
Some("\{start}:\{end}")
}
None => None
}
_ =>
match (parse_column_ref(left), parse_column_ref(right)) {
(Some((c1, ac1)), Some((c2, ac2))) =>
match adjust_range_cols_after_remove(c1, c2, col, count) {
Some((new_min, new_max)) => {
let start = format_column_ref(new_min, ac1)
let end = format_column_ref(new_max, ac2)
Some("\{start}:\{end}")
}
None => None
}
_ => Some(text)
}
}
}
None =>
match parse_cell_ref(text) {
Some((r1, c1, ar1, ac1)) =>
match adjust_range_cols_after_remove(c1, c1, col, count) {
Some((new_col, _)) => Some(format_cell_ref(r1, new_col, ar1, ac1))
None => None
}
None =>
match parse_column_ref(text) {
Some((c1, ac1)) =>
match adjust_range_cols_after_remove(c1, c1, col, count) {
Some((new_col, _)) => Some(format_column_ref(new_col, ac1))
None => None
}
None => Some(text)
}
}
}
}
///|
fn adjust_defined_name_refers_to_for_sheet(
refers_to : StringView,
sheet_name : StringView,
adjust_fn : (StringView) -> String? raise XlsxError,
cancelled? : () -> Bool = () => false,
) -> String raise XlsxError {
check_read_cancelled(cancelled)
let raw_chars = refers_to.to_array()
let plain_prefix = "\{sheet_name.to_owned()}!"
let quoted_prefix = "'\{escape_sheet_name(sheet_name)}'!"
let plain_chars = plain_prefix.to_array()
let quoted_chars = quoted_prefix.to_array()
let sb = StringBuilder::new()
let mut idx = 0
let find_prefix = fn(
text : Array[Char],
prefix : Array[Char],
start : Int,
) -> Int? raise XlsxError {
if prefix.length() == 0 {
return None
}
let mut pos = start
while pos + prefix.length() <= text.length() {
if (pos & 4095) == 0 {
check_read_cancelled(cancelled)
}
let mut matched = true
for i in 0..
if p < q {
Some((p, plain_prefix, plain_chars.length()))
} else {
Some((q, quoted_prefix, quoted_chars.length()))
}
(Some(p), None) => Some((p, plain_prefix, plain_chars.length()))
(None, Some(q)) => Some((q, quoted_prefix, quoted_chars.length()))
(None, None) => None
}
match next {
Some((pos, prefix, prefix_len)) => {
for i in idx.. sb.write_view(value)
None => sb.write_view("#REF!")
}
idx = end
}
None => {
for i in idx..