///|
/// The remaining allowance for the next archive part. `maximum` is the tighter
/// of the per-entry ceiling and the aggregate archive space still available;
/// the other fields retain the policy-level error reported when it is crossed.
priv struct WritePartBudget {
maximum : Int
resource_kind : String
resource_limit : Int
}
///|
/// A UTF-8-byte-counted XML builder. The ceiling is checked before appending
/// each fragment, so generated XML cannot first grow past the remaining archive
/// allowance. Escaped text and attributes are streamed directly into this
/// builder instead of allocating an expanded intermediate string.
priv struct LimitedXmlBuilder {
builder : StringBuilder
budget : WritePartBudget?
mut encoded_bytes : Int
}
///|
fn LimitedXmlBuilder::new(budget : WritePartBudget?) -> LimitedXmlBuilder {
{ builder: StringBuilder::new(), budget, encoded_bytes: 0 }
}
///|
fn xml_utf8_length_within(value : StringView, maximum : Int) -> Int? {
let mut length = 0
for character in value {
let scalar = character.to_int()
let width = if scalar <= 0x7F {
1
} else if scalar <= 0x7FF {
2
} else if scalar <= 0xFFFF {
3
} else {
4
}
if width > maximum - length {
return None
}
length = length + width
}
Some(length)
}
///|
fn is_valid_xml_output_character(character : Char) -> Bool {
let scalar = character.to_int()
scalar == 0x09 ||
scalar == 0x0A ||
scalar == 0x0D ||
(scalar >= 0x20 && scalar <= 0xD7FF) ||
(scalar >= 0xE000 && scalar <= 0xFFFD) ||
(scalar >= 0x10000 && scalar <= 0x10FFFF)
}
///|
fn validate_xml_output(value : StringView) -> Unit raise XlsxError {
for character in value {
if !is_valid_xml_output_character(character) {
raise InvalidXml(
msg="generated XML contains forbidden code point \{character.to_int()}",
)
}
}
}
///|
fn LimitedXmlBuilder::write_view(
self : LimitedXmlBuilder,
value : StringView,
) -> Unit raise XlsxError {
validate_xml_output(value)
match self.budget {
Some(budget) => {
let remaining = budget.maximum - self.encoded_bytes
let fragment_bytes = match xml_utf8_length_within(value, remaining) {
Some(length) => length
None =>
raise ResourceLimitExceeded(
kind=budget.resource_kind,
limit=budget.resource_limit,
actual=bounded_actual_above_limit(budget.resource_limit),
)
}
self.builder.write_view(value)
self.encoded_bytes = self.encoded_bytes + fragment_bytes
}
None => self.builder.write_view(value)
}
}
///|
fn LimitedXmlBuilder::write_char(
self : LimitedXmlBuilder,
value : Char,
) -> Unit raise XlsxError {
if !is_valid_xml_output_character(value) {
raise InvalidXml(
msg="generated XML contains forbidden code point \{value.to_int()}",
)
}
let scalar = value.to_int()
let width = if scalar <= 0x7F {
1
} else if scalar <= 0x7FF {
2
} else if scalar <= 0xFFFF {
3
} else {
4
}
match self.budget {
Some(budget) if width > budget.maximum - self.encoded_bytes =>
raise ResourceLimitExceeded(
kind=budget.resource_kind,
limit=budget.resource_limit,
actual=bounded_actual_above_limit(budget.resource_limit),
)
_ => ()
}
self.builder.write_char(value)
self.encoded_bytes = self.encoded_bytes + width
}
///|
fn LimitedXmlBuilder::write_xml_text(
self : LimitedXmlBuilder,
value : StringView,
) -> Unit raise XlsxError {
let mut literal_start = 0
for index in 0.. Some("&")
unit if unit == ('<' : UInt16) => Some("<")
unit if unit == ('>' : UInt16) => Some(">")
_ => None
}
match escaped {
Some(entity) => {
self.write_view(value[literal_start:index])
self.write_view(entity)
literal_start = index + 1
}
None => ()
}
}
self.write_view(value[literal_start:])
}
///|
fn LimitedXmlBuilder::write_xml_attr(
self : LimitedXmlBuilder,
value : StringView,
) -> Unit raise XlsxError {
let mut literal_start = 0
for index in 0.. Some("&")
unit if unit == ('<' : UInt16) => Some("<")
unit if unit == ('>' : UInt16) => Some(">")
unit if unit == ('"' : UInt16) => Some(""")
unit if unit == ('\'' : UInt16) => Some("'")
_ => None
}
match escaped {
Some(entity) => {
self.write_view(value[literal_start:index])
self.write_view(entity)
literal_start = index + 1
}
None => ()
}
}
self.write_view(value[literal_start:])
}
///|
/// Write a `` text element, adding `xml:space="preserve"` when the text
/// carries leading/trailing spaces or embedded newlines that XML parsers
/// would otherwise be free to normalize away.
fn LimitedXmlBuilder::write_t_element(
self : LimitedXmlBuilder,
text : StringView,
) -> Unit raise XlsxError {
if text.has_prefix(" ") || text.has_suffix(" ") || text.contains("\n") {
self.write_view("")
} else {
self.write_view("")
}
self.write_xml_text(text)
self.write_view("")
}
///|
/// Write the `` element of a rich-text run font. Theme takes
/// precedence over indexed, which takes precedence over rgb; a zero tint is
/// omitted. Shared by the shared-strings and comments writers.
fn LimitedXmlBuilder::write_rich_text_color(
self : LimitedXmlBuilder,
font : RichTextFont,
) -> Unit raise XlsxError {
let tint = match font.color_tint {
Some(v) => if v == 0.0 { None } else { Some(v) }
None => None
}
match font.color_theme {
Some(value) => {
self.write_view(" self.write_view(" tint=\"\{v}\"")
None => ()
}
self.write_view("/>")
return
}
None => ()
}
match font.color_indexed {
Some(value) => {
self.write_view(" self.write_view(" tint=\"\{v}\"")
None => ()
}
self.write_view("/>")
return
}
None => ()
}
match font.color {
Some(color) => {
let normalized = color.replace_all(old="#", new="")
let rgb = if normalized.length() == 6 {
"FF" + normalized
} else {
normalized
}
self.write_view(" self.write_view(" tint=\"\{v}\"")
None => ()
}
self.write_view("/>")
}
None => ()
}
}
///|
/// Write one rich-text run body: the `...` font properties (when
/// present) followed by the run's `` element. The caller writes the
/// surrounding ``/`` markers so each emitter keeps its own indentation
/// and line-break layout.
fn LimitedXmlBuilder::write_rich_text_run(
self : LimitedXmlBuilder,
run : RichTextRun,
) -> Unit raise XlsxError {
match run.font {
Some(font) => {
self.write_view("")
if font.bold {
self.write_view("")
}
if font.italic {
self.write_view("")
}
if font.strike {
self.write_view("")
}
if font.outline {
self.write_view("")
}
if font.shadow {
self.write_view("")
}
if font.condense {
self.write_view("")
}
if font.extended {
self.write_view("")
}
match font.underline {
Some(value) => {
self.write_view("")
}
None => ()
}
match font.size {
Some(size) => self.write_view("")
None => ()
}
match font.charset {
Some(value) => self.write_view("")
None => ()
}
match font.family_number {
Some(value) => self.write_view("")
None => ()
}
match font.family {
Some(family) => {
self.write_view("")
}
None => ()
}
match font.color {
Some(_) => self.write_rich_text_color(font)
None =>
if font.color_theme is Some(_) ||
font.color_indexed is Some(_) ||
font.color_tint is Some(_) {
self.write_rich_text_color(font)
}
}
match font.vert_align {
Some(value) => {
self.write_view("")
}
None => ()
}
match font.scheme {
Some(value) => {
self.write_view("")
}
None => ()
}
self.write_view("")
}
None => ()
}
self.write_t_element(run.text)
}
///|
fn LimitedXmlBuilder::to_string(self : LimitedXmlBuilder) -> String {
self.builder.to_string()
}
///|
/// Returns the unused portion of this builder's policy for a nested generator.
/// Passing this budget downstream prevents a child fragment from allocating up
/// to the whole part ceiling after its parent has already emitted content.
fn LimitedXmlBuilder::remaining_budget(
self : LimitedXmlBuilder,
) -> WritePartBudget? {
match self.budget {
Some(budget) =>
Some({
maximum: budget.maximum - self.encoded_bytes,
resource_kind: budget.resource_kind,
resource_limit: budget.resource_limit,
})
None => None
}
}
///|
/// Tracks XML fragments that must be retained before their parent can append
/// them. Each fragment receives only the allowance left after earlier retained
/// fragments, preventing an array of individually valid fragments from growing
/// beyond the parent part's remaining budget.
priv struct WritePartBudgetTracker {
budget : WritePartBudget?
mut encoded_bytes : Int
}
///|
fn WritePartBudgetTracker::new(
budget : WritePartBudget?,
) -> WritePartBudgetTracker {
{ budget, encoded_bytes: 0 }
}
///|
fn WritePartBudgetTracker::remaining_budget(
self : WritePartBudgetTracker,
) -> WritePartBudget? {
match self.budget {
Some(budget) =>
Some({
maximum: budget.maximum - self.encoded_bytes,
resource_kind: budget.resource_kind,
resource_limit: budget.resource_limit,
})
None => None
}
}
///|
fn WritePartBudgetTracker::consume(
self : WritePartBudgetTracker,
value : StringView,
) -> Unit raise XlsxError {
match self.budget {
Some(budget) => {
let remaining = budget.maximum - self.encoded_bytes
let fragment_bytes = match xml_utf8_length_within(value, remaining) {
Some(length) => length
None =>
raise ResourceLimitExceeded(
kind=budget.resource_kind,
limit=budget.resource_limit,
actual=bounded_actual_above_limit(budget.resource_limit),
)
}
self.encoded_bytes = self.encoded_bytes + fragment_bytes
}
None => ()
}
}
///|
/// Parent allowance for a generated archive part that is assembled through
/// multiple simultaneously-retained strings. Every individual intermediate
/// remains subject to the per-entry ceiling, while their combined retained
/// bytes debit the aggregate space left after already-archived parts.
priv struct WriteCompositionBudgetTracker {
limits : WriteLimits?
archived_bytes : Int
mut retained_bytes : Int
}
///|
fn WriteCompositionBudgetTracker::remaining_budget(
self : WriteCompositionBudgetTracker,
) -> WritePartBudget? {
match self.limits {
Some(limits) => {
let aggregate_remaining = limits.max_total_uncompressed_bytes -
self.archived_bytes -
self.retained_bytes
if limits.max_entry_uncompressed_bytes <= aggregate_remaining {
Some({
maximum: limits.max_entry_uncompressed_bytes,
resource_kind: "entry_uncompressed_bytes",
resource_limit: limits.max_entry_uncompressed_bytes,
})
} else {
Some({
maximum: aggregate_remaining,
resource_kind: "total_uncompressed_bytes",
resource_limit: limits.max_total_uncompressed_bytes,
})
}
}
None => None
}
}
///|
fn WriteCompositionBudgetTracker::consume(
self : WriteCompositionBudgetTracker,
value : StringView,
) -> Unit raise XlsxError {
match self.remaining_budget() {
Some(budget) => {
let fragment_bytes = match xml_utf8_length_within(value, budget.maximum) {
Some(length) => length
None =>
raise ResourceLimitExceeded(
kind=budget.resource_kind,
limit=budget.resource_limit,
actual=bounded_actual_above_limit(budget.resource_limit),
)
}
self.retained_bytes = self.retained_bytes + fragment_bytes
}
None => ()
}
}
///|
fn write_workbook_protection_xml(
protection : WorkbookProtection,
budget? : WritePartBudget,
) -> String raise XlsxError {
let sb = LimitedXmlBuilder::new(budget)
sb.write_view(" 0 {
sb.write_view(" workbookSpinCount=\"\{protection.spin_count}\"")
}
sb.write_view("/>\n")
sb.to_string()
}
///|
fn write_shared_strings_xml(
values : Array[SharedStringEntry],
total_count : Int,
budget? : WritePartBudget,
) -> String raise XlsxError {
let sb = LimitedXmlBuilder::new(budget)
sb.write_view("\n")
sb.write_view(
"\n",
)
for entry in values {
match entry.runs {
Some(runs) => {
sb.write_view(" \n")
for run in runs {
sb.write_view(" ")
sb.write_rich_text_run(run)
sb.write_view("\n")
}
sb.write_view(" \n")
}
None => {
sb.write_view(" ")
sb.write_t_element(entry.text)
sb.write_view("\n")
}
}
}
sb.write_view("")
sb.to_string()
}
///|
fn collect_formula_cells(workbook : Workbook) -> Array[(Int, String)] {
let formula_cells : Array[(Int, String)] = []
for i, sheet in workbook.sheets() {
let sheet_id = i + 1
for cell in sheet.cells() {
match cell.formula {
Some(value) =>
if value != "" {
formula_cells.push((sheet_id, cell.reference))
}
None => ()
}
}
}
formula_cells
}
///|
fn write_calc_chain_xml(
cells : Array[(Int, String)],
budget? : WritePartBudget,
) -> String raise XlsxError {
let sb = LimitedXmlBuilder::new(budget)
sb.write_view("\n")
sb.write_view(
"\n",
)
for cell in cells {
let (sheet_id, reference) = cell
sb.write_view(" \n")
}
sb.write_view("")
sb.to_string()
}
///|
priv struct DrawingImage {
image : Image
rel_id : Int
hyperlink_rel_id : Int?
}
///|
priv struct DrawingChart {
reference : String
offset_x : Int
offset_y : Int
width_emu : Int
height_emu : Int
print_object : Bool
locked : Bool
positioning : PicturePositioning
rel_id : Int
drawing_offset_x_emu : Int?
drawing_offset_y_emu : Int?
drawing_width_emu : Int?
drawing_height_emu : Int?
drawing_order : Int?
}
///|
priv struct DrawingHyperlink {
rel_id : Int
target : String
link_type : HyperlinkType
}
///|
priv struct DrawingSlicer {
slicer : Slicer
is_table_slicer : Bool
}
///|
priv struct OrderedDrawingAnchor {
xml : String
order : Int?
fallback_order : Int
}
///|
priv struct ResolvedSlicerCache {
cache_id : Int
cache_name : String
source_name : String
item_desc : Bool
source : ResolvedSlicerCacheSource
}
///|
priv enum ResolvedSlicerCacheSource {
Table(Int, Int)
Pivot(Int, Int, String)
}
///|
let emu_per_pixel = 9525
///|
fn drawing_int64_to_int_for_write(
value : Int64,
field : StringView,
) -> Int raise XlsxError {
let min_int = -0x7fff_ffffL - 1L
if value < min_int || value > 0x7fff_ffffL {
raise InvalidSheetOperation(msg=field.to_owned() + " out of range")
}
value.to_int()
}
///|
fn drawing_pixel_offset_for_write(
value : Int,
field : StringView,
) -> Int raise XlsxError {
drawing_int64_to_int_for_write(
Int64::from_int(value) * Int64::from_int(emu_per_pixel),
field,
)
}
///|
fn drawing_extent_for_write(
value : Int,
field : StringView,
) -> Int raise XlsxError {
if value < 0 {
raise InvalidSheetOperation(msg=field.to_owned() + " must be >= 0")
}
value
}
///|
fn drawing_scaled_extent_for_write(
pixels : Double,
scale : Double,
field : StringView,
) -> Int raise XlsxError {
let value = (pixels * scale * emu_per_pixel.to_double()).round()
if Double::is_nan(value) ||
Double::is_inf(value) ||
value < 0.0 ||
value > 2147483647.0 {
raise InvalidSheetOperation(msg=field.to_owned() + " out of range")
}
value.to_int()
}
///|
fn drawing_axis_boundary_emu_for_write(
metrics : DrawingAxisMetrics,
idx0 : Int,
) -> Int64 raise XlsxError {
let pixels = metrics.pixel_span(0, idx0)
let factor = Int64::from_int(emu_per_pixel)
let max_pixels = (0x7fff_ffff_ffff_ffffL - 0xffff_ffffL) / factor
if pixels > max_pixels {
raise InvalidSheetOperation(msg="drawing grid geometry out of range")
}
pixels * factor
}
///|
fn two_cell_axis_to(
metrics : DrawingAxisMetrics,
start_idx0 : Int,
start_off_emu : Int,
size_emu : Int,
) -> (Int, Int) raise XlsxError {
if start_idx0 < 0 || start_idx0 >= metrics.limit {
raise InvalidSheetOperation(msg="drawing " + metrics.axis + " invalid")
}
ignore(drawing_extent_for_write(size_emu, "drawing extent"))
let start = drawing_axis_boundary_emu_for_write(metrics, start_idx0) +
Int64::from_int(start_off_emu)
let target = start + Int64::from_int(size_emu)
let grid_end = drawing_axis_boundary_emu_for_write(metrics, metrics.limit)
if target > grid_end {
raise InvalidSheetOperation(msg="drawing extends beyond worksheet grid")
}
if target < 0L {
return (0, drawing_int64_to_int_for_write(target, "drawing marker offset"))
}
if target == grid_end {
return (metrics.limit, 0)
}
let mut low = 0
let mut high = metrics.limit + 1
while low < high {
let mid = low + (high - low) / 2
let boundary = drawing_axis_boundary_emu_for_write(metrics, mid)
if boundary <= target {
low = mid + 1
} else {
high = mid
}
}
let idx0 = low - 1
let boundary = drawing_axis_boundary_emu_for_write(metrics, idx0)
(
idx0,
drawing_int64_to_int_for_write(target - boundary, "drawing marker offset"),
)
}
///|
fn write_drawing_open(sb : LimitedXmlBuilder) -> Unit raise XlsxError {
sb.write_view("\n")
sb.write_view(
"\n",
)
}
///|
fn allocate_drawing_numeric_id(
next : Int,
reserved : Set[String],
) -> (Int, Int) raise XlsxError {
let mut candidate = next
while reserved.contains("\{candidate}") {
if candidate == 0x7fff_ffff {
raise InvalidSheetOperation(msg="drawing object id exhausted")
}
candidate = candidate + 1
}
if candidate == 0x7fff_ffff {
raise InvalidSheetOperation(msg="drawing object id exhausted")
}
(candidate, candidate + 1)
}
///|
fn allocate_drawing_relationship_id(
next : Int,
reserved : Set[String],
) -> (Int, Int) raise XlsxError {
let mut candidate = next
while reserved.contains("rId\{candidate}") {
if candidate == 0x7fff_ffff {
raise InvalidSheetOperation(msg="drawing relationship id exhausted")
}
candidate = candidate + 1
}
if candidate == 0x7fff_ffff {
raise InvalidSheetOperation(msg="drawing relationship id exhausted")
}
(candidate, candidate + 1)
}
///|
fn compare_ordered_drawing_anchor(
left : OrderedDrawingAnchor,
right : OrderedDrawingAnchor,
) -> Int {
match (left.order, right.order) {
(Some(a), Some(b)) =>
if a < b {
-1
} else if a > b {
1
} else if left.fallback_order < right.fallback_order {
-1
} else if left.fallback_order > right.fallback_order {
1
} else {
0
}
(Some(_), None) => -1
(None, Some(_)) => 1
(None, None) =>
if left.fallback_order < right.fallback_order {
-1
} else if left.fallback_order > right.fallback_order {
1
} else {
0
}
}
}
///|
fn write_drawing_xml(
sheet : Worksheet?,
images : Array[DrawingImage],
charts : Array[DrawingChart],
shapes : Array[Shape],
slicers : Array[DrawingSlicer],
preserved_anchors? : ArrayView[PreservedDrawingAnchor] = [],
reserved_object_ids? : ArrayView[String] = [],
budget? : WritePartBudget,
) -> String raise XlsxError {
let drawing_metrics = match sheet {
Some(value) => drawing_metrics_for_sheet(value)
None => drawing_metrics_for_sheet(Worksheet::new("Drawing"))
}
let sb = LimitedXmlBuilder::new(budget)
write_drawing_open(sb)
let reserved_object_id_set : Set[String] = Set([])
for id in reserved_object_ids {
reserved_object_id_set.add(id)
}
let mut obj_id = 1
for item in images {
let image = item.image
let (row, col) = cell_ref_to_rc(image.reference)
let col_idx = col - 1
let row_idx = row - 1
let (pic_id, next_obj_id) = allocate_drawing_numeric_id(
obj_id, reserved_object_id_set,
)
obj_id = next_obj_id
let col_off = match image.drawing_offset_x_emu {
Some(value) => value
None =>
drawing_pixel_offset_for_write(
image.offset_x,
"drawing image column offset",
)
}
let row_off = match image.drawing_offset_y_emu {
Some(value) => value
None =>
drawing_pixel_offset_for_write(
image.offset_y,
"drawing image row offset",
)
}
let cx = drawing_extent_for_write(
match image.drawing_width_emu {
Some(value) => value
None => image.width_emu
},
"drawing image width",
)
let cy = drawing_extent_for_write(
match image.drawing_height_emu {
Some(value) => value
None => image.height_emu
},
"drawing image height",
)
let picture_name = if image.name == "" {
"Image \{pic_id}"
} else {
image.name
}
match image.positioning {
TwoCell => {
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \{col_idx}\n")
sb.write_view(" \{col_off}\n")
sb.write_view(" \{row_idx}\n")
sb.write_view(" \{row_off}\n")
sb.write_view(" \n")
let (to_col_idx, to_col_off) = two_cell_axis_to(
drawing_metrics.cols,
col_idx,
col_off,
cx,
)
let (to_row_idx, to_row_off) = two_cell_axis_to(
drawing_metrics.rows,
row_idx,
row_off,
cy,
)
sb.write_view(" \n")
sb.write_view(" \{to_col_idx}\n")
sb.write_view(" \{to_col_off}\n")
sb.write_view(" \{to_row_idx}\n")
sb.write_view(" \{to_row_off}\n")
sb.write_view(" \n")
}
Absolute => {
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \{col_idx}\n")
sb.write_view(" \{col_off}\n")
sb.write_view(" \{row_idx}\n")
sb.write_view(" \{row_off}\n")
sb.write_view(" \n")
let (to_col_idx, to_col_off) = two_cell_axis_to(
drawing_metrics.cols,
col_idx,
col_off,
cx,
)
let (to_row_idx, to_row_off) = two_cell_axis_to(
drawing_metrics.rows,
row_idx,
row_off,
cy,
)
sb.write_view(" \n")
sb.write_view(" \{to_col_idx}\n")
sb.write_view(" \{to_col_off}\n")
sb.write_view(" \{to_row_idx}\n")
sb.write_view(" \{to_row_off}\n")
sb.write_view(" \n")
}
OneCell => {
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \{col_idx}\n")
sb.write_view(" \{col_off}\n")
sb.write_view(" \{row_idx}\n")
sb.write_view(" \{row_off}\n")
sb.write_view(" \n")
sb.write_view(" \n")
}
}
sb.write_view(" \n")
sb.write_view(" \n")
match item.hyperlink_rel_id {
Some(link_id) => {
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
}
None => {
sb.write_view(" \n")
}
}
if image.lock_aspect_ratio {
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
} else {
sb.write_view(" \n")
}
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(
" \n",
)
match image.positioning {
TwoCell => sb.write_view(" \n")
Absolute => sb.write_view(" \n")
OneCell => sb.write_view(" \n")
}
}
for chart in charts {
let (row, col) = cell_ref_to_rc(chart.reference)
let col_idx = col - 1
let row_idx = row - 1
let (frame_id, next_obj_id) = allocate_drawing_numeric_id(
obj_id, reserved_object_id_set,
)
obj_id = next_obj_id
let col_off = match chart.drawing_offset_x_emu {
Some(value) => value
None =>
drawing_pixel_offset_for_write(
chart.offset_x,
"drawing chart column offset",
)
}
let row_off = match chart.drawing_offset_y_emu {
Some(value) => value
None =>
drawing_pixel_offset_for_write(
chart.offset_y,
"drawing chart row offset",
)
}
let cx = drawing_extent_for_write(
match chart.drawing_width_emu {
Some(value) => value
None => chart.width_emu
},
"drawing chart width",
)
let cy = drawing_extent_for_write(
match chart.drawing_height_emu {
Some(value) => value
None => chart.height_emu
},
"drawing chart height",
)
match chart.positioning {
TwoCell => {
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \{col_idx}\n")
sb.write_view(" \{col_off}\n")
sb.write_view(" \{row_idx}\n")
sb.write_view(" \{row_off}\n")
sb.write_view(" \n")
let (to_col_idx, to_col_off) = two_cell_axis_to(
drawing_metrics.cols,
col_idx,
col_off,
cx,
)
let (to_row_idx, to_row_off) = two_cell_axis_to(
drawing_metrics.rows,
row_idx,
row_off,
cy,
)
sb.write_view(" \n")
sb.write_view(" \{to_col_idx}\n")
sb.write_view(" \{to_col_off}\n")
sb.write_view(" \{to_row_idx}\n")
sb.write_view(" \{to_row_off}\n")
sb.write_view(" \n")
}
Absolute => {
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \{col_idx}\n")
sb.write_view(" \{col_off}\n")
sb.write_view(" \{row_idx}\n")
sb.write_view(" \{row_off}\n")
sb.write_view(" \n")
let (to_col_idx, to_col_off) = two_cell_axis_to(
drawing_metrics.cols,
col_idx,
col_off,
cx,
)
let (to_row_idx, to_row_off) = two_cell_axis_to(
drawing_metrics.rows,
row_idx,
row_off,
cy,
)
sb.write_view(" \n")
sb.write_view(" \{to_col_idx}\n")
sb.write_view(" \{to_col_off}\n")
sb.write_view(" \{to_row_idx}\n")
sb.write_view(" \{to_row_off}\n")
sb.write_view(" \n")
}
OneCell => {
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \{col_idx}\n")
sb.write_view(" \{col_off}\n")
sb.write_view(" \{row_idx}\n")
sb.write_view(" \{row_off}\n")
sb.write_view(" \n")
sb.write_view(" \n")
}
}
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(
" \n",
)
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(
" \n",
)
sb.write_view(
" \n",
)
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(
" \n",
)
match chart.positioning {
TwoCell => sb.write_view(" \n")
Absolute => sb.write_view(" \n")
OneCell => sb.write_view(" \n")
}
}
for shape in shapes {
let (row, col) = cell_ref_to_rc(shape.reference)
let col_idx = col - 1
let row_idx = row - 1
let offset_x = shape.format.offset_x.unwrap_or(0)
let offset_y = shape.format.offset_y.unwrap_or(0)
let col_off = match shape.drawing_offset_x_emu {
Some(value) => value
None =>
drawing_pixel_offset_for_write(offset_x, "drawing shape column offset")
}
let row_off = match shape.drawing_offset_y_emu {
Some(value) => value
None =>
drawing_pixel_offset_for_write(offset_y, "drawing shape row offset")
}
let (shape_id, next_obj_id) = allocate_drawing_numeric_id(
obj_id, reserved_object_id_set,
)
obj_id = next_obj_id
let cx = match shape.drawing_width_emu {
Some(value) => drawing_extent_for_write(value, "drawing shape width")
None =>
drawing_scaled_extent_for_write(
shape.width.to_double(),
shape.scale_x,
"drawing shape width",
)
}
let cy = match shape.drawing_height_emu {
Some(value) => drawing_extent_for_write(value, "drawing shape height")
None =>
drawing_scaled_extent_for_write(
shape.height.to_double(),
shape.scale_y,
"drawing shape height",
)
}
match shape.positioning {
TwoCell => {
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \{col_idx}\n")
sb.write_view(" \{col_off}\n")
sb.write_view(" \{row_idx}\n")
sb.write_view(" \{row_off}\n")
sb.write_view(" \n")
let (to_col_idx, to_col_off) = two_cell_axis_to(
drawing_metrics.cols,
col_idx,
col_off,
cx,
)
let (to_row_idx, to_row_off) = two_cell_axis_to(
drawing_metrics.rows,
row_idx,
row_off,
cy,
)
sb.write_view(" \n")
sb.write_view(" \{to_col_idx}\n")
sb.write_view(" \{to_col_off}\n")
sb.write_view(" \{to_row_idx}\n")
sb.write_view(" \{to_row_off}\n")
sb.write_view(" \n")
}
Absolute => {
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \{col_idx}\n")
sb.write_view(" \{col_off}\n")
sb.write_view(" \{row_idx}\n")
sb.write_view(" \{row_off}\n")
sb.write_view(" \n")
let (to_col_idx, to_col_off) = two_cell_axis_to(
drawing_metrics.cols,
col_idx,
col_off,
cx,
)
let (to_row_idx, to_row_off) = two_cell_axis_to(
drawing_metrics.rows,
row_idx,
row_off,
cy,
)
sb.write_view(" \n")
sb.write_view(" \{to_col_idx}\n")
sb.write_view(" \{to_col_off}\n")
sb.write_view(" \{to_row_idx}\n")
sb.write_view(" \{to_row_off}\n")
sb.write_view(" \n")
}
OneCell => {
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \{col_idx}\n")
sb.write_view(" \{col_off}\n")
sb.write_view(" \{row_idx}\n")
sb.write_view(" \{row_off}\n")
sb.write_view(" \n")
sb.write_view(" \n")
}
}
sb.write_view(" \n")
sb.write_view(" \n")
let shape_name = if shape.name == "" {
"Shape \{shape_id}"
} else {
shape.name
}
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
if shape.fill_color != "" {
let fill = shape.fill_color.replace_all(old="#", new="").to_upper()
sb.write_view(" 0 {
let alpha = (100 - shape.fill_transparency) * 1000
sb.write_view(
"\">\n",
)
} else {
sb.write_view("\"/>\n")
}
}
if shape.line_color != "" || shape.line_width != 1.0 {
sb.write_view(" \n")
} else {
let line = shape.line_color.replace_all(old="#", new="").to_upper()
sb.write_view(">\n")
}
}
sb.write_view(" \n")
if shape.paragraph.length() > 0 || shape.text != "" {
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
let runs = if shape.paragraph.length() > 0 {
shape.paragraph[:]
} else {
[{ text: shape.text, font: None }]
}
for run in runs {
sb.write_view(" \n")
sb.write_view(" \n")
match run.font {
Some(font) => {
sb.write_view(" {
sb.write_view(" u=\"")
sb.write_xml_attr(value)
sb.write_view("\"")
}
None => ()
}
match font.size {
Some(size) =>
sb.write_view(" sz=\"\{(size * 100.0).round().to_int()}\"")
None => ()
}
sb.write_view(">")
match font.family {
Some(family) => {
sb.write_view("")
sb.write_view("")
sb.write_view("")
}
None => ()
}
match font.color {
Some(color) => {
let normalized = color.replace_all(old="#", new="").to_upper()
sb.write_view("")
}
None => ()
}
sb.write_view("\n")
}
None => sb.write_view(" \n")
}
let text_value = if run.text == "" { " " } else { run.text }
if text_value.has_prefix(" ") ||
text_value.has_suffix(" ") ||
text_value.contains("\n") {
sb.write_view(" ")
} else {
sb.write_view(" ")
}
sb.write_xml_text(text_value)
sb.write_view("\n")
sb.write_view(" \n")
sb.write_view(" \n")
}
sb.write_view(" \n")
}
sb.write_view(" \n")
sb.write_view(
" \n",
)
match shape.positioning {
TwoCell => sb.write_view(" \n")
Absolute => sb.write_view(" \n")
OneCell => sb.write_view(" \n")
}
}
for item in slicers {
let slicer = item.slicer
let (row, col) = cell_ref_to_rc(slicer.cell)
let col_idx = col - 1
let row_idx = row - 1
let (frame_id, next_obj_id) = allocate_drawing_numeric_id(
obj_id, reserved_object_id_set,
)
obj_id = next_obj_id
let offset_x = match slicer.format.offset_x {
Some(v) => v
None => 0
}
let offset_y = match slicer.format.offset_y {
Some(v) => v
None => 0
}
let col_off = match slicer.drawing_offset_x_emu {
Some(value) => value
None =>
drawing_pixel_offset_for_write(offset_x, "drawing slicer column offset")
}
let row_off = match slicer.drawing_offset_y_emu {
Some(value) => value
None =>
drawing_pixel_offset_for_write(offset_y, "drawing slicer row offset")
}
let scale_x = match slicer.format.scale_x {
Some(v) => if v <= 0.0 { 1.0 } else { v }
None => 1.0
}
let scale_y = match slicer.format.scale_y {
Some(v) => if v <= 0.0 { 1.0 } else { v }
None => 1.0
}
let cx = match slicer.drawing_width_emu {
Some(value) => drawing_extent_for_write(value, "drawing slicer width")
None =>
drawing_scaled_extent_for_write(
slicer.width.to_double(),
scale_x,
"drawing slicer width",
)
}
let cy = match slicer.drawing_height_emu {
Some(value) => drawing_extent_for_write(value, "drawing slicer height")
None =>
drawing_scaled_extent_for_write(
slicer.height.to_double(),
scale_y,
"drawing slicer height",
)
}
let positioning = match slicer.format.positioning {
Some(v) => v
None => TwoCell
}
match positioning {
TwoCell => {
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \{col_idx}\n")
sb.write_view(" \{col_off}\n")
sb.write_view(" \{row_idx}\n")
sb.write_view(" \{row_off}\n")
sb.write_view(" \n")
let (to_col_idx, to_col_off) = two_cell_axis_to(
drawing_metrics.cols,
col_idx,
col_off,
cx,
)
let (to_row_idx, to_row_off) = two_cell_axis_to(
drawing_metrics.rows,
row_idx,
row_off,
cy,
)
sb.write_view(" \n")
sb.write_view(" \{to_col_idx}\n")
sb.write_view(" \{to_col_off}\n")
sb.write_view(" \{to_row_idx}\n")
sb.write_view(" \{to_row_off}\n")
sb.write_view(" \n")
}
Absolute => {
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \{col_idx}\n")
sb.write_view(" \{col_off}\n")
sb.write_view(" \{row_idx}\n")
sb.write_view(" \{row_off}\n")
sb.write_view(" \n")
let (to_col_idx, to_col_off) = two_cell_axis_to(
drawing_metrics.cols,
col_idx,
col_off,
cx,
)
let (to_row_idx, to_row_off) = two_cell_axis_to(
drawing_metrics.rows,
row_idx,
row_off,
cy,
)
sb.write_view(" \n")
sb.write_view(" \{to_col_idx}\n")
sb.write_view(" \{to_col_off}\n")
sb.write_view(" \{to_row_idx}\n")
sb.write_view(" \{to_row_off}\n")
sb.write_view(" \n")
}
OneCell => {
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \{col_idx}\n")
sb.write_view(" \{col_off}\n")
sb.write_view(" \{row_idx}\n")
sb.write_view(" \{row_off}\n")
sb.write_view(" \n")
sb.write_view(" \n")
}
}
let alt_text = match slicer.format.alt_text {
Some(v) => v
None => ""
}
sb.write_view(
" \n",
)
if item.is_table_slicer {
sb.write_view(
" \n",
)
} else {
sb.write_view(
" \n",
)
}
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(
" \n",
)
sb.write_view(
" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(
" \n",
)
sb.write_view(
" \n",
)
sb.write_view(
" \n",
)
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(
" \n",
)
sb.write_view(" ")
sb.write_xml_text(
"This shape represents a table slicer. Table slicers are not supported in this version of Excel.",
)
sb.write_view("\n")
sb.write_view(" ")
sb.write_xml_text(
"If the shape was modified in an earlier version of Excel, or if the workbook was saved in Excel 2007 or earlier, the slicer can't be used.",
)
sb.write_view("\n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
let locked = match slicer.format.locked {
Some(v) => v
None => true
}
let print_object = match slicer.format.print_object {
Some(v) => v
None => true
}
sb.write_view(
" \n",
)
match positioning {
TwoCell => sb.write_view(" \n")
Absolute => sb.write_view(" \n")
OneCell => sb.write_view(" \n")
}
}
sb.write_view("")
let grouped_xml = sb.to_string()
let known_orders : Array[Int?] = []
let mut needs_ordering = preserved_anchors.length() > 0
for item in images {
known_orders.push(item.image.drawing_order)
if item.image.drawing_order is Some(_) {
needs_ordering = true
}
}
for item in charts {
known_orders.push(item.drawing_order)
if item.drawing_order is Some(_) {
needs_ordering = true
}
}
for shape in shapes {
known_orders.push(shape.drawing_order)
if shape.drawing_order is Some(_) {
needs_ordering = true
}
}
for item in slicers {
known_orders.push(item.slicer.drawing_order)
if item.slicer.drawing_order is Some(_) {
needs_ordering = true
}
}
if !needs_ordering {
return grouped_xml
}
let generated_anchors = parse_drawing_anchors(grouped_xml)
if generated_anchors.length() != known_orders.length() {
raise InvalidSheetOperation(msg="generated drawing anchor count mismatch")
}
let ordered : Array[OrderedDrawingAnchor] = []
for index, anchor in generated_anchors {
ordered.push({
xml: anchor.xml,
order: known_orders[index],
fallback_order: index,
})
}
for index, anchor in preserved_anchors {
ordered.push({
xml: anchor.xml,
order: Some(anchor.order),
fallback_order: generated_anchors.length() + index,
})
}
ordered.sort_by(compare_ordered_drawing_anchor)
let output = LimitedXmlBuilder::new(budget)
write_drawing_open(output)
for anchor in ordered {
output.write_view(" ")
output.write_view(anchor.xml)
output.write_view("\n")
}
output.write_view("")
output.to_string()
}
///|
fn write_sheet_pr_xml(
options : SheetPropsOptions,
budget? : WritePartBudget,
) -> String raise XlsxError {
let sb = LimitedXmlBuilder::new(budget)
sb.write_view(" {
sb.write_view(" codeName=\"")
sb.write_xml_attr(value)
sb.write_view("\"")
}
None => ()
}
match options.enable_format_conditions_calculation {
Some(value) =>
sb.write_view(
" enableFormatConditionsCalculation=\"\{bool_attr(value)}\"",
)
None => ()
}
match options.published {
Some(value) => sb.write_view(" published=\"\{bool_attr(value)}\"")
None => ()
}
let has_tab_color = options.tab_color_indexed is Some(_) ||
options.tab_color_rgb is Some(_) ||
options.tab_color_theme is Some(_) ||
options.tab_color_tint is Some(_)
let has_outline = options.outline_summary_below is Some(_) ||
options.outline_summary_right is Some(_)
let has_page_setup = options.auto_page_breaks is Some(_) ||
options.fit_to_page is Some(_)
if !has_tab_color && !has_outline && !has_page_setup {
sb.write_view("/>\n")
return sb.to_string()
}
sb.write_view(">\n")
if has_tab_color {
sb.write_view(" sb.write_view(" indexed=\"\{value}\"")
None => ()
}
match options.tab_color_rgb {
Some(value) => {
sb.write_view(" rgb=\"")
sb.write_xml_attr(value)
sb.write_view("\"")
}
None => ()
}
match options.tab_color_theme {
Some(value) => sb.write_view(" theme=\"\{value}\"")
None => ()
}
match options.tab_color_tint {
Some(value) => sb.write_view(" tint=\"\{value}\"")
None => ()
}
sb.write_view("/>\n")
}
if has_outline {
sb.write_view(" sb.write_view(" summaryBelow=\"\{bool_attr(value)}\"")
None => ()
}
match options.outline_summary_right {
Some(value) => sb.write_view(" summaryRight=\"\{bool_attr(value)}\"")
None => ()
}
sb.write_view("/>\n")
}
if has_page_setup {
sb.write_view(" sb.write_view(" autoPageBreaks=\"\{bool_attr(value)}\"")
None => ()
}
match options.fit_to_page {
Some(value) => sb.write_view(" fitToPage=\"\{bool_attr(value)}\"")
None => ()
}
sb.write_view("/>\n")
}
sb.write_view(" \n")
sb.to_string()
}
///|
fn write_sheet_format_pr_xml(
options : SheetPropsOptions,
budget? : WritePartBudget,
) -> String raise XlsxError {
let sb = LimitedXmlBuilder::new(budget)
sb.write_view(" sb.write_view(" baseColWidth=\"\{value}\"")
None => ()
}
match options.default_col_width {
Some(value) => sb.write_view(" defaultColWidth=\"\{value}\"")
None => ()
}
match options.default_row_height {
Some(value) => sb.write_view(" defaultRowHeight=\"\{value}\"")
None => ()
}
match options.custom_height {
Some(value) => sb.write_view(" customHeight=\"\{bool_attr(value)}\"")
None => ()
}
match options.zero_height {
Some(value) => sb.write_view(" zeroHeight=\"\{bool_attr(value)}\"")
None => ()
}
match options.thick_top {
Some(value) => sb.write_view(" thickTop=\"\{bool_attr(value)}\"")
None => ()
}
match options.thick_bottom {
Some(value) => sb.write_view(" thickBottom=\"\{bool_attr(value)}\"")
None => ()
}
sb.write_view("/>\n")
sb.to_string()
}
///|
fn write_sheet_views_xml(
sheet : Worksheet,
budget? : WritePartBudget,
) -> String? raise XlsxError {
if sheet.sheet_views.length() == 0 {
return None
}
let sb = LimitedXmlBuilder::new(budget)
sb.write_view(" \n")
for view in sheet.sheet_views {
sb.write_view(" sb.write_view(" defaultGridColor=\"\{bool_attr(value)}\"")
None => ()
}
match view.right_to_left {
Some(value) => sb.write_view(" rightToLeft=\"\{bool_attr(value)}\"")
None => ()
}
match view.show_formulas {
Some(value) => sb.write_view(" showFormulas=\"\{bool_attr(value)}\"")
None => ()
}
match view.show_grid_lines {
Some(value) => sb.write_view(" showGridLines=\"\{bool_attr(value)}\"")
None => ()
}
match view.show_row_col_headers {
Some(value) => sb.write_view(" showRowColHeaders=\"\{bool_attr(value)}\"")
None => ()
}
match view.show_ruler {
Some(value) => sb.write_view(" showRuler=\"\{bool_attr(value)}\"")
None => ()
}
match view.show_zeros {
Some(value) => sb.write_view(" showZeros=\"\{bool_attr(value)}\"")
None => ()
}
match view.top_left_cell {
Some(value) => {
sb.write_view(" topLeftCell=\"")
sb.write_xml_attr(normalize_cell_ref_for_write(value))
sb.write_view("\"")
}
None => ()
}
match view.view {
Some(value) => {
sb.write_view(" view=\"")
sb.write_xml_attr(value)
sb.write_view("\"")
}
None => ()
}
match view.zoom_scale {
Some(value) => sb.write_view(" zoomScale=\"\{value}\"")
None => ()
}
if view.tab_selected {
sb.write_view(" tabSelected=\"1\"")
}
sb.write_view(" workbookViewId=\"\{view.workbook_view_id}\"")
if view.pane is None && view.selection.length() == 0 {
sb.write_view("/>\n")
continue
}
sb.write_view(">\n")
match view.pane {
Some(pane) => {
sb.write_view(" \n")
}
None => ()
}
for selection in view.selection {
sb.write_view(" \n")
}
sb.write_view(" \n")
}
sb.write_view(" \n")
Some(sb.to_string())
}
///|
fn write_chartsheet_xml(
drawing_rel_id : Int,
budget? : WritePartBudget,
) -> String raise XlsxError {
let sb = LimitedXmlBuilder::new(budget)
sb.write_view("\n")
sb.write_view(
"\n",
)
sb.write_view(" \n")
sb.write_view(
" \n",
)
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view("")
sb.to_string()
}
///|
fn worksheet_dimension(sheet : Worksheet) -> String? raise XlsxError {
match sheet.dimension_ref {
Some(value) => return Some(value)
None => ()
}
let mut has_any = false
let mut min_row = 0
let mut min_col = 0
let mut max_row = 0
let mut max_col = 0
for cell in sheet.cells() {
let row = cell.row
let col = cell.col
if !has_any {
has_any = true
min_row = row
max_row = row
min_col = col
max_col = col
} else {
if row < min_row {
min_row = row
}
if row > max_row {
max_row = row
}
if col < min_col {
min_col = col
}
if col > max_col {
max_col = col
}
}
}
for range_ref in sheet.merged_cells() {
let (row1, col1, row2, col2) = parse_range_ref(range_ref)
if !has_any {
has_any = true
min_row = row1
max_row = row2
min_col = col1
max_col = col2
} else {
if row1 < min_row {
min_row = row1
}
if row2 > max_row {
max_row = row2
}
if col1 < min_col {
min_col = col1
}
if col2 > max_col {
max_col = col2
}
}
}
for table in sheet.tables() {
let (row1, col1, row2, col2) = parse_range_ref(table.range_ref)
if !has_any {
has_any = true
min_row = row1
max_row = row2
min_col = col1
max_col = col2
} else {
if row1 < min_row {
min_row = row1
}
if row2 > max_row {
max_row = row2
}
if col1 < min_col {
min_col = col1
}
if col2 > max_col {
max_col = col2
}
}
}
match sheet.auto_filter() {
Some(filter) => {
let (row1, col1, row2, col2) = parse_range_ref(filter.range_ref)
if !has_any {
has_any = true
min_row = row1
max_row = row2
min_col = col1
max_col = col2
} else {
if row1 < min_row {
min_row = row1
}
if row2 > max_row {
max_row = row2
}
if col1 < min_col {
min_col = col1
}
if col2 > max_col {
max_col = col2
}
}
}
None => ()
}
if !has_any {
return None
}
let start_ref = cell_ref_from(min_row, min_col)
let end_ref = cell_ref_from(max_row, max_col)
Some("\{start_ref}:\{end_ref}")
}
///|
fn write_worksheet_xml(
sheet : Worksheet,
string_map : Map[String, Int],
rich_text_map : Map[String, Int],
hyperlink_rel_ids : Map[String, Int],
hyperlinks : ArrayView[Hyperlink],
drawing_rel_id : Int?,
picture_rel_id : Int?,
table_rel_ids : ArrayView[Int],
_pivot_rel_ids : ArrayView[Int],
slicer_rel_ids_x14 : ArrayView[Int],
slicer_rel_ids_x15 : ArrayView[Int],
legacy_drawing_rel_id : Int?,
legacy_drawing_hf_rel_id : Int?,
budget? : WritePartBudget,
) -> String raise XlsxError {
fn write_worksheet_ext_lst_xml(
slicer_rel_ids_x14 : ArrayView[Int],
slicer_rel_ids_x15 : ArrayView[Int],
sparkline_groups : ArrayView[SparklineGroup],
x14_data_bars : Map[String, X14DataBarProps],
unknown_ext_blocks : ArrayView[String],
budget : WritePartBudget?,
) -> String? raise XlsxError {
fn write_x14_conditional_formattings_ext_block_xml(
x14_data_bars : Map[String, X14DataBarProps],
budget : WritePartBudget?,
) -> String? raise XlsxError {
let by_sqref : Map[String, Array[(String, X14DataBarProps)]] = Map([])
for id, props in x14_data_bars {
if props.bar_direction == "" &&
!props.bar_solid &&
props.bar_border_color == "" {
continue
}
match by_sqref.get(props.sqref) {
Some(ids) => {
ids.push((id, props))
by_sqref[props.sqref] = ids
}
None => by_sqref[props.sqref] = [(id, props)]
}
}
if by_sqref.length() == 0 {
return None
}
let sb = LimitedXmlBuilder::new(budget)
sb.write_view(
" \n",
)
sb.write_view(
" \n",
)
for sqref, entries in by_sqref {
sb.write_view(" \n")
for entry in entries {
let (id, props) = entry
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
if props.bar_border_color != "" {
sb.write_view(" \n")
}
sb.write_view(
" \n",
)
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
}
sb.write_view(" ")
sb.write_xml_text(sqref)
sb.write_view("\n")
sb.write_view(" \n")
}
sb.write_view(" \n")
sb.write_view(" \n")
Some(sb.to_string())
}
let mut has_conditional = false
for _, props in x14_data_bars {
if props.bar_direction != "" ||
props.bar_solid ||
props.bar_border_color != "" {
has_conditional = true
break
}
}
let has_slicers = slicer_rel_ids_x14.length() > 0 ||
slicer_rel_ids_x15.length() > 0
let has_sparklines = sparkline_groups.length() > 0
let has_unknown = unknown_ext_blocks.length() > 0
if !has_slicers && !has_sparklines && !has_conditional && !has_unknown {
return None
}
if has_sparklines && !has_slicers && !has_conditional && !has_unknown {
return Some(
write_sparkline_ext_xml(sheet.name(), sparkline_groups, budget?),
)
}
let sb = LimitedXmlBuilder::new(budget)
sb.write_view(" \n")
if has_conditional {
match
write_x14_conditional_formattings_ext_block_xml(
x14_data_bars,
sb.remaining_budget(),
) {
Some(block) => sb.write_view(block)
None => ()
}
}
if has_sparklines {
sb.write_view(
write_sparkline_ext_block_xml(
sheet.name(),
sparkline_groups,
budget?=sb.remaining_budget(),
),
)
}
if slicer_rel_ids_x14.length() > 0 {
sb.write_view(
" \n",
)
sb.write_view(
" \n",
)
for rel_id in slicer_rel_ids_x14 {
sb.write_view(" \n")
}
sb.write_view(" \n")
sb.write_view(" \n")
}
if slicer_rel_ids_x15.length() > 0 {
sb.write_view(
" \n",
)
sb.write_view(
" \n",
)
for rel_id in slicer_rel_ids_x15 {
sb.write_view(" \n")
}
sb.write_view(" \n")
sb.write_view(" \n")
}
if unknown_ext_blocks.length() > 0 {
for block in unknown_ext_blocks {
let block_text = block.to_string()
if block_text.has_prefix("\n")
Some(sb.to_string())
}
let sb = LimitedXmlBuilder::new(budget)
sb.write_view("\n")
let needs_rels = drawing_rel_id is Some(_) ||
table_rel_ids.length() > 0 ||
_pivot_rel_ids.length() > 0 ||
slicer_rel_ids_x14.length() > 0 ||
slicer_rel_ids_x15.length() > 0 ||
hyperlink_rel_ids.length() > 0 ||
legacy_drawing_rel_id is Some(_) ||
legacy_drawing_hf_rel_id is Some(_) ||
picture_rel_id is Some(_)
let needs_x14_ns = sheet.sparkline_groups().length() > 0 ||
sheet.x14_data_bars.length() > 0 ||
slicer_rel_ids_x14.length() > 0 ||
slicer_rel_ids_x15.length() > 0
sb.write_view(
"\n")
match sheet.sheet_props {
Some(options) =>
if sheet_props_has_sheet_pr(options) {
sb.write_view(
write_sheet_pr_xml(options, budget?=sb.remaining_budget()),
)
}
None => ()
}
match worksheet_dimension(sheet) {
Some(value) => {
sb.write_view(" \n")
}
None => ()
}
match write_sheet_views_xml(sheet, budget?=sb.remaining_budget()) {
Some(value) => sb.write_view(value)
None => ()
}
match sheet.sheet_props {
Some(options) =>
if sheet_props_has_sheet_format(options) {
sb.write_view(
write_sheet_format_pr_xml(options, budget?=sb.remaining_budget()),
)
}
None => ()
}
if sheet.col_dimensions.length() > 0 {
let cols : Array[Int] = []
for col, _ in sheet.col_dimensions {
cols.push(col)
}
cols.sort_by((a, b) => a - b)
sb.write_view(" \n")
for col in cols {
let dim = sheet.col_dimensions
.get(col)
.unwrap_or({
width: None,
hidden: false,
outline_level: 0,
style_id: None,
})
write_col_attributes(sb, col, dim)
}
sb.write_view(" \n")
}
sb.write_view(" \n")
let cells = sheet.sorted_cells()
let row_cells : Map[Int, Array[Cell]] = Map([])
for cell in cells {
let entries = match row_cells.get(cell.row) {
Some(value) => value
None => []
}
entries.push(cell)
row_cells[cell.row] = entries
}
let row_keys : Array[Int] = []
let row_present : Map[Int, Bool] = Map([])
for row, _ in row_cells {
row_keys.push(row)
row_present[row] = true
}
for row, _ in sheet.row_dimensions {
if !row_present.contains(row) {
row_keys.push(row)
}
}
row_keys.sort_by((a, b) => a - b)
for row in row_keys {
let row_entries = match row_cells.get(row) {
Some(value) => value
None => []
}
sb.write_view(" write_row_attributes(sb, dim)
None => ()
}
if row_entries.length() == 0 {
sb.write_view("/>\n")
continue
}
sb.write_view(">\n")
for cell in row_entries {
sb.write_view(" {
match cell.value_type {
String =>
if cell.formula_value_present {
sb.write_view(" t=\"str\"")
}
Bool => sb.write_view(" t=\"b\"")
Error => sb.write_view(" t=\"e\"")
Number => ()
}
sb.write_view(">")
sb.write_view(" {
sb.write_view(" t=\"")
sb.write_view(formula_type_to_ooxml(kind))
sb.write_view("\"")
}
None => ()
}
match cell.formula_ref {
Some(value) => {
sb.write_view(" ref=\"")
sb.write_xml_attr(value)
sb.write_view("\"")
}
None => ()
}
match cell.formula_shared_index {
Some(si) => sb.write_view(" si=\"\{si}\"")
None => ()
}
sb.write_view(">")
sb.write_xml_text(formula)
sb.write_view("")
if cell.formula_value_present {
sb.write_view("")
match cell.value_type {
String => sb.write_xml_text(cell.value)
Error => sb.write_xml_text(cell.value)
_ => sb.write_view(cell.value)
}
sb.write_view("")
}
sb.write_view("\n")
}
None =>
match cell.value_type {
String => {
let idx = match cell.rich_text {
Some(_) =>
match rich_text_map.get(cell.reference) {
Some(value) => value
None => raise InvalidSharedString(index=cell.row)
}
None =>
match string_map.get(cell.value) {
Some(value) => value
None => raise InvalidSharedString(index=cell.row)
}
}
sb.write_view(" t=\"s\">\{idx}\n")
}
Bool => {
sb.write_view(" t=\"b\">")
sb.write_view(cell.value)
sb.write_view("\n")
}
Error => {
sb.write_view(" t=\"e\">")
sb.write_xml_text(cell.value)
sb.write_view("\n")
}
Number => {
sb.write_view(">")
sb.write_view(cell.value)
sb.write_view("\n")
}
}
}
}
sb.write_view("
\n")
}
sb.write_view(" \n")
match sheet.sheet_protection() {
Some(protection) =>
sb.write_view(
write_sheet_protection_xml(protection, budget?=sb.remaining_budget()),
)
None => ()
}
match sheet.auto_filter() {
Some(filter) =>
sb.write_view(
write_auto_filter_xml(filter, budget?=sb.remaining_budget()),
)
None => ()
}
if sheet.merged_cells().length() > 0 {
sb.write_view(" \n")
for range_ref in sheet.merged_cells() {
sb.write_view(" \n")
}
sb.write_view(" \n")
}
for xml in sheet.conditional_formats() {
sb.write_view(" ")
sb.write_view(xml)
sb.write_view("\n")
}
if sheet.data_validations().length() > 0 {
sb.write_view(
" \n",
)
for xml in sheet.data_validations() {
sb.write_view(" ")
sb.write_view(xml)
sb.write_view("\n")
}
sb.write_view(" \n")
}
if hyperlinks.length() > 0 {
let mut wrote = false
for link in hyperlinks {
if link.link_type == Unset {
continue
}
if !wrote {
sb.write_view(" \n")
wrote = true
}
sb.write_view(" sb.write_view(" r:id=\"rId\{rel_id}\"")
None => raise InvalidHyperlink(msg="hyperlink relationship missing")
}
match link.location {
Some(value) => {
sb.write_view(" location=\"")
sb.write_xml_attr(value)
sb.write_view("\"")
}
None => ()
}
} else {
sb.write_view(" location=\"")
sb.write_xml_attr(link.target)
sb.write_view("\"")
}
match link.display {
Some(value) => {
sb.write_view(" display=\"")
sb.write_xml_attr(value)
sb.write_view("\"")
}
None => ()
}
match link.tooltip {
Some(value) => {
sb.write_view(" tooltip=\"")
sb.write_xml_attr(value)
sb.write_view("\"")
}
None => ()
}
sb.write_view("/>\n")
}
if wrote {
sb.write_view(" \n")
}
}
match sheet.page_margins {
Some(margins) => {
match write_print_options_xml(margins, budget?=sb.remaining_budget()) {
Some(xml) => sb.write_view(xml)
None => ()
}
let merged = page_margins_with_defaults(Some(margins))
sb.write_view(
write_page_margins_xml(merged, budget?=sb.remaining_budget()),
)
}
None => ()
}
match sheet.page_layout {
Some(layout) =>
sb.write_view(write_page_setup_xml(layout, budget?=sb.remaining_budget()))
None => ()
}
match sheet.header_footer() {
Some(options) =>
sb.write_view(
write_header_footer_xml(options, budget?=sb.remaining_budget()),
)
None => ()
}
match
write_page_breaks_xml(
"rowBreaks",
sheet.row_breaks(),
budget?=sb.remaining_budget(),
) {
Some(xml) => sb.write_view(xml)
None => ()
}
match
write_page_breaks_xml(
"colBreaks",
sheet.col_breaks(),
budget?=sb.remaining_budget(),
) {
Some(xml) => sb.write_view(xml)
None => ()
}
match
write_ignored_errors_xml(
sheet.ignored_errors(),
budget?=sb.remaining_budget(),
) {
Some(value) => sb.write_view(value)
None => ()
}
match drawing_rel_id {
Some(rel_id) => sb.write_view(" \n")
None => ()
}
match legacy_drawing_rel_id {
Some(rel_id) => sb.write_view(" \n")
None => ()
}
match legacy_drawing_hf_rel_id {
Some(rel_id) =>
sb.write_view(" \n")
None => ()
}
match picture_rel_id {
Some(rel_id) => sb.write_view(" \n")
None => ()
}
if table_rel_ids.length() > 0 {
sb.write_view(" \n")
for rel_id in table_rel_ids {
sb.write_view(" \n")
}
sb.write_view(" \n")
}
match
write_worksheet_ext_lst_xml(
slicer_rel_ids_x14,
slicer_rel_ids_x15,
sheet.sparkline_groups(),
sheet.x14_data_bars,
sheet.unknown_ext_blocks,
sb.remaining_budget(),
) {
Some(xml) => sb.write_view(xml)
None => ()
}
sb.write_view("")
sb.to_string()
}
///|
let default_theme_xml =
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
///|
fn write_theme_xml(
theme_colors : Array[String]?,
budget? : WritePartBudget,
) -> String raise XlsxError {
fn write_theme_color(
sb : LimitedXmlBuilder,
value : StringView,
) -> Unit raise XlsxError {
let normalized = if value.length() == 8 && value.has_prefix("FF") {
value[2:]
} else {
value
}
for character in normalized {
if character != '#' {
sb.write_char(character.to_ascii_uppercase())
}
}
}
match theme_colors {
Some(colors) =>
if colors.length() >= 10 {
// Order matches parse_theme_colors: lt1, dk1, lt2, dk2, accent1..accent6.
let sb = LimitedXmlBuilder::new(budget)
sb.write_view("\n")
sb.write_view(
"\n",
)
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(
" \n",
)
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(
" \n",
)
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(
" \n",
)
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(
" \n",
)
sb.write_view(
" \n",
)
sb.write_view(
" \n",
)
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(
" \n",
)
sb.write_view(
" \n",
)
sb.write_view(
" \n",
)
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(
" \n",
)
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(
" \n",
)
sb.write_view(
" \n",
)
sb.write_view(
" \n",
)
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view(" \n")
sb.write_view("\n")
sb.to_string()
} else {
default_theme_xml
}
None => default_theme_xml
}
}
///|
/// Archive builder used by bounded writes. Limits are checked before
/// `Archive::add` takes its defensive payload copy, so aggregate growth cannot
/// first materialize an oversized retained archive and only fail at ZIP output.
priv struct WriteArchiveBuilder {
archive : @zip.Archive
limits : WriteLimits?
mut entry_count : Int
mut total_uncompressed_bytes : Int
}
///|
fn WriteArchiveBuilder::new(limits : WriteLimits?) -> WriteArchiveBuilder {
{
archive: @zip.Archive::new(),
limits,
entry_count: 0,
total_uncompressed_bytes: 0,
}
}
///|
fn WriteArchiveBuilder::next_composition_budget(
self : WriteArchiveBuilder,
) -> WriteCompositionBudgetTracker raise XlsxError {
match self.limits {
Some(limits) if self.entry_count >= limits.max_archive_entries =>
raise ResourceLimitExceeded(
kind="entry_count",
limit=limits.max_archive_entries,
actual=bounded_actual_above_limit(limits.max_archive_entries),
)
_ => ()
}
{
limits: self.limits,
archived_bytes: self.total_uncompressed_bytes,
retained_bytes: 0,
}
}
///|
fn WriteArchiveBuilder::next_part_budget(
self : WriteArchiveBuilder,
) -> WritePartBudget? raise XlsxError {
match self.limits {
Some(limits) => {
if self.entry_count >= limits.max_archive_entries {
raise ResourceLimitExceeded(
kind="entry_count",
limit=limits.max_archive_entries,
actual=bounded_actual_above_limit(limits.max_archive_entries),
)
}
let aggregate_remaining = limits.max_total_uncompressed_bytes -
self.total_uncompressed_bytes
if limits.max_entry_uncompressed_bytes <= aggregate_remaining {
Some({
maximum: limits.max_entry_uncompressed_bytes,
resource_kind: "entry_uncompressed_bytes",
resource_limit: limits.max_entry_uncompressed_bytes,
})
} else {
Some({
maximum: aggregate_remaining,
resource_kind: "total_uncompressed_bytes",
resource_limit: limits.max_total_uncompressed_bytes,
})
}
}
None => None
}
}
///|
/// Adds an already-retained text part after checking its exact UTF-8 length and
/// before allocating the encoded byte buffer.
fn WriteArchiveBuilder::add_text(
self : WriteArchiveBuilder,
name : String,
text : StringView,
) -> Unit raise XlsxError {
match self.next_part_budget() {
Some(budget) =>
match xml_utf8_length_within(text, budget.maximum) {
Some(_) => ()
None =>
raise ResourceLimitExceeded(
kind=budget.resource_kind,
limit=budget.resource_limit,
actual=bounded_actual_above_limit(budget.resource_limit),
)
}
None => ()
}
self.add(name, @encoding/utf8.encode(text))
}
///|
fn WriteArchiveBuilder::add(
self : WriteArchiveBuilder,
name : String,
data : BytesView,
) -> Unit raise XlsxError {
match self.limits {
Some(limits) => {
if self.entry_count >= limits.max_archive_entries {
raise ResourceLimitExceeded(
kind="entry_count",
limit=limits.max_archive_entries,
actual=bounded_actual_above_limit(limits.max_archive_entries),
)
}
let next_entry_count = self.entry_count + 1
let size = data.length()
if size > limits.max_entry_uncompressed_bytes {
raise ResourceLimitExceeded(
kind="entry_uncompressed_bytes",
limit=limits.max_entry_uncompressed_bytes,
actual=size,
)
}
if size >
limits.max_total_uncompressed_bytes - self.total_uncompressed_bytes {
raise ResourceLimitExceeded(
kind="total_uncompressed_bytes",
limit=limits.max_total_uncompressed_bytes,
actual=bounded_actual_above_limit(limits.max_total_uncompressed_bytes),
)
}
let next_total = self.total_uncompressed_bytes + size
self.entry_count = next_entry_count
self.total_uncompressed_bytes = next_total
}
None => ()
}
self.archive.add(name, data)
}
///|
fn WriteArchiveBuilder::finish(self : WriteArchiveBuilder) -> @zip.Archive {
self.archive
}
///|
fn write_with_io(
workbook : Workbook,
io_context : WorkbookIOContext,
limits? : WriteLimits,
) -> Bytes raise XlsxError {
let (shared_strings, string_map, rich_text_map, total_count) = collect_shared_strings(
workbook,
)
let include_shared = shared_strings.length() > 0
let sheet_count = workbook.sheets().length()
let formula_cells = collect_formula_cells(workbook)
let include_calc_chain = formula_cells.length() > 0
let rel_offset = 2 +
(if include_shared { 1 } else { 0 }) +
(if include_calc_chain { 1 } else { 0 })
let chartsheet_rel_start = sheet_count + rel_offset + 1
let manifest = @ooxml.WorkbookManifest::new(
sheet_count,
include_shared_strings=include_shared,
include_calc_chain~,
)
let mut has_path_content_type = false
match io_context.file_path {
Some(path) =>
match extension_from_path(path) {
Some(ext) =>
match workbook_content_type_for_extension(ext) {
Some(content_type) => {
manifest.set_workbook_content_type(content_type)
has_path_content_type = true
}
None => ()
}
None => ()
}
None => ()
}
if has_path_content_type {
manifest.add_content_default("bin", ct_vba_project)
}
for i, _sheet in workbook.chart_sheets() {
let sheet_id = i + 1
manifest.add_content_override(
content_override_path(chartsheet_part_path(sheet_id)),
ct_chartsheet,
)
manifest.add_workbook_relationship(
rel_chartsheet,
chartsheet_workbook_rel_target(sheet_id),
)
}
match workbook.vba_project {
Some(_) => {
if !has_path_content_type {
manifest.set_workbook_content_type(ct_workbook_macro)
}
manifest.add_content_default("bin", ct_vba_project)
manifest.add_workbook_relationship(rel_vba_project, "vbaProject.bin")
}
None => ()
}
let archive = WriteArchiveBuilder::new(limits)
let root_rels_xml = match archive.next_part_budget() {
Some(budget) =>
match manifest.root_rels_xml_limited(budget.maximum) {
Some(xml) => xml
None =>
raise ResourceLimitExceeded(
kind=budget.resource_kind,
limit=budget.resource_limit,
actual=bounded_actual_above_limit(budget.resource_limit),
)
}
None => manifest.root_rels_xml()
}
archive.add_text(root_rels_part_path, root_rels_xml)
let tables_by_sheet : Array[Array[(Int, Table)]] = []
let mut next_table_id = 1
for sheet in workbook.sheets() {
for table in sheet.tables() {
if table.id >= next_table_id {
next_table_id = table.id + 1
}
}
}
for sheet in workbook.sheets() {
let sheet_tables : Array[(Int, Table)] = []
for table in sheet.tables() {
let table_id = if table.id > 0 {
table.id
} else {
let id = next_table_id
next_table_id = next_table_id + 1
id
}
sheet_tables.push((table_id, table))
}
tables_by_sheet.push(sheet_tables)
}
let pivots_by_sheet : Array[Array[(Int, Int, PivotTable)]] = []
let mut next_pivot_table_id = 1
let mut next_cache_id = 1
for sheet in workbook.sheets() {
for pivot in sheet.pivot_tables() {
if pivot.table_id >= next_pivot_table_id {
next_pivot_table_id = pivot.table_id + 1
}
if pivot.cache_id >= next_cache_id {
next_cache_id = pivot.cache_id + 1
}
}
}
for sheet in workbook.sheets() {
let sheet_pivots : Array[(Int, Int, PivotTable)] = []
for pivot in sheet.pivot_tables() {
let table_id = if pivot.table_id > 0 {
pivot.table_id
} else {
let id = next_pivot_table_id
next_pivot_table_id = next_pivot_table_id + 1
id
}
let cache_id = if pivot.cache_id > 0 {
pivot.cache_id
} else {
let id = next_cache_id
next_cache_id = next_cache_id + 1
id
}
sheet_pivots.push((table_id, cache_id, pivot))
}
pivots_by_sheet.push(sheet_pivots)
}
let table_lookup : Map[String, (Int, Table)] = Map([])
for i, sheet in workbook.sheets() {
let sheet_key = normalize_sheet_name(sheet.name())
for entry in tables_by_sheet[i] {
let (table_id, table) = entry
table_lookup["\{sheet_key}|\{table.name.to_lower()}"] = (table_id, table)
}
}
let pivot_lookup : Map[String, (Int, Int, PivotTable)] = Map([])
for i, sheet in workbook.sheets() {
let sheet_key = normalize_sheet_name(sheet.name())
let sheet_id = i + 1
for entry in pivots_by_sheet[i] {
let (_pivot_id, cache_id, pivot) = entry
if pivot.name != "" {
pivot_lookup["\{sheet_key}|\{pivot.name.to_lower()}"] = (
cache_id, sheet_id, pivot,
)
}
}
}
let pivot_cache_slicer_id_by_cache_id : Map[Int, Int] = Map([])
let mut next_pivot_cache_slicer_id = 1
for sheet_pivots in pivots_by_sheet {
for entry in sheet_pivots {
let (_pivot_id, cache_id, pivot) = entry
match parse_pivot_cache_slicer_id(pivot.cache_definition_xml) {
Some(id) => {
pivot_cache_slicer_id_by_cache_id[cache_id] = id
if id >= next_pivot_cache_slicer_id {
next_pivot_cache_slicer_id = id + 1
}
}
None => ()
}
}
}
let slicer_cache_id_by_name : Map[String, Int] = Map([])
let slicer_cache_rel_id_by_name : Map[String, String] = Map([])
let resolved_slicer_caches : Array[ResolvedSlicerCache] = []
let workbook_slicer_cache_rel_ids_x14 : Array[String] = []
let workbook_slicer_cache_rel_ids_x15 : Array[String] = []
let mut next_slicer_cache_id = 1
for sheet in workbook.sheets() {
for slicer in sheet.slicers() {
let cache_name = slicer.cache
if slicer_cache_id_by_name.contains(cache_name) {
continue
}
let cache_id = next_slicer_cache_id
next_slicer_cache_id = next_slicer_cache_id + 1
slicer_cache_id_by_name[cache_name] = cache_id
let source_sheet_key = normalize_sheet_name(slicer.table_sheet)
let source_key = "\{source_sheet_key}|\{slicer.table_name.to_lower()}"
match table_lookup.get(source_key) {
Some((table_id, table)) => {
let mut column : Int? = None
for i, col_name in table.columns {
if col_name.to_lower() == slicer.source_name.to_lower() {
column = Some(i + 1)
break
}
}
let col = match column {
Some(v) => v
None =>
raise InvalidSheetOperation(msg="slicer table field missing")
}
let rel_id = manifest.add_workbook_relationship_with_id(
rel_slicer_cache,
slicer_cache_workbook_rel_target(cache_id),
)
slicer_cache_rel_id_by_name[cache_name] = rel_id
workbook_slicer_cache_rel_ids_x15.push(rel_id)
resolved_slicer_caches.push({
cache_id,
cache_name,
source_name: slicer.source_name,
item_desc: slicer.item_desc,
source: Table(table_id, col),
})
}
None =>
match pivot_lookup.get(source_key) {
Some((pivot_cache_ref_id, pivot_sheet_id, pivot)) => {
let slicer_pivot_cache_id = match
pivot_cache_slicer_id_by_cache_id.get(pivot_cache_ref_id) {
Some(id) => id
None => {
let id = next_pivot_cache_slicer_id
next_pivot_cache_slicer_id = next_pivot_cache_slicer_id + 1
pivot_cache_slicer_id_by_cache_id[pivot_cache_ref_id] = id
id
}
}
let rel_id = manifest.add_workbook_relationship_with_id(
rel_slicer_cache,
slicer_cache_workbook_rel_target(cache_id),
)
slicer_cache_rel_id_by_name[cache_name] = rel_id
workbook_slicer_cache_rel_ids_x14.push(rel_id)
resolved_slicer_caches.push({
cache_id,
cache_name,
source_name: slicer.source_name,
item_desc: slicer.item_desc,
source: Pivot(slicer_pivot_cache_id, pivot_sheet_id, pivot.name),
})
}
None => raise InvalidSheetOperation(msg="slicer source missing")
}
}
}
}
let workbook_pivot_cache_rel_entries : Array[(Int, String)] = []
let pivot_cache_rel_written : Map[Int, Bool] = Map([])
for sheet_pivots in pivots_by_sheet {
for entry in sheet_pivots {
let (_pivot_id, cache_id, _pivot) = entry
if pivot_cache_rel_written.contains(cache_id) {
continue
}
pivot_cache_rel_written[cache_id] = true
let rel_id = manifest.add_workbook_relationship_with_id(
rel_pivot_cache,
pivot_cache_workbook_rel_target(cache_id),
)
workbook_pivot_cache_rel_entries.push((cache_id, rel_id))
}
}
archive.add_text(
workbook_part_path,
write_workbook_xml(
workbook,
chartsheet_rel_start,
workbook_pivot_cache_rel_entries,
workbook_slicer_cache_rel_ids_x14,
workbook_slicer_cache_rel_ids_x15,
budget?=archive.next_part_budget(),
),
)
let added_extensions : Map[String, Bool] = Map([])
let mut image_index = 1
let mut chart_index = 1
let mut vml_index = 1
let mut comment_index = 1
let mut drawing_index = sheet_count + 1
let mut slicer_part_index = 1
for i, sheet in workbook.sheets() {
let sheet_id = i + 1
let preserved_drawing = preserved_drawing_write_plan(sheet, sheet_id)
for part in preserved_drawing.parts {
archive.add(part.path, part.data)
manifest.add_content_override(
content_override_path(part.path),
part.content_type,
)
if part.relationships.length() > 0 {
archive.add_text(
rels_path_for_part(part.path),
write_preserved_drawing_rels_xml(
part.relationships,
budget?=archive.next_part_budget(),
),
)
}
}
let sheet_tables = tables_by_sheet[i]
let sheet_pivots = pivots_by_sheet[i]
let has_drawing = sheet.images().length() > 0 ||
sheet.charts().length() > 0 ||
sheet.shapes().length() > 0 ||
sheet.slicers().length() > 0 ||
preserved_drawing.anchors.length() > 0
let mut rel_id = 1
let drawing_rel_id = if has_drawing {
let id = rel_id
rel_id = rel_id + 1
Some(id)
} else {
None
}
let table_rel_ids : Array[Int] = []
let table_targets : Array[String] = []
for entry in sheet_tables {
let (table_id, _table) = entry
table_rel_ids.push(rel_id)
table_targets.push(table_rel_target(table_id))
rel_id = rel_id + 1
}
let pivot_rel_ids : Array[Int] = []
let pivot_targets : Array[String] = []
for entry in sheet_pivots {
let (pivot_id, _cache_id, _pivot) = entry
pivot_rel_ids.push(rel_id)
pivot_targets.push(pivot_table_rel_target(pivot_id))
rel_id = rel_id + 1
}
let slicer_targets : Array[String] = []
let slicer_rel_ids_x14 : Array[Int] = []
let slicer_rel_ids_x15 : Array[Int] = []
let pivot_slicers : Array[Slicer] = []
let table_slicers : Array[Slicer] = []
for slicer in sheet.slicers() {
let source_sheet_key = normalize_sheet_name(slicer.table_sheet)
let source_key = "\{source_sheet_key}|\{slicer.table_name.to_lower()}"
if table_lookup.contains(source_key) {
table_slicers.push(slicer)
} else {
pivot_slicers.push(slicer)
}
}
let mut pivot_slicer_part_id : Int? = None
let mut table_slicer_part_id : Int? = None
if pivot_slicers.length() > 0 {
let part_id = slicer_part_index
slicer_part_index = slicer_part_index + 1
pivot_slicer_part_id = Some(part_id)
slicer_rel_ids_x14.push(rel_id)
slicer_targets.push(slicer_rel_target(part_id))
rel_id = rel_id + 1
}
if table_slicers.length() > 0 {
let part_id = slicer_part_index
slicer_part_index = slicer_part_index + 1
table_slicer_part_id = Some(part_id)
slicer_rel_ids_x15.push(rel_id)
slicer_targets.push(slicer_rel_target(part_id))
rel_id = rel_id + 1
}
let hyperlink_targets : Array[String] = []
let hyperlink_rel_ids : Map[String, Int] = Map([])
for link in sheet.hyperlinks() {
if link.link_type == External {
hyperlink_targets.push(link.target)
hyperlink_rel_ids[link.reference] = rel_id
rel_id = rel_id + 1
}
}
let comment_targets : Array[String] = []
let stored_comments = sheet.stored_comments()
let comment_xml = if stored_comments.length() > 0 {
Some(
write_comments_xml(stored_comments, budget?=archive.next_part_budget()),
)
} else {
None
}
match comment_xml {
Some(xml) => {
let comment_id = comment_index
comment_index = comment_index + 1
comment_targets.push(comment_rel_target(comment_id))
archive.add_text(comment_part_path(comment_id), xml)
manifest.add_content_override(
content_override_path(comment_part_path(comment_id)),
ct_comments,
)
}
None => ()
}
if comment_targets.length() > 0 {
rel_id = rel_id + comment_targets.length()
}
let vml_targets : Array[String] = []
// Comment/control VML can require several retained intermediates before
// the final part is known. Give the whole composition one parent budget,
// debit each generated intermediate immediately, and archive the final
// part below before beginning any other generated VML.
let legacy_vml_budget = archive.next_composition_budget()
let source_vml = sheet.vml_drawing_xml()
let form_controls = sheet.form_controls()
let (base_vml, missing_comments) = match source_vml {
Some(xml) =>
if stored_comments.length() > 0 {
let (reconciled, missing, _) = reconcile_vml_comment_shapes(
xml,
stored_comments,
budget?=legacy_vml_budget.remaining_budget(),
)
if reconciled != xml {
legacy_vml_budget.consume(reconciled)
}
(Some(reconciled), missing)
} else {
(Some(xml), [])
}
None => (None, [])
}
let generated_comment_count = match source_vml {
Some(_) => missing_comments.length()
None => stored_comments.length()
}
let needs_generated_vml_ids = generated_comment_count > 0 ||
form_controls.length() > 0
let (used_vml_shape_ids, max_retained_shape_id) = if needs_generated_vml_ids {
match base_vml {
Some(xml) => vml_used_shape_numeric_ids(xml)
None => (Map([]), 1024)
}
} else {
(Map([]), 1024)
}
let next_comment_shape_id = if generated_comment_count > 0 {
allocate_vml_shape_id_range(
used_vml_shape_ids, max_retained_shape_id, 1025, generated_comment_count,
)
} else {
1025
}
reserve_vml_shape_id_range(
used_vml_shape_ids, next_comment_shape_id, generated_comment_count,
)
let preferred_after_comments = if generated_comment_count > 0 {
let last_comment_shape_id = next_comment_shape_id +
generated_comment_count -
1
last_comment_shape_id.max(max_retained_shape_id)
} else {
max_retained_shape_id
}
let form_control_shape_id_start = if form_controls.length() > 0 {
allocate_vml_shape_id_range(
used_vml_shape_ids,
preferred_after_comments,
2048,
form_controls.length(),
)
} else {
2048
}
let comment_vml_xml = match source_vml {
Some(_) =>
if missing_comments.length() > 0 {
let xml = write_comments_vml(
sheet_id,
missing_comments,
shape_id_start=next_comment_shape_id,
budget?=legacy_vml_budget.remaining_budget(),
)
legacy_vml_budget.consume(xml)
Some(xml)
} else {
None
}
None =>
if stored_comments.length() > 0 {
let xml = write_comments_vml(
sheet_id,
stored_comments,
shape_id_start=next_comment_shape_id,
budget?=legacy_vml_budget.remaining_budget(),
)
legacy_vml_budget.consume(xml)
Some(xml)
} else {
None
}
}
let form_control_vml_xml = if form_controls.length() > 0 {
let xml = write_form_controls_vml(
sheet_id,
sheet,
form_controls,
shape_id_start=form_control_shape_id_start,
budget?=legacy_vml_budget.remaining_budget(),
)
legacy_vml_budget.consume(xml)
Some(xml)
} else {
None
}
let merged_vml = match base_vml {
Some(xml) =>
match form_control_vml_xml {
Some(control_xml) => {
let merged = merge_vml_drawings(
xml,
control_xml,
budget?=legacy_vml_budget.remaining_budget(),
)
legacy_vml_budget.consume(merged)
Some(merged)
}
None => Some(xml)
}
None => form_control_vml_xml
}
let legacy_drawing_xml = match merged_vml {
Some(xml) =>
match comment_vml_xml {
Some(comment_xml) => {
let merged = merge_vml_drawings(
xml,
comment_xml,
budget?=legacy_vml_budget.remaining_budget(),
)
legacy_vml_budget.consume(merged)
Some(merged)
}
None => Some(xml)
}
None => comment_vml_xml
}
let legacy_drawing_rel_id = match legacy_drawing_xml {
Some(xml) => {
let id = rel_id
rel_id = rel_id + 1
let vml_id = vml_index
vml_index = vml_index + 1
vml_targets.push(vml_rel_target(vml_id))
archive.add_text(vml_part_path(vml_id), xml)
manifest.add_content_override(
content_override_path(vml_part_path(vml_id)),
ct_vml,
)
Some(id)
}
None => None
}
let header_footer_images = sheet.header_footer_images
let header_footer_vml_budget = archive.next_composition_budget()
let header_footer_vml = if header_footer_images.length() > 0 {
let xml = write_header_footer_vml(
sheet_id,
header_footer_images,
budget?=header_footer_vml_budget.remaining_budget(),
)
header_footer_vml_budget.consume(xml)
Some(xml)
} else {
None
}
let header_footer_shape_ids : Array[String] = []
if header_footer_images.length() > 0 {
for image in header_footer_images {
header_footer_shape_ids.push(
header_footer_image_shape_id(
image.position,
image.is_footer,
image.first_page,
),
)
}
}
let legacy_drawing_hf_xml = match sheet.vml_drawing_hf_xml() {
Some(xml) =>
match header_footer_vml {
Some(extra_xml) => {
let filtered = filter_vml_shapes_by_id(
xml,
header_footer_shape_ids,
budget?=header_footer_vml_budget.remaining_budget(),
)
header_footer_vml_budget.consume(filtered)
let merged = merge_vml_drawings_with_shapetype(
filtered,
extra_xml,
"_x0000_t75",
budget?=header_footer_vml_budget.remaining_budget(),
)
header_footer_vml_budget.consume(merged)
Some(merged)
}
None => Some(xml)
}
None => header_footer_vml
}
let legacy_drawing_hf_ids : (Int, Int)? = match legacy_drawing_hf_xml {
Some(xml) => {
let id = rel_id
rel_id = rel_id + 1
let vml_id = vml_index
vml_index = vml_index + 1
vml_targets.push(vml_rel_target(vml_id))
archive.add_text(vml_part_path(vml_id), xml)
manifest.add_content_override(
content_override_path(vml_part_path(vml_id)),
ct_vml,
)
Some((id, vml_id))
}
None => None
}
let header_footer_image_targets : Array[String] = []
if header_footer_images.length() > 0 {
for image in header_footer_images {
let file_name = "image\{image_index}.\{image.extension}"
image_index = image_index + 1
archive.add(media_part_path(file_name), image.data)
header_footer_image_targets.push(media_rel_target(file_name))
if !added_extensions.contains(image.extension) {
manifest.add_content_default(image.extension, image.content_type)
added_extensions.set(image.extension, true)
}
}
}
match legacy_drawing_hf_ids {
Some((_id, vml_id)) if header_footer_image_targets.length() > 0 => {
let vml_rels_xml = write_vml_rels_xml(
header_footer_image_targets,
budget?=archive.next_part_budget(),
)
archive.add_text(vml_rels_part_path(vml_id), vml_rels_xml)
}
_ => ()
}
let legacy_drawing_hf_rel_id = match legacy_drawing_hf_ids {
Some((id, _vml_id)) => Some(id)
None => None
}
let picture_targets : Array[String] = []
let picture_rel_id = match sheet.sheet_background() {
Some(background) => {
let id = rel_id
rel_id = rel_id + 1
let file_name = "image\{image_index}.\{background.extension}"
image_index = image_index + 1
archive.add(media_part_path(file_name), background.data)
picture_targets.push(media_rel_target(file_name))
if !added_extensions.contains(background.extension) {
manifest.add_content_default(
background.extension,
background.content_type,
)
added_extensions.set(background.extension, true)
}
Some(id)
}
None => None
}
let sheet_xml = write_worksheet_xml(
sheet,
string_map,
rich_text_map,
hyperlink_rel_ids,
sheet.hyperlinks(),
drawing_rel_id,
picture_rel_id,
table_rel_ids,
pivot_rel_ids,
slicer_rel_ids_x14,
slicer_rel_ids_x15,
legacy_drawing_rel_id,
legacy_drawing_hf_rel_id,
budget?=archive.next_part_budget(),
)
archive.add_text(worksheet_part_path(sheet_id), sheet_xml)
if drawing_rel_id is Some(_) ||
table_targets.length() > 0 ||
pivot_targets.length() > 0 ||
slicer_targets.length() > 0 ||
hyperlink_targets.length() > 0 ||
comment_targets.length() > 0 ||
vml_targets.length() > 0 ||
picture_targets.length() > 0 {
let drawing_target = match drawing_rel_id {
Some(_) => Some(drawing_rel_target(sheet_id))
None => None
}
let sheet_rels_xml = write_sheet_rels_xml(
drawing_target,
table_targets,
pivot_targets,
slicer_targets,
hyperlink_targets,
comment_targets,
vml_targets,
picture_targets,
budget?=archive.next_part_budget(),
)
archive.add_text(worksheet_rels_part_path(sheet_id), sheet_rels_xml)
}
for entry in sheet_tables {
let (table_id, table) = entry
let table_xml = write_table_xml(
table_id,
table,
budget?=archive.next_part_budget(),
)
archive.add_text(table_part_path(table_id), table_xml)
manifest.add_content_override(
content_override_path(table_part_path(table_id)),
ct_table,
)
}
for entry in sheet_pivots {
let (pivot_id, cache_id, pivot) = entry
archive.add_text(pivot_table_part_path(pivot_id), pivot.table_xml)
manifest.add_content_override(
content_override_path(pivot_table_part_path(pivot_id)),
ct_pivot_table,
)
let pivot_rels_xml = write_pivot_table_rels_xml(
cache_id,
budget?=archive.next_part_budget(),
)
archive.add_text(pivot_table_rels_part_path(pivot_id), pivot_rels_xml)
let cache_definition_xml = match
pivot_cache_slicer_id_by_cache_id.get(cache_id) {
Some(slicer_pivot_cache_id) =>
ensure_pivot_cache_slicer_id(
pivot.cache_definition_xml,
slicer_pivot_cache_id,
budget?=archive.next_part_budget(),
)
None => pivot.cache_definition_xml
}
archive.add_text(
pivot_cache_definition_part_path(cache_id),
cache_definition_xml,
)
manifest.add_content_override(
content_override_path(pivot_cache_definition_part_path(cache_id)),
ct_pivot_cache_def,
)
match pivot.cache_records_xml {
Some(records) => {
archive.add_text(pivot_cache_records_part_path(cache_id), records)
manifest.add_content_override(
content_override_path(pivot_cache_records_part_path(cache_id)),
ct_pivot_cache_records,
)
let cache_rels_xml = write_pivot_cache_rels_xml(
cache_id,
budget?=archive.next_part_budget(),
)
archive.add_text(
pivot_cache_definition_rels_part_path(cache_id),
cache_rels_xml,
)
}
None => ()
}
}
match pivot_slicer_part_id {
Some(part_id) => {
let slicers_xml = write_slicers_part_xml(
pivot_slicers,
budget?=archive.next_part_budget(),
)
archive.add_text(slicer_part_path(part_id), slicers_xml)
manifest.add_content_override(
content_override_path(slicer_part_path(part_id)),
ct_slicer,
)
}
None => ()
}
match table_slicer_part_id {
Some(part_id) => {
let slicers_xml = write_slicers_part_xml(
table_slicers,
budget?=archive.next_part_budget(),
)
archive.add_text(slicer_part_path(part_id), slicers_xml)
manifest.add_content_override(
content_override_path(slicer_part_path(part_id)),
ct_slicer,
)
}
None => ()
}
if !has_drawing {
continue
}
let image_targets : Array[(Int, String)] = []
let chart_targets : Array[(Int, String)] = []
let drawing_images : Array[DrawingImage] = []
let drawing_hyperlinks : Array[DrawingHyperlink] = []
let drawing_charts : Array[DrawingChart] = []
let drawing_shapes : Array[Shape] = []
let drawing_slicers : Array[DrawingSlicer] = []
let reserved_relationship_ids : Set[String] = Set([])
for id in preserved_drawing.relationship_ids {
reserved_relationship_ids.add(id)
}
let mut rel_id = 1
for image in sheet.images() {
let file_name = "image\{image_index}.\{image.extension}"
image_index = image_index + 1
archive.add(media_part_path(file_name), image.data)
let (image_rel_id, next_rel_id) = allocate_drawing_relationship_id(
rel_id, reserved_relationship_ids,
)
rel_id = next_rel_id
let mut hyperlink_rel_id : Int? = None
if image.hyperlink != "" && image.hyperlink_type != Unset {
let (link_rel_id, next_rel_id) = allocate_drawing_relationship_id(
rel_id, reserved_relationship_ids,
)
rel_id = next_rel_id
drawing_hyperlinks.push({
rel_id: link_rel_id,
target: image.hyperlink,
link_type: image.hyperlink_type,
})
hyperlink_rel_id = Some(link_rel_id)
}
image_targets.push((image_rel_id, media_rel_target(file_name)))
drawing_images.push({ image, rel_id: image_rel_id, hyperlink_rel_id })
if !added_extensions.contains(image.extension) {
manifest.add_content_default(image.extension, image.content_type)
added_extensions.set(image.extension, true)
}
}
for chart in sheet.charts() {
let file_name = "chart\{chart_index}.xml"
chart_index = chart_index + 1
archive.add_text(chart_part_path(file_name), chart.xml)
let (chart_rel_id, next_rel_id) = allocate_drawing_relationship_id(
rel_id, reserved_relationship_ids,
)
rel_id = next_rel_id
chart_targets.push((chart_rel_id, chart_rel_target(file_name)))
drawing_charts.push({
reference: chart.reference,
offset_x: chart.offset_x,
offset_y: chart.offset_y,
width_emu: chart.width_emu,
height_emu: chart.height_emu,
print_object: chart.print_object,
locked: chart.locked,
positioning: chart.positioning,
rel_id: chart_rel_id,
drawing_offset_x_emu: chart.drawing_offset_x_emu,
drawing_offset_y_emu: chart.drawing_offset_y_emu,
drawing_width_emu: chart.drawing_width_emu,
drawing_height_emu: chart.drawing_height_emu,
drawing_order: chart.drawing_order,
})
manifest.add_content_override(
content_override_path(chart_part_path(file_name)),
ct_chart,
)
}
manifest.add_content_override(
content_override_path(drawing_part_path(sheet_id)),
ct_drawing,
)
for shape in sheet.shapes() {
drawing_shapes.push(shape)
}
for slicer in sheet.slicers() {
let source_sheet_key = normalize_sheet_name(slicer.table_sheet)
let source_key = "\{source_sheet_key}|\{slicer.table_name.to_lower()}"
let is_table_slicer = table_lookup.contains(source_key)
drawing_slicers.push({ slicer, is_table_slicer })
}
let drawing_xml = write_drawing_xml(
Some(sheet),
drawing_images,
drawing_charts,
drawing_shapes,
drawing_slicers,
preserved_anchors=preserved_drawing.anchors,
reserved_object_ids=preserved_drawing.object_ids,
budget?=archive.next_part_budget(),
)
archive.add_text(drawing_part_path(sheet_id), drawing_xml)
let drawing_rels_xml = write_drawing_rels_xml(
image_targets,
chart_targets,
drawing_hyperlinks,
preserved_relationships=preserved_drawing.drawing_relationships,
budget?=archive.next_part_budget(),
)
archive.add_text(drawing_rels_part_path(sheet_id), drawing_rels_xml)
}
for entry in resolved_slicer_caches {
let cache_id = entry.cache_id
let cache_xml = match entry.source {
Table(table_id, column) =>
write_slicer_cache_xml_table(
entry.cache_name,
entry.source_name,
table_id,
column,
entry.item_desc,
budget?=archive.next_part_budget(),
)
Pivot(pivot_cache_id, pivot_sheet_id, pivot_name) =>
write_slicer_cache_xml_pivot(
entry.cache_name,
entry.source_name,
pivot_sheet_id,
pivot_name,
pivot_cache_id,
entry.item_desc,
budget?=archive.next_part_budget(),
)
}
archive.add_text(slicer_cache_part_path(cache_id), cache_xml)
manifest.add_content_override(
content_override_path(slicer_cache_part_path(cache_id)),
ct_slicer_cache,
)
}
for i, sheet in workbook.chart_sheets() {
let sheet_id = i + 1
let chart_name = "chart\{chart_index}.xml"
chart_index = chart_index + 1
archive.add_text(chart_part_path(chart_name), sheet.chart_xml())
manifest.add_content_override(
content_override_path(chart_part_path(chart_name)),
ct_chart,
)
let drawing_id = drawing_index
drawing_index = drawing_index + 1
manifest.add_content_override(
content_override_path(drawing_part_path(drawing_id)),
ct_drawing,
)
let drawing_images : Array[DrawingImage] = []
let drawing_charts : Array[DrawingChart] = [
{
reference: "A1",
offset_x: 0,
offset_y: 0,
width_emu: emu_per_pixel * 480,
height_emu: emu_per_pixel * 260,
print_object: true,
locked: true,
positioning: OneCell,
rel_id: 1,
drawing_offset_x_emu: None,
drawing_offset_y_emu: None,
drawing_width_emu: None,
drawing_height_emu: None,
drawing_order: None,
},
]
let drawing_xml = write_drawing_xml(
None,
drawing_images,
drawing_charts,
[],
[],
budget?=archive.next_part_budget(),
)
archive.add_text(drawing_part_path(drawing_id), drawing_xml)
let chart_targets : Array[(Int, String)] = [
(1, chart_rel_target(chart_name)),
]
let drawing_rels_xml = write_drawing_rels_xml(
[],
chart_targets,
[],
budget?=archive.next_part_budget(),
)
archive.add_text(drawing_rels_part_path(drawing_id), drawing_rels_xml)
let chartsheet_xml = write_chartsheet_xml(
1,
budget?=archive.next_part_budget(),
)
archive.add_text(chartsheet_part_path(sheet_id), chartsheet_xml)
let chartsheet_rels_xml = write_sheet_rels_xml(
Some(drawing_rel_target(drawing_id)),
[],
[],
[],
[],
[],
[],
[],
budget?=archive.next_part_budget(),
)
archive.add_text(chartsheet_rels_part_path(sheet_id), chartsheet_rels_xml)
}
match workbook.vba_project {
Some(value) => archive.add(vba_project_part_path, value)
None => ()
}
if include_shared {
archive.add_text(
shared_strings_part_path,
write_shared_strings_xml(
shared_strings,
total_count,
budget?=archive.next_part_budget(),
),
)
}
if include_calc_chain {
archive.add_text(
calc_chain_part_path,
write_calc_chain_xml(formula_cells, budget?=archive.next_part_budget()),
)
}
archive.add_text(
styles_part_path,
write_styles_xml(
workbook.styles(),
workbook.conditional_styles(),
workbook.default_font(),
workbook.default_table_style,
workbook.default_pivot_style,
workbook.indexed_colors,
workbook.mru_colors_xml,
workbook.styles_ext_lst_xml,
budget?=archive.next_part_budget(),
),
)
archive.add_text(
theme_part_path,
match workbook.theme_xml {
Some(xml) => xml
None =>
write_theme_xml(
workbook.theme_colors,
budget?=archive.next_part_budget(),
)
},
)
archive.add_text(
core_props_part_path,
write_core_properties_xml(
workbook.core_properties(),
budget?=archive.next_part_budget(),
),
)
archive.add_text(
app_props_part_path,
write_app_properties_xml(
workbook.app_properties(),
budget?=archive.next_part_budget(),
),
)
let mut has_custom_props = false
for prop in workbook.custom_properties() {
if prop.value is Some(_) {
has_custom_props = true
break
}
}
if has_custom_props {
manifest.add_custom_properties()
archive.add_text(
custom_props_part_path,
write_custom_properties_xml(
workbook.custom_properties(),
budget?=archive.next_part_budget(),
),
)
}
let workbook_rels_xml = match archive.next_part_budget() {
Some(budget) =>
match manifest.workbook_rels_xml_limited(budget.maximum) {
Some(xml) => xml
None =>
raise ResourceLimitExceeded(
kind=budget.resource_kind,
limit=budget.resource_limit,
actual=bounded_actual_above_limit(budget.resource_limit),
)
}
None => manifest.workbook_rels_xml()
}
archive.add_text(workbook_rels_part_path, workbook_rels_xml)
let content_types_xml = match archive.next_part_budget() {
Some(budget) =>
match manifest.content_types_xml_limited(budget.maximum) {
Some(xml) => xml
None =>
raise ResourceLimitExceeded(
kind=budget.resource_kind,
limit=budget.resource_limit,
actual=bounded_actual_above_limit(budget.resource_limit),
)
}
None => manifest.content_types_xml()
}
archive.add_text(content_types_part_path, content_types_xml)
let archive = archive.finish()
match limits {
Some(policy) =>
@zip.write_limited(archive, max_output_bytes=policy.max_output_bytes) catch {
OutputLimitExceeded(limit~) =>
raise ResourceLimitExceeded(
kind="output_bytes",
limit~,
actual=bounded_actual_above_limit(limit),
)
err => raise UnsupportedFeature(msg=repr(err))
}
None =>
match io_context.zip_writer {
Some(writer) =>
writer(archive) catch {
err => raise UnsupportedFeature(msg=repr(err))
}
None =>
@zip.write(archive) catch {
err => raise UnsupportedFeature(msg=repr(err))
}
}
}
}
///|
pub fn write(workbook : Workbook) -> Bytes raise XlsxError {
write_with_io(workbook, workbook_io_context(workbook))
}
///|
/// Serializes a workbook under a fail-closed construction and output policy.
/// Generated parts are bounded before archive retention, then the ZIP writer
/// proves the final package size before allocating its output buffer. A custom
/// zip writer installed on the workbook is intentionally bypassed so it cannot
/// weaken this policy.
pub fn write_limited(
workbook : Workbook,
limits : WriteLimits,
) -> Bytes raise XlsxError {
write_with_io(workbook, workbook_io_context(workbook), limits~)
}
///|
pub fn write_with_password(
workbook : Workbook,
password : String,
) -> Bytes raise XlsxError {
let raw = write_with_io(workbook, workbook_io_context(workbook))
if password.length() == 0 {
raw
} else {
encrypt_package(raw, password)
}
}
///|
fn wb_count_substr(text : StringView, needle : StringView) -> Int {
let mut count = 0
let mut rest = text.to_owned()
let target = needle.to_owned()
if target == "" {
return 0
}
while true {
match rest.find(target) {
Some(pos) => {
count = count + 1
let next = pos + target.length()
rest = rest[next:].to_owned()
}
None => break
}
}
count
}
///|
fn wb_drawing_image_with_offset(
positioning : PicturePositioning,
offset_x : Int,
) -> DrawingImage {
let image : Image = {
reference: "B3",
data: @encoding/utf8.encode("img"),
extension: "png",
content_type: "image/png",
offset_x,
offset_y: 0,
scale_x: 1.0,
scale_y: 1.0,
width_emu: 1000,
height_emu: 2000,
print_object: true,
locked: true,
hyperlink: "",
hyperlink_type: Unset,
name: "",
alt_text: "",
lock_aspect_ratio: false,
positioning,
drawing_offset_x_emu: None,
drawing_offset_y_emu: None,
drawing_width_emu: None,
drawing_height_emu: None,
drawing_order: None,
}
{ image, rel_id: 1, hyperlink_rel_id: None }
}
///|
fn wb_drawing_image(positioning : PicturePositioning) -> DrawingImage {
wb_drawing_image_with_offset(positioning, 0)
}
///|
fn wb_drawing_chart(positioning : PicturePositioning) -> DrawingChart {
{
reference: "C4",
offset_x: 0,
offset_y: 0,
width_emu: 3000,
height_emu: 4000,
print_object: true,
locked: true,
positioning,
rel_id: 1,
drawing_offset_x_emu: None,
drawing_offset_y_emu: None,
drawing_width_emu: None,
drawing_height_emu: None,
drawing_order: None,
}
}
///|
fn wb_shape_for_anchor(
positioning : PicturePositioning,
) -> Shape raise XlsxError {
let font : RichTextFont = {
bold: false,
italic: false,
strike: true,
outline: false,
shadow: false,
condense: false,
extended: false,
underline: None,
size: Some(11.0),
color: None,
color_theme: None,
color_indexed: None,
color_tint: None,
charset: None,
family_number: None,
scheme: None,
vert_align: None,
family: None,
}
Shape::new(
"D5",
"rect",
paragraph=[{ text: "shape", font: Some(font) }],
line_width=2.0,
positioning~,
)
}
///|
fn wb_drawing_slicer(
name : String,
positioning : PicturePositioning?,
scale_x : Double?,
scale_y : Double?,
is_table_slicer : Bool,
) -> DrawingSlicer {
let format = GraphicOptions::new()
match positioning {
Some(value) => format.positioning = Some(value)
None => ()
}
match scale_x {
Some(value) => format.scale_x = Some(value)
None => ()
}
match scale_y {
Some(value) => format.scale_y = Some(value)
None => ()
}
let slicer : Slicer = {
name,
cache: "Slicer_" + name,
source_name: name,
cell: "C3",
table_sheet: "Sheet1",
table_name: "Table1",
caption: "",
macro_name: "",
width: 200,
height: 200,
display_header: None,
item_desc: false,
format,
drawing_offset_x_emu: None,
drawing_offset_y_emu: None,
drawing_width_emu: None,
drawing_height_emu: None,
drawing_order: None,
}
{ slicer, is_table_slicer }
}
///|
test "write hardening wb: write_drawing_xml uses None-sheet fallback for two-cell anchors" {
let xml = write_drawing_xml(None, [wb_drawing_image(TwoCell)], [], [], [])
inspect(xml.contains(""), content="true")
inspect(xml.contains("1"), content="true")
inspect(xml.contains("2"), content="true")
inspect(xml.contains("1000"), content="true")
inspect(xml.contains("2000"), content="true")
}
///|
test "write hardening wb: write_drawing_xml uses None-sheet fallback for absolute anchors" {
let xml = write_drawing_xml(None, [wb_drawing_image(Absolute)], [], [], [])
inspect(
xml.contains(""),
content="true",
)
inspect(xml.contains("1"), content="true")
inspect(xml.contains("2"), content="true")
inspect(xml.contains("1000"), content="true")
inspect(xml.contains("2000"), content="true")
}
///|
test "write hardening wb: write_shared_strings rich-text color and preserve-space branches" {
let font_indexed_tint : RichTextFont = {
bold: false,
italic: false,
strike: false,
outline: false,
shadow: false,
condense: false,
extended: false,
underline: None,
size: None,
color: None,
color_theme: None,
color_indexed: Some(64),
color_tint: Some(0.25),
charset: None,
family_number: None,
scheme: None,
vert_align: None,
family: None,
}
let font_rgb_tint : RichTextFont = {
bold: false,
italic: false,
strike: false,
outline: false,
shadow: false,
condense: false,
extended: false,
underline: None,
size: None,
color: Some("FF00AA00"),
color_theme: None,
color_indexed: None,
color_tint: Some(0.5),
charset: None,
family_number: None,
scheme: None,
vert_align: None,
family: None,
}
let font_tint_only : RichTextFont = {
bold: false,
italic: false,
strike: false,
outline: false,
shadow: false,
condense: false,
extended: false,
underline: None,
size: None,
color: None,
color_theme: None,
color_indexed: None,
color_tint: Some(0.75),
charset: None,
family_number: None,
scheme: None,
vert_align: None,
family: None,
}
let font_theme_no_tint : RichTextFont = {
bold: false,
italic: false,
strike: false,
outline: false,
shadow: false,
condense: false,
extended: false,
underline: None,
size: None,
color: None,
color_theme: Some(5),
color_indexed: None,
color_tint: None,
charset: None,
family_number: None,
scheme: None,
vert_align: None,
family: None,
}
let entries : Array[SharedStringEntry] = [
{ text: "I", runs: Some([{ text: "I", font: Some(font_indexed_tint) }]) },
{ text: "R", runs: Some([{ text: "R", font: Some(font_rgb_tint) }]) },
{ text: "T", runs: Some([{ text: "T", font: Some(font_tint_only) }]) },
{ text: "M", runs: Some([{ text: "M", font: Some(font_theme_no_tint) }]) },
{ text: " plain", runs: None },
]
let xml = write_shared_strings_xml(entries, 5)
inspect(xml.contains(""), content="true")
inspect(
xml.contains(""),
content="true",
)
inspect(xml.contains(""), content="true")
inspect(wb_count_substr(xml, "= 2, content="true")
inspect(xml.contains(""), content="true")
}
///|
test "write hardening wb: write_drawing_xml covers chart none-sheet anchor branches" {
let xml = write_drawing_xml(
None,
[],
[wb_drawing_chart(TwoCell), wb_drawing_chart(Absolute)],
[],
[],
)
inspect(
xml.contains(""),
content="true",
)
inspect(wb_count_substr(xml, ""), content="2")
inspect(wb_count_substr(xml, "") >= 2, content="true")
}
///|
test "write hardening wb: write_drawing_xml covers shape none-sheet and run-font branches" {
let shape_two = wb_shape_for_anchor(TwoCell) catch {
err => fail("shape init failed: \{repr(err)}")
}
let shape_abs = wb_shape_for_anchor(Absolute) catch {
err => fail("shape init failed: \{repr(err)}")
}
let xml = write_drawing_xml(None, [], [], [shape_two, shape_abs], [])
inspect(
xml.contains(""),
content="true",
)
inspect(xml.contains(""), content="true")
inspect(xml.contains("strike=\"sngStrike\""), content="true")
inspect(wb_count_substr(xml, ""), content="2")
}
///|
test "write hardening wb: two_cell_axis_to skips zero-size cells" {
let sheet = Worksheet::new("Sheet1")
sheet.set_col_visible(2, false)
sheet.set_row_visible(3, false)
let xml = write_drawing_xml(
Some(sheet),
[wb_drawing_image(TwoCell)],
[],
[],
[],
)
inspect(xml.contains(""), content="true")
inspect(xml.contains("2"), content="true")
}
///|
test "write hardening wb: two-cell anchors enforce the worksheet boundary" {
let chart : DrawingChart = {
reference: "XFD1",
offset_x: 0,
offset_y: 0,
width_emu: emu_per_pixel * 480,
height_emu: emu_per_pixel * 260,
print_object: true,
locked: true,
positioning: TwoCell,
rel_id: 1,
drawing_offset_x_emu: None,
drawing_offset_y_emu: None,
drawing_width_emu: None,
drawing_height_emu: None,
drawing_order: None,
}
let result : Result[String, Error] = Ok(
write_drawing_xml(Some(Worksheet::new("Sheet1")), [], [chart], [], []),
) catch {
error => Err(error)
}
match result {
Err(XlsxError::InvalidSheetOperation(msg~)) =>
inspect(msg, content="drawing extends beyond worksheet grid")
_ => fail("expected drawing grid boundary error")
}
}
///|
test "write hardening wb: signed offsets and exact end markers stay readable" {
let image = wb_drawing_image_with_offset(TwoCell, -1)
let signed_xml = write_drawing_xml(None, [image], [], [], [])
inspect(signed_xml.contains("-9525"), content="true")
let chart : DrawingChart = {
reference: "XFD1",
offset_x: 0,
offset_y: 0,
width_emu: 64 * emu_per_pixel,
height_emu: emu_per_pixel,
print_object: true,
locked: true,
positioning: TwoCell,
rel_id: 1,
drawing_offset_x_emu: None,
drawing_offset_y_emu: None,
drawing_width_emu: None,
drawing_height_emu: None,
drawing_order: None,
}
let boundary_xml = write_drawing_xml(
Some(Worksheet::new("Sheet1")),
[],
[chart],
[],
[],
)
inspect(boundary_xml.contains("16384"), content="true")
inspect(boundary_xml.contains("0"), content="true")
}
///|
test "write hardening wb: drawing arithmetic rejects overflow" {
let image = wb_drawing_image_with_offset(OneCell, 0x7fff_ffff)
let offset_result : Result[String, Error] = Ok(
write_drawing_xml(None, [image], [], [], []),
) catch {
error => Err(error)
}
match offset_result {
Err(XlsxError::InvalidSheetOperation(msg~)) =>
inspect(msg, content="drawing image column offset out of range")
_ => fail("expected drawing offset overflow")
}
let extent_result : Result[Int, Error] = Ok(
drawing_scaled_extent_for_write(2147483647.0, 2.0, "drawing shape width"),
) catch {
error => Err(error)
}
match extent_result {
Err(XlsxError::InvalidSheetOperation(msg~)) =>
inspect(msg, content="drawing shape width out of range")
_ => fail("expected drawing extent overflow")
}
}
///|
test "write hardening wb: write_drawing_xml covers chart absolute with sheet anchors" {
let sheet = Worksheet::new("Sheet1")
let xml = write_drawing_xml(
Some(sheet),
[],
[wb_drawing_chart(Absolute)],
[],
[],
)
inspect(
xml.contains(""),
content="true",
)
inspect(wb_count_substr(xml, ""), content="1")
}
///|
test "write hardening wb: write_drawing_xml covers slicer positioning and defaults" {
let two = wb_drawing_slicer("SlicerTwo", None, Some(0.0), Some(-1.0), true)
let abs = wb_drawing_slicer("SlicerAbs", Some(Absolute), None, None, false)
let one = wb_drawing_slicer("SlicerOne", Some(OneCell), None, None, false)
let xml = write_drawing_xml(None, [], [], [], [two, abs, one])
inspect(xml.contains("Requires=\"sle15\""), content="true")
inspect(xml.contains("Requires=\"a14\""), content="true")
inspect(
xml.contains(""),
content="true",
)
inspect(xml.contains(""), content="true")
inspect(
xml.contains("fLocksWithSheet=\"1\" fPrintsWithSheet=\"1\""),
content="true",
)
}
///|
test "write hardening wb: sheet property emitters cover option branches" {
let empty_xml = write_sheet_pr_xml(SheetPropsOptions::new())
inspect(empty_xml.contains(""), content="true")
let tab_indexed_xml = write_sheet_pr_xml(
SheetPropsOptions::with_values(tab_color_indexed=4),
)
inspect(tab_indexed_xml.contains(""), content="true")
let outline_right_xml = write_sheet_pr_xml(
SheetPropsOptions::with_values(outline_summary_right=true),
)
inspect(
outline_right_xml.contains(""),
content="true",
)
let outline_below_xml = write_sheet_pr_xml(
SheetPropsOptions::with_values(outline_summary_below=false),
)
inspect(
outline_below_xml.contains(""),
content="true",
)
let page_setup_xml = write_sheet_pr_xml(
SheetPropsOptions::with_values(auto_page_breaks=true),
)
inspect(
page_setup_xml.contains(""),
content="true",
)
let format_xml = write_sheet_format_pr_xml(
SheetPropsOptions::with_values(base_col_width=8),
)
inspect(
format_xml.contains(" value
None => fail("missing sheet views xml")
}
inspect(xml.contains("rightToLeft=\"1\""), content="true")
inspect(xml.contains("showFormulas=\"1\""), content="true")
inspect(xml.contains("showRuler=\"0\""), content="true")
inspect(xml.contains("showZeros=\"0\""), content="true")
}
///|
test "write hardening wb: worksheet_dimension covers cell merge and table min/max branches" {
let sheet = Worksheet::new("Dim")
sheet.set_cell("D3", "x")
sheet.set_cell("C4", "y")
sheet.merge_cells("B2:B5")
ignore(sheet.add_table("A1:E6", "DimTable", ["A", "B", "C", "D", "E"]))
let dimension = worksheet_dimension(sheet) catch {
err => fail("worksheet_dimension failed: \{repr(err)}")
}
debug_inspect(dimension, content="Some(\"A1:E6\")")
}
///|
test "write hardening wb: worksheet_dimension covers auto-filter expansion branches" {
let sheet = Worksheet::new("FilterDim")
sheet.set_cell("C3", "x")
sheet.auto_filter = Some({ range_ref: "B2:D4", columns: [] })
let dimension = worksheet_dimension(sheet) catch {
err => fail("worksheet_dimension failed: \{repr(err)}")
}
debug_inspect(dimension, content="Some(\"B2:D4\")")
}
///|
test "write hardening wb: write_worksheet_xml covers x14 map aggregation and unknown ext fallback" {
let sheet = Worksheet::new("Exts")
sheet.x14_data_bars["skip"] = {
sqref: "A1",
bar_direction: "",
bar_solid: false,
bar_border_color: "",
}
sheet.x14_data_bars["id1"] = {
sqref: "A1",
bar_direction: "leftToRight",
bar_solid: false,
bar_border_color: "",
}
sheet.x14_data_bars["id2"] = {
sqref: "A1",
bar_direction: "",
bar_solid: true,
bar_border_color: "",
}
sheet.unknown_ext_blocks.push("")
let xml = write_worksheet_xml(
sheet,
Map([]),
Map([]),
Map([]),
[],
None,
None,
[],
[],
[],
[],
None,
None,
) catch {
err => fail("write_worksheet_xml failed: \{repr(err)}")
}
inspect(xml.contains(""), content="true")
inspect(xml.contains(""), content="true")
inspect(xml.contains(""), content="true")
}
///|
test "write hardening wb: write_theme_xml covers custom palette and fallback branches" {
let custom = write_theme_xml(
Some([
"111111", "222222", "333333", "444444", "555555", "666666", "777777", "888888",
"999999", "AAAAAA",
]),
)
inspect(custom.contains(""), content="true")
inspect(custom.contains("val=\"111111\""), content="true")
let fallback = write_theme_xml(Some(["FFFFFF"]))
inspect(fallback == default_theme_xml, content="true")
}
///|
test "write hardening wb: write_worksheet_xml covers formula bool and error branches" {
let sheet = Worksheet::new("FormulaBranches")
sheet.cells.push({
reference: "A1",
row: 1,
col: 1,
value: "1",
value_type: Bool,
rich_text: None,
formula: Some("1=1"),
formula_type: None,
formula_ref: None,
formula_shared_index: None,
formula_value_present: true,
style_explicit: false,
style_id: 0,
})
sheet.cells.push({
reference: "A2",
row: 2,
col: 1,
value: "#N/A",
value_type: Error,
rich_text: None,
formula: Some("NA()"),
formula_type: None,
formula_ref: None,
formula_shared_index: None,
formula_value_present: true,
style_explicit: false,
style_id: 0,
})
let xml = write_worksheet_xml(
sheet,
Map([]),
Map([]),
Map([]),
[],
None,
None,
[],
[],
[],
[],
None,
None,
) catch {
err => fail("write_worksheet_xml failed: \{repr(err)}")
}
inspect(xml.contains("r=\"A1\" t=\"b\""), content="true")
inspect(xml.contains("1=11"), content="true")
inspect(xml.contains("r=\"A2\" t=\"e\""), content="true")
inspect(xml.contains("NA()#N/A"), content="true")
}
///|
test "write hardening wb: write_worksheet_xml covers shared-string and hyperlink error branches" {
let rich_missing = Worksheet::new("RichMissing")
rich_missing.cells.push({
reference: "A1",
row: 1,
col: 1,
value: "ignored",
value_type: String,
rich_text: Some([{ text: "R", font: None }]),
formula: None,
formula_type: None,
formula_ref: None,
formula_shared_index: None,
formula_value_present: false,
style_explicit: false,
style_id: 0,
})
let missing_rich : Result[String, Error] = Ok(
write_worksheet_xml(
rich_missing,
Map([]),
Map([]),
Map([]),
[],
None,
None,
[],
[],
[],
[],
None,
None,
),
) catch {
e => Err(e)
}
inspect(
missing_rich is Err(XlsxError::InvalidSharedString(_)),
content="true",
)
let plain_missing = Worksheet::new("PlainMissing")
plain_missing.cells.push({
reference: "A1",
row: 1,
col: 1,
value: "plain",
value_type: String,
rich_text: None,
formula: None,
formula_type: None,
formula_ref: None,
formula_shared_index: None,
formula_value_present: false,
style_explicit: false,
style_id: 0,
})
let missing_plain : Result[String, Error] = Ok(
write_worksheet_xml(
plain_missing,
Map([]),
Map([]),
Map([]),
[],
None,
None,
[],
[],
[],
[],
None,
None,
),
) catch {
e => Err(e)
}
inspect(
missing_plain is Err(XlsxError::InvalidSharedString(_)),
content="true",
)
let no_hyperlinks_xml = write_worksheet_xml(
Worksheet::new("NoLinks"),
Map([]),
Map([]),
Map([]),
[
{
reference: "A1",
target: "",
link_type: Unset,
location: None,
display: None,
tooltip: None,
},
],
None,
None,
[],
[],
[],
[],
None,
None,
) catch {
err => fail("write_worksheet_xml failed: \{repr(err)}")
}
inspect(no_hyperlinks_xml.contains(""), content="false")
let missing_rel : Result[String, Error] = Ok(
write_worksheet_xml(
Worksheet::new("MissingRel"),
Map([]),
Map([]),
Map([]),
[
{
reference: "A1",
target: "https://example.com",
link_type: External,
location: None,
display: None,
tooltip: None,
},
],
None,
None,
[],
[],
[],
[],
None,
None,
),
) catch {
e => Err(e)
}
inspect(missing_rel is Err(XlsxError::InvalidHyperlink(_)), content="true")
}
///|
test "write hardening wb: write_with_io covers file-path extension fallback branches" {
let workbook = Workbook::new()
ignore(workbook.add_sheet("Sheet1"))
let no_ext = write_with_io(workbook, {
file_path: Some("demo"),
zip_writer: None,
charset_transcoder: None,
}) catch {
err => fail("write_with_io without extension failed: \{repr(err)}")
}
inspect(no_ext.length() > 0, content="true")
let unsupported_ext = write_with_io(workbook, {
file_path: Some("demo.unsupported"),
zip_writer: None,
charset_transcoder: None,
}) catch {
err => fail("write_with_io unsupported extension failed: \{repr(err)}")
}
inspect(unsupported_ext.length() > 0, content="true")
}
///|
test "write hardening wb: write_with_password empty password returns raw bytes" {
let workbook = Workbook::new()
ignore(workbook.add_sheet("Sheet1"))
let raw = write(workbook) catch { err => fail("write failed: \{repr(err)}") }
let with_empty_password = write_with_password(workbook, "") catch {
err => fail("write_with_password failed: \{repr(err)}")
}
inspect(raw == with_empty_password, content="true")
}
///|
test "write hardening wb: write merges base vml with form-control and comment vml" {
let workbook = Workbook::new()
let sheet = workbook.add_sheet("Sheet1")
sheet.add_vml_drawing_xml("base")
sheet.add_form_control(FormControl::new("A1", "Button"))
sheet.add_comment({
cell: "B2",
author: "A",
author_id: None,
text: "c",
paragraph: [],
width: None,
height: None,
})
let bytes = write(workbook) catch {
err => fail("write failed: \{repr(err)}")
}
inspect(bytes.length() > 0, content="true")
}
///|
test "write hardening wb: write fails for slicer source missing" {
let workbook = Workbook::new()
let sheet = workbook.add_sheet("Sheet1")
sheet.slicers.push({
name: "BadSlicer",
cache: "Slicer_BadSlicer",
source_name: "Field",
cell: "A1",
table_sheet: "Sheet1",
table_name: "MissingTable",
caption: "",
macro_name: "",
width: 200,
height: 200,
display_header: None,
item_desc: false,
format: GraphicOptions::new(),
drawing_offset_x_emu: None,
drawing_offset_y_emu: None,
drawing_width_emu: None,
drawing_height_emu: None,
drawing_order: None,
})
let result : Result[Bytes, Error] = Ok(write(workbook)) catch { e => Err(e) }
inspect(result is Err(XlsxError::InvalidSheetOperation(_)), content="true")
}
///|
test "write hardening wb: write fails for slicer table field missing" {
let workbook = Workbook::new()
let sheet = workbook.add_sheet("Sheet1")
ignore(sheet.add_table("A1:B2", "Table1", ["A", "B"]))
sheet.slicers.push({
name: "BadField",
cache: "Slicer_BadField",
source_name: "MissingColumn",
cell: "A1",
table_sheet: "Sheet1",
table_name: "Table1",
caption: "",
macro_name: "",
width: 200,
height: 200,
display_header: None,
item_desc: false,
format: GraphicOptions::new(),
drawing_offset_x_emu: None,
drawing_offset_y_emu: None,
drawing_width_emu: None,
drawing_height_emu: None,
drawing_order: None,
})
let result : Result[Bytes, Error] = Ok(write(workbook)) catch { e => Err(e) }
inspect(result is Err(XlsxError::InvalidSheetOperation(_)), content="true")
}
///|
test "write hardening wb: write handles duplicate pivot cache relationship entries" {
let workbook = Workbook::new()
let sheet = workbook.add_sheet("Sheet1")
sheet.pivot_tables.push({
name: "",
table_id: 1,
cache_id: 1,
table_xml: "",
cache_definition_xml: "",
cache_records_xml: None,
})
sheet.pivot_tables.push({
name: "",
table_id: 2,
cache_id: 1,
table_xml: "",
cache_definition_xml: "",
cache_records_xml: None,
})
let bytes = write(workbook) catch {
err => fail("write failed: \{repr(err)}")
}
inspect(bytes.length() > 0, content="true")
}
///|
test "write hardening wb: failing custom zip writer maps to UnsupportedFeature" {
let workbook = Workbook::new()
ignore(workbook.add_sheet("Sheet1"))
workbook.set_zip_writer(_archive => {
raise InvalidSheetOperation(msg="zip writer failed")
})
let result : Result[Bytes, Error] = Ok(workbook.write_to_buffer()) catch {
e => Err(e)
}
inspect(result is Err(XlsxError::UnsupportedFeature(_)), content="true")
}
///|
test "write hardening wb: default zip writer failure maps to UnsupportedFeature" {
let workbook = Workbook::new()
let sheet = workbook.add_sheet("Sheet1")
sheet.images.push({
reference: "A1",
data: @encoding/utf8.encode("x"),
extension: "a".repeat(70000),
content_type: "image/png",
offset_x: 0,
offset_y: 0,
scale_x: 1.0,
scale_y: 1.0,
width_emu: 1,
height_emu: 1,
print_object: true,
locked: true,
hyperlink: "",
hyperlink_type: Unset,
name: "",
alt_text: "",
lock_aspect_ratio: false,
positioning: TwoCell,
drawing_offset_x_emu: None,
drawing_offset_y_emu: None,
drawing_width_emu: None,
drawing_height_emu: None,
drawing_order: None,
})
let result : Result[Bytes, Error] = Ok(write(workbook)) catch { e => Err(e) }
inspect(result is Err(XlsxError::UnsupportedFeature(_)), content="true")
}