From f1562e0a4f1e45ac2ecc556aae3da912af0df959 Mon Sep 17 00:00:00 2001 From: Jan Marvin Garbuszus Date: Sat, 15 Jul 2023 14:28:42 +0200 Subject: [PATCH] read encrypted xlsx files --- DESCRIPTION | 2 + R/RcppExports.R | 4 + R/wb_load.R | 8 + configure | 78 + src/Makevars | 43 +- src/Makevars.in | 42 + src/RcppExports.cpp | 13 + src/decrypt.cpp | 12 + src/xlcpp/aes.cpp | 466 ++++ src/xlcpp/aes.h | 26 + src/xlcpp/b64.cpp | 106 + src/xlcpp/b64.h | 6 + src/xlcpp/cfbf.cpp | 847 ++++++++ src/xlcpp/cfbf.h | 77 + src/xlcpp/mmap.cpp | 472 +++++ src/xlcpp/mmap.h | 151 ++ src/xlcpp/sha1.cpp | 263 +++ src/xlcpp/sha1.h | 35 + src/xlcpp/sha512.cpp | 174 ++ src/xlcpp/sha512.h | 16 + src/xlcpp/utf16.h | 346 +++ src/xlcpp/xlcpp-pimpl.h | 364 ++++ src/xlcpp/xlcpp.cpp | 2070 ++++++++++++++++++ src/xlcpp/xlcpp.h | 155 ++ src/xlcpp/xlsb.cpp | 565 +++++ src/xlcpp/xlsb.h | 2707 ++++++++++++++++++++++++ src/xlcpp/xml-reader.cpp | 398 ++++ src/xlcpp/xml-writer.cpp | 77 + tests/testthat/helper.R | 3 +- tests/testthat/test-loading_workbook.R | 12 + 30 files changed, 9536 insertions(+), 2 deletions(-) create mode 100755 configure create mode 100644 src/Makevars.in create mode 100644 src/decrypt.cpp create mode 100644 src/xlcpp/aes.cpp create mode 100644 src/xlcpp/aes.h create mode 100644 src/xlcpp/b64.cpp create mode 100644 src/xlcpp/b64.h create mode 100644 src/xlcpp/cfbf.cpp create mode 100644 src/xlcpp/cfbf.h create mode 100644 src/xlcpp/mmap.cpp create mode 100644 src/xlcpp/mmap.h create mode 100644 src/xlcpp/sha1.cpp create mode 100644 src/xlcpp/sha1.h create mode 100644 src/xlcpp/sha512.cpp create mode 100644 src/xlcpp/sha512.h create mode 100644 src/xlcpp/utf16.h create mode 100644 src/xlcpp/xlcpp-pimpl.h create mode 100644 src/xlcpp/xlcpp.cpp create mode 100644 src/xlcpp/xlcpp.h create mode 100644 src/xlcpp/xlsb.cpp create mode 100644 src/xlcpp/xlsb.h create mode 100644 src/xlcpp/xml-reader.cpp create mode 100644 src/xlcpp/xml-writer.cpp diff --git a/DESCRIPTION b/DESCRIPTION index befc45c3d..40b2ff2fb 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -43,3 +43,5 @@ Roxygen: list(markdown = TRUE) Config/testthat/edition: 3 Config/testthat/parallel: false Config/testthat/start-first: aaa +SystemRequirements: C++20, libarchive: libarchive-dev (deb), + libarchive-devel (rpm), libarchive (homebrew), libarchive_dev (csw) diff --git a/R/RcppExports.R b/R/RcppExports.R index 3ab1ad0b4..212d4424d 100644 --- a/R/RcppExports.R +++ b/R/RcppExports.R @@ -1,6 +1,10 @@ # Generated by using Rcpp::compileAttributes() -> do not edit by hand # Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 +read_encryption <- function(PATH, OUT, PASSWORD) { + invisible(.Call(`_openxlsx2_read_encryption`, PATH, OUT, PASSWORD)) +} + #' Check if path is to long to be an R file path #' @param path the file path used in file.exists() #' @noRd diff --git a/R/wb_load.R b/R/wb_load.R index 3ac4019a0..e710ea97e 100644 --- a/R/wb_load.R +++ b/R/wb_load.R @@ -49,6 +49,14 @@ wb_load <- function( stop("File does not exist.") } + pwd <- list(...)$password + + if (!is.null(pwd)) { + unencrypted_xlsx <- temp_xlsx() + read_encryption(PATH = path.expand(file), OUT = unencrypted_xlsx, PASSWORD = pwd) + file <- unencrypted_xlsx + } + ## create temp dir xmlDir <- temp_dir("_openxlsx_wb_load") diff --git a/configure b/configure new file mode 100755 index 000000000..e984c6134 --- /dev/null +++ b/configure @@ -0,0 +1,78 @@ +# Anticonf (tm) script by Jeroen Ooms, Jim Hester (2017) +# This script will query 'pkg-config' for the required cflags and ldflags. +# If pkg-config is unavailable or does not find the library, try setting +# INCLUDE_DIR and LIB_DIR manually via e.g: +# R CMD INSTALL --configure-vars='INCLUDE_DIR=/.../include LIB_DIR=/.../lib' + +# Library settings +PKG_CONFIG_NAME="libarchive" +PKG_DEB_NAME="libarchive-dev" +PKG_RPM_NAME="libarchive-devel" +PKG_CSW_NAME="libarchive_dev" +PKG_BREW_NAME="libarchive" +PKG_TEST_HEADER="" +PKG_LIBS=-larchive + +# Use pkg-config if available +pkg-config ${PKG_CONFIG_NAME} --atleast-version=1.0 2>/dev/null +if [ $? -eq 0 ]; then + PKGCONFIG_CFLAGS=`pkg-config --cflags ${PKG_CONFIG_NAME}` + PKGCONFIG_LIBS=`pkg-config --libs ${PKG_CONFIG_NAME}` +fi + +# Note that cflags may be empty in case of success +if [ "$INCLUDE_DIR" ] || [ "$LIB_DIR" ]; then + echo "Found INCLUDE_DIR and/or LIB_DIR!" + PKG_CFLAGS="-I$INCLUDE_DIR $PKG_CFLAGS" + PKG_LIBS="-L$LIB_DIR $PKG_LIBS" +elif [ "$PKGCONFIG_CFLAGS" ] || [ "$PKGCONFIG_LIBS" ]; then + echo "Found pkg-config cflags and libs!" + PKG_CFLAGS=${PKGCONFIG_CFLAGS} + PKG_LIBS=${PKGCONFIG_LIBS} +elif [ `uname` = "Darwin" ]; then + test ! "$CI" && brew --version 2>/dev/null + if [ $? -eq 0 ]; then + BREWDIR=`brew --prefix` + PKG_CFLAGS="-I$BREWDIR/opt/libarchive/include" + PKG_LIBS="-L$BREWDIR/opt/libarchive/lib $PKG_LIBS" + else + curl -sfL "https://autobrew.github.io/scripts/$PKG_BREW_NAME" > autobrew + . ./autobrew + fi +fi + +# Find compiler +CXX20=`${R_HOME}/bin/R CMD config CXX20` +CXX20FLAGS=`${R_HOME}/bin/R CMD config CXX20FLAGS` +CPPFLAGS=`${R_HOME}/bin/R CMD config CPPFLAGS` + +# For debugging +echo "PKG_CFLAGS=$PKG_CFLAGS" +echo "PKG_LIBS=$PKG_LIBS" + +# Test configuration +echo "#include $PKG_TEST_HEADER" | ${CXX20} ${CPPFLAGS} ${PKG_CFLAGS} ${CXX20FLAGS} -E -xc++ - > /dev/null + +# Customize the error +if [ $? -ne 0 ]; then + echo "--------------------------- [ANTICONF] --------------------------------" + echo "Configuration failed because $PKG_CONFIG_NAME was not found. Try installing:" + echo " * deb: $PKG_DEB_NAME (Debian, Ubuntu, etc)" + echo " * rpm: $PKG_RPM_NAME (Fedora, CentOS, RHEL)" + echo " * csw: $PKG_CSW_NAME (Solaris)" + echo " * brew: $PKG_BREW_NAME (Mac OSX)" + echo "If $PKG_CONFIG_NAME is already installed, check that 'pkg-config' is in your" + echo "PATH and PKG_CONFIG_PATH contains a $PKG_CONFIG_NAME.pc file. If pkg-config" + echo "is unavailable you can set INCLUDE_DIR and LIB_DIR manually via:" + echo "R CMD INSTALL --configure-vars='INCLUDE_DIR=... LIB_DIR=...'" + echo "-------------------------- [ERROR MESSAGE] ---------------------------" + cat configure.log + echo "--------------------------------------------------------------------" + exit 1 +fi + +# Write to Makevars +sed -e "s|@PKG_CXXFLAGS@|$PKG_CFLAGS|" -e "s|@PKG_LIBS@|$PKG_LIBS|" src/Makevars.in > src/Makevars + +# Success +exit 0 diff --git a/src/Makevars b/src/Makevars index ee4055f80..49045080d 100644 --- a/src/Makevars +++ b/src/Makevars @@ -1 +1,42 @@ -PKG_CPPFLAGS = -I. -I../inst/include/pugixml \ No newline at end of file + +#PKG_CPPFLAGS = -I. -I../inst/include/pugixml -Ixlcpp -DXLCPP_EXPORT +PKG_CXXFLAGS = -I. -Ixlcpp -I../inst/include/pugixml -DXLCPP_EXPORT + +#PKG_LIBS=-Lpugixml -Lcfbf -L/usr/lib -larchive -L/usr/lib -lfmt +PKG_LIBS = -larchive -Lpugixml -Lxlcpp -lfmt + +PKGROOT = ./xlcpp + +OBJECTS = decrypt.o \ + helper_functions.o \ + load_workbook.o \ + pugi.o \ + strings_xml.o \ + styles_xml.o \ + write_file.o \ + $(PKGROOT)/aes.o \ + $(PKGROOT)/b64.o \ + $(PKGROOT)/cfbf.o \ + $(PKGROOT)/mmap.o \ + $(PKGROOT)/sha1.o \ + $(PKGROOT)/sha512.o \ + $(PKGROOT)/xlcpp.o \ + $(PKGROOT)/xlsb.o \ + $(PKGROOT)/xml-reader.o \ + $(PKGROOT)/xml-writer.o \ + RcppExports.o + +XLCPP = $(PKGROOT)/aes.cpp \ + $(PKGROOT)/b64.cpp \ + $(PKGROOT)/cfbf.cpp \ + $(PKGROOT)/mmap.cpp \ + $(PKGROOT)/sha1.cpp \ + $(PKGROOT)/sha512.cpp \ + $(PKGROOT)/xml-reader.cpp \ + $(PKGROOT)/xml-writer.cpp \ + $(PKGROOT)/xlcpp.cpp \ + $(PKGROOT)/xlsb.cpp + +PUGIXML = ../inst/include/pugixml/pugixml.cpp + +# CXX_STD=CXX20 diff --git a/src/Makevars.in b/src/Makevars.in new file mode 100644 index 000000000..ca584e421 --- /dev/null +++ b/src/Makevars.in @@ -0,0 +1,42 @@ + +#PKG_CPPFLAGS = -I. -I../inst/include/pugixml -Ixlcpp -DXLCPP_EXPORT +PKG_CXXFLAGS = @PKG_CXXFLAGS@ -I. -Ixlcpp -I../inst/include/pugixml -DXLCPP_EXPORT + +#PKG_LIBS=-Lpugixml -Lcfbf -L/usr/lib -larchive -L/usr/lib -lfmt +PKG_LIBS = @PKG_LIBS@ -Lpugixml -Lxlcpp -lfmt + +PKGROOT = ./xlcpp + +OBJECTS = decrypt.o \ + helper_functions.o \ + load_workbook.o \ + pugi.o \ + strings_xml.o \ + styles_xml.o \ + write_file.o \ + $(PKGROOT)/aes.o \ + $(PKGROOT)/b64.o \ + $(PKGROOT)/cfbf.o \ + $(PKGROOT)/mmap.o \ + $(PKGROOT)/sha1.o \ + $(PKGROOT)/sha512.o \ + $(PKGROOT)/xlcpp.o \ + $(PKGROOT)/xlsb.o \ + $(PKGROOT)/xml-reader.o \ + $(PKGROOT)/xml-writer.o \ + RcppExports.o + +XLCPP = $(PKGROOT)/aes.cpp \ + $(PKGROOT)/b64.cpp \ + $(PKGROOT)/cfbf.cpp \ + $(PKGROOT)/mmap.cpp \ + $(PKGROOT)/sha1.cpp \ + $(PKGROOT)/sha512.cpp \ + $(PKGROOT)/xml-reader.cpp \ + $(PKGROOT)/xml-writer.cpp \ + $(PKGROOT)/xlcpp.cpp \ + $(PKGROOT)/xlsb.cpp + +PUGIXML = ../inst/include/pugixml/pugixml.cpp + +# CXX_STD=CXX20 diff --git a/src/RcppExports.cpp b/src/RcppExports.cpp index 22ac1e9d4..fefe5b0ec 100644 --- a/src/RcppExports.cpp +++ b/src/RcppExports.cpp @@ -11,6 +11,18 @@ Rcpp::Rostream& Rcpp::Rcout = Rcpp::Rcpp_cout_get(); Rcpp::Rostream& Rcpp::Rcerr = Rcpp::Rcpp_cerr_get(); #endif +// read_encryption +void read_encryption(std::string PATH, std::string OUT, std::string PASSWORD); +RcppExport SEXP _openxlsx2_read_encryption(SEXP PATHSEXP, SEXP OUTSEXP, SEXP PASSWORDSEXP) { +BEGIN_RCPP + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< std::string >::type PATH(PATHSEXP); + Rcpp::traits::input_parameter< std::string >::type OUT(OUTSEXP); + Rcpp::traits::input_parameter< std::string >::type PASSWORD(PASSWORDSEXP); + read_encryption(PATH, OUT, PASSWORD); + return R_NilValue; +END_RCPP +} // to_long bool to_long(std::string path); RcppExport SEXP _openxlsx2_to_long(SEXP pathSEXP) { @@ -842,6 +854,7 @@ END_RCPP } static const R_CallMethodDef CallEntries[] = { + {"_openxlsx2_read_encryption", (DL_FUNC) &_openxlsx2_read_encryption, 3}, {"_openxlsx2_to_long", (DL_FUNC) &_openxlsx2_to_long, 1}, {"_openxlsx2_openxlsx2_type", (DL_FUNC) &_openxlsx2_openxlsx2_type, 1}, {"_openxlsx2_int_to_col", (DL_FUNC) &_openxlsx2_int_to_col, 1}, diff --git a/src/decrypt.cpp b/src/decrypt.cpp new file mode 100644 index 000000000..e0de3cd74 --- /dev/null +++ b/src/decrypt.cpp @@ -0,0 +1,12 @@ +#include + +// [[Rcpp::export]] +void read_encryption(std::string PATH, std::string OUT, std::string PASSWORD) { + + const std::filesystem::path path = PATH; + + xlcpp::workbook wb(path, PASSWORD, OUT); + + // this creates xlcpp workbook, not the unzipped xlsx file + // wb.save(OUT); +} diff --git a/src/xlcpp/aes.cpp b/src/xlcpp/aes.cpp new file mode 100644 index 000000000..683b2c397 --- /dev/null +++ b/src/xlcpp/aes.cpp @@ -0,0 +1,466 @@ +/* + +This is an implementation of the AES algorithm, specifically ECB, CTR and CBC mode. +Block size can be chosen in aes.h - available choices are AES128, AES192, AES256. + +The implementation is verified against the test vectors in: + National Institute of Standards and Technology Special Publication 800-38A 2001 ED + +ECB-AES128 +---------- + + plain-text: + 6bc1bee22e409f96e93d7e117393172a + ae2d8a571e03ac9c9eb76fac45af8e51 + 30c81c46a35ce411e5fbc1191a0a52ef + f69f2445df4f9b17ad2b417be66c3710 + + key: + 2b7e151628aed2a6abf7158809cf4f3c + + resulting cipher + 3ad77bb40d7a3660a89ecaf32466ef97 + f5d3d58503b9699de785895a96fdbaaf + 43b1cd7f598ece23881b00e3ed030688 + 7b0c785e27e8ad3f8223207104725dd4 + + +NOTE: String length must be evenly divisible by 16byte (str_len % 16 == 0) + You should pad the end of the string with zeros if this is not the case. + For AES192/256 the key size is proportionally larger. + +*/ + +/*****************************************************************************/ +/* Includes: */ +/*****************************************************************************/ +#include "aes.h" +#include + +/*****************************************************************************/ +/* Defines: */ +/*****************************************************************************/ +// The number of columns comprising a state in AES. This is a constant in AES. Value=4 +#define Nb 4 + +#define Nk_128 4 // The number of 32 bit words in a key. +#define Nr_128 10 // The number of rounds in AES Cipher. + +#define Nk_256 8 +#define Nr_256 14 + +/*****************************************************************************/ +/* Private variables: */ +/*****************************************************************************/ +// state - array holding the intermediate results during decryption. +typedef uint8_t state_t[4][4]; + +// The lookup-tables are marked const so they can be placed in read-only storage instead of RAM +// The numbers below can be computed dynamically trading ROM for RAM - +// This can be useful in (embedded) bootloader applications, where ROM is often limited. +static const uint8_t sbox[256] = { + //0 1 2 3 4 5 6 7 8 9 A B C D E F + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 }; + +static const uint8_t rsbox[256] = { + 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, + 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, + 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, + 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, + 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, + 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, + 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, + 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, + 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, + 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, + 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d }; + +// The round constant word array, Rcon[i], contains the values given by +// x to the power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8) +static const uint8_t Rcon[11] = { + 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 }; + +// This function produces Nb(Nr+1) round keys. The round keys are used in each round to decrypt the states. +template +static void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key) { + uint8_t tempa[4]; // Used for the column/row operations + + // The first round key is the key itself. + for (unsigned int i = 0; i < Nk; i++) { + RoundKey[i * 4] = Key[i * 4]; + RoundKey[(i * 4) + 1] = Key[(i * 4) + 1]; + RoundKey[(i * 4) + 2] = Key[(i * 4) + 2]; + RoundKey[(i * 4) + 3] = Key[(i * 4) + 3]; + } + + // All other round keys are found from the previous round keys. + for (unsigned int i = Nk; i < Nb * (Nr + 1); i++) { + tempa[0] = RoundKey[(i * 4) - 4]; + tempa[1] = RoundKey[(i * 4) - 3]; + tempa[2] = RoundKey[(i * 4) - 2]; + tempa[3] = RoundKey[(i * 4) - 1]; + + if (i % Nk == 0) { + // This function shifts the 4 bytes in a word to the left once. + // [a0,a1,a2,a3] becomes [a1,a2,a3,a0] + + // Function RotWord() + { + const uint8_t u8tmp = tempa[0]; + tempa[0] = tempa[1]; + tempa[1] = tempa[2]; + tempa[2] = tempa[3]; + tempa[3] = u8tmp; + } + + // SubWord() is a function that takes a four-byte input word and + // applies the S-box to each of the four bytes to produce an output word. + + // Function Subword() + { + tempa[0] = sbox[tempa[0]]; + tempa[1] = sbox[tempa[1]]; + tempa[2] = sbox[tempa[2]]; + tempa[3] = sbox[tempa[3]]; + } + + tempa[0] = tempa[0] ^ Rcon[i/Nk]; + } + + if constexpr (Nk == Nk_256) { + if (i % Nk == 4) { + // Function Subword() + tempa[0] = sbox[tempa[0]]; + tempa[1] = sbox[tempa[1]]; + tempa[2] = sbox[tempa[2]]; + tempa[3] = sbox[tempa[3]]; + } + } + + RoundKey[i * 4] = RoundKey[(i - Nk) * 4] ^ tempa[0]; + RoundKey[(i * 4) + 1] = RoundKey[((i - Nk) * 4) + 1] ^ tempa[1]; + RoundKey[(i * 4) + 2] = RoundKey[((i - Nk) * 4) + 2] ^ tempa[2]; + RoundKey[(i * 4) + 3] = RoundKey[((i - Nk) * 4) + 3] ^ tempa[3]; + } +} + +void AES128_init_ctx(struct AES_ctx* ctx, const uint8_t* key) { + KeyExpansion(ctx->RoundKey, key); +} + +void AES128_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv) { + KeyExpansion(ctx->RoundKey, key); + memcpy(ctx->Iv, iv, AES_BLOCKLEN); +} + +void AES256_init_ctx(struct AES_ctx* ctx, const uint8_t* key) { + KeyExpansion(ctx->RoundKey, key); +} + +void AES256_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv) { + KeyExpansion(ctx->RoundKey, key); + memcpy(ctx->Iv, iv, AES_BLOCKLEN); +} + +// This function adds the round key to state. +// The round key is added to the state by an XOR function. +static void AddRoundKey(uint8_t round, state_t& state, const uint8_t* RoundKey) { + for (uint8_t i = 0; i < 4; i++) { + for (uint8_t j = 0; j < 4; ++j) { + state[i][j] ^= RoundKey[(round * Nb * 4) + (i * Nb) + j]; + } + } +} + +// The SubBytes Function Substitutes the values in the +// state matrix with values in an S-box. +static void SubBytes(state_t& state) { + for (uint8_t i = 0; i < 4; i++) { + for (uint8_t j = 0; j < 4; j++) { + state[j][i] = sbox[state[j][i]]; + } + } +} + +// The ShiftRows() function shifts the rows in the state to the left. +// Each row is shifted with different offset. +// Offset = Row number. So the first row is not shifted. +static void ShiftRows(state_t& state) { + uint8_t temp; + + // Rotate first row 1 columns to left + temp = state[0][1]; + state[0][1] = state[1][1]; + state[1][1] = state[2][1]; + state[2][1] = state[3][1]; + state[3][1] = temp; + + // Rotate second row 2 columns to left + temp = state[0][2]; + state[0][2] = state[2][2]; + state[2][2] = temp; + + temp = state[1][2]; + state[1][2] = state[3][2]; + state[3][2] = temp; + + // Rotate third row 3 columns to left + temp = state[0][3]; + state[0][3] = state[3][3]; + state[3][3] = state[2][3]; + state[2][3] = state[1][3]; + state[1][3] = temp; +} + +static constexpr uint8_t galois_double(uint8_t x) { + return (uint8_t)((x << 1) ^ (((x >> 7) & 1) * 0x1b)); +} + +static_assert(galois_double(0x00) == 0x00); +static_assert(galois_double(0x01) == 0x02); +static_assert(galois_double(0x7f) == 0xfe); +static_assert(galois_double(0x80) == 0x1b); +static_assert(galois_double(0xff) == 0xe5); + +// MixColumns function mixes the columns of the state matrix +static void MixColumns(state_t& state) { + for (uint8_t i = 0; i < 4; i++) { + auto t = state[i][0]; + uint8_t tmp = state[i][0] ^ state[i][1] ^ state[i][2] ^ state[i][3]; + + for (unsigned int j = 0; j < 4; j++) { + uint8_t tm = state[i][j]; + + if (j == 3) + tm ^= t; + else + tm ^= state[i][j+1]; + + tm = galois_double(tm); + state[i][j] ^= tm ^ tmp; + } + } +} + +// Multiply is used to multiply numbers in the field GF(2^8) +static constexpr uint8_t Multiply(uint8_t x, uint8_t y) { + uint8_t ret = (y & 1) * x; + + x = galois_double(x); + + if (y & 2) + ret ^= x; + + x = galois_double(x); + + if (y & 4) + ret ^= x; + + if (y & 8) { + x = galois_double(x); + ret ^= x; + } + + return ret; +} + +// MixColumns function mixes the columns of the state matrix. +// The method used to multiply may be difficult to understand for the inexperienced. +// Please use the references to gain more information. +static void InvMixColumns(state_t& state) { + for (unsigned int i = 0; i < 4; i++) { + auto a = state[i][0]; + auto b = state[i][1]; + auto c = state[i][2]; + auto d = state[i][3]; + + state[i][0] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09); + state[i][1] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d); + state[i][2] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b); + state[i][3] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e); + } +} + + +// The SubBytes Function Substitutes the values in the +// state matrix with values in an S-box. +static void InvSubBytes(state_t& state) { + for (uint8_t i = 0; i < 4; i++) { + for (uint8_t j = 0; j < 4; j++) { + state[j][i] = rsbox[state[j][i]]; + } + } +} + +static void InvShiftRows(state_t& state) { + uint8_t temp; + + // Rotate first row 1 columns to right + temp = state[3][1]; + state[3][1] = state[2][1]; + state[2][1] = state[1][1]; + state[1][1] = state[0][1]; + state[0][1] = temp; + + // Rotate second row 2 columns to right + temp = state[0][2]; + state[0][2] = state[2][2]; + state[2][2] = temp; + + temp = state[1][2]; + state[1][2] = state[3][2]; + state[3][2] = temp; + + // Rotate third row 3 columns to right + temp = state[0][3]; + state[0][3] = state[1][3]; + state[1][3] = state[2][3]; + state[2][3] = state[3][3]; + state[3][3] = temp; +} + +// Cipher is the main function that encrypts the PlainText. +template +static void Cipher(state_t& state, const uint8_t* RoundKey) { + // Add the First round key to the state before starting the rounds. + AddRoundKey(0, state, RoundKey); + + // There will be Nr rounds. + // The first Nr-1 rounds are identical. + // These Nr rounds are executed in the loop below. + // Last one without MixColumns() + for (uint8_t round = 1; ; round++) { + SubBytes(state); + ShiftRows(state); + + if (round == Nr) + break; + + MixColumns(state); + AddRoundKey(round, state, RoundKey); + } + + // Add round key to last round + AddRoundKey(Nr, state, RoundKey); +} + +template +static void InvCipher(state_t& state, const uint8_t* RoundKey) { + // Add the First round key to the state before starting the rounds. + AddRoundKey(Nr, state, RoundKey); + + // There will be Nr rounds. + // The first Nr-1 rounds are identical. + // These Nr rounds are executed in the loop below. + // Last one without InvMixColumn() + for (uint8_t round = Nr - 1; ; round--) { + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(round, state, RoundKey); + + if (round == 0) + break; + + InvMixColumns(state); + } +} + +/*****************************************************************************/ +/* Public functions: */ +/*****************************************************************************/ + +static void XorWithIv(uint8_t* buf, const uint8_t* Iv) { + for (uint8_t i = 0; i < AES_BLOCKLEN; i++) { // The block in AES is always 128bit no matter the key size + buf[i] ^= Iv[i]; + } +} + +void AES128_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf) { + // The next function call encrypts the PlainText with the Key using AES algorithm. + Cipher(*(state_t*)buf, ctx->RoundKey); +} + +void AES128_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf) { + // The next function call decrypts the PlainText with the Key using AES algorithm. + InvCipher(*(state_t*)buf, ctx->RoundKey); +} + +void AES128_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) { + auto Iv = ctx->Iv; + + for (size_t i = 0; i < length; i += AES_BLOCKLEN) { + XorWithIv(buf, Iv); + Cipher(*(state_t*)buf, ctx->RoundKey); + Iv = buf; + buf += AES_BLOCKLEN; + } + /* store Iv in ctx for next call */ + memcpy(ctx->Iv, Iv, AES_BLOCKLEN); +} + +void AES128_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) { + uint8_t storeNextIv[AES_BLOCKLEN]; + + for (size_t i = 0; i < length; i += AES_BLOCKLEN) { + memcpy(storeNextIv, buf, AES_BLOCKLEN); + InvCipher(*(state_t*)buf, ctx->RoundKey); + XorWithIv(buf, ctx->Iv); + memcpy(ctx->Iv, storeNextIv, AES_BLOCKLEN); + buf += AES_BLOCKLEN; + } +} + +void AES256_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf) { + // The next function call encrypts the PlainText with the Key using AES algorithm. + Cipher(*(state_t*)buf, ctx->RoundKey); +} + +void AES256_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf) { + // The next function call decrypts the PlainText with the Key using AES algorithm. + InvCipher(*(state_t*)buf, ctx->RoundKey); +} + +void AES256_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) { + auto Iv = ctx->Iv; + + for (size_t i = 0; i < length; i += AES_BLOCKLEN) { + XorWithIv(buf, Iv); + Cipher(*(state_t*)buf, ctx->RoundKey); + Iv = buf; + buf += AES_BLOCKLEN; + } + /* store Iv in ctx for next call */ + memcpy(ctx->Iv, Iv, AES_BLOCKLEN); +} + +void AES256_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) { + uint8_t storeNextIv[AES_BLOCKLEN]; + + for (size_t i = 0; i < length; i += AES_BLOCKLEN) { + memcpy(storeNextIv, buf, AES_BLOCKLEN); + InvCipher(*(state_t*)buf, ctx->RoundKey); + XorWithIv(buf, ctx->Iv); + memcpy(ctx->Iv, storeNextIv, AES_BLOCKLEN); + buf += AES_BLOCKLEN; + } +} diff --git a/src/xlcpp/aes.h b/src/xlcpp/aes.h new file mode 100644 index 000000000..bbe1d63c2 --- /dev/null +++ b/src/xlcpp/aes.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +#define AES_BLOCKLEN 16 // Block length in bytes - AES is 128b block only +#define AES_keyExpSize 240 + +struct AES_ctx { + uint8_t RoundKey[AES_keyExpSize]; + uint8_t Iv[AES_BLOCKLEN]; +}; + +void AES128_init_ctx(struct AES_ctx* ctx, const uint8_t* key); +void AES128_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv); +void AES128_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf); +void AES128_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf); +void AES128_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); +void AES128_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); + +void AES256_init_ctx(struct AES_ctx* ctx, const uint8_t* key); +void AES256_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv); +void AES256_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf); +void AES256_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf); +void AES256_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); +void AES256_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); diff --git a/src/xlcpp/b64.cpp b/src/xlcpp/b64.cpp new file mode 100644 index 000000000..35250950a --- /dev/null +++ b/src/xlcpp/b64.cpp @@ -0,0 +1,106 @@ +/* +* Base64 encoding/decoding (RFC1341) +* Copyright (c) 2005-2011, Jouni Malinen +* +* This software may be distributed under the terms of the BSD license. +* See README for more details. +*/ + +// 2016-12-12 - Gaspard Petit : Slightly modified to return a std::string +// instead of a buffer allocated with malloc. + +#include +#include "b64.h" + +static const unsigned char base64_table[65] = +"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/** +* base64_encode - Base64 encode +* @src: Data to be encoded +* @len: Length of the data to be encoded +* @out_len: Pointer to output length variable, or %NULL if not used +* Returns: Allocated buffer of out_len bytes of encoded data, +* or empty string on failure +*/ +std::string b64encode(std::string_view sv) { + const unsigned char* src = (const unsigned char*)sv.data(); + size_t len = sv.length(); + unsigned char *out, *pos; + const unsigned char *end, *in; + + size_t olen; + + olen = 4*((len + 2) / 3); /* 3-byte blocks to 4-byte */ + + if (olen < len) + return std::string(); /* integer overflow */ + + std::string outStr; + outStr.resize(olen); + out = (unsigned char*)&outStr[0]; + + end = src + len; + in = src; + pos = out; + while (end - in >= 3) { + *pos++ = base64_table[in[0] >> 2]; + *pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)]; + *pos++ = base64_table[((in[1] & 0x0f) << 2) | (in[2] >> 6)]; + *pos++ = base64_table[in[2] & 0x3f]; + in += 3; + } + + if (end - in) { + *pos++ = base64_table[in[0] >> 2]; + if (end - in == 1) { + *pos++ = base64_table[(in[0] & 0x03) << 4]; + *pos++ = '='; + } + else { + *pos++ = base64_table[((in[0] & 0x03) << 4) | + (in[1] >> 4)]; + *pos++ = base64_table[(in[1] & 0x0f) << 2]; + } + *pos++ = '='; + } + + return outStr; +} + +static const int B64index[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 63, 62, 62, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 63, + 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 +}; + +std::string b64decode(std::string_view sv) { + auto p = (unsigned char*)sv.data(); + int pad = sv.length() > 0 && (sv.length() % 4 || p[sv.length() - 1] == '='); + const size_t L = ((sv.length() + 3) / 4 - pad) * 4; + std::string str(L / 4 * 3 + pad, '\0'); + + for (size_t i = 0, j = 0; i < L; i += 4) { + int n = B64index[p[i]] << 18 | B64index[p[i + 1]] << 12 | B64index[p[i + 2]] << 6 | B64index[p[i + 3]]; + str[j++] = (char)(n >> 16); + str[j++] = (char)(n >> 8 & 0xFF); + str[j++] = (char)(n & 0xFF); + } + + if (pad) { + int n = B64index[p[L]] << 18 | B64index[p[L + 1]] << 12; + str[str.size() - 1] = (char)(n >> 16); + + if (sv.length() > L + 2 && p[L + 2] != '=') { + n |= B64index[p[L + 2]] << 6; + str.push_back((char)(n >> 8 & 0xFF)); + } + } + + return str; +} diff --git a/src/xlcpp/b64.h b/src/xlcpp/b64.h new file mode 100644 index 000000000..01dfaa9cb --- /dev/null +++ b/src/xlcpp/b64.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +std::string b64encode(std::string_view sv); +std::string b64decode(std::string_view sv); diff --git a/src/xlcpp/cfbf.cpp b/src/xlcpp/cfbf.cpp new file mode 100644 index 000000000..94896d1dc --- /dev/null +++ b/src/xlcpp/cfbf.cpp @@ -0,0 +1,847 @@ +#include "openxlsx2.h" + +#include +#include +#include +#include "cfbf.h" +#include "utf16.h" +#include "sha1.h" +#include "sha512.h" +#include "aes.h" +#include "b64.h" +#include "xlcpp-pimpl.h" + +using namespace std; + +static const uint32_t NOSTREAM = 0xffffffff; + +struct structured_storage_header { + uint64_t sig; + uint8_t clsid[16]; + uint16_t minor_version; + uint16_t major_version; + uint16_t byte_order; + uint16_t sector_shift; + uint16_t mini_sector_shift; + uint16_t reserved1; + uint32_t reserved2; + uint32_t num_sect_dir; + uint32_t num_sect_fat; + uint32_t sect_dir_start; + uint32_t transaction_signature; + uint32_t mini_sector_cutoff; + uint32_t mini_fat_start; + uint32_t num_sect_mini_fat; + uint32_t sect_dif_start; + uint32_t num_sect_dif; + uint32_t sect_dif[109]; +}; + +static_assert(sizeof(structured_storage_header) == 0x200); + +enum class obj_type : uint8_t { + STGTY_INVALID = 0, + STGTY_STORAGE = 1, + STGTY_STREAM = 2, + STGTY_LOCKBYTES = 3, + STGTY_PROPERTY = 4, + STGTY_ROOT = 5 +}; + +enum class tree_colour : uint8_t { + red = 0, + black = 1 +}; + +#pragma pack(push,1) + +struct dirent { + char16_t name[32]; + uint16_t name_len; + obj_type type; + tree_colour colour; + uint32_t sid_left_sibling; + uint32_t sid_right_sibling; + uint32_t sid_child; + uint8_t clsid[16]; + uint32_t user_flags; + uint64_t create_time; + uint64_t modify_time; + uint32_t sect_start; + uint64_t size; +}; + +#pragma pack(pop) + +static_assert(sizeof(dirent) == 0x80); + +static const string_view NS_ENCRYPTION = "http://schemas.microsoft.com/office/2006/encryption"; +static const string_view NS_PASSWORD = "http://schemas.microsoft.com/office/2006/keyEncryptor/password"; + +cfbf::cfbf(span s) : s(s) { + auto& ssh = *(structured_storage_header*)s.data(); + + if (ssh.sig != CFBF_SIGNATURE) + Rcpp::stop("Incorrect signature."); + + auto& de = *(dirent*)(s.data() + (ssh.sect_dir_start + 1) * (1 << ssh.sector_shift)); + + if (de.type != obj_type::STGTY_ROOT) + Rcpp::stop("Root directory entry did not have type STGTY_ROOT."); + + add_entry("", 0, false); +} + +const dirent& cfbf::find_dirent(uint32_t num) { + auto& ssh = *(structured_storage_header*)s.data(); + + auto dirents_per_sector = (1 << ssh.sector_shift) / sizeof(dirent); + auto sector_skip = num / dirents_per_sector; + auto sector = ssh.sect_dir_start; + + while (sector_skip > 0) { + sector = next_sector(sector); + sector_skip--; + } + + return *(dirent*)(s.data() + ((sector + 1) << ssh.sector_shift) + ((num % dirents_per_sector) * sizeof(dirent))); +} + +void cfbf::add_entry(string_view path, uint32_t num, bool ignore_right) { + const auto& de = find_dirent(num); + + if (de.sid_left_sibling != NOSTREAM) + add_entry(path, de.sid_left_sibling, true); + + auto name = de.name_len >= sizeof(char16_t) && num != 0 ? utf16_to_utf8(u16string_view(de.name, (de.name_len / sizeof(char16_t)) - 1)) : ""; + + entries.emplace_back(*this, de, string(path) + name); + + if (de.sid_child != NOSTREAM) + add_entry(string(path) + string(name) + "/", de.sid_child, false); + + if (!ignore_right && de.sid_right_sibling != NOSTREAM) + add_entry(path, de.sid_right_sibling, false); +} + +cfbf_entry::cfbf_entry(cfbf& file, const dirent& de, string_view name) : file(file), de(de), name(name) { +} + +uint32_t cfbf::next_sector(uint32_t sector) const { + auto& ssh = *(structured_storage_header*)s.data(); + auto sectors_per_dif = (1 << ssh.sector_shift) / sizeof(uint32_t); + auto fat = (uint32_t*)(s.data() + ((ssh.sect_dif[sector / sectors_per_dif] + 1) << ssh.sector_shift)); + + return fat[sector % sectors_per_dif]; +} + +uint32_t cfbf::next_mini_sector(uint32_t sector) const { + auto& ssh = *(structured_storage_header*)s.data(); + auto mini_fat = (uint32_t*)(s.data() + ((ssh.mini_fat_start + 1) << ssh.sector_shift)); + + return mini_fat[sector]; +} + +size_t cfbf_entry::read(span buf, uint64_t off) const { + auto& ssh = *(structured_storage_header*)file.s.data(); + + if (off >= de.size) + return 0; + + if (off + buf.size() > de.size) + buf = buf.subspan(0, de.size - off); + + size_t read = 0; + + if (de.size < ssh.mini_sector_cutoff) { + auto mini_sector = de.sect_start; + auto mini_sector_skip = off >> ssh.mini_sector_shift; + + for (unsigned int i = 0; i < mini_sector_skip; i++) { + mini_sector = file.next_mini_sector(mini_sector); + } + + auto mini_sectors_per_sector = 1 << (ssh.sector_shift - ssh.mini_sector_shift); + + do { + auto mini_stream_sector = mini_sector / mini_sectors_per_sector; + auto sector = file.entries[0].de.sect_start; + + while (mini_stream_sector > 0) { + sector = file.next_sector(sector); + mini_stream_sector--; + } + + auto src = file.s.subspan(((sector + 1) << ssh.sector_shift) + ((mini_sector % mini_sectors_per_sector) << ssh.mini_sector_shift), 1 << ssh.mini_sector_shift); + auto to_copy = min(src.size(), buf.size()); + + memcpy(buf.data(), src.data(), to_copy); + + read += to_copy; + buf = buf.subspan(to_copy); + + if (buf.empty()) + break; + + mini_sector = file.next_mini_sector(mini_sector); + } while (true); + } else { + auto sector = de.sect_start; + auto sector_skip = off >> ssh.sector_shift; + + for (unsigned int i = 0; i < sector_skip; i++) { + sector = file.next_sector(sector); + } + + do { + auto src = file.s.subspan((sector + 1) << ssh.sector_shift, 1 << ssh.sector_shift); + auto to_copy = min(src.size(), buf.size()); + + memcpy(buf.data(), src.data(), to_copy); + + read += to_copy; + buf = buf.subspan(to_copy); + + if (buf.empty()) + break; + + sector = file.next_sector(sector); + } while (true); + } + + return read; +} + +size_t cfbf_entry::get_size() const { + return de.size; +} + +static array generate_key(u16string_view password, span salt, unsigned int spin_count) { + array h; + + { + SHA1_CTX ctx; + + ctx.update(salt); + ctx.update(span((uint8_t*)password.data(), password.size() * sizeof(char16_t))); + + ctx.finalize(h); + } + + for (uint32_t i = 0; i < spin_count; i++) { + SHA1_CTX ctx; + + ctx.update(span((uint8_t*)&i, sizeof(uint32_t))); + ctx.update(h); + + ctx.finalize(h); + } + + { + SHA1_CTX ctx; + uint32_t block = 0; + + ctx.update(h); + ctx.update(span((uint8_t*)&block, sizeof(uint32_t))); + + ctx.finalize(h); + } + + array buf1 = { + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36 + }; + + for (unsigned int i = 0; auto c : h) { + buf1[i] ^= c; + i++; + } + + auto x1 = sha1(buf1); + + array ret; + memcpy(ret.data(), x1.data(), ret.size()); + + return ret; +} + +static void generate_key44_sha1(u16string_view password, span salt, unsigned int spin_count, + span block_key, span ret) { + array h; + + { + SHA1_CTX ctx; + + ctx.update(salt); + ctx.update(span((uint8_t*)password.data(), password.size() * sizeof(char16_t))); + + ctx.finalize(h); + } + + for (uint32_t i = 0; i < spin_count; i++) { + SHA1_CTX ctx; + + ctx.update(span((uint8_t*)&i, sizeof(uint32_t))); + ctx.update(h); + + ctx.finalize(h); + } + + { + SHA1_CTX ctx; + + ctx.update(h); + ctx.update(block_key); + + ctx.finalize(h); + } + + memcpy(ret.data(), h.data(), 16); +} + +static void generate_key44_sha512(u16string_view password, span salt, unsigned int spin_count, + span block_key, span ret) { + array h; + + { + sha512_state ctx; + + sha_init(ctx); + sha_process(ctx, salt.data(), (uint32_t)salt.size()); + sha_process(ctx, password.data(), (uint32_t)(password.size() * sizeof(char16_t))); + sha_done(ctx, h.data()); + } + + for (uint32_t i = 0; i < spin_count; i++) { + sha512_state ctx; + + sha_init(ctx); + sha_process(ctx, &i, sizeof(uint32_t)); + sha_process(ctx, h.data(), h.size()); + sha_done(ctx, h.data()); + } + + { + sha512_state ctx; + + sha_init(ctx); + sha_process(ctx, h.data(), h.size()); + sha_process(ctx, block_key.data(), (uint32_t)block_key.size()); + sha_done(ctx, h.data()); + } + + memcpy(ret.data(), h.data(), 64); +} + +#pragma pack(push, 1) + +struct encryption_info { + uint16_t major; + uint16_t minor; + uint32_t flags; + uint32_t header_size; +}; + +struct encryption_header { + uint32_t flags; + uint32_t size_extra; + uint32_t alg_id; + uint32_t alg_id_hash; + uint32_t key_size; + uint32_t provider_type; + uint32_t reserved1; + uint32_t reserved2; + char16_t csp_name[0]; +}; + +#pragma pack(pop) + +static const uint32_t ALG_ID_AES_128 = 0x660e; +static const uint32_t ALG_ID_SHA_1 = 0x8004; + +void cfbf::check_password(u16string_view password, span salt, + span encrypted_verifier, + span encrypted_verifier_hash) { + auto key = generate_key(password, salt, 50000); + AES_ctx ctx; + array verifier; + array verifier_hash; + + if (encrypted_verifier.size() != verifier.size()) + Rcpp::stop("encrypted_verifier.size() was {}, expected {}", encrypted_verifier.size(), verifier.size()); + + if (encrypted_verifier_hash.size() != verifier_hash.size()) + Rcpp::stop("encrypted_verifier_hash.size() was {}, expected {}", encrypted_verifier_hash.size(), verifier_hash.size()); + + AES128_init_ctx(&ctx, key.data()); + + memcpy(verifier.data(), encrypted_verifier.data(), encrypted_verifier.size()); + + AES128_ECB_decrypt(&ctx, verifier.data()); + +#if 0 + fmt::print("verifier = "); + for (auto c : verifier) { + fmt::print("{:02x} ", c); + } + fmt::print("\n"); +#endif + + memcpy(verifier_hash.data(), encrypted_verifier_hash.data(), encrypted_verifier_hash.size()); + + AES128_ECB_decrypt(&ctx, verifier_hash.data()); + AES128_ECB_decrypt(&ctx, verifier_hash.data() + 16); + +#if 0 + fmt::print("verifier hash = "); + for (auto c : verifier_hash) { + fmt::print("{:02x} ", c); + } + fmt::print("\n"); +#endif + + auto hash = sha1(verifier); + + if (memcmp(hash.data(), verifier_hash.data(), hash.size())) + Rcpp::stop("Incorrect password."); + + key_size = 16; + memcpy(this->key.data(), key.data(), key_size); +} + +void cfbf::parse_enc_info_44(span enc_info, u16string_view password) { + enc_info = enc_info.subspan(sizeof(uint32_t)); + + if (enc_info.size() < sizeof(uint32_t) || *(uint32_t*)enc_info.data() != 0x40) + Rcpp::stop("EncryptionInfo reserved value was not 0x40."); + + enc_info = enc_info.subspan(sizeof(uint32_t)); + + xml_reader r(string_view((char*)enc_info.data(), enc_info.size())); + + bool found_root = false, found_key_data = false, found_password = false; + + while (r.read()) { + if (r.node_type() == xml_node::element) { + if (!found_root) { + if (r.local_name() != "encryption" || !r.namespace_uri_raw().cmp(NS_ENCRYPTION)) + Rcpp::stop("Root tag was {{{}}}{}, expected {{{}}}encryption.", + r.namespace_uri_raw().decode(), r.local_name(), NS_ENCRYPTION); + + found_root = true; + } else { + if (r.local_name() == "keyData" && r.namespace_uri_raw().cmp(NS_ENCRYPTION)) { + string salt_value_b64, cipher_algorithm, key_bits_str, cipher_chaining, hash_algorithm; + unsigned int key_bits; + + r.attributes_loop_raw([&](string_view local_name, xml_enc_string_view namespace_uri_raw, + xml_enc_string_view value_raw) { + + if (local_name == "saltValue") + salt_value_b64 = value_raw.decode(); + else if (local_name == "cipherAlgorithm") + cipher_algorithm = value_raw.decode(); + else if (local_name == "keyBits") + key_bits_str = value_raw.decode(); + else if (local_name == "cipherChaining") + cipher_chaining = value_raw.decode(); + else if (local_name == "hashAlgorithm") + hash_algorithm = value_raw.decode(); + + return true; + }); + + if (salt_value_b64.empty()) + Rcpp::stop("saltValue not set"); + + if (cipher_algorithm.empty()) + Rcpp::stop("cipherAlgorithm not set"); + + if (key_bits_str.empty()) + Rcpp::stop("keyBits not set"); + + if (cipher_chaining.empty()) + Rcpp::stop("cipherChaining not set"); + + if (hash_algorithm.empty()) + Rcpp::stop("hashAlgorithm not set"); + + auto salt_value = b64decode(salt_value_b64); + + if (cipher_algorithm != "AES") + Rcpp::stop("cipherAlgorithm was {}, expected AES", cipher_algorithm); + + { + auto [ptr, ec] = from_chars(key_bits_str.data(), key_bits_str.data() + key_bits_str.size(), key_bits); + + if (ptr != key_bits_str.data() + key_bits_str.size()) + Rcpp::stop("Could not convert \"{}\" to integer.", key_bits_str); + } + + if (key_bits != 128 && key_bits != 256) + Rcpp::stop("keyBits was {}, expected 128 or 256", key_bits); + + if (cipher_chaining != "ChainingModeCBC") + Rcpp::stop("cipherChaining was {}, expected ChainingModeCBC", cipher_chaining); + + if (hash_algorithm != "SHA1" && hash_algorithm != "SHA512") + Rcpp::stop("hashAlgorithm was {}, expected SHA1 or SHA512", hash_algorithm); + + memcpy(salt.data(), salt_value.data(), min(salt_value.size(), salt.size())); + found_key_data = true; + } else if (r.local_name() == "encryptedKey" && r.namespace_uri_raw().cmp(NS_PASSWORD)) { + string spin_count_str, salt_value_b64, cipher_algorithm, key_bits_str, cipher_chaining, hash_algorithm, + encrypted_verifier_hash_input_b64, encrypted_verifier_hash_value_b64, encrypted_key_value_b64; + unsigned int spin_count, key_bits; + + r.attributes_loop_raw([&](string_view local_name, xml_enc_string_view namespace_uri_raw, + xml_enc_string_view value_raw) { + + if (local_name == "spinCount") + spin_count_str = value_raw.decode(); + else if (local_name == "saltValue") + salt_value_b64 = value_raw.decode(); + else if (local_name == "cipherAlgorithm") + cipher_algorithm = value_raw.decode(); + else if (local_name == "keyBits") + key_bits_str = value_raw.decode(); + else if (local_name == "cipherChaining") + cipher_chaining = value_raw.decode(); + else if (local_name == "hashAlgorithm") + hash_algorithm = value_raw.decode(); + else if (local_name == "encryptedVerifierHashInput") + encrypted_verifier_hash_input_b64 = value_raw.decode(); + else if (local_name == "encryptedVerifierHashValue") + encrypted_verifier_hash_value_b64 = value_raw.decode(); + else if (local_name == "encryptedKeyValue") + encrypted_key_value_b64 = value_raw.decode(); + + return true; + }); + + if (spin_count_str.empty()) + Rcpp::stop("spinCount not set"); + + if (salt_value_b64.empty()) + Rcpp::stop("saltValue not set"); + + if (cipher_algorithm.empty()) + Rcpp::stop("cipherAlgorithm not set"); + + if (key_bits_str.empty()) + Rcpp::stop("keyBits not set"); + + if (cipher_chaining.empty()) + Rcpp::stop("cipherChaining not set"); + + if (hash_algorithm.empty()) + Rcpp::stop("hashAlgorithm not set"); + + if (encrypted_verifier_hash_input_b64.empty()) + Rcpp::stop("encryptedVerifierHashInput not set"); + + if (encrypted_verifier_hash_value_b64.empty()) + Rcpp::stop("encryptedVerifierHashValue not set"); + + if (encrypted_key_value_b64.empty()) + Rcpp::stop("encryptedKeyValue not set"); + + { + auto [ptr, ec] = from_chars(spin_count_str.data(), spin_count_str.data() + spin_count_str.size(), spin_count); + + if (ptr != spin_count_str.data() + spin_count_str.size()) + Rcpp::stop("Could not convert \"{}\" to integer.", spin_count_str); + } + + auto salt_value = b64decode(salt_value_b64); + + if (cipher_algorithm != "AES") + Rcpp::stop("cipherAlgorithm was {}, expected AES", cipher_algorithm); + + { + auto [ptr, ec] = from_chars(key_bits_str.data(), key_bits_str.data() + key_bits_str.size(), key_bits); + + if (ptr != key_bits_str.data() + key_bits_str.size()) + Rcpp::stop("Could not convert \"{}\" to integer.", key_bits_str); + } + + if (key_bits != 128 && key_bits != 256) + Rcpp::stop("keyBits was {}, expected 128 or 256", key_bits); + + if (cipher_chaining != "ChainingModeCBC") + Rcpp::stop("cipherChaining was {}, expected ChainingModeCBC", cipher_chaining); + + if (hash_algorithm == "SHA1") + hashalgo = hash_algorithm::sha1; + else if (hash_algorithm == "SHA512") + hashalgo = hash_algorithm::sha512; + else + Rcpp::stop("hashAlgorithm was {}, expected SHA1 or SHA512", hash_algorithm); + + auto encrypted_verifier_hash_input = b64decode(encrypted_verifier_hash_input_b64); + + auto encrypted_verifier_hash_value = b64decode(encrypted_verifier_hash_value_b64); + + auto encrypted_key_value = b64decode(encrypted_key_value_b64); + + static const array block1 = { 0xfe, 0xa7, 0xd2, 0x76, 0x3b, 0x4b, 0x9e, 0x79 }; + static const array block2 = { 0xd7, 0xaa, 0x0f, 0x6d, 0x30, 0x61, 0x34, 0x4e }; + static const array block3 = { 0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, 0xd6 }; + + // FIXME - we can save time by saving the partial hash for key1, key2, and key3 + + array key1, key2, key3; + + if (hashalgo == hash_algorithm::sha512) + generate_key44_sha512(password, span((uint8_t*)salt_value.data(), salt_value.size()), spin_count, block1, key1); + else + generate_key44_sha1(password, span((uint8_t*)salt_value.data(), salt_value.size()), spin_count, block1, key1); + + // FIXME - extend key if short + + AES_ctx ctx; + array verifier; + array verifier_hash; + + if (encrypted_verifier_hash_input.size() != verifier.size()) + Rcpp::stop("encrypted_verifier_hash_input.size() was {}, expected {}", encrypted_verifier_hash_input.size(), verifier.size()); + + if (encrypted_verifier_hash_value.size() > verifier_hash.size()) + Rcpp::stop("encrypted_verifier_hash_value.size() was {}, expected at most {}", encrypted_verifier_hash_value.size(), verifier_hash.size()); + + array iv; + + memcpy(iv.data(), salt_value.data(), min(salt_value.size(), sizeof(iv))); + + if (salt_value.size() < sizeof(iv)) + memset(&iv[salt_value.size()], 0, sizeof(iv) - salt_value.size()); + + memcpy(verifier.data(), encrypted_verifier_hash_input.data(), encrypted_verifier_hash_input.size()); + + if (key_bits == 256) { + AES256_init_ctx_iv(&ctx, key1.data(), iv.data()); + AES256_CBC_decrypt_buffer(&ctx, verifier.data(), verifier.size()); + } else { + AES128_init_ctx_iv(&ctx, key1.data(), iv.data()); + AES128_CBC_decrypt_buffer(&ctx, verifier.data(), verifier.size()); + } + + memcpy(verifier_hash.data(), encrypted_verifier_hash_value.data(), encrypted_verifier_hash_value.size()); + + if (hashalgo == hash_algorithm::sha512) + generate_key44_sha512(password, span((uint8_t*)salt_value.data(), salt_value.size()), spin_count, block2, key2); + else + generate_key44_sha1(password, span((uint8_t*)salt_value.data(), salt_value.size()), spin_count, block2, key2); + + if (key_bits == 256) { + AES256_init_ctx_iv(&ctx, key2.data(), iv.data()); + AES256_CBC_decrypt_buffer(&ctx, verifier_hash.data(), verifier_hash.size()); + } else { + AES128_init_ctx_iv(&ctx, key2.data(), iv.data()); + AES128_CBC_decrypt_buffer(&ctx, verifier_hash.data(), verifier_hash.size()); + } + + if (hashalgo == hash_algorithm::sha512) { + array hash; + sha512_state ctx; + + sha_init(ctx); + sha_process(ctx, verifier.data(), (uint32_t)verifier.size()); + sha_done(ctx, hash.data()); + + if (memcmp(hash.data(), verifier_hash.data(), hash.size())) + Rcpp::stop("Incorrect password."); + } else { + auto hash = sha1(verifier); + if (memcmp(hash.data(), verifier_hash.data(), hash.size())) + Rcpp::stop("Incorrect password."); + } + + if (hashalgo == hash_algorithm::sha512) + generate_key44_sha512(password, span((uint8_t*)salt_value.data(), salt_value.size()), spin_count, block3, key3); + else + generate_key44_sha1(password, span((uint8_t*)salt_value.data(), salt_value.size()), spin_count, block3, key3); + + if (key_bits == 256) { + AES256_init_ctx_iv(&ctx, key3.data(), iv.data()); + AES256_CBC_decrypt_buffer(&ctx, (uint8_t*)encrypted_key_value.data(), encrypted_key_value.size()); + } else { + AES128_init_ctx_iv(&ctx, key3.data(), iv.data()); + AES128_CBC_decrypt_buffer(&ctx, (uint8_t*)encrypted_key_value.data(), encrypted_key_value.size()); + } + + key_size = key_bits / 8; + memcpy(key.data(), encrypted_key_value.data(), min((size_t)key_size, encrypted_key_value.size())); + + found_password = true; + } + } + } + } + + if (!found_key_data) + Rcpp::stop("keyData not found"); + + if (!found_password) + Rcpp::stop("encryptedKey not found"); + + agile_enc = true; +} + +void cfbf::parse_enc_info(span enc_info, u16string_view password) { + if (enc_info.size() < sizeof(encryption_info)) + Rcpp::stop("EncryptionInfo was {} bytes, expected at least {}", enc_info.size(), sizeof(encryption_info)); + + auto& ei = *(encryption_info*)enc_info.data(); + + if (ei.major == 4 && ei.minor == 4) { + parse_enc_info_44(enc_info, password); + return; + } else if (ei.major != 3 || ei.minor != 2) + Rcpp::stop("Unsupported EncryptionInfo version {}.{}", ei.major, ei.minor); + + if (ei.flags != 0x24) // AES + Rcpp::stop("Unsupported EncryptionInfo flags {:x}", ei.flags); + + if (ei.header_size < offsetof(encryption_header, csp_name)) + Rcpp::stop("Encryption header was {} bytes, expected at least {}", ei.header_size, offsetof(encryption_header, csp_name)); + + if (ei.header_size > enc_info.size() - sizeof(encryption_info)) + Rcpp::stop("Encryption header was {} bytes, but only {} remaining", ei.header_size, enc_info.size() - sizeof(encryption_info)); + + auto& h = *(encryption_header*)(enc_info.data() + sizeof(encryption_info)); + + if (h.alg_id != ALG_ID_AES_128) + Rcpp::stop("Unsupported algorithm ID {:x}", h.alg_id); + + if (h.alg_id_hash != ALG_ID_SHA_1 && h.alg_id_hash != 0) + Rcpp::stop("Unsupported hash algorithm ID {:x}", h.alg_id_hash); + + if (h.key_size != 128) + Rcpp::stop("Key size was {}, expected 128", h.key_size); + + auto sp = enc_info.subspan(sizeof(encryption_info) + ei.header_size); + + if (sp.size() < sizeof(uint32_t)) + Rcpp::stop("Malformed EncryptionInfo"); + + auto salt_size = *(uint32_t*)sp.data(); + sp = sp.subspan(sizeof(uint32_t)); + + if (sp.size() < salt_size) + Rcpp::stop("Malformed EncryptionInfo"); + + auto salt = sp.subspan(0, salt_size); + sp = sp.subspan(salt_size); + + if (sp.size() < 16) + Rcpp::stop("Malformed EncryptionInfo"); + + auto encrypted_verifier = sp.subspan(0, 16); + sp = sp.subspan(16); + + if (sp.size() < sizeof(uint32_t)) + Rcpp::stop("Malformed EncryptionInfo"); + + // skip verifier_hash_size + sp = sp.subspan(sizeof(uint32_t)); + + if (sp.size() < 32) + Rcpp::stop("Malformed EncryptionInfo"); + + auto encrypted_verifier_hash = sp.subspan(0, 32); + + check_password(password, salt, encrypted_verifier, encrypted_verifier_hash); // throws if wrong + + memcpy(this->salt.data(), salt.data(), this->salt.size()); +} + +vector cfbf::decrypt44(span enc_package) { + uint32_t segment_no = 0; + vector ret; + + static const size_t SEGMENT_LENGTH = 0x1000; + + ret.resize(enc_package.size()); + auto ptr = ret.data(); + + while (true) { + array h; + AES_ctx ctx; + + auto seg = enc_package.subspan(0, min(SEGMENT_LENGTH, enc_package.size())); + + memcpy(ptr, seg.data(), seg.size()); + + if (hashalgo == hash_algorithm::sha512) { + sha512_state ctx; + + sha_init(ctx); + sha_process(ctx, salt.data(), (uint32_t)salt.size()); + sha_process(ctx, &segment_no, sizeof(segment_no)); + sha_done(ctx, h.data()); + } else { + SHA1_CTX ctx; + + ctx.update(salt); + ctx.update(span((uint8_t*)&segment_no, sizeof(segment_no))); + + ctx.finalize(h); + } + + if (key_size == 32) { + AES256_init_ctx_iv(&ctx, key.data(), h.data()); + AES256_CBC_decrypt_buffer(&ctx, ptr, seg.size()); + } else { + AES128_init_ctx_iv(&ctx, key.data(), h.data()); + AES128_CBC_decrypt_buffer(&ctx, ptr, seg.size()); + } + + if (enc_package.size() == seg.size()) + break; + + enc_package = enc_package.subspan(seg.size()); + segment_no++; + ptr += seg.size(); + } + + return ret; +} + +vector cfbf::decrypt(span enc_package) { + if (enc_package.size() < sizeof(uint64_t)) + Rcpp::stop("EncryptedPackage was {} bytes, expected at least {}", enc_package.size(), sizeof(uint64_t)); + + auto size = *(uint64_t*)enc_package.data(); + + enc_package = enc_package.subspan(sizeof(uint64_t)); + + if (enc_package.size() < size) + Rcpp::stop("EncryptedPackage was {} bytes, expected at least {}", enc_package.size() + sizeof(uint64_t), size + sizeof(uint64_t)); + + if (agile_enc) + return decrypt44(enc_package); + + AES_ctx ctx; + auto buf = enc_package; + + AES128_init_ctx(&ctx, key.data()); + + while (!buf.empty()) { + AES128_ECB_decrypt(&ctx, buf.data()); + + buf = buf.subspan(16); + } + + vector ret; + + ret.assign(enc_package.begin(), enc_package.end()); + + return ret; +} diff --git a/src/xlcpp/cfbf.h b/src/xlcpp/cfbf.h new file mode 100644 index 000000000..732be30c9 --- /dev/null +++ b/src/xlcpp/cfbf.h @@ -0,0 +1,77 @@ +#pragma once + +#include "mmap.h" +#include +#include +#include +#include +#include +#include +#include +#include + +static const uint64_t CFBF_SIGNATURE = 0xe11ab1a1e011cfd0; + +class _formatted_error : public std::exception { +public: + template + _formatted_error(T&& s, Args&&... args) { + msg = fmt::format(s, std::forward(args)...); + } + + const char* what() const noexcept { + return msg.c_str(); + } + +private: + std::string msg; +}; + +#define formatted_error(s, ...) _formatted_error(FMT_COMPILE(s), ##__VA_ARGS__) + +class cfbf; +struct dirent; + +class cfbf_entry { +public: + cfbf_entry(cfbf& file, const dirent& de, std::string_view name); + size_t read(std::span buf, uint64_t off) const; + size_t get_size() const; + + cfbf& file; + const dirent& de; + std::string name; +}; + +enum class hash_algorithm { + sha1, + sha512 +}; + +class cfbf { +public: + cfbf(std::span s); + uint32_t next_sector(uint32_t sector) const; + uint32_t next_mini_sector(uint32_t sector) const; + void parse_enc_info(std::span enc_info, std::u16string_view password); + void parse_enc_info_44(std::span enc_info, std::u16string_view password); + std::vector decrypt(std::span enc_package); + + std::vector entries; + std::span s; + +private: + void add_entry(std::string_view path, uint32_t num, bool ignore_right); + void check_password(std::u16string_view password, std::span salt, + std::span encrypted_verifier, + std::span encrypted_verifier_hash); + const dirent& find_dirent(uint32_t num); + std::vector decrypt44(std::span enc_package); + + std::unique_ptr m; + std::array key; + unsigned int key_size; + std::array salt; + bool agile_enc = false; + enum hash_algorithm hashalgo; +}; diff --git a/src/xlcpp/mmap.cpp b/src/xlcpp/mmap.cpp new file mode 100644 index 000000000..d2c609c3f --- /dev/null +++ b/src/xlcpp/mmap.cpp @@ -0,0 +1,472 @@ +#include "mmap.h" + +#ifndef _WIN32 +#include +#include +#include +#include +#endif + +using namespace std; + +#ifndef _WIN32 +errno_error::errno_error(string_view function, int en) : msg(function) { + msg += " failed ("; + + switch (en) { + case EPERM: + msg += "EPERM"; + break; + case ENOENT: + msg += "ENOENT"; + break; + case ESRCH: + msg += "ESRCH"; + break; + case EINTR: + msg += "EINTR"; + break; + case EIO: + msg += "EIO"; + break; + case ENXIO: + msg += "ENXIO"; + break; + case E2BIG: + msg += "E2BIG"; + break; + case ENOEXEC: + msg += "ENOEXEC"; + break; + case EBADF: + msg += "EBADF"; + break; + case ECHILD: + msg += "ECHILD"; + break; + case EAGAIN: + msg += "EAGAIN"; + break; + case ENOMEM: + msg += "ENOMEM"; + break; + case EACCES: + msg += "EACCES"; + break; + case EFAULT: + msg += "EFAULT"; + break; + case ENOTBLK: + msg += "ENOTBLK"; + break; + case EBUSY: + msg += "EBUSY"; + break; + case EEXIST: + msg += "EEXIST"; + break; + case EXDEV: + msg += "EXDEV"; + break; + case ENODEV: + msg += "ENODEV"; + break; + case ENOTDIR: + msg += "ENOTDIR"; + break; + case EISDIR: + msg += "EISDIR"; + break; + case EINVAL: + msg += "EINVAL"; + break; + case ENFILE: + msg += "ENFILE"; + break; + case EMFILE: + msg += "EMFILE"; + break; + case ENOTTY: + msg += "ENOTTY"; + break; + case ETXTBSY: + msg += "ETXTBSY"; + break; + case EFBIG: + msg += "EFBIG"; + break; + case ENOSPC: + msg += "ENOSPC"; + break; + case ESPIPE: + msg += "ESPIPE"; + break; + case EROFS: + msg += "EROFS"; + break; + case EMLINK: + msg += "EMLINK"; + break; + case EPIPE: + msg += "EPIPE"; + break; + case EDOM: + msg += "EDOM"; + break; + case ERANGE: + msg += "ERANGE"; + break; + case ENAMETOOLONG: + msg += "ENAMETOOLONG"; + break; + case ENOLCK: + msg += "ENOLCK"; + break; + case ENOSYS: + msg += "ENOSYS"; + break; + case ENOTEMPTY: + msg += "ENOTEMPTY"; + break; + case ELOOP: + msg += "ELOOP"; + break; + case ENOMSG: + msg += "ENOMSG"; + break; + case EIDRM: + msg += "EIDRM"; + break; + case ECHRNG: + msg += "ECHRNG"; + break; + case EL2NSYNC: + msg += "EL2NSYNC"; + break; + case EL3HLT: + msg += "EL3HLT"; + break; + case EL3RST: + msg += "EL3RST"; + break; + case ELNRNG: + msg += "ELNRNG"; + break; + case EUNATCH: + msg += "EUNATCH"; + break; + case ENOCSI: + msg += "ENOCSI"; + break; + case EL2HLT: + msg += "EL2HLT"; + break; + case EBADE: + msg += "EBADE"; + break; + case EBADR: + msg += "EBADR"; + break; + case EXFULL: + msg += "EXFULL"; + break; + case ENOANO: + msg += "ENOANO"; + break; + case EBADRQC: + msg += "EBADRQC"; + break; + case EBADSLT: + msg += "EBADSLT"; + break; + case EDEADLOCK: + msg += "EDEADLOCK"; + break; + case EBFONT: + msg += "EBFONT"; + break; + case ENOSTR: + msg += "ENOSTR"; + break; + case ENODATA: + msg += "ENODATA"; + break; + case ETIME: + msg += "ETIME"; + break; + case ENOSR: + msg += "ENOSR"; + break; + case ENONET: + msg += "ENONET"; + break; + case ENOPKG: + msg += "ENOPKG"; + break; + case EREMOTE: + msg += "EREMOTE"; + break; + case ENOLINK: + msg += "ENOLINK"; + break; + case EADV: + msg += "EADV"; + break; + case ESRMNT: + msg += "ESRMNT"; + break; + case ECOMM: + msg += "ECOMM"; + break; + case EPROTO: + msg += "EPROTO"; + break; + case EMULTIHOP: + msg += "EMULTIHOP"; + break; + case EDOTDOT: + msg += "EDOTDOT"; + break; + case EBADMSG: + msg += "EBADMSG"; + break; + case EOVERFLOW: + msg += "EOVERFLOW"; + break; + case ENOTUNIQ: + msg += "ENOTUNIQ"; + break; + case EBADFD: + msg += "EBADFD"; + break; + case EREMCHG: + msg += "EREMCHG"; + break; + case ELIBACC: + msg += "ELIBACC"; + break; + case ELIBBAD: + msg += "ELIBBAD"; + break; + case ELIBSCN: + msg += "ELIBSCN"; + break; + case ELIBMAX: + msg += "ELIBMAX"; + break; + case ELIBEXEC: + msg += "ELIBEXEC"; + break; + case EILSEQ: + msg += "EILSEQ"; + break; + case ERESTART: + msg += "ERESTART"; + break; + case ESTRPIPE: + msg += "ESTRPIPE"; + break; + case EUSERS: + msg += "EUSERS"; + break; + case ENOTSOCK: + msg += "ENOTSOCK"; + break; + case EDESTADDRREQ: + msg += "EDESTADDRREQ"; + break; + case EMSGSIZE: + msg += "EMSGSIZE"; + break; + case EPROTOTYPE: + msg += "EPROTOTYPE"; + break; + case ENOPROTOOPT: + msg += "ENOPROTOOPT"; + break; + case EPROTONOSUPPORT: + msg += "EPROTONOSUPPORT"; + break; + case ESOCKTNOSUPPORT: + msg += "ESOCKTNOSUPPORT"; + break; + case EOPNOTSUPP: + msg += "EOPNOTSUPP"; + break; + case EPFNOSUPPORT: + msg += "EPFNOSUPPORT"; + break; + case EAFNOSUPPORT: + msg += "EAFNOSUPPORT"; + break; + case EADDRINUSE: + msg += "EADDRINUSE"; + break; + case EADDRNOTAVAIL: + msg += "EADDRNOTAVAIL"; + break; + case ENETDOWN: + msg += "ENETDOWN"; + break; + case ENETUNREACH: + msg += "ENETUNREACH"; + break; + case ENETRESET: + msg += "ENETRESET"; + break; + case ECONNABORTED: + msg += "ECONNABORTED"; + break; + case ECONNRESET: + msg += "ECONNRESET"; + break; + case ENOBUFS: + msg += "ENOBUFS"; + break; + case EISCONN: + msg += "EISCONN"; + break; + case ENOTCONN: + msg += "ENOTCONN"; + break; + case ESHUTDOWN: + msg += "ESHUTDOWN"; + break; + case ETOOMANYREFS: + msg += "ETOOMANYREFS"; + break; + case ETIMEDOUT: + msg += "ETIMEDOUT"; + break; + case ECONNREFUSED: + msg += "ECONNREFUSED"; + break; + case EHOSTDOWN: + msg += "EHOSTDOWN"; + break; + case EHOSTUNREACH: + msg += "EHOSTUNREACH"; + break; + case EALREADY: + msg += "EALREADY"; + break; + case EINPROGRESS: + msg += "EINPROGRESS"; + break; + case ESTALE: + msg += "ESTALE"; + break; + case EUCLEAN: + msg += "EUCLEAN"; + break; + case ENOTNAM: + msg += "ENOTNAM"; + break; + case ENAVAIL: + msg += "ENAVAIL"; + break; + case EISNAM: + msg += "EISNAM"; + break; + case EREMOTEIO: + msg += "EREMOTEIO"; + break; + case EDQUOT: + msg += "EDQUOT"; + break; + case ENOMEDIUM: + msg += "ENOMEDIUM"; + break; + case EMEDIUMTYPE: + msg += "EMEDIUMTYPE"; + break; + case ECANCELED: + msg += "ECANCELED"; + break; + case ENOKEY: + msg += "ENOKEY"; + break; + case EKEYEXPIRED: + msg += "EKEYEXPIRED"; + break; + case EKEYREVOKED: + msg += "EKEYREVOKED"; + break; + case EKEYREJECTED: + msg += "EKEYREJECTED"; + break; + case EOWNERDEAD: + msg += "EOWNERDEAD"; + break; + case ENOTRECOVERABLE: + msg += "ENOTRECOVERABLE"; + break; + case ERFKILL: + msg += "ERFKILL"; + break; + case EHWPOISON: + msg += "EHWPOISON"; + break; + default: + msg += to_string(en); + break; + } + + msg += ")"; +} +#endif + +mmap::mmap(fd_t h) { +#ifdef _WIN32 + LARGE_INTEGER fsli; + + if (!GetFileSizeEx(h, &fsli)) + throw last_error("GetFileSizeEx", GetLastError()); + + filesize = fsli.QuadPart; + + mh = CreateFileMappingW(h, nullptr, PAGE_READONLY, 0, 0, nullptr); + if (!mh) + throw last_error("CreateFileMapping", GetLastError()); +#else + struct stat st; + + if (fstat(h, &st) == -1) + throw errno_error("fstat", errno); + + filesize = st.st_size; + + ptr = ::mmap(nullptr, filesize, PROT_READ, MAP_SHARED, h, 0); + + if (ptr == MAP_FAILED) + throw errno_error("mmap", errno); +#endif +} + +mmap::~mmap() { +#ifdef _WIN32 + if (ptr) + UnmapViewOfFile(ptr); + + if (mh) + CloseHandle(mh); + + if (h != INVALID_HANDLE_VALUE) + CloseHandle(h); +#else + munmap(ptr, filesize); +#endif +} + +span mmap::map() { +#ifdef _WIN32 + if (!ptr) { + ptr = MapViewOfFile(mh, FILE_MAP_READ, 0, 0, 0); + + if (!ptr) + throw last_error("MapViewOfFile", GetLastError()); + } +#endif + + return span((const uint8_t*)ptr, filesize); +} diff --git a/src/xlcpp/mmap.h b/src/xlcpp/mmap.h new file mode 100644 index 000000000..9750e6b8a --- /dev/null +++ b/src/xlcpp/mmap.h @@ -0,0 +1,151 @@ +#pragma once + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include "utf16.h" +using fd_t = HANDLE; +#else +#include +using fd_t = int; +#endif + +#ifdef _WIN32 +class last_error : public std::exception { +public: + last_error(std::string_view function, int le) { + std::string nice_msg; + + { + char16_t* fm; + + if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, + le, 0, reinterpret_cast(&fm), 0, nullptr)) { + try { + std::u16string_view s = fm; + + while (!s.empty() && (s[s.length() - 1] == u'\r' || s[s.length() - 1] == u'\n')) { + s.remove_suffix(1); + } + + nice_msg = utf16_to_utf8(s); + } catch (...) { + LocalFree(fm); + throw; + } + + LocalFree(fm); + } + } + + msg = std::string(function) + " failed (error " + std::to_string(le) + (!nice_msg.empty() ? (", " + nice_msg) : "") + ")."; + } + + const char* what() const noexcept { + return msg.c_str(); + } + +private: + std::string msg; +}; +#endif + +#ifdef _WIN32 + +class handle_closer { +public: + typedef HANDLE pointer; + + void operator()(HANDLE h) { + if (h == INVALID_HANDLE_VALUE) + return; + + CloseHandle(h); + } +}; + +using unique_handle = std::unique_ptr; + +#else + +class unique_handle { +public: + unique_handle() : fd(0) { + } + + explicit unique_handle(int fd) : fd(fd) { + } + + unique_handle(unique_handle&& that) noexcept { + fd = that.fd; + that.fd = 0; + } + + unique_handle(const unique_handle&) = delete; + unique_handle& operator=(const unique_handle&) = delete; + + unique_handle& operator=(unique_handle&& that) noexcept { + if (fd > 0) + close(fd); + + fd = that.fd; + that.fd = 0; + + return *this; + } + + ~unique_handle() { + if (fd <= 0) + return; + + close(fd); + } + + void reset(int new_fd) noexcept { + if (fd > 0) + close(fd); + + fd = new_fd; + } + + int get() const noexcept { + return fd; + } + +private: + int fd; +}; +#endif + +class errno_error : public std::exception { +public: + errno_error(std::string_view function, int en); + + const char* what() const noexcept { + return msg.c_str(); + } + +private: + std::string msg; +}; + +class mmap { +public: + explicit mmap(fd_t h); + ~mmap(); + std::span map(); + + size_t filesize; + +private: +#ifdef _WIN32 + HANDLE h = INVALID_HANDLE_VALUE; + HANDLE mh = INVALID_HANDLE_VALUE; +#endif + void* ptr = nullptr; +}; diff --git a/src/xlcpp/sha1.cpp b/src/xlcpp/sha1.cpp new file mode 100644 index 000000000..cc251480c --- /dev/null +++ b/src/xlcpp/sha1.cpp @@ -0,0 +1,263 @@ +/* +SHA-1 in C +By Steve Reid +100% Public Domain + +Test Vectors (from FIPS PUB 180-1) +"abc" +A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D +"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" +84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 +A million repetitions of "a" +34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F +*/ + +/* #define LITTLE_ENDIAN * This should be #define'd already, if true. */ +/* #define SHA1HANDSOFF * Copies data before messing with it. */ + +#define SHA1HANDSOFF + +#include +#include +#include +#include + +#include "sha1.h" + +using namespace std; + +constexpr void R0(uint32_t v, uint32_t& w, uint32_t x, uint32_t y, uint32_t& z, uint32_t& bl) { + z += (w & (x^y)) ^ y; + + bl = (rotl(bl,24) & 0xff00ff00) | (rotl(bl,8) & 0x00ff00ff); + z += bl; + + z += 0x5a827999; + z += rotl(v, 5); + + w = rotl(w, 30); +} + +constexpr void R1(uint32_t v, uint32_t& w, uint32_t x, uint32_t y, uint32_t& z, uint32_t i, uint32_t* l) { + z += (w & (x ^ y)) ^ y; + + l[i&15] = rotl(l[(i+13)&15] ^ l[(i+8)&15] ^ l[(i+2)&15] ^ l[i&15], 1); + z += l[i&15]; + + z += 0x5a827999; + z += rotl(v, 5); + + w = rotl(w, 30); +} + +constexpr void R2(uint32_t v, uint32_t& w, uint32_t x, uint32_t y, uint32_t& z, uint32_t i, uint32_t* l) { + z += w ^ x ^ y; + + l[i&15] = rotl(l[(i+13)&15] ^ l[(i+8)&15] ^ l[(i+2)&15] ^ l[i&15], 1); + z += l[i&15]; + + z += 0x6ed9eba1; + z += rotl(v, 5); + + w = rotl(w, 30); +} + +constexpr void R3(uint32_t v, uint32_t& w, uint32_t x, uint32_t y, uint32_t& z, uint32_t i, uint32_t* l) { + z += ((w | x) & y) | (w & x); + + l[i&15] = rotl(l[(i+13)&15] ^ l[(i+8)&15] ^ l[(i+2)&15] ^ l[i&15], 1); + z += l[i&15]; + + z += 0x8f1bbcdc; + z += rotl(v, 5); + + w = rotl(w, 30); +} + +constexpr void R4(uint32_t v, uint32_t& w, uint32_t x, uint32_t y, uint32_t& z, uint32_t i, uint32_t* l) { + z += w ^ x ^ y; + + l[i&15] = rotl(l[(i+13)&15] ^ l[(i+8)&15] ^ l[(i+2)&15] ^ l[i&15], 1); + z += l[i&15]; + + z += 0xca62c1d6; + z += rotl(v,5); + + w = rotl(w,30); +} + + +/* Hash a single 512-bit block. This is the core of the algorithm. */ + +static void SHA1Transform(uint32_t state[5], uint8_t buffer[64]) { + uint32_t a, b, c, d, e; + uint32_t l[16]; + + memcpy(l, buffer, 64); + + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a,b,c,d,e, l[0]); + R0(e,a,b,c,d, l[1]); + R0(d,e,a,b,c, l[2]); + R0(c,d,e,a,b, l[3]); + R0(b,c,d,e,a, l[4]); + R0(a,b,c,d,e, l[5]); + R0(e,a,b,c,d, l[6]); + R0(d,e,a,b,c, l[7]); + R0(c,d,e,a,b, l[8]); + R0(b,c,d,e,a, l[9]); + R0(a,b,c,d,e, l[10]); + R0(e,a,b,c,d, l[11]); + R0(d,e,a,b,c, l[12]); + R0(c,d,e,a,b, l[13]); + R0(b,c,d,e,a, l[14]); + R0(a,b,c,d,e, l[15]); + + R1(e,a,b,c,d, 16, l); + R1(d,e,a,b,c, 17, l); + R1(c,d,e,a,b, 18, l); + R1(b,c,d,e,a, 19, l); + + R2(a,b,c,d,e,20, l); + R2(e,a,b,c,d,21, l); + R2(d,e,a,b,c,22, l); + R2(c,d,e,a,b,23, l); + R2(b,c,d,e,a,24, l); + R2(a,b,c,d,e,25, l); + R2(e,a,b,c,d,26, l); + R2(d,e,a,b,c,27, l); + R2(c,d,e,a,b,28, l); + R2(b,c,d,e,a,29, l); + R2(a,b,c,d,e,30, l); + R2(e,a,b,c,d,31, l); + R2(d,e,a,b,c,32, l); + R2(c,d,e,a,b,33, l); + R2(b,c,d,e,a,34, l); + R2(a,b,c,d,e,35, l); + R2(e,a,b,c,d,36, l); + R2(d,e,a,b,c,37, l); + R2(c,d,e,a,b,38, l); + R2(b,c,d,e,a,39, l); + + R3(a,b,c,d,e,40, l); + R3(e,a,b,c,d,41, l); + R3(d,e,a,b,c,42, l); + R3(c,d,e,a,b,43, l); + R3(b,c,d,e,a,44, l); + R3(a,b,c,d,e,45, l); + R3(e,a,b,c,d,46, l); + R3(d,e,a,b,c,47, l); + R3(c,d,e,a,b,48, l); + R3(b,c,d,e,a,49, l); + R3(a,b,c,d,e,50, l); + R3(e,a,b,c,d,51, l); + R3(d,e,a,b,c,52, l); + R3(c,d,e,a,b,53, l); + R3(b,c,d,e,a,54, l); + R3(a,b,c,d,e,55, l); + R3(e,a,b,c,d,56, l); + R3(d,e,a,b,c,57, l); + R3(c,d,e,a,b,58, l); + R3(b,c,d,e,a,59, l); + + R4(a,b,c,d,e,60, l); + R4(e,a,b,c,d,61, l); + R4(d,e,a,b,c,62, l); + R4(c,d,e,a,b,63, l); + R4(b,c,d,e,a,64, l); + R4(a,b,c,d,e,65, l); + R4(e,a,b,c,d,66, l); + R4(d,e,a,b,c,67, l); + R4(c,d,e,a,b,68, l); + R4(b,c,d,e,a,69, l); + R4(a,b,c,d,e,70, l); + R4(e,a,b,c,d,71, l); + R4(d,e,a,b,c,72, l); + R4(c,d,e,a,b,73, l); + R4(b,c,d,e,a,74, l); + R4(a,b,c,d,e,75, l); + R4(e,a,b,c,d,76, l); + R4(d,e,a,b,c,77, l); + R4(c,d,e,a,b,78, l); + R4(b,c,d,e,a,79, l); + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; +} + +/* Run your data through this. */ + +void SHA1_CTX::update(std::span data) { + uint32_t i; + + auto j = count[0]; + + count[0] += (uint32_t)(data.size() << 3); + + if (count[0] < j) + count[1]++; + + count[1] += (uint32_t)(data.size() >> 29); + + j = (j >> 3) & 63; + + if (j + data.size() > 63) { + i = 64 - j; + memcpy(&buffer[j], data.data(), i); + SHA1Transform(state, buffer); + for ( ; i + 63 < data.size(); i += 64) { + SHA1Transform(state, (uint8_t*)&data[i]); + } + j = 0; + } else + i = 0; + + memcpy(&buffer[j], &data[i], data.size() - i); +} + + +/* Add padding and return the message digest. */ + +void SHA1_CTX::finalize(span digest) { + unsigned i; + unsigned char finalcount[8]; + unsigned char c; + + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char)((count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ + } + + c = 0200; + update(span(&c, 1)); + while ((count[0] & 504) != 448) { + c = 0000; + update(span(&c, 1)); + } + update(span(finalcount, 8)); /* Should cause a SHA1Transform() */ + for (i = 0; i < 20; i++) { + digest[i] = (unsigned char) + ((state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } +} + +array sha1(span s) { + array digest; + SHA1_CTX ctx; + + ctx.update(s); + ctx.finalize(digest); + + return digest; +} + diff --git a/src/xlcpp/sha1.h b/src/xlcpp/sha1.h new file mode 100644 index 000000000..9e77d69a3 --- /dev/null +++ b/src/xlcpp/sha1.h @@ -0,0 +1,35 @@ +/* +SHA-1 in C +By Steve Reid +100% Public Domain +*/ + +#pragma once + +#include +#include + +struct SHA1_CTX { + constexpr SHA1_CTX() { + /* SHA1 initialization constants */ + state[0] = 0x67452301; + state[1] = 0xEFCDAB89; + state[2] = 0x98BADCFE; + state[3] = 0x10325476; + state[4] = 0xC3D2E1F0; + count[0] = count[1] = 0; + + for (unsigned int i = 0; i < sizeof(buffer); i++) { + buffer[i] = 0; + } + } + + void update(std::span data); + void finalize(std::span digest); + + uint32_t state[5]; + uint32_t count[2]; + unsigned char buffer[64]; +}; + +std::array sha1(std::span s); diff --git a/src/xlcpp/sha512.cpp b/src/xlcpp/sha512.cpp new file mode 100644 index 000000000..89e6f270f --- /dev/null +++ b/src/xlcpp/sha512.cpp @@ -0,0 +1,174 @@ +// SHA-512. Adapted from LibTomCrypt. This code is Public Domain +#include "sha512.h" +#include + +static const uint64_t K[80] = +{ + 0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL, 0x3956c25bf348b538ULL, + 0x59f111f1b605d019ULL, 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL, 0xd807aa98a3030242ULL, 0x12835b0145706fbeULL, + 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL, 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, 0x9bdc06a725c71235ULL, + 0xc19bf174cf692694ULL, 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL, 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, + 0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL, 0x983e5152ee66dfabULL, + 0xa831c66d2db43210ULL, 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL, 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, + 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, 0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, 0x4d2c6dfc5ac42aedULL, + 0x53380d139d95b3dfULL, 0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL, + 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, 0xd192e819d6ef5218ULL, + 0xd69906245565a910ULL, 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL, 0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, + 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL, 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL, 0x5b9cca4f7763e373ULL, + 0x682e6ff3d6b2b8a3ULL, 0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL, + 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL, 0xca273eceea26619cULL, + 0xd186b8c721c0c207ULL, 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, 0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, + 0x113f9804bef90daeULL, 0x1b710b35131c471bULL, 0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, 0x3c9ebe0a15c9bebcULL, + 0x431d67c49c100d4cULL, 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL +}; + +static uint32_t min(uint32_t x, uint32_t y) +{ + return x < y ? x : y; +} + +static void store64(uint64_t x, unsigned char* y) +{ + for(int i = 0; i != 8; ++i) + y[i] = (x >> ((7-i) * 8)) & 255; +} + +static uint64_t load64(const unsigned char* y) +{ + uint64_t res = 0; + for(int i = 0; i != 8; ++i) + res |= uint64_t(y[i]) << ((7-i) * 8); + return res; +} + +static uint64_t Ch(uint64_t x, uint64_t y, uint64_t z) { return z ^ (x & (y ^ z)); } +static uint64_t Maj(uint64_t x, uint64_t y, uint64_t z) { return ((x | y) & z) | (x & y); } +static uint64_t Rot(uint64_t x, uint64_t n) { return (x >> (n & 63)) | (x << (64 - (n & 63))); } +static uint64_t Sh(uint64_t x, uint64_t n) { return x >> n; } +static uint64_t Sigma0(uint64_t x) { return Rot(x, 28) ^ Rot(x, 34) ^ Rot(x, 39); } +static uint64_t Sigma1(uint64_t x) { return Rot(x, 14) ^ Rot(x, 18) ^ Rot(x, 41); } +static uint64_t Gamma0(uint64_t x) { return Rot(x, 1) ^ Rot(x, 8) ^ Sh(x, 7); } +static uint64_t Gamma1(uint64_t x) { return Rot(x, 19) ^ Rot(x, 61) ^ Sh(x, 6); } + +static void sha_compress(sha512_state& md, const unsigned char *buf) +{ + uint64_t S[8], W[80], t0, t1; + + // Copy state into S + for(int i = 0; i < 8; i++) + S[i] = md.state[i]; + + // Copy the state into 1024-bits into W[0..15] + for(int i = 0; i < 16; i++) + W[i] = load64(buf + (8*i)); + + // Fill W[16..79] + for(int i = 16; i < 80; i++) + W[i] = Gamma1(W[i - 2]) + W[i - 7] + Gamma0(W[i - 15]) + W[i - 16]; + + // Compress + auto RND = [&](uint64_t a, uint64_t b, uint64_t c, uint64_t& d, uint64_t e, uint64_t f, uint64_t g, uint64_t& h, uint64_t i) + { + t0 = h + Sigma1(e) + Ch(e, f, g) + K[i] + W[i]; + t1 = Sigma0(a) + Maj(a, b, c); + d += t0; + h = t0 + t1; + }; + + for(int i = 0; i < 80; i += 8) + { + RND(S[0],S[1],S[2],S[3],S[4],S[5],S[6],S[7],i+0); + RND(S[7],S[0],S[1],S[2],S[3],S[4],S[5],S[6],i+1); + RND(S[6],S[7],S[0],S[1],S[2],S[3],S[4],S[5],i+2); + RND(S[5],S[6],S[7],S[0],S[1],S[2],S[3],S[4],i+3); + RND(S[4],S[5],S[6],S[7],S[0],S[1],S[2],S[3],i+4); + RND(S[3],S[4],S[5],S[6],S[7],S[0],S[1],S[2],i+5); + RND(S[2],S[3],S[4],S[5],S[6],S[7],S[0],S[1],i+6); + RND(S[1],S[2],S[3],S[4],S[5],S[6],S[7],S[0],i+7); + } + + // Feedback + for(int i = 0; i < 8; i++) + md.state[i] = md.state[i] + S[i]; +} + +// Public interface + +void sha_init(sha512_state& md) +{ + md.curlen = 0; + md.length = 0; + md.state[0] = 0x6a09e667f3bcc908ULL; + md.state[1] = 0xbb67ae8584caa73bULL; + md.state[2] = 0x3c6ef372fe94f82bULL; + md.state[3] = 0xa54ff53a5f1d36f1ULL; + md.state[4] = 0x510e527fade682d1ULL; + md.state[5] = 0x9b05688c2b3e6c1fULL; + md.state[6] = 0x1f83d9abfb41bd6bULL; + md.state[7] = 0x5be0cd19137e2179ULL; +} + +void sha_process(sha512_state& md, const void* src, uint32_t inlen) +{ + const uint32_t block_size = sizeof(sha512_state::buf); + auto in = static_cast(src); + + while(inlen > 0) + { + if(md.curlen == 0 && inlen >= block_size) + { + sha_compress(md, in); + md.length += block_size * 8; + in += block_size; + inlen -= block_size; + } + else + { + uint32_t n = min(inlen, (block_size - md.curlen)); + memcpy(md.buf + md.curlen, in, n); + md.curlen += n; + in += n; + inlen -= n; + + if(md.curlen == block_size) + { + sha_compress(md, md.buf); + md.length += 8*block_size; + md.curlen = 0; + } + } + } +} + +void sha_done(sha512_state& md, void *out) +{ + // Increase the length of the message + md.length += md.curlen * 8ULL; + + // Append the '1' bit + md.buf[md.curlen++] = static_cast(0x80); + + // If the length is currently above 112 bytes we append zeros then compress. + // Then we can fall back to padding zeros and length encoding like normal. + if(md.curlen > 112) + { + while(md.curlen < 128) + md.buf[md.curlen++] = 0; + sha_compress(md, md.buf); + md.curlen = 0; + } + + // Pad upto 120 bytes of zeroes + // note: that from 112 to 120 is the 64 MSB of the length. We assume that + // you won't hash 2^64 bits of data... :-) + while(md.curlen < 120) + md.buf[md.curlen++] = 0; + + // Store length + store64(md.length, md.buf+120); + sha_compress(md, md.buf); + + // Copy output + for(int i = 0; i < 8; i++) + store64(md.state[i], static_cast(out)+(8*i)); +} diff --git a/src/xlcpp/sha512.h b/src/xlcpp/sha512.h new file mode 100644 index 000000000..0ba62cb19 --- /dev/null +++ b/src/xlcpp/sha512.h @@ -0,0 +1,16 @@ +// SHA-512. Adapted from LibTomCrypt. This code is Public Domain +#pragma once + +#include + +struct sha512_state +{ + uint64_t length; + uint64_t state[8]; + uint32_t curlen; + unsigned char buf[128]; +}; + +void sha_init(sha512_state& md); +void sha_process(sha512_state& md, const void* in, uint32_t inlen); +void sha_done(sha512_state& md, void* out); diff --git a/src/xlcpp/utf16.h b/src/xlcpp/utf16.h new file mode 100644 index 000000000..8c3c03a95 --- /dev/null +++ b/src/xlcpp/utf16.h @@ -0,0 +1,346 @@ +#pragma once + +#include + +template +requires (std::is_same_v || (sizeof(wchar_t) == 2 && std::is_same_v)) +static constexpr size_t utf16_to_utf8_len(std::basic_string_view sv) noexcept { + size_t ret = 0; + + while (!sv.empty()) { + if (sv[0] < 0x80) + ret++; + else if (sv[0] < 0x800) + ret += 2; + else if (sv[0] < 0xd800) + ret += 3; + else if (sv[0] < 0xdc00) { + if (sv.length() < 2 || (sv[1] & 0xdc00) != 0xdc00) { + ret += 3; + sv = sv.substr(1); + continue; + } + + ret += 4; + sv = sv.substr(1); + } else + ret += 3; + + sv = sv.substr(1); + } + + return ret; +} + +template +requires (std::is_same_v || (sizeof(wchar_t) == 2 && std::is_same_v)) +static constexpr size_t utf16_to_utf8_len(const T (&str)[N]) noexcept { + return utf16_to_utf8_len(std::basic_string_view{str, N - 1}); +} + +template +requires (std::is_same_v || (sizeof(wchar_t) == 2 && std::is_same_v)) && +((std::ranges::output_range && std::is_same_v, char>) || +(std::ranges::output_range && std::is_same_v, char8_t>)) +static constexpr void utf16_to_utf8_range(std::basic_string_view sv, U& t) noexcept { + auto ptr = t.begin(); + + if (ptr == t.end()) + return; + + while (!sv.empty()) { + if (sv[0] < 0x80) { + *ptr = (uint8_t)sv[0]; + ptr++; + + if (ptr == t.end()) + return; + } else if (sv[0] < 0x800) { + *ptr = (uint8_t)(0xc0 | (sv[0] >> 6)); + ptr++; + + if (ptr == t.end()) + return; + + *ptr = (uint8_t)(0x80 | (sv[0] & 0x3f)); + ptr++; + + if (ptr == t.end()) + return; + } else if (sv[0] < 0xd800) { + *ptr = (uint8_t)(0xe0 | (sv[0] >> 12)); + ptr++; + + if (ptr == t.end()) + return; + + *ptr = (uint8_t)(0x80 | ((sv[0] >> 6) & 0x3f)); + ptr++; + + if (ptr == t.end()) + return; + + *ptr = (uint8_t)(0x80 | (sv[0] & 0x3f)); + ptr++; + + if (ptr == t.end()) + return; + } else if (sv[0] < 0xdc00) { + if (sv.length() < 2 || (sv[1] & 0xdc00) != 0xdc00) { + *ptr = (uint8_t)0xef; + ptr++; + + if (ptr == t.end()) + return; + + *ptr = (uint8_t)0xbf; + ptr++; + + if (ptr == t.end()) + return; + + *ptr = (uint8_t)0xbd; + ptr++; + + if (ptr == t.end()) + return; + + sv = sv.substr(1); + continue; + } + + char32_t cp = 0x10000 | ((sv[0] & ~0xd800) << 10) | (sv[1] & ~0xdc00); + + *ptr = (uint8_t)(0xf0 | (cp >> 18)); + ptr++; + + if (ptr == t.end()) + return; + + *ptr = (uint8_t)(0x80 | ((cp >> 12) & 0x3f)); + ptr++; + + if (ptr == t.end()) + return; + + *ptr = (uint8_t)(0x80 | ((cp >> 6) & 0x3f)); + ptr++; + + if (ptr == t.end()) + return; + + *ptr = (uint8_t)(0x80 | (cp & 0x3f)); + ptr++; + + if (ptr == t.end()) + return; + + sv = sv.substr(1); + } else if (sv[0] < 0xe000) { + *ptr = (uint8_t)0xef; + ptr++; + + if (ptr == t.end()) + return; + + *ptr = (uint8_t)0xbf; + ptr++; + + if (ptr == t.end()) + return; + + *ptr = (uint8_t)0xbd; + ptr++; + + if (ptr == t.end()) + return; + } else { + *ptr = (uint8_t)(0xe0 | (sv[0] >> 12)); + ptr++; + + if (ptr == t.end()) + return; + + *ptr = (uint8_t)(0x80 | ((sv[0] >> 6) & 0x3f)); + ptr++; + + if (ptr == t.end()) + return; + + *ptr = (uint8_t)(0x80 | (sv[0] & 0x3f)); + ptr++; + + if (ptr == t.end()) + return; + } + + sv = sv.substr(1); + } +} + +static std::string __inline utf16_to_utf8(std::u16string_view sv) { + if (sv.empty()) + return ""; + + std::string ret(utf16_to_utf8_len(sv), 0); + + utf16_to_utf8_range(sv, ret); + + return ret; +} + +static constexpr size_t utf8_to_utf16_len(std::string_view sv) noexcept { + size_t ret = 0; + + while (!sv.empty()) { + if ((uint8_t)sv[0] < 0x80) { + ret++; + sv = sv.substr(1); + } else if (((uint8_t)sv[0] & 0xe0) == 0xc0 && (uint8_t)sv.length() >= 2 && ((uint8_t)sv[1] & 0xc0) == 0x80) { + ret++; + sv = sv.substr(2); + } else if (((uint8_t)sv[0] & 0xf0) == 0xe0 && (uint8_t)sv.length() >= 3 && ((uint8_t)sv[1] & 0xc0) == 0x80 && ((uint8_t)sv[2] & 0xc0) == 0x80) { + ret++; + sv = sv.substr(3); + } else if (((uint8_t)sv[0] & 0xf8) == 0xf0 && (uint8_t)sv.length() >= 4 && ((uint8_t)sv[1] & 0xc0) == 0x80 && ((uint8_t)sv[2] & 0xc0) == 0x80 && ((uint8_t)sv[3] & 0xc0) == 0x80) { + char32_t cp = (char32_t)(((uint8_t)sv[0] & 0x7) << 18) | (char32_t)(((uint8_t)sv[1] & 0x3f) << 12) | (char32_t)(((uint8_t)sv[2] & 0x3f) << 6) | (char32_t)((uint8_t)sv[3] & 0x3f); + + if (cp > 0x10ffff) { + ret++; + sv = sv.substr(4); + continue; + } + + ret += 2; + sv = sv.substr(4); + } else { + ret++; + sv = sv.substr(1); + } + } + + return ret; +} + +static constexpr size_t utf8_to_utf16_len(std::u8string_view sv) noexcept { + return utf8_to_utf16_len(std::string_view(std::bit_cast(sv.data()), sv.length())); +} + +template +requires (std::ranges::output_range && std::is_same_v, char16_t>) || + (sizeof(wchar_t) == 2 && std::ranges::output_range && std::is_same_v, wchar_t>) +static constexpr void utf8_to_utf16_range(std::string_view sv, T& t) noexcept { + auto ptr = t.begin(); + + if (ptr == t.end()) + return; + + while (!sv.empty()) { + if ((uint8_t)sv[0] < 0x80) { + *ptr = (uint8_t)sv[0]; + ptr++; + + if (ptr == t.end()) + return; + + sv = sv.substr(1); + } else if (((uint8_t)sv[0] & 0xe0) == 0xc0 && (uint8_t)sv.length() >= 2 && ((uint8_t)sv[1] & 0xc0) == 0x80) { + char16_t cp = (char16_t)(((uint8_t)sv[0] & 0x1f) << 6) | (char16_t)((uint8_t)sv[1] & 0x3f); + + *ptr = cp; + ptr++; + + if (ptr == t.end()) + return; + + sv = sv.substr(2); + } else if (((uint8_t)sv[0] & 0xf0) == 0xe0 && (uint8_t)sv.length() >= 3 && ((uint8_t)sv[1] & 0xc0) == 0x80 && ((uint8_t)sv[2] & 0xc0) == 0x80) { + char16_t cp = (char16_t)(((uint8_t)sv[0] & 0xf) << 12) | (char16_t)(((uint8_t)sv[1] & 0x3f) << 6) | (char16_t)((uint8_t)sv[2] & 0x3f); + + if (cp >= 0xd800 && cp <= 0xdfff) { + *ptr = 0xfffd; + ptr++; + + if (ptr == t.end()) + return; + + sv = sv.substr(3); + continue; + } + + *ptr = cp; + ptr++; + + if (ptr == t.end()) + return; + + sv = sv.substr(3); + } else if (((uint8_t)sv[0] & 0xf8) == 0xf0 && (uint8_t)sv.length() >= 4 && ((uint8_t)sv[1] & 0xc0) == 0x80 && ((uint8_t)sv[2] & 0xc0) == 0x80 && ((uint8_t)sv[3] & 0xc0) == 0x80) { + char32_t cp = (char32_t)(((uint8_t)sv[0] & 0x7) << 18) | (char32_t)(((uint8_t)sv[1] & 0x3f) << 12) | (char32_t)(((uint8_t)sv[2] & 0x3f) << 6) | (char32_t)((uint8_t)sv[3] & 0x3f); + + if (cp > 0x10ffff) { + *ptr = 0xfffd; + ptr++; + + if (ptr == t.end()) + return; + + sv = sv.substr(4); + continue; + } + + cp -= 0x10000; + + *ptr = (char16_t)(0xd800 | (cp >> 10)); + ptr++; + + if (ptr == t.end()) + return; + + *ptr = (char16_t)(0xdc00 | (cp & 0x3ff)); + ptr++; + + if (ptr == t.end()) + return; + + sv = sv.substr(4); + } else { + *ptr = 0xfffd; + ptr++; + + if (ptr == t.end()) + return; + + sv = sv.substr(1); + } + } +} + +template +requires (std::ranges::output_range && std::is_same_v, char16_t>) || + (sizeof(wchar_t) == 2 && std::ranges::output_range && std::is_same_v, wchar_t>) +static constexpr void utf8_to_utf16_range(std::u8string_view sv, T& t) noexcept { + utf8_to_utf16_range(std::string_view((char*)sv.data(), sv.length()), t); +} + +static __inline std::u16string utf8_to_utf16(std::string_view sv) { + if (sv.empty()) + return u""; + + std::u16string ret(utf8_to_utf16_len(sv), 0); + + utf8_to_utf16_range(sv, ret); + + return ret; +} + +static __inline std::u16string utf8_to_utf16(std::u8string_view sv) { + if (sv.empty()) + return u""; + + std::u16string ret(utf8_to_utf16_len(sv), 0); + + utf8_to_utf16_range(sv, ret); + + return ret; +} diff --git a/src/xlcpp/xlcpp-pimpl.h b/src/xlcpp/xlcpp-pimpl.h new file mode 100644 index 000000000..f3614fcd4 --- /dev/null +++ b/src/xlcpp/xlcpp-pimpl.h @@ -0,0 +1,364 @@ +#pragma once + +#include "xlcpp.h" +#include "mmap.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xlcpp { + +struct shared_string { + unsigned int num; +}; + +typedef struct { + std::string content_type; + std::string data; +} file; + +class font { +public: + font(std::string_view font_name, unsigned int font_size, bool bold) : font_name(font_name), font_size(font_size), bold(bold) { } + + std::string font_name; + unsigned int font_size; + bool bold; +}; + +class font_hash { +public: + size_t operator()(const font& f) const { + return std::hash{}(f.font_name) | + (std::hash{}(f.font_size) << 1) | + (std::hash{}(f.bold) << 2); + } +}; + +bool operator==(const font& lhs, const font& rhs) noexcept; + +class style { +public: + style(std::string_view number_format, std::string_view font, unsigned int font_size, bool bold = false) : + number_format(number_format), font(font, font_size, bold) { } + + void set_font(std::string_view font_name, unsigned int font_size, bool bold); + void set_number_format(std::string_view fmt); + + std::string number_format; + xlcpp::font font; + + mutable unsigned int num; + mutable unsigned int number_format_num; +}; + +class style_hash { +public: + size_t operator()(const style& s) const { + return std::hash{}(s.number_format) | + (font_hash{}(s.font) << 1); + } +}; + +bool operator==(const style& lhs, const style& rhs) noexcept; + +class workbook_pimpl { +public: + workbook_pimpl() = default; + workbook_pimpl(const std::filesystem::path& fn, std::string_view password, std::string_view outfile); + workbook_pimpl(std::span sv, std::string_view password, std::string_view outfile); + sheet& add_sheet(std::string_view name, bool visible); + void save(const std::filesystem::path& fn) const; + std::string data() const; + + void write_workbook_xml(struct archive* a) const; + void write_content_types_xml(struct archive* a) const; + void write_rels(struct archive* a) const; + void write_workbook_rels(struct archive* a) const; + shared_string get_shared_string(std::string_view s); + void write_shared_strings(struct archive* a) const; + void write_styles(struct archive* a) const; + void write_archive(struct archive* a) const; + la_ssize_t write_callback(struct archive* a, const void* buffer, size_t length) const; + void parse_workbook(std::string_view fn, std::string_view data, + const std::unordered_map& files); + void parse_workbook_binary(std::string_view fn, std::span data, + const std::unordered_map& files); + void load_sheet(std::string_view name, std::string_view data, bool visible); + void load_sheet_binary(std::string_view name, std::span data, bool visible); + void load_shared_strings2(std::string_view sv); + void load_shared_strings_binary(std::span data); + void load_shared_strings(const std::unordered_map& files); + void load_styles(const std::unordered_map& files); + void load_styles2(std::string_view sv); + void load_styles_binary(std::span data); + std::string find_number_format(unsigned int num); + void load_archive(struct archive* a); + +#ifdef _WIN32 + void rename(const std::filesystem::path& fn) const; +#endif + + template + const style* find_style(Args&&... args) { + auto ret = styles.emplace(args...); + + if (ret.second) + ret.first->num = (unsigned int)(styles.size() - 1); + + return &(*ret.first); + } + + std::list sheets; + std::map> shared_strings; + std::vector shared_strings2; + std::unordered_set styles; + std::unordered_map number_formats; + std::vector> cell_styles; + bool date1904 = false; + + mutable std::string buf; + +#ifdef _WIN32 + unique_handle h; + HANDLE h2; + uint8_t readbuf[1048576]; +#endif + +private: + void load_from_memory(std::span sv, std::string_view password, std::string_view outfile); +}; + +class sheet_pimpl { +public: + sheet_pimpl(workbook_pimpl& wb, std::string_view name, unsigned int num, bool visible) : parent(wb), name(name), num(num), visible(visible) { } + + void write(struct archive* a) const; + std::string xml() const; + row& add_row(); + + workbook_pimpl& parent; + std::string name; + unsigned int num; + bool visible; + std::list rows; +}; + +class row_pimpl { +public: + row_pimpl(sheet_pimpl& s, unsigned int num) : parent(s), num(num) { } + + template + cell& add_cell(const T& val) { + return *cells.emplace(cells.end(), *this, cells.size() + 1, val); + } + + sheet_pimpl& parent; + unsigned int num; + std::list cells; +}; + +class cell_pimpl { +public: + template + cell_pimpl(row_pimpl& r, unsigned int num, const T& t); + + cell_pimpl(row_pimpl& r, unsigned int num, std::string_view t); + cell_pimpl(row_pimpl& r, unsigned int num, const std::chrono::system_clock::time_point& val) : cell_pimpl(r, num, datetime{val}) { } + + void set_number_format(std::string_view fmt); + void set_font(std::string_view name, unsigned int size, bool bold = false); + + row_pimpl& parent; + + const style* sty; + + unsigned int num; + std::variant val; + std::string number_format; +}; + +}; + +class xml_writer { +public: + std::string dump() const; + void start_document(); + void start_element(std::string_view tag, const std::unordered_map& namespaces = {}); + void end_element(); + void text(std::string_view s); + void attribute(std::string_view name, std::string_view value); + +private: + std::string buf; + std::stack tags; + bool empty_tag; +}; + +enum class xml_node { + unknown, + text, + whitespace, + element, + end_element, + processing_instruction, + comment, + cdata +}; + +template<> +struct fmt::formatter { + constexpr auto parse(format_parse_context& ctx) { + auto it = ctx.begin(); + + if (it != ctx.end() && *it != '}') + throw format_error("invalid format"); + + return it; + } + + template + auto format(enum xml_node n, format_context& ctx) const { + switch (n) { + case xml_node::unknown: + return fmt::format_to(ctx.out(), "unknown"); + case xml_node::text: + return fmt::format_to(ctx.out(), "text"); + case xml_node::whitespace: + return fmt::format_to(ctx.out(), "whitespace"); + case xml_node::element: + return fmt::format_to(ctx.out(), "element"); + case xml_node::end_element: + return fmt::format_to(ctx.out(), "end_element"); + case xml_node::processing_instruction: + return fmt::format_to(ctx.out(), "processing_instruction"); + case xml_node::comment: + return fmt::format_to(ctx.out(), "comment"); + case xml_node::cdata: + return fmt::format_to(ctx.out(), "cdata"); + default: + return fmt::format_to(ctx.out(), "{:x}", (unsigned int)n); + } + } +}; + +class xml_enc_string_view { +public: + xml_enc_string_view() { } + xml_enc_string_view(std::string_view sv) : sv(sv) { } + + bool empty() const noexcept { + return sv.empty(); + } + + std::string decode() const; + bool cmp(std::string_view str) const; + +private: + std::string_view sv; +}; + +using ns_list = std::vector>; + +class xml_reader { +public: + xml_reader(std::string_view sv) : sv(sv) { } + bool read(); + enum xml_node node_type() const; + bool is_empty() const; + void attributes_loop_raw(const std::function& func) const; + std::optional get_attribute(std::string_view name, std::string_view ns = "") const; + xml_enc_string_view namespace_uri_raw() const; + std::string_view name() const; + std::string_view local_name() const; + std::string value() const; + +private: + std::string_view sv, node; + enum xml_node type = xml_node::unknown; + bool empty_tag; + std::vector namespaces; +}; + +class archive_read_closer { +public: + typedef archive* pointer; + + void operator()(archive* a) { + archive_read_free(a); + } +}; + +using archive_read_t = std::unique_ptr; + +class archive_write_closer { +public: + typedef archive* pointer; + + void operator()(archive* a) { + archive_write_free(a); + } +}; + +using archive_write_t = std::unique_ptr; + + +static constexpr std::chrono::year_month_day number_to_date(unsigned int num, bool date1904) noexcept { + unsigned int J = num + 2415019; + unsigned int f, e, g, h; + unsigned int day, month; + int year; + + if (date1904) + J += 1462; + else if (num < 61) // Excel's 29/2/1900 bug + J++; + + f = J; + f *= 4; + f += 274277; + f /= 146097; + f *= 3; + f /= 4; + f += J; + f += 1363; + + e = (f * 4) + 3; + + g = e % 1461; + g /= 4; + + h = (5 * g) + 2; + + day = h % 153; + day /= 5; + day++; + + month = h; + month /= 153; + month += 2; + month %= 12; + month++; + + year = 14 - month; + year /= 12; + year -= 4716; + year += e / 1461; + + return std::chrono::year_month_day{std::chrono::year{year}, std::chrono::month{month}, std::chrono::day{day}}; +} + +// xlcpp.cpp +bool is_date(std::string_view sv); +bool is_time(std::string_view sv); +std::unordered_map read_relationships(std::string_view fn, const std::unordered_map& files); diff --git a/src/xlcpp/xlcpp.cpp b/src/xlcpp/xlcpp.cpp new file mode 100644 index 000000000..d873cb5e7 --- /dev/null +++ b/src/xlcpp/xlcpp.cpp @@ -0,0 +1,2070 @@ +#include "openxlsx2.h" +#include +#include + +#include "xlcpp.h" +#include "xlcpp-pimpl.h" +#include "mmap.h" +#include "cfbf.h" +#include "utf16.h" +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#endif + +#define FMT_HEADER_ONLY +#include +#include + +#define BLOCK_SIZE 20480 + +using namespace std; + +static const string NS_SPREADSHEET = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"; +static const string NS_SPREADSHEET_STRICT = "http://purl.oclc.org/ooxml/spreadsheetml/main"; +static const string NS_RELATIONSHIPS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"; +static const string NS_RELATIONSHIPS_STRICT = "http://purl.oclc.org/ooxml/officeDocument/relationships"; +static const string NS_PACKAGE_RELATIONSHIPS = "http://schemas.openxmlformats.org/package/2006/relationships"; +static const string NS_CONTENT_TYPES = "http://schemas.openxmlformats.org/package/2006/content-types"; + +#define NUMFMT_OFFSET 165 + +static const array builtin_styles = { + pair{ 0, "General" }, + pair{ 1, "0" }, + pair{ 2, "0.00" }, + pair{ 3, "#,##0" }, + pair{ 4, "#,##0.00" }, + pair{ 9, "0%" }, + pair{ 10, "0.00%" }, + pair{ 11, "0.00E+00" }, + pair{ 12, "# ?/?" }, + pair{ 13, "# \?\?/??" }, + pair{ 14, "mm-dd-yy" }, + pair{ 15, "d-mmm-yy" }, + pair{ 16, "d-mmm" }, + pair{ 17, "mmm-yy" }, + pair{ 18, "h:mm AM/PM" }, + pair{ 19, "h:mm:ss AM/PM" }, + pair{ 20, "h:mm" }, + pair{ 21, "h:mm:ss" }, + pair{ 22, "m/d/yy h:mm" }, + pair{ 37, "#,##0 ;(#,##0)" }, + pair{ 38, "#,##0 ;[Red](#,##0)" }, + pair{ 39, "#,##0.00;(#,##0.00)" }, + pair{ 40, "#,##0.00;[Red](#,##0.00)" }, + pair{ 45, "mm:ss" }, + pair{ 46, "[h]:mm:ss" }, + pair{ 47, "mmss.0" }, + pair{ 48, "##0.0E+0" }, + pair{ 49, "@" }, +}; + +bool is_date(string_view sv) { + if (sv == "General") + return false; + + string s; + + s.reserve(sv.length()); + + for (auto c : sv) { + if (c >= 'a' && c <= 'z') { + if (s.empty() || s.back() != c) + s += c; + } else if (c >= 'A' && c <= 'Z') { + if (s.empty() || s.back() != c - 'A' + 'a') + s += c - 'A' + 'a'; + } + } + + static const char* patterns[] = { + "dmy", + "ymd", + "mdy", + "my" + }; + + for (const auto& p : patterns) { + if (s.find(p) != string::npos) + return true; + } + + return false; +} + +bool is_time(string_view sv) { + if (sv == "General") + return false; + + string s; + + s.reserve(sv.length()); + + for (auto c : sv) { + if (c >= 'a' && c <= 'z') { + if (s.empty() || s.back() != c) + s += c; + } else if (c >= 'A' && c <= 'Z') { + if (s.empty() || s.back() != c - 'A' + 'a') + s += c - 'A' + 'a'; + } + } + + // FIXME - "h AM/PM" etc. + + return s.find("hm") != string::npos; +} + +static string try_decode(const optional& sv) { + if (!sv) + return ""; + + return sv.value().decode(); +} + +unordered_map read_relationships(string_view fn, const unordered_map& files) { + filesystem::path p = fn; + unordered_map rels; + + p.remove_filename(); + p /= "_rels"; + p /= filesystem::path(fn).filename().string() + ".rels"; + + auto ps = p.string(); + + for (auto& c : ps) { + if (c == '\\') + c = '/'; + } + + if (files.count(ps) == 0) + Rcpp::stop("File {} not found.", ps); + + xml_reader r(files.at(ps).data); + unsigned int depth = 0; + + while (r.read()) { + unsigned int next_depth; + + if (r.node_type() == xml_node::element && !r.is_empty()) + next_depth = depth + 1; + else if (r.node_type() == xml_node::end_element) + next_depth = depth - 1; + else + next_depth = depth; + + if (r.node_type() == xml_node::element) { + if (depth == 0) { + if (r.local_name() != "Relationships" || !r.namespace_uri_raw().cmp(NS_PACKAGE_RELATIONSHIPS)) { + Rcpp::stop("Root tag was {{{}}}{}, expected {{{}}}Relationships.", + r.namespace_uri_raw().decode(), r.local_name(), NS_PACKAGE_RELATIONSHIPS); + } + } else if (depth == 1) { + if (r.local_name() == "Relationship" && r.namespace_uri_raw().cmp(NS_PACKAGE_RELATIONSHIPS)) { + auto id = try_decode(r.get_attribute("Id")); + auto target = try_decode(r.get_attribute("Target")); + + if (!id.empty() && !target.empty()) + rels[id] = target; + } + } + } + + depth = next_depth; + } + + return rels; +} + +namespace xlcpp { + +sheet& workbook_pimpl::add_sheet(string_view name, bool visible) { + return *sheets.emplace(sheets.end(), *this, name, sheets.size() + 1, visible); +} + +sheet& workbook::add_sheet(string_view name, bool visible) { + return impl->add_sheet(name, visible); +} + +class _formatted_error : public exception { +public: + template + _formatted_error(T&& s, Args&&... args) { + msg = fmt::format(s, forward(args)...); + } + + const char* what() const noexcept { + return msg.c_str(); + } + +private: + string msg; +}; + +#define formatted_error(s, ...) _formatted_error(FMT_COMPILE(s), ##__VA_ARGS__) + +static string make_reference(unsigned int row, unsigned int col) { + char colstr[5]; + + col--; + + if (col < 26) { + colstr[0] = (char)(col + 'A'); + colstr[1] = 0; + } else if (col < 702) { + colstr[0] = (char)((col / 26) - 1 + 'A'); + colstr[1] = (char)((col % 26) + 'A'); + colstr[2] = 0; + } else if (col < 16384) { + colstr[0] = (char)((col / 676) - 1 + 'A'); + colstr[1] = (char)(((col / 26) % 26) - 1 + 'A'); + colstr[2] = (char)((col % 26) + 'A'); + colstr[3] = 0; + } else + Rcpp::stop("Column {} too large.", col); + + return string(colstr) + to_string(row); +} + +// hopefully from_chars will be constexpr some day - see P2291R0 +template +requires is_integral_v && is_unsigned_v +static constexpr from_chars_result from_chars_constexpr(const char* first, const char* last, T& t) { + from_chars_result res; + + res.ptr = first; + res.ec = {}; + + t = 0; + + if (first == last || *first < '0' || *first > '9') + return res; + + while (first != last && *first >= '0' && *first <= '9') { + t *= 10; + t += (T)(*first - '0'); + first++; + res.ptr++; + } + + return res; +} + +static constexpr bool resolve_reference(string_view sv, unsigned int& row, unsigned int& col) noexcept { + from_chars_result fcr; + + if (sv.length() >= 2 && sv[0] >= 'A' && sv[0] <= 'Z' && sv[1] >= '0' && sv[1] <= '9') { + col = sv[0] - 'A'; + fcr = from_chars_constexpr(sv.data() + 1, sv.data() + sv.length(), row); + } else if (sv.length() >= 3 && sv[0] >= 'A' && sv[0] <= 'Z' && sv[1] >= 'A' && sv[1] <= 'Z' && sv[2] >= '0' && sv[2] <= '9') { + col = ((sv[0] - 'A' + 1) * 26) + sv[1] - 'A'; + fcr = from_chars_constexpr(sv.data() + 2, sv.data() + sv.length(), row); + } else if (sv.length() >= 4 && sv[0] >= 'A' && sv[0] <= 'Z' && sv[1] >= 'A' && sv[1] <= 'Z' && sv[2] >= 'A' && sv[2] <= 'Z' && sv[3] >= '0' && sv[3] <= '9') { + col = ((sv[0] - 'A' + 1) * 676) + ((sv[1] - 'A' + 1) * 26) + sv[2] - 'A'; + fcr = from_chars_constexpr(sv.data() + 3, sv.data() + sv.length(), row); + } else + return false; + + if (fcr.ptr != sv.data() + sv.length()) + return false; + + row--; + + return true; +} + +static constexpr bool test_resolve_reference(string_view sv, bool exp_valid, unsigned int exp_row, + unsigned int exp_col) noexcept { + bool valid; + unsigned int row, col; + + valid = resolve_reference(sv, row, col); + + if (!valid) + return !exp_valid; + else if (!exp_valid) + return false; + + return row == exp_row && col == exp_col; +} + +static_assert(test_resolve_reference("", false, 0, 0)); +static_assert(test_resolve_reference("A", false, 0, 0)); +static_assert(test_resolve_reference("1", false, 0, 0)); +static_assert(test_resolve_reference("A1", true, 0, 0)); +static_assert(test_resolve_reference("D3", true, 2, 3)); +static_assert(test_resolve_reference("Z255", true, 254, 25)); +static_assert(test_resolve_reference("AA", false, 0, 0)); +static_assert(test_resolve_reference("AA1", true, 0, 26)); +static_assert(test_resolve_reference("MH229", true, 228, 345)); +static_assert(test_resolve_reference("ZZ16383", true, 16382, 701)); +static_assert(test_resolve_reference("AAA", false, 0, 0)); +static_assert(test_resolve_reference("AAA1", true, 0, 702)); +static_assert(test_resolve_reference("AMJ1048576", true, 1048575, 1023)); + +static constexpr unsigned int date_to_number(const chrono::year_month_day& ymd, bool date1904) noexcept { + int m2 = ((int)(unsigned int)ymd.month() - 14) / 12; + long long n; + + n = (1461 * ((int)ymd.year() + 4800 + m2)) / 4; + n += (367 * ((int)(unsigned int)ymd.month() - 2 - (12 * m2))) / 12; + n -= (3 * (((int)ymd.year() + 4900 + m2)/100)) / 4; + n += (unsigned int)ymd.day(); + n -= 2447094; + + if (date1904) + n -= 1462; + else if (n < 61) // Excel's 29/2/1900 bug + n--; + + return (unsigned int)n; +} + +static_assert(date_to_number(chrono::year_month_day{1900y, chrono::January, 1d}, false) == 1); +static_assert(date_to_number(chrono::year_month_day{1900y, chrono::February, 28d}, false) == 59); +static_assert(date_to_number(chrono::year_month_day{1900y, chrono::March, 1d}, false) == 61); +static_assert(date_to_number(chrono::year_month_day{1998y, chrono::July, 5d}, false) == 35981); +static_assert(date_to_number(chrono::year_month_day{1998y, chrono::July, 5d}, true) == 34519); + +static_assert(number_to_date(1, false) == chrono::year_month_day{1900y, chrono::January, 1d}); +static_assert(number_to_date(59, false) == chrono::year_month_day{1900y, chrono::February, 28d}); +static_assert(number_to_date(61, false) == chrono::year_month_day{1900y, chrono::March, 1d}); +static_assert(number_to_date(35981, false) == chrono::year_month_day{1998y, chrono::July, 5d}); +static_assert(number_to_date(34519, true) == chrono::year_month_day{1998y, chrono::July, 5d}); + +static constexpr double datetime_to_number(const datetime& dt, bool date1904) noexcept { + return (double)date_to_number(dt.d, date1904) + ((double)dt.t.count() / 86400.0); +} + +template +inline constexpr bool always_false_v = false; + +string sheet_pimpl::xml() const { + xml_writer writer; + + writer.start_document(); + + writer.start_element("worksheet", {{"", NS_SPREADSHEET}}); + + writer.start_element("sheetData"); + + for (const auto& r : rows) { + writer.start_element("row"); + + writer.attribute("r", to_string(r.impl->num)); + writer.attribute("customFormat", "false"); + writer.attribute("ht", "12.8"); + writer.attribute("hidden", "false"); + writer.attribute("customHeight", "false"); + writer.attribute("outlineLevel", "0"); + writer.attribute("collapsed", "false"); + + for (const auto& c : r.impl->cells) { + writer.start_element("c"); + + writer.attribute("r", make_reference(r.impl->num, c.impl->num)); + writer.attribute("s", to_string(c.impl->sty->num)); + + visit([&](auto&& arg) { + using T = decay_t; + + if constexpr (is_same_v) { + writer.attribute("t", "n"); // number + + writer.start_element("v"); + writer.text(to_string(arg)); + writer.end_element(); + } else if constexpr (is_same_v) { + writer.attribute("t", "s"); // shared string + + writer.start_element("v"); + writer.text(to_string(arg.num)); + writer.end_element(); + } else if constexpr (is_same_v) { + writer.attribute("t", "n"); // number + + writer.start_element("v"); + writer.text(to_string(arg)); + writer.end_element(); + } else if constexpr (is_same_v) { + writer.attribute("t", "n"); // number + + writer.start_element("v"); + writer.text(to_string(date_to_number(arg, parent.date1904))); + writer.end_element(); + } else if constexpr (is_same_v) { + auto s = (double)arg.count() / 86400.0; + + writer.attribute("t", "n"); // number + + writer.start_element("v"); + writer.text(to_string(s)); + writer.end_element(); + } else if constexpr (is_same_v) { + writer.attribute("t", "n"); // number + + writer.start_element("v"); + writer.text(to_string(datetime_to_number(arg, parent.date1904))); + writer.end_element(); + } else if constexpr (is_same_v) { + writer.attribute("t", "b"); // bool + + writer.start_element("v"); + writer.text(to_string(arg)); + writer.end_element(); + } else if constexpr (is_same_v) { + // nop + } else if constexpr (is_same_v) { + writer.attribute("t", "str"); + + writer.start_element("v"); + writer.text(arg); // FIXME - Unicode should be encoded + writer.end_element(); + } else + static_assert(always_false_v); + }, c.impl->val); + + writer.end_element(); + } + + writer.end_element(); + } + + writer.end_element(); + + writer.end_element(); + + return writer.dump(); +} + +void sheet_pimpl::write(struct archive* a) const { + struct archive_entry* entry; + string data = xml(); + + entry = archive_entry_new(); + archive_entry_set_pathname(entry, ("xl/worksheets/sheet" + to_string(num) + ".xml").c_str()); + archive_entry_set_size(entry, data.length()); + archive_entry_set_filetype(entry, AE_IFREG); + archive_entry_set_perm(entry, 0644); + archive_write_header(a, entry); + archive_write_data(a, data.data(), data.length()); + // FIXME - set date? + archive_entry_free(entry); +} + +void workbook_pimpl::write_workbook_xml(struct archive* a) const { + struct archive_entry* entry; + string data; + + { + xml_writer writer; + + writer.start_document(); + + writer.start_element("workbook", {{"", NS_SPREADSHEET}, {"r", NS_RELATIONSHIPS}}); + + writer.start_element("workbookPr"); + writer.attribute("date1904", date1904 ? "true" : "false"); + writer.end_element(); + + writer.start_element("sheets"); + + for (const auto& sh : sheets) { + writer.start_element("sheet"); + writer.attribute("name", sh.impl->name); + writer.attribute("sheetId", to_string(sh.impl->num)); + writer.attribute("state", "visible"); + writer.attribute("r:id", "rId" + to_string(sh.impl->num)); + writer.end_element(); + } + + writer.end_element(); + + writer.end_element(); + + data = move(writer.dump()); + } + + entry = archive_entry_new(); + archive_entry_set_pathname(entry, "xl/workbook.xml"); + archive_entry_set_size(entry, data.length()); + archive_entry_set_filetype(entry, AE_IFREG); + archive_entry_set_perm(entry, 0644); + archive_write_header(a, entry); + archive_write_data(a, data.data(), data.length()); + // FIXME - set date? + archive_entry_free(entry); +} + +void workbook_pimpl::write_content_types_xml(struct archive* a) const { + struct archive_entry* entry; + string data; + + { + xml_writer writer; + + writer.start_document(); + + writer.start_element("Types", {{"", NS_CONTENT_TYPES}}); + + writer.start_element("Override"); + writer.attribute("PartName", "/xl/workbook.xml"); + writer.attribute("ContentType", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"); + writer.end_element(); + + for (const auto& sh : sheets) { + writer.start_element("Override"); + writer.attribute("PartName", "/xl/worksheets/sheet" + to_string(sh.impl->num) + ".xml"); + writer.attribute("ContentType", "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"); + writer.end_element(); + } + + writer.start_element("Override"); + writer.attribute("PartName", "/_rels/.rels"); + writer.attribute("ContentType", "application/vnd.openxmlformats-package.relationships+xml"); + writer.end_element(); + + writer.start_element("Override"); + writer.attribute("PartName", "/xl/_rels/workbook.xml.rels"); + writer.attribute("ContentType", "application/vnd.openxmlformats-package.relationships+xml"); + writer.end_element(); + + if (!shared_strings.empty()) { + writer.start_element("Override"); + writer.attribute("PartName", "/xl/sharedStrings.xml"); + writer.attribute("ContentType", "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"); + writer.end_element(); + } + + if (!styles.empty()) { + writer.start_element("Override"); + writer.attribute("PartName", "/xl/styles.xml"); + writer.attribute("ContentType", "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"); + writer.end_element(); + } + + writer.end_element(); + + data = move(writer.dump()); + } + + entry = archive_entry_new(); + archive_entry_set_pathname(entry, "[Content_Types].xml"); + archive_entry_set_size(entry, data.length()); + archive_entry_set_filetype(entry, AE_IFREG); + archive_entry_set_perm(entry, 0644); + archive_write_header(a, entry); + archive_write_data(a, data.data(), data.length()); + // FIXME - set date? + archive_entry_free(entry); +} + +void workbook_pimpl::write_rels(struct archive* a) const { + struct archive_entry* entry; + string data; + + { + xml_writer writer; + + writer.start_document(); + + writer.start_element("Relationships", {{"", "http://schemas.openxmlformats.org/package/2006/relationships"}}); + + writer.start_element("Relationship"); + writer.attribute("Id", "rId1"); + writer.attribute("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"); + writer.attribute("Target", "/xl/workbook.xml"); + writer.end_element(); + + writer.end_element(); + + data = move(writer.dump()); + } + + entry = archive_entry_new(); + archive_entry_set_pathname(entry, "_rels/.rels"); + archive_entry_set_size(entry, data.length()); + archive_entry_set_filetype(entry, AE_IFREG); + archive_entry_set_perm(entry, 0644); + archive_write_header(a, entry); + archive_write_data(a, data.data(), data.length()); + // FIXME - set date? + archive_entry_free(entry); +} + +void workbook_pimpl::write_workbook_rels(struct archive* a) const { + struct archive_entry* entry; + string data; + + { + unsigned int num = 1; + xml_writer writer; + + writer.start_document(); + + writer.start_element("Relationships", {{"", "http://schemas.openxmlformats.org/package/2006/relationships"}}); + + for (const auto& sh : sheets) { + writer.start_element("Relationship"); + writer.attribute("Id", "rId" + to_string(num)); + writer.attribute("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"); + writer.attribute("Target", "worksheets/sheet" + to_string(sh.impl->num) + ".xml"); + writer.end_element(); + num++; + } + + if (!shared_strings.empty()) { + writer.start_element("Relationship"); + writer.attribute("Id", "rId" + to_string(num)); + writer.attribute("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"); + writer.attribute("Target", "sharedStrings.xml"); + writer.end_element(); + num++; + } + + if (!styles.empty()) { + writer.start_element("Relationship"); + writer.attribute("Id", "rId" + to_string(num)); + writer.attribute("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"); + writer.attribute("Target", "styles.xml"); + writer.end_element(); + } + + writer.end_element(); + + data = move(writer.dump()); + } + + entry = archive_entry_new(); + archive_entry_set_pathname(entry, "xl/_rels/workbook.xml.rels"); + archive_entry_set_size(entry, data.length()); + archive_entry_set_filetype(entry, AE_IFREG); + archive_entry_set_perm(entry, 0644); + archive_write_header(a, entry); + archive_write_data(a, data.data(), data.length()); + // FIXME - set date? + archive_entry_free(entry); +} + +void workbook_pimpl::write_shared_strings(struct archive* a) const { + struct archive_entry* entry; + string data; + + { + xml_writer writer; + + writer.start_document(); + + writer.start_element("sst", {{"", NS_SPREADSHEET}}); + writer.attribute("count", to_string(shared_strings.size())); + writer.attribute("uniqueCount", to_string(shared_strings.size())); + + vector strings; + + strings.resize(shared_strings.size()); + + for (const auto& ss : shared_strings) { + strings[ss.second.num] = ss.first; + } + + for (const auto& s : strings) { + writer.start_element("si"); + + writer.start_element("t"); + writer.attribute("xml:space", "preserve"); + writer.text(s); + writer.end_element(); + + writer.end_element(); + } + + writer.end_element(); + + data = move(writer.dump()); + } + + entry = archive_entry_new(); + archive_entry_set_pathname(entry, "xl/sharedStrings.xml"); + archive_entry_set_size(entry, data.length()); + archive_entry_set_filetype(entry, AE_IFREG); + archive_entry_set_perm(entry, 0644); + archive_write_header(a, entry); + archive_write_data(a, data.data(), data.length()); + // FIXME - set date? + archive_entry_free(entry); +} + +void workbook_pimpl::write_styles(struct archive* a) const { + struct archive_entry* entry; + string data; + + { + xml_writer writer; + vector sty; + vector number_format_nums; + unordered_map number_formats; + vector number_formats2; + font default_font("Arial", 10, false); + unordered_map fonts; + vector fonts2; + + writer.start_document(); + + writer.start_element("styleSheet"); + writer.attribute("xmlns", NS_SPREADSHEET); + + fonts[default_font] = 0; + fonts2.emplace_back(&default_font); + + for (const auto& s : styles) { + if (number_formats.find(s.number_format) == number_formats.end()) { + number_formats[s.number_format] = (unsigned int)number_formats.size(); + number_formats2.emplace_back(&s.number_format); + } + + if (fonts.find(s.font) == fonts.end()) { + fonts[s.font] = (unsigned int)fonts.size(); + fonts2.emplace_back(&s.font); + } + } + + writer.start_element("numFmts"); + writer.attribute("count", to_string(number_formats.size())); + + for (unsigned int i = 0; i < number_formats.size(); i++) { + writer.start_element("numFmt"); + writer.attribute("numFmtId", to_string(i + NUMFMT_OFFSET)); + writer.attribute("formatCode", *number_formats2[i]); + writer.end_element(); + } + + writer.end_element(); + + writer.start_element("fonts"); + writer.attribute("count", to_string(fonts.size())); + + for (unsigned int i = 0; i < fonts.size(); i++) { + writer.start_element("font"); + + if (fonts2[i]->bold) { + writer.start_element("b"); + writer.attribute("val", "true"); + writer.end_element(); + } + + writer.start_element("sz"); + writer.attribute("val", to_string(fonts2[i]->font_size)); + writer.end_element(); + + writer.start_element("name"); + writer.attribute("val", fonts2[i]->font_name); + writer.end_element(); + + writer.end_element(); + } + + writer.end_element(); + + writer.start_element("fills"); + writer.attribute("count", "2"); + + writer.start_element("fill"); + writer.start_element("patternFill"); + writer.attribute("patternType", "none"); + writer.end_element(); + writer.end_element(); + + writer.start_element("fill"); + writer.start_element("patternFill"); + writer.attribute("patternType", "gray125"); + writer.end_element(); + writer.end_element(); + + writer.end_element(); + + writer.start_element("borders"); + writer.attribute("count", "1"); + + writer.start_element("border"); + writer.start_element("left"); writer.end_element(); + writer.start_element("right"); writer.end_element(); + writer.start_element("top"); writer.end_element(); + writer.start_element("bottom"); writer.end_element(); + writer.start_element("diagonal"); writer.end_element(); + writer.end_element(); + + writer.end_element(); + + sty.resize(styles.size()); + + for (const auto& s : styles) { + sty[s.num] = &s; + } + + { + unsigned int style_id = 0; + + writer.start_element("cellXfs"); + writer.attribute("count", to_string(styles.size())); + + for (const auto& s : sty) { + writer.start_element("xf"); + writer.attribute("numFmtId", to_string(number_formats[s->number_format] + NUMFMT_OFFSET)); + writer.attribute("fontId", to_string(fonts[s->font])); + writer.attribute("fillId", "0"); + writer.attribute("borderId", "0"); + writer.attribute("applyFont", "true"); + writer.attribute("applyNumberFormat", "true"); + writer.end_element(); + + style_id++; + } + + writer.end_element(); + } + + writer.end_element(); + + data = move(writer.dump()); + } + + entry = archive_entry_new(); + archive_entry_set_pathname(entry, "xl/styles.xml"); + archive_entry_set_size(entry, data.length()); + archive_entry_set_filetype(entry, AE_IFREG); + archive_entry_set_perm(entry, 0644); + archive_write_header(a, entry); + archive_write_data(a, data.data(), data.length()); + // FIXME - set date? + archive_entry_free(entry); +} + +void workbook_pimpl::write_archive(struct archive* a) const { + for (const auto& sh : sheets) { + sh.impl->write(a); + } + + write_workbook_xml(a); + write_content_types_xml(a); + write_rels(a); + write_workbook_rels(a); + + if (!shared_strings.empty()) + write_shared_strings(a); + + if (!styles.empty()) + write_styles(a); +} + +void workbook_pimpl::save(const filesystem::path& fn) const { + struct archive* a; + + a = archive_write_new(); + archive_write_set_format_zip(a); + + archive_write_open_filename(a, fn.string().c_str()); + + write_archive(a); + + archive_write_close(a); + archive_write_free(a); +} + +void workbook::save(const filesystem::path& fn) const { + impl->save(fn); +} + +static int archive_dummy_callback(struct archive* a, void* client_data) { + return ARCHIVE_OK; +} + +la_ssize_t workbook_pimpl::write_callback(struct archive* a, const void* buffer, size_t length) const { + buf.append((const char*)buffer, length); + + return length; +} + +string workbook_pimpl::data() const { + buf.clear(); + + archive_write_t a{archive_write_new()}; + + archive_write_set_format_zip(a.get()); + archive_write_set_bytes_in_last_block(a.get(), 1); + + auto ret = archive_write_open(a.get(), (void*)this, archive_dummy_callback, + [](struct archive* a, void* client_data, const void* buffer, size_t length) { + auto wb = (const workbook_pimpl*)client_data; + + return wb->write_callback(a, buffer, length); + }, + archive_dummy_callback); + if (ret != ARCHIVE_OK) + Rcpp::stop("archive_write_open returned {}.", ret); + + write_archive(a.get()); + + archive_write_close(a.get()); + + return buf; +} + +string workbook::data() const { + return impl->data(); +} + +row& sheet_pimpl::add_row() { + return *rows.emplace(rows.end(), *this, rows.size() + 1); +} + +row& sheet::add_row() { + return impl->add_row(); +} + +shared_string workbook_pimpl::get_shared_string(string_view s) { + shared_string ss; + + if (shared_strings.contains(s)) + return shared_strings.find(s)->second; + + ss.num = (unsigned int)shared_strings.size(); + + shared_strings.emplace(s, ss); + + return ss; +} + +template +cell_pimpl::cell_pimpl(row_pimpl& r, unsigned int num, const T& t) : parent(r), num(num), val(t) { + if constexpr (is_same_v) + sty = parent.parent.parent.find_style(style("dd/mm/yy", "Arial", 10)); // FIXME - localization + else if constexpr (is_same_v) + sty = parent.parent.parent.find_style(style("HH:MM:SS", "Arial", 10)); // FIXME - localization + else if constexpr (is_same_v) + sty = parent.parent.parent.find_style(style("dd/mm/yy HH:MM:SS", "Arial", 10)); // FIXME - localization + else + sty = parent.parent.parent.find_style(style("General", "Arial", 10)); +} + +cell_pimpl::cell_pimpl(row_pimpl& r, unsigned int num, string_view t) : cell_pimpl(r, num, r.parent.parent.get_shared_string(t)) { +} + +cell::cell(row_pimpl& r, unsigned int num, int64_t val) { + impl = new cell_pimpl(r, num, val); +} + +cell::cell(row_pimpl& r, unsigned int num, string_view val) { + impl = new cell_pimpl(r, num, val); +} + +cell::cell(row_pimpl& r, unsigned int num, double val) { + impl = new cell_pimpl(r, num, val); +} + +cell::cell(row_pimpl& r, unsigned int num, const chrono::year_month_day& val) { + impl = new cell_pimpl(r, num, val); +} + +cell::cell(row_pimpl& r, unsigned int num, const chrono::seconds& val) { + impl = new cell_pimpl(r, num, val); +} + +cell::cell(row_pimpl& r, unsigned int num, const datetime& val) { + impl = new cell_pimpl(r, num, val); +} + +cell::cell(row_pimpl& r, unsigned int num, const chrono::system_clock::time_point& val) { + impl = new cell_pimpl(r, num, val); +} + +cell::cell(row_pimpl& r, unsigned int num, bool val) { + impl = new cell_pimpl(r, num, val); +} + +cell::cell(row_pimpl& r, unsigned int num, nullptr_t) { + impl = new cell_pimpl(r, num, nullptr); +} + +bool operator==(const font& lhs, const font& rhs) noexcept { + return lhs.font_name == rhs.font_name && + lhs.font_size == rhs.font_size && + ((lhs.bold && rhs.bold) || (!lhs.bold && !rhs.bold)); +} + +bool operator==(const style& lhs, const style& rhs) noexcept { + return lhs.number_format == rhs.number_format && + lhs.font == rhs.font; +} + +void style::set_font(string_view font_name, unsigned int font_size, bool bold) { + this->font = xlcpp::font(font_name, font_size, bold); +} + +void style::set_number_format(string_view fmt) { + number_format = fmt; +} + +void cell_pimpl::set_font(string_view name, unsigned int size, bool bold) { + auto sty2 = *sty; + + sty2.set_font(name, size, bold); + + sty = parent.parent.parent.find_style(sty2); +} + +void cell::set_font(string_view name, unsigned int size, bool bold) { + impl->set_font(name, size, bold); +} + +void cell_pimpl::set_number_format(string_view fmt) { + auto sty2 = *sty; + + sty2.set_number_format(fmt); + + sty = parent.parent.parent.find_style(sty2); +} + +void cell::set_number_format(string_view fmt) { + impl->set_number_format(fmt); +} + +workbook::workbook() { + impl = new workbook_pimpl; + impl->date1904 = false; +} + +static void parse_content_types(string_view ct, unordered_map& files) { + xml_reader r(ct); + unsigned int depth = 0; + unordered_map defs, over; + + while (r.read()) { + unsigned int next_depth; + + if (r.node_type() == xml_node::element && !r.is_empty()) + next_depth = depth + 1; + else if (r.node_type() == xml_node::end_element) + next_depth = depth - 1; + else + next_depth = depth; + + if (r.node_type() == xml_node::element) { + if (depth == 0) { + if (r.local_name() != "Types" || !r.namespace_uri_raw().cmp(NS_CONTENT_TYPES)) { + Rcpp::stop("Root tag was {{{}}}{}, expected {{{}}}Types.", + r.namespace_uri_raw().decode(), r.local_name(), NS_CONTENT_TYPES); + } + } else if (depth == 1) { + if (r.local_name() == "Default" && r.namespace_uri_raw().cmp(NS_CONTENT_TYPES)) { + auto ext = try_decode(r.get_attribute("Extension")); + auto ct = try_decode(r.get_attribute("ContentType")); + + defs[ext] = ct; + } else if (r.local_name() == "Override" && r.namespace_uri_raw().cmp(NS_CONTENT_TYPES)) { + auto part = try_decode(r.get_attribute("PartName")); + auto ct = try_decode(r.get_attribute("ContentType")); + + over[part] = ct; + } + } + } + + depth = next_depth; + } + + for (auto& f : files) { + for (const auto& o : over) { + if (o.first == "/" + f.first) { + f.second.content_type = o.second; + break; + } + } + + if (!f.second.content_type.empty()) + continue; + + auto st = f.first.rfind("."); + string ext = st == string::npos ? "" : f.first.substr(st + 1); + + for (const auto& d : defs) { + if (ext == d.first) { + f.second.content_type = d.second; + break; + } + } + } +} + +static const pair& find_workbook(const unordered_map& files) { + for (const auto& f : files) { + if (f.second.content_type == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" || + f.second.content_type == "application/vnd.ms-excel.sheet.macroEnabled.main+xml" || + f.second.content_type == "application/vnd.ms-excel.sheet.binary.macroEnabled.main") { + return f; + } + } + + Rcpp::stop("Could not find workbook file."); +} + +string workbook_pimpl::find_number_format(unsigned int num) { + if (num >= cell_styles.size()) + return ""; + + if (!cell_styles[num].has_value()) + return ""; + + unsigned int numfmtid = cell_styles[num].value(); + + for (const auto& nf : number_formats) { + if (nf.first == numfmtid) + return nf.second; + } + + for (const auto& bs : builtin_styles) { + if ((unsigned int)bs.first == numfmtid) + return bs.second; + } + + return ""; +} + +static constexpr bool __inline is_hex(char c) noexcept { + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); +} + +static constexpr uint8_t __inline hex_digit(char c) noexcept { + if (c >= '0' && c <= '9') + return (uint8_t)(c - '0'); + else if (c >= 'A' && c <= 'F') + return (uint8_t)(c - 'A' + 0xa); + else if (c >= 'a' && c <= 'f') + return (uint8_t)(c - 'a' + 0xa); + + return 0; +} + +static string decode_escape_sequences(string_view sv) { + bool has_underscore = false; + + if (sv.length() < 7) + return string(sv); + + for (auto c : sv) { + if (c == '_') { + has_underscore = true; + break; + } + } + + if (!has_underscore) + return string(sv); + + string s; + + s.reserve(sv.length()); + + for (unsigned int i = 0; i < sv.length(); i++) { + if (i + 6 < sv.length() && sv[i] == '_' && sv[i+1] == 'x' && is_hex(sv[i+2]) && is_hex(sv[i+3]) && is_hex(sv[i+4]) && is_hex(sv[i+5]) && sv[i+6] == '_') { + auto val = (uint16_t)((hex_digit(sv[i+2]) << 12) | (hex_digit(sv[i+3]) << 8) | (hex_digit(sv[i+4]) << 4) | hex_digit(sv[i+5])); + + if (val < 0x80) + s += (char)val; + else if (val < 0x800) { + char buf[3]; + + buf[0] = (char)(0xc0 | (val >> 6)); + buf[1] = (char)(0x80 | (val & 0x3f)); + buf[2] = 0; + + s += buf; + } else { + char buf[4]; + + buf[0] = (char)(0xe0 | (val >> 12)); + buf[1] = (char)(0x80 | ((val & 0xfc0) >> 6)); + buf[2] = (char)(0x80 | (val & 0x3f)); + buf[3] = 0; + + s += buf; + } + + i += 6; + } else + s += sv[i]; + } + + return s; +} + +static constexpr bool is_valid_date(uint16_t y, uint8_t m, uint8_t d) { + if (y == 0 || m == 0 || d == 0) + return false; + + if (d > 31 || m > 12) + return false; + + if (d == 31 && (m == 4 || m == 6 || m == 9 || m == 11)) + return false; + + if (d == 30 && m == 2) + return false; + + if (d == 29 && m == 2) { + if (y % 4) + return false; + + if (!(y % 100) && y % 400) + return false; + } + + return true; +} + +static constexpr bool __inline is_digit(char c) noexcept { + return c >= '0' && c <= '9'; +} + +static variant parse_iso8601(string_view t) { + if (t.length() >= 10 && is_digit(t[0]) && is_digit(t[1]) && is_digit(t[2]) && is_digit(t[3]) && + t[4] == '-' && is_digit(t[5]) && is_digit(t[6]) && t[7] == '-' && is_digit(t[8]) && + is_digit(t[9])) { + uint16_t y; + uint8_t mon, d; + + from_chars_constexpr(t.data(), t.data() + 4, y); + from_chars_constexpr(t.data() + 5, t.data() + 7, mon); + from_chars_constexpr(t.data() + 8, t.data() + 10, d); + + if (!is_valid_date(y, mon, d)) + Rcpp::stop("Invalid ISO 8601 date \"{}\".", t); + + if (t.length() >= 19 && t[10] == 'T' && is_digit(t[11]) && is_digit(t[12]) && t[13] == ':' && + is_digit(t[14]) && is_digit(t[15]) && t[16] == ':' && is_digit(t[17]) && is_digit(t[18])) { + uint8_t h, mins, s; + + from_chars_constexpr(t.data() + 11, t.data() + 13, h); + from_chars_constexpr(t.data() + 14, t.data() + 16, mins); + from_chars_constexpr(t.data() + 17, t.data() + 19, s); + + if (h >= 24 || mins >= 60 || s >= 60) + Rcpp::stop("Invalid ISO 8601 date \"{}\".", t); + + // FIXME - fractional seconds? + + return datetime(chrono::year{y}, chrono::month{mon}, chrono::day{d}, chrono::hours{h}, chrono::minutes{mins}, chrono::seconds{s}); + } + + return chrono::year_month_day{chrono::year{y}, chrono::month{mon}, chrono::day{d}}; + } else + Rcpp::stop("Failed to parse ISO 8601 date \"{}\".", t); +} + +void workbook_pimpl::load_sheet(string_view name, string_view data, bool visible) { + auto& s = *sheets.emplace(sheets.end(), *this, name, sheets.size() + 1, visible); + + xml_reader r(data); + unsigned int depth = 0; + bool in_sheet_data = false; + unsigned int last_index = 0, last_col = 0; + row* row = nullptr; + string s_val, t_val, v_val, is_val; + bool in_v = false, in_is = false; + + while (r.read()) { + unsigned int next_depth; + + if (r.node_type() == xml_node::element && !r.is_empty()) + next_depth = depth + 1; + else if (r.node_type() == xml_node::end_element) + next_depth = depth - 1; + else + next_depth = depth; + + if (r.node_type() == xml_node::element) { + if (depth == 0) { + if (r.local_name() != "worksheet" || (!r.namespace_uri_raw().cmp(NS_SPREADSHEET) && !r.namespace_uri_raw().cmp(NS_SPREADSHEET_STRICT))) { + Rcpp::stop("Root tag was {{{}}}{}, expected {{{}}}worksheet.", + r.namespace_uri_raw().decode(), r.local_name(), NS_SPREADSHEET); + } + } else if (depth == 1 && r.local_name() == "sheetData" && (r.namespace_uri_raw().cmp(NS_SPREADSHEET) || r.namespace_uri_raw().cmp(NS_SPREADSHEET_STRICT)) && !r.is_empty()) + in_sheet_data = true; + else if (in_sheet_data) { + if (r.local_name() == "row" && (r.namespace_uri_raw().cmp(NS_SPREADSHEET) || r.namespace_uri_raw().cmp(NS_SPREADSHEET_STRICT))) { + unsigned int row_index = 0; + auto rsv = r.get_attribute("r"); + + if (rsv) { + auto value = rsv.value().decode(); + auto [ptr, ec] = from_chars(value.data(), value.data() + value.length(), row_index); + + if (ptr != value.data() + value.length()) + Rcpp::stop("Invalid r attribute \"{}\" for row tag.", value); + } + + if (row_index == 0) + Rcpp::stop("No row index given."); + + if (row_index <= last_index) + Rcpp::stop("Rows out of order."); + + while (last_index + 1 < row_index) { + s.impl->rows.emplace(s.impl->rows.end(), *s.impl, s.impl->rows.size() + 1); + last_index++; + } + + s.impl->rows.emplace(s.impl->rows.end(), *s.impl, s.impl->rows.size() + 1); + + row = &s.impl->rows.back(); + + last_index = row_index; + last_col = 0; + } else if (row && r.local_name() == "c" && (r.namespace_uri_raw().cmp(NS_SPREADSHEET) || r.namespace_uri_raw().cmp(NS_SPREADSHEET_STRICT))) { + unsigned int row_num, col_num; + auto r_val = try_decode(r.get_attribute("r")); + + v_val = ""; + is_val = ""; + + t_val = try_decode(r.get_attribute("t")); + s_val = try_decode(r.get_attribute("s")); + + if (r_val.empty()) + Rcpp::stop("Cell had no r value."); + + if (!resolve_reference(r_val, row_num, col_num)) + Rcpp::stop("Malformed reference \"{}\".", r_val); + + if (row_num + 1 != last_index) + Rcpp::stop("Cell was in wrong row."); + + if (col_num < last_col) + Rcpp::stop("Cells out of order."); + + while (last_col < col_num) { + row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, nullptr); + last_col++; + } + + last_col = col_num + 1; + + if (r.is_empty()) + row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, nullptr); + } else if (row && r.local_name() == "v" && (r.namespace_uri_raw().cmp(NS_SPREADSHEET) || r.namespace_uri_raw().cmp(NS_SPREADSHEET_STRICT)) && !r.is_empty()) + in_v = true; + else if (row && r.local_name() == "is" && (r.namespace_uri_raw().cmp(NS_SPREADSHEET) || r.namespace_uri_raw().cmp(NS_SPREADSHEET_STRICT)) && !r.is_empty()) + in_is = true; + } + } else if (r.node_type() == xml_node::end_element) { + if (depth == 1 && r.local_name() == "sheetData" && (r.namespace_uri_raw().cmp(NS_SPREADSHEET) || r.namespace_uri_raw().cmp(NS_SPREADSHEET_STRICT))) + in_sheet_data = false; + else if (depth == 2 && r.local_name() == "row" && (r.namespace_uri_raw().cmp(NS_SPREADSHEET) || r.namespace_uri_raw().cmp(NS_SPREADSHEET_STRICT))) + row = nullptr; + else if (row && r.local_name() == "c" && (r.namespace_uri_raw().cmp(NS_SPREADSHEET) || r.namespace_uri_raw().cmp(NS_SPREADSHEET_STRICT))) { + cell* c; + string number_format; + + if (!s_val.empty()) + number_format = find_number_format(stoi(s_val)); + + if (t_val == "n" || t_val.empty()) { // number + bool dt = is_date(number_format); + bool tm = is_time(number_format); + + // FIXME - we can optimize is_date and is_time if one of the preset number formats + + if (dt && tm) { + auto d = stod(v_val); + auto n = (unsigned int)((d - (int)d) * 86400.0); + datetime dt(1970y, chrono::January, 1d, chrono::seconds{n}); + + dt.d = number_to_date((int)d, date1904); + + c = &*row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, dt); + } else if (dt) { + int num; + + auto [ptr, ec] = from_chars(v_val.data(), v_val.data() + v_val.length(), num); + + if (ec == errc() && ptr == v_val.data() + v_val.length()) { + auto ymd = number_to_date(num, date1904); + + c = &*row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, ymd); + } else + c = &*row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, nullptr); + } else if (tm) { + auto n = (unsigned int)(stod(v_val) * 86400.0); + + chrono::seconds t{n % 86400}; + + c = &*row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, t); + } else { + bool is_int = true; + + for (const auto c : v_val) { + if (c != '-' && (c < '0' || c > '9')) { + is_int = false; + break; + } + } + + if (is_int) { + int64_t val; + + auto [ptr, ec] = from_chars(v_val.data(), v_val.data() + v_val.length(), val); + + if (ptr != v_val.data() + v_val.length()) + Rcpp::stop("Could not convert {} to int64_t.", v_val); + + c = &*row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, val); + } else + c = &*row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, stod(v_val)); + } + } else if (t_val == "b") // boolean + c = &*row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, stoi(v_val) != 0); + else if (t_val == "s") { // shared string + shared_string ss; + + auto [ptr, ec] = from_chars(v_val.data(), v_val.data() + v_val.length(), ss.num); + + if (ptr != v_val.data() + v_val.length()) + Rcpp::stop("Invalid v attribute \"{}\" on shared-string cell.", v_val); + + c = &*row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, nullptr); + + // so we don't have to expose shared_string publicly + delete c->impl; + c->impl = new cell_pimpl(*row->impl, (unsigned int)row->impl->cells.size(), ss); + } else if (t_val == "e") // error + c = &*row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, nullptr); + else if (t_val == "str") { // string + c = &*row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, nullptr); + + if (!v_val.empty()) { + // so we don't have to expose shared_string publicly + c->impl->val = decode_escape_sequences(v_val); + } + } else if (t_val == "inlineStr") { // inline string + c = &*row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, nullptr); + + if (!is_val.empty()) { + // so we don't have to expose shared_string publicly + c->impl->val = decode_escape_sequences(is_val); + } + } else if (t_val == "d") { // datetime + auto dt = parse_iso8601(v_val); + + visit([&](auto&& arg) { + c = &*row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, arg); + }, dt); + } else + Rcpp::stop("Unhandled cell type value \"{}\".", t_val); + + if (!s_val.empty()) + c->impl->number_format = number_format; + } else if (in_v && r.local_name() == "v" && (r.namespace_uri_raw().cmp(NS_SPREADSHEET) || r.namespace_uri_raw().cmp(NS_SPREADSHEET_STRICT))) + in_v = false; + else if (in_is && r.local_name() == "is" && (r.namespace_uri_raw().cmp(NS_SPREADSHEET) || r.namespace_uri_raw().cmp(NS_SPREADSHEET_STRICT))) + in_is = false; + } else if (r.node_type() == xml_node::text) { + if (in_v) + v_val += r.value(); + else if (in_is) + is_val += r.value(); + } + + depth = next_depth; + } +} + +void workbook_pimpl::parse_workbook(string_view fn, string_view data, const unordered_map& files) { + xml_reader r(data); + unsigned int depth = 0; + + struct sheet_info { + sheet_info(string_view rid, string_view name, bool visible) : + rid(rid), name(name), visible(visible) { } + + string rid; + string name; + bool visible; + }; + + vector sheets_rels; + + while (r.read()) { + unsigned int next_depth; + + if (r.node_type() == xml_node::element && !r.is_empty()) + next_depth = depth + 1; + else if (r.node_type() == xml_node::end_element) + next_depth = depth - 1; + else + next_depth = depth; + + if (r.node_type() == xml_node::element) { + if (depth == 0) { + if (r.local_name() != "workbook" || (!r.namespace_uri_raw().cmp(NS_SPREADSHEET) && !r.namespace_uri_raw().cmp(NS_SPREADSHEET_STRICT))) { + Rcpp::stop("Root tag was {{{}}}{}, expected {{{}}}workbook.", + r.namespace_uri_raw().decode(), r.local_name(), NS_SPREADSHEET); + } + } else if (depth == 1) { + if (r.local_name() == "workbookPr" && (r.namespace_uri_raw().cmp(NS_SPREADSHEET) || r.namespace_uri_raw().cmp(NS_SPREADSHEET_STRICT))) { + auto date1904sv = r.get_attribute("date1904"); + + if (date1904sv) { + auto value = date1904sv.value().decode(); + date1904 = value == "true" || value == "1"; + } + } + } else if (depth == 2) { + if (r.local_name() == "sheet" && (r.namespace_uri_raw().cmp(NS_SPREADSHEET) || r.namespace_uri_raw().cmp(NS_SPREADSHEET_STRICT))) { + bool visible = true; + + auto sheet_name = try_decode(r.get_attribute("name")); + string rid; + + if (r.get_attribute("id", NS_RELATIONSHIPS).has_value()) + rid = r.get_attribute("id", NS_RELATIONSHIPS).value().decode(); + else if (r.get_attribute("id", NS_RELATIONSHIPS_STRICT).has_value()) + rid = r.get_attribute("id", NS_RELATIONSHIPS_STRICT).value().decode(); + + if (!rid.empty()) { + auto statesv = r.get_attribute("state"); + if (statesv) + visible = statesv.value().decode() != "hidden"; + + if (!sheet_name.empty()) + sheets_rels.emplace_back(rid, sheet_name, visible); + } + } + } + } + + depth = next_depth; + } + + // FIXME - preserve sheet order + + auto rels = read_relationships(fn, files); + + for (const auto& sr : sheets_rels) { + for (const auto& r : rels) { + if (r.first == sr.rid) { + auto name = filesystem::path(fn); + + // FIXME - can we resolve relative paths properly? + + name.remove_filename(); + name /= r.second; + + auto ns = name.string(); + + for (auto& c : ns) { + if (c == '\\') + c = '/'; + } + + if (files.count(ns) == 0) + Rcpp::stop("File {} not found.", ns); + + load_sheet(sr.name, files.at(ns).data, sr.visible); + break; + } + } + } +} + +void workbook_pimpl::load_shared_strings2(string_view sv) { + xml_reader r(sv); + unsigned int depth = 0; + bool in_si = false; + string si_val; + + while (r.read()) { + unsigned int next_depth; + + if (r.node_type() == xml_node::element && !r.is_empty()) + next_depth = depth + 1; + else if (r.node_type() == xml_node::end_element) + next_depth = depth - 1; + else + next_depth = depth; + + if (r.node_type() == xml_node::element) { + if (depth == 0) { + if (r.local_name() != "sst" || (!r.namespace_uri_raw().cmp(NS_SPREADSHEET) && !r.namespace_uri_raw().cmp(NS_SPREADSHEET_STRICT))) { + Rcpp::stop("Root tag was {{{}}}{}, expected {{{}}}sst.", + r.namespace_uri_raw().decode(), r.local_name(), NS_SPREADSHEET); + } + } else if (depth == 1) { + if (r.local_name() == "si" && (r.namespace_uri_raw().cmp(NS_SPREADSHEET) || r.namespace_uri_raw().cmp(NS_SPREADSHEET_STRICT))) { + in_si = true; + si_val = ""; + } + } + } else if (r.node_type() == xml_node::text) { + if (in_si) + si_val += decode_escape_sequences(r.value()); + } else if (r.node_type() == xml_node::end_element) { + if (r.local_name() == "si" && (r.namespace_uri_raw().cmp(NS_SPREADSHEET) || r.namespace_uri_raw().cmp(NS_SPREADSHEET_STRICT))) { + shared_strings2.emplace_back(si_val); + in_si = false; + } + } + + depth = next_depth; + } +} + +void workbook_pimpl::load_shared_strings(const unordered_map& files) { + for (const auto& f : files) { + if (f.second.content_type == "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml") { + load_shared_strings2(f.second.data); + return; + } else if (f.second.content_type == "application/vnd.ms-excel.sharedStrings") { + load_shared_strings_binary(span((uint8_t*)f.second.data.data(), f.second.data.size())); + return; + } + } +} + +void workbook_pimpl::load_styles2(string_view sv) { + xml_reader r(sv); + unsigned int depth = 0; + bool in_numfmts = false, in_cellxfs = false; + + while (r.read()) { + unsigned int next_depth; + + if (r.node_type() == xml_node::element && !r.is_empty()) + next_depth = depth + 1; + else if (r.node_type() == xml_node::end_element) + next_depth = depth - 1; + else + next_depth = depth; + + if (r.node_type() == xml_node::element) { + if (depth == 0) { + if (r.local_name() != "styleSheet" || (!r.namespace_uri_raw().cmp(NS_SPREADSHEET) && !r.namespace_uri_raw().cmp(NS_SPREADSHEET_STRICT))) { + Rcpp::stop("Root tag was {{{}}}{}, expected {{{}}}styleSheet.", + r.namespace_uri_raw().decode(), r.local_name(), NS_SPREADSHEET); + } + } else if (depth == 1) { + if (r.local_name() == "numFmts" && (r.namespace_uri_raw().cmp(NS_SPREADSHEET) || r.namespace_uri_raw().cmp(NS_SPREADSHEET_STRICT)) && !r.is_empty()) + in_numfmts = true; + else if (r.local_name() == "cellXfs" && (r.namespace_uri_raw().cmp(NS_SPREADSHEET) || r.namespace_uri_raw().cmp(NS_SPREADSHEET_STRICT)) && !r.is_empty()) + in_cellxfs = true; + } else if (depth == 2) { + if (in_numfmts && r.local_name() == "numFmt" && (r.namespace_uri_raw().cmp(NS_SPREADSHEET) || r.namespace_uri_raw().cmp(NS_SPREADSHEET_STRICT))) { + unsigned int id = 0; + string format_code; + + auto numfmtsv = r.get_attribute("numFmtId"); + if (numfmtsv) { + auto value = numfmtsv.value().decode(); + auto fcr = from_chars(value.data(), value.data() + value.length(), id); + + if (fcr.ec != errc() || fcr.ptr != value.data() + value.length()) + Rcpp::stop("Failed to parse numFmtId value {}.", value); + + if (id != 0) + number_formats[id] = try_decode(r.get_attribute("formatCode")); + } + } else if (in_cellxfs && r.local_name() == "xf" && (r.namespace_uri_raw().cmp(NS_SPREADSHEET) || r.namespace_uri_raw().cmp(NS_SPREADSHEET_STRICT))) { + optional numfmtid; + bool apply_number_format = true; + + auto numfmtsv = r.get_attribute("numFmtId"); + if (numfmtsv) { + unsigned int num; + + auto value = numfmtsv.value().decode(); + auto fcr = from_chars(value.data(), value.data() + value.length(), num); + + if (fcr.ec != errc() || fcr.ptr != value.data() + value.length()) + Rcpp::stop("Failed to parse numFmtId value {}.", value); + + numfmtid = num; + } + + auto applysv = r.get_attribute("applyNumberFormat"); + if (applysv) { + auto value = applysv.value().decode(); + apply_number_format = value == "true" || value == "1"; + } + + if (!apply_number_format) + numfmtid = nullopt; + + cell_styles.push_back(numfmtid); + } + } + } else if (r.node_type() == xml_node::end_element) { + if (in_numfmts && r.local_name() == "numFmts" && (r.namespace_uri_raw().cmp(NS_SPREADSHEET) || r.namespace_uri_raw().cmp(NS_SPREADSHEET_STRICT))) + in_numfmts = false; + else if (in_cellxfs && r.local_name() == "cellXfs" && (r.namespace_uri_raw().cmp(NS_SPREADSHEET) || r.namespace_uri_raw().cmp(NS_SPREADSHEET_STRICT))) + in_cellxfs = false; + } + + depth = next_depth; + } +} + +void workbook_pimpl::load_styles(const unordered_map& files) { + for (const auto& f : files) { + if (f.second.content_type == "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml") { + load_styles2(f.second.data); + return; + } else if (f.second.content_type == "application/vnd.ms-excel.styles") { + load_styles_binary(span((uint8_t*)f.second.data.data(), f.second.data.size())); + return; + } + } +} + +#ifdef _WIN32 +__inline string utf16_to_utf8(const u16string_view& s) { + string ret; + + if (s.empty()) + return ""; + + auto len = WideCharToMultiByte(CP_UTF8, 0, (const wchar_t*)s.data(), (int)s.length(), nullptr, 0, + nullptr, nullptr); + + if (len == 0) + Rcpp::stop("WideCharToMultiByte 1 failed."); + + ret.resize(len); + + len = WideCharToMultiByte(CP_UTF8, 0, (const wchar_t*)s.data(), (int)s.length(), ret.data(), len, + nullptr, nullptr); + + if (len == 0) + Rcpp::stop("WideCharToMultiByte 2 failed."); + + return ret; +} +#endif + +void workbook_pimpl::load_archive(struct archive* a) { + struct archive_entry* entry; + unordered_map files; + + while (archive_read_next_header(a, &entry) == ARCHIVE_OK) { + if (archive_entry_filetype(entry) == AE_IFREG && archive_entry_pathname(entry)) { + filesystem::path name = archive_entry_pathname(entry); + auto ext = name.extension().string(); + + for (auto& c : ext) { + if (c >= 'A' && c <= 'Z') + c = c - 'A' + 'a'; + } + + if (ext != ".xml" && ext != ".rels" && ext != ".bin") + continue; + + string buf; + string tmp(BLOCK_SIZE, 0); + + do { + auto read = archive_read_data(a, tmp.data(), BLOCK_SIZE); + + if (read == 0) + break; + + if (read < 0) + Rcpp::stop("archive_read_data returned {} for {} ({})", read, name.string(), archive_error_string(a)); + + buf += tmp.substr(0, read); + } while (true); + + files[name.string()].data = buf; + } + } + + if (files.count("[Content_Types].xml") == 0) + Rcpp::stop("[Content_Types].xml not found."); + + parse_content_types(files.at("[Content_Types].xml").data, files); + + load_shared_strings(files); + + load_styles(files); + + auto& wb = find_workbook(files); + + if (wb.second.content_type == "application/vnd.ms-excel.sheet.binary.macroEnabled.main") + parse_workbook_binary(wb.first, span((uint8_t*)wb.second.data.data(), wb.second.data.size()), files); + else + parse_workbook(wb.first, wb.second.data, files); +} + +workbook_pimpl::workbook_pimpl(const filesystem::path& fn, string_view password, string_view outfile) { +#ifdef _WIN32 + unique_handle hup{CreateFileW((LPCWSTR)fn.u16string().c_str(), FILE_READ_DATA | DELETE, FILE_SHARE_READ, nullptr, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, nullptr)}; + if (hup.get() == INVALID_HANDLE_VALUE) + throw last_error("CreateFile", GetLastError()); +#else + unique_handle hup{open(fn.string().c_str(), O_RDONLY)}; + + if (hup.get() == -1) + Rcpp::stop("open failed (errno = {})", errno); +#endif + + mmap m(hup.get()); + + auto mem = m.map(); + + load_from_memory(mem, password, outfile); +} + +workbook_pimpl::workbook_pimpl(span sv, string_view password, string_view outfile) { + load_from_memory(sv, password, outfile); +} + +void workbook_pimpl::load_from_memory(span mem, string_view password, string_view outfile) { + vector plaintext; + + if (mem.size() >= sizeof(uint64_t) && *(uint64_t*)mem.data() == CFBF_SIGNATURE) { + cfbf c(mem); + string enc_info, enc_package; + + // FIXME - handle old-style Excel files + + for (unsigned int num = 0; const auto& e : c.entries) { + if (num == 0) { // root + num++; + continue; + } + + if (e.name == "/EncryptionInfo" || e.name == "/EncryptedPackage") { + auto& str = e.name == "/EncryptionInfo" ? enc_info : enc_package; + + str.resize(e.get_size()); + + uint64_t off = 0; + auto buf = span((std::byte*)str.data(), str.size()); + + while (true) { + auto size = e.read(buf, off); + + if (size == 0) + break; + + off += size; + } + } + + num++; + } + + if (enc_info.empty()) + Rcpp::stop("EncryptionInfo not found."); + + auto u16password = utf8_to_utf16(password); + + c.parse_enc_info(span((uint8_t*)enc_info.data(), enc_info.size()), u16password); + plaintext = c.decrypt(span((uint8_t*)enc_package.data(), enc_package.size())); + + mem = plaintext; + } + + archive_read_t a{archive_read_new()}; + + archive_read_support_format_zip(a.get()); + + auto r = archive_read_open_memory(a.get(), mem.data(), mem.size()); + + if (r != ARCHIVE_OK) + Rcpp::stop("{}", archive_error_string(a.get())); + + // archive_read_t b{archive_read_new()}; + // archive_write_set_format_zip(b.get()); + // archive_write_open_filename(b.get(), "/tmp/test.xlsx"); + // // archive_write_data_block(b, mem.data(), mem.size(), 0); + // // archive_write_data(b, mem.data(), mem.size()); + // archive_write_open_memory(b, mem.data(), mem.size()); + // archive_write_close(b.get()); + // archive_write_free(b.get()); + + // FIXME I'm to stupid to write the file with libarchive + + if (outfile.compare("") != 0) { + std::ofstream xlsx((std::string)outfile, ios::out | ios::binary); + xlsx.write((char *) mem.data(), mem.size()); + xlsx.close(); + } + + load_archive(a.get()); +} + +workbook::workbook(const filesystem::path& fn, std::string_view password, std::string_view outfile) { + impl = new workbook_pimpl(fn, password, outfile); +} + +workbook::workbook(span sv, std::string_view password, std::string_view outfile) { + impl = new workbook_pimpl(sv, password, outfile); +} + +workbook::~workbook() { + delete impl; +} + +sheet::sheet(workbook_pimpl& wb, string_view name, unsigned int num, bool visible) { + impl = new sheet_pimpl(wb, name, num, visible); +} + +sheet::~sheet() { + delete impl; +} + +row::row(sheet_pimpl& s, unsigned int num) { + impl = new row_pimpl(s, num); +} + +row::~row() { + delete impl; +} + +cell& row::add_cell(int64_t val) { + return impl->add_cell(val); +} + +cell& row::add_cell(string_view val) { + return impl->add_cell(val); +} + +cell& row::add_cell(double val) { + return impl->add_cell(val); +} + +cell& row::add_cell(const chrono::year_month_day& val) { + return impl->add_cell(val); +} + +cell& row::add_cell(const chrono::seconds& val) { + return impl->add_cell(val); +} + +cell& row::add_cell(const datetime& val) { + return impl->add_cell(val); +} + +cell& row::add_cell(const chrono::system_clock::time_point& val) { + return impl->add_cell(val); +} + +cell& row::add_cell(bool val) { + return impl->add_cell(val); +} + +cell& row::add_cell(nullptr_t) { + return impl->add_cell(nullptr); +} + +const list& workbook::sheets() const { + return impl->sheets; +} + +string sheet::name() const { + return impl->name; +} + +bool sheet::visible() const { + return impl->visible; +} + +const list& sheet::rows() const { + return impl->rows; +} + +const list& row::cells() const { + return impl->cells; +} + +ostream& operator<<(ostream& os, const cell& c) { + visit([&](auto&& arg) { + using T = decay_t; + + if constexpr (is_same_v) + os << arg; + else if constexpr (is_same_v) + os << arg; + else if constexpr (is_same_v) + os << (arg ? "true" : "false"); + else if constexpr (is_same_v) + os << c.impl->parent.parent.parent.shared_strings2[arg.num]; + else if constexpr (is_same_v) + os << fmt::format("{:04}-{:02}-{:02}", (int)arg.year(), (unsigned int)arg.month(), (unsigned int)arg.day()); + else if constexpr (is_same_v) { + const auto& t = chrono::hh_mm_ss{arg}; + + os << fmt::format("{:02}:{:02}:{:02}", t.hours().count(), t.minutes().count(), t.seconds().count()); + } else if constexpr (is_same_v) { + const auto& t = chrono::hh_mm_ss{arg.t}; + + os << fmt::format("{:04}-{:02}-{:02} {:02}:{:02}:{:02}", + (int)arg.d.year(), (unsigned int)arg.d.month(), (unsigned int)arg.d.day(), + t.hours().count(), t.minutes().count(), t.seconds().count()); + } else if constexpr (is_same_v) { + // nop + } else if constexpr (is_same_v) + os << arg; + else + static_assert(always_false_v); + }, c.impl->val); + + return os; +} + +string cell::get_number_format() const { + return impl->number_format; +} + +cell_t cell::value() const { + decltype(value()) v; + + visit([&](auto&& arg) { + if constexpr (is_same_v, shared_string>) + v = impl->parent.parent.parent.shared_strings2[get(impl->val).num]; + else + v = arg; + }, impl->val); + + return v; +} + +#ifdef _WIN32 +void workbook_pimpl::rename(const filesystem::path& fn) const { + vector buf; + auto dest = fn.u16string(); + + buf.resize(offsetof(FILE_RENAME_INFO, FileName) + ((dest.length() + 1) * sizeof(char16_t))); + + auto fri = (FILE_RENAME_INFO*)buf.data(); + + fri->ReplaceIfExists = true; + fri->RootDirectory = nullptr; + fri->FileNameLength = (DWORD)(dest.length() * sizeof(char16_t)); + memcpy(fri->FileName, dest.data(), fri->FileNameLength); + fri->FileName[dest.length()] = 0; + + if (!SetFileInformationByHandle(h.get(), FileRenameInfo, fri, (DWORD)buf.size())) + throw last_error("SetFileInformationByHandle", GetLastError()); +} + +void workbook::rename(const filesystem::path& fn) const { + impl->rename(fn); +} +#endif + +} diff --git a/src/xlcpp/xlcpp.h b/src/xlcpp/xlcpp.h new file mode 100644 index 000000000..fac2fcaed --- /dev/null +++ b/src/xlcpp/xlcpp.h @@ -0,0 +1,155 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 + +#include + +#ifdef XLCPP_EXPORT +#define XLCPP __declspec(dllexport) +#elif !defined(XLCPP_STATIC) +#define XLCPP __declspec(dllimport) +#else +#define XLCPP +#endif + +#else + +#ifdef XLCPP_EXPORT +#define XLCPP __attribute__ ((visibility ("default"))) +#elif !defined(XLCPP_STATIC) +#define XLCPP __attribute__ ((dllimport)) +#else +#define XLCPP +#endif + +#endif + +namespace xlcpp { + +class workbook_pimpl; +class sheet; + +class XLCPP workbook { +public: + workbook(); + workbook(const std::filesystem::path& fn, std::string_view password = "", std::string_view outfile = ""); + workbook(std::span sv, std::string_view password = "", std::string_view outfile = ""); + ~workbook(); + sheet& add_sheet(std::string_view name, bool visible = true); + void save(const std::filesystem::path& fn) const; + std::string data() const; + const std::list& sheets() const; +#ifdef _WIN32 + void rename(const std::filesystem::path& fn) const; +#endif + + workbook_pimpl* impl; +}; + +class sheet_pimpl; +class row; + +class XLCPP sheet { +public: + sheet(workbook_pimpl& wb, std::string_view name, unsigned int num, bool visible = true); + ~sheet(); + row& add_row(); + std::string name() const; + bool visible() const; + const std::list& rows() const; + + sheet_pimpl* impl; +}; + +class XLCPP datetime { +public: + constexpr datetime(std::chrono::year year, std::chrono::month month, std::chrono::day day, std::chrono::hours hour, std::chrono::minutes minute, std::chrono::seconds second) : + d(year, month, day), t(hour + minute + second) { } + constexpr datetime(std::chrono::year year, std::chrono::month month, std::chrono::day day, std::chrono::seconds t) : + d(year, month, day), t(t) { } + + template + constexpr datetime(const std::chrono::time_point& chr) : + d(std::chrono::floor(chr)), + t(std::chrono::floor(chr - std::chrono::floor(chr)).count()) { + } + + std::chrono::year_month_day d; + std::chrono::seconds t; +}; + +class row_pimpl; +class cell_pimpl; + +using cell_t = std::variant; + +class XLCPP cell { +public: + cell(row_pimpl& r, unsigned int num, std::nullptr_t); + cell(row_pimpl& r, unsigned int num, int64_t val); + cell(row_pimpl& r, unsigned int num, std::string_view val); + cell(row_pimpl& r, unsigned int num, double val); + cell(row_pimpl& r, unsigned int num, const std::chrono::year_month_day& val); + cell(row_pimpl& r, unsigned int num, const std::chrono::seconds& val); + cell(row_pimpl& r, unsigned int num, const datetime& val); + cell(row_pimpl& r, unsigned int num, const std::chrono::system_clock::time_point& val); + cell(row_pimpl& r, unsigned int num, bool val); + + template + cell(row_pimpl& r, unsigned int num, T* t) = delete; + + void set_number_format(std::string_view fmt); + void set_font(std::string_view name, unsigned int size, bool bold = false); + std::string get_number_format() const; + cell_t value() const; + + cell_pimpl* impl; +}; + +XLCPP std::ostream& operator<<(std::ostream& os, const cell& c); + +class XLCPP row { +public: + row(sheet_pimpl& s, unsigned int num); + ~row(); + + cell& add_cell(int64_t val); + cell& add_cell(std::string_view val); + cell& add_cell(double val); + cell& add_cell(const std::chrono::year_month_day& val); + cell& add_cell(const std::chrono::seconds& val); + cell& add_cell(const datetime& val); + cell& add_cell(const std::chrono::system_clock::time_point& val); + cell& add_cell(bool val); + cell& add_cell(std::nullptr_t); + + cell& add_cell(const char* val) { + return add_cell(std::string_view(val)); + } + + cell& add_cell(char* val) { + return add_cell(std::string_view(val)); + } + + template + requires std::is_integral_v + cell& add_cell(T val) { + return add_cell(static_cast(val)); + } + + template + cell& add_cell(T* val) = delete; + + const std::list& cells() const; + + row_pimpl* impl; +}; + +}; diff --git a/src/xlcpp/xlsb.cpp b/src/xlcpp/xlsb.cpp new file mode 100644 index 000000000..8bc1e9ea6 --- /dev/null +++ b/src/xlcpp/xlsb.cpp @@ -0,0 +1,565 @@ +#include "openxlsx2.h" + +#include "xlcpp.h" +#include "xlcpp-pimpl.h" +#include "cfbf.h" +#include "utf16.h" +#include "xlsb.h" + +using namespace std; + +namespace xlcpp { + +static void xlsb_walk(span data, const function)>& func) { + while (!data.empty()) { + uint16_t type; + uint32_t length; + + type = data[0]; + data = data.subspan(1); + + if (type & 0x80) { + type = (uint16_t)((type & 0x7f) | ((uint16_t)data[0] << 7)); + data = data.subspan(1); + } + + length = data[0]; + data = data.subspan(1); + + if (length & 0x80) { + length = (length & 0x7f) | ((uint32_t)data[0] << 7); + data = data.subspan(1); + } + + if (length & 0x4000) { + length = (length & 0x3fff) | ((uint32_t)data[0] << 14); + data = data.subspan(1); + } + + if (length & 0x200000) { + length = (length & 0x1fffff) | ((uint32_t)(data[0] & 0x7f) << 21); + data = data.subspan(1); + } + + func((enum xlsb_type)type, data.subspan(0, length)); + + data = data.subspan(length); + } +} + +void workbook_pimpl::load_sheet_binary(string_view name, span data, bool visible) { + auto& s = *sheets.emplace(sheets.end(), *this, name, sheets.size() + 1, visible); + unsigned int last_index = 0, last_col = 0; + row* row = nullptr; + bool in_sheet_data = false; + + xlsb_walk(data, [&](enum xlsb_type type, span d) { + switch (type) { + case xlsb_type::BrtBeginSheetData: + in_sheet_data = true; + break; + + case xlsb_type::BrtEndSheetData: + in_sheet_data = false; + break; + + case xlsb_type::BrtRowHdr: { + if (!in_sheet_data) + break; + + if (d.size() < sizeof(brt_row_hdr)) + Rcpp::stop("Malformed BrtRowHdr record."); + + const auto& h = *(brt_row_hdr*)d.data(); + + if (h.rw < last_index) + Rcpp::stop("Rows out of order."); + + while (last_index < h.rw) { + s.impl->rows.emplace(s.impl->rows.end(), *s.impl, s.impl->rows.size() + 1); + last_index++; + } + + s.impl->rows.emplace(s.impl->rows.end(), *s.impl, s.impl->rows.size() + 1); + + row = &s.impl->rows.back(); + + last_index = h.rw + 1; + last_col = 0; + + break; + } + + case xlsb_type::BrtCellBlank: + case xlsb_type::BrtCellError: + case xlsb_type::BrtFmlaError: { + if (!in_sheet_data) + break; + + if (d.size() < sizeof(xlsb_cell)) + Rcpp::stop("Malformed cell record."); + + const auto& h = *(xlsb_cell*)d.data(); + + if (h.column < last_col) + Rcpp::stop("Cells out of order."); + + while (last_col < h.column) { + row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, nullptr); + last_col++; + } + + last_col = h.column + 1; + + auto number_format = find_number_format(h.iStyleRef); + + row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, nullptr); + break; + } + + case xlsb_type::BrtCellRk: { + if (!in_sheet_data) + break; + + if (d.size() < sizeof(brt_cell_rk)) + Rcpp::stop("Malformed BrtCellRk record."); + + const auto& h = *(brt_cell_rk*)d.data(); + + if (h.cell.column < last_col) + Rcpp::stop("Cells out of order."); + + while (last_col < h.cell.column) { + row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, nullptr); + last_col++; + } + + last_col = h.cell.column + 1; + + auto number_format = find_number_format(h.cell.iStyleRef); + + double d; + + if (h.value.fInt) { + auto num = h.value.num; + auto val = *reinterpret_cast(&num); + + d = val; + } else { + auto num = ((uint64_t)h.value.num) << 34; + d = *reinterpret_cast(&num); + } + + if (h.value.fx100) + d /= 100.0; + + bool dt = is_date(number_format); + bool tm = is_time(number_format); + cell* c; + + // FIXME - we can optimize is_date and is_time if one of the preset number formats + + if (dt && tm) { + auto n = (unsigned int)((d - (int)d) * 86400.0); + datetime dt(1970y, chrono::January, 1d, chrono::seconds{n}); + + dt.d = number_to_date((int)d, date1904); + + c = &*row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, dt); + } else if (dt) { + auto ymd = number_to_date((unsigned int)d, date1904); + + c = &*row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, ymd); + } else if (tm) { + auto n = (unsigned int)(d * 86400.0); + chrono::seconds t{n % 86400}; + + c = &*row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, t); + } else + c = &*row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, d); + + c->impl->number_format = number_format; + + break; + } + + case xlsb_type::BrtCellBool: + case xlsb_type::BrtFmlaBool: { + if (!in_sheet_data) + break; + + if (d.size() < sizeof(brt_cell_bool)) + Rcpp::stop("Malformed BrtCellBool record."); + + const auto& h = *(brt_cell_bool*)d.data(); + + if (h.cell.column < last_col) + Rcpp::stop("Cells out of order."); + + while (last_col < h.cell.column) { + row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, nullptr); + last_col++; + } + + last_col = h.cell.column + 1; + + auto number_format = find_number_format(h.cell.iStyleRef); + + auto c = &*row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, h.fBool != 0); + + c->impl->number_format = number_format; + + break; + } + + case xlsb_type::BrtCellReal: + case xlsb_type::BrtFmlaNum: { + if (!in_sheet_data) + break; + + if (d.size() < sizeof(brt_cell_real)) + Rcpp::stop("Malformed BrtCellReal record."); + + const auto& h = *(brt_cell_real*)d.data(); + + if (h.cell.column < last_col) + Rcpp::stop("Cells out of order."); + + while (last_col < h.cell.column) { + row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, nullptr); + last_col++; + } + + last_col = h.cell.column + 1; + + auto number_format = find_number_format(h.cell.iStyleRef); + + bool dt = is_date(number_format); + bool tm = is_time(number_format); + cell* c; + + if (dt && tm) { + auto n = (unsigned int)((h.xnum - (int)h.xnum) * 86400.0); + datetime dt(1970y, chrono::January, 1d, chrono::seconds{n}); + + dt.d = number_to_date((int)h.xnum, date1904); + + c = &*row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, dt); + } else if (dt) { + auto ymd = number_to_date((unsigned int)h.xnum, date1904); + + c = &*row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, ymd); + } else if (tm) { + auto n = (unsigned int)(h.xnum * 86400.0); + chrono::seconds t{n % 86400}; + + c = &*row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, t); + } else + c = &*row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, h.xnum); + + c->impl->number_format = number_format; + + break; + } + + case xlsb_type::BrtCellSt: + case xlsb_type::BrtFmlaString: { + if (!in_sheet_data) + break; + + if (d.size() < offsetof(brt_cell_st, str)) + Rcpp::stop("Malformed BrtCellSt record."); + + const auto& h = *(brt_cell_st*)d.data(); + + if (d.size() < offsetof(brt_cell_st, str) + (h.len * sizeof(char16_t))) + Rcpp::stop("Malformed BrtCellSt record."); + + if (h.cell.column < last_col) + Rcpp::stop("Cells out of order."); + + while (last_col < h.cell.column) { + row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, nullptr); + last_col++; + } + + last_col = h.cell.column + 1; + + auto number_format = find_number_format(h.cell.iStyleRef); + + auto u16sv = u16string_view(h.str, h.len); + + auto c = &*row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, nullptr); + + // so we don't have to expose shared_string publicly + c->impl->val = utf16_to_utf8(u16sv); + c->impl->number_format = number_format; + + break; + } + + case xlsb_type::BrtCellIsst: { + if (!in_sheet_data) + break; + + if (d.size() < sizeof(brt_cell_isst)) + Rcpp::stop("Malformed BrtCellIsst record."); + + const auto& h = *(brt_cell_isst*)d.data(); + + if (h.cell.column < last_col) + Rcpp::stop("Cells out of order."); + + while (last_col < h.cell.column) { + row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, nullptr); + last_col++; + } + + last_col = h.cell.column + 1; + + auto number_format = find_number_format(h.cell.iStyleRef); + + shared_string ss; + ss.num = h.isst; + + auto c = &*row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, nullptr); + + // so we don't have to expose shared_string publicly + delete c->impl; + c->impl = new cell_pimpl(*row->impl, (unsigned int)row->impl->cells.size(), ss); + + c->impl->number_format = number_format; + + break; + } + + case xlsb_type::BrtCellRString: { + if (!in_sheet_data) + break; + + if (d.size() < offsetof(brt_cell_rstring, value.str)) + Rcpp::stop("Malformed BrtCellRString record."); + + const auto& h = *(brt_cell_rstring*)d.data(); + + if (d.size() < offsetof(brt_cell_rstring, value.str) + (h.value.len * sizeof(char16_t))) + Rcpp::stop("Malformed BrtCellRString record."); + + if (h.cell.column < last_col) + Rcpp::stop("Cells out of order."); + + while (last_col < h.cell.column) { + row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, nullptr); + last_col++; + } + + last_col = h.cell.column + 1; + + auto number_format = find_number_format(h.cell.iStyleRef); + + auto u16sv = u16string_view(h.value.str, h.value.len); + + auto c = &*row->impl->cells.emplace(row->impl->cells.end(), *row->impl, row->impl->cells.size() + 1, nullptr); + + // so we don't have to expose shared_string publicly + c->impl->val = utf16_to_utf8(u16sv); + + c->impl->number_format = number_format; + + break; + } + + default: + break; + } + }); +} + +void workbook_pimpl::parse_workbook_binary(string_view fn, span data, const unordered_map& files) { + struct sheet_info { + sheet_info(string_view rid, string_view name, bool visible) : + rid(rid), name(name), visible(visible) { } + + string rid; + string name; + bool visible; + }; + + vector sheets_rels; + + xlsb_walk(data, [&](enum xlsb_type type, span d) { + switch (type) { + case xlsb_type::BrtBundleSh: { + if (d.size() < sizeof(brt_bundle_sh)) + Rcpp::stop("Malformed BrtBundleSh entry."); + + auto& h = *(brt_bundle_sh*)d.data(); + + d = d.subspan(sizeof(brt_bundle_sh)); + + if (d.size() < sizeof(uint32_t)) + Rcpp::stop("Malformed BrtBundleSh entry."); + + auto relid_size = *(uint32_t*)d.data(); + + d = d.subspan(sizeof(uint32_t)); + + if (relid_size == 0xffffffff) + relid_size = 0; + + if (d.size() < relid_size * sizeof(char16_t)) + Rcpp::stop("Malformed BrtBundleSh entry."); + + auto strRelID = u16string_view((char16_t*)d.data(), relid_size); + + d = d.subspan(relid_size * sizeof(char16_t)); + + if (d.size() < sizeof(uint32_t)) + Rcpp::stop("Malformed BrtBundleSh entry."); + + auto name_size = *(uint32_t*)d.data(); + + d = d.subspan(sizeof(uint32_t)); + + if (d.size() < name_size * sizeof(char16_t)) + Rcpp::stop("Malformed BrtBundleSh entry."); + + auto strName = u16string_view((char16_t*)d.data(), name_size); + + sheets_rels.emplace_back(utf16_to_utf8(strRelID), utf16_to_utf8(strName), h.hsState == 0); + + break; + } + + case xlsb_type::BrtWbProp: { + if (d.size() < sizeof(brt_wb_prop)) + Rcpp::stop("Malformed BrtWbProp entry."); + + const auto& h = *(brt_wb_prop*)d.data(); + + date1904 = h.f1904; + + break; + } + + default: + break; + } + }); + + // FIXME - preserve sheet order + + auto rels = read_relationships(fn, files); + + for (const auto& sr : sheets_rels) { + for (const auto& r : rels) { + if (r.first == sr.rid) { + auto name = filesystem::path(fn); + + // FIXME - can we resolve relative paths properly? + + name.remove_filename(); + name /= r.second; + + auto ns = name.string(); + + for (auto& c : ns) { + if (c == '\\') + c = '/'; + } + + if (files.count(ns) == 0) + Rcpp::stop("File {} not found.", ns); + + auto& d = files.at(ns).data; + + load_sheet_binary(sr.name, span((uint8_t*)d.data(), d.size()), sr.visible); + break; + } + } + } +} + +void workbook_pimpl::load_shared_strings_binary(span data) { + xlsb_walk(data, [&](enum xlsb_type type, span d) { + if (type == xlsb_type::BrtSSTItem) { + if (d.size() < offsetof(brt_sst_item, richStr.str)) + Rcpp::stop("Malformed BrtSSTItem record."); + + const auto& h = *(brt_sst_item*)d.data(); + + if (d.size() < offsetof(brt_sst_item, richStr.str) + (sizeof(char16_t) * h.richStr.len)) + Rcpp::stop("Malformed BrtSSTItem record."); + + auto u16sv = u16string_view(h.richStr.str, h.richStr.len); + + shared_strings2.emplace_back(utf16_to_utf8(u16sv)); + } + }); +} + +void workbook_pimpl::load_styles_binary(span data) { + bool in_numfmts = false, in_cellxfs = false; + + xlsb_walk(data, [&](enum xlsb_type type, span d) { + switch (type) { + case xlsb_type::BrtBeginCellXFs: + in_cellxfs = true; + break; + + case xlsb_type::BrtEndCellXFs: + in_cellxfs = false; + break; + + case xlsb_type::BrtBeginFmts: + in_numfmts = true; + break; + + case xlsb_type::BrtEndFmts: + in_numfmts = false; + break; + + case xlsb_type::BrtXF: + if (in_cellxfs) { + optional numfmtid; + + if (d.size() < sizeof(brt_xf)) + Rcpp::stop("Malformed BrtXF record."); + + const auto& h = *(brt_xf*)d.data(); + + numfmtid = h.iFmt; + + if (h.xfGrbitAtr & 1) + numfmtid = nullopt; + + cell_styles.push_back(numfmtid); + } + break; + + case xlsb_type::BrtFmt: + if (in_numfmts) { + if (d.size() < offsetof(brt_fmt, stFmtCode)) + Rcpp::stop("Malformed BrtFmt record."); + + const auto& h = *(brt_fmt*)d.data(); + + if (d.size() < offsetof(brt_fmt, stFmtCode) + (h.stFmtCode_len * sizeof(char16_t))) + Rcpp::stop("Malformed BrtFmt record."); + + auto u16sv = u16string_view(h.stFmtCode, h.stFmtCode_len); + auto s = utf16_to_utf8(u16sv); + + // FIXME - removing backslashes? + + number_formats[h.ifmt] = s; + } + break; + + default: + break; + } + }); +} + +}; diff --git a/src/xlcpp/xlsb.h b/src/xlcpp/xlsb.h new file mode 100644 index 000000000..af02fd90d --- /dev/null +++ b/src/xlcpp/xlsb.h @@ -0,0 +1,2707 @@ +#pragma once + +enum class xlsb_type { + BrtRowHdr = 0, + BrtCellBlank = 1, + BrtCellRk = 2, + BrtCellError = 3, + BrtCellBool = 4, + BrtCellReal = 5, + BrtCellSt = 6, + BrtCellIsst = 7, + BrtFmlaString = 8, + BrtFmlaNum = 9, + BrtFmlaBool = 10, + BrtFmlaError = 11, + BrtSSTItem = 19, + BrtPCDIMissing = 20, + BrtPCDINumber = 21, + BrtPCDIBoolean = 22, + BrtPCDIError = 23, + BrtPCDIString = 24, + BrtPCDIDatetime = 25, + BrtPCDIIndex = 26, + BrtPCDIAMissing = 27, + BrtPCDIANumber = 28, + BrtPCDIABoolean = 29, + BrtPCDIAError = 30, + BrtPCDIAString = 31, + BrtPCDIADatetime = 32, + BrtPCRRecord = 33, + BrtPCRRecordDt = 34, + BrtFRTBegin = 35, + BrtFRTEnd = 36, + BrtACBegin = 37, + BrtACEnd = 38, + BrtName = 39, + BrtIndexRowBlock = 40, + BrtIndexBlock = 42, + BrtFont = 43, + BrtFmt = 44, + BrtFill = 45, + BrtBorder = 46, + BrtXF = 47, + BrtStyle = 48, + BrtCellMeta = 49, + BrtValueMeta = 50, + BrtMdb = 51, + BrtBeginFmd = 52, + BrtEndFmd = 53, + BrtBeginMdx = 54, + BrtEndMdx = 55, + BrtBeginMdxTuple = 56, + BrtEndMdxTuple = 57, + BrtMdxMbrIstr = 58, + BrtStr = 59, + BrtColInfo = 60, + BrtCellRString = 62, + BrtDVal = 64, + BrtSxvcellNum = 65, + BrtSxvcellStr = 66, + BrtSxvcellBool = 67, + BrtSxvcellErr = 68, + BrtSxvcellDate = 69, + BrtSxvcellNil = 70, + BrtFileVersion = 128, + BrtBeginSheet = 129, + BrtEndSheet = 130, + BrtBeginBook = 131, + BrtEndBook = 132, + BrtBeginWsViews = 133, + BrtEndWsViews = 134, + BrtBeginBookViews = 135, + BrtEndBookViews = 136, + BrtBeginWsView = 137, + BrtEndWsView = 138, + BrtBeginCsViews = 139, + BrtEndCsViews = 140, + BrtBeginCsView = 141, + BrtEndCsView = 142, + BrtBeginBundleShs = 143, + BrtEndBundleShs = 144, + BrtBeginSheetData = 145, + BrtEndSheetData = 146, + BrtWsProp = 147, + BrtWsDim = 148, + BrtPane = 151, + BrtSel = 152, + BrtWbProp = 153, + BrtWbFactoid = 154, + BrtFileRecover = 155, + BrtBundleSh = 156, + BrtCalcProp = 157, + BrtBookView = 158, + BrtBeginSst = 159, + BrtEndSst = 160, + BrtBeginAFilter = 161, + BrtEndAFilter = 162, + BrtBeginFilterColumn = 163, + BrtEndFilterColumn = 164, + BrtBeginFilters = 165, + BrtEndFilters = 166, + BrtFilter = 167, + BrtColorFilter = 168, + BrtIconFilter = 169, + BrtTop10Filter = 170, + BrtDynamicFilter = 171, + BrtBeginCustomFilters = 172, + BrtEndCustomFilters = 173, + BrtCustomFilter = 174, + BrtAFilterDateGroupItem = 175, + BrtMergeCell = 176, + BrtBeginMergeCells = 177, + BrtEndMergeCells = 178, + BrtBeginPivotCacheDef = 179, + BrtEndPivotCacheDef = 180, + BrtBeginPCDFields = 181, + BrtEndPCDFields = 182, + BrtBeginPCDField = 183, + BrtEndPCDField = 184, + BrtBeginPCDSource = 185, + BrtEndPCDSource = 186, + BrtBeginPCDSRange = 187, + BrtEndPCDSRange = 188, + BrtBeginPCDFAtbl = 189, + BrtEndPCDFAtbl = 190, + BrtBeginPCDIRun = 191, + BrtEndPCDIRun = 192, + BrtBeginPivotCacheRecords = 193, + BrtEndPivotCacheRecords = 194, + BrtBeginPCDHierarchies = 195, + BrtEndPCDHierarchies = 196, + BrtBeginPCDHierarchy = 197, + BrtEndPCDHierarchy = 198, + BrtBeginPCDHFieldsUsage = 199, + BrtEndPCDHFieldsUsage = 200, + BrtBeginExtConnection = 201, + BrtEndExtConnection = 202, + BrtBeginECDbProps = 203, + BrtEndECDbProps = 204, + BrtBeginECOlapProps = 205, + BrtEndECOlapProps = 206, + BrtBeginPCDSConsol = 207, + BrtEndPCDSConsol = 208, + BrtBeginPCDSCPages = 209, + BrtEndPCDSCPages = 210, + BrtBeginPCDSCPage = 211, + BrtEndPCDSCPage = 212, + BrtBeginPCDSCPItem = 213, + BrtEndPCDSCPItem = 214, + BrtBeginPCDSCSets = 215, + BrtEndPCDSCSets = 216, + BrtBeginPCDSCSet = 217, + BrtEndPCDSCSet = 218, + BrtBeginPCDFGroup = 219, + BrtEndPCDFGroup = 220, + BrtBeginPCDFGItems = 221, + BrtEndPCDFGItems = 222, + BrtBeginPCDFGRange = 223, + BrtEndPCDFGRange = 224, + BrtBeginPCDFGDiscrete = 225, + BrtEndPCDFGDiscrete = 226, + BrtBeginPCDSDTupleCache = 227, + BrtEndPCDSDTupleCache = 228, + BrtBeginPCDSDTCEntries = 229, + BrtEndPCDSDTCEntries = 230, + BrtBeginPCDSDTCEMembers = 231, + BrtEndPCDSDTCEMembers = 232, + BrtBeginPCDSDTCEMember = 233, + BrtEndPCDSDTCEMember = 234, + BrtBeginPCDSDTCQueries = 235, + BrtEndPCDSDTCQueries = 236, + BrtBeginPCDSDTCQuery = 237, + BrtEndPCDSDTCQuery = 238, + BrtBeginPCDSDTCSets = 239, + BrtEndPCDSDTCSets = 240, + BrtBeginPCDSDTCSet = 241, + BrtEndPCDSDTCSet = 242, + BrtBeginPCDCalcItems = 243, + BrtEndPCDCalcItems = 244, + BrtBeginPCDCalcItem = 245, + BrtEndPCDCalcItem = 246, + BrtBeginPRule = 247, + BrtEndPRule = 248, + BrtBeginPRFilters = 249, + BrtEndPRFilters = 250, + BrtBeginPRFilter = 251, + BrtEndPRFilter = 252, + BrtBeginPNames = 253, + BrtEndPNames = 254, + BrtBeginPName = 255, + BrtEndPName = 256, + BrtBeginPNPairs = 257, + BrtEndPNPairs = 258, + BrtBeginPNPair = 259, + BrtEndPNPair = 260, + BrtBeginECWebProps = 261, + BrtEndECWebProps = 262, + BrtBeginEcWpTables = 263, + BrtEndECWPTables = 264, + BrtBeginECParams = 265, + BrtEndECParams = 266, + BrtBeginECParam = 267, + BrtEndECParam = 268, + BrtBeginPCDKPIs = 269, + BrtEndPCDKPIs = 270, + BrtBeginPCDKPI = 271, + BrtEndPCDKPI = 272, + BrtBeginDims = 273, + BrtEndDims = 274, + BrtBeginDim = 275, + BrtEndDim = 276, + BrtIndexPartEnd = 277, + BrtBeginStyleSheet = 278, + BrtEndStyleSheet = 279, + BrtBeginSXView = 280, + BrtEndSXVI = 281, + BrtBeginSXVI = 282, + BrtBeginSXVIs = 283, + BrtEndSXVIs = 284, + BrtBeginSXVD = 285, + BrtEndSXVD = 286, + BrtBeginSXVDs = 287, + BrtEndSXVDs = 288, + BrtBeginSXPI = 289, + BrtEndSXPI = 290, + BrtBeginSXPIs = 291, + BrtEndSXPIs = 292, + BrtBeginSXDI = 293, + BrtEndSXDI = 294, + BrtBeginSXDIs = 295, + BrtEndSXDIs = 296, + BrtBeginSXLI = 297, + BrtEndSXLI = 298, + BrtBeginSXLIRws = 299, + BrtEndSXLIRws = 300, + BrtBeginSXLICols = 301, + BrtEndSXLICols = 302, + BrtBeginSXFormat = 303, + BrtEndSXFormat = 304, + BrtBeginSXFormats = 305, + BrtEndSxFormats = 306, + BrtBeginSxSelect = 307, + BrtEndSxSelect = 308, + BrtBeginISXVDRws = 309, + BrtEndISXVDRws = 310, + BrtBeginISXVDCols = 311, + BrtEndISXVDCols = 312, + BrtEndSXLocation = 313, + BrtBeginSXLocation = 314, + BrtEndSXView = 315, + BrtBeginSXTHs = 316, + BrtEndSXTHs = 317, + BrtBeginSXTH = 318, + BrtEndSXTH = 319, + BrtBeginISXTHRws = 320, + BrtEndISXTHRws = 321, + BrtBeginISXTHCols = 322, + BrtEndISXTHCols = 323, + BrtBeginSXTDMPS = 324, + BrtEndSXTDMPs = 325, + BrtBeginSXTDMP = 326, + BrtEndSXTDMP = 327, + BrtBeginSXTHItems = 328, + BrtEndSXTHItems = 329, + BrtBeginSXTHItem = 330, + BrtEndSXTHItem = 331, + BrtBeginMetadata = 332, + BrtEndMetadata = 333, + BrtBeginEsmdtinfo = 334, + BrtMdtinfo = 335, + BrtEndEsmdtinfo = 336, + BrtBeginEsmdb = 337, + BrtEndEsmdb = 338, + BrtBeginEsfmd = 339, + BrtEndEsfmd = 340, + BrtBeginSingleCells = 341, + BrtEndSingleCells = 342, + BrtBeginList = 343, + BrtEndList = 344, + BrtBeginListCols = 345, + BrtEndListCols = 346, + BrtBeginListCol = 347, + BrtEndListCol = 348, + BrtBeginListXmlCPr = 349, + BrtEndListXmlCPr = 350, + BrtListCCFmla = 351, + BrtListTrFmla = 352, + BrtBeginExternals = 353, + BrtEndExternals = 354, + BrtSupBookSrc = 355, + BrtSupSelf = 357, + BrtSupSame = 358, + BrtSupTabs = 359, + BrtBeginSupBook = 360, + BrtPlaceholderName = 361, + BrtExternSheet = 362, + BrtExternTableStart = 363, + BrtExternTableEnd = 364, + BrtExternRowHdr = 366, + BrtExternCellBlank = 367, + BrtExternCellReal = 368, + BrtExternCellBool = 369, + BrtExternCellError = 370, + BrtExternCellString = 371, + BrtBeginEsmdx = 372, + BrtEndEsmdx = 373, + BrtBeginMdxSet = 374, + BrtEndMdxSet = 375, + BrtBeginMdxMbrProp = 376, + BrtEndMdxMbrProp = 377, + BrtBeginMdxKPI = 378, + BrtEndMdxKPI = 379, + BrtBeginEsstr = 380, + BrtEndEsstr = 381, + BrtBeginPRFItem = 382, + BrtEndPRFItem = 383, + BrtBeginPivotCacheIDs = 384, + BrtEndPivotCacheIDs = 385, + BrtBeginPivotCacheID = 386, + BrtEndPivotCacheID = 387, + BrtBeginISXVIs = 388, + BrtEndISXVIs = 389, + BrtBeginColInfos = 390, + BrtEndColInfos = 391, + BrtBeginRwBrk = 392, + BrtEndRwBrk = 393, + BrtBeginColBrk = 394, + BrtEndColBrk = 395, + BrtBrk = 396, + BrtUserBookView = 397, + BrtInfo = 398, + BrtCUsr = 399, + BrtUsr = 400, + BrtBeginUsers = 401, + BrtEOF = 403, + BrtUCR = 404, + BrtRRInsDel = 405, + BrtRREndInsDel = 406, + BrtRRMove = 407, + BrtRREndMove = 408, + BrtRRChgCell = 409, + BrtRREndChgCell = 410, + BrtRRHeader = 411, + BrtRRUserView = 412, + BrtRRRenSheet = 413, + BrtRRInsertSh = 414, + BrtRRDefName = 415, + BrtRRNote = 416, + BrtRRConflict = 417, + BrtRRTQSIF = 418, + BrtRRFormat = 419, + BrtRREndFormat = 420, + BrtRRAutoFmt = 421, + BrtBeginUserShViews = 422, + BrtBeginUserShView = 423, + BrtEndUserShView = 424, + BrtEndUserShViews = 425, + BrtArrFmla = 426, + BrtShrFmla = 427, + BrtTable = 428, + BrtBeginExtConnections = 429, + BrtEndExtConnections = 430, + BrtBeginPCDCalcMems = 431, + BrtEndPCDCalcMems = 432, + BrtBeginPCDCalcMem = 433, + BrtEndPCDCalcMem = 434, + BrtBeginPCDHGLevels = 435, + BrtEndPCDHGLevels = 436, + BrtBeginPCDHGLevel = 437, + BrtEndPCDHGLevel = 438, + BrtBeginPCDHGLGroups = 439, + BrtEndPCDHGLGroups = 440, + BrtBeginPCDHGLGroup = 441, + BrtEndPCDHGLGroup = 442, + BrtBeginPCDHGLGMembers = 443, + BrtEndPCDHGLGMembers = 444, + BrtBeginPCDHGLGMember = 445, + BrtEndPCDHGLGMember = 446, + BrtBeginQSI = 447, + BrtEndQSI = 448, + BrtBeginQSIR = 449, + BrtEndQSIR = 450, + BrtBeginDeletedNames = 451, + BrtEndDeletedNames = 452, + BrtBeginDeletedName = 453, + BrtEndDeletedName = 454, + BrtBeginQSIFs = 455, + BrtEndQSIFs = 456, + BrtBeginQSIF = 457, + BrtEndQSIF = 458, + BrtBeginAutoSortScope = 459, + BrtEndAutoSortScope = 460, + BrtBeginConditionalFormatting = 461, + BrtEndConditionalFormatting = 462, + BrtBeginCFRule = 463, + BrtEndCFRule = 464, + BrtBeginIconSet = 465, + BrtEndIconSet = 466, + BrtBeginDatabar = 467, + BrtEndDatabar = 468, + BrtBeginColorScale = 469, + BrtEndColorScale = 470, + BrtCFVO = 471, + BrtExternValueMeta = 472, + BrtBeginColorPalette = 473, + BrtEndColorPalette = 474, + BrtIndexedColor = 475, + BrtMargins = 476, + BrtPrintOptions = 477, + BrtPageSetup = 478, + BrtBeginHeaderFooter = 479, + BrtEndHeaderFooter = 480, + BrtBeginSXCrtFormat = 481, + BrtEndSXCrtFormat = 482, + BrtBeginSXCrtFormats = 483, + BrtEndSXCrtFormats = 484, + BrtWsFmtInfo = 485, + BrtBeginMgs = 486, + BrtEndMGs = 487, + BrtBeginMGMaps = 488, + BrtEndMGMaps = 489, + BrtBeginMG = 490, + BrtEndMG = 491, + BrtBeginMap = 492, + BrtEndMap = 493, + BrtHLink = 494, + BrtBeginDCon = 495, + BrtEndDCon = 496, + BrtBeginDRefs = 497, + BrtEndDRefs = 498, + BrtDRef = 499, + BrtBeginScenMan = 500, + BrtEndScenMan = 501, + BrtBeginSct = 502, + BrtEndSct = 503, + BrtSlc = 504, + BrtBeginDXFs = 505, + BrtEndDXFs = 506, + BrtDXF = 507, + BrtBeginTableStyles = 508, + BrtEndTableStyles = 509, + BrtBeginTableStyle = 510, + BrtEndTableStyle = 511, + BrtTableStyleElement = 512, + BrtTableStyleClient = 513, + BrtBeginVolDeps = 514, + BrtEndVolDeps = 515, + BrtBeginVolType = 516, + BrtEndVolType = 517, + BrtBeginVolMain = 518, + BrtEndVolMain = 519, + BrtBeginVolTopic = 520, + BrtEndVolTopic = 521, + BrtVolSubtopic = 522, + BrtVolRef = 523, + BrtVolNum = 524, + BrtVolErr = 525, + BrtVolStr = 526, + BrtVolBool = 527, + BrtBeginSortState = 530, + BrtEndSortState = 531, + BrtBeginSortCond = 532, + BrtEndSortCond = 533, + BrtBookProtection = 534, + BrtSheetProtection = 535, + BrtRangeProtection = 536, + BrtPhoneticInfo = 537, + BrtBeginECTxtWiz = 538, + BrtEndECTxtWiz = 539, + BrtBeginECTWFldInfoLst = 540, + BrtEndECTWFldInfoLst = 541, + BrtBeginECTwFldInfo = 542, + BrtFileSharing = 548, + BrtOleSize = 549, + BrtDrawing = 550, + BrtLegacyDrawing = 551, + BrtLegacyDrawingHF = 552, + BrtWebOpt = 553, + BrtBeginWebPubItems = 554, + BrtEndWebPubItems = 555, + BrtBeginWebPubItem = 556, + BrtEndWebPubItem = 557, + BrtBeginSXCondFmt = 558, + BrtEndSXCondFmt = 559, + BrtBeginSXCondFmts = 560, + BrtEndSXCondFmts = 561, + BrtBkHim = 562, + BrtColor = 564, + BrtBeginIndexedColors = 565, + BrtEndIndexedColors = 566, + BrtBeginMRUColors = 569, + BrtEndMRUColors = 570, + BrtMRUColor = 572, + BrtBeginDVals = 573, + BrtEndDVals = 574, + BrtSupNameStart = 577, + BrtSupNameValueStart = 578, + BrtSupNameValueEnd = 579, + BrtSupNameNum = 580, + BrtSupNameErr = 581, + BrtSupNameSt = 582, + BrtSupNameNil = 583, + BrtSupNameBool = 584, + BrtSupNameFmla = 585, + BrtSupNameBits = 586, + BrtSupNameEnd = 587, + BrtEndSupBook = 588, + BrtCellSmartTagProperty = 589, + BrtBeginCellSmartTag = 590, + BrtEndCellSmartTag = 591, + BrtBeginCellSmartTags = 592, + BrtEndCellSmartTags = 593, + BrtBeginSmartTags = 594, + BrtEndSmartTags = 595, + BrtSmartTagType = 596, + BrtBeginSmartTagTypes = 597, + BrtEndSmartTagTypes = 598, + BrtBeginSXFilters = 599, + BrtEndSXFilters = 600, + BrtBeginSXFILTER = 601, + BrtEndSXFilter = 602, + BrtBeginFills = 603, + BrtEndFills = 604, + BrtBeginCellWatches = 605, + BrtEndCellWatches = 606, + BrtCellWatch = 607, + BrtBeginCRErrs = 608, + BrtEndCRErrs = 609, + BrtCrashRecErr = 610, + BrtBeginFonts = 611, + BrtEndFonts = 612, + BrtBeginBorders = 613, + BrtEndBorders = 614, + BrtBeginFmts = 615, + BrtEndFmts = 616, + BrtBeginCellXFs = 617, + BrtEndCellXFs = 618, + BrtBeginStyles = 619, + BrtEndStyles = 620, + BrtBigName = 625, + BrtBeginCellStyleXFs = 626, + BrtEndCellStyleXFs = 627, + BrtBeginComments = 628, + BrtEndComments = 629, + BrtBeginCommentAuthors = 630, + BrtEndCommentAuthors = 631, + BrtCommentAuthor = 632, + BrtBeginCommentList = 633, + BrtEndCommentList = 634, + BrtBeginComment = 635, + BrtEndComment = 636, + BrtCommentText = 637, + BrtBeginOleObjects = 638, + BrtOleObject = 639, + BrtEndOleObjects = 640, + BrtBeginSxrules = 641, + BrtEndSxRules = 642, + BrtBeginActiveXControls = 643, + BrtActiveX = 644, + BrtEndActiveXControls = 645, + BrtBeginPCDSDTCEMembersSortBy = 646, + BrtBeginCellIgnoreECs = 648, + BrtCellIgnoreEC = 649, + BrtEndCellIgnoreECs = 650, + BrtCsProp = 651, + BrtCsPageSetup = 652, + BrtBeginUserCsViews = 653, + BrtEndUserCsViews = 654, + BrtBeginUserCsView = 655, + BrtEndUserCsView = 656, + BrtBeginPcdSFCIEntries = 657, + BrtEndPCDSFCIEntries = 658, + BrtPCDSFCIEntry = 659, + BrtBeginListParts = 660, + BrtListPart = 661, + BrtEndListParts = 662, + BrtSheetCalcProp = 663, + BrtBeginFnGroup = 664, + BrtFnGroup = 665, + BrtEndFnGroup = 666, + BrtSupAddin = 667, + BrtSXTDMPOrder = 668, + BrtCsProtection = 669, + BrtBeginWsSortMap = 671, + BrtEndWsSortMap = 672, + BrtBeginRRSort = 673, + BrtEndRRSort = 674, + BrtRRSortItem = 675, + BrtFileSharingIso = 676, + BrtBookProtectionIso = 677, + BrtSheetProtectionIso = 678, + BrtCsProtectionIso = 679, + BrtRangeProtectionIso = 680, + BrtDValList = 681, + BrtRwDescent = 1024, + BrtKnownFonts = 1025, + BrtBeginSXTupleSet = 1026, + BrtEndSXTupleSet = 1027, + BrtBeginSXTupleSetHeader = 1028, + BrtEndSXTupleSetHeader = 1029, + BrtSXTupleSetHeaderItem = 1030, + BrtBeginSXTupleSetData = 1031, + BrtEndSXTupleSetData = 1032, + BrtBeginSXTupleSetRow = 1033, + BrtEndSXTupleSetRow = 1034, + BrtSXTupleSetRowItem = 1035, + BrtNameExt = 1036, + BrtPCDH14 = 1037, + BrtBeginPCDCalcMem14 = 1038, + BrtEndPCDCalcMem14 = 1039, + BrtSXTH14 = 1040, + BrtBeginSparklineGroup = 1041, + BrtEndSparklineGroup = 1042, + BrtSparkline = 1043, + BrtSXDI14 = 1044, + BrtWsFmtInfoEx14 = 1045, + BrtBeginConditionalFormatting14 = 1046, + BrtEndConditionalFormatting14 = 1047, + BrtBeginCFRule14 = 1048, + BrtEndCFRule14 = 1049, + BrtCFVO14 = 1050, + BrtBeginDatabar14 = 1051, + BrtBeginIconSet14 = 1052, + BrtDVal14 = 1053, + BrtBeginDVals14 = 1054, + BrtColor14 = 1055, + BrtBeginSparklines = 1056, + BrtEndSparklines = 1057, + BrtBeginSparklineGroups = 1058, + BrtEndSparklineGroups = 1059, + BrtSXVD14 = 1061, + BrtBeginSxView14 = 1062, + BrtEndSxView14 = 1063, + BrtBeginSXView16 = 1064, + BrtEndSXView16 = 1065, + BrtBeginPCD14 = 1066, + BrtEndPCD14 = 1067, + BrtBeginExtConn14 = 1068, + BrtEndExtConn14 = 1069, + BrtBeginSlicerCacheIDs = 1070, + BrtEndSlicerCacheIDs = 1071, + BrtBeginSlicerCacheID = 1072, + BrtEndSlicerCacheID = 1073, + BrtBeginSlicerCache = 1075, + BrtEndSlicerCache = 1076, + BrtBeginSlicerCacheDef = 1077, + BrtEndSlicerCacheDef = 1078, + BrtBeginSlicersEx = 1079, + BrtEndSlicersEx = 1080, + BrtBeginSlicerEx = 1081, + BrtEndSlicerEx = 1082, + BrtBeginSlicer = 1083, + BrtEndSlicer = 1084, + BrtSlicerCachePivotTables = 1085, + BrtBeginSlicerCacheOlapImpl = 1086, + BrtEndSlicerCacheOlapImpl = 1087, + BrtBeginSlicerCacheLevelsData = 1088, + BrtEndSlicerCacheLevelsData = 1089, + BrtBeginSlicerCacheLevelData = 1090, + BrtEndSlicerCacheLevelData = 1091, + BrtBeginSlicerCacheSiRanges = 1092, + BrtEndSlicerCacheSiRanges = 1093, + BrtBeginSlicerCacheSiRange = 1094, + BrtEndSlicerCacheSiRange = 1095, + BrtSlicerCacheOlapItem = 1096, + BrtBeginSlicerCacheSelections = 1097, + BrtSlicerCacheSelection = 1098, + BrtEndSlicerCacheSelections = 1099, + BrtBeginSlicerCacheNative = 1100, + BrtEndSlicerCacheNative = 1101, + BrtSlicerCacheNativeItem = 1102, + BrtRangeProtection14 = 1103, + BrtRangeProtectionIso14 = 1104, + BrtCellIgnoreEC14 = 1105, + BrtList14 = 1111, + BrtCFIcon = 1112, + BrtBeginSlicerCachesPivotCacheIDs = 1113, + BrtEndSlicerCachesPivotCacheIDs = 1114, + BrtBeginSlicers = 1115, + BrtEndSlicers = 1116, + BrtWbProp14 = 1117, + BrtBeginSXEdit = 1118, + BrtEndSXEdit = 1119, + BrtBeginSXEdits = 1120, + BrtEndSXEdits = 1121, + BrtBeginSXChange = 1122, + BrtEndSXChange = 1123, + BrtBeginSXChanges = 1124, + BrtEndSXChanges = 1125, + BrtSXTupleItems = 1126, + BrtBeginSlicerStyle = 1128, + BrtEndSlicerStyle = 1129, + BrtSlicerStyleElement = 1130, + BrtBeginStyleSheetExt14 = 1131, + BrtEndStyleSheetExt14 = 1132, + BrtBeginSlicerCachesPivotCacheID = 1133, + BrtEndSlicerCachesPivotCacheID = 1134, + BrtBeginConditionalFormattings = 1135, + BrtEndConditionalFormattings = 1136, + BrtBeginPCDCalcMemExt = 1137, + BrtEndPCDCalcMemExt = 1138, + BrtBeginPCDCalcMemsExt = 1139, + BrtEndPCDCalcMemsExt = 1140, + BrtPCDField14 = 1141, + BrtBeginSlicerStyles = 1142, + BrtEndSlicerStyles = 1143, + BrtBeginSlicerStyleElements = 1144, + BrtEndSlicerStyleElements = 1145, + BrtCFRuleExt = 1146, + BrtBeginSXCondFmt14 = 1147, + BrtEndSXCondFmt14 = 1148, + BrtBeginSXCondFmts14 = 1149, + BrtEndSXCondFmts14 = 1150, + BrtBeginSortCond14 = 1152, + BrtEndSortCond14 = 1153, + BrtEndDVals14 = 1154, + BrtEndIconSet14 = 1155, + BrtEndDatabar14 = 1156, + BrtBeginColorScale14 = 1157, + BrtEndColorScale14 = 1158, + BrtBeginSxrules14 = 1159, + BrtEndSxrules14 = 1160, + BrtBeginPRule14 = 1161, + BrtEndPRule14 = 1162, + BrtBeginPRFilters14 = 1163, + BrtEndPRFilters14 = 1164, + BrtBeginPRFilter14 = 1165, + BrtEndPRFilter14 = 1166, + BrtBeginPRFItem14 = 1167, + BrtEndPRFItem14 = 1168, + BrtBeginCellIgnoreECs14 = 1169, + BrtEndCellIgnoreECs14 = 1170, + BrtDxf14 = 1171, + BrtBeginDxF14s = 1172, + BrtEndDxf14s = 1173, + BrtFilter14 = 1177, + BrtBeginCustomFilters14 = 1178, + BrtCustomFilter14 = 1180, + BrtIconFilter14 = 1181, + BrtPivotCacheConnectionName = 1182, + BrtBeginDecoupledPivotCacheIDs = 2048, + BrtEndDecoupledPivotCacheIDs = 2049, + BrtDecoupledPivotCacheID = 2050, + BrtBeginPivotTableRefs = 2051, + BrtEndPivotTableRefs = 2052, + BrtPivotTableRef = 2053, + BrtSlicerCacheBookPivotTables = 2054, + BrtBeginSxvcells = 2055, + BrtEndSxvcells = 2056, + BrtBeginSxRow = 2057, + BrtEndSxRow = 2058, + BrtPcdCalcMem15 = 2060, + BrtQsi15 = 2067, + BrtBeginWebExtensions = 2068, + BrtEndWebExtensions = 2069, + BrtWebExtension = 2070, + BrtAbsPath15 = 2071, + BrtBeginPivotTableUISettings = 2072, + BrtEndPivotTableUISettings = 2073, + BrtTableSlicerCacheIDs = 2075, + BrtTableSlicerCacheID = 2076, + BrtBeginTableSlicerCache = 2077, + BrtEndTableSlicerCache = 2078, + BrtSxFilter15 = 2079, + BrtBeginTimelineCachePivotCacheIDs = 2080, + BrtEndTimelineCachePivotCacheIDs = 2081, + BrtTimelineCachePivotCacheID = 2082, + BrtBeginTimelineCacheIDs = 2083, + BrtEndTimelineCacheIDs = 2084, + BrtBeginTimelineCacheID = 2085, + BrtEndTimelineCacheID = 2086, + BrtBeginTimelinesEx = 2087, + BrtEndTimelinesEx = 2088, + BrtBeginTimelineEx = 2089, + BrtEndTimelineEx = 2090, + BrtWorkBookPr15 = 2091, + BrtPCDH15 = 2092, + BrtBeginTimelineStyle = 2093, + BrtEndTimelineStyle = 2094, + BrtTimelineStyleElement = 2095, + BrtBeginTimelineStylesheetExt15 = 2096, + BrtEndTimelineStylesheetExt15 = 2097, + BrtBeginTimelineStyles = 2098, + BrtEndTimelineStyles = 2099, + BrtBeginTimelineStyleElements = 2100, + BrtEndTimelineStyleElements = 2101, + BrtDxf15 = 2102, + BrtBeginDxfs15 = 2103, + BrtEndDXFs15 = 2104, + BrtSlicerCacheHideItemsWithNoData = 2105, + BrtBeginItemUniqueNames = 2106, + BrtEndItemUniqueNames = 2107, + BrtItemUniqueName = 2108, + BrtBeginExtConn15 = 2109, + BrtEndExtConn15 = 2110, + BrtBeginOledbPr15 = 2111, + BrtEndOledbPr15 = 2112, + BrtBeginDataFeedPr15 = 2113, + BrtEndDataFeedPr15 = 2114, + BrtTextPr15 = 2115, + BrtRangePr15 = 2116, + BrtDbCommand15 = 2117, + BrtBeginDbTables15 = 2118, + BrtEndDbTables15 = 2119, + BrtDbTable15 = 2120, + BrtBeginDataModel = 2121, + BrtEndDataModel = 2122, + BrtBeginModelTables = 2123, + BrtEndModelTables = 2124, + BrtModelTable = 2125, + BrtBeginModelRelationships = 2126, + BrtEndModelRelationships = 2127, + BrtModelRelationship = 2128, + BrtBeginECTxtWiz15 = 2129, + BrtEndECTxtWiz15 = 2130, + BrtBeginECTWFldInfoLst15 = 2131, + BrtEndECTWFldInfoLst15 = 2132, + BrtBeginECTWFldInfo15 = 2133, + BrtFieldListActiveItem = 2134, + BrtPivotCacheIdVersion = 2135, + BrtSXDI15 = 2136, + brtBeginModelTimeGroupings = 2137, + brtEndModelTimeGroupings = 2138, + brtBeginModelTimeGrouping = 2139, + brtEndModelTimeGrouping = 2140, + brtModelTimeGroupingCalcCol = 2141, + brtRevisionPtr = 3073, + BrtBeginDynamicArrayPr = 4096, + BrtEndDynamicArrayPr = 4097, + BrtBeginRichValueBlock = 5002, + BrtEndRichValueBlock = 5003, + BrtBeginRichFilters = 5081, + BrtEndRichFilters = 5082, + BrtRichFilter = 5083, + BrtBeginRichFilterColumn = 5084, + BrtEndRichFilterColumn = 5085, + BrtBeginCustomRichFilters = 5086, + BrtEndCustomRichFilters = 5087, + BRTCustomRichFilter = 5088, + BrtTop10RichFilter = 5089, + BrtDynamicRichFilter = 5090, + BrtBeginRichSortCondition = 5092, + BrtEndRichSortCondition = 5093, + BrtRichFilterDateGroupItem = 5094, + BrtBeginCalcFeatures = 5095, + BrtEndCalcFeatures = 5096, + BrtCalcFeature = 5097, + BrtExternalLinksPr = 5099 +}; + +template<> +struct fmt::formatter { + constexpr auto parse(format_parse_context& ctx) { + auto it = ctx.begin(); + + if (it != ctx.end() && *it != '}') + throw format_error("invalid format"); + + return it; + } + + template + auto format(enum xlsb_type t, format_context& ctx) const { + switch (t) { + case xlsb_type::BrtRowHdr: + return fmt::format_to(ctx.out(), "BrtRowHdr"); + case xlsb_type::BrtCellBlank: + return fmt::format_to(ctx.out(), "BrtCellBlank"); + case xlsb_type::BrtCellRk: + return fmt::format_to(ctx.out(), "BrtCellRk"); + case xlsb_type::BrtCellError: + return fmt::format_to(ctx.out(), "BrtCellError"); + case xlsb_type::BrtCellBool: + return fmt::format_to(ctx.out(), "BrtCellBool"); + case xlsb_type::BrtCellReal: + return fmt::format_to(ctx.out(), "BrtCellReal"); + case xlsb_type::BrtCellSt: + return fmt::format_to(ctx.out(), "BrtCellSt"); + case xlsb_type::BrtCellIsst: + return fmt::format_to(ctx.out(), "BrtCellIsst"); + case xlsb_type::BrtFmlaString: + return fmt::format_to(ctx.out(), "BrtFmlaString"); + case xlsb_type::BrtFmlaNum: + return fmt::format_to(ctx.out(), "BrtFmlaNum"); + case xlsb_type::BrtFmlaBool: + return fmt::format_to(ctx.out(), "BrtFmlaBool"); + case xlsb_type::BrtFmlaError: + return fmt::format_to(ctx.out(), "BrtFmlaError"); + case xlsb_type::BrtSSTItem: + return fmt::format_to(ctx.out(), "BrtSSTItem"); + case xlsb_type::BrtPCDIMissing: + return fmt::format_to(ctx.out(), "BrtPCDIMissing"); + case xlsb_type::BrtPCDINumber: + return fmt::format_to(ctx.out(), "BrtPCDINumber"); + case xlsb_type::BrtPCDIBoolean: + return fmt::format_to(ctx.out(), "BrtPCDIBoolean"); + case xlsb_type::BrtPCDIError: + return fmt::format_to(ctx.out(), "BrtPCDIError"); + case xlsb_type::BrtPCDIString: + return fmt::format_to(ctx.out(), "BrtPCDIString"); + case xlsb_type::BrtPCDIDatetime: + return fmt::format_to(ctx.out(), "BrtPCDIDatetime"); + case xlsb_type::BrtPCDIIndex: + return fmt::format_to(ctx.out(), "BrtPCDIIndex"); + case xlsb_type::BrtPCDIAMissing: + return fmt::format_to(ctx.out(), "BrtPCDIAMissing"); + case xlsb_type::BrtPCDIANumber: + return fmt::format_to(ctx.out(), "BrtPCDIANumber"); + case xlsb_type::BrtPCDIABoolean: + return fmt::format_to(ctx.out(), "BrtPCDIABoolean"); + case xlsb_type::BrtPCDIAError: + return fmt::format_to(ctx.out(), "BrtPCDIAError"); + case xlsb_type::BrtPCDIAString: + return fmt::format_to(ctx.out(), "BrtPCDIAString"); + case xlsb_type::BrtPCDIADatetime: + return fmt::format_to(ctx.out(), "BrtPCDIADatetime"); + case xlsb_type::BrtPCRRecord: + return fmt::format_to(ctx.out(), "BrtPCRRecord"); + case xlsb_type::BrtPCRRecordDt: + return fmt::format_to(ctx.out(), "BrtPCRRecordDt"); + case xlsb_type::BrtFRTBegin: + return fmt::format_to(ctx.out(), "BrtFRTBegin"); + case xlsb_type::BrtFRTEnd: + return fmt::format_to(ctx.out(), "BrtFRTEnd"); + case xlsb_type::BrtACBegin: + return fmt::format_to(ctx.out(), "BrtACBegin"); + case xlsb_type::BrtACEnd: + return fmt::format_to(ctx.out(), "BrtACEnd"); + case xlsb_type::BrtName: + return fmt::format_to(ctx.out(), "BrtName"); + case xlsb_type::BrtIndexRowBlock: + return fmt::format_to(ctx.out(), "BrtIndexRowBlock"); + case xlsb_type::BrtIndexBlock: + return fmt::format_to(ctx.out(), "BrtIndexBlock"); + case xlsb_type::BrtFont: + return fmt::format_to(ctx.out(), "BrtFont"); + case xlsb_type::BrtFmt: + return fmt::format_to(ctx.out(), "BrtFmt"); + case xlsb_type::BrtFill: + return fmt::format_to(ctx.out(), "BrtFill"); + case xlsb_type::BrtBorder: + return fmt::format_to(ctx.out(), "BrtBorder"); + case xlsb_type::BrtXF: + return fmt::format_to(ctx.out(), "BrtXF"); + case xlsb_type::BrtStyle: + return fmt::format_to(ctx.out(), "BrtStyle"); + case xlsb_type::BrtCellMeta: + return fmt::format_to(ctx.out(), "BrtCellMeta"); + case xlsb_type::BrtValueMeta: + return fmt::format_to(ctx.out(), "BrtValueMeta"); + case xlsb_type::BrtMdb: + return fmt::format_to(ctx.out(), "BrtMdb"); + case xlsb_type::BrtBeginFmd: + return fmt::format_to(ctx.out(), "BrtBeginFmd"); + case xlsb_type::BrtEndFmd: + return fmt::format_to(ctx.out(), "BrtEndFmd"); + case xlsb_type::BrtBeginMdx: + return fmt::format_to(ctx.out(), "BrtBeginMdx"); + case xlsb_type::BrtEndMdx: + return fmt::format_to(ctx.out(), "BrtEndMdx"); + case xlsb_type::BrtBeginMdxTuple: + return fmt::format_to(ctx.out(), "BrtBeginMdxTuple"); + case xlsb_type::BrtEndMdxTuple: + return fmt::format_to(ctx.out(), "BrtEndMdxTuple"); + case xlsb_type::BrtMdxMbrIstr: + return fmt::format_to(ctx.out(), "BrtMdxMbrIstr"); + case xlsb_type::BrtStr: + return fmt::format_to(ctx.out(), "BrtStr"); + case xlsb_type::BrtColInfo: + return fmt::format_to(ctx.out(), "BrtColInfo"); + case xlsb_type::BrtCellRString: + return fmt::format_to(ctx.out(), "BrtCellRString"); + case xlsb_type::BrtDVal: + return fmt::format_to(ctx.out(), "BrtDVal"); + case xlsb_type::BrtSxvcellNum: + return fmt::format_to(ctx.out(), "BrtSxvcellNum"); + case xlsb_type::BrtSxvcellStr: + return fmt::format_to(ctx.out(), "BrtSxvcellStr"); + case xlsb_type::BrtSxvcellBool: + return fmt::format_to(ctx.out(), "BrtSxvcellBool"); + case xlsb_type::BrtSxvcellErr: + return fmt::format_to(ctx.out(), "BrtSxvcellErr"); + case xlsb_type::BrtSxvcellDate: + return fmt::format_to(ctx.out(), "BrtSxvcellDate"); + case xlsb_type::BrtSxvcellNil: + return fmt::format_to(ctx.out(), "BrtSxvcellNil"); + case xlsb_type::BrtFileVersion: + return fmt::format_to(ctx.out(), "BrtFileVersion"); + case xlsb_type::BrtBeginSheet: + return fmt::format_to(ctx.out(), "BrtBeginSheet"); + case xlsb_type::BrtEndSheet: + return fmt::format_to(ctx.out(), "BrtEndSheet"); + case xlsb_type::BrtBeginBook: + return fmt::format_to(ctx.out(), "BrtBeginBook"); + case xlsb_type::BrtEndBook: + return fmt::format_to(ctx.out(), "BrtEndBook"); + case xlsb_type::BrtBeginWsViews: + return fmt::format_to(ctx.out(), "BrtBeginWsViews"); + case xlsb_type::BrtEndWsViews: + return fmt::format_to(ctx.out(), "BrtEndWsViews"); + case xlsb_type::BrtBeginBookViews: + return fmt::format_to(ctx.out(), "BrtBeginBookViews"); + case xlsb_type::BrtEndBookViews: + return fmt::format_to(ctx.out(), "BrtEndBookViews"); + case xlsb_type::BrtBeginWsView: + return fmt::format_to(ctx.out(), "BrtBeginWsView"); + case xlsb_type::BrtEndWsView: + return fmt::format_to(ctx.out(), "BrtEndWsView"); + case xlsb_type::BrtBeginCsViews: + return fmt::format_to(ctx.out(), "BrtBeginCsViews"); + case xlsb_type::BrtEndCsViews: + return fmt::format_to(ctx.out(), "BrtEndCsViews"); + case xlsb_type::BrtBeginCsView: + return fmt::format_to(ctx.out(), "BrtBeginCsView"); + case xlsb_type::BrtEndCsView: + return fmt::format_to(ctx.out(), "BrtEndCsView"); + case xlsb_type::BrtBeginBundleShs: + return fmt::format_to(ctx.out(), "BrtBeginBundleShs"); + case xlsb_type::BrtEndBundleShs: + return fmt::format_to(ctx.out(), "BrtEndBundleShs"); + case xlsb_type::BrtBeginSheetData: + return fmt::format_to(ctx.out(), "BrtBeginSheetData"); + case xlsb_type::BrtEndSheetData: + return fmt::format_to(ctx.out(), "BrtEndSheetData"); + case xlsb_type::BrtWsProp: + return fmt::format_to(ctx.out(), "BrtWsProp"); + case xlsb_type::BrtWsDim: + return fmt::format_to(ctx.out(), "BrtWsDim"); + case xlsb_type::BrtPane: + return fmt::format_to(ctx.out(), "BrtPane"); + case xlsb_type::BrtSel: + return fmt::format_to(ctx.out(), "BrtSel"); + case xlsb_type::BrtWbProp: + return fmt::format_to(ctx.out(), "BrtWbProp"); + case xlsb_type::BrtWbFactoid: + return fmt::format_to(ctx.out(), "BrtWbFactoid"); + case xlsb_type::BrtFileRecover: + return fmt::format_to(ctx.out(), "BrtFileRecover"); + case xlsb_type::BrtBundleSh: + return fmt::format_to(ctx.out(), "BrtBundleSh"); + case xlsb_type::BrtCalcProp: + return fmt::format_to(ctx.out(), "BrtCalcProp"); + case xlsb_type::BrtBookView: + return fmt::format_to(ctx.out(), "BrtBookView"); + case xlsb_type::BrtBeginSst: + return fmt::format_to(ctx.out(), "BrtBeginSst"); + case xlsb_type::BrtEndSst: + return fmt::format_to(ctx.out(), "BrtEndSst"); + case xlsb_type::BrtBeginAFilter: + return fmt::format_to(ctx.out(), "BrtBeginAFilter"); + case xlsb_type::BrtEndAFilter: + return fmt::format_to(ctx.out(), "BrtEndAFilter"); + case xlsb_type::BrtBeginFilterColumn: + return fmt::format_to(ctx.out(), "BrtBeginFilterColumn"); + case xlsb_type::BrtEndFilterColumn: + return fmt::format_to(ctx.out(), "BrtEndFilterColumn"); + case xlsb_type::BrtBeginFilters: + return fmt::format_to(ctx.out(), "BrtBeginFilters"); + case xlsb_type::BrtEndFilters: + return fmt::format_to(ctx.out(), "BrtEndFilters"); + case xlsb_type::BrtFilter: + return fmt::format_to(ctx.out(), "BrtFilter"); + case xlsb_type::BrtColorFilter: + return fmt::format_to(ctx.out(), "BrtColorFilter"); + case xlsb_type::BrtIconFilter: + return fmt::format_to(ctx.out(), "BrtIconFilter"); + case xlsb_type::BrtTop10Filter: + return fmt::format_to(ctx.out(), "BrtTop10Filter"); + case xlsb_type::BrtDynamicFilter: + return fmt::format_to(ctx.out(), "BrtDynamicFilter"); + case xlsb_type::BrtBeginCustomFilters: + return fmt::format_to(ctx.out(), "BrtBeginCustomFilters"); + case xlsb_type::BrtEndCustomFilters: + return fmt::format_to(ctx.out(), "BrtEndCustomFilters"); + case xlsb_type::BrtCustomFilter: + return fmt::format_to(ctx.out(), "BrtCustomFilter"); + case xlsb_type::BrtAFilterDateGroupItem: + return fmt::format_to(ctx.out(), "BrtAFilterDateGroupItem"); + case xlsb_type::BrtMergeCell: + return fmt::format_to(ctx.out(), "BrtMergeCell"); + case xlsb_type::BrtBeginMergeCells: + return fmt::format_to(ctx.out(), "BrtBeginMergeCells"); + case xlsb_type::BrtEndMergeCells: + return fmt::format_to(ctx.out(), "BrtEndMergeCells"); + case xlsb_type::BrtBeginPivotCacheDef: + return fmt::format_to(ctx.out(), "BrtBeginPivotCacheDef"); + case xlsb_type::BrtEndPivotCacheDef: + return fmt::format_to(ctx.out(), "BrtEndPivotCacheDef"); + case xlsb_type::BrtBeginPCDFields: + return fmt::format_to(ctx.out(), "BrtBeginPCDFields"); + case xlsb_type::BrtEndPCDFields: + return fmt::format_to(ctx.out(), "BrtEndPCDFields"); + case xlsb_type::BrtBeginPCDField: + return fmt::format_to(ctx.out(), "BrtBeginPCDField"); + case xlsb_type::BrtEndPCDField: + return fmt::format_to(ctx.out(), "BrtEndPCDField"); + case xlsb_type::BrtBeginPCDSource: + return fmt::format_to(ctx.out(), "BrtBeginPCDSource"); + case xlsb_type::BrtEndPCDSource: + return fmt::format_to(ctx.out(), "BrtEndPCDSource"); + case xlsb_type::BrtBeginPCDSRange: + return fmt::format_to(ctx.out(), "BrtBeginPCDSRange"); + case xlsb_type::BrtEndPCDSRange: + return fmt::format_to(ctx.out(), "BrtEndPCDSRange"); + case xlsb_type::BrtBeginPCDFAtbl: + return fmt::format_to(ctx.out(), "BrtBeginPCDFAtbl"); + case xlsb_type::BrtEndPCDFAtbl: + return fmt::format_to(ctx.out(), "BrtEndPCDFAtbl"); + case xlsb_type::BrtBeginPCDIRun: + return fmt::format_to(ctx.out(), "BrtBeginPCDIRun"); + case xlsb_type::BrtEndPCDIRun: + return fmt::format_to(ctx.out(), "BrtEndPCDIRun"); + case xlsb_type::BrtBeginPivotCacheRecords: + return fmt::format_to(ctx.out(), "BrtBeginPivotCacheRecords"); + case xlsb_type::BrtEndPivotCacheRecords: + return fmt::format_to(ctx.out(), "BrtEndPivotCacheRecords"); + case xlsb_type::BrtBeginPCDHierarchies: + return fmt::format_to(ctx.out(), "BrtBeginPCDHierarchies"); + case xlsb_type::BrtEndPCDHierarchies: + return fmt::format_to(ctx.out(), "BrtEndPCDHierarchies"); + case xlsb_type::BrtBeginPCDHierarchy: + return fmt::format_to(ctx.out(), "BrtBeginPCDHierarchy"); + case xlsb_type::BrtEndPCDHierarchy: + return fmt::format_to(ctx.out(), "BrtEndPCDHierarchy"); + case xlsb_type::BrtBeginPCDHFieldsUsage: + return fmt::format_to(ctx.out(), "BrtBeginPCDHFieldsUsage"); + case xlsb_type::BrtEndPCDHFieldsUsage: + return fmt::format_to(ctx.out(), "BrtEndPCDHFieldsUsage"); + case xlsb_type::BrtBeginExtConnection: + return fmt::format_to(ctx.out(), "BrtBeginExtConnection"); + case xlsb_type::BrtEndExtConnection: + return fmt::format_to(ctx.out(), "BrtEndExtConnection"); + case xlsb_type::BrtBeginECDbProps: + return fmt::format_to(ctx.out(), "BrtBeginECDbProps"); + case xlsb_type::BrtEndECDbProps: + return fmt::format_to(ctx.out(), "BrtEndECDbProps"); + case xlsb_type::BrtBeginECOlapProps: + return fmt::format_to(ctx.out(), "BrtBeginECOlapProps"); + case xlsb_type::BrtEndECOlapProps: + return fmt::format_to(ctx.out(), "BrtEndECOlapProps"); + case xlsb_type::BrtBeginPCDSConsol: + return fmt::format_to(ctx.out(), "BrtBeginPCDSConsol"); + case xlsb_type::BrtEndPCDSConsol: + return fmt::format_to(ctx.out(), "BrtEndPCDSConsol"); + case xlsb_type::BrtBeginPCDSCPages: + return fmt::format_to(ctx.out(), "BrtBeginPCDSCPages"); + case xlsb_type::BrtEndPCDSCPages: + return fmt::format_to(ctx.out(), "BrtEndPCDSCPages"); + case xlsb_type::BrtBeginPCDSCPage: + return fmt::format_to(ctx.out(), "BrtBeginPCDSCPage"); + case xlsb_type::BrtEndPCDSCPage: + return fmt::format_to(ctx.out(), "BrtEndPCDSCPage"); + case xlsb_type::BrtBeginPCDSCPItem: + return fmt::format_to(ctx.out(), "BrtBeginPCDSCPItem"); + case xlsb_type::BrtEndPCDSCPItem: + return fmt::format_to(ctx.out(), "BrtEndPCDSCPItem"); + case xlsb_type::BrtBeginPCDSCSets: + return fmt::format_to(ctx.out(), "BrtBeginPCDSCSets"); + case xlsb_type::BrtEndPCDSCSets: + return fmt::format_to(ctx.out(), "BrtEndPCDSCSets"); + case xlsb_type::BrtBeginPCDSCSet: + return fmt::format_to(ctx.out(), "BrtBeginPCDSCSet"); + case xlsb_type::BrtEndPCDSCSet: + return fmt::format_to(ctx.out(), "BrtEndPCDSCSet"); + case xlsb_type::BrtBeginPCDFGroup: + return fmt::format_to(ctx.out(), "BrtBeginPCDFGroup"); + case xlsb_type::BrtEndPCDFGroup: + return fmt::format_to(ctx.out(), "BrtEndPCDFGroup"); + case xlsb_type::BrtBeginPCDFGItems: + return fmt::format_to(ctx.out(), "BrtBeginPCDFGItems"); + case xlsb_type::BrtEndPCDFGItems: + return fmt::format_to(ctx.out(), "BrtEndPCDFGItems"); + case xlsb_type::BrtBeginPCDFGRange: + return fmt::format_to(ctx.out(), "BrtBeginPCDFGRange"); + case xlsb_type::BrtEndPCDFGRange: + return fmt::format_to(ctx.out(), "BrtEndPCDFGRange"); + case xlsb_type::BrtBeginPCDFGDiscrete: + return fmt::format_to(ctx.out(), "BrtBeginPCDFGDiscrete"); + case xlsb_type::BrtEndPCDFGDiscrete: + return fmt::format_to(ctx.out(), "BrtEndPCDFGDiscrete"); + case xlsb_type::BrtBeginPCDSDTupleCache: + return fmt::format_to(ctx.out(), "BrtBeginPCDSDTupleCache"); + case xlsb_type::BrtEndPCDSDTupleCache: + return fmt::format_to(ctx.out(), "BrtEndPCDSDTupleCache"); + case xlsb_type::BrtBeginPCDSDTCEntries: + return fmt::format_to(ctx.out(), "BrtBeginPCDSDTCEntries"); + case xlsb_type::BrtEndPCDSDTCEntries: + return fmt::format_to(ctx.out(), "BrtEndPCDSDTCEntries"); + case xlsb_type::BrtBeginPCDSDTCEMembers: + return fmt::format_to(ctx.out(), "BrtBeginPCDSDTCEMembers"); + case xlsb_type::BrtEndPCDSDTCEMembers: + return fmt::format_to(ctx.out(), "BrtEndPCDSDTCEMembers"); + case xlsb_type::BrtBeginPCDSDTCEMember: + return fmt::format_to(ctx.out(), "BrtBeginPCDSDTCEMember"); + case xlsb_type::BrtEndPCDSDTCEMember: + return fmt::format_to(ctx.out(), "BrtEndPCDSDTCEMember"); + case xlsb_type::BrtBeginPCDSDTCQueries: + return fmt::format_to(ctx.out(), "BrtBeginPCDSDTCQueries"); + case xlsb_type::BrtEndPCDSDTCQueries: + return fmt::format_to(ctx.out(), "BrtEndPCDSDTCQueries"); + case xlsb_type::BrtBeginPCDSDTCQuery: + return fmt::format_to(ctx.out(), "BrtBeginPCDSDTCQuery"); + case xlsb_type::BrtEndPCDSDTCQuery: + return fmt::format_to(ctx.out(), "BrtEndPCDSDTCQuery"); + case xlsb_type::BrtBeginPCDSDTCSets: + return fmt::format_to(ctx.out(), "BrtBeginPCDSDTCSets"); + case xlsb_type::BrtEndPCDSDTCSets: + return fmt::format_to(ctx.out(), "BrtEndPCDSDTCSets"); + case xlsb_type::BrtBeginPCDSDTCSet: + return fmt::format_to(ctx.out(), "BrtBeginPCDSDTCSet"); + case xlsb_type::BrtEndPCDSDTCSet: + return fmt::format_to(ctx.out(), "BrtEndPCDSDTCSet"); + case xlsb_type::BrtBeginPCDCalcItems: + return fmt::format_to(ctx.out(), "BrtBeginPCDCalcItems"); + case xlsb_type::BrtEndPCDCalcItems: + return fmt::format_to(ctx.out(), "BrtEndPCDCalcItems"); + case xlsb_type::BrtBeginPCDCalcItem: + return fmt::format_to(ctx.out(), "BrtBeginPCDCalcItem"); + case xlsb_type::BrtEndPCDCalcItem: + return fmt::format_to(ctx.out(), "BrtEndPCDCalcItem"); + case xlsb_type::BrtBeginPRule: + return fmt::format_to(ctx.out(), "BrtBeginPRule"); + case xlsb_type::BrtEndPRule: + return fmt::format_to(ctx.out(), "BrtEndPRule"); + case xlsb_type::BrtBeginPRFilters: + return fmt::format_to(ctx.out(), "BrtBeginPRFilters"); + case xlsb_type::BrtEndPRFilters: + return fmt::format_to(ctx.out(), "BrtEndPRFilters"); + case xlsb_type::BrtBeginPRFilter: + return fmt::format_to(ctx.out(), "BrtBeginPRFilter"); + case xlsb_type::BrtEndPRFilter: + return fmt::format_to(ctx.out(), "BrtEndPRFilter"); + case xlsb_type::BrtBeginPNames: + return fmt::format_to(ctx.out(), "BrtBeginPNames"); + case xlsb_type::BrtEndPNames: + return fmt::format_to(ctx.out(), "BrtEndPNames"); + case xlsb_type::BrtBeginPName: + return fmt::format_to(ctx.out(), "BrtBeginPName"); + case xlsb_type::BrtEndPName: + return fmt::format_to(ctx.out(), "BrtEndPName"); + case xlsb_type::BrtBeginPNPairs: + return fmt::format_to(ctx.out(), "BrtBeginPNPairs"); + case xlsb_type::BrtEndPNPairs: + return fmt::format_to(ctx.out(), "BrtEndPNPairs"); + case xlsb_type::BrtBeginPNPair: + return fmt::format_to(ctx.out(), "BrtBeginPNPair"); + case xlsb_type::BrtEndPNPair: + return fmt::format_to(ctx.out(), "BrtEndPNPair"); + case xlsb_type::BrtBeginECWebProps: + return fmt::format_to(ctx.out(), "BrtBeginECWebProps"); + case xlsb_type::BrtEndECWebProps: + return fmt::format_to(ctx.out(), "BrtEndECWebProps"); + case xlsb_type::BrtBeginEcWpTables: + return fmt::format_to(ctx.out(), "BrtBeginEcWpTables"); + case xlsb_type::BrtEndECWPTables: + return fmt::format_to(ctx.out(), "BrtEndECWPTables"); + case xlsb_type::BrtBeginECParams: + return fmt::format_to(ctx.out(), "BrtBeginECParams"); + case xlsb_type::BrtEndECParams: + return fmt::format_to(ctx.out(), "BrtEndECParams"); + case xlsb_type::BrtBeginECParam: + return fmt::format_to(ctx.out(), "BrtBeginECParam"); + case xlsb_type::BrtEndECParam: + return fmt::format_to(ctx.out(), "BrtEndECParam"); + case xlsb_type::BrtBeginPCDKPIs: + return fmt::format_to(ctx.out(), "BrtBeginPCDKPIs"); + case xlsb_type::BrtEndPCDKPIs: + return fmt::format_to(ctx.out(), "BrtEndPCDKPIs"); + case xlsb_type::BrtBeginPCDKPI: + return fmt::format_to(ctx.out(), "BrtBeginPCDKPI"); + case xlsb_type::BrtEndPCDKPI: + return fmt::format_to(ctx.out(), "BrtEndPCDKPI"); + case xlsb_type::BrtBeginDims: + return fmt::format_to(ctx.out(), "BrtBeginDims"); + case xlsb_type::BrtEndDims: + return fmt::format_to(ctx.out(), "BrtEndDims"); + case xlsb_type::BrtBeginDim: + return fmt::format_to(ctx.out(), "BrtBeginDim"); + case xlsb_type::BrtEndDim: + return fmt::format_to(ctx.out(), "BrtEndDim"); + case xlsb_type::BrtIndexPartEnd: + return fmt::format_to(ctx.out(), "BrtIndexPartEnd"); + case xlsb_type::BrtBeginStyleSheet: + return fmt::format_to(ctx.out(), "BrtBeginStyleSheet"); + case xlsb_type::BrtEndStyleSheet: + return fmt::format_to(ctx.out(), "BrtEndStyleSheet"); + case xlsb_type::BrtBeginSXView: + return fmt::format_to(ctx.out(), "BrtBeginSXView"); + case xlsb_type::BrtEndSXVI: + return fmt::format_to(ctx.out(), "BrtEndSXVI"); + case xlsb_type::BrtBeginSXVI: + return fmt::format_to(ctx.out(), "BrtBeginSXVI"); + case xlsb_type::BrtBeginSXVIs: + return fmt::format_to(ctx.out(), "BrtBeginSXVIs"); + case xlsb_type::BrtEndSXVIs: + return fmt::format_to(ctx.out(), "BrtEndSXVIs"); + case xlsb_type::BrtBeginSXVD: + return fmt::format_to(ctx.out(), "BrtBeginSXVD"); + case xlsb_type::BrtEndSXVD: + return fmt::format_to(ctx.out(), "BrtEndSXVD"); + case xlsb_type::BrtBeginSXVDs: + return fmt::format_to(ctx.out(), "BrtBeginSXVDs"); + case xlsb_type::BrtEndSXVDs: + return fmt::format_to(ctx.out(), "BrtEndSXVDs"); + case xlsb_type::BrtBeginSXPI: + return fmt::format_to(ctx.out(), "BrtBeginSXPI"); + case xlsb_type::BrtEndSXPI: + return fmt::format_to(ctx.out(), "BrtEndSXPI"); + case xlsb_type::BrtBeginSXPIs: + return fmt::format_to(ctx.out(), "BrtBeginSXPIs"); + case xlsb_type::BrtEndSXPIs: + return fmt::format_to(ctx.out(), "BrtEndSXPIs"); + case xlsb_type::BrtBeginSXDI: + return fmt::format_to(ctx.out(), "BrtBeginSXDI"); + case xlsb_type::BrtEndSXDI: + return fmt::format_to(ctx.out(), "BrtEndSXDI"); + case xlsb_type::BrtBeginSXDIs: + return fmt::format_to(ctx.out(), "BrtBeginSXDIs"); + case xlsb_type::BrtEndSXDIs: + return fmt::format_to(ctx.out(), "BrtEndSXDIs"); + case xlsb_type::BrtBeginSXLI: + return fmt::format_to(ctx.out(), "BrtBeginSXLI"); + case xlsb_type::BrtEndSXLI: + return fmt::format_to(ctx.out(), "BrtEndSXLI"); + case xlsb_type::BrtBeginSXLIRws: + return fmt::format_to(ctx.out(), "BrtBeginSXLIRws"); + case xlsb_type::BrtEndSXLIRws: + return fmt::format_to(ctx.out(), "BrtEndSXLIRws"); + case xlsb_type::BrtBeginSXLICols: + return fmt::format_to(ctx.out(), "BrtBeginSXLICols"); + case xlsb_type::BrtEndSXLICols: + return fmt::format_to(ctx.out(), "BrtEndSXLICols"); + case xlsb_type::BrtBeginSXFormat: + return fmt::format_to(ctx.out(), "BrtBeginSXFormat"); + case xlsb_type::BrtEndSXFormat: + return fmt::format_to(ctx.out(), "BrtEndSXFormat"); + case xlsb_type::BrtBeginSXFormats: + return fmt::format_to(ctx.out(), "BrtBeginSXFormats"); + case xlsb_type::BrtEndSxFormats: + return fmt::format_to(ctx.out(), "BrtEndSxFormats"); + case xlsb_type::BrtBeginSxSelect: + return fmt::format_to(ctx.out(), "BrtBeginSxSelect"); + case xlsb_type::BrtEndSxSelect: + return fmt::format_to(ctx.out(), "BrtEndSxSelect"); + case xlsb_type::BrtBeginISXVDRws: + return fmt::format_to(ctx.out(), "BrtBeginISXVDRws"); + case xlsb_type::BrtEndISXVDRws: + return fmt::format_to(ctx.out(), "BrtEndISXVDRws"); + case xlsb_type::BrtBeginISXVDCols: + return fmt::format_to(ctx.out(), "BrtBeginISXVDCols"); + case xlsb_type::BrtEndISXVDCols: + return fmt::format_to(ctx.out(), "BrtEndISXVDCols"); + case xlsb_type::BrtEndSXLocation: + return fmt::format_to(ctx.out(), "BrtEndSXLocation"); + case xlsb_type::BrtBeginSXLocation: + return fmt::format_to(ctx.out(), "BrtBeginSXLocation"); + case xlsb_type::BrtEndSXView: + return fmt::format_to(ctx.out(), "BrtEndSXView"); + case xlsb_type::BrtBeginSXTHs: + return fmt::format_to(ctx.out(), "BrtBeginSXTHs"); + case xlsb_type::BrtEndSXTHs: + return fmt::format_to(ctx.out(), "BrtEndSXTHs"); + case xlsb_type::BrtBeginSXTH: + return fmt::format_to(ctx.out(), "BrtBeginSXTH"); + case xlsb_type::BrtEndSXTH: + return fmt::format_to(ctx.out(), "BrtEndSXTH"); + case xlsb_type::BrtBeginISXTHRws: + return fmt::format_to(ctx.out(), "BrtBeginISXTHRws"); + case xlsb_type::BrtEndISXTHRws: + return fmt::format_to(ctx.out(), "BrtEndISXTHRws"); + case xlsb_type::BrtBeginISXTHCols: + return fmt::format_to(ctx.out(), "BrtBeginISXTHCols"); + case xlsb_type::BrtEndISXTHCols: + return fmt::format_to(ctx.out(), "BrtEndISXTHCols"); + case xlsb_type::BrtBeginSXTDMPS: + return fmt::format_to(ctx.out(), "BrtBeginSXTDMPS"); + case xlsb_type::BrtEndSXTDMPs: + return fmt::format_to(ctx.out(), "BrtEndSXTDMPs"); + case xlsb_type::BrtBeginSXTDMP: + return fmt::format_to(ctx.out(), "BrtBeginSXTDMP"); + case xlsb_type::BrtEndSXTDMP: + return fmt::format_to(ctx.out(), "BrtEndSXTDMP"); + case xlsb_type::BrtBeginSXTHItems: + return fmt::format_to(ctx.out(), "BrtBeginSXTHItems"); + case xlsb_type::BrtEndSXTHItems: + return fmt::format_to(ctx.out(), "BrtEndSXTHItems"); + case xlsb_type::BrtBeginSXTHItem: + return fmt::format_to(ctx.out(), "BrtBeginSXTHItem"); + case xlsb_type::BrtEndSXTHItem: + return fmt::format_to(ctx.out(), "BrtEndSXTHItem"); + case xlsb_type::BrtBeginMetadata: + return fmt::format_to(ctx.out(), "BrtBeginMetadata"); + case xlsb_type::BrtEndMetadata: + return fmt::format_to(ctx.out(), "BrtEndMetadata"); + case xlsb_type::BrtBeginEsmdtinfo: + return fmt::format_to(ctx.out(), "BrtBeginEsmdtinfo"); + case xlsb_type::BrtMdtinfo: + return fmt::format_to(ctx.out(), "BrtMdtinfo"); + case xlsb_type::BrtEndEsmdtinfo: + return fmt::format_to(ctx.out(), "BrtEndEsmdtinfo"); + case xlsb_type::BrtBeginEsmdb: + return fmt::format_to(ctx.out(), "BrtBeginEsmdb"); + case xlsb_type::BrtEndEsmdb: + return fmt::format_to(ctx.out(), "BrtEndEsmdb"); + case xlsb_type::BrtBeginEsfmd: + return fmt::format_to(ctx.out(), "BrtBeginEsfmd"); + case xlsb_type::BrtEndEsfmd: + return fmt::format_to(ctx.out(), "BrtEndEsfmd"); + case xlsb_type::BrtBeginSingleCells: + return fmt::format_to(ctx.out(), "BrtBeginSingleCells"); + case xlsb_type::BrtEndSingleCells: + return fmt::format_to(ctx.out(), "BrtEndSingleCells"); + case xlsb_type::BrtBeginList: + return fmt::format_to(ctx.out(), "BrtBeginList"); + case xlsb_type::BrtEndList: + return fmt::format_to(ctx.out(), "BrtEndList"); + case xlsb_type::BrtBeginListCols: + return fmt::format_to(ctx.out(), "BrtBeginListCols"); + case xlsb_type::BrtEndListCols: + return fmt::format_to(ctx.out(), "BrtEndListCols"); + case xlsb_type::BrtBeginListCol: + return fmt::format_to(ctx.out(), "BrtBeginListCol"); + case xlsb_type::BrtEndListCol: + return fmt::format_to(ctx.out(), "BrtEndListCol"); + case xlsb_type::BrtBeginListXmlCPr: + return fmt::format_to(ctx.out(), "BrtBeginListXmlCPr"); + case xlsb_type::BrtEndListXmlCPr: + return fmt::format_to(ctx.out(), "BrtEndListXmlCPr"); + case xlsb_type::BrtListCCFmla: + return fmt::format_to(ctx.out(), "BrtListCCFmla"); + case xlsb_type::BrtListTrFmla: + return fmt::format_to(ctx.out(), "BrtListTrFmla"); + case xlsb_type::BrtBeginExternals: + return fmt::format_to(ctx.out(), "BrtBeginExternals"); + case xlsb_type::BrtEndExternals: + return fmt::format_to(ctx.out(), "BrtEndExternals"); + case xlsb_type::BrtSupBookSrc: + return fmt::format_to(ctx.out(), "BrtSupBookSrc"); + case xlsb_type::BrtSupSelf: + return fmt::format_to(ctx.out(), "BrtSupSelf"); + case xlsb_type::BrtSupSame: + return fmt::format_to(ctx.out(), "BrtSupSame"); + case xlsb_type::BrtSupTabs: + return fmt::format_to(ctx.out(), "BrtSupTabs"); + case xlsb_type::BrtBeginSupBook: + return fmt::format_to(ctx.out(), "BrtBeginSupBook"); + case xlsb_type::BrtPlaceholderName: + return fmt::format_to(ctx.out(), "BrtPlaceholderName"); + case xlsb_type::BrtExternSheet: + return fmt::format_to(ctx.out(), "BrtExternSheet"); + case xlsb_type::BrtExternTableStart: + return fmt::format_to(ctx.out(), "BrtExternTableStart"); + case xlsb_type::BrtExternTableEnd: + return fmt::format_to(ctx.out(), "BrtExternTableEnd"); + case xlsb_type::BrtExternRowHdr: + return fmt::format_to(ctx.out(), "BrtExternRowHdr"); + case xlsb_type::BrtExternCellBlank: + return fmt::format_to(ctx.out(), "BrtExternCellBlank"); + case xlsb_type::BrtExternCellReal: + return fmt::format_to(ctx.out(), "BrtExternCellReal"); + case xlsb_type::BrtExternCellBool: + return fmt::format_to(ctx.out(), "BrtExternCellBool"); + case xlsb_type::BrtExternCellError: + return fmt::format_to(ctx.out(), "BrtExternCellError"); + case xlsb_type::BrtExternCellString: + return fmt::format_to(ctx.out(), "BrtExternCellString"); + case xlsb_type::BrtBeginEsmdx: + return fmt::format_to(ctx.out(), "BrtBeginEsmdx"); + case xlsb_type::BrtEndEsmdx: + return fmt::format_to(ctx.out(), "BrtEndEsmdx"); + case xlsb_type::BrtBeginMdxSet: + return fmt::format_to(ctx.out(), "BrtBeginMdxSet"); + case xlsb_type::BrtEndMdxSet: + return fmt::format_to(ctx.out(), "BrtEndMdxSet"); + case xlsb_type::BrtBeginMdxMbrProp: + return fmt::format_to(ctx.out(), "BrtBeginMdxMbrProp"); + case xlsb_type::BrtEndMdxMbrProp: + return fmt::format_to(ctx.out(), "BrtEndMdxMbrProp"); + case xlsb_type::BrtBeginMdxKPI: + return fmt::format_to(ctx.out(), "BrtBeginMdxKPI"); + case xlsb_type::BrtEndMdxKPI: + return fmt::format_to(ctx.out(), "BrtEndMdxKPI"); + case xlsb_type::BrtBeginEsstr: + return fmt::format_to(ctx.out(), "BrtBeginEsstr"); + case xlsb_type::BrtEndEsstr: + return fmt::format_to(ctx.out(), "BrtEndEsstr"); + case xlsb_type::BrtBeginPRFItem: + return fmt::format_to(ctx.out(), "BrtBeginPRFItem"); + case xlsb_type::BrtEndPRFItem: + return fmt::format_to(ctx.out(), "BrtEndPRFItem"); + case xlsb_type::BrtBeginPivotCacheIDs: + return fmt::format_to(ctx.out(), "BrtBeginPivotCacheIDs"); + case xlsb_type::BrtEndPivotCacheIDs: + return fmt::format_to(ctx.out(), "BrtEndPivotCacheIDs"); + case xlsb_type::BrtBeginPivotCacheID: + return fmt::format_to(ctx.out(), "BrtBeginPivotCacheID"); + case xlsb_type::BrtEndPivotCacheID: + return fmt::format_to(ctx.out(), "BrtEndPivotCacheID"); + case xlsb_type::BrtBeginISXVIs: + return fmt::format_to(ctx.out(), "BrtBeginISXVIs"); + case xlsb_type::BrtEndISXVIs: + return fmt::format_to(ctx.out(), "BrtEndISXVIs"); + case xlsb_type::BrtBeginColInfos: + return fmt::format_to(ctx.out(), "BrtBeginColInfos"); + case xlsb_type::BrtEndColInfos: + return fmt::format_to(ctx.out(), "BrtEndColInfos"); + case xlsb_type::BrtBeginRwBrk: + return fmt::format_to(ctx.out(), "BrtBeginRwBrk"); + case xlsb_type::BrtEndRwBrk: + return fmt::format_to(ctx.out(), "BrtEndRwBrk"); + case xlsb_type::BrtBeginColBrk: + return fmt::format_to(ctx.out(), "BrtBeginColBrk"); + case xlsb_type::BrtEndColBrk: + return fmt::format_to(ctx.out(), "BrtEndColBrk"); + case xlsb_type::BrtBrk: + return fmt::format_to(ctx.out(), "BrtBrk"); + case xlsb_type::BrtUserBookView: + return fmt::format_to(ctx.out(), "BrtUserBookView"); + case xlsb_type::BrtInfo: + return fmt::format_to(ctx.out(), "BrtInfo"); + case xlsb_type::BrtCUsr: + return fmt::format_to(ctx.out(), "BrtCUsr"); + case xlsb_type::BrtUsr: + return fmt::format_to(ctx.out(), "BrtUsr"); + case xlsb_type::BrtBeginUsers: + return fmt::format_to(ctx.out(), "BrtBeginUsers"); + case xlsb_type::BrtEOF: + return fmt::format_to(ctx.out(), "BrtEOF"); + case xlsb_type::BrtUCR: + return fmt::format_to(ctx.out(), "BrtUCR"); + case xlsb_type::BrtRRInsDel: + return fmt::format_to(ctx.out(), "BrtRRInsDel"); + case xlsb_type::BrtRREndInsDel: + return fmt::format_to(ctx.out(), "BrtRREndInsDel"); + case xlsb_type::BrtRRMove: + return fmt::format_to(ctx.out(), "BrtRRMove"); + case xlsb_type::BrtRREndMove: + return fmt::format_to(ctx.out(), "BrtRREndMove"); + case xlsb_type::BrtRRChgCell: + return fmt::format_to(ctx.out(), "BrtRRChgCell"); + case xlsb_type::BrtRREndChgCell: + return fmt::format_to(ctx.out(), "BrtRREndChgCell"); + case xlsb_type::BrtRRHeader: + return fmt::format_to(ctx.out(), "BrtRRHeader"); + case xlsb_type::BrtRRUserView: + return fmt::format_to(ctx.out(), "BrtRRUserView"); + case xlsb_type::BrtRRRenSheet: + return fmt::format_to(ctx.out(), "BrtRRRenSheet"); + case xlsb_type::BrtRRInsertSh: + return fmt::format_to(ctx.out(), "BrtRRInsertSh"); + case xlsb_type::BrtRRDefName: + return fmt::format_to(ctx.out(), "BrtRRDefName"); + case xlsb_type::BrtRRNote: + return fmt::format_to(ctx.out(), "BrtRRNote"); + case xlsb_type::BrtRRConflict: + return fmt::format_to(ctx.out(), "BrtRRConflict"); + case xlsb_type::BrtRRTQSIF: + return fmt::format_to(ctx.out(), "BrtRRTQSIF"); + case xlsb_type::BrtRRFormat: + return fmt::format_to(ctx.out(), "BrtRRFormat"); + case xlsb_type::BrtRREndFormat: + return fmt::format_to(ctx.out(), "BrtRREndFormat"); + case xlsb_type::BrtRRAutoFmt: + return fmt::format_to(ctx.out(), "BrtRRAutoFmt"); + case xlsb_type::BrtBeginUserShViews: + return fmt::format_to(ctx.out(), "BrtBeginUserShViews"); + case xlsb_type::BrtBeginUserShView: + return fmt::format_to(ctx.out(), "BrtBeginUserShView"); + case xlsb_type::BrtEndUserShView: + return fmt::format_to(ctx.out(), "BrtEndUserShView"); + case xlsb_type::BrtEndUserShViews: + return fmt::format_to(ctx.out(), "BrtEndUserShViews"); + case xlsb_type::BrtArrFmla: + return fmt::format_to(ctx.out(), "BrtArrFmla"); + case xlsb_type::BrtShrFmla: + return fmt::format_to(ctx.out(), "BrtShrFmla"); + case xlsb_type::BrtTable: + return fmt::format_to(ctx.out(), "BrtTable"); + case xlsb_type::BrtBeginExtConnections: + return fmt::format_to(ctx.out(), "BrtBeginExtConnections"); + case xlsb_type::BrtEndExtConnections: + return fmt::format_to(ctx.out(), "BrtEndExtConnections"); + case xlsb_type::BrtBeginPCDCalcMems: + return fmt::format_to(ctx.out(), "BrtBeginPCDCalcMems"); + case xlsb_type::BrtEndPCDCalcMems: + return fmt::format_to(ctx.out(), "BrtEndPCDCalcMems"); + case xlsb_type::BrtBeginPCDCalcMem: + return fmt::format_to(ctx.out(), "BrtBeginPCDCalcMem"); + case xlsb_type::BrtEndPCDCalcMem: + return fmt::format_to(ctx.out(), "BrtEndPCDCalcMem"); + case xlsb_type::BrtBeginPCDHGLevels: + return fmt::format_to(ctx.out(), "BrtBeginPCDHGLevels"); + case xlsb_type::BrtEndPCDHGLevels: + return fmt::format_to(ctx.out(), "BrtEndPCDHGLevels"); + case xlsb_type::BrtBeginPCDHGLevel: + return fmt::format_to(ctx.out(), "BrtBeginPCDHGLevel"); + case xlsb_type::BrtEndPCDHGLevel: + return fmt::format_to(ctx.out(), "BrtEndPCDHGLevel"); + case xlsb_type::BrtBeginPCDHGLGroups: + return fmt::format_to(ctx.out(), "BrtBeginPCDHGLGroups"); + case xlsb_type::BrtEndPCDHGLGroups: + return fmt::format_to(ctx.out(), "BrtEndPCDHGLGroups"); + case xlsb_type::BrtBeginPCDHGLGroup: + return fmt::format_to(ctx.out(), "BrtBeginPCDHGLGroup"); + case xlsb_type::BrtEndPCDHGLGroup: + return fmt::format_to(ctx.out(), "BrtEndPCDHGLGroup"); + case xlsb_type::BrtBeginPCDHGLGMembers: + return fmt::format_to(ctx.out(), "BrtBeginPCDHGLGMembers"); + case xlsb_type::BrtEndPCDHGLGMembers: + return fmt::format_to(ctx.out(), "BrtEndPCDHGLGMembers"); + case xlsb_type::BrtBeginPCDHGLGMember: + return fmt::format_to(ctx.out(), "BrtBeginPCDHGLGMember"); + case xlsb_type::BrtEndPCDHGLGMember: + return fmt::format_to(ctx.out(), "BrtEndPCDHGLGMember"); + case xlsb_type::BrtBeginQSI: + return fmt::format_to(ctx.out(), "BrtBeginQSI"); + case xlsb_type::BrtEndQSI: + return fmt::format_to(ctx.out(), "BrtEndQSI"); + case xlsb_type::BrtBeginQSIR: + return fmt::format_to(ctx.out(), "BrtBeginQSIR"); + case xlsb_type::BrtEndQSIR: + return fmt::format_to(ctx.out(), "BrtEndQSIR"); + case xlsb_type::BrtBeginDeletedNames: + return fmt::format_to(ctx.out(), "BrtBeginDeletedNames"); + case xlsb_type::BrtEndDeletedNames: + return fmt::format_to(ctx.out(), "BrtEndDeletedNames"); + case xlsb_type::BrtBeginDeletedName: + return fmt::format_to(ctx.out(), "BrtBeginDeletedName"); + case xlsb_type::BrtEndDeletedName: + return fmt::format_to(ctx.out(), "BrtEndDeletedName"); + case xlsb_type::BrtBeginQSIFs: + return fmt::format_to(ctx.out(), "BrtBeginQSIFs"); + case xlsb_type::BrtEndQSIFs: + return fmt::format_to(ctx.out(), "BrtEndQSIFs"); + case xlsb_type::BrtBeginQSIF: + return fmt::format_to(ctx.out(), "BrtBeginQSIF"); + case xlsb_type::BrtEndQSIF: + return fmt::format_to(ctx.out(), "BrtEndQSIF"); + case xlsb_type::BrtBeginAutoSortScope: + return fmt::format_to(ctx.out(), "BrtBeginAutoSortScope"); + case xlsb_type::BrtEndAutoSortScope: + return fmt::format_to(ctx.out(), "BrtEndAutoSortScope"); + case xlsb_type::BrtBeginConditionalFormatting: + return fmt::format_to(ctx.out(), "BrtBeginConditionalFormatting"); + case xlsb_type::BrtEndConditionalFormatting: + return fmt::format_to(ctx.out(), "BrtEndConditionalFormatting"); + case xlsb_type::BrtBeginCFRule: + return fmt::format_to(ctx.out(), "BrtBeginCFRule"); + case xlsb_type::BrtEndCFRule: + return fmt::format_to(ctx.out(), "BrtEndCFRule"); + case xlsb_type::BrtBeginIconSet: + return fmt::format_to(ctx.out(), "BrtBeginIconSet"); + case xlsb_type::BrtEndIconSet: + return fmt::format_to(ctx.out(), "BrtEndIconSet"); + case xlsb_type::BrtBeginDatabar: + return fmt::format_to(ctx.out(), "BrtBeginDatabar"); + case xlsb_type::BrtEndDatabar: + return fmt::format_to(ctx.out(), "BrtEndDatabar"); + case xlsb_type::BrtBeginColorScale: + return fmt::format_to(ctx.out(), "BrtBeginColorScale"); + case xlsb_type::BrtEndColorScale: + return fmt::format_to(ctx.out(), "BrtEndColorScale"); + case xlsb_type::BrtCFVO: + return fmt::format_to(ctx.out(), "BrtCFVO"); + case xlsb_type::BrtExternValueMeta: + return fmt::format_to(ctx.out(), "BrtExternValueMeta"); + case xlsb_type::BrtBeginColorPalette: + return fmt::format_to(ctx.out(), "BrtBeginColorPalette"); + case xlsb_type::BrtEndColorPalette: + return fmt::format_to(ctx.out(), "BrtEndColorPalette"); + case xlsb_type::BrtIndexedColor: + return fmt::format_to(ctx.out(), "BrtIndexedColor"); + case xlsb_type::BrtMargins: + return fmt::format_to(ctx.out(), "BrtMargins"); + case xlsb_type::BrtPrintOptions: + return fmt::format_to(ctx.out(), "BrtPrintOptions"); + case xlsb_type::BrtPageSetup: + return fmt::format_to(ctx.out(), "BrtPageSetup"); + case xlsb_type::BrtBeginHeaderFooter: + return fmt::format_to(ctx.out(), "BrtBeginHeaderFooter"); + case xlsb_type::BrtEndHeaderFooter: + return fmt::format_to(ctx.out(), "BrtEndHeaderFooter"); + case xlsb_type::BrtBeginSXCrtFormat: + return fmt::format_to(ctx.out(), "BrtBeginSXCrtFormat"); + case xlsb_type::BrtEndSXCrtFormat: + return fmt::format_to(ctx.out(), "BrtEndSXCrtFormat"); + case xlsb_type::BrtBeginSXCrtFormats: + return fmt::format_to(ctx.out(), "BrtBeginSXCrtFormats"); + case xlsb_type::BrtEndSXCrtFormats: + return fmt::format_to(ctx.out(), "BrtEndSXCrtFormats"); + case xlsb_type::BrtWsFmtInfo: + return fmt::format_to(ctx.out(), "BrtWsFmtInfo"); + case xlsb_type::BrtBeginMgs: + return fmt::format_to(ctx.out(), "BrtBeginMgs"); + case xlsb_type::BrtEndMGs: + return fmt::format_to(ctx.out(), "BrtEndMGs"); + case xlsb_type::BrtBeginMGMaps: + return fmt::format_to(ctx.out(), "BrtBeginMGMaps"); + case xlsb_type::BrtEndMGMaps: + return fmt::format_to(ctx.out(), "BrtEndMGMaps"); + case xlsb_type::BrtBeginMG: + return fmt::format_to(ctx.out(), "BrtBeginMG"); + case xlsb_type::BrtEndMG: + return fmt::format_to(ctx.out(), "BrtEndMG"); + case xlsb_type::BrtBeginMap: + return fmt::format_to(ctx.out(), "BrtBeginMap"); + case xlsb_type::BrtEndMap: + return fmt::format_to(ctx.out(), "BrtEndMap"); + case xlsb_type::BrtHLink: + return fmt::format_to(ctx.out(), "BrtHLink"); + case xlsb_type::BrtBeginDCon: + return fmt::format_to(ctx.out(), "BrtBeginDCon"); + case xlsb_type::BrtEndDCon: + return fmt::format_to(ctx.out(), "BrtEndDCon"); + case xlsb_type::BrtBeginDRefs: + return fmt::format_to(ctx.out(), "BrtBeginDRefs"); + case xlsb_type::BrtEndDRefs: + return fmt::format_to(ctx.out(), "BrtEndDRefs"); + case xlsb_type::BrtDRef: + return fmt::format_to(ctx.out(), "BrtDRef"); + case xlsb_type::BrtBeginScenMan: + return fmt::format_to(ctx.out(), "BrtBeginScenMan"); + case xlsb_type::BrtEndScenMan: + return fmt::format_to(ctx.out(), "BrtEndScenMan"); + case xlsb_type::BrtBeginSct: + return fmt::format_to(ctx.out(), "BrtBeginSct"); + case xlsb_type::BrtEndSct: + return fmt::format_to(ctx.out(), "BrtEndSct"); + case xlsb_type::BrtSlc: + return fmt::format_to(ctx.out(), "BrtSlc"); + case xlsb_type::BrtBeginDXFs: + return fmt::format_to(ctx.out(), "BrtBeginDXFs"); + case xlsb_type::BrtEndDXFs: + return fmt::format_to(ctx.out(), "BrtEndDXFs"); + case xlsb_type::BrtDXF: + return fmt::format_to(ctx.out(), "BrtDXF"); + case xlsb_type::BrtBeginTableStyles: + return fmt::format_to(ctx.out(), "BrtBeginTableStyles"); + case xlsb_type::BrtEndTableStyles: + return fmt::format_to(ctx.out(), "BrtEndTableStyles"); + case xlsb_type::BrtBeginTableStyle: + return fmt::format_to(ctx.out(), "BrtBeginTableStyle"); + case xlsb_type::BrtEndTableStyle: + return fmt::format_to(ctx.out(), "BrtEndTableStyle"); + case xlsb_type::BrtTableStyleElement: + return fmt::format_to(ctx.out(), "BrtTableStyleElement"); + case xlsb_type::BrtTableStyleClient: + return fmt::format_to(ctx.out(), "BrtTableStyleClient"); + case xlsb_type::BrtBeginVolDeps: + return fmt::format_to(ctx.out(), "BrtBeginVolDeps"); + case xlsb_type::BrtEndVolDeps: + return fmt::format_to(ctx.out(), "BrtEndVolDeps"); + case xlsb_type::BrtBeginVolType: + return fmt::format_to(ctx.out(), "BrtBeginVolType"); + case xlsb_type::BrtEndVolType: + return fmt::format_to(ctx.out(), "BrtEndVolType"); + case xlsb_type::BrtBeginVolMain: + return fmt::format_to(ctx.out(), "BrtBeginVolMain"); + case xlsb_type::BrtEndVolMain: + return fmt::format_to(ctx.out(), "BrtEndVolMain"); + case xlsb_type::BrtBeginVolTopic: + return fmt::format_to(ctx.out(), "BrtBeginVolTopic"); + case xlsb_type::BrtEndVolTopic: + return fmt::format_to(ctx.out(), "BrtEndVolTopic"); + case xlsb_type::BrtVolSubtopic: + return fmt::format_to(ctx.out(), "BrtVolSubtopic"); + case xlsb_type::BrtVolRef: + return fmt::format_to(ctx.out(), "BrtVolRef"); + case xlsb_type::BrtVolNum: + return fmt::format_to(ctx.out(), "BrtVolNum"); + case xlsb_type::BrtVolErr: + return fmt::format_to(ctx.out(), "BrtVolErr"); + case xlsb_type::BrtVolStr: + return fmt::format_to(ctx.out(), "BrtVolStr"); + case xlsb_type::BrtVolBool: + return fmt::format_to(ctx.out(), "BrtVolBool"); + case xlsb_type::BrtBeginSortState: + return fmt::format_to(ctx.out(), "BrtBeginSortState"); + case xlsb_type::BrtEndSortState: + return fmt::format_to(ctx.out(), "BrtEndSortState"); + case xlsb_type::BrtBeginSortCond: + return fmt::format_to(ctx.out(), "BrtBeginSortCond"); + case xlsb_type::BrtEndSortCond: + return fmt::format_to(ctx.out(), "BrtEndSortCond"); + case xlsb_type::BrtBookProtection: + return fmt::format_to(ctx.out(), "BrtBookProtection"); + case xlsb_type::BrtSheetProtection: + return fmt::format_to(ctx.out(), "BrtSheetProtection"); + case xlsb_type::BrtRangeProtection: + return fmt::format_to(ctx.out(), "BrtRangeProtection"); + case xlsb_type::BrtPhoneticInfo: + return fmt::format_to(ctx.out(), "BrtPhoneticInfo"); + case xlsb_type::BrtBeginECTxtWiz: + return fmt::format_to(ctx.out(), "BrtBeginECTxtWiz"); + case xlsb_type::BrtEndECTxtWiz: + return fmt::format_to(ctx.out(), "BrtEndECTxtWiz"); + case xlsb_type::BrtBeginECTWFldInfoLst: + return fmt::format_to(ctx.out(), "BrtBeginECTWFldInfoLst"); + case xlsb_type::BrtEndECTWFldInfoLst: + return fmt::format_to(ctx.out(), "BrtEndECTWFldInfoLst"); + case xlsb_type::BrtBeginECTwFldInfo: + return fmt::format_to(ctx.out(), "BrtBeginECTwFldInfo"); + case xlsb_type::BrtFileSharing: + return fmt::format_to(ctx.out(), "BrtFileSharing"); + case xlsb_type::BrtOleSize: + return fmt::format_to(ctx.out(), "BrtOleSize"); + case xlsb_type::BrtDrawing: + return fmt::format_to(ctx.out(), "BrtDrawing"); + case xlsb_type::BrtLegacyDrawing: + return fmt::format_to(ctx.out(), "BrtLegacyDrawing"); + case xlsb_type::BrtLegacyDrawingHF: + return fmt::format_to(ctx.out(), "BrtLegacyDrawingHF"); + case xlsb_type::BrtWebOpt: + return fmt::format_to(ctx.out(), "BrtWebOpt"); + case xlsb_type::BrtBeginWebPubItems: + return fmt::format_to(ctx.out(), "BrtBeginWebPubItems"); + case xlsb_type::BrtEndWebPubItems: + return fmt::format_to(ctx.out(), "BrtEndWebPubItems"); + case xlsb_type::BrtBeginWebPubItem: + return fmt::format_to(ctx.out(), "BrtBeginWebPubItem"); + case xlsb_type::BrtEndWebPubItem: + return fmt::format_to(ctx.out(), "BrtEndWebPubItem"); + case xlsb_type::BrtBeginSXCondFmt: + return fmt::format_to(ctx.out(), "BrtBeginSXCondFmt"); + case xlsb_type::BrtEndSXCondFmt: + return fmt::format_to(ctx.out(), "BrtEndSXCondFmt"); + case xlsb_type::BrtBeginSXCondFmts: + return fmt::format_to(ctx.out(), "BrtBeginSXCondFmts"); + case xlsb_type::BrtEndSXCondFmts: + return fmt::format_to(ctx.out(), "BrtEndSXCondFmts"); + case xlsb_type::BrtBkHim: + return fmt::format_to(ctx.out(), "BrtBkHim"); + case xlsb_type::BrtColor: + return fmt::format_to(ctx.out(), "BrtColor"); + case xlsb_type::BrtBeginIndexedColors: + return fmt::format_to(ctx.out(), "BrtBeginIndexedColors"); + case xlsb_type::BrtEndIndexedColors: + return fmt::format_to(ctx.out(), "BrtEndIndexedColors"); + case xlsb_type::BrtBeginMRUColors: + return fmt::format_to(ctx.out(), "BrtBeginMRUColors"); + case xlsb_type::BrtEndMRUColors: + return fmt::format_to(ctx.out(), "BrtEndMRUColors"); + case xlsb_type::BrtMRUColor: + return fmt::format_to(ctx.out(), "BrtMRUColor"); + case xlsb_type::BrtBeginDVals: + return fmt::format_to(ctx.out(), "BrtBeginDVals"); + case xlsb_type::BrtEndDVals: + return fmt::format_to(ctx.out(), "BrtEndDVals"); + case xlsb_type::BrtSupNameStart: + return fmt::format_to(ctx.out(), "BrtSupNameStart"); + case xlsb_type::BrtSupNameValueStart: + return fmt::format_to(ctx.out(), "BrtSupNameValueStart"); + case xlsb_type::BrtSupNameValueEnd: + return fmt::format_to(ctx.out(), "BrtSupNameValueEnd"); + case xlsb_type::BrtSupNameNum: + return fmt::format_to(ctx.out(), "BrtSupNameNum"); + case xlsb_type::BrtSupNameErr: + return fmt::format_to(ctx.out(), "BrtSupNameErr"); + case xlsb_type::BrtSupNameSt: + return fmt::format_to(ctx.out(), "BrtSupNameSt"); + case xlsb_type::BrtSupNameNil: + return fmt::format_to(ctx.out(), "BrtSupNameNil"); + case xlsb_type::BrtSupNameBool: + return fmt::format_to(ctx.out(), "BrtSupNameBool"); + case xlsb_type::BrtSupNameFmla: + return fmt::format_to(ctx.out(), "BrtSupNameFmla"); + case xlsb_type::BrtSupNameBits: + return fmt::format_to(ctx.out(), "BrtSupNameBits"); + case xlsb_type::BrtSupNameEnd: + return fmt::format_to(ctx.out(), "BrtSupNameEnd"); + case xlsb_type::BrtEndSupBook: + return fmt::format_to(ctx.out(), "BrtEndSupBook"); + case xlsb_type::BrtCellSmartTagProperty: + return fmt::format_to(ctx.out(), "BrtCellSmartTagProperty"); + case xlsb_type::BrtBeginCellSmartTag: + return fmt::format_to(ctx.out(), "BrtBeginCellSmartTag"); + case xlsb_type::BrtEndCellSmartTag: + return fmt::format_to(ctx.out(), "BrtEndCellSmartTag"); + case xlsb_type::BrtBeginCellSmartTags: + return fmt::format_to(ctx.out(), "BrtBeginCellSmartTags"); + case xlsb_type::BrtEndCellSmartTags: + return fmt::format_to(ctx.out(), "BrtEndCellSmartTags"); + case xlsb_type::BrtBeginSmartTags: + return fmt::format_to(ctx.out(), "BrtBeginSmartTags"); + case xlsb_type::BrtEndSmartTags: + return fmt::format_to(ctx.out(), "BrtEndSmartTags"); + case xlsb_type::BrtSmartTagType: + return fmt::format_to(ctx.out(), "BrtSmartTagType"); + case xlsb_type::BrtBeginSmartTagTypes: + return fmt::format_to(ctx.out(), "BrtBeginSmartTagTypes"); + case xlsb_type::BrtEndSmartTagTypes: + return fmt::format_to(ctx.out(), "BrtEndSmartTagTypes"); + case xlsb_type::BrtBeginSXFilters: + return fmt::format_to(ctx.out(), "BrtBeginSXFilters"); + case xlsb_type::BrtEndSXFilters: + return fmt::format_to(ctx.out(), "BrtEndSXFilters"); + case xlsb_type::BrtBeginSXFILTER: + return fmt::format_to(ctx.out(), "BrtBeginSXFILTER"); + case xlsb_type::BrtEndSXFilter: + return fmt::format_to(ctx.out(), "BrtEndSXFilter"); + case xlsb_type::BrtBeginFills: + return fmt::format_to(ctx.out(), "BrtBeginFills"); + case xlsb_type::BrtEndFills: + return fmt::format_to(ctx.out(), "BrtEndFills"); + case xlsb_type::BrtBeginCellWatches: + return fmt::format_to(ctx.out(), "BrtBeginCellWatches"); + case xlsb_type::BrtEndCellWatches: + return fmt::format_to(ctx.out(), "BrtEndCellWatches"); + case xlsb_type::BrtCellWatch: + return fmt::format_to(ctx.out(), "BrtCellWatch"); + case xlsb_type::BrtBeginCRErrs: + return fmt::format_to(ctx.out(), "BrtBeginCRErrs"); + case xlsb_type::BrtEndCRErrs: + return fmt::format_to(ctx.out(), "BrtEndCRErrs"); + case xlsb_type::BrtCrashRecErr: + return fmt::format_to(ctx.out(), "BrtCrashRecErr"); + case xlsb_type::BrtBeginFonts: + return fmt::format_to(ctx.out(), "BrtBeginFonts"); + case xlsb_type::BrtEndFonts: + return fmt::format_to(ctx.out(), "BrtEndFonts"); + case xlsb_type::BrtBeginBorders: + return fmt::format_to(ctx.out(), "BrtBeginBorders"); + case xlsb_type::BrtEndBorders: + return fmt::format_to(ctx.out(), "BrtEndBorders"); + case xlsb_type::BrtBeginFmts: + return fmt::format_to(ctx.out(), "BrtBeginFmts"); + case xlsb_type::BrtEndFmts: + return fmt::format_to(ctx.out(), "BrtEndFmts"); + case xlsb_type::BrtBeginCellXFs: + return fmt::format_to(ctx.out(), "BrtBeginCellXFs"); + case xlsb_type::BrtEndCellXFs: + return fmt::format_to(ctx.out(), "BrtEndCellXFs"); + case xlsb_type::BrtBeginStyles: + return fmt::format_to(ctx.out(), "BrtBeginStyles"); + case xlsb_type::BrtEndStyles: + return fmt::format_to(ctx.out(), "BrtEndStyles"); + case xlsb_type::BrtBigName: + return fmt::format_to(ctx.out(), "BrtBigName"); + case xlsb_type::BrtBeginCellStyleXFs: + return fmt::format_to(ctx.out(), "BrtBeginCellStyleXFs"); + case xlsb_type::BrtEndCellStyleXFs: + return fmt::format_to(ctx.out(), "BrtEndCellStyleXFs"); + case xlsb_type::BrtBeginComments: + return fmt::format_to(ctx.out(), "BrtBeginComments"); + case xlsb_type::BrtEndComments: + return fmt::format_to(ctx.out(), "BrtEndComments"); + case xlsb_type::BrtBeginCommentAuthors: + return fmt::format_to(ctx.out(), "BrtBeginCommentAuthors"); + case xlsb_type::BrtEndCommentAuthors: + return fmt::format_to(ctx.out(), "BrtEndCommentAuthors"); + case xlsb_type::BrtCommentAuthor: + return fmt::format_to(ctx.out(), "BrtCommentAuthor"); + case xlsb_type::BrtBeginCommentList: + return fmt::format_to(ctx.out(), "BrtBeginCommentList"); + case xlsb_type::BrtEndCommentList: + return fmt::format_to(ctx.out(), "BrtEndCommentList"); + case xlsb_type::BrtBeginComment: + return fmt::format_to(ctx.out(), "BrtBeginComment"); + case xlsb_type::BrtEndComment: + return fmt::format_to(ctx.out(), "BrtEndComment"); + case xlsb_type::BrtCommentText: + return fmt::format_to(ctx.out(), "BrtCommentText"); + case xlsb_type::BrtBeginOleObjects: + return fmt::format_to(ctx.out(), "BrtBeginOleObjects"); + case xlsb_type::BrtOleObject: + return fmt::format_to(ctx.out(), "BrtOleObject"); + case xlsb_type::BrtEndOleObjects: + return fmt::format_to(ctx.out(), "BrtEndOleObjects"); + case xlsb_type::BrtBeginSxrules: + return fmt::format_to(ctx.out(), "BrtBeginSxrules"); + case xlsb_type::BrtEndSxRules: + return fmt::format_to(ctx.out(), "BrtEndSxRules"); + case xlsb_type::BrtBeginActiveXControls: + return fmt::format_to(ctx.out(), "BrtBeginActiveXControls"); + case xlsb_type::BrtActiveX: + return fmt::format_to(ctx.out(), "BrtActiveX"); + case xlsb_type::BrtEndActiveXControls: + return fmt::format_to(ctx.out(), "BrtEndActiveXControls"); + case xlsb_type::BrtBeginPCDSDTCEMembersSortBy: + return fmt::format_to(ctx.out(), "BrtBeginPCDSDTCEMembersSortBy"); + case xlsb_type::BrtBeginCellIgnoreECs: + return fmt::format_to(ctx.out(), "BrtBeginCellIgnoreECs"); + case xlsb_type::BrtCellIgnoreEC: + return fmt::format_to(ctx.out(), "BrtCellIgnoreEC"); + case xlsb_type::BrtEndCellIgnoreECs: + return fmt::format_to(ctx.out(), "BrtEndCellIgnoreECs"); + case xlsb_type::BrtCsProp: + return fmt::format_to(ctx.out(), "BrtCsProp"); + case xlsb_type::BrtCsPageSetup: + return fmt::format_to(ctx.out(), "BrtCsPageSetup"); + case xlsb_type::BrtBeginUserCsViews: + return fmt::format_to(ctx.out(), "BrtBeginUserCsViews"); + case xlsb_type::BrtEndUserCsViews: + return fmt::format_to(ctx.out(), "BrtEndUserCsViews"); + case xlsb_type::BrtBeginUserCsView: + return fmt::format_to(ctx.out(), "BrtBeginUserCsView"); + case xlsb_type::BrtEndUserCsView: + return fmt::format_to(ctx.out(), "BrtEndUserCsView"); + case xlsb_type::BrtBeginPcdSFCIEntries: + return fmt::format_to(ctx.out(), "BrtBeginPcdSFCIEntries"); + case xlsb_type::BrtEndPCDSFCIEntries: + return fmt::format_to(ctx.out(), "BrtEndPCDSFCIEntries"); + case xlsb_type::BrtPCDSFCIEntry: + return fmt::format_to(ctx.out(), "BrtPCDSFCIEntry"); + case xlsb_type::BrtBeginListParts: + return fmt::format_to(ctx.out(), "BrtBeginListParts"); + case xlsb_type::BrtListPart: + return fmt::format_to(ctx.out(), "BrtListPart"); + case xlsb_type::BrtEndListParts: + return fmt::format_to(ctx.out(), "BrtEndListParts"); + case xlsb_type::BrtSheetCalcProp: + return fmt::format_to(ctx.out(), "BrtSheetCalcProp"); + case xlsb_type::BrtBeginFnGroup: + return fmt::format_to(ctx.out(), "BrtBeginFnGroup"); + case xlsb_type::BrtFnGroup: + return fmt::format_to(ctx.out(), "BrtFnGroup"); + case xlsb_type::BrtEndFnGroup: + return fmt::format_to(ctx.out(), "BrtEndFnGroup"); + case xlsb_type::BrtSupAddin: + return fmt::format_to(ctx.out(), "BrtSupAddin"); + case xlsb_type::BrtSXTDMPOrder: + return fmt::format_to(ctx.out(), "BrtSXTDMPOrder"); + case xlsb_type::BrtCsProtection: + return fmt::format_to(ctx.out(), "BrtCsProtection"); + case xlsb_type::BrtBeginWsSortMap: + return fmt::format_to(ctx.out(), "BrtBeginWsSortMap"); + case xlsb_type::BrtEndWsSortMap: + return fmt::format_to(ctx.out(), "BrtEndWsSortMap"); + case xlsb_type::BrtBeginRRSort: + return fmt::format_to(ctx.out(), "BrtBeginRRSort"); + case xlsb_type::BrtEndRRSort: + return fmt::format_to(ctx.out(), "BrtEndRRSort"); + case xlsb_type::BrtRRSortItem: + return fmt::format_to(ctx.out(), "BrtRRSortItem"); + case xlsb_type::BrtFileSharingIso: + return fmt::format_to(ctx.out(), "BrtFileSharingIso"); + case xlsb_type::BrtBookProtectionIso: + return fmt::format_to(ctx.out(), "BrtBookProtectionIso"); + case xlsb_type::BrtSheetProtectionIso: + return fmt::format_to(ctx.out(), "BrtSheetProtectionIso"); + case xlsb_type::BrtCsProtectionIso: + return fmt::format_to(ctx.out(), "BrtCsProtectionIso"); + case xlsb_type::BrtRangeProtectionIso: + return fmt::format_to(ctx.out(), "BrtRangeProtectionIso"); + case xlsb_type::BrtDValList: + return fmt::format_to(ctx.out(), "BrtDValList"); + case xlsb_type::BrtRwDescent: + return fmt::format_to(ctx.out(), "BrtRwDescent"); + case xlsb_type::BrtKnownFonts: + return fmt::format_to(ctx.out(), "BrtKnownFonts"); + case xlsb_type::BrtBeginSXTupleSet: + return fmt::format_to(ctx.out(), "BrtBeginSXTupleSet"); + case xlsb_type::BrtEndSXTupleSet: + return fmt::format_to(ctx.out(), "BrtEndSXTupleSet"); + case xlsb_type::BrtBeginSXTupleSetHeader: + return fmt::format_to(ctx.out(), "BrtBeginSXTupleSetHeader"); + case xlsb_type::BrtEndSXTupleSetHeader: + return fmt::format_to(ctx.out(), "BrtEndSXTupleSetHeader"); + case xlsb_type::BrtSXTupleSetHeaderItem: + return fmt::format_to(ctx.out(), "BrtSXTupleSetHeaderItem"); + case xlsb_type::BrtBeginSXTupleSetData: + return fmt::format_to(ctx.out(), "BrtBeginSXTupleSetData"); + case xlsb_type::BrtEndSXTupleSetData: + return fmt::format_to(ctx.out(), "BrtEndSXTupleSetData"); + case xlsb_type::BrtBeginSXTupleSetRow: + return fmt::format_to(ctx.out(), "BrtBeginSXTupleSetRow"); + case xlsb_type::BrtEndSXTupleSetRow: + return fmt::format_to(ctx.out(), "BrtEndSXTupleSetRow"); + case xlsb_type::BrtSXTupleSetRowItem: + return fmt::format_to(ctx.out(), "BrtSXTupleSetRowItem"); + case xlsb_type::BrtNameExt: + return fmt::format_to(ctx.out(), "BrtNameExt"); + case xlsb_type::BrtPCDH14: + return fmt::format_to(ctx.out(), "BrtPCDH14"); + case xlsb_type::BrtBeginPCDCalcMem14: + return fmt::format_to(ctx.out(), "BrtBeginPCDCalcMem14"); + case xlsb_type::BrtEndPCDCalcMem14: + return fmt::format_to(ctx.out(), "BrtEndPCDCalcMem14"); + case xlsb_type::BrtSXTH14: + return fmt::format_to(ctx.out(), "BrtSXTH14"); + case xlsb_type::BrtBeginSparklineGroup: + return fmt::format_to(ctx.out(), "BrtBeginSparklineGroup"); + case xlsb_type::BrtEndSparklineGroup: + return fmt::format_to(ctx.out(), "BrtEndSparklineGroup"); + case xlsb_type::BrtSparkline: + return fmt::format_to(ctx.out(), "BrtSparkline"); + case xlsb_type::BrtSXDI14: + return fmt::format_to(ctx.out(), "BrtSXDI14"); + case xlsb_type::BrtWsFmtInfoEx14: + return fmt::format_to(ctx.out(), "BrtWsFmtInfoEx14"); + case xlsb_type::BrtBeginConditionalFormatting14: + return fmt::format_to(ctx.out(), "BrtBeginConditionalFormatting14"); + case xlsb_type::BrtEndConditionalFormatting14: + return fmt::format_to(ctx.out(), "BrtEndConditionalFormatting14"); + case xlsb_type::BrtBeginCFRule14: + return fmt::format_to(ctx.out(), "BrtBeginCFRule14"); + case xlsb_type::BrtEndCFRule14: + return fmt::format_to(ctx.out(), "BrtEndCFRule14"); + case xlsb_type::BrtCFVO14: + return fmt::format_to(ctx.out(), "BrtCFVO14"); + case xlsb_type::BrtBeginDatabar14: + return fmt::format_to(ctx.out(), "BrtBeginDatabar14"); + case xlsb_type::BrtBeginIconSet14: + return fmt::format_to(ctx.out(), "BrtBeginIconSet14"); + case xlsb_type::BrtDVal14: + return fmt::format_to(ctx.out(), "BrtDVal14"); + case xlsb_type::BrtBeginDVals14: + return fmt::format_to(ctx.out(), "BrtBeginDVals14"); + case xlsb_type::BrtColor14: + return fmt::format_to(ctx.out(), "BrtColor14"); + case xlsb_type::BrtBeginSparklines: + return fmt::format_to(ctx.out(), "BrtBeginSparklines"); + case xlsb_type::BrtEndSparklines: + return fmt::format_to(ctx.out(), "BrtEndSparklines"); + case xlsb_type::BrtBeginSparklineGroups: + return fmt::format_to(ctx.out(), "BrtBeginSparklineGroups"); + case xlsb_type::BrtEndSparklineGroups: + return fmt::format_to(ctx.out(), "BrtEndSparklineGroups"); + case xlsb_type::BrtSXVD14: + return fmt::format_to(ctx.out(), "BrtSXVD14"); + case xlsb_type::BrtBeginSxView14: + return fmt::format_to(ctx.out(), "BrtBeginSxView14"); + case xlsb_type::BrtEndSxView14: + return fmt::format_to(ctx.out(), "BrtEndSxView14"); + case xlsb_type::BrtBeginSXView16: + return fmt::format_to(ctx.out(), "BrtBeginSXView16"); + case xlsb_type::BrtEndSXView16: + return fmt::format_to(ctx.out(), "BrtEndSXView16"); + case xlsb_type::BrtBeginPCD14: + return fmt::format_to(ctx.out(), "BrtBeginPCD14"); + case xlsb_type::BrtEndPCD14: + return fmt::format_to(ctx.out(), "BrtEndPCD14"); + case xlsb_type::BrtBeginExtConn14: + return fmt::format_to(ctx.out(), "BrtBeginExtConn14"); + case xlsb_type::BrtEndExtConn14: + return fmt::format_to(ctx.out(), "BrtEndExtConn14"); + case xlsb_type::BrtBeginSlicerCacheIDs: + return fmt::format_to(ctx.out(), "BrtBeginSlicerCacheIDs"); + case xlsb_type::BrtEndSlicerCacheIDs: + return fmt::format_to(ctx.out(), "BrtEndSlicerCacheIDs"); + case xlsb_type::BrtBeginSlicerCacheID: + return fmt::format_to(ctx.out(), "BrtBeginSlicerCacheID"); + case xlsb_type::BrtEndSlicerCacheID: + return fmt::format_to(ctx.out(), "BrtEndSlicerCacheID"); + case xlsb_type::BrtBeginSlicerCache: + return fmt::format_to(ctx.out(), "BrtBeginSlicerCache"); + case xlsb_type::BrtEndSlicerCache: + return fmt::format_to(ctx.out(), "BrtEndSlicerCache"); + case xlsb_type::BrtBeginSlicerCacheDef: + return fmt::format_to(ctx.out(), "BrtBeginSlicerCacheDef"); + case xlsb_type::BrtEndSlicerCacheDef: + return fmt::format_to(ctx.out(), "BrtEndSlicerCacheDef"); + case xlsb_type::BrtBeginSlicersEx: + return fmt::format_to(ctx.out(), "BrtBeginSlicersEx"); + case xlsb_type::BrtEndSlicersEx: + return fmt::format_to(ctx.out(), "BrtEndSlicersEx"); + case xlsb_type::BrtBeginSlicerEx: + return fmt::format_to(ctx.out(), "BrtBeginSlicerEx"); + case xlsb_type::BrtEndSlicerEx: + return fmt::format_to(ctx.out(), "BrtEndSlicerEx"); + case xlsb_type::BrtBeginSlicer: + return fmt::format_to(ctx.out(), "BrtBeginSlicer"); + case xlsb_type::BrtEndSlicer: + return fmt::format_to(ctx.out(), "BrtEndSlicer"); + case xlsb_type::BrtSlicerCachePivotTables: + return fmt::format_to(ctx.out(), "BrtSlicerCachePivotTables"); + case xlsb_type::BrtBeginSlicerCacheOlapImpl: + return fmt::format_to(ctx.out(), "BrtBeginSlicerCacheOlapImpl"); + case xlsb_type::BrtEndSlicerCacheOlapImpl: + return fmt::format_to(ctx.out(), "BrtEndSlicerCacheOlapImpl"); + case xlsb_type::BrtBeginSlicerCacheLevelsData: + return fmt::format_to(ctx.out(), "BrtBeginSlicerCacheLevelsData"); + case xlsb_type::BrtEndSlicerCacheLevelsData: + return fmt::format_to(ctx.out(), "BrtEndSlicerCacheLevelsData"); + case xlsb_type::BrtBeginSlicerCacheLevelData: + return fmt::format_to(ctx.out(), "BrtBeginSlicerCacheLevelData"); + case xlsb_type::BrtEndSlicerCacheLevelData: + return fmt::format_to(ctx.out(), "BrtEndSlicerCacheLevelData"); + case xlsb_type::BrtBeginSlicerCacheSiRanges: + return fmt::format_to(ctx.out(), "BrtBeginSlicerCacheSiRanges"); + case xlsb_type::BrtEndSlicerCacheSiRanges: + return fmt::format_to(ctx.out(), "BrtEndSlicerCacheSiRanges"); + case xlsb_type::BrtBeginSlicerCacheSiRange: + return fmt::format_to(ctx.out(), "BrtBeginSlicerCacheSiRange"); + case xlsb_type::BrtEndSlicerCacheSiRange: + return fmt::format_to(ctx.out(), "BrtEndSlicerCacheSiRange"); + case xlsb_type::BrtSlicerCacheOlapItem: + return fmt::format_to(ctx.out(), "BrtSlicerCacheOlapItem"); + case xlsb_type::BrtBeginSlicerCacheSelections: + return fmt::format_to(ctx.out(), "BrtBeginSlicerCacheSelections"); + case xlsb_type::BrtSlicerCacheSelection: + return fmt::format_to(ctx.out(), "BrtSlicerCacheSelection"); + case xlsb_type::BrtEndSlicerCacheSelections: + return fmt::format_to(ctx.out(), "BrtEndSlicerCacheSelections"); + case xlsb_type::BrtBeginSlicerCacheNative: + return fmt::format_to(ctx.out(), "BrtBeginSlicerCacheNative"); + case xlsb_type::BrtEndSlicerCacheNative: + return fmt::format_to(ctx.out(), "BrtEndSlicerCacheNative"); + case xlsb_type::BrtSlicerCacheNativeItem: + return fmt::format_to(ctx.out(), "BrtSlicerCacheNativeItem"); + case xlsb_type::BrtRangeProtection14: + return fmt::format_to(ctx.out(), "BrtRangeProtection14"); + case xlsb_type::BrtRangeProtectionIso14: + return fmt::format_to(ctx.out(), "BrtRangeProtectionIso14"); + case xlsb_type::BrtCellIgnoreEC14: + return fmt::format_to(ctx.out(), "BrtCellIgnoreEC14"); + case xlsb_type::BrtList14: + return fmt::format_to(ctx.out(), "BrtList14"); + case xlsb_type::BrtCFIcon: + return fmt::format_to(ctx.out(), "BrtCFIcon"); + case xlsb_type::BrtBeginSlicerCachesPivotCacheIDs: + return fmt::format_to(ctx.out(), "BrtBeginSlicerCachesPivotCacheIDs"); + case xlsb_type::BrtEndSlicerCachesPivotCacheIDs: + return fmt::format_to(ctx.out(), "BrtEndSlicerCachesPivotCacheIDs"); + case xlsb_type::BrtBeginSlicers: + return fmt::format_to(ctx.out(), "BrtBeginSlicers"); + case xlsb_type::BrtEndSlicers: + return fmt::format_to(ctx.out(), "BrtEndSlicers"); + case xlsb_type::BrtWbProp14: + return fmt::format_to(ctx.out(), "BrtWbProp14"); + case xlsb_type::BrtBeginSXEdit: + return fmt::format_to(ctx.out(), "BrtBeginSXEdit"); + case xlsb_type::BrtEndSXEdit: + return fmt::format_to(ctx.out(), "BrtEndSXEdit"); + case xlsb_type::BrtBeginSXEdits: + return fmt::format_to(ctx.out(), "BrtBeginSXEdits"); + case xlsb_type::BrtEndSXEdits: + return fmt::format_to(ctx.out(), "BrtEndSXEdits"); + case xlsb_type::BrtBeginSXChange: + return fmt::format_to(ctx.out(), "BrtBeginSXChange"); + case xlsb_type::BrtEndSXChange: + return fmt::format_to(ctx.out(), "BrtEndSXChange"); + case xlsb_type::BrtBeginSXChanges: + return fmt::format_to(ctx.out(), "BrtBeginSXChanges"); + case xlsb_type::BrtEndSXChanges: + return fmt::format_to(ctx.out(), "BrtEndSXChanges"); + case xlsb_type::BrtSXTupleItems: + return fmt::format_to(ctx.out(), "BrtSXTupleItems"); + case xlsb_type::BrtBeginSlicerStyle: + return fmt::format_to(ctx.out(), "BrtBeginSlicerStyle"); + case xlsb_type::BrtEndSlicerStyle: + return fmt::format_to(ctx.out(), "BrtEndSlicerStyle"); + case xlsb_type::BrtSlicerStyleElement: + return fmt::format_to(ctx.out(), "BrtSlicerStyleElement"); + case xlsb_type::BrtBeginStyleSheetExt14: + return fmt::format_to(ctx.out(), "BrtBeginStyleSheetExt14"); + case xlsb_type::BrtEndStyleSheetExt14: + return fmt::format_to(ctx.out(), "BrtEndStyleSheetExt14"); + case xlsb_type::BrtBeginSlicerCachesPivotCacheID: + return fmt::format_to(ctx.out(), "BrtBeginSlicerCachesPivotCacheID"); + case xlsb_type::BrtEndSlicerCachesPivotCacheID: + return fmt::format_to(ctx.out(), "BrtEndSlicerCachesPivotCacheID"); + case xlsb_type::BrtBeginConditionalFormattings: + return fmt::format_to(ctx.out(), "BrtBeginConditionalFormattings"); + case xlsb_type::BrtEndConditionalFormattings: + return fmt::format_to(ctx.out(), "BrtEndConditionalFormattings"); + case xlsb_type::BrtBeginPCDCalcMemExt: + return fmt::format_to(ctx.out(), "BrtBeginPCDCalcMemExt"); + case xlsb_type::BrtEndPCDCalcMemExt: + return fmt::format_to(ctx.out(), "BrtEndPCDCalcMemExt"); + case xlsb_type::BrtBeginPCDCalcMemsExt: + return fmt::format_to(ctx.out(), "BrtBeginPCDCalcMemsExt"); + case xlsb_type::BrtEndPCDCalcMemsExt: + return fmt::format_to(ctx.out(), "BrtEndPCDCalcMemsExt"); + case xlsb_type::BrtPCDField14: + return fmt::format_to(ctx.out(), "BrtPCDField14"); + case xlsb_type::BrtBeginSlicerStyles: + return fmt::format_to(ctx.out(), "BrtBeginSlicerStyles"); + case xlsb_type::BrtEndSlicerStyles: + return fmt::format_to(ctx.out(), "BrtEndSlicerStyles"); + case xlsb_type::BrtBeginSlicerStyleElements: + return fmt::format_to(ctx.out(), "BrtBeginSlicerStyleElements"); + case xlsb_type::BrtEndSlicerStyleElements: + return fmt::format_to(ctx.out(), "BrtEndSlicerStyleElements"); + case xlsb_type::BrtCFRuleExt: + return fmt::format_to(ctx.out(), "BrtCFRuleExt"); + case xlsb_type::BrtBeginSXCondFmt14: + return fmt::format_to(ctx.out(), "BrtBeginSXCondFmt14"); + case xlsb_type::BrtEndSXCondFmt14: + return fmt::format_to(ctx.out(), "BrtEndSXCondFmt14"); + case xlsb_type::BrtBeginSXCondFmts14: + return fmt::format_to(ctx.out(), "BrtBeginSXCondFmts14"); + case xlsb_type::BrtEndSXCondFmts14: + return fmt::format_to(ctx.out(), "BrtEndSXCondFmts14"); + case xlsb_type::BrtBeginSortCond14: + return fmt::format_to(ctx.out(), "BrtBeginSortCond14"); + case xlsb_type::BrtEndSortCond14: + return fmt::format_to(ctx.out(), "BrtEndSortCond14"); + case xlsb_type::BrtEndDVals14: + return fmt::format_to(ctx.out(), "BrtEndDVals14"); + case xlsb_type::BrtEndIconSet14: + return fmt::format_to(ctx.out(), "BrtEndIconSet14"); + case xlsb_type::BrtEndDatabar14: + return fmt::format_to(ctx.out(), "BrtEndDatabar14"); + case xlsb_type::BrtBeginColorScale14: + return fmt::format_to(ctx.out(), "BrtBeginColorScale14"); + case xlsb_type::BrtEndColorScale14: + return fmt::format_to(ctx.out(), "BrtEndColorScale14"); + case xlsb_type::BrtBeginSxrules14: + return fmt::format_to(ctx.out(), "BrtBeginSxrules14"); + case xlsb_type::BrtEndSxrules14: + return fmt::format_to(ctx.out(), "BrtEndSxrules14"); + case xlsb_type::BrtBeginPRule14: + return fmt::format_to(ctx.out(), "BrtBeginPRule14"); + case xlsb_type::BrtEndPRule14: + return fmt::format_to(ctx.out(), "BrtEndPRule14"); + case xlsb_type::BrtBeginPRFilters14: + return fmt::format_to(ctx.out(), "BrtBeginPRFilters14"); + case xlsb_type::BrtEndPRFilters14: + return fmt::format_to(ctx.out(), "BrtEndPRFilters14"); + case xlsb_type::BrtBeginPRFilter14: + return fmt::format_to(ctx.out(), "BrtBeginPRFilter14"); + case xlsb_type::BrtEndPRFilter14: + return fmt::format_to(ctx.out(), "BrtEndPRFilter14"); + case xlsb_type::BrtBeginPRFItem14: + return fmt::format_to(ctx.out(), "BrtBeginPRFItem14"); + case xlsb_type::BrtEndPRFItem14: + return fmt::format_to(ctx.out(), "BrtEndPRFItem14"); + case xlsb_type::BrtBeginCellIgnoreECs14: + return fmt::format_to(ctx.out(), "BrtBeginCellIgnoreECs14"); + case xlsb_type::BrtEndCellIgnoreECs14: + return fmt::format_to(ctx.out(), "BrtEndCellIgnoreECs14"); + case xlsb_type::BrtDxf14: + return fmt::format_to(ctx.out(), "BrtDxf14"); + case xlsb_type::BrtBeginDxF14s: + return fmt::format_to(ctx.out(), "BrtBeginDxF14s"); + case xlsb_type::BrtEndDxf14s: + return fmt::format_to(ctx.out(), "BrtEndDxf14s"); + case xlsb_type::BrtFilter14: + return fmt::format_to(ctx.out(), "BrtFilter14"); + case xlsb_type::BrtBeginCustomFilters14: + return fmt::format_to(ctx.out(), "BrtBeginCustomFilters14"); + case xlsb_type::BrtCustomFilter14: + return fmt::format_to(ctx.out(), "BrtCustomFilter14"); + case xlsb_type::BrtIconFilter14: + return fmt::format_to(ctx.out(), "BrtIconFilter14"); + case xlsb_type::BrtPivotCacheConnectionName: + return fmt::format_to(ctx.out(), "BrtPivotCacheConnectionName"); + case xlsb_type::BrtBeginDecoupledPivotCacheIDs: + return fmt::format_to(ctx.out(), "BrtBeginDecoupledPivotCacheIDs"); + case xlsb_type::BrtEndDecoupledPivotCacheIDs: + return fmt::format_to(ctx.out(), "BrtEndDecoupledPivotCacheIDs"); + case xlsb_type::BrtDecoupledPivotCacheID: + return fmt::format_to(ctx.out(), "BrtDecoupledPivotCacheID"); + case xlsb_type::BrtBeginPivotTableRefs: + return fmt::format_to(ctx.out(), "BrtBeginPivotTableRefs"); + case xlsb_type::BrtEndPivotTableRefs: + return fmt::format_to(ctx.out(), "BrtEndPivotTableRefs"); + case xlsb_type::BrtPivotTableRef: + return fmt::format_to(ctx.out(), "BrtPivotTableRef"); + case xlsb_type::BrtSlicerCacheBookPivotTables: + return fmt::format_to(ctx.out(), "BrtSlicerCacheBookPivotTables"); + case xlsb_type::BrtBeginSxvcells: + return fmt::format_to(ctx.out(), "BrtBeginSxvcells"); + case xlsb_type::BrtEndSxvcells: + return fmt::format_to(ctx.out(), "BrtEndSxvcells"); + case xlsb_type::BrtBeginSxRow: + return fmt::format_to(ctx.out(), "BrtBeginSxRow"); + case xlsb_type::BrtEndSxRow: + return fmt::format_to(ctx.out(), "BrtEndSxRow"); + case xlsb_type::BrtPcdCalcMem15: + return fmt::format_to(ctx.out(), "BrtPcdCalcMem15"); + case xlsb_type::BrtQsi15: + return fmt::format_to(ctx.out(), "BrtQsi15"); + case xlsb_type::BrtBeginWebExtensions: + return fmt::format_to(ctx.out(), "BrtBeginWebExtensions"); + case xlsb_type::BrtEndWebExtensions: + return fmt::format_to(ctx.out(), "BrtEndWebExtensions"); + case xlsb_type::BrtWebExtension: + return fmt::format_to(ctx.out(), "BrtWebExtension"); + case xlsb_type::BrtAbsPath15: + return fmt::format_to(ctx.out(), "BrtAbsPath15"); + case xlsb_type::BrtBeginPivotTableUISettings: + return fmt::format_to(ctx.out(), "BrtBeginPivotTableUISettings"); + case xlsb_type::BrtEndPivotTableUISettings: + return fmt::format_to(ctx.out(), "BrtEndPivotTableUISettings"); + case xlsb_type::BrtTableSlicerCacheIDs: + return fmt::format_to(ctx.out(), "BrtTableSlicerCacheIDs"); + case xlsb_type::BrtTableSlicerCacheID: + return fmt::format_to(ctx.out(), "BrtTableSlicerCacheID"); + case xlsb_type::BrtBeginTableSlicerCache: + return fmt::format_to(ctx.out(), "BrtBeginTableSlicerCache"); + case xlsb_type::BrtEndTableSlicerCache: + return fmt::format_to(ctx.out(), "BrtEndTableSlicerCache"); + case xlsb_type::BrtSxFilter15: + return fmt::format_to(ctx.out(), "BrtSxFilter15"); + case xlsb_type::BrtBeginTimelineCachePivotCacheIDs: + return fmt::format_to(ctx.out(), "BrtBeginTimelineCachePivotCacheIDs"); + case xlsb_type::BrtEndTimelineCachePivotCacheIDs: + return fmt::format_to(ctx.out(), "BrtEndTimelineCachePivotCacheIDs"); + case xlsb_type::BrtTimelineCachePivotCacheID: + return fmt::format_to(ctx.out(), "BrtTimelineCachePivotCacheID"); + case xlsb_type::BrtBeginTimelineCacheIDs: + return fmt::format_to(ctx.out(), "BrtBeginTimelineCacheIDs"); + case xlsb_type::BrtEndTimelineCacheIDs: + return fmt::format_to(ctx.out(), "BrtEndTimelineCacheIDs"); + case xlsb_type::BrtBeginTimelineCacheID: + return fmt::format_to(ctx.out(), "BrtBeginTimelineCacheID"); + case xlsb_type::BrtEndTimelineCacheID: + return fmt::format_to(ctx.out(), "BrtEndTimelineCacheID"); + case xlsb_type::BrtBeginTimelinesEx: + return fmt::format_to(ctx.out(), "BrtBeginTimelinesEx"); + case xlsb_type::BrtEndTimelinesEx: + return fmt::format_to(ctx.out(), "BrtEndTimelinesEx"); + case xlsb_type::BrtBeginTimelineEx: + return fmt::format_to(ctx.out(), "BrtBeginTimelineEx"); + case xlsb_type::BrtEndTimelineEx: + return fmt::format_to(ctx.out(), "BrtEndTimelineEx"); + case xlsb_type::BrtWorkBookPr15: + return fmt::format_to(ctx.out(), "BrtWorkBookPr15"); + case xlsb_type::BrtPCDH15: + return fmt::format_to(ctx.out(), "BrtPCDH15"); + case xlsb_type::BrtBeginTimelineStyle: + return fmt::format_to(ctx.out(), "BrtBeginTimelineStyle"); + case xlsb_type::BrtEndTimelineStyle: + return fmt::format_to(ctx.out(), "BrtEndTimelineStyle"); + case xlsb_type::BrtTimelineStyleElement: + return fmt::format_to(ctx.out(), "BrtTimelineStyleElement"); + case xlsb_type::BrtBeginTimelineStylesheetExt15: + return fmt::format_to(ctx.out(), "BrtBeginTimelineStylesheetExt15"); + case xlsb_type::BrtEndTimelineStylesheetExt15: + return fmt::format_to(ctx.out(), "BrtEndTimelineStylesheetExt15"); + case xlsb_type::BrtBeginTimelineStyles: + return fmt::format_to(ctx.out(), "BrtBeginTimelineStyles"); + case xlsb_type::BrtEndTimelineStyles: + return fmt::format_to(ctx.out(), "BrtEndTimelineStyles"); + case xlsb_type::BrtBeginTimelineStyleElements: + return fmt::format_to(ctx.out(), "BrtBeginTimelineStyleElements"); + case xlsb_type::BrtEndTimelineStyleElements: + return fmt::format_to(ctx.out(), "BrtEndTimelineStyleElements"); + case xlsb_type::BrtDxf15: + return fmt::format_to(ctx.out(), "BrtDxf15"); + case xlsb_type::BrtBeginDxfs15: + return fmt::format_to(ctx.out(), "BrtBeginDxfs15"); + case xlsb_type::BrtEndDXFs15: + return fmt::format_to(ctx.out(), "BrtEndDXFs15"); + case xlsb_type::BrtSlicerCacheHideItemsWithNoData: + return fmt::format_to(ctx.out(), "BrtSlicerCacheHideItemsWithNoData"); + case xlsb_type::BrtBeginItemUniqueNames: + return fmt::format_to(ctx.out(), "BrtBeginItemUniqueNames"); + case xlsb_type::BrtEndItemUniqueNames: + return fmt::format_to(ctx.out(), "BrtEndItemUniqueNames"); + case xlsb_type::BrtItemUniqueName: + return fmt::format_to(ctx.out(), "BrtItemUniqueName"); + case xlsb_type::BrtBeginExtConn15: + return fmt::format_to(ctx.out(), "BrtBeginExtConn15"); + case xlsb_type::BrtEndExtConn15: + return fmt::format_to(ctx.out(), "BrtEndExtConn15"); + case xlsb_type::BrtBeginOledbPr15: + return fmt::format_to(ctx.out(), "BrtBeginOledbPr15"); + case xlsb_type::BrtEndOledbPr15: + return fmt::format_to(ctx.out(), "BrtEndOledbPr15"); + case xlsb_type::BrtBeginDataFeedPr15: + return fmt::format_to(ctx.out(), "BrtBeginDataFeedPr15"); + case xlsb_type::BrtEndDataFeedPr15: + return fmt::format_to(ctx.out(), "BrtEndDataFeedPr15"); + case xlsb_type::BrtTextPr15: + return fmt::format_to(ctx.out(), "BrtTextPr15"); + case xlsb_type::BrtRangePr15: + return fmt::format_to(ctx.out(), "BrtRangePr15"); + case xlsb_type::BrtDbCommand15: + return fmt::format_to(ctx.out(), "BrtDbCommand15"); + case xlsb_type::BrtBeginDbTables15: + return fmt::format_to(ctx.out(), "BrtBeginDbTables15"); + case xlsb_type::BrtEndDbTables15: + return fmt::format_to(ctx.out(), "BrtEndDbTables15"); + case xlsb_type::BrtDbTable15: + return fmt::format_to(ctx.out(), "BrtDbTable15"); + case xlsb_type::BrtBeginDataModel: + return fmt::format_to(ctx.out(), "BrtBeginDataModel"); + case xlsb_type::BrtEndDataModel: + return fmt::format_to(ctx.out(), "BrtEndDataModel"); + case xlsb_type::BrtBeginModelTables: + return fmt::format_to(ctx.out(), "BrtBeginModelTables"); + case xlsb_type::BrtEndModelTables: + return fmt::format_to(ctx.out(), "BrtEndModelTables"); + case xlsb_type::BrtModelTable: + return fmt::format_to(ctx.out(), "BrtModelTable"); + case xlsb_type::BrtBeginModelRelationships: + return fmt::format_to(ctx.out(), "BrtBeginModelRelationships"); + case xlsb_type::BrtEndModelRelationships: + return fmt::format_to(ctx.out(), "BrtEndModelRelationships"); + case xlsb_type::BrtModelRelationship: + return fmt::format_to(ctx.out(), "BrtModelRelationship"); + case xlsb_type::BrtBeginECTxtWiz15: + return fmt::format_to(ctx.out(), "BrtBeginECTxtWiz15"); + case xlsb_type::BrtEndECTxtWiz15: + return fmt::format_to(ctx.out(), "BrtEndECTxtWiz15"); + case xlsb_type::BrtBeginECTWFldInfoLst15: + return fmt::format_to(ctx.out(), "BrtBeginECTWFldInfoLst15"); + case xlsb_type::BrtEndECTWFldInfoLst15: + return fmt::format_to(ctx.out(), "BrtEndECTWFldInfoLst15"); + case xlsb_type::BrtBeginECTWFldInfo15: + return fmt::format_to(ctx.out(), "BrtBeginECTWFldInfo15"); + case xlsb_type::BrtFieldListActiveItem: + return fmt::format_to(ctx.out(), "BrtFieldListActiveItem"); + case xlsb_type::BrtPivotCacheIdVersion: + return fmt::format_to(ctx.out(), "BrtPivotCacheIdVersion"); + case xlsb_type::BrtSXDI15: + return fmt::format_to(ctx.out(), "BrtSXDI15"); + case xlsb_type::brtBeginModelTimeGroupings: + return fmt::format_to(ctx.out(), "brtBeginModelTimeGroupings"); + case xlsb_type::brtEndModelTimeGroupings: + return fmt::format_to(ctx.out(), "brtEndModelTimeGroupings"); + case xlsb_type::brtBeginModelTimeGrouping: + return fmt::format_to(ctx.out(), "brtBeginModelTimeGrouping"); + case xlsb_type::brtEndModelTimeGrouping: + return fmt::format_to(ctx.out(), "brtEndModelTimeGrouping"); + case xlsb_type::brtModelTimeGroupingCalcCol: + return fmt::format_to(ctx.out(), "brtModelTimeGroupingCalcCol"); + case xlsb_type::brtRevisionPtr: + return fmt::format_to(ctx.out(), "brtRevisionPtr"); + case xlsb_type::BrtBeginDynamicArrayPr: + return fmt::format_to(ctx.out(), "BrtBeginDynamicArrayPr"); + case xlsb_type::BrtEndDynamicArrayPr: + return fmt::format_to(ctx.out(), "BrtEndDynamicArrayPr"); + case xlsb_type::BrtBeginRichValueBlock: + return fmt::format_to(ctx.out(), "BrtBeginRichValueBlock"); + case xlsb_type::BrtEndRichValueBlock: + return fmt::format_to(ctx.out(), "BrtEndRichValueBlock"); + case xlsb_type::BrtBeginRichFilters: + return fmt::format_to(ctx.out(), "BrtBeginRichFilters"); + case xlsb_type::BrtEndRichFilters: + return fmt::format_to(ctx.out(), "BrtEndRichFilters"); + case xlsb_type::BrtRichFilter: + return fmt::format_to(ctx.out(), "BrtRichFilter"); + case xlsb_type::BrtBeginRichFilterColumn: + return fmt::format_to(ctx.out(), "BrtBeginRichFilterColumn"); + case xlsb_type::BrtEndRichFilterColumn: + return fmt::format_to(ctx.out(), "BrtEndRichFilterColumn"); + case xlsb_type::BrtBeginCustomRichFilters: + return fmt::format_to(ctx.out(), "BrtBeginCustomRichFilters"); + case xlsb_type::BrtEndCustomRichFilters: + return fmt::format_to(ctx.out(), "BrtEndCustomRichFilters"); + case xlsb_type::BRTCustomRichFilter: + return fmt::format_to(ctx.out(), "BRTCustomRichFilter"); + case xlsb_type::BrtTop10RichFilter: + return fmt::format_to(ctx.out(), "BrtTop10RichFilter"); + case xlsb_type::BrtDynamicRichFilter: + return fmt::format_to(ctx.out(), "BrtDynamicRichFilter"); + case xlsb_type::BrtBeginRichSortCondition: + return fmt::format_to(ctx.out(), "BrtBeginRichSortCondition"); + case xlsb_type::BrtEndRichSortCondition: + return fmt::format_to(ctx.out(), "BrtEndRichSortCondition"); + case xlsb_type::BrtRichFilterDateGroupItem: + return fmt::format_to(ctx.out(), "BrtRichFilterDateGroupItem"); + case xlsb_type::BrtBeginCalcFeatures: + return fmt::format_to(ctx.out(), "BrtBeginCalcFeatures"); + case xlsb_type::BrtEndCalcFeatures: + return fmt::format_to(ctx.out(), "BrtEndCalcFeatures"); + case xlsb_type::BrtCalcFeature: + return fmt::format_to(ctx.out(), "BrtCalcFeature"); + case xlsb_type::BrtExternalLinksPr: + return fmt::format_to(ctx.out(), "BrtExternalLinksPr"); + default: + return fmt::format_to(ctx.out(), "{}", (unsigned int)t); + } + } +}; + +struct brt_bundle_sh { + uint32_t hsState; + uint32_t iTabID; +}; + +#pragma pack(push,1) + +struct brt_row_hdr { + uint32_t rw; + uint32_t ixfe; + uint16_t miyRw; + // FIXME - make sure bitfields are in correct order + uint16_t fExtraAsc : 1; + uint16_t fExtraDsc : 1; + uint16_t reserved1 : 6; + uint16_t iOutLevel : 3; + uint16_t fCollapsed : 1; + uint16_t fDyZero : 1; + uint16_t fUnsynced : 1; + uint16_t fGhostDirty : 1; + uint16_t fReserved : 1; + uint8_t fPhShow : 1; + uint8_t reserved2 : 7; + uint32_t ccolspan; +}; + +struct xlsb_cell { + uint32_t column; + uint32_t iStyleRef : 24; + uint32_t fPhShow : 1; + uint32_t reserved : 7; +}; + +struct rk_number { + uint32_t fx100 : 1; + uint32_t fInt : 1; + uint32_t num : 30; +}; + +#pragma pack(pop) + +struct brt_cell_rk { + xlsb_cell cell; + rk_number value; +}; + +struct brt_cell_bool { + xlsb_cell cell; + uint8_t fBool; +}; + +struct brt_cell_real { + xlsb_cell cell; + double xnum; +}; + +struct brt_cell_st { + xlsb_cell cell; + uint32_t len; + char16_t str[0]; +}; + +#pragma pack(push,1) + +struct rich_str { + uint8_t fRichStr : 1; + uint8_t fExtStr : 1; + uint8_t unused1 : 6; + uint32_t len; + char16_t str[0]; +}; + +#pragma pack(pop) + +struct brt_sst_item { + rich_str richStr; +}; + +struct brt_cell_rstring { + xlsb_cell cell; + rich_str value; +}; + +struct brt_cell_isst { + xlsb_cell cell; + uint32_t isst; +}; + +#pragma pack(push,1) + +struct brt_xf { + uint16_t ixfeParent; + uint16_t iFmt; + uint16_t iFont; + uint16_t iFill; + uint16_t ixBOrder; + uint8_t trot; + uint8_t indent; + uint16_t f123Prefix : 1; + uint16_t fSxButton : 1; + uint16_t fHidden : 1; + uint16_t fLocked : 1; + uint16_t iReadingOrder : 2; + uint16_t fMergeCell : 1; + uint16_t fShrinkToFit : 1; + uint16_t fJustLast : 1; + uint16_t fWrap : 1; + uint16_t alcv : 3; + uint16_t alc : 3; + uint16_t unused : 10; + uint16_t xfGrbitAtr : 6; +}; + +#pragma pack(pop) + +struct brt_wb_prop { + uint32_t f1904 : 1; + uint32_t reserved1 : 1; + uint32_t fHideBorderUnselLists : 1; + uint32_t fFilterPrivacy : 1; + uint32_t fBuggedUserABoutSolution : 1; + uint32_t fShowInkAnnotation : 1; + uint32_t fBackup : 1; + uint32_t fNoSaveSup : 1; + uint32_t grbitUpdateLinks : 2; + uint32_t fHidePivotTableFList : 1; + uint32_t fPublishedBookItems : 1; + uint32_t fCheckCompat : 1; + uint32_t mdDspObj : 2; + uint32_t fShowPivotChartFilter : 1; + uint32_t fAutoCompressPictures : 1; + uint32_t reserved2 : 1; + uint32_t fRefreshAll : 1; + uint32_t unused : 13; + uint32_t dwThemeVersion; + uint32_t strName_len; +}; + +#pragma pack(push,1) + +struct brt_fmt { + uint16_t ifmt; + uint32_t stFmtCode_len; + char16_t stFmtCode[0]; +}; + +#pragma pack(pop) diff --git a/src/xlcpp/xml-reader.cpp b/src/xlcpp/xml-reader.cpp new file mode 100644 index 000000000..baaca6d79 --- /dev/null +++ b/src/xlcpp/xml-reader.cpp @@ -0,0 +1,398 @@ +#include "openxlsx2.h" + +#include "xlcpp-pimpl.h" +#include + +using namespace std; + +static bool __inline is_whitespace(char c) { + return c == ' ' || c == '\t' || c == '\r' || c == '\n'; +} + +static void parse_attributes(string_view node, const function& func) { + auto s = node.substr(1, node.length() - 2); + + if (!s.empty() && s.back() == '/') { + s.remove_suffix(1); + } + + while (!s.empty() && !is_whitespace(s.front())) { + s.remove_prefix(1); + } + + while (!s.empty() && is_whitespace(s.front())) { + s.remove_prefix(1); + } + + while (!s.empty()) { + auto av = s; + + auto eq = av.find_first_of('='); + string_view n, v; + + if (eq == string::npos) { + n = av; + v = ""; + } else { + n = av.substr(0, eq); + v = av.substr(eq + 1); + + while (!v.empty() && is_whitespace(v.front())) { + v = v.substr(1); + } + + if (v.length() >= 2 && (v.front() == '"' || v.front() == '\'')) { + auto c = v.front(); + + v.remove_prefix(1); + + auto end = v.find_first_of(c); + + if (end != string::npos) { + v = v.substr(0, end); + av = av.substr(0, v.data() + v.length() - av.data() + 1); + } else { + for (size_t i = 0; i < av.length(); i++) { + if (is_whitespace(av[i])) { + av = av.substr(0, i); + break; + } + } + } + } + } + + while (!n.empty() && is_whitespace(n.back())) { + n.remove_suffix(1); + } + + if (!func(n, v)) + return; + + s.remove_prefix(av.length()); + + while (!s.empty() && is_whitespace(s.front())) { + s.remove_prefix(1); + } + } +} + +bool xml_reader::read() { + if (sv.empty()) + return false; + + // FIXME - DOCTYPE (, ]>) + + if (type == xml_node::element && empty_tag) + namespaces.pop_back(); + + if (sv.front() != '<') { // text + auto pos = sv.find_first_of('<'); + + if (pos == string::npos) { + node = sv; + sv = ""; + } else { + node = sv.substr(0, pos); + sv = sv.substr(pos); + } + + type = xml_node::whitespace; + + for (auto c : sv) { + if (!is_whitespace(c)) { + type = xml_node::text; + break; + } + } + } else { + if (sv.starts_with(""); + + if (pos == string::npos) { + node = sv; + sv = ""; + } else { + node = sv.substr(0, pos + 2); + sv = sv.substr(pos + 2); + } + + type = xml_node::processing_instruction; + } else if (sv.starts_with("'); + + if (pos == string::npos) { + node = sv; + sv = ""; + } else { + node = sv.substr(0, pos + 1); + sv = sv.substr(pos + 1); + } + + type = xml_node::end_element; + namespaces.pop_back(); + } else if (sv.starts_with(""); + + if (pos == string::npos) + Rcpp::stop("Malformed comment."); + + node = sv.substr(0, pos + 3); + sv = sv.substr(pos + 3); + + type = xml_node::comment; + } else if (sv.starts_with(""); + + if (pos == string::npos) + Rcpp::stop("Malformed CDATA."); + + node = sv.substr(0, pos + 3); + sv = sv.substr(pos + 3); + + type = xml_node::cdata; + } else { + auto pos = sv.find_first_of('>'); + + if (pos == string::npos) { + node = sv; + sv = ""; + } else { + node = sv.substr(0, pos + 1); + sv = sv.substr(pos + 1); + } + + type = xml_node::element; + ns_list ns; + + parse_attributes(node, [&](string_view name, xml_enc_string_view value) { + if (name.starts_with("xmlns:")) + ns.emplace_back(name.substr(6), value); + else if (name == "xmlns") + ns.emplace_back("", value); + + return true; + }); + + namespaces.push_back(ns); + + empty_tag = node.ends_with("/>"); + } + } + + return true; +} + +enum xml_node xml_reader::node_type() const { + return type; +} + +bool xml_reader::is_empty() const { + return type == xml_node::element && empty_tag; +} + +void xml_reader::attributes_loop_raw(const function& func) const { + if (type != xml_node::element) + return; + + parse_attributes(node, [&](string_view name, xml_enc_string_view value_raw) { + auto colon = name.find_first_of(':'); + + if (colon == string::npos) + return func(name, xml_enc_string_view{}, value_raw); + + auto prefix = name.substr(0, colon); + + for (auto it = namespaces.rbegin(); it != namespaces.rend(); it++) { + for (const auto& v : *it) { + if (v.first == prefix) + return func(name.substr(colon + 1), v.second, value_raw); + } + } + + return func(name.substr(colon + 1), xml_enc_string_view{}, value_raw); + }); +} + +optional xml_reader::get_attribute(string_view name, string_view ns) const { + if (type != xml_node::element) + return nullopt; + + optional xesv; + + attributes_loop_raw([&](string_view local_name, xml_enc_string_view namespace_uri_raw, + xml_enc_string_view value_raw) { + if (local_name == name && namespace_uri_raw.cmp(ns)) { + xesv = value_raw; + return false; + } + + return true; + }); + + return xesv; +} + +xml_enc_string_view xml_reader::namespace_uri_raw() const { + auto tag = name(); + auto colon = tag.find_first_of(':'); + string_view prefix; + + if (colon != string::npos) + prefix = tag.substr(0, colon); + + for (auto it = namespaces.rbegin(); it != namespaces.rend(); it++) { + for (const auto& v : *it) { + if (v.first == prefix) + return v.second; + } + } + + return {}; +} + +string_view xml_reader::name() const { + if (type != xml_node::element && type != xml_node::end_element) + return ""; + + auto tag = node.substr(type == xml_node::end_element ? 2 : 1); + + tag.remove_suffix(1); + + for (size_t i = 0; i < tag.length(); i++) { + if (is_whitespace(tag[i])) { + tag = tag.substr(0, i); + break; + } + } + + return tag; +} + +string_view xml_reader::local_name() const { + if (type != xml_node::element && type != xml_node::end_element) + return ""; + + auto tag = name(); + auto pos = tag.find_first_of(':'); + + if (pos == string::npos) + return tag; + else + return tag.substr(pos + 1); +} + +string xml_reader::value() const { + switch (type) { + case xml_node::text: + return xml_enc_string_view{node}.decode(); + + case xml_node::cdata: + return string{node.substr(9, node.length() - 12)}; + + default: + return {}; + } +} + +static string esc_char(string_view s) { + uint32_t c = 0; + from_chars_result fcr; + + if (s.starts_with("x")) + fcr = from_chars(s.data() + 1, s.data() + s.length(), c, 16); + else + fcr = from_chars(s.data(), s.data() + s.length(), c); + + if (c == 0 || c > 0x10ffff) + return ""; + + if (c < 0x80) + return string{(char)c, 1}; + else if (c < 0x800) { + char t[2]; + + t[0] = (char)(0xc0 | (c >> 6)); + t[1] = (char)(0x80 | (c & 0x3f)); + + return string{string_view(t, 2)}; + } else if (c < 0x10000) { + char t[3]; + + t[0] = (char)(0xe0 | (c >> 12)); + t[1] = (char)(0x80 | ((c >> 6) & 0x3f)); + t[2] = (char)(0x80 | (c & 0x3f)); + + return string{string_view(t, 3)}; + } else { + char t[4]; + + t[0] = (char)(0xf0 | (c >> 18)); + t[1] = (char)(0x80 | ((c >> 12) & 0x3f)); + t[2] = (char)(0x80 | ((c >> 6) & 0x3f)); + t[3] = (char)(0x80 | (c & 0x3f)); + + return string{string_view(t, 4)}; + } +} + +string xml_enc_string_view::decode() const { + auto v = sv; + string s; + + s.reserve(v.length()); + + while (!v.empty()) { + if (v.front() == '&') { + v.remove_prefix(1); + + if (v.starts_with("amp;")) { + s += "&"; + v.remove_prefix(4); + } else if (v.starts_with("lt;")) { + s += "<"; + v.remove_prefix(3); + } else if (v.starts_with("gt;")) { + s += ">"; + v.remove_prefix(3); + } else if (v.starts_with("quot;")) { + s += "\""; + v.remove_prefix(5); + } else if (v.starts_with("apos;")) { + s += "'"; + v.remove_prefix(5); + } else if (v.starts_with("#")) { + string_view bit; + + v.remove_prefix(1); + + auto sc = v.find_first_of(';'); + if (sc == string::npos) { + bit = v; + v = ""; + } else { + bit = v.substr(0, sc); + v.remove_prefix(sc + 1); + } + + s += esc_char(bit); + } else + s += "&"; + } else { + s += v.front(); + v.remove_prefix(1); + } + } + + return s; +} + +bool xml_enc_string_view::cmp(string_view str) const { + for (auto c : sv) { + if (c == '&') + return decode() == str; + } + + return sv == str; +} diff --git a/src/xlcpp/xml-writer.cpp b/src/xlcpp/xml-writer.cpp new file mode 100644 index 000000000..421d0d992 --- /dev/null +++ b/src/xlcpp/xml-writer.cpp @@ -0,0 +1,77 @@ +#include "xlcpp-pimpl.h" + +using namespace std; + +string xml_writer::dump() const { + return buf; +} + +void xml_writer::start_document() { + buf += "\n"; + empty_tag = false; +} + +static string xml_escape(string_view s, bool att) { + string ret; + + ret.reserve(s.length()); + + for (auto c : s) { + if (c == '<') + ret += "<"; + else if (c == '>') + ret += ">"; + else if (c == '&') + ret += "&"; + else if (c == '"' && att) + ret += """; + else + ret += c; + } + + return ret; +} + +void xml_writer::start_element(string_view tag, const unordered_map& namespaces) { + if (empty_tag) + buf += ">"; + + buf += "<" + string{tag}; + tags.emplace(tag); + + empty_tag = true; + + for (const auto& ns : namespaces) { + buf += " xmlns"; + + if (!ns.first.empty()) + buf += ":" + ns.first; + + buf += "=\""; + buf += xml_escape(ns.second, true); + buf += "\""; + } +} + +void xml_writer::end_element() { + if (empty_tag) { + buf += "/>"; + empty_tag = false; + } else + buf += ""; + + tags.pop(); +} + +void xml_writer::text(string_view s) { + if (empty_tag) { + buf += ">"; + empty_tag = false; + } + + buf += xml_escape(s, false); +} + +void xml_writer::attribute(string_view name, string_view value) { + buf += " " + string{name} + "=\"" + xml_escape(value, true) + "\""; +} diff --git a/tests/testthat/helper.R b/tests/testthat/helper.R index 549a31fc6..b4fc1ab2d 100644 --- a/tests/testthat/helper.R +++ b/tests/testthat/helper.R @@ -1021,7 +1021,8 @@ download_testfiles <- function() { "umlauts.xlsx", "unemployment-nrw202208.xlsx", "update_test.xlsx", - "vml_numbering.xlsx" + "vml_numbering.xlsx", + "openxlsx2_example_pass.xlsx" ) test_path <- testthat::test_path("testfiles") diff --git a/tests/testthat/test-loading_workbook.R b/tests/testthat/test-loading_workbook.R index f0e1f23b0..cb63404db 100644 --- a/tests/testthat/test-loading_workbook.R +++ b/tests/testthat/test-loading_workbook.R @@ -386,3 +386,15 @@ test_that("sheetView is not switched", { expect_equal(exp, got) }) + +test_that("loading password protected workbooks works", { + + fl <- testfile_path("openxlsx2_example_pass.xlsx") + + wb <- wb_load(fl, password = "openxlsx2") + + exp <- c(10, 9) + got <- dim(wb_to_df(wb)) + expect_equal(exp, got) + +})