source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+[[package]]
+name = "aes"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+]
+
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
+[[package]]
+name = "arbitrary"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223"
+dependencies = [
+ "derive_arbitrary",
+]
+
[[package]]
name = "array-init"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
[[package]]
name = "bumpalo"
-version = "3.16.0"
+version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
+checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]]
name = "bytemuck"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540"
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
[[package]]
name = "bytes"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
+[[package]]
+name = "bzip2"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47"
+dependencies = [
+ "bzip2-sys",
+]
+
+[[package]]
+name = "bzip2-sys"
+version = "0.1.13+1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
+
[[package]]
name = "cairo-rs"
version = "0.20.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48"
dependencies = [
+ "jobserver",
+ "libc",
"shlex",
]
"windows-link",
]
+[[package]]
+name = "cipher"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
+dependencies = [
+ "crypto-common",
+ "inout",
+]
+
[[package]]
name = "clap"
version = "4.5.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
+[[package]]
+name = "constant_time_eq"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
+
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc"
+version = "3.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675"
+dependencies = [
+ "crc-catalog",
+]
+
+[[package]]
+name = "crc-catalog"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
+
[[package]]
name = "crc32fast"
version = "1.4.2"
"cfg-if",
]
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
[[package]]
name = "dashmap"
version = "5.5.3"
"parking_lot_core",
]
+[[package]]
+name = "deflate64"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b"
+
+[[package]]
+name = "deranged"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "derive_arbitrary"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.87",
+]
+
[[package]]
name = "derive_more"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+ "subtle",
+]
+
[[package]]
name = "either"
version = "1.13.0"
"slab",
]
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
[[package]]
name = "getrandom"
version = "0.3.2"
checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
dependencies = [
"cfg-if",
+ "js-sys",
"libc",
"r-efi",
"wasi 0.14.2+wasi-0.2.4",
+ "wasm-bindgen",
]
[[package]]
"termcolor",
]
+[[package]]
+name = "hmac"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
+dependencies = [
+ "digest",
+]
+
[[package]]
name = "httparse"
version = "1.9.4"
"hashbrown",
]
+[[package]]
+name = "inout"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
+dependencies = [
+ "generic-array",
+]
+
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+[[package]]
+name = "jobserver"
+version = "0.1.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "js-sys"
-version = "0.3.70"
+version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a"
+checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
dependencies = [
+ "once_cell",
"wasm-bindgen",
]
[[package]]
name = "log"
-version = "0.4.22"
+version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
+checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "lsp-types"
"url",
]
+[[package]]
+name = "lzma-rs"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e"
+dependencies = [
+ "byteorder",
+ "crc",
+]
+
+[[package]]
+name = "lzma-sys"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+]
+
[[package]]
name = "matrixmultiply"
version = "0.3.9"
"num-traits",
]
+[[package]]
+name = "num-conv"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
[[package]]
name = "num-integer"
version = "0.1.46"
"windows-targets 0.52.6",
]
+[[package]]
+name = "pbkdf2"
+version = "0.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
+dependencies = [
+ "digest",
+ "hmac",
+]
+
[[package]]
name = "percent-encoding"
version = "2.3.1"
"portable-atomic",
]
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
[[package]]
name = "ppv-lite86"
version = "0.2.21"
"unicode-linebreak",
"unicode-width",
"windows-sys 0.48.0",
+ "xmlwriter",
+ "zip",
]
[[package]]
"windows-sys 0.52.0",
]
+[[package]]
+name = "rustversion"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
+
[[package]]
name = "ryu"
version = "1.0.18"
"serde",
]
+[[package]]
+name = "sha1"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
[[package]]
name = "shlex"
version = "1.3.0"
"libc",
]
+[[package]]
+name = "simd-adler32"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
+
[[package]]
name = "slab"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
[[package]]
name = "syn"
version = "1.0.109"
"syn 2.0.87",
]
+[[package]]
+name = "time"
+version = "0.3.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
+dependencies = [
+ "deranged",
+ "num-conv",
+ "powerfmt",
+ "serde",
+ "time-core",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
+
[[package]]
name = "tinyvec"
version = "1.8.0"
"once_cell",
]
+[[package]]
+name = "typenum"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
+
[[package]]
name = "unicase"
version = "2.7.0"
[[package]]
name = "wasm-bindgen"
-version = "0.2.93"
+version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5"
+checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
dependencies = [
"cfg-if",
"once_cell",
+ "rustversion",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
-version = "0.2.93"
+version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
+checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
dependencies = [
"bumpalo",
"log",
- "once_cell",
"proc-macro2",
"quote",
"syn 2.0.87",
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.93"
+version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf"
+checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.93"
+version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
+checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.93"
+version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
+checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
+dependencies = [
+ "unicode-ident",
+]
[[package]]
name = "winapi"
"bitflags 2.6.0",
]
+[[package]]
+name = "xmlwriter"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
+
+[[package]]
+name = "xz2"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2"
+dependencies = [
+ "lzma-sys",
+]
+
[[package]]
name = "zerocopy"
version = "0.8.24"
"quote",
"syn 2.0.87",
]
+
+[[package]]
+name = "zeroize"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
+dependencies = [
+ "zeroize_derive",
+]
+
+[[package]]
+name = "zeroize_derive"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.87",
+]
+
+[[package]]
+name = "zip"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dcb24d0152526ae49b9b96c1dcf71850ca1e0b882e4e28ed898a93c41334744"
+dependencies = [
+ "aes",
+ "arbitrary",
+ "bzip2",
+ "constant_time_eq",
+ "crc32fast",
+ "crossbeam-utils",
+ "deflate64",
+ "flate2",
+ "getrandom",
+ "hmac",
+ "indexmap",
+ "lzma-rs",
+ "memchr",
+ "pbkdf2",
+ "sha1",
+ "time",
+ "xz2",
+ "zeroize",
+ "zopfli",
+ "zstd",
+]
+
+[[package]]
+name = "zopfli"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7"
+dependencies = [
+ "bumpalo",
+ "crc32fast",
+ "log",
+ "simd-adler32",
+]
+
+[[package]]
+name = "zstd"
+version = "0.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
+dependencies = [
+ "zstd-safe",
+]
+
+[[package]]
+name = "zstd-safe"
+version = "7.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
+dependencies = [
+ "zstd-sys",
+]
+
+[[package]]
+name = "zstd-sys"
+version = "2.0.15+zstd.1.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
--- /dev/null
+use core::f64;
+use std::{
+ borrow::Cow,
+ fmt::Write as _,
+ io::{Cursor, Result as IoResult, Seek, Write},
+ sync::Arc,
+};
+
+use binrw::{BinWrite, Endian};
+use quick_xml::{
+ events::{attributes::Attribute, BytesText},
+ writer::Writer as XmlWriter,
+ ElementWriter,
+};
+use serde::Serialize;
+use smallstr::SmallString;
+use zip::{result::ZipResult, write::SimpleFileOptions, ZipWriter};
+
+use crate::{
+ format::{Format, Type},
+ output::{
+ driver::Driver,
+ pivot::{
+ Axis2, CellStyle, Color, FontStyle, HeadingRegion, HorzAlign, PivotTable, Value,
+ ValueInner, ValueStyle, VertAlign,
+ },
+ Item,
+ },
+ settings::Show,
+};
+
+fn light_table_name(table_id: u64) -> String {
+ format!("{:010}_lightTableData.bin", table_id)
+}
+
+pub struct SpvWriter<W>
+where
+ W: Write + Seek,
+{
+ writer: ZipWriter<W>,
+ needs_page_break: bool,
+ next_table_id: u64,
+}
+
+impl<W> SpvWriter<W>
+where
+ W: Write + Seek,
+{
+ pub fn new(writer: W) -> Self {
+ Self {
+ writer: ZipWriter::new(writer),
+ needs_page_break: false,
+ next_table_id: 1,
+ }
+ }
+
+ pub fn close(mut self) -> ZipResult<W> {
+ self.writer
+ .start_file("META-INF/MANIFEST.MF", SimpleFileOptions::default())?;
+ write!(&mut self.writer, "allowPivoting=true")?;
+ self.writer.finish()
+ }
+
+ fn page_break_before(&mut self) -> bool {
+ let page_break_before = self.needs_page_break;
+ self.needs_page_break = false;
+ page_break_before
+ }
+
+ fn write_table(&mut self, item: &Item, pivot_table: &PivotTable) -> Container {
+ let table_id = self.next_table_id;
+ self.next_table_id += 1;
+
+ let mut content = Vec::new();
+ let mut cursor = Cursor::new(&mut content);
+ Header::new(pivot_table).write_le(&mut cursor).unwrap();
+
+ self.writer
+ .start_file(light_table_name(table_id), SimpleFileOptions::default())
+ .unwrap(); // XXX
+ self.writer.write_all(&content).unwrap(); // XXX
+
+ Container {
+ page_break_before: self.page_break_before(),
+ label: Label(item.label().into_owned()),
+ show: item.show,
+ command_name: item.command_name.clone(),
+ content: Content::Table(Table {
+ table_properties: None,
+ table_structure: TableStructure,
+ table_id,
+ subtype: match &pivot_table.subtype {
+ Some(subtype) => subtype.display(pivot_table).to_string(),
+ None => String::from("unknown"),
+ },
+ }),
+ }
+ }
+}
+
+impl<W> Driver for SpvWriter<W>
+where
+ W: Write + Seek,
+{
+ fn name(&self) -> Cow<'static, str> {
+ Cow::from("spv")
+ }
+
+ fn write(&mut self, item: &Arc<Item>) {
+ match &item.details {
+ super::Details::Chart => todo!(),
+ super::Details::Image => todo!(),
+ super::Details::Group(items) => todo!(),
+ super::Details::Message(diagnostic) => todo!(),
+ super::Details::PageBreak => {
+ self.needs_page_break = true;
+ return;
+ }
+ super::Details::Table(pivot_table) => self.write_table(&*item, pivot_table),
+ super::Details::Text(text) => todo!(),
+ };
+ todo!()
+ }
+}
+
+struct Heading {
+ command_name: Option<String>,
+ show: bool,
+ label: Label,
+ children: Vec<Child>,
+}
+
+impl Heading {
+ fn emit<W>(&self, writer: &mut XmlWriter<W>) -> IoResult<()>
+ where
+ W: Write,
+ {
+ let mut element = writer.create_element("heading");
+ if let Some(command_name) = &self.command_name {
+ element = element.with_attribute(("commandName", command_name.as_str()));
+ }
+ if !self.show {
+ element = element.with_attribute(("visibility", "collapsed"));
+ }
+ element.write_inner_content(|writer| {
+ self.label.emit(writer)?;
+ Ok(())
+ })?;
+ Ok(())
+ }
+}
+
+enum Child {
+ Container(Container),
+ Heading(Box<Heading>),
+}
+
+impl Child {
+ fn emit<W>(&self, writer: &mut XmlWriter<W>) -> IoResult<()>
+ where
+ W: Write,
+ {
+ match self {
+ Child::Container(container) => container.emit(writer),
+ Child::Heading(heading) => heading.emit(writer),
+ }
+ }
+}
+
+fn maybe_with_attribute<'a, 'b, W, I>(
+ element: ElementWriter<'a, W>,
+ attr: Option<I>,
+) -> ElementWriter<'a, W>
+where
+ I: Into<Attribute<'b>>,
+{
+ if let Some(attr) = attr {
+ element.with_attribute(attr)
+ } else {
+ element
+ }
+}
+
+struct Container {
+ page_break_before: bool,
+ label: Label,
+ show: bool,
+ command_name: Option<String>,
+ content: Content,
+}
+
+impl Container {
+ fn emit<W>(&self, writer: &mut XmlWriter<W>) -> IoResult<()>
+ where
+ W: Write,
+ {
+ let mut element = writer
+ .create_element("container")
+ .with_attribute(("visibility", if self.show { "visible" } else { "hidden" }));
+ if self.page_break_before {
+ element = element.with_attribute(("page-break-before", "always"));
+ }
+ element.write_inner_content(|writer| {
+ self.label.emit(writer)?;
+ self.content
+ .emit(writer, self.command_name.as_ref().map(|name| name.as_str()))?;
+ Ok(())
+ })?;
+ Ok(())
+ }
+}
+
+struct Label(String);
+
+impl Label {
+ fn emit<W>(&self, writer: &mut XmlWriter<W>) -> IoResult<()>
+ where
+ W: Write,
+ {
+ writer
+ .create_element("label")
+ .write_text_content(BytesText::new(&self.0))?;
+ Ok(())
+ }
+}
+
+enum Content {
+ Table(Table),
+}
+
+impl Content {
+ fn emit<W>(&self, writer: &mut XmlWriter<W>, command_name: Option<&str>) -> IoResult<()>
+ where
+ W: Write,
+ {
+ match self {
+ Content::Table(table) => table.emit(writer, command_name),
+ }
+ }
+
+ fn element<'a, W>(
+ writer: &'a mut XmlWriter<W>,
+ name: &'static str,
+ command_name: Option<&str>,
+ ) -> ElementWriter<'a, W> {
+ let element = writer.create_element(name);
+ let element = maybe_with_attribute(
+ element,
+ command_name.map(|command_name| ("commandName", command_name)),
+ );
+ element
+ }
+}
+
+struct Table {
+ table_properties: Option<()>,
+
+ table_structure: TableStructure,
+ table_id: u64,
+ subtype: String,
+}
+
+impl Table {
+ fn emit<W>(&self, writer: &mut XmlWriter<W>, command_name: Option<&str>) -> IoResult<()>
+ where
+ W: Write,
+ {
+ Content::element(writer, "vtb:table", command_name)
+ .with_attribute(("type", "table"))
+ .with_attribute(("tableId", Cow::from(format!("{}", self.table_id))))
+ .with_attribute(("subtype", self.subtype.as_str()))
+ .write_inner_content(|w| {
+ w.create_element("vtb:TableStructure")
+ .write_inner_content(|w| {
+ w.create_element("vtb:dataPath")
+ .write_text_content(BytesText::new(&light_table_name(self.table_id)))?;
+ Ok(())
+ })?;
+ Ok(())
+ })?;
+ Ok(())
+ }
+}
+
+#[derive(Serialize)]
+struct TableStructure;
+
+struct Bool(bool);
+impl BinWrite for Bool {
+ type Args<'a> = ();
+
+ fn write_options<W: Write + Seek>(
+ &self,
+ writer: &mut W,
+ endian: binrw::Endian,
+ args: Self::Args<'_>,
+ ) -> binrw::BinResult<()> {
+ (self.0 as u8).write_options(writer, endian, args)
+ }
+}
+
+struct SpvString<'a>(&'a str);
+impl<'a> SpvString<'a> {
+ fn optional(s: &'a Option<String>) -> Self {
+ Self(s.as_ref().map_or("", |s| s.as_str()))
+ }
+}
+impl BinWrite for SpvString<'_> {
+ type Args<'a> = ();
+
+ fn write_options<W: Write + Seek>(
+ &self,
+ writer: &mut W,
+ endian: binrw::Endian,
+ args: Self::Args<'_>,
+ ) -> binrw::BinResult<()> {
+ let length = self.0.len() as u32;
+ (length, self.0.as_bytes()).write_options(writer, endian, args)
+ }
+}
+
+#[derive(BinWrite)]
+#[bw(little)]
+struct Header {
+ #[bw(magic(1u16))]
+ version: u8,
+
+ x0: Bool,
+ x1: Bool,
+ rotate_inner_column_labels: Bool,
+ rotate_outer_row_labels: Bool,
+ x2: Bool,
+ x3: u32,
+ min_col_heading_width: i32,
+ max_col_heading_width: i32,
+ min_row_heading_width: i32,
+ max_row_heading_width: i32,
+}
+
+impl Header {
+ fn new(pivot_table: &PivotTable) -> Self {
+ Self {
+ version: 3,
+ x0: Bool(true),
+ x1: Bool(false),
+ rotate_inner_column_labels: Bool(pivot_table.rotate_inner_column_labels),
+ rotate_outer_row_labels: Bool(pivot_table.rotate_outer_row_labels),
+ x2: Bool(true),
+ x3: 0x15,
+ min_col_heading_width: *pivot_table.look.heading_widths[HeadingRegion::Columns].start()
+ as i32,
+ max_col_heading_width: *pivot_table.look.heading_widths[HeadingRegion::Columns].end()
+ as i32,
+ min_row_heading_width: *pivot_table.look.heading_widths[HeadingRegion::Rows].start()
+ as i32,
+ max_row_heading_width: *pivot_table.look.heading_widths[HeadingRegion::Rows].end()
+ as i32,
+ }
+ }
+}
+
+impl Show {
+ fn as_spv(this: &Option<Show>) -> u8 {
+ match this {
+ None => 0,
+ Some(Show::Value) => 1,
+ Some(Show::Label) => 2,
+ Some(Show::Both) => 3,
+ }
+ }
+}
+
+struct Count(u64);
+
+impl Count {
+ fn new<W>(writer: &mut W) -> binrw::BinResult<Self>
+ where
+ W: Write + Seek,
+ {
+ 0u32.write_le(writer)?;
+ Ok(Self(writer.stream_position()?))
+ }
+
+ fn finish<W>(self, writer: &mut W, endian: Endian) -> binrw::BinResult<()>
+ where
+ W: Write + Seek,
+ {
+ let saved_position = writer.stream_position()?;
+ let n_bytes = saved_position - self.0;
+ writer.seek(std::io::SeekFrom::Start(self.0 - 4))?;
+ (n_bytes as u32).write_options(writer, endian, ())?;
+ writer.seek(std::io::SeekFrom::Start(saved_position))?;
+ Ok(())
+ }
+
+ fn finish_le32<W>(self, writer: &mut W) -> binrw::BinResult<()>
+ where
+ W: Write + Seek,
+ {
+ self.finish(writer, Endian::Little)
+ }
+
+ fn finish_be32<W>(self, writer: &mut W) -> binrw::BinResult<()>
+ where
+ W: Write + Seek,
+ {
+ self.finish(writer, Endian::Big)
+ }
+}
+
+#[derive(Default)]
+struct StylePair<'a> {
+ font_style: Option<&'a FontStyle>,
+ cell_style: Option<&'a CellStyle>,
+}
+
+impl BinWrite for Color {
+ type Args<'a> = ();
+
+ fn write_options<W: Write + Seek>(
+ &self,
+ writer: &mut W,
+ endian: Endian,
+ args: Self::Args<'_>,
+ ) -> binrw::BinResult<()> {
+ let mut s = SmallString::<[u8; 16]>::new();
+ write!(&mut s, "{}", self.without_alpha().display_css()).unwrap();
+ SpvString(&s).write_options(writer, endian, args)
+ }
+}
+
+impl BinWrite for FontStyle {
+ type Args<'a> = ();
+
+ fn write_options<W: Write + Seek>(
+ &self,
+ writer: &mut W,
+ endian: Endian,
+ args: Self::Args<'_>,
+ ) -> binrw::BinResult<()> {
+ let typeface = if self.font.is_empty() {
+ "SansSerif"
+ } else {
+ self.font.as_str()
+ };
+ (
+ Bool(self.bold),
+ Bool(self.italic),
+ Bool(self.underline),
+ Bool(true),
+ self.fg[0],
+ self.bg[0],
+ SpvString(typeface),
+ (self.size as f64 * 1.33).ceil() as u8,
+ )
+ .write_options(writer, endian, args)
+ }
+}
+
+impl HorzAlign {
+ fn as_spv(&self, decimal: u32) -> u32 {
+ match self {
+ HorzAlign::Right => 4,
+ HorzAlign::Left => 2,
+ HorzAlign::Center => 0,
+ HorzAlign::Decimal { .. } => decimal,
+ }
+ }
+
+ fn decimal_offset(&self) -> Option<f64> {
+ match *self {
+ HorzAlign::Decimal { offset, .. } => Some(offset),
+ _ => None,
+ }
+ }
+}
+
+impl VertAlign {
+ fn as_spv(&self) -> u32 {
+ match self {
+ VertAlign::Top => 1,
+ VertAlign::Middle => 0,
+ VertAlign::Bottom => 3,
+ }
+ }
+}
+
+impl BinWrite for CellStyle {
+ type Args<'a> = ();
+
+ fn write_options<W: Write + Seek>(
+ &self,
+ writer: &mut W,
+ endian: Endian,
+ args: Self::Args<'_>,
+ ) -> binrw::BinResult<()> {
+ (
+ self.horz_align
+ .map_or(0xffffffad, |horz_align| horz_align.as_spv(6)),
+ self.vert_align.as_spv(),
+ self.horz_align
+ .map(|horz_align| horz_align.decimal_offset())
+ .unwrap_or_default(),
+ u16::try_from(self.margins[Axis2::X][0]).unwrap_or_default(),
+ u16::try_from(self.margins[Axis2::X][1]).unwrap_or_default(),
+ u16::try_from(self.margins[Axis2::Y][0]).unwrap_or_default(),
+ u16::try_from(self.margins[Axis2::Y][1]).unwrap_or_default(),
+ )
+ .write_options(writer, endian, args)
+ }
+}
+
+impl<'a> BinWrite for StylePair<'a> {
+ type Args<'b> = ();
+
+ fn write_options<W: Write + Seek>(
+ &self,
+ writer: &mut W,
+ endian: Endian,
+ args: Self::Args<'_>,
+ ) -> binrw::BinResult<()> {
+ if let Some(font_style) = self.font_style {
+ (0x31u8, font_style).write_options(writer, endian, args)?;
+ } else {
+ 0x58u8.write_options(writer, endian, args)?;
+ }
+
+ if let Some(cell_style) = self.cell_style {
+ (0x31u8, cell_style).write_options(writer, endian, args)?;
+ } else {
+ 0x58u8.write_options(writer, endian, args)?;
+ }
+
+ Ok(())
+ }
+}
+
+struct OptionalStyle<'a> {
+ style: &'a Option<Box<ValueStyle>>,
+ template: Option<&'a str>,
+}
+
+impl<'a> BinWrite for OptionalStyle<'a> {
+ type Args<'b> = ();
+
+ fn write_options<W: Write + Seek>(
+ &self,
+ writer: &mut W,
+ endian: binrw::Endian,
+ args: Self::Args<'_>,
+ ) -> binrw::BinResult<()> {
+ if self.style.as_ref().is_some_and(|style| !style.is_empty()) || self.template.is_some() {
+ 0x31u8.write_options(writer, endian, args)?;
+ let default_style = Default::default();
+ let style = self.style.as_ref().unwrap_or(&default_style);
+
+ (style.footnotes.len() as u32).write_options(writer, endian, args)?;
+ for footnote in &style.footnotes {
+ (footnote.index() as u16).write_options(writer, endian, args)?;
+ }
+
+ (style.subscripts.len() as u32).write_options(writer, endian, args)?;
+ for subscript in &style.subscripts {
+ SpvString(subscript.as_str()).write_options(writer, endian, args)?;
+ }
+ let v3_start = Count::new(writer)?;
+ let template_string_start = Count::new(writer)?;
+ if let Some(template) = self.template {
+ Count::new(writer)?.finish_le32(writer)?;
+ (0x31u8, SpvString(template)).write_options(writer, endian, args)?;
+ }
+ template_string_start.finish_le32(writer)?;
+ style
+ .style
+ .as_ref()
+ .map_or_else(
+ || StylePair::default(),
+ |area_style| StylePair {
+ font_style: Some(&area_style.font_style),
+ cell_style: Some(&area_style.cell_style),
+ },
+ )
+ .write_options(writer, endian, args)?;
+ v3_start.finish_le32(writer)
+ } else {
+ 0x58u8.write_options(writer, endian, args)
+ }
+ }
+}
+
+struct SpvFormat {
+ format: Format,
+ honor_small: bool,
+}
+
+impl BinWrite for SpvFormat {
+ type Args<'a> = ();
+
+ fn write_options<W: Write + Seek>(
+ &self,
+ writer: &mut W,
+ endian: binrw::Endian,
+ args: Self::Args<'_>,
+ ) -> binrw::BinResult<()> {
+ let type_ = if self.format.type_() == Type::F && self.honor_small {
+ 40
+ } else {
+ self.format.type_().into()
+ };
+ (((type_ as u32) << 16) | ((self.format.w() as u32) << 8) | (self.format.d() as u32))
+ .write_options(writer, endian, args)
+ }
+}
+
+impl BinWrite for Value {
+ type Args<'a> = ();
+
+ fn write_options<W: Write + Seek>(
+ &self,
+ writer: &mut W,
+ endian: binrw::Endian,
+ args: Self::Args<'_>,
+ ) -> binrw::BinResult<()> {
+ match &self.inner {
+ ValueInner::Number {
+ show,
+ format,
+ honor_small,
+ value,
+ var_name,
+ value_label,
+ } => {
+ if var_name.is_some() || value_label.is_some() {
+ 2u8.write_options(writer, endian, args)?;
+ //write_optional_style(self.styling.as_ref(),writer, endian, args)?;
+ (
+ SpvFormat {
+ format: *format,
+ honor_small: *honor_small,
+ },
+ value.unwrap_or(-f64::MAX),
+ SpvString::optional(var_name),
+ SpvString::optional(value_label),
+ Show::as_spv(show),
+ )
+ .write_options(writer, endian, args)?;
+ } else {
+ 1u8.write_options(writer, endian, args)?;
+ //write_optional_style(self.styling.as_ref(),writer, endian, args)?;
+ value
+ .unwrap_or(-f64::MAX)
+ .write_options(writer, endian, args)?;
+ Show::as_spv(show).write_options(writer, endian, args)?;
+ }
+ }
+ ValueInner::String {
+ show,
+ hex,
+ s,
+ var_name,
+ value_label,
+ } => todo!(),
+ ValueInner::Variable {
+ show,
+ var_name,
+ variable_label,
+ } => todo!(),
+ ValueInner::Text {
+ user_provided,
+ local,
+ c,
+ id,
+ } => todo!(),
+ ValueInner::Template { args, local, id } => todo!(),
+ ValueInner::Empty => todo!(),
+ }
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::output::spv::Heading;
+
+ #[test]
+ fn serialize() {
+ let heading = Heading {
+ command_name: Some("foo".into()),
+ show: false,
+ label: super::Label("bar".into()),
+ children: Vec::new(),
+ };
+ let mut output = Vec::new();
+ let mut writer = quick_xml::writer::Writer::new(&mut output);
+ heading.emit(&mut writer).unwrap();
+ drop(writer);
+ let output = String::from_utf8(output).unwrap();
+ println!("{output}");
+ }
+}