///|
let root_rels_part_path = "_rels/.rels"
///|
let content_types_part_path = "[Content_Types].xml"
///|
let workbook_part_path = "xl/workbook.xml"
///|
let workbook_rels_part_path = "xl/_rels/workbook.xml.rels"
///|
let shared_strings_part_path = "xl/sharedStrings.xml"
///|
let calc_chain_part_path = "xl/calcChain.xml"
///|
let styles_part_path = "xl/styles.xml"
///|
let theme_part_path = "xl/theme/theme1.xml"
///|
let core_props_part_path = "docProps/core.xml"
///|
let app_props_part_path = "docProps/app.xml"
///|
let custom_props_part_path = "docProps/custom.xml"
///|
let vba_project_part_path = "xl/vbaProject.bin"
///|
fn chart_part_path(file_name : String) -> String {
"xl/charts/\{file_name}"
}
///|
fn worksheet_part_path(sheet_id : Int) -> String {
"xl/worksheets/sheet\{sheet_id}.xml"
}
///|
fn worksheet_rels_part_path(sheet_id : Int) -> String {
"xl/worksheets/_rels/sheet\{sheet_id}.xml.rels"
}
///|
fn drawing_part_path(drawing_id : Int) -> String {
"xl/drawings/drawing\{drawing_id}.xml"
}
///|
fn drawing_rels_part_path(drawing_id : Int) -> String {
"xl/drawings/_rels/drawing\{drawing_id}.xml.rels"
}
///|
fn chartsheet_part_path(sheet_id : Int) -> String {
"xl/chartsheets/sheet\{sheet_id}.xml"
}
///|
fn chartsheet_rels_part_path(sheet_id : Int) -> String {
"xl/chartsheets/_rels/sheet\{sheet_id}.xml.rels"
}
///|
fn table_part_path(table_id : Int) -> String {
"xl/tables/table\{table_id}.xml"
}
///|
fn pivot_table_part_path(pivot_id : Int) -> String {
"xl/pivotTables/pivotTable\{pivot_id}.xml"
}
///|
fn pivot_table_rels_part_path(pivot_id : Int) -> String {
"xl/pivotTables/_rels/pivotTable\{pivot_id}.xml.rels"
}
///|
fn pivot_cache_definition_part_path(cache_id : Int) -> String {
"xl/pivotCache/pivotCacheDefinition\{cache_id}.xml"
}
///|
fn pivot_cache_definition_rels_part_path(cache_id : Int) -> String {
"xl/pivotCache/_rels/pivotCacheDefinition\{cache_id}.xml.rels"
}
///|
fn pivot_cache_records_part_path(cache_id : Int) -> String {
"xl/pivotCache/pivotCacheRecords\{cache_id}.xml"
}
///|
fn slicer_part_path(part_id : Int) -> String {
"xl/slicers/slicer\{part_id}.xml"
}
///|
fn slicer_cache_part_path(cache_id : Int) -> String {
"xl/slicerCaches/slicerCache\{cache_id}.xml"
}
///|
fn comment_part_path(comment_id : Int) -> String {
"xl/comments\{comment_id}.xml"
}
///|
fn vml_part_path(vml_id : Int) -> String {
"xl/drawings/vmlDrawing\{vml_id}.vml"
}
///|
fn vml_rels_part_path(vml_id : Int) -> String {
"xl/drawings/_rels/vmlDrawing\{vml_id}.vml.rels"
}
///|
fn media_part_path(file_name : String) -> String {
"xl/media/\{file_name}"
}
///|
fn content_override_path(part_path : String) -> String {
"/\{part_path}"
}
///|
fn archive_path_from_part_name(
part_name : StringView,
) -> String raise XlsxError {
let part = part_name.to_owned()
if part == "" {
raise InvalidXml(msg="content type part name empty")
}
match part.strip_prefix("/") {
Some(rest) => {
let path = rest.to_owned()
if path == "" {
raise InvalidXml(msg="content type part name empty")
}
path
}
None => part
}
}
///|
fn rels_path_for_part(part_path : StringView) -> String raise XlsxError {
let part = part_path.to_owned()
if part == "" || part.has_prefix("/") {
raise InvalidXml(msg="part rels path invalid")
}
let segments = Array::from_iter(part.split("/"))
let file_name = segments[segments.length() - 1].to_owned()
if file_name == "" {
raise InvalidXml(msg="part rels path invalid")
}
if segments.length() == 1 {
return "_rels/\{file_name}.rels"
}
let folder_parts : Array[String] = []
for i, segment in segments {
if i + 1 < segments.length() {
folder_parts.push(segment.to_owned())
}
}
let folder = folder_parts.join("/")
"\{folder}/_rels/\{file_name}.rels"
}
///|
fn drawing_rel_target(sheet_id : Int) -> String {
"../drawings/drawing\{sheet_id}.xml"
}
///|
fn table_rel_target(table_id : Int) -> String {
"../tables/table\{table_id}.xml"
}
///|
fn pivot_table_rel_target(pivot_id : Int) -> String {
"../pivotTables/pivotTable\{pivot_id}.xml"
}
///|
fn slicer_rel_target(part_id : Int) -> String {
"../slicers/slicer\{part_id}.xml"
}
///|
fn comment_rel_target(comment_id : Int) -> String {
"../comments\{comment_id}.xml"
}
///|
fn vml_rel_target(vml_id : Int) -> String {
"../drawings/vmlDrawing\{vml_id}.vml"
}
///|
fn media_rel_target(file_name : String) -> String {
"../media/\{file_name}"
}
///|
fn chart_rel_target(file_name : String) -> String {
"../charts/\{file_name}"
}
///|
fn chartsheet_workbook_rel_target(sheet_id : Int) -> String {
"chartsheets/sheet\{sheet_id}.xml"
}
///|
fn slicer_cache_workbook_rel_target(cache_id : Int) -> String {
"slicerCaches/slicerCache\{cache_id}.xml"
}
///|
fn pivot_cache_workbook_rel_target(cache_id : Int) -> String {
"pivotCache/pivotCacheDefinition\{cache_id}.xml"
}
///|
test "write part paths: worksheet and drawing paths are stable" {
debug_inspect(
(worksheet_part_path(4), worksheet_rels_part_path(4)),
content="(\"xl/worksheets/sheet4.xml\", \"xl/worksheets/_rels/sheet4.xml.rels\")",
)
debug_inspect(
(drawing_part_path(3), drawing_rels_part_path(3)),
content="(\"xl/drawings/drawing3.xml\", \"xl/drawings/_rels/drawing3.xml.rels\")",
)
debug_inspect(
(chartsheet_part_path(2), chartsheet_rels_part_path(2)),
content="(\"xl/chartsheets/sheet2.xml\", \"xl/chartsheets/_rels/sheet2.xml.rels\")",
)
inspect(chart_part_path("chart7.xml"), content="xl/charts/chart7.xml")
}
///|
test "write part paths: pivot and slicer paths are stable" {
debug_inspect(
(
pivot_table_part_path(9),
pivot_table_rels_part_path(9),
pivot_cache_definition_part_path(9),
pivot_cache_definition_rels_part_path(9),
pivot_cache_records_part_path(9),
),
content=(
#|(
#| "xl/pivotTables/pivotTable9.xml",
#| "xl/pivotTables/_rels/pivotTable9.xml.rels",
#| "xl/pivotCache/pivotCacheDefinition9.xml",
#| "xl/pivotCache/_rels/pivotCacheDefinition9.xml.rels",
#| "xl/pivotCache/pivotCacheRecords9.xml",
#|)
),
)
debug_inspect(
(slicer_part_path(5), slicer_cache_part_path(5)),
content=(
#|("xl/slicers/slicer5.xml", "xl/slicerCaches/slicerCache5.xml")
),
)
}
///|
test "write part paths: static package part names and override path" {
debug_inspect(
(
root_rels_part_path, content_types_part_path, workbook_part_path, workbook_rels_part_path,
),
content=(
#|(
#| "_rels/.rels",
#| "[Content_Types].xml",
#| "xl/workbook.xml",
#| "xl/_rels/workbook.xml.rels",
#|)
),
)
debug_inspect(
(
shared_strings_part_path, calc_chain_part_path, styles_part_path, theme_part_path,
vba_project_part_path,
),
content=(
#|(
#| "xl/sharedStrings.xml",
#| "xl/calcChain.xml",
#| "xl/styles.xml",
#| "xl/theme/theme1.xml",
#| "xl/vbaProject.bin",
#|)
),
)
debug_inspect(
(
core_props_part_path,
app_props_part_path,
custom_props_part_path,
content_override_path(drawing_part_path(6)),
media_part_path("image3.png"),
),
content=(
#|(
#| "docProps/core.xml",
#| "docProps/app.xml",
#| "docProps/custom.xml",
#| "/xl/drawings/drawing6.xml",
#| "xl/media/image3.png",
#|)
),
)
}
///|
test "write part paths: read-side path helpers are stable" {
debug_inspect(
(
archive_path_from_part_name("/xl/workbook.xml"),
archive_path_from_part_name("xl/workbook.xml"),
rels_path_for_part("xl/workbook.xml"),
rels_path_for_part("workbook.xml"),
),
content=(
#|(
#| "xl/workbook.xml",
#| "xl/workbook.xml",
#| "xl/_rels/workbook.xml.rels",
#| "_rels/workbook.xml.rels",
#|)
),
)
}
///|
test "write part paths: invalid read-side path helpers raise InvalidXml" {
let empty_part_result = Ok(archive_path_from_part_name("")) catch {
e => Err(e)
}
match empty_part_result {
Err(InvalidXml(msg~)) =>
inspect(msg, content="content type part name empty")
_ => fail("expected content type part name empty")
}
let slash_part_result = Ok(archive_path_from_part_name("/")) catch {
e => Err(e)
}
match slash_part_result {
Err(InvalidXml(msg~)) =>
inspect(msg, content="content type part name empty")
_ => fail("expected content type part name empty for /")
}
let empty_rels_result = Ok(rels_path_for_part("")) catch { e => Err(e) }
match empty_rels_result {
Err(InvalidXml(msg~)) => inspect(msg, content="part rels path invalid")
_ => fail("expected part rels path invalid")
}
let slash_rels_result = Ok(rels_path_for_part("/xl/workbook.xml")) catch {
e => Err(e)
}
match slash_rels_result {
Err(InvalidXml(msg~)) => inspect(msg, content="part rels path invalid")
_ => fail("expected part rels path invalid for leading slash")
}
let trailing_slash_rels_result = Ok(rels_path_for_part("xl/")) catch {
e => Err(e)
}
match trailing_slash_rels_result {
Err(InvalidXml(msg~)) => inspect(msg, content="part rels path invalid")
_ => fail("expected part rels path invalid for trailing slash")
}
}
///|
test "write part paths: relationship targets are stable" {
debug_inspect(
(
drawing_rel_target(3),
table_rel_target(4),
pivot_table_rel_target(5),
slicer_rel_target(6),
comment_rel_target(7),
vml_rel_target(8),
media_rel_target("image1.png"),
chart_rel_target("chart9.xml"),
),
content=(
#|(
#| "../drawings/drawing3.xml",
#| "../tables/table4.xml",
#| "../pivotTables/pivotTable5.xml",
#| "../slicers/slicer6.xml",
#| "../comments7.xml",
#| "../drawings/vmlDrawing8.vml",
#| "../media/image1.png",
#| "../charts/chart9.xml",
#|)
),
)
debug_inspect(
(
chartsheet_workbook_rel_target(2),
slicer_cache_workbook_rel_target(3),
pivot_cache_workbook_rel_target(4),
),
content=(
#|(
#| "chartsheets/sheet2.xml",
#| "slicerCaches/slicerCache3.xml",
#| "pivotCache/pivotCacheDefinition4.xml",
#|)
),
)
}