///|
let xlsx_extension_namespace_x14 = "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"
///|
let xlsx_extension_namespace_x15 = "http://schemas.microsoft.com/office/spreadsheetml/2010/11/main"
///|
let xlsx_extension_namespace_xm = "http://schemas.microsoft.com/office/excel/2006/main"
///|
let transitional_spreadsheet_drawing_namespace = "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing"
///|
let strict_spreadsheet_drawing_namespace = "http://purl.oclc.org/ooxml/drawingml/spreadsheetDrawing"
///|
let transitional_drawing_main_namespace = "http://schemas.openxmlformats.org/drawingml/2006/main"
///|
let strict_drawing_main_namespace = "http://purl.oclc.org/ooxml/drawingml/main"
///|
let transitional_drawing_chart_namespace = "http://schemas.openxmlformats.org/drawingml/2006/chart"
///|
let strict_drawing_chart_namespace = "http://purl.oclc.org/ooxml/drawingml/chart"
///|
let drawing_slicer_namespace = "http://schemas.microsoft.com/office/drawing/2010/slicer"
///|
let drawing_main_2010_namespace = "http://schemas.microsoft.com/office/drawing/2010/main"
///|
let drawing_slicer_2012_namespace = "http://schemas.microsoft.com/office/drawing/2012/slicer"
///|
let markup_compatibility_namespace = "http://schemas.openxmlformats.org/markup-compatibility/2006"
///|
let xlsx_mce_understood_namespaces : Array[String] = [
transitional_spreadsheet_namespace, strict_spreadsheet_namespace, transitional_relationship_attribute_namespace,
strict_relationship_attribute_namespace,
]
///|
let xlsx_mce_extension_elements : Array[(String, String)] = [
(transitional_spreadsheet_namespace, "ext"),
(strict_spreadsheet_namespace, "ext"),
]
///|
let xlsx_drawing_mce_understood_namespaces : Array[String] = [
transitional_spreadsheet_drawing_namespace, strict_spreadsheet_drawing_namespace,
transitional_drawing_main_namespace, strict_drawing_main_namespace, transitional_drawing_chart_namespace,
strict_drawing_chart_namespace, drawing_slicer_namespace, transitional_relationship_attribute_namespace,
strict_relationship_attribute_namespace, drawing_main_2010_namespace, drawing_slicer_2012_namespace,
]
///|
let xlsx_drawing_mce_extension_elements : Array[(String, String)] = [
(transitional_drawing_main_namespace, "ext"),
(strict_drawing_main_namespace, "ext"),
(transitional_drawing_chart_namespace, "ext"),
(strict_drawing_chart_namespace, "ext"),
(drawing_slicer_namespace, "ext"),
]
///|
let xlsx_drawing_extension_list_namespaces : Array[String] = [
transitional_spreadsheet_drawing_namespace, strict_spreadsheet_drawing_namespace,
transitional_drawing_main_namespace, strict_drawing_main_namespace, transitional_drawing_chart_namespace,
strict_drawing_chart_namespace, drawing_slicer_namespace, drawing_main_2010_namespace,
drawing_slicer_2012_namespace,
]
///|
let xlsx_drawing_core_element_namespaces : Array[String] = [
transitional_spreadsheet_drawing_namespace, strict_spreadsheet_drawing_namespace,
transitional_drawing_main_namespace, strict_drawing_main_namespace, transitional_drawing_chart_namespace,
strict_drawing_chart_namespace, drawing_slicer_namespace,
]
///|
fn xlsx_drawing_anchor_name(local_name : StringView) -> Bool {
local_name == "oneCellAnchor" ||
local_name == "twoCellAnchor" ||
local_name == "absoluteAnchor"
}
///|
fn xlsx_drawing_reader_object_name(local_name : StringView) -> Bool {
local_name == "pic" || local_name == "sp" || local_name == "graphicFrame"
}
///|
fn xlsx_drawing_unsupported_reader_object_name(local_name : StringView) -> Bool {
local_name == "grpSp" || local_name == "cxnSp" || local_name == "contentPart"
}
///|
fn drawing_reader_sequence_matches(
actual : ArrayView[String],
expected : ArrayView[String],
) -> Bool {
if actual.length() != expected.length() {
return false
}
for i in 0.. Unit raise XlsxError {
if marker_parent != "" &&
!drawing_reader_sequence_matches(sequence, [
"col", "colOff", "row", "rowOff",
]) {
raise InvalidXml(msg="drawing marker children are invalid")
}
}
///|
fn validate_drawing_anchor_grammar(
anchor_name : StringView,
sequence : ArrayView[String],
) -> Unit raise XlsxError {
let valid = if anchor_name == "oneCellAnchor" {
drawing_reader_sequence_matches(sequence, [
"from", "ext", "object", "clientData",
])
} else if anchor_name == "twoCellAnchor" {
drawing_reader_sequence_matches(sequence, [
"from", "to", "object", "clientData",
])
} else if anchor_name == "absoluteAnchor" {
drawing_reader_sequence_matches(sequence, [
"pos", "ext", "object", "clientData",
])
} else {
false
}
if !valid {
raise InvalidXml(msg="drawing anchor children are invalid")
}
}
///|
fn drawing_reader_relationship_attribute(
scanner : @ooxml.XmlStartTagScanner,
relationship_namespace : StringView,
other_relationship_namespace : StringView,
local_name : StringView,
required : Bool,
) -> String? raise XlsxError {
if workbook_scanner_attribute(
scanner, other_relationship_namespace, local_name,
)
is Some(_) {
raise InvalidXml(msg="drawing relationship attribute dialect is invalid")
}
match
workbook_scanner_attribute(scanner, relationship_namespace, local_name) {
Some(value) if value != "" => Some(value)
_ if required =>
raise InvalidXml(msg="drawing relationship attribute is missing")
_ => None
}
}
///|
/// Proves that every element consumed by a namespace-oblivious drawing reader
/// occurs on the schema path that reader assumes. Merely filtering namespaces
/// is insufficient: a supported-namespace decoy nested in `graphicData`, a
/// group, or another anchor would otherwise win the readers' first-match
/// lexical searches.
fn validate_xlsx_drawing_reader_structure(
source : StringView,
cancelled? : () -> Bool = () => false,
) -> Unit raise XlsxError {
let scanner = @ooxml.XmlStartTagScanner::new(source, cancelled~)
if !workbook_scanner_next(scanner) ||
scanner.depth() != 1 ||
scanner.local_name() != "wsDr" ||
(
scanner.namespace_uri() != transitional_spreadsheet_drawing_namespace &&
scanner.namespace_uri() != strict_spreadsheet_drawing_namespace
) {
raise InvalidXml(msg="drawing document element is invalid")
}
let drawing_namespace = scanner.namespace_uri().to_owned()
let (
main_namespace,
chart_namespace,
other_drawing_namespace,
other_main_namespace,
other_chart_namespace,
relationship_namespace,
other_relationship_namespace,
) = if drawing_namespace == transitional_spreadsheet_drawing_namespace {
(
transitional_drawing_main_namespace, transitional_drawing_chart_namespace,
strict_spreadsheet_drawing_namespace, strict_drawing_main_namespace, strict_drawing_chart_namespace,
transitional_relationship_attribute_namespace, strict_relationship_attribute_namespace,
)
} else {
(
strict_drawing_main_namespace, strict_drawing_chart_namespace, transitional_spreadsheet_drawing_namespace,
transitional_drawing_main_namespace, transitional_drawing_chart_namespace,
strict_relationship_attribute_namespace, transitional_relationship_attribute_namespace,
)
}
let mut in_reader_anchor = false
let mut anchor_name = ""
let mut reader_object_seen = false
let mut current_reader_object = ""
let mut reader_payload_seen = false
let mut graphic_data_uri : String? = None
let mut extension_depth : Int? = None
let mut text_body_depth : Int? = None
let mut text_container = ""
let mut text_run_properties_seen = false
let mut text_value_seen = false
let mut solid_fill_depth : Int? = None
let mut color_depth : Int? = None
let mut marker_parent = ""
let mut marker_col_seen = false
let mut marker_col_off_seen = false
let mut marker_row_seen = false
let mut marker_row_off_seen = false
let mut from_seen = false
let mut to_seen = false
let mut anchor_pos_seen = false
let mut anchor_ext_seen = false
let mut client_data_seen = false
let mut non_visual_container_seen = false
let mut c_nv_pr_seen = false
let mut c_nv_pic_pr_seen = false
let mut blip_fill_seen = false
let mut blip_seen = false
let mut hyperlink_seen = false
let mut pic_locks_seen = false
let mut sp_pr_seen = false
let mut xfrm_seen = false
let mut xfrm_ext_seen = false
let mut geometry_seen = false
let mut shape_fill_seen = false
let mut line_seen = false
let mut line_fill_seen = false
let mut color_seen = false
let mut alpha_seen = false
let mut text_body_seen = false
let mut graphic_seen = false
let mut graphic_data_seen = false
let mut unsupported_reader_object_depth : Int? = None
let anchor_sequence : Array[String] = []
let marker_sequence : Array[String] = []
while workbook_scanner_next(scanner) {
let depth = scanner.depth()
let local_name = scanner.local_name()
let namespace_uri = scanner.namespace_uri().to_owned()
if text_container != "" && depth <= 6 {
if (text_container == "r" || text_container == "fld") && !text_value_seen {
raise InvalidXml(msg="drawing text run is invalid")
}
text_container = ""
text_run_properties_seen = false
text_value_seen = false
}
if marker_parent != "" && depth <= 3 {
validate_drawing_marker_grammar(marker_parent, marker_sequence)
marker_parent = ""
marker_sequence.clear()
}
if in_reader_anchor && depth <= 2 {
validate_drawing_anchor_grammar(anchor_name, anchor_sequence)
anchor_sequence.clear()
}
match text_body_depth {
Some(start) if depth <= start => text_body_depth = None
_ => ()
}
match solid_fill_depth {
Some(start) if depth <= start => solid_fill_depth = None
_ => ()
}
match color_depth {
Some(start) if depth <= start => color_depth = None
_ => ()
}
if depth <= 2 {
in_reader_anchor = false
anchor_name = ""
reader_object_seen = false
current_reader_object = ""
reader_payload_seen = false
from_seen = false
to_seen = false
anchor_pos_seen = false
anchor_ext_seen = false
client_data_seen = false
} else if depth <= 3 {
current_reader_object = ""
marker_parent = ""
}
if depth <= 5 {
graphic_data_uri = None
}
match extension_depth {
Some(extension_start) if depth > extension_start =>
raise InvalidXml(msg="drawing extension payload is misplaced")
Some(_) => extension_depth = None
None => ()
}
if namespace_uri == other_drawing_namespace ||
namespace_uri == other_main_namespace ||
namespace_uri == other_chart_namespace {
raise InvalidXml(msg="drawing reader target dialect is mixed")
}
let inside_unsupported_reader_object = match
unsupported_reader_object_depth {
Some(start) if depth > start => true
Some(_) => {
unsupported_reader_object_depth = None
false
}
None => false
}
if inside_unsupported_reader_object {
continue
}
// Rich-text run properties use the same DrawingML element names as shape
// styling. Prove every name consumed by the text reader occurs on its
// exact txBody/paragraph/run path before tolerating unmodeled DrawingML
// properties. Other dialects and xdr/c/sle descendants still fall through
// to the normal decoy checks below.
let inside_shape_text_body = match text_body_depth {
Some(start) => depth > start
None => false
}
if inside_shape_text_body && namespace_uri == main_namespace {
let parent_name = scanner.parent_local_name().unwrap_or("")
let parent_namespace = scanner.parent_namespace_uri()
if local_name == "p" {
if depth != 5 ||
parent_name != "txBody" ||
parent_namespace != Some(drawing_namespace) {
raise InvalidXml(msg="drawing text paragraph is misplaced")
}
} else if local_name == "r" || local_name == "fld" || local_name == "br" {
if depth != 6 ||
parent_name != "p" ||
parent_namespace != Some(main_namespace) {
raise InvalidXml(msg="drawing text run is misplaced")
}
text_container = local_name.to_owned()
text_run_properties_seen = false
text_value_seen = false
} else if local_name == "rPr" {
if depth != 7 ||
parent_name != text_container ||
parent_namespace != Some(main_namespace) ||
text_container == "" {
raise InvalidXml(msg="drawing text properties are misplaced")
}
if text_run_properties_seen || text_value_seen {
raise InvalidXml(msg="drawing text run is invalid")
}
text_run_properties_seen = true
} else if local_name == "t" {
if depth != 7 ||
(text_container != "r" && text_container != "fld") ||
parent_name != text_container ||
parent_namespace != Some(main_namespace) {
raise InvalidXml(msg="drawing text target is misplaced")
}
if text_value_seen {
raise InvalidXml(msg="drawing text run is invalid")
}
text_value_seen = true
}
// Run-property parsing below is namespace-aware and only consumes
// direct children of the validated rPr above. Other DrawingML text
// properties (for example pPr/defRPr defaults and nested underline
// fills) are valid but deliberately unmodeled, so they can be ignored
// without becoming lexical decoys.
continue
}
if local_name == "ext" &&
xlsx_drawing_extension_list_namespaces.any(candidate => {
candidate == namespace_uri
}) {
extension_depth = Some(depth)
}
if namespace_uri == drawing_namespace &&
xlsx_drawing_anchor_name(local_name) {
if depth != 2 ||
scanner.parent_local_name() != Some("wsDr") ||
scanner.parent_namespace_uri() != Some(drawing_namespace) {
raise InvalidXml(msg="drawing anchor is misplaced")
}
in_reader_anchor = true
anchor_name = local_name.to_owned()
reader_object_seen = false
current_reader_object = ""
reader_payload_seen = false
from_seen = false
to_seen = false
anchor_pos_seen = false
anchor_ext_seen = false
client_data_seen = false
anchor_sequence.clear()
} else if namespace_uri == drawing_namespace &&
xlsx_drawing_reader_object_name(local_name) {
if depth != 3 ||
!in_reader_anchor ||
!xlsx_drawing_anchor_name(scanner.parent_local_name().unwrap_or("")) ||
scanner.parent_namespace_uri() != Some(drawing_namespace) {
raise InvalidXml(msg="drawing reader object is misplaced")
}
if reader_object_seen {
raise InvalidXml(msg="drawing anchor has multiple reader objects")
}
reader_object_seen = true
anchor_sequence.push("object")
current_reader_object = local_name.to_owned()
non_visual_container_seen = false
c_nv_pr_seen = false
c_nv_pic_pr_seen = false
blip_fill_seen = false
blip_seen = false
hyperlink_seen = false
pic_locks_seen = false
sp_pr_seen = false
xfrm_seen = false
xfrm_ext_seen = false
geometry_seen = false
shape_fill_seen = false
line_seen = false
line_fill_seen = false
color_seen = false
alpha_seen = false
text_body_seen = false
graphic_seen = false
graphic_data_seen = false
} else if namespace_uri == drawing_namespace &&
xlsx_drawing_unsupported_reader_object_name(local_name) {
if depth != 3 ||
!in_reader_anchor ||
!xlsx_drawing_anchor_name(scanner.parent_local_name().unwrap_or("")) ||
scanner.parent_namespace_uri() != Some(drawing_namespace) {
raise InvalidXml(msg="unsupported drawing reader object is misplaced")
}
if reader_object_seen {
raise InvalidXml(msg="drawing anchor has multiple reader objects")
}
// These are valid anchor payloads, but the semantic readers below do
// not model them. Ignore the complete subtree so nested `sp`, `pic`, or
// `graphicFrame` elements cannot be mistaken for direct anchor objects.
reader_object_seen = true
anchor_sequence.push("object")
current_reader_object = ""
unsupported_reader_object_depth = Some(depth)
} else if namespace_uri == drawing_namespace &&
(
local_name == "from" ||
local_name == "to" ||
local_name == "pos" ||
local_name == "ext" ||
local_name == "clientData"
) {
if depth != 3 ||
!in_reader_anchor ||
scanner.parent_local_name() != Some(anchor_name) ||
scanner.parent_namespace_uri() != Some(drawing_namespace) {
raise InvalidXml(msg="drawing anchor reader target is misplaced")
}
anchor_sequence.push(local_name.to_owned())
if local_name == "from" {
if anchor_name == "absoluteAnchor" {
raise InvalidXml(msg="drawing anchor from target is misplaced")
}
if from_seen {
raise InvalidXml(msg="drawing anchor from target is duplicated")
}
from_seen = true
marker_parent = "from"
marker_col_seen = false
marker_col_off_seen = false
marker_row_seen = false
marker_row_off_seen = false
marker_sequence.clear()
} else if local_name == "to" {
if anchor_name == "absoluteAnchor" {
raise InvalidXml(msg="drawing anchor to target is misplaced")
}
if to_seen {
raise InvalidXml(msg="drawing anchor to target is duplicated")
}
to_seen = true
marker_parent = "to"
marker_col_seen = false
marker_col_off_seen = false
marker_row_seen = false
marker_row_off_seen = false
marker_sequence.clear()
} else if local_name == "pos" {
if anchor_name != "absoluteAnchor" {
raise InvalidXml(msg="drawing anchor pos target is misplaced")
}
if anchor_pos_seen {
raise InvalidXml(msg="drawing anchor pos target is duplicated")
}
anchor_pos_seen = true
} else if local_name == "ext" {
if anchor_ext_seen {
raise InvalidXml(msg="drawing anchor ext target is duplicated")
}
anchor_ext_seen = true
} else {
if client_data_seen {
raise InvalidXml(msg="drawing clientData target is duplicated")
}
client_data_seen = true
}
} else if namespace_uri == drawing_namespace &&
(
local_name == "col" ||
local_name == "colOff" ||
local_name == "row" ||
local_name == "rowOff"
) {
if depth != 4 ||
(marker_parent != "from" && marker_parent != "to") ||
scanner.parent_local_name() != Some(marker_parent) ||
scanner.parent_namespace_uri() != Some(drawing_namespace) {
raise InvalidXml(msg="drawing marker target is misplaced")
}
marker_sequence.push(local_name.to_owned())
if local_name == "col" {
if marker_col_seen {
raise InvalidXml(msg="drawing marker col target is duplicated")
}
marker_col_seen = true
} else if local_name == "colOff" {
if marker_col_off_seen {
raise InvalidXml(msg="drawing marker colOff target is duplicated")
}
marker_col_off_seen = true
} else if local_name == "row" {
if marker_row_seen {
raise InvalidXml(msg="drawing marker row target is duplicated")
}
marker_row_seen = true
} else {
if marker_row_off_seen {
raise InvalidXml(msg="drawing marker rowOff target is duplicated")
}
marker_row_off_seen = true
}
} else if namespace_uri == drawing_namespace &&
(
local_name == "nvPicPr" ||
local_name == "nvSpPr" ||
local_name == "nvGraphicFramePr"
) {
let expected = if current_reader_object == "pic" {
"nvPicPr"
} else if current_reader_object == "sp" {
"nvSpPr"
} else {
"nvGraphicFramePr"
}
if current_reader_object == "" ||
local_name != expected ||
depth != 4 ||
scanner.parent_local_name() != Some(current_reader_object) ||
scanner.parent_namespace_uri() != Some(drawing_namespace) {
raise InvalidXml(msg="drawing nonvisual target is misplaced")
}
if non_visual_container_seen {
raise InvalidXml(msg="drawing nonvisual target is duplicated")
}
non_visual_container_seen = true
} else if namespace_uri == drawing_namespace && local_name == "cNvPr" {
let expected_parent = if current_reader_object == "pic" {
"nvPicPr"
} else if current_reader_object == "sp" {
"nvSpPr"
} else {
"nvGraphicFramePr"
}
if current_reader_object == "" ||
depth != 5 ||
scanner.parent_local_name() != Some(expected_parent) ||
scanner.parent_namespace_uri() != Some(drawing_namespace) {
raise InvalidXml(msg="drawing cNvPr target is misplaced")
}
if c_nv_pr_seen {
raise InvalidXml(msg="drawing cNvPr target is duplicated")
}
c_nv_pr_seen = true
} else if namespace_uri == drawing_namespace && local_name == "cNvPicPr" {
if current_reader_object != "pic" ||
depth != 5 ||
scanner.parent_local_name() != Some("nvPicPr") ||
scanner.parent_namespace_uri() != Some(drawing_namespace) {
raise InvalidXml(msg="drawing cNvPicPr target is misplaced")
}
if c_nv_pic_pr_seen {
raise InvalidXml(msg="drawing cNvPicPr target is duplicated")
}
c_nv_pic_pr_seen = true
} else if namespace_uri == drawing_namespace && local_name == "blipFill" {
if current_reader_object != "pic" ||
depth != 4 ||
scanner.parent_local_name() != Some("pic") ||
scanner.parent_namespace_uri() != Some(drawing_namespace) {
raise InvalidXml(msg="drawing blipFill target is misplaced")
}
if blip_fill_seen {
raise InvalidXml(msg="drawing blipFill target is duplicated")
}
blip_fill_seen = true
} else if namespace_uri == drawing_namespace && local_name == "spPr" {
if (current_reader_object != "sp" && current_reader_object != "pic") ||
depth != 4 ||
scanner.parent_local_name() != Some(current_reader_object) ||
scanner.parent_namespace_uri() != Some(drawing_namespace) {
raise InvalidXml(msg="drawing spPr target is misplaced")
}
if sp_pr_seen {
raise InvalidXml(msg="drawing spPr target is duplicated")
}
sp_pr_seen = true
} else if namespace_uri == drawing_namespace && local_name == "txBody" {
if current_reader_object != "sp" ||
depth != 4 ||
scanner.parent_local_name() != Some("sp") ||
scanner.parent_namespace_uri() != Some(drawing_namespace) {
raise InvalidXml(msg="drawing txBody target is misplaced")
}
if text_body_seen {
raise InvalidXml(msg="drawing txBody target is duplicated")
}
text_body_seen = true
text_body_depth = Some(depth)
} else if namespace_uri == main_namespace && local_name == "graphic" {
if depth != 4 ||
current_reader_object != "graphicFrame" ||
scanner.parent_local_name() != Some("graphicFrame") ||
scanner.parent_namespace_uri() != Some(drawing_namespace) {
raise InvalidXml(msg="drawing graphic element is misplaced")
}
if graphic_seen {
raise InvalidXml(msg="drawing graphic element is duplicated")
}
graphic_seen = true
} else if namespace_uri == main_namespace && local_name == "graphicData" {
if depth != 5 ||
current_reader_object != "graphicFrame" ||
scanner.parent_local_name() != Some("graphic") ||
scanner.parent_namespace_uri() != Some(main_namespace) {
raise InvalidXml(msg="drawing graphicData element is misplaced")
}
if graphic_data_seen {
raise InvalidXml(msg="drawing graphicData element is duplicated")
}
graphic_data_seen = true
graphic_data_uri = workbook_scanner_attribute(scanner, "", "uri")
} else if namespace_uri == main_namespace &&
local_name == "blip" &&
current_reader_object == "pic" {
if depth != 5 ||
scanner.parent_local_name() != Some("blipFill") ||
scanner.parent_namespace_uri() != Some(drawing_namespace) {
raise InvalidXml(msg="drawing blip target is misplaced")
}
if blip_seen {
raise InvalidXml(msg="drawing blip target is duplicated")
}
blip_seen = true
ignore(
drawing_reader_relationship_attribute(
scanner, relationship_namespace, other_relationship_namespace, "embed",
true,
),
)
} else if namespace_uri == main_namespace &&
local_name == "hlinkClick" &&
current_reader_object == "pic" {
if depth != 6 ||
scanner.parent_local_name() != Some("cNvPr") ||
scanner.parent_namespace_uri() != Some(drawing_namespace) {
raise InvalidXml(msg="drawing hyperlink target is misplaced")
}
if hyperlink_seen {
raise InvalidXml(msg="drawing hyperlink target is duplicated")
}
hyperlink_seen = true
ignore(
drawing_reader_relationship_attribute(
scanner, relationship_namespace, other_relationship_namespace, "id", false,
),
)
} else if namespace_uri == main_namespace && local_name == "picLocks" {
if current_reader_object != "pic" ||
depth != 6 ||
scanner.parent_local_name() != Some("cNvPicPr") ||
scanner.parent_namespace_uri() != Some(drawing_namespace) {
raise InvalidXml(msg="drawing picLocks target is misplaced")
}
if pic_locks_seen {
raise InvalidXml(msg="drawing picLocks target is duplicated")
}
pic_locks_seen = true
} else if namespace_uri == main_namespace &&
local_name == "xfrm" &&
current_reader_object == "sp" {
if depth != 5 ||
scanner.parent_local_name() != Some("spPr") ||
scanner.parent_namespace_uri() != Some(drawing_namespace) {
raise InvalidXml(msg="drawing xfrm target is misplaced")
}
if xfrm_seen {
raise InvalidXml(msg="drawing xfrm target is duplicated")
}
xfrm_seen = true
} else if namespace_uri == main_namespace &&
local_name == "ext" &&
current_reader_object == "sp" {
if depth != 6 ||
scanner.parent_local_name() != Some("xfrm") ||
scanner.parent_namespace_uri() != Some(main_namespace) {
raise InvalidXml(msg="drawing shape ext target is misplaced")
}
if xfrm_ext_seen {
raise InvalidXml(msg="drawing shape ext target is duplicated")
}
xfrm_ext_seen = true
} else if namespace_uri == main_namespace &&
local_name == "prstGeom" &&
current_reader_object == "sp" {
if depth != 5 ||
scanner.parent_local_name() != Some("spPr") ||
scanner.parent_namespace_uri() != Some(drawing_namespace) {
raise InvalidXml(msg="drawing geometry target is misplaced")
}
if geometry_seen {
raise InvalidXml(msg="drawing geometry target is duplicated")
}
geometry_seen = true
} else if namespace_uri == main_namespace &&
local_name == "solidFill" &&
current_reader_object == "sp" {
let shape_fill = depth == 5 &&
scanner.parent_local_name() == Some("spPr") &&
scanner.parent_namespace_uri() == Some(drawing_namespace)
let line_fill = depth == 6 &&
scanner.parent_local_name() == Some("ln") &&
scanner.parent_namespace_uri() == Some(main_namespace)
if !shape_fill && !line_fill {
raise InvalidXml(msg="drawing solidFill target is misplaced")
}
if shape_fill {
if shape_fill_seen {
raise InvalidXml(msg="drawing shape fill target is duplicated")
}
shape_fill_seen = true
} else {
if line_fill_seen {
raise InvalidXml(msg="drawing line fill target is duplicated")
}
line_fill_seen = true
}
solid_fill_depth = Some(depth)
color_seen = false
alpha_seen = false
} else if namespace_uri == main_namespace &&
local_name == "srgbClr" &&
current_reader_object == "sp" {
let fill_depth = solid_fill_depth.unwrap_or(-1)
if depth != fill_depth + 1 ||
scanner.parent_local_name() != Some("solidFill") ||
scanner.parent_namespace_uri() != Some(main_namespace) {
raise InvalidXml(msg="drawing color target is misplaced")
}
if color_seen {
raise InvalidXml(msg="drawing color target is duplicated")
}
color_seen = true
alpha_seen = false
color_depth = Some(depth)
} else if namespace_uri == main_namespace &&
local_name == "alpha" &&
current_reader_object == "sp" {
let parent_depth = color_depth.unwrap_or(-1)
if depth != parent_depth + 1 ||
scanner.parent_local_name() != Some("srgbClr") ||
scanner.parent_namespace_uri() != Some(main_namespace) {
raise InvalidXml(msg="drawing alpha target is misplaced")
}
if alpha_seen {
raise InvalidXml(msg="drawing alpha target is duplicated")
}
alpha_seen = true
} else if namespace_uri == main_namespace &&
local_name == "ln" &&
current_reader_object == "sp" {
if depth != 5 ||
scanner.parent_local_name() != Some("spPr") ||
scanner.parent_namespace_uri() != Some(drawing_namespace) {
raise InvalidXml(msg="drawing line target is misplaced")
}
if line_seen {
raise InvalidXml(msg="drawing line target is duplicated")
}
line_seen = true
} else if namespace_uri == main_namespace &&
local_name == "t" &&
current_reader_object == "sp" {
match text_body_depth {
Some(body_depth) if depth > body_depth => ()
_ => raise InvalidXml(msg="drawing text target is misplaced")
}
} else if (
namespace_uri == transitional_drawing_chart_namespace ||
namespace_uri == strict_drawing_chart_namespace
) &&
local_name == "chart" {
if namespace_uri != chart_namespace ||
depth != 6 ||
scanner.parent_local_name() != Some("graphicData") ||
scanner.parent_namespace_uri() != Some(main_namespace) {
raise InvalidXml(msg="drawing chart payload is misplaced")
}
if graphic_data_uri != Some(chart_namespace) {
raise InvalidXml(msg="drawing chart graphicData URI is invalid")
}
if reader_payload_seen {
raise InvalidXml(msg="drawing anchor has multiple reader payloads")
}
ignore(
drawing_reader_relationship_attribute(
scanner, relationship_namespace, other_relationship_namespace, "id", true,
),
)
reader_payload_seen = true
} else if namespace_uri == drawing_slicer_namespace &&
local_name == "slicer" {
if depth != 6 ||
scanner.parent_local_name() != Some("graphicData") ||
scanner.parent_namespace_uri() != Some(main_namespace) {
raise InvalidXml(msg="drawing slicer payload is misplaced")
}
if graphic_data_uri != Some(drawing_slicer_namespace) {
raise InvalidXml(msg="drawing slicer graphicData URI is invalid")
}
if reader_payload_seen {
raise InvalidXml(msg="drawing anchor has multiple reader payloads")
}
reader_payload_seen = true
} else if namespace_uri == drawing_namespace &&
depth == 3 &&
in_reader_anchor &&
scanner.parent_local_name() == Some(anchor_name) &&
scanner.parent_namespace_uri() == Some(drawing_namespace) {
raise InvalidXml(msg="drawing anchor content is invalid")
}
}
if marker_parent != "" {
validate_drawing_marker_grammar(marker_parent, marker_sequence)
}
if in_reader_anchor {
validate_drawing_anchor_grammar(anchor_name, anchor_sequence)
}
if text_container != "" &&
(text_container == "r" || text_container == "fld") &&
!text_value_seen {
raise InvalidXml(msg="drawing text run is invalid")
}
}
///|
/// Produces the effective SpreadsheetML view before namespace filtering or
/// lexical feature parsing. In particular, core content promoted from an MCE
/// Fallback must be selected while its namespace context is still available.
fn project_xlsx_markup_compatibility(
source : StringView,
max_output_chars : Int,
cancelled? : () -> Bool = () => false,
) -> String raise XlsxError {
@ooxml.project_xml_markup_compatibility(
source,
xlsx_mce_understood_namespaces,
max_output_chars~,
cancelled~,
extension_elements=xlsx_mce_extension_elements,
) catch {
InvalidXml(msg~) => raise InvalidXml(msg~)
ReadCancelled => raise ReadCancelled
}
}
///|
/// Converts namespace-qualified OOXML element names into the lexical form used
/// by the existing bounded feature parsers. The source is fully namespace- and
/// structure-validated before rewriting; only names change, never values.
fn canonicalize_xlsx_xml(
xml : StringView,
max_output_chars? : Int = default_max_xml_part_bytes,
cancelled? : () -> Bool = () => false,
) -> String raise XlsxError {
let root_scanner = @ooxml.XmlStartTagScanner::new(xml, cancelled~)
if !workbook_scanner_next(root_scanner) || root_scanner.depth() != 1 {
raise InvalidXml(msg="XLSX XML document element is missing")
}
let root_namespace = root_scanner.namespace_uri().to_owned()
let unprefixed_namespaces = [
transitional_spreadsheet_namespace, strict_spreadsheet_namespace,
]
let extension_prefixes : Array[(String, String)] = []
for
namespace_and_prefix in [
(xlsx_extension_namespace_x14, "x14"),
(xlsx_extension_namespace_x15, "x15"),
(xlsx_extension_namespace_xm, "xm"),
] {
let (namespace_uri, prefix) = namespace_and_prefix
if namespace_uri == root_namespace {
unprefixed_namespaces.push(namespace_uri)
} else {
extension_prefixes.push((namespace_uri, prefix))
}
}
@ooxml.canonicalize_xml_expanded_names(
xml,
unprefixed_namespaces,
[
(transitional_relationship_attribute_namespace, "r"),
(strict_relationship_attribute_namespace, "r"),
],
max_output_chars~,
cancelled~,
element_prefixes=extension_prefixes,
) catch {
InvalidXml(msg~) => raise InvalidXml(msg~)
ReadCancelled => raise ReadCancelled
}
}
///|
/// Validates the SpreadsheetML document element and removes foreign-namespace
/// descendant subtrees from the namespace-oblivious core view. The original
/// canonical view remains available to extension-specific readers.
fn xlsx_core_source_without_foreign(
source : StringView,
root_local_name : StringView,
cancelled? : () -> Bool = () => false,
) -> String? raise XlsxError {
let scanner = @ooxml.XmlStartTagScanner::new(source, cancelled~)
if !workbook_scanner_next(scanner) ||
scanner.depth() != 1 ||
scanner.local_name() != root_local_name ||
(
scanner.namespace_uri() != transitional_spreadsheet_namespace &&
scanner.namespace_uri() != strict_spreadsheet_namespace
) {
raise InvalidXml(
msg="\{root_local_name.to_owned()} document element is invalid",
)
}
let dialect = scanner.namespace_uri().to_owned()
@ooxml.remove_xml_foreign_namespace_subtrees(source, [dialect], cancelled~) catch {
InvalidXml(msg~) => raise InvalidXml(msg~)
ReadCancelled => raise ReadCancelled
}
}
///|
/// Produces the extension-aware worksheet view used by known feature parsers.
/// Core SpreadsheetML plus the explicitly supported x14/x15/xm namespaces are
/// retained; every other namespace subtree is removed before lexical parsing,
/// including a foreign default namespace that reuses a core local name.
fn canonicalize_xlsx_worksheet_features(
source : StringView,
max_output_chars : Int,
cancelled? : () -> Bool = () => false,
) -> String raise XlsxError {
let scanner = @ooxml.XmlStartTagScanner::new(source, cancelled~)
if !workbook_scanner_next(scanner) ||
scanner.depth() != 1 ||
scanner.local_name() != "worksheet" ||
(
scanner.namespace_uri() != transitional_spreadsheet_namespace &&
scanner.namespace_uri() != strict_spreadsheet_namespace
) {
raise InvalidXml(msg="worksheet document element is invalid")
}
let dialect = scanner.namespace_uri().to_owned()
let filtered = @ooxml.remove_xml_foreign_namespace_subtrees(
source,
[
dialect, xlsx_extension_namespace_x14, xlsx_extension_namespace_x15, xlsx_extension_namespace_xm,
],
cancelled~,
) catch {
InvalidXml(msg~) => raise InvalidXml(msg~)
ReadCancelled => raise ReadCancelled
}
canonicalize_xlsx_xml(
filtered.unwrap_or(source.to_owned()),
max_output_chars~,
cancelled~,
)
}
///|
fn charge_xlsx_drawing_xml_pass(
budget : ReadBudget?,
xml : StringView,
) -> Unit raise XlsxError {
match budget {
Some(value) => {
value.checkpoint()
value.charge_work(xml.length())
}
None => ()
}
}
///|
fn charge_xlsx_drawing_xml_materialization(
budget : ReadBudget?,
xml : StringView,
) -> Unit raise XlsxError {
match budget {
Some(value) => {
value.checkpoint()
value.charge_work(xml.length())
}
None => ()
}
}
///|
/// Produces the effective DrawingML view and canonicalizes it by expanded
/// namespace identity. MCE selection must run first, while `Choice.Requires`
/// prefixes still have their original namespace bindings; otherwise a lexical
/// feature reader could consume an unsupported branch ahead of its fallback.
/// Prefix aliases and default namespaces are then normalized to the names used
/// by the drawing readers, while a foreign namespace borrowing `xdr`, `a`,
/// `c`, `sle`, or `mc` is defanged by the bounded XML canonicalizer.
fn canonicalize_xlsx_drawing_xml(
source : StringView,
max_output_chars : Int,
budget? : ReadBudget,
cancelled? : () -> Bool = () => false,
) -> String raise XlsxError {
charge_xlsx_drawing_xml_pass(budget, source)
let scanner = @ooxml.XmlStartTagScanner::new(source, cancelled~)
if !workbook_scanner_next(scanner) ||
scanner.depth() != 1 ||
scanner.local_name() != "wsDr" ||
(
scanner.namespace_uri() != transitional_spreadsheet_drawing_namespace &&
scanner.namespace_uri() != strict_spreadsheet_drawing_namespace
) {
raise InvalidXml(msg="drawing document element is invalid")
}
// Application extension payload is deliberately absent from the lexical
// reader view. Removing its structural container before MCE projection also
// prevents directives inside opaque payload from affecting core selection.
charge_xlsx_drawing_xml_pass(budget, source)
let without_extension_lists = @ooxml.remove_xml_expanded_name_subtrees(
source,
xlsx_drawing_extension_list_namespaces,
"extLst",
cancelled~,
) catch {
InvalidXml(msg~) => raise InvalidXml(msg~)
ReadCancelled => raise ReadCancelled
}
match without_extension_lists {
Some(value) => charge_xlsx_drawing_xml_materialization(budget, value)
None => ()
}
let projected = match without_extension_lists {
Some(extension_free) => {
charge_xlsx_drawing_xml_pass(budget, extension_free)
@ooxml.project_xml_markup_compatibility(
extension_free,
xlsx_drawing_mce_understood_namespaces,
max_output_chars~,
cancelled~,
extension_elements=xlsx_drawing_mce_extension_elements,
) catch {
InvalidXml(msg~) => raise InvalidXml(msg~)
ReadCancelled => raise ReadCancelled
}
}
None => {
charge_xlsx_drawing_xml_pass(budget, source)
@ooxml.project_xml_markup_compatibility(
source,
xlsx_drawing_mce_understood_namespaces,
max_output_chars~,
cancelled~,
extension_elements=xlsx_drawing_mce_extension_elements,
) catch {
InvalidXml(msg~) => raise InvalidXml(msg~)
ReadCancelled => raise ReadCancelled
}
}
}
charge_xlsx_drawing_xml_materialization(budget, projected)
// Foreign wrappers are removed with their descendants. Merely renaming a
// wrapper would leave nested core-looking tags visible to lexical readers.
charge_xlsx_drawing_xml_pass(budget, projected)
let core_projected = @ooxml.remove_xml_foreign_namespace_subtrees(
projected,
xlsx_drawing_core_element_namespaces,
cancelled~,
) catch {
InvalidXml(msg~) => raise InvalidXml(msg~)
ReadCancelled => raise ReadCancelled
}
match core_projected {
Some(value) => charge_xlsx_drawing_xml_materialization(budget, value)
None => ()
}
let reader_source = match core_projected {
Some(value) => value
None => projected
}
charge_xlsx_drawing_xml_pass(budget, reader_source)
validate_xlsx_drawing_reader_structure(reader_source, cancelled~)
charge_xlsx_drawing_xml_pass(budget, reader_source)
let canonical = @ooxml.canonicalize_xml_expanded_names(
reader_source,
[],
[
(transitional_relationship_attribute_namespace, "r"),
(strict_relationship_attribute_namespace, "r"),
],
max_output_chars~,
cancelled~,
element_prefixes=[
(transitional_spreadsheet_drawing_namespace, "xdr"),
(strict_spreadsheet_drawing_namespace, "xdr"),
(transitional_drawing_main_namespace, "a"),
(strict_drawing_main_namespace, "a"),
(transitional_drawing_chart_namespace, "c"),
(strict_drawing_chart_namespace, "c"),
(drawing_slicer_namespace, "sle"),
(markup_compatibility_namespace, "mc"),
],
) catch {
InvalidXml(msg~) => raise InvalidXml(msg~)
ReadCancelled => raise ReadCancelled
}
charge_xlsx_drawing_xml_materialization(budget, canonical)
canonical
}
///|
fn validate_xlsx_worksheet_core(
xml : StringView,
cancelled? : () -> Bool = () => false,
) -> Unit raise XlsxError {
let scanner = @ooxml.XmlStartTagScanner::new(xml, cancelled~)
let mut worksheet_namespace : String? = None
let mut sheet_data_seen = false
let mut cell_formula_seen = false
let mut cell_value_seen = false
let mut cell_inline_string_seen = false
while workbook_scanner_next(scanner) {
let local_name = scanner.local_name()
let namespace_uri = scanner.namespace_uri()
if scanner.depth() == 1 {
if local_name != "worksheet" ||
(
namespace_uri != transitional_spreadsheet_namespace &&
namespace_uri != strict_spreadsheet_namespace
) {
raise InvalidXml(msg="worksheet document element is invalid")
}
worksheet_namespace = Some(namespace_uri.to_owned())
continue
}
let dialect = worksheet_namespace.unwrap_or("")
if local_name == "sheetData" {
if namespace_uri != dialect ||
scanner.depth() != 2 ||
scanner.parent_local_name() != Some("worksheet") ||
scanner.parent_namespace_uri() != worksheet_namespace {
raise InvalidXml(msg="worksheet sheetData element is misplaced")
}
if sheet_data_seen {
raise InvalidXml(msg="worksheet has multiple sheetData elements")
}
sheet_data_seen = true
} else if local_name == "row" {
if namespace_uri != dialect ||
scanner.depth() != 3 ||
scanner.parent_local_name() != Some("sheetData") ||
scanner.parent_namespace_uri() != worksheet_namespace {
raise InvalidXml(msg="worksheet row element is misplaced")
}
} else if local_name == "c" {
if namespace_uri != dialect ||
scanner.depth() != 4 ||
scanner.parent_local_name() != Some("row") ||
scanner.parent_namespace_uri() != worksheet_namespace {
raise InvalidXml(msg="worksheet cell element is misplaced")
}
cell_formula_seen = false
cell_value_seen = false
cell_inline_string_seen = false
} else if local_name == "f" || local_name == "v" || local_name == "is" {
if namespace_uri != dialect ||
scanner.depth() != 5 ||
scanner.parent_local_name() != Some("c") ||
scanner.parent_namespace_uri() != worksheet_namespace {
raise InvalidXml(msg="worksheet cell payload element is misplaced")
}
if local_name == "f" {
if cell_formula_seen {
raise InvalidXml(msg="worksheet cell has multiple formula elements")
}
cell_formula_seen = true
} else if local_name == "v" {
if cell_value_seen {
raise InvalidXml(msg="worksheet cell has multiple value elements")
}
cell_value_seen = true
} else {
if cell_inline_string_seen {
raise InvalidXml(
msg="worksheet cell has multiple inline string elements",
)
}
cell_inline_string_seen = true
}
} else if local_name == "r" {
if namespace_uri != dialect ||
scanner.depth() != 6 ||
scanner.parent_local_name() != Some("is") ||
scanner.parent_namespace_uri() != worksheet_namespace {
raise InvalidXml(msg="worksheet inline string run is misplaced")
}
} else if local_name == "t" {
let valid_parent = scanner.parent_local_name() == Some("is") ||
scanner.parent_local_name() == Some("r") ||
scanner.parent_local_name() == Some("rPh")
if namespace_uri != dialect ||
!valid_parent ||
scanner.parent_namespace_uri() != worksheet_namespace ||
(scanner.depth() != 6 && scanner.depth() != 7) {
raise InvalidXml(msg="worksheet inline string text is misplaced")
}
} else if local_name == "cols" {
if namespace_uri != dialect ||
scanner.depth() != 2 ||
scanner.parent_local_name() != Some("worksheet") ||
scanner.parent_namespace_uri() != worksheet_namespace {
raise InvalidXml(msg="worksheet cols element is misplaced")
}
} else if local_name == "col" {
if namespace_uri != dialect ||
scanner.depth() != 3 ||
scanner.parent_local_name() != Some("cols") ||
scanner.parent_namespace_uri() != worksheet_namespace {
raise InvalidXml(msg="worksheet col element is misplaced")
}
}
}
}
///|
/// Produces the namespace-oblivious view used by core worksheet parsers.
/// Extension payload stays available in the full canonical view, but is
/// removed before core elements are validated and flattened.
fn canonicalize_xlsx_worksheet_core(
source : StringView,
full_canonical : String,
max_output_chars : Int,
cancelled? : () -> Bool = () => false,
) -> String raise XlsxError {
let without_extensions = @ooxml.remove_xml_expanded_name_subtrees(
source,
[transitional_spreadsheet_namespace, strict_spreadsheet_namespace],
"extLst",
cancelled~,
) catch {
InvalidXml(msg~) => raise InvalidXml(msg~)
ReadCancelled => raise ReadCancelled
}
match without_extensions {
Some(core_source) =>
match
xlsx_core_source_without_foreign(core_source, "worksheet", cancelled~) {
Some(filtered) => {
validate_xlsx_worksheet_core(filtered, cancelled~)
canonicalize_xlsx_xml(filtered, max_output_chars~, cancelled~)
}
None => {
validate_xlsx_worksheet_core(core_source, cancelled~)
canonicalize_xlsx_xml(core_source, max_output_chars~, cancelled~)
}
}
None =>
match xlsx_core_source_without_foreign(source, "worksheet", cancelled~) {
Some(filtered) => {
validate_xlsx_worksheet_core(filtered, cancelled~)
canonicalize_xlsx_xml(filtered, max_output_chars~, cancelled~)
}
None => {
validate_xlsx_worksheet_core(source, cancelled~)
full_canonical
}
}
}
}
///|
test "XLSX XML canonicalization handles Strict elements and relationship attributes" {
let source =
#|ok
inspect(
canonicalize_xlsx_xml(source),
content=(
#|ok
),
)
}
///|
test "SpreadsheetML MCE fallback is projected before core filtering" {
let workbook_source =
#|
let workbook_projected = project_xlsx_markup_compatibility(
workbook_source, default_max_xml_part_bytes,
)
let sheets = parse_workbook_sheets(workbook_projected, 10)
assert_eq(sheets.length(), 1)
assert_eq(sheets[0].name, "Data")
assert_eq(sheets[0].rel_id, "rId1")
let worksheet_source =
#|kept
let worksheet_projected = project_xlsx_markup_compatibility(
worksheet_source, default_max_xml_part_bytes,
)
let worksheet_full = canonicalize_xlsx_xml(worksheet_projected)
let worksheet_core = canonicalize_xlsx_worksheet_core(
worksheet_projected, worksheet_full, default_max_xml_part_bytes,
)
assert_true(worksheet_core.contains(""))
assert_true(worksheet_core.contains("kept"))
assert_false(worksheet_core.contains("futureData"))
}
///|
test "XLSX XML canonicalization follows extension namespace identity" {
let source =
#|A1
let canonical = canonicalize_xlsx_xml(source)
assert_true(canonical.contains(""))
assert_true(canonical.contains("A1"))
assert_true(canonical.contains("<_foreign_x14_dataValidations/>"))
}
///|
test "DrawingML canonicalization follows aliases and removes foreign subtrees" {
let valid =
#|000
0
let canonical = canonicalize_xlsx_drawing_xml(
valid, default_max_xml_part_bytes,
)
assert_true(canonical.contains(""))
assert_true(canonical.contains(""))
assert_true(canonical.contains(""))
assert_true(canonical.contains(""))
assert_false(canonical.contains("
let spoofed_canonical = canonicalize_xlsx_drawing_xml(
spoofed, default_max_xml_part_bytes,
)
assert_false(spoofed_canonical.contains("oneCellAnchor"))
assert_false(spoofed_canonical.contains("AlternateContent"))
assert_false(spoofed_canonical.contains("0000",
"drawing anchor children are invalid",
),
(
"0000",
"drawing marker children are invalid",
),
(
"0000",
"drawing anchor children are invalid",
),
]
for case in cases {
let (source, expected) = case
try
canonicalize_xlsx_drawing_xml(source, default_max_xml_part_bytes)
catch {
InvalidXml(msg~) => inspect(msg, content=expected)
_ => fail("unexpected drawing grammar error")
} noraise {
_ => fail("expected drawing grammar rejection")
}
}
}
///|
test "XLSX XML canonicalization propagates cancellation from a long pass" {
let checks = [0]
let source = "" +
"x".repeat(32 * 1024) +
""
try
canonicalize_xlsx_xml(source, cancelled=() => {
checks[0] += 1
checks[0] >= 12
})
catch {
ReadCancelled => assert_true(checks[0] >= 12)
_ => fail("expected XLSX canonicalization cancellation")
} noraise {
_ => fail("expected XLSX canonicalization cancellation")
}
}