///|
fn worksheet_sheet_data_body(xml : StringView) -> String raise XlsxError {
match extract_tag_body(xml, "sheetData") {
Some(value) => value
None => ""
}
}
///|
/// Accounts for one lexical worksheet pass before it begins. The worksheet
/// reader intentionally composes several focused parsers; charging every pass
/// keeps that convenience inside the same cumulative work policy.
fn worksheet_parse_pass(
source : StringView,
budget : ReadBudget?,
) -> Unit raise XlsxError {
match budget {
Some(value) => {
value.checkpoint()
value.charge_work(source.length())
}
None => ()
}
}
///|
fn parse_worksheet(
xml : StringView,
shared_strings : Array[SharedStringEntry],
style_count : Int,
materialized_cells : Ref[Int],
max_materialized_cells : Int,
materialized_dimensions : Ref[Int],
max_materialized_dimensions : Int,
dimension_work : Ref[Int],
max_dimension_work : Int,
budget? : ReadBudget,
) -> (
Array[Cell],
Map[UInt, SharedFormulaMaster],
Array[String],
AutoFilter?,
Map[Int, RowDimension],
Map[Int, ColDimension],
PageLayoutMarginsOptions?,
PageLayoutOptions?,
HeaderFooterOptions?,
SheetProtection?,
Array[PageBreak],
Array[PageBreak],
Array[SheetView],
SheetPropsOptions?,
String?,
String?,
) raise XlsxError {
let cells : Array[Cell] = []
worksheet_parse_pass(xml, budget)
let sheet_data = worksheet_sheet_data_body(xml)
worksheet_parse_pass(sheet_data, budget)
let mut first = true
for chunk in sheet_data.split(" value.checkpoint()
None => ()
}
if first {
first = false
continue
}
if chunk.length() == 0 || !is_xml_open_tag_boundary(chunk[0]) {
continue
}
if materialized_cells.val >= max_materialized_cells {
raise ResourceLimitExceeded(
kind="materialized_cells",
limit=max_materialized_cells,
actual=bounded_actual_above_limit(max_materialized_cells),
)
}
let next_cell_count = materialized_cells.val + 1
materialized_cells.val = next_cell_count
let text = chunk.to_owned()
let end = match xml_open_tag_end_from(text, 0) {
Some(pos) => pos
None => raise InvalidXml(msg="cell tag not closed")
}
let tag = text[:end]
let ref_value = match attr_value(tag, "r") {
Some(value) => unescape_xml_text(value)
None => raise InvalidXml(msg="cell reference missing")
}
let style_attribute = attr_value(tag, "s")
let style_id = match style_attribute {
Some(value) =>
@string.parse_int(value, base=10) catch {
_ => raise InvalidXml(msg="style id invalid")
}
None => 0
}
if style_id < 0 || style_id >= style_count {
raise InvalidStyleId(index=style_id)
}
let cell_type = attr_value(tag, "t")
let mut tag_last = tag.length() - 1
while tag_last >= 0 && is_xml_attr_space_unit(tag[tag_last]) {
tag_last = tag_last - 1
}
let self_closing = tag_last >= 0 && tag[tag_last] == '/'
let after_open = text[end + 1:]
let cell_body = if self_closing {
after_open[:0]
} else {
match find_xml_close_tag_from(after_open, "c", 0) {
Some((pos, _)) => after_open[:pos]
None => raise InvalidXml(msg="cell not closed")
}
}
let formula_start = find_xml_open_tag_start(cell_body, "f")
let (formula, formula_type, formula_ref, formula_shared_index) = match
formula_start {
Some(pos) => {
let f_rest = cell_body[pos:]
let open_end = match xml_open_tag_end_from(f_rest, 0) {
Some(value) => value
None => raise InvalidXml(msg="formula tag not closed")
}
let open_tag = f_rest[:open_end]
let open_chars = open_tag.to_array()
let mut last = open_chars.length() - 1
while last >= 0 && is_attr_space(open_chars[last]) {
last = last - 1
}
let is_self_closing = last >= 0 && open_chars[last] == '/'
let kind = match attr_value(open_tag, "t") {
Some(value) =>
match formula_type_from_ooxml(value) {
Some(parsed) => Some(parsed)
None => raise InvalidXml(msg="formula type invalid")
}
None => None
}
let ref_value = match attr_value(open_tag, "ref") {
Some(value) =>
Some(
validate_cell_or_range_ref_for_read(
unescape_xml_text(value),
"formula",
),
)
None => None
}
let si_value = match attr_value(open_tag, "si") {
Some(value) =>
Some(
@string.parse_uint(value, base=10) catch {
_ => raise InvalidXml(msg="formula si invalid")
},
)
None => None
}
if is_self_closing {
(Some(""), kind, ref_value, si_value)
} else {
let after_open = f_rest[open_end + 1:]
let close_end = match find_xml_close_tag_from(after_open, "f", 0) {
Some((value, _)) => value
None => raise InvalidXml(msg="formula not closed")
}
let content = unescape_xml_text(after_open[:close_end])
(Some(content), kind, ref_value, si_value)
}
}
None => (None, None, None, None)
}
// ECMA-376 defines the expression on non-master shared formulas as
// ignored. Canonicalize followers immediately so downstream consumers do
// not accidentally evaluate producer-specific garbage from that node.
let formula = match (formula_type, formula_ref, formula) {
(Some(Shared), None, Some(_)) => Some("")
_ => formula
}
let raw_value_opt = match find_xml_open_tag_start(cell_body, "v") {
Some(pos) => {
let v_rest = cell_body[pos:]
let open_end = match xml_open_tag_end_from(v_rest, 0) {
Some(end) => end
None => raise InvalidXml(msg="cell value tag not closed")
}
let open_tag = v_rest[:open_end].to_owned()
if open_tag.trim().has_suffix("/") {
Some("")
} else {
let value_rest = v_rest[open_end + 1:]
let v_end = match find_xml_close_tag_from(value_rest, "v", 0) {
Some((end, _)) => end
None => raise InvalidXml(msg="cell value not closed")
}
Some(value_rest[:v_end].to_owned())
}
}
None => None
}
let raw_value = raw_value_opt.unwrap_or("")
let inline_entry = match cell_type {
Some("inlineStr") => Some(parse_inline_string(cell_body))
_ => None
}
let force_string = raw_value_opt is None &&
formula is Some(_) &&
!(cell_type is Some("inlineStr"))
let mut value = ""
let mut value_type : CellValueType = String
let mut rich_text : Array[RichTextRun]? = None
if force_string {
value = ""
value_type = String
} else {
match cell_type {
Some("s") => {
let index = @string.parse_int(raw_value, base=10) catch {
_ => raise InvalidXml(msg="shared string index invalid")
}
if index < 0 || index >= shared_strings.length() {
raise InvalidSharedString(index~)
}
let entry = shared_strings[index]
value = entry.text
rich_text = entry.runs
value_type = String
}
Some("b") => {
if raw_value != "0" && raw_value != "1" {
raise InvalidXml(msg="cell boolean invalid")
}
value = raw_value
value_type = Bool
}
Some("e") => {
value = unescape_xml_text(raw_value)
value_type = Error
}
Some("str") => {
value = unescape_xml_text(raw_value)
value_type = String
}
Some("inlineStr") =>
match inline_entry {
Some(entry) => {
value = entry.text
rich_text = entry.runs
value_type = String
}
None => {
value = ""
value_type = String
}
}
Some("d") => {
value = unescape_xml_text(raw_value)
value_type = String
}
// A missing `t` attribute defaults to a numeric cell, same as t="n".
Some("n") | None =>
if raw_value == "" {
value = ""
value_type = String
} else {
let number = @string.parse_double(raw_value) catch {
_ => raise InvalidXml(msg="cell number invalid")
}
if number.is_nan() || number.is_inf() {
raise InvalidXml(msg="cell number must be finite")
}
value = raw_value
value_type = Number
}
Some(_) => {
value = unescape_xml_text(raw_value)
value_type = String
}
}
}
let valid_ref = validate_cell_ref_for_read(ref_value, "cell")
let (row, col) = cell_ref_to_rc(valid_ref)
let canonical = cell_ref_from(row, col)
cells.push({
reference: canonical,
row,
col,
value,
value_type,
rich_text,
formula,
formula_type,
formula_ref,
formula_shared_index,
formula_value_present: formula is Some(_) && raw_value_opt is Some(_),
style_explicit: style_attribute is Some(_),
style_id,
})
}
// Shared formulas are a worksheet-wide construct: followers carry only an
// index, so validate the complete group before exposing a partial model.
let shared_formula_masters_index = validated_shared_formula_masters(
cells,
budget?,
)
worksheet_parse_pass(xml, budget)
let merged_cells = parse_merge_cells(xml)
worksheet_parse_pass(xml, budget)
let auto_filter = parse_auto_filter(xml)
worksheet_parse_pass(sheet_data, budget)
let row_dimensions = parse_row_dimensions(
sheet_data, style_count, materialized_dimensions, max_materialized_dimensions,
dimension_work, max_dimension_work,
)
worksheet_parse_pass(xml, budget)
let col_dimensions = parse_col_dimensions(
xml, style_count, materialized_dimensions, max_materialized_dimensions, dimension_work,
max_dimension_work,
)
worksheet_parse_pass(xml, budget)
let page_margins = parse_page_margins(xml)
worksheet_parse_pass(xml, budget)
let page_layout = parse_page_layout(xml)
worksheet_parse_pass(xml, budget)
let header_footer = parse_header_footer(xml)
worksheet_parse_pass(xml, budget)
let sheet_protection = parse_sheet_protection(xml)
worksheet_parse_pass(xml, budget)
let row_breaks = parse_page_breaks(xml, "rowBreaks")
worksheet_parse_pass(xml, budget)
let col_breaks = parse_page_breaks(xml, "colBreaks")
worksheet_parse_pass(xml, budget)
let sheet_views = parse_sheet_views(xml, budget?)
worksheet_parse_pass(xml, budget)
let sheet_props = parse_sheet_props(xml)
worksheet_parse_pass(xml, budget)
let dimension_ref = parse_sheet_dimension(xml)
worksheet_parse_pass(xml, budget)
let picture_rel_id = parse_picture_rel_id(xml)
(
cells, shared_formula_masters_index, merged_cells, auto_filter, row_dimensions,
col_dimensions, page_margins, page_layout, header_footer, sheet_protection, row_breaks,
col_breaks, sheet_views, sheet_props, dimension_ref, picture_rel_id,
)
}