use crate::{XlOpenOptions, XlWriteOptions, XL_OPT_DEFAULT, XL_OPT_FALSE, XL_OPT_TRUE};
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct OpenOptions {
pub csv_sniff_dialect: Option<bool>,
pub csv_delimiter: Option<i32>,
pub csv_quote: Option<i32>,
pub csv_detect_bom: Option<bool>,
pub csv_max_cell_bytes: Option<i32>,
pub csv_intern_strings: Option<bool>,
pub max_total_decompressed_bytes: Option<i64>,
pub max_cell_bytes: Option<i32>,
pub max_shared_string_bytes: Option<i64>,
pub max_zip_entries: Option<i32>,
pub prefetch_decompression: Option<bool>,
pub intern_strings: Option<bool>,
pub password: Option<String>,
}
macro_rules! setters {
($($(#[$doc:meta])* $name:ident: $ty:ty),* $(,)?) => {
$(
$(#[$doc])*
#[must_use]
pub fn $name(mut self, value: $ty) -> Self {
self.$name = Some(value);
self
}
)*
};
}
impl OpenOptions {
#[must_use]
pub fn new() -> Self {
Self::default()
}
setters! {
csv_sniff_dialect: bool,
csv_delimiter: i32,
csv_quote: i32,
csv_detect_bom: bool,
csv_max_cell_bytes: i32,
csv_intern_strings: bool,
max_total_decompressed_bytes: i64,
max_cell_bytes: i32,
max_shared_string_bytes: i64,
max_zip_entries: i32,
prefetch_decompression: bool,
intern_strings: bool,
}
#[must_use]
pub fn password(mut self, value: impl Into<String>) -> Self {
self.password = Some(value.into());
self
}
#[must_use]
pub fn to_raw(&self) -> OpenOptionsRaw<'_> {
let password = self.password.as_deref().map(str::as_bytes);
let mut raw = XlOpenOptions {
struct_size: std::mem::size_of::<XlOpenOptions>() as i32,
csv_sniff_dialect: opt_state(self.csv_sniff_dialect),
csv_delimiter: opt_number(self.csv_delimiter),
csv_quote: opt_number(self.csv_quote),
csv_detect_bom: opt_state(self.csv_detect_bom),
csv_max_cell_bytes: opt_number(self.csv_max_cell_bytes),
csv_intern_strings: opt_state(self.csv_intern_strings),
max_total_decompressed_bytes: opt_number(self.max_total_decompressed_bytes),
max_cell_bytes: opt_number(self.max_cell_bytes),
max_shared_string_bytes: opt_number(self.max_shared_string_bytes),
max_zip_entries: opt_number(self.max_zip_entries),
prefetch_decompression: opt_state(self.prefetch_decompression),
intern_strings: opt_state(self.intern_strings),
password: std::ptr::null(),
password_len: 0,
};
if let Some(bytes) = password {
raw.password = bytes.as_ptr();
raw.password_len = bytes.len() as i32;
}
OpenOptionsRaw {
raw,
_password: password,
}
}
}
pub struct OpenOptionsRaw<'a> {
raw: XlOpenOptions,
_password: Option<&'a [u8]>,
}
impl OpenOptionsRaw<'_> {
pub(crate) fn as_ptr(&self) -> *const XlOpenOptions {
&self.raw
}
}
fn opt_state(value: Option<bool>) -> i32 {
match value {
None => XL_OPT_DEFAULT,
Some(true) => XL_OPT_TRUE,
Some(false) => XL_OPT_FALSE,
}
}
fn opt_number<T: Default>(value: Option<T>) -> T {
value.unwrap_or_default()
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct WriteOptions {
pub sheet_name: Option<String>,
pub csv_delimiter: Option<i32>,
pub csv_quote: Option<i32>,
pub date1904: Option<bool>,
pub use_shared_strings: Option<bool>,
}
impl WriteOptions {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn sheet_name(mut self, value: impl Into<String>) -> Self {
self.sheet_name = Some(value.into());
self
}
setters! {
csv_delimiter: i32,
csv_quote: i32,
date1904: bool,
use_shared_strings: bool,
}
pub(crate) fn to_raw(&self) -> XlWriteOptions {
XlWriteOptions {
struct_size: std::mem::size_of::<XlWriteOptions>() as i32,
sheet_name_len: self.sheet_name.as_ref().map_or(0, |n| n.len() as i32),
sheet_name: self
.sheet_name
.as_ref()
.map_or(std::ptr::null(), |n| n.as_ptr()),
csv_delimiter: opt_number(self.csv_delimiter),
csv_quote: opt_number(self.csv_quote),
date1904: opt_state(self.date1904),
use_shared_strings: opt_state(self.use_shared_strings),
}
}
}
#[inline]
pub(crate) fn ptr_or_null<T>(raw: &Option<T>) -> *const T {
raw.as_ref()
.map_or(std::ptr::null(), |value| value as *const T)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unset_fields_lower_to_the_abi_defaults() {
let options = OpenOptions::new();
let guard = options.to_raw();
let raw = guard.raw;
assert_eq!(raw.struct_size, std::mem::size_of::<XlOpenOptions>() as i32);
assert_eq!(raw.csv_sniff_dialect, XL_OPT_DEFAULT);
assert_eq!(raw.prefetch_decompression, XL_OPT_DEFAULT);
assert_eq!(raw.csv_delimiter, 0);
assert_eq!(raw.max_total_decompressed_bytes, 0);
assert!(
raw.password.is_null(),
"no password set means a null pointer"
);
assert_eq!(raw.password_len, 0);
}
#[test]
fn booleans_lower_to_distinct_true_and_false_states() {
let options = OpenOptions::new()
.prefetch_decompression(true)
.intern_strings(false);
let guard = options.to_raw();
let raw = guard.raw;
assert_eq!(raw.prefetch_decompression, XL_OPT_TRUE);
assert_eq!(raw.intern_strings, XL_OPT_FALSE);
assert_eq!(raw.csv_detect_bom, XL_OPT_DEFAULT);
}
#[test]
fn numbers_pass_through_unchanged() {
let options = OpenOptions::new()
.csv_delimiter(b';' as i32)
.max_zip_entries(1024)
.max_shared_string_bytes(1 << 20);
let guard = options.to_raw();
let raw = guard.raw;
assert_eq!(raw.csv_delimiter, b';' as i32);
assert_eq!(raw.max_zip_entries, 1024);
assert_eq!(raw.max_shared_string_bytes, 1 << 20);
}
#[test]
fn password_lowers_to_a_pointer_and_length_into_the_owning_options() {
let options = OpenOptions::new().password("hunter2");
let guard = options.to_raw();
let raw = guard.raw;
assert_eq!(raw.password_len, 7);
assert!(!raw.password.is_null());
let bytes = unsafe { std::slice::from_raw_parts(raw.password, raw.password_len as usize) };
assert_eq!(bytes, b"hunter2");
}
#[test]
fn password_survives_a_temporary_source() {
let options = OpenOptions::new().password(String::from("hunter2"));
let guard = options.to_raw();
let raw = guard.raw;
let bytes = unsafe { std::slice::from_raw_parts(raw.password, raw.password_len as usize) };
assert_eq!(bytes, b"hunter2");
}
#[test]
fn write_options_lower_to_the_abi_defaults() {
let raw = WriteOptions::new().to_raw();
assert_eq!(
raw.struct_size,
std::mem::size_of::<crate::XlWriteOptions>() as i32
);
assert!(raw.sheet_name.is_null(), "an unset sheet name means Sheet1");
assert_eq!(raw.sheet_name_len, 0);
assert_eq!(raw.csv_delimiter, 0);
assert_eq!(raw.date1904, XL_OPT_DEFAULT);
}
#[test]
fn write_options_lower_every_set_field() {
let options = WriteOptions::new()
.sheet_name("Dados")
.csv_delimiter(b';' as i32)
.date1904(false)
.use_shared_strings(true);
let raw = options.to_raw();
assert_eq!(raw.sheet_name_len, 5);
assert!(!raw.sheet_name.is_null());
assert_eq!(raw.csv_delimiter, b';' as i32);
assert_eq!(raw.date1904, XL_OPT_FALSE);
assert_eq!(raw.use_shared_strings, XL_OPT_TRUE);
}
#[test]
fn write_options_match_the_c_struct_size() {
assert_eq!(std::mem::size_of::<crate::XlWriteOptions>(), 32);
}
}