///|
let media_type : String = "application/vnd.oasis.opendocument.spreadsheet"
///|
fn manifest_xml() -> String {
""
}
///|
fn styles_xml() -> String {
"\{preset_styles()}"
}
// ODF needs mimetype first. Use the shared DEFLATE library's CRC directly,
// without pulling in another ZIP implementation and its incompatible version.
// Fixed ASCII paths only, no assets or path input; no timestamps/extras/comments.
///|
fn stored_package(files : Array[(String, Bytes)]) -> Bytes raise OdsError {
let body = @buffer.Buffer()
let central = @buffer.Buffer()
for (path, data) in files {
let name = @utf8.encode(path[:])
let offset = body.length()
let size = data.length()
if size > 33554432 || offset + size > 33554432 {
raise Invalid("package exceeds 32 MiB")
}
let crc = @checksum.crc32(data[:]).reinterpret_as_int()
body.write_int_le(0x04034b50)
for n in [20, 0, 0, 0, 33] {
body.write_uint16_le(n.to_uint16())
}
for n in [crc, size, size] {
body.write_int_le(n)
}
body.write_uint16_le(name.length().to_uint16())
body.write_uint16_le(0)
body.write_bytes(name[:])
body.write_bytes(data[:])
central.write_int_le(0x02014b50)
for n in [20, 20, 0, 0, 0, 33] {
central.write_uint16_le(n.to_uint16())
}
for n in [crc, size, size] {
central.write_int_le(n)
}
for n in [name.length(), 0, 0, 0, 0] {
central.write_uint16_le(n.to_uint16())
}
central.write_int_le(0)
central.write_int_le(offset)
central.write_bytes(name[:])
}
let cd_offset = body.length()
let cd_size = central.length()
body.write_bytes(central.to_bytes()[:])
body.write_int_le(0x06054b50)
for n in [0, 0, files.length(), files.length()] {
body.write_uint16_le(n.to_uint16())
}
body.write_int_le(cd_size)
body.write_int_le(cd_offset)
body.write_uint16_le(0)
if body.length() > 33554432 {
raise Invalid("package exceeds 32 MiB")
}
body.to_bytes()
}
///|
/// Return a deterministic ODS within the reader's bounded output profile:
/// 4 MiB total uncompressed entry bytes and the existing XML depth/node caps.
/// Grid/text limits alone do not guarantee serializability. Oversized output
/// raises before returning any package; I/O belongs to the caller.
pub fn Workbook::to_ods(self : Workbook) -> Bytes raise OdsError {
let content = self.content_xml()
let files = [
("mimetype", @utf8.encode(media_type[:])),
("content.xml", @utf8.encode(content[:])),
(
"styles.xml",
@utf8.encode(
styles_xml().replace(
old="",
new=self.typed_styles() + "",
)[:],
),
),
("META-INF/manifest.xml", @utf8.encode(manifest_xml()[:])),
]
let mut total = 0
for (_, data) in files {
if data.length() > 4194304 - total {
raise Invalid("ODS output exceeds 4 MiB reader profile")
}
total += data.length()
}
read_xml(content) |> ignore
stored_package(files)
}