///|
/// Emit the shared `` + opening `` preamble.
fn write_rels_open(sb : LimitedXmlBuilder) -> Unit raise XlsxError {
sb.write_view("\n")
sb.write_view(
"\n",
)
}
///|
/// Emit one `` element (two-space indented, newline-terminated).
/// `external` adds `TargetMode="External"`. Centralizing the element format
/// keeps every rels writer byte-identical.
fn write_relationship_with_id(
sb : LimitedXmlBuilder,
rel_id : StringView,
rel_type : StringView,
target : StringView,
external? : Bool = false,
) -> Unit raise XlsxError {
sb.write_view(" \n")
} else {
sb.write_view("\"/>\n")
}
}
///|
fn write_relationship(
sb : LimitedXmlBuilder,
rel_id : Int,
rel_type : String,
target : String,
external? : Bool = false,
) -> Unit raise XlsxError {
write_relationship_with_id(sb, "rId\{rel_id}", rel_type, target, external~)
}
///|
fn write_sheet_rels_xml(
drawing_target : String?,
table_targets : ArrayView[String],
pivot_table_targets : ArrayView[String],
slicer_targets : ArrayView[String],
hyperlink_targets : ArrayView[String],
comment_targets : ArrayView[String],
vml_targets : ArrayView[String],
image_targets : ArrayView[String],
budget? : WritePartBudget,
) -> String raise XlsxError {
let sb = LimitedXmlBuilder::new(budget)
write_rels_open(sb)
let mut rel_id = 1
match drawing_target {
Some(target) => {
write_relationship(sb, rel_id, rel_drawing, target)
rel_id = rel_id + 1
}
None => ()
}
for target in table_targets {
write_relationship(sb, rel_id, rel_table, target)
rel_id = rel_id + 1
}
for target in pivot_table_targets {
write_relationship(sb, rel_id, rel_pivot_table, target)
rel_id = rel_id + 1
}
for target in slicer_targets {
write_relationship(sb, rel_id, rel_slicer, target)
rel_id = rel_id + 1
}
for target in hyperlink_targets {
write_relationship(sb, rel_id, rel_hyperlink, target, external=true)
rel_id = rel_id + 1
}
for target in comment_targets {
write_relationship(sb, rel_id, rel_comments, target)
rel_id = rel_id + 1
}
for target in vml_targets {
write_relationship(sb, rel_id, rel_vml, target)
rel_id = rel_id + 1
}
for target in image_targets {
write_relationship(sb, rel_id, rel_image, target)
rel_id = rel_id + 1
}
sb.write_view("")
sb.to_string()
}
///|
fn write_pivot_table_rels_xml(
cache_id : Int,
budget? : WritePartBudget,
) -> String raise XlsxError {
let sb = LimitedXmlBuilder::new(budget)
write_rels_open(sb)
write_relationship(
sb,
1,
rel_pivot_cache,
"../pivotCache/pivotCacheDefinition\{cache_id}.xml",
)
sb.write_view("")
sb.to_string()
}
///|
fn write_pivot_cache_rels_xml(
cache_id : Int,
budget? : WritePartBudget,
) -> String raise XlsxError {
let sb = LimitedXmlBuilder::new(budget)
write_rels_open(sb)
write_relationship(
sb,
1,
rel_pivot_cache_records,
"pivotCacheRecords\{cache_id}.xml",
)
sb.write_view("")
sb.to_string()
}
///|
fn write_drawing_rels_xml(
image_targets : Array[(Int, String)],
chart_targets : Array[(Int, String)],
hyperlink_targets : Array[DrawingHyperlink],
preserved_relationships? : ArrayView[PreservedDrawingRelationship] = [],
budget? : WritePartBudget,
) -> String raise XlsxError {
let sb = LimitedXmlBuilder::new(budget)
write_rels_open(sb)
for entry in image_targets {
let (rel_id, target) = entry
write_relationship(sb, rel_id, rel_image, target)
}
for entry in chart_targets {
let (rel_id, target) = entry
write_relationship(sb, rel_id, rel_chart, target)
}
for entry in hyperlink_targets {
write_relationship(
sb,
entry.rel_id,
rel_hyperlink,
entry.target,
external=entry.link_type == External,
)
}
for relationship in preserved_relationships {
write_relationship_with_id(
sb,
relationship.id,
relationship.rel_type,
relationship.target,
external=relationship.external,
)
}
sb.write_view("")
sb.to_string()
}
///|
fn write_preserved_drawing_rels_xml(
relationships : ArrayView[PreservedDrawingRelationship],
budget? : WritePartBudget,
) -> String raise XlsxError {
let sb = LimitedXmlBuilder::new(budget)
write_rels_open(sb)
for relationship in relationships {
write_relationship_with_id(
sb,
relationship.id,
relationship.rel_type,
relationship.target,
external=relationship.external,
)
}
sb.write_view("")
sb.to_string()
}
///|
fn write_vml_rels_xml(
image_targets : Array[String],
budget? : WritePartBudget,
) -> String raise XlsxError {
let sb = LimitedXmlBuilder::new(budget)
write_rels_open(sb)
let mut rel_id = 1
for target in image_targets {
write_relationship(sb, rel_id, rel_image, target)
rel_id = rel_id + 1
}
sb.write_view("")
sb.to_string()
}
///|
test "write rels xml: sheet relationships include expected relationship types" {
let xml = write_sheet_rels_xml(
Some("../drawings/drawing7.xml"),
["../tables/table2.xml"],
["../pivotTables/pivotTable5.xml"],
["../slicers/slicer4.xml"],
["https://example.com"],
["../comments1.xml"],
["../drawings/vmlDrawing3.vml"],
["../media/image8.png"],
)
inspect(
xml.contains(
"Id=\"rId1\" Type=\"\{rel_drawing}\" Target=\"../drawings/drawing7.xml\"",
),
content="true",
)
inspect(
xml.contains(
"Id=\"rId2\" Type=\"\{rel_table}\" Target=\"../tables/table2.xml\"",
),
content="true",
)
inspect(
xml.contains(
"Id=\"rId3\" Type=\"\{rel_pivot_table}\" Target=\"../pivotTables/pivotTable5.xml\"",
),
content="true",
)
inspect(
xml.contains(
"Id=\"rId4\" Type=\"\{rel_slicer}\" Target=\"../slicers/slicer4.xml\"",
),
content="true",
)
inspect(
xml.contains(
"Id=\"rId5\" Type=\"\{rel_hyperlink}\" Target=\"https://example.com\" TargetMode=\"External\"",
),
content="true",
)
inspect(
xml.contains(
"Id=\"rId6\" Type=\"\{rel_comments}\" Target=\"../comments1.xml\"",
),
content="true",
)
inspect(
xml.contains(
"Id=\"rId7\" Type=\"\{rel_vml}\" Target=\"../drawings/vmlDrawing3.vml\"",
),
content="true",
)
inspect(
xml.contains(
"Id=\"rId8\" Type=\"\{rel_image}\" Target=\"../media/image8.png\"",
),
content="true",
)
}
///|
test "write rels xml: pivot table and cache rels point to cache parts" {
let pivot_xml = write_pivot_table_rels_xml(12)
inspect(
pivot_xml.contains(
"Type=\"\{rel_pivot_cache}\" Target=\"../pivotCache/pivotCacheDefinition12.xml\"",
),
content="true",
)
let cache_xml = write_pivot_cache_rels_xml(12)
inspect(
cache_xml.contains(
"Type=\"\{rel_pivot_cache_records}\" Target=\"pivotCacheRecords12.xml\"",
),
content="true",
)
}
///|
test "write rels xml: drawing relationships preserve hyperlink target modes" {
let xml = write_drawing_rels_xml(
[(2, "../media/image1.png")],
[(3, "../charts/chart1.xml")],
[
{ rel_id: 4, target: "https://example.com", link_type: External },
{ rel_id: 5, target: "Sheet1!A1", link_type: Location },
],
)
inspect(
xml.contains(
"Id=\"rId2\" Type=\"\{rel_image}\" Target=\"../media/image1.png\"",
),
content="true",
)
inspect(
xml.contains(
"Id=\"rId3\" Type=\"\{rel_chart}\" Target=\"../charts/chart1.xml\"",
),
content="true",
)
inspect(
xml.contains(
"Id=\"rId4\" Type=\"\{rel_hyperlink}\" Target=\"https://example.com\" TargetMode=\"External\"",
),
content="true",
)
inspect(
xml.contains("Id=\"rId5\" Type=\"\{rel_hyperlink}\" Target=\"Sheet1!A1\"/>"),
content="true",
)
}
///|
test "write rels xml: vml rels enumerate image targets with incremental rIds" {
let xml = write_vml_rels_xml(["../media/image1.png", "../media/image2.png"])
inspect(
xml.contains(
"Id=\"rId1\" Type=\"\{rel_image}\" Target=\"../media/image1.png\"",
),
content="true",
)
inspect(
xml.contains(
"Id=\"rId2\" Type=\"\{rel_image}\" Target=\"../media/image2.png\"",
),
content="true",
)
}