use std::collections::{btree_map::Range as BTreeMapRange, BTreeMap, BTreeSet};
#[cfg(feature = "serde")]
use serde::de::{IntoDeserializer, VariantAccess};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ExcelDateTime {
pub year: i64,
pub month: u32,
pub day: u32,
pub hour: u32,
pub minute: u32,
pub second: u32,
}
impl ExcelDateTime {
pub fn date_string(&self) -> String {
format!("{:04}-{:02}-{:02}", self.year, self.month, self.day)
}
pub fn time_string(&self) -> String {
format!("{:02}:{:02}:{:02}", self.hour, self.minute, self.second)
}
#[cfg(feature = "chrono")]
pub fn to_naive_datetime(self) -> Option<chrono::NaiveDateTime> {
let year = i32::try_from(self.year).ok()?;
chrono::NaiveDate::from_ymd_opt(year, self.month, self.day)?.and_hms_opt(
self.hour,
self.minute,
self.second,
)
}
}
impl std::fmt::Display for ExcelDateTime {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} {}", self.date_string(), self.time_string())
}
}
pub fn excel_serial_to_datetime(serial: f64, date1904: bool) -> Option<ExcelDateTime> {
let (year, month, day, hour, minute, second) =
crate::format::serial_to_datetime_parts(serial, date1904)?;
Some(ExcelDateTime {
year,
month,
day,
hour,
minute,
second,
})
}
#[cfg(feature = "chrono")]
pub fn excel_serial_to_naive_datetime(
serial: f64,
date1904: bool,
) -> Option<chrono::NaiveDateTime> {
excel_serial_to_datetime(serial, date1904)?.to_naive_datetime()
}
#[cfg(feature = "chrono")]
pub fn excel_serial_to_duration(serial: f64) -> Option<chrono::Duration> {
if !serial.is_finite() {
return None;
}
let milliseconds = (serial * 86_400_000.0).round();
if !milliseconds.is_finite() || milliseconds < i64::MIN as f64 || milliseconds > i64::MAX as f64
{
return None;
}
Some(chrono::Duration::milliseconds(milliseconds as i64))
}
#[derive(Debug, Clone, PartialEq)]
pub enum Cell {
Text(String),
Number(f64),
Date(f64),
Bool(bool),
Error(String),
Formula {
formula: String,
cached: Box<Cell>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CellErrorType {
Div0,
NA,
Name,
Null,
Num,
Ref,
Value,
GettingData,
}
impl CellErrorType {
pub fn from_excel_error(error: &str) -> Option<Self> {
match error {
"#DIV/0!" => Some(Self::Div0),
"#N/A" => Some(Self::NA),
"#NAME?" => Some(Self::Name),
"#NULL!" => Some(Self::Null),
"#NUM!" => Some(Self::Num),
"#REF!" => Some(Self::Ref),
"#VALUE!" => Some(Self::Value),
"#GETTING_DATA" | "#DATA!" => Some(Self::GettingData),
_ => None,
}
}
pub fn as_str(self) -> &'static str {
match self {
Self::Div0 => "#DIV/0!",
Self::NA => "#N/A",
Self::Name => "#NAME?",
Self::Null => "#NULL!",
Self::Num => "#NUM!",
Self::Ref => "#REF!",
Self::Value => "#VALUE!",
Self::GettingData => "#DATA!",
}
}
}
impl std::fmt::Display for CellErrorType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
pub type Data = Cell;
pub type DataRef<'a> = &'a Cell;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub enum HeaderRow {
#[default]
FirstNonEmptyRow,
Row(u32),
}
impl From<u32> for HeaderRow {
fn from(row: u32) -> Self {
HeaderRow::Row(row)
}
}
pub trait DataType {
fn is_empty(&self) -> bool;
fn is_int(&self) -> bool;
fn is_float(&self) -> bool;
fn is_bool(&self) -> bool;
fn is_string(&self) -> bool;
fn is_error(&self) -> bool;
fn is_datetime(&self) -> bool;
fn is_datetime_iso(&self) -> bool;
fn is_duration_iso(&self) -> bool;
fn is_formula(&self) -> bool;
fn get_int(&self) -> Option<i64>;
fn get_float(&self) -> Option<f64>;
fn get_bool(&self) -> Option<bool>;
fn get_string(&self) -> Option<&str>;
fn get_error(&self) -> Option<&str>;
fn get_error_type(&self) -> Option<CellErrorType>;
fn get_datetime(&self) -> Option<f64>;
fn get_formula(&self) -> Option<&str>;
fn get_datetime_iso(&self) -> Option<&str>;
fn get_duration_iso(&self) -> Option<&str>;
fn cached_value(&self) -> Option<&Cell>;
fn as_string(&self) -> Option<String>;
fn as_i64(&self) -> Option<i64>;
fn as_f64(&self) -> Option<f64>;
fn as_datetime(&self, date1904: bool) -> Option<ExcelDateTime>;
#[cfg(feature = "chrono")]
fn as_naive_datetime(&self, date1904: bool) -> Option<chrono::NaiveDateTime>;
#[cfg(feature = "chrono")]
fn as_naive_date(&self, date1904: bool) -> Option<chrono::NaiveDate>;
#[cfg(feature = "chrono")]
fn as_date(&self, date1904: bool) -> Option<chrono::NaiveDate>;
#[cfg(feature = "chrono")]
fn as_naive_time(&self, date1904: bool) -> Option<chrono::NaiveTime>;
#[cfg(feature = "chrono")]
fn as_time(&self, date1904: bool) -> Option<chrono::NaiveTime>;
#[cfg(feature = "chrono")]
fn as_duration(&self) -> Option<chrono::Duration>;
}
impl DataType for Cell {
fn is_empty(&self) -> bool {
Cell::is_empty(self)
}
fn is_int(&self) -> bool {
Cell::is_int(self)
}
fn is_float(&self) -> bool {
Cell::is_float(self)
}
fn is_bool(&self) -> bool {
Cell::is_bool(self)
}
fn is_string(&self) -> bool {
Cell::is_string(self)
}
fn is_error(&self) -> bool {
Cell::is_error(self)
}
fn is_datetime(&self) -> bool {
Cell::is_datetime(self)
}
fn is_datetime_iso(&self) -> bool {
Cell::is_datetime_iso(self)
}
fn is_duration_iso(&self) -> bool {
Cell::is_duration_iso(self)
}
fn is_formula(&self) -> bool {
Cell::is_formula(self)
}
fn get_int(&self) -> Option<i64> {
Cell::get_int(self)
}
fn get_float(&self) -> Option<f64> {
Cell::get_float(self)
}
fn get_bool(&self) -> Option<bool> {
Cell::get_bool(self)
}
fn get_string(&self) -> Option<&str> {
Cell::get_string(self)
}
fn get_error(&self) -> Option<&str> {
Cell::get_error(self)
}
fn get_error_type(&self) -> Option<CellErrorType> {
Cell::get_error_type(self)
}
fn get_datetime(&self) -> Option<f64> {
Cell::get_datetime(self)
}
fn get_formula(&self) -> Option<&str> {
Cell::get_formula(self)
}
fn get_datetime_iso(&self) -> Option<&str> {
Cell::get_datetime_iso(self)
}
fn get_duration_iso(&self) -> Option<&str> {
Cell::get_duration_iso(self)
}
fn cached_value(&self) -> Option<&Cell> {
Cell::cached_value(self)
}
fn as_string(&self) -> Option<String> {
Cell::as_string(self)
}
fn as_i64(&self) -> Option<i64> {
Cell::as_i64(self)
}
fn as_f64(&self) -> Option<f64> {
Cell::as_f64(self)
}
fn as_datetime(&self, date1904: bool) -> Option<ExcelDateTime> {
Cell::as_datetime(self, date1904)
}
#[cfg(feature = "chrono")]
fn as_naive_datetime(&self, date1904: bool) -> Option<chrono::NaiveDateTime> {
Cell::as_naive_datetime(self, date1904)
}
#[cfg(feature = "chrono")]
fn as_naive_date(&self, date1904: bool) -> Option<chrono::NaiveDate> {
Cell::as_naive_date(self, date1904)
}
#[cfg(feature = "chrono")]
fn as_date(&self, date1904: bool) -> Option<chrono::NaiveDate> {
Cell::as_date(self, date1904)
}
#[cfg(feature = "chrono")]
fn as_naive_time(&self, date1904: bool) -> Option<chrono::NaiveTime> {
Cell::as_naive_time(self, date1904)
}
#[cfg(feature = "chrono")]
fn as_time(&self, date1904: bool) -> Option<chrono::NaiveTime> {
Cell::as_time(self, date1904)
}
#[cfg(feature = "chrono")]
fn as_duration(&self) -> Option<chrono::Duration> {
Cell::as_duration(self)
}
}
impl<T> DataType for &T
where
T: DataType + ?Sized,
{
fn is_empty(&self) -> bool {
(**self).is_empty()
}
fn is_int(&self) -> bool {
(**self).is_int()
}
fn is_float(&self) -> bool {
(**self).is_float()
}
fn is_bool(&self) -> bool {
(**self).is_bool()
}
fn is_string(&self) -> bool {
(**self).is_string()
}
fn is_error(&self) -> bool {
(**self).is_error()
}
fn is_datetime(&self) -> bool {
(**self).is_datetime()
}
fn is_datetime_iso(&self) -> bool {
(**self).is_datetime_iso()
}
fn is_duration_iso(&self) -> bool {
(**self).is_duration_iso()
}
fn is_formula(&self) -> bool {
(**self).is_formula()
}
fn get_int(&self) -> Option<i64> {
(**self).get_int()
}
fn get_float(&self) -> Option<f64> {
(**self).get_float()
}
fn get_bool(&self) -> Option<bool> {
(**self).get_bool()
}
fn get_string(&self) -> Option<&str> {
(**self).get_string()
}
fn get_error(&self) -> Option<&str> {
(**self).get_error()
}
fn get_error_type(&self) -> Option<CellErrorType> {
(**self).get_error_type()
}
fn get_datetime(&self) -> Option<f64> {
(**self).get_datetime()
}
fn get_formula(&self) -> Option<&str> {
(**self).get_formula()
}
fn get_datetime_iso(&self) -> Option<&str> {
(**self).get_datetime_iso()
}
fn get_duration_iso(&self) -> Option<&str> {
(**self).get_duration_iso()
}
fn cached_value(&self) -> Option<&Cell> {
(**self).cached_value()
}
fn as_string(&self) -> Option<String> {
(**self).as_string()
}
fn as_i64(&self) -> Option<i64> {
(**self).as_i64()
}
fn as_f64(&self) -> Option<f64> {
(**self).as_f64()
}
fn as_datetime(&self, date1904: bool) -> Option<ExcelDateTime> {
(**self).as_datetime(date1904)
}
#[cfg(feature = "chrono")]
fn as_naive_datetime(&self, date1904: bool) -> Option<chrono::NaiveDateTime> {
(**self).as_naive_datetime(date1904)
}
#[cfg(feature = "chrono")]
fn as_naive_date(&self, date1904: bool) -> Option<chrono::NaiveDate> {
(**self).as_naive_date(date1904)
}
#[cfg(feature = "chrono")]
fn as_date(&self, date1904: bool) -> Option<chrono::NaiveDate> {
(**self).as_date(date1904)
}
#[cfg(feature = "chrono")]
fn as_naive_time(&self, date1904: bool) -> Option<chrono::NaiveTime> {
(**self).as_naive_time(date1904)
}
#[cfg(feature = "chrono")]
fn as_time(&self, date1904: bool) -> Option<chrono::NaiveTime> {
(**self).as_time(date1904)
}
#[cfg(feature = "chrono")]
fn as_duration(&self) -> Option<chrono::Duration> {
(**self).as_duration()
}
}
impl Cell {
pub fn is_empty(&self) -> bool {
false
}
pub fn is_int(&self) -> bool {
match self {
Cell::Number(n) => n.is_finite() && n.fract() == 0.0,
Cell::Formula { cached, .. } => cached.is_int(),
_ => false,
}
}
pub fn is_float(&self) -> bool {
match self {
Cell::Number(_) => true,
Cell::Formula { cached, .. } => cached.is_float(),
_ => false,
}
}
pub fn is_bool(&self) -> bool {
match self {
Cell::Bool(_) => true,
Cell::Formula { cached, .. } => cached.is_bool(),
_ => false,
}
}
pub fn is_string(&self) -> bool {
match self {
Cell::Text(_) => true,
Cell::Formula { cached, .. } => cached.is_string(),
_ => false,
}
}
pub fn is_error(&self) -> bool {
match self {
Cell::Error(_) => true,
Cell::Formula { cached, .. } => cached.is_error(),
_ => false,
}
}
pub fn is_datetime(&self) -> bool {
match self {
Cell::Date(_) => true,
Cell::Formula { cached, .. } => cached.is_datetime(),
_ => false,
}
}
pub fn is_datetime_iso(&self) -> bool {
match self {
Cell::Formula { cached, .. } => cached.is_datetime_iso(),
_ => false,
}
}
pub fn is_duration_iso(&self) -> bool {
match self {
Cell::Formula { cached, .. } => cached.is_duration_iso(),
_ => false,
}
}
pub fn is_formula(&self) -> bool {
matches!(self, Cell::Formula { .. })
}
pub fn get_int(&self) -> Option<i64> {
match self {
Cell::Number(n) if n.is_finite() && n.fract() == 0.0 => Some(*n as i64),
Cell::Formula { cached, .. } => cached.get_int(),
_ => None,
}
}
pub fn get_float(&self) -> Option<f64> {
match self {
Cell::Number(n) => Some(*n),
Cell::Formula { cached, .. } => cached.get_float(),
_ => None,
}
}
pub fn get_bool(&self) -> Option<bool> {
match self {
Cell::Bool(b) => Some(*b),
Cell::Formula { cached, .. } => cached.get_bool(),
_ => None,
}
}
pub fn get_string(&self) -> Option<&str> {
match self {
Cell::Text(s) => Some(s.as_str()),
Cell::Formula { cached, .. } => cached.get_string(),
_ => None,
}
}
pub fn get_error(&self) -> Option<&str> {
match self {
Cell::Error(e) => Some(e.as_str()),
Cell::Formula { cached, .. } => cached.get_error(),
_ => None,
}
}
pub fn get_error_type(&self) -> Option<CellErrorType> {
match self {
Cell::Error(error) => CellErrorType::from_excel_error(error),
Cell::Formula { cached, .. } => cached.get_error_type(),
_ => None,
}
}
pub fn get_datetime(&self) -> Option<f64> {
match self {
Cell::Date(serial) => Some(*serial),
Cell::Formula { cached, .. } => cached.get_datetime(),
_ => None,
}
}
pub fn get_formula(&self) -> Option<&str> {
match self {
Cell::Formula { formula, .. } => Some(formula.as_str()),
_ => None,
}
}
pub fn get_datetime_iso(&self) -> Option<&str> {
match self {
Cell::Formula { cached, .. } => cached.get_datetime_iso(),
_ => None,
}
}
pub fn get_duration_iso(&self) -> Option<&str> {
match self {
Cell::Formula { cached, .. } => cached.get_duration_iso(),
_ => None,
}
}
pub fn cached_value(&self) -> Option<&Cell> {
match self {
Cell::Formula { cached, .. } => Some(cached.as_ref()),
_ => None,
}
}
pub fn as_string(&self) -> Option<String> {
match self {
Cell::Text(s) => Some(s.clone()),
Cell::Number(n) => Some(crate::format_number(*n)),
Cell::Formula { cached, .. } => cached.as_string(),
_ => None,
}
}
pub fn as_i64(&self) -> Option<i64> {
match self {
Cell::Number(n) | Cell::Date(n) if n.is_finite() => Some(*n as i64),
Cell::Bool(b) => Some(i64::from(*b)),
Cell::Text(s) => s.parse::<i64>().ok(),
Cell::Formula { cached, .. } => cached.as_i64(),
_ => None,
}
}
pub fn as_f64(&self) -> Option<f64> {
match self {
Cell::Number(n) | Cell::Date(n) => Some(*n),
Cell::Bool(b) => Some(f64::from(*b as u8)),
Cell::Text(s) => s.parse::<f64>().ok(),
Cell::Formula { cached, .. } => cached.as_f64(),
_ => None,
}
}
pub fn as_datetime(&self, date1904: bool) -> Option<ExcelDateTime> {
match self {
Cell::Number(serial) | Cell::Date(serial) => {
excel_serial_to_datetime(*serial, date1904)
}
Cell::Formula { cached, .. } => cached.as_datetime(date1904),
_ => None,
}
}
#[cfg(feature = "chrono")]
pub fn as_naive_datetime(&self, date1904: bool) -> Option<chrono::NaiveDateTime> {
self.as_datetime(date1904)?.to_naive_datetime()
}
#[cfg(feature = "chrono")]
pub fn as_naive_date(&self, date1904: bool) -> Option<chrono::NaiveDate> {
self.as_naive_datetime(date1904).map(|dt| dt.date())
}
#[cfg(feature = "chrono")]
pub fn as_date(&self, date1904: bool) -> Option<chrono::NaiveDate> {
self.as_naive_date(date1904)
}
#[cfg(feature = "chrono")]
pub fn as_naive_time(&self, date1904: bool) -> Option<chrono::NaiveTime> {
self.as_naive_datetime(date1904).map(|dt| dt.time())
}
#[cfg(feature = "chrono")]
pub fn as_time(&self, date1904: bool) -> Option<chrono::NaiveTime> {
self.as_naive_time(date1904)
}
#[cfg(feature = "chrono")]
pub fn as_duration(&self) -> Option<chrono::Duration> {
match self {
Cell::Number(serial) | Cell::Date(serial) => excel_serial_to_duration(*serial),
Cell::Formula { cached, .. } => cached.as_duration(),
_ => None,
}
}
}
#[cfg(feature = "serde")]
const CELL_VARIANTS: &[&str] = &["Text", "Number", "Date", "Bool", "Error", "Formula"];
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Cell {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_enum("Cell", CELL_VARIANTS, CellVisitor)
}
}
#[cfg(feature = "serde")]
struct CellVisitor;
#[cfg(feature = "serde")]
impl<'de> serde::de::Visitor<'de> for CellVisitor {
type Value = Cell;
fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("an rxls Cell")
}
fn visit_enum<A>(self, data: A) -> std::result::Result<Self::Value, A::Error>
where
A: serde::de::EnumAccess<'de>,
{
let (variant, access) = data.variant::<String>()?;
match variant.as_str() {
"Text" => access.newtype_variant().map(Cell::Text),
"Number" => access.newtype_variant().map(Cell::Number),
"Date" => access.newtype_variant().map(Cell::Date),
"Bool" => access.newtype_variant().map(Cell::Bool),
"Error" => access.newtype_variant().map(Cell::Error),
"Formula" => {
let (formula, cached): (String, Cell) = access.newtype_variant()?;
Ok(Cell::Formula {
formula,
cached: Box::new(cached),
})
}
other => Err(serde::de::Error::unknown_variant(other, CELL_VARIANTS)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Color(pub [u8; 3]);
impl Color {
pub const fn rgb(red: u8, green: u8, blue: u8) -> Self {
Self([red, green, blue])
}
pub const fn as_rgb(self) -> [u8; 3] {
self.0
}
}
impl From<[u8; 3]> for Color {
fn from(rgb: [u8; 3]) -> Self {
Self(rgb)
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub enum FormatScript {
#[default]
None,
Superscript,
Subscript,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub enum FormatPattern {
#[default]
None,
Solid,
MediumGray,
DarkGray,
LightGray,
DarkHorizontal,
DarkVertical,
DarkDown,
DarkUp,
DarkGrid,
DarkTrellis,
LightHorizontal,
LightVertical,
LightDown,
LightUp,
LightGrid,
LightTrellis,
Gray125,
Gray0625,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct Fill {
pub pattern: FormatPattern,
pub background: Option<Color>,
pub foreground: Option<Color>,
}
impl Fill {
pub fn new() -> Self {
Self::default()
}
pub fn solid(color: impl Into<Color>) -> Self {
Self {
pattern: FormatPattern::Solid,
background: Some(color.into()),
foreground: None,
}
}
pub fn with_pattern(mut self, pattern: FormatPattern) -> Self {
self.pattern = pattern;
self
}
pub fn with_background(mut self, color: impl Into<Color>) -> Self {
self.background = Some(color.into());
if self.pattern == FormatPattern::None {
self.pattern = FormatPattern::Solid;
}
self
}
pub fn with_foreground(mut self, color: impl Into<Color>) -> Self {
self.foreground = Some(color.into());
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum HAlign {
Left,
Center,
Right,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum VAlign {
Top,
Middle,
Bottom,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
pub struct Alignment {
pub horizontal: Option<HAlign>,
pub vertical: Option<VAlign>,
pub wrap: bool,
pub rotation: i16,
pub indent: u8,
pub shrink_to_fit: bool,
}
impl Alignment {
pub fn new() -> Self {
Self::default()
}
pub fn with_horizontal(mut self, horizontal: HAlign) -> Self {
self.horizontal = Some(horizontal);
self
}
pub fn with_vertical(mut self, vertical: VAlign) -> Self {
self.vertical = Some(vertical);
self
}
pub fn wrapped(mut self) -> Self {
self.wrap = true;
self
}
pub fn with_wrap(mut self, wrap: bool) -> Self {
self.wrap = wrap;
self
}
pub fn with_indent(mut self, level: u8) -> Self {
self.indent = level;
self
}
pub fn with_rotation(mut self, degrees: i16) -> Self {
self.rotation = degrees.clamp(-90, 90);
self
}
pub fn with_shrink_to_fit(mut self) -> Self {
self.shrink_to_fit = true;
self
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
pub struct Font {
pub name: Option<String>,
pub size_pt: Option<u16>,
pub color: Option<Color>,
pub bold: bool,
pub italic: bool,
pub underline: bool,
pub strikethrough: bool,
pub script: FormatScript,
}
impl Font {
pub fn new() -> Self {
Font::default()
}
pub fn with_name(mut self, name: impl AsRef<str>) -> Self {
self.name = Some(name.as_ref().to_string());
self
}
pub fn with_size(mut self, points: u16) -> Self {
self.size_pt = Some(points);
self
}
pub fn with_color(mut self, color: impl Into<Color>) -> Self {
self.color = Some(color.into());
self
}
pub fn bold(mut self) -> Self {
self.bold = true;
self
}
pub fn italic(mut self) -> Self {
self.italic = true;
self
}
pub fn underline(mut self) -> Self {
self.underline = true;
self
}
pub fn strikethrough(mut self) -> Self {
self.strikethrough = true;
self
}
pub fn with_script(mut self, script: FormatScript) -> Self {
self.script = script;
self
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
pub struct TextRun {
pub text: String,
pub font: Font,
}
impl TextRun {
pub fn new(text: impl Into<String>, font: Font) -> Self {
Self {
text: text.into(),
font,
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub enum BorderStyle {
#[default]
None,
Thin,
Medium,
Thick,
Double,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
pub struct Border {
pub left: BorderStyle,
pub right: BorderStyle,
pub top: BorderStyle,
pub bottom: BorderStyle,
pub color: Option<Color>,
pub left_color: Option<Color>,
pub right_color: Option<Color>,
pub top_color: Option<Color>,
pub bottom_color: Option<Color>,
}
impl Border {
pub fn new() -> Self {
Self::default()
}
pub fn with_all(mut self, style: BorderStyle) -> Self {
self.left = style;
self.right = style;
self.top = style;
self.bottom = style;
self
}
pub fn with_left(mut self, style: BorderStyle) -> Self {
self.left = style;
self
}
pub fn with_right(mut self, style: BorderStyle) -> Self {
self.right = style;
self
}
pub fn with_top(mut self, style: BorderStyle) -> Self {
self.top = style;
self
}
pub fn with_bottom(mut self, style: BorderStyle) -> Self {
self.bottom = style;
self
}
pub fn with_color(mut self, color: impl Into<Color>) -> Self {
self.color = Some(color.into());
self
}
pub fn with_left_color(mut self, color: impl Into<Color>) -> Self {
self.left_color = Some(color.into());
self
}
pub fn with_right_color(mut self, color: impl Into<Color>) -> Self {
self.right_color = Some(color.into());
self
}
pub fn with_top_color(mut self, color: impl Into<Color>) -> Self {
self.top_color = Some(color.into());
self
}
pub fn with_bottom_color(mut self, color: impl Into<Color>) -> Self {
self.bottom_color = Some(color.into());
self
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
pub struct CellStyle {
pub font: Option<Font>,
pub fill: Option<Color>,
pub pattern_fill: Option<Fill>,
pub border: Option<Border>,
pub num_fmt: Option<String>,
pub align: Option<Alignment>,
pub protection: Option<CellProtection>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
pub struct CellProtection {
pub locked: Option<bool>,
pub hidden: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
pub struct Format {
style: CellStyle,
}
fn merge_font(base: Option<&Font>, overlay: Option<&Font>) -> Option<Font> {
match (base, overlay) {
(None, None) => None,
(Some(base), None) => Some(base.clone()),
(None, Some(overlay)) => Some(overlay.clone()),
(Some(base), Some(overlay)) => {
let mut merged = base.clone();
if overlay.name.is_some() {
merged.name = overlay.name.clone();
}
if overlay.size_pt.is_some() {
merged.size_pt = overlay.size_pt;
}
if overlay.color.is_some() {
merged.color = overlay.color;
}
if overlay.bold {
merged.bold = true;
}
if overlay.italic {
merged.italic = true;
}
if overlay.underline {
merged.underline = true;
}
if overlay.strikethrough {
merged.strikethrough = true;
}
if overlay.script != FormatScript::None {
merged.script = overlay.script;
}
Some(merged)
}
}
}
fn merge_alignment(base: Option<&Alignment>, overlay: Option<&Alignment>) -> Option<Alignment> {
match (base, overlay) {
(None, None) => None,
(Some(base), None) => Some(base.clone()),
(None, Some(overlay)) => Some(overlay.clone()),
(Some(base), Some(overlay)) => {
let mut merged = base.clone();
if overlay.horizontal.is_some() {
merged.horizontal = overlay.horizontal;
}
if overlay.vertical.is_some() {
merged.vertical = overlay.vertical;
}
if overlay.wrap {
merged.wrap = true;
}
if overlay.rotation != 0 {
merged.rotation = overlay.rotation;
}
if overlay.indent != 0 {
merged.indent = overlay.indent;
}
if overlay.shrink_to_fit {
merged.shrink_to_fit = true;
}
Some(merged)
}
}
}
fn merge_border(base: Option<&Border>, overlay: Option<&Border>) -> Option<Border> {
match (base, overlay) {
(None, None) => None,
(Some(base), None) => Some(base.clone()),
(None, Some(overlay)) => Some(overlay.clone()),
(Some(base), Some(overlay)) => {
let mut merged = base.clone();
if overlay.left != BorderStyle::None {
merged.left = overlay.left;
}
if overlay.right != BorderStyle::None {
merged.right = overlay.right;
}
if overlay.top != BorderStyle::None {
merged.top = overlay.top;
}
if overlay.bottom != BorderStyle::None {
merged.bottom = overlay.bottom;
}
if overlay.color.is_some() {
merged.color = overlay.color;
}
if overlay.left_color.is_some() {
merged.left_color = overlay.left_color;
}
if overlay.right_color.is_some() {
merged.right_color = overlay.right_color;
}
if overlay.top_color.is_some() {
merged.top_color = overlay.top_color;
}
if overlay.bottom_color.is_some() {
merged.bottom_color = overlay.bottom_color;
}
Some(merged)
}
}
}
fn merge_protection(
base: Option<&CellProtection>,
overlay: Option<&CellProtection>,
) -> Option<CellProtection> {
match (base, overlay) {
(None, None) => None,
(Some(base), None) => Some(base.clone()),
(None, Some(overlay)) => Some(overlay.clone()),
(Some(base), Some(overlay)) => {
let mut merged = base.clone();
if overlay.locked.is_some() {
merged.locked = overlay.locked;
}
if overlay.hidden {
merged.hidden = true;
}
Some(merged)
}
}
}
impl Format {
pub fn new() -> Self {
Self::default()
}
pub fn from_cell_style(style: CellStyle) -> Self {
Self { style }
}
pub fn as_cell_style(&self) -> &CellStyle {
&self.style
}
pub fn into_cell_style(self) -> CellStyle {
self.style
}
pub fn merge(&self, overlay: &Format) -> Self {
Self {
style: self.style.merge(overlay.as_cell_style()),
}
}
pub fn font_name(mut self, name: impl AsRef<str>) -> Self {
self.style = self.style.font_name(name);
self
}
pub fn size(mut self, points: u16) -> Self {
self.style = self.style.size(points);
self
}
pub fn color(mut self, color: impl Into<Color>) -> Self {
self.style = self.style.color(color);
self
}
pub fn bold(mut self) -> Self {
self.style = self.style.bold();
self
}
pub fn italic(mut self) -> Self {
self.style = self.style.italic();
self
}
pub fn underline(mut self) -> Self {
self.style = self.style.underline();
self
}
pub fn strikethrough(mut self) -> Self {
self.style = self.style.strikethrough();
self
}
pub fn font_script(mut self, script: FormatScript) -> Self {
self.style = self.style.font_script(script);
self
}
pub fn fill(mut self, color: impl Into<Color>) -> Self {
self.style = self.style.fill(color);
self
}
pub fn pattern(mut self, pattern: FormatPattern) -> Self {
self.style = self.style.pattern(pattern);
self
}
pub fn background_color(mut self, color: impl Into<Color>) -> Self {
self.style = self.style.background_color(color);
self
}
pub fn foreground_color(mut self, color: impl Into<Color>) -> Self {
self.style = self.style.foreground_color(color);
self
}
pub fn pattern_fill(mut self, fill: Fill) -> Self {
self.style = self.style.pattern_fill(fill);
self
}
pub fn num_fmt(mut self, code: impl AsRef<str>) -> Self {
self.style = self.style.num_fmt(code);
self
}
pub fn wrap(mut self) -> Self {
self.style = self.style.wrap();
self
}
pub fn align(mut self, h: HAlign) -> Self {
self.style = self.style.align(h);
self
}
pub fn valign(mut self, v: VAlign) -> Self {
self.style = self.style.valign(v);
self
}
pub fn alignment(mut self, alignment: Alignment) -> Self {
self.style = self.style.alignment(alignment);
self
}
pub fn indent(mut self, level: u8) -> Self {
self.style = self.style.indent(level);
self
}
pub fn shrink_to_fit(mut self) -> Self {
self.style = self.style.shrink_to_fit();
self
}
pub fn text_rotation(mut self, degrees: i16) -> Self {
self.style = self.style.text_rotation(degrees);
self
}
pub fn locked(mut self) -> Self {
self.style = self.style.locked();
self
}
pub fn unlocked(mut self) -> Self {
self.style = self.style.unlocked();
self
}
pub fn hidden(mut self) -> Self {
self.style = self.style.hidden();
self
}
pub fn border(mut self, border: Border) -> Self {
self.style = self.style.border(border);
self
}
pub fn border_top(mut self, style: FormatBorder) -> Self {
self.style = self.style.border_top(style);
self
}
pub fn border_bottom(mut self, style: FormatBorder) -> Self {
self.style = self.style.border_bottom(style);
self
}
pub fn border_left(mut self, style: FormatBorder) -> Self {
self.style = self.style.border_left(style);
self
}
pub fn border_right(mut self, style: FormatBorder) -> Self {
self.style = self.style.border_right(style);
self
}
pub fn border_top_color(mut self, color: impl Into<Color>) -> Self {
self.style = self.style.border_top_color(color);
self
}
pub fn border_bottom_color(mut self, color: impl Into<Color>) -> Self {
self.style = self.style.border_bottom_color(color);
self
}
pub fn border_left_color(mut self, color: impl Into<Color>) -> Self {
self.style = self.style.border_left_color(color);
self
}
pub fn border_right_color(mut self, color: impl Into<Color>) -> Self {
self.style = self.style.border_right_color(color);
self
}
pub fn set_font_name(self, name: impl AsRef<str>) -> Self {
self.font_name(name)
}
pub fn set_font_size(self, points: u16) -> Self {
self.size(points)
}
pub fn set_font_color(self, color: impl Into<Color>) -> Self {
self.color(color)
}
pub fn set_bold(self) -> Self {
self.bold()
}
pub fn set_italic(self) -> Self {
self.italic()
}
pub fn set_underline(self) -> Self {
self.underline()
}
pub fn set_font_strikethrough(self) -> Self {
self.strikethrough()
}
pub fn set_strikethrough(self) -> Self {
self.strikethrough()
}
pub fn set_font_script(self, script: FormatScript) -> Self {
self.font_script(script)
}
pub fn set_bg_color(self, color: impl Into<Color>) -> Self {
self.fill(color)
}
pub fn set_background_color(self, color: impl Into<Color>) -> Self {
self.background_color(color)
}
pub fn set_foreground_color(self, color: impl Into<Color>) -> Self {
self.foreground_color(color)
}
pub fn set_pattern_fill(self, fill: Fill) -> Self {
self.pattern_fill(fill)
}
pub fn set_pattern(self, pattern: FormatPattern) -> Self {
self.pattern(pattern)
}
pub fn set_num_format(self, code: impl AsRef<str>) -> Self {
self.num_fmt(code)
}
pub fn set_align(self, h: FormatAlign) -> Self {
self.align(h)
}
pub fn set_valign(self, v: VAlign) -> Self {
self.valign(v)
}
pub fn set_alignment(self, alignment: Alignment) -> Self {
self.alignment(alignment)
}
pub fn set_text_wrap(self) -> Self {
self.wrap()
}
pub fn set_indent(self, level: u8) -> Self {
self.indent(level)
}
pub fn set_shrink_to_fit(self) -> Self {
self.shrink_to_fit()
}
pub fn set_text_rotation(self, degrees: i16) -> Self {
self.text_rotation(degrees)
}
pub fn set_locked(self) -> Self {
self.locked()
}
pub fn set_unlocked(self) -> Self {
self.unlocked()
}
pub fn set_hidden(self) -> Self {
self.hidden()
}
pub fn set_border(mut self, style: FormatBorder) -> Self {
self.style = self.style.set_border(style);
self
}
pub fn set_border_top(self, style: FormatBorder) -> Self {
self.border_top(style)
}
pub fn set_border_bottom(self, style: FormatBorder) -> Self {
self.border_bottom(style)
}
pub fn set_border_left(self, style: FormatBorder) -> Self {
self.border_left(style)
}
pub fn set_border_right(self, style: FormatBorder) -> Self {
self.border_right(style)
}
pub fn set_border_top_color(self, color: impl Into<Color>) -> Self {
self.border_top_color(color)
}
pub fn set_border_bottom_color(self, color: impl Into<Color>) -> Self {
self.border_bottom_color(color)
}
pub fn set_border_left_color(self, color: impl Into<Color>) -> Self {
self.border_left_color(color)
}
pub fn set_border_right_color(self, color: impl Into<Color>) -> Self {
self.border_right_color(color)
}
pub fn set_border_color(mut self, color: impl Into<Color>) -> Self {
self.style = self.style.set_border_color(color);
self
}
}
impl From<CellStyle> for Format {
fn from(style: CellStyle) -> Self {
Self::from_cell_style(style)
}
}
impl From<Format> for CellStyle {
fn from(format: Format) -> Self {
format.into_cell_style()
}
}
impl std::ops::Deref for Format {
type Target = CellStyle;
fn deref(&self) -> &Self::Target {
self.as_cell_style()
}
}
impl std::ops::DerefMut for Format {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.style
}
}
pub type FormatAlign = HAlign;
pub type FormatBorder = BorderStyle;
#[derive(Debug, Default, PartialEq, Eq, Hash, Ord, PartialOrd, Copy, Clone)]
pub struct Dimensions {
pub start: (u32, u32),
pub end: (u32, u32),
}
impl Dimensions {
pub fn new(start: (u32, u32), end: (u32, u32)) -> Self {
Self { start, end }
}
pub fn from_range_tuple(range: (u32, u16, u32, u16)) -> Self {
Self::new((range.0, u32::from(range.1)), (range.2, u32::from(range.3)))
}
pub fn contains(&self, row: u32, col: u32) -> bool {
!self.is_empty()
&& row >= self.start.0
&& row <= self.end.0
&& col >= self.start.1
&& col <= self.end.1
}
pub fn len(&self) -> u64 {
if self.is_empty() {
0
} else {
(u64::from(self.end.0) - u64::from(self.start.0) + 1)
* (u64::from(self.end.1) - u64::from(self.start.1) + 1)
}
}
pub fn is_empty(&self) -> bool {
self.start.0 > self.end.0 || self.start.1 > self.end.1
}
}
impl From<(u32, u16, u32, u16)> for Dimensions {
fn from(range: (u32, u16, u32, u16)) -> Self {
Self::from_range_tuple(range)
}
}
#[derive(Debug, Clone)]
pub(crate) struct CellEntry {
pub(crate) row: u32,
pub(crate) col: u16,
pub(crate) value: Cell,
pub(crate) text: String,
#[cfg_attr(not(feature = "xlsx"), allow(dead_code))]
pub(crate) style: Option<CellStyle>,
#[cfg_attr(not(feature = "xlsx"), allow(dead_code))]
pub(crate) hyperlink: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct Range<'a> {
start: Option<(u32, u16)>,
end: Option<(u32, u16)>,
cells: BTreeMap<(u32, u16), RangeCell<'a>>,
}
#[derive(Debug, Clone, Default)]
pub struct FormulaRange<'a> {
start: Option<(u32, u16)>,
end: Option<(u32, u16)>,
formulas: BTreeMap<(u32, u16), FormulaEntry<'a>>,
}
#[derive(Debug, Clone)]
enum RangeCell<'a> {
Borrowed { value: &'a Cell, text: &'a str },
Owned { value: Cell, text: String },
}
impl RangeCell<'_> {
fn value(&self) -> &Cell {
match self {
RangeCell::Borrowed { value, .. } => value,
RangeCell::Owned { value, .. } => value,
}
}
fn text(&self) -> &str {
match self {
RangeCell::Borrowed { text, .. } => text,
RangeCell::Owned { text, .. } => text,
}
}
}
impl<'left, 'right> PartialEq<RangeCell<'right>> for RangeCell<'left> {
fn eq(&self, other: &RangeCell<'right>) -> bool {
self.value() == other.value() && self.text() == other.text()
}
}
#[derive(Debug, Clone)]
enum FormulaEntry<'a> {
Borrowed(&'a str),
Owned(String),
}
impl FormulaEntry<'_> {
fn as_str(&self) -> &str {
match self {
FormulaEntry::Borrowed(formula) => formula,
FormulaEntry::Owned(formula) => formula.as_str(),
}
}
}
impl<'left, 'right> PartialEq<FormulaEntry<'right>> for FormulaEntry<'left> {
fn eq(&self, other: &FormulaEntry<'right>) -> bool {
self.as_str() == other.as_str()
}
}
impl<'a> Eq for FormulaEntry<'a> {}
impl<'left, 'right> PartialEq<Range<'right>> for Range<'left> {
fn eq(&self, other: &Range<'right>) -> bool {
self.start == other.start
&& self.end == other.end
&& self.cells.len() == other.cells.len()
&& self
.cells
.iter()
.zip(other.cells.iter())
.all(|(left, right)| left.0 == right.0 && left.1 == right.1)
}
}
impl<'left, 'right> PartialEq<FormulaRange<'right>> for FormulaRange<'left> {
fn eq(&self, other: &FormulaRange<'right>) -> bool {
self.start == other.start
&& self.end == other.end
&& self.formulas.len() == other.formulas.len()
&& self
.formulas
.iter()
.zip(other.formulas.iter())
.all(|(left, right)| left.0 == right.0 && left.1 == right.1)
}
}
impl<'a> Eq for FormulaRange<'a> {}
fn row_span_len(start: u32, end: u32) -> usize {
if start > end {
return 0;
}
let span = u64::from(end) - u64::from(start) + 1;
usize::try_from(span).unwrap_or(usize::MAX)
}
fn col_span_len(start: u16, end: u16) -> usize {
if start > end {
return 0;
}
usize::from(end) - usize::from(start) + 1
}
impl<'a> Range<'a> {
pub fn new(start: (u32, u32), end: (u32, u32)) -> Self {
assert!(
start.0 <= end.0 && start.1 <= end.1,
"range start must not be after range end"
);
let start_col =
u16::try_from(start.1).expect("range start column exceeds rxls worksheet grid");
let end_col = u16::try_from(end.1).expect("range end column exceeds rxls worksheet grid");
Self {
start: Some((start.0, start_col)),
end: Some((end.0, end_col)),
cells: BTreeMap::new(),
}
}
pub fn empty() -> Self {
Self {
start: None,
end: None,
cells: BTreeMap::new(),
}
}
pub fn from_sparse<I, V>(cells: I) -> Self
where
I: IntoIterator<Item = ((u32, u32), V)>,
V: Into<Cell>,
{
let mut start: Option<(u32, u16)> = None;
let mut end: Option<(u32, u16)> = None;
let mut entries = BTreeMap::new();
for ((row, col), value) in cells {
let col = u16::try_from(col).expect("range column exceeds rxls worksheet grid");
start = Some(match start {
Some((r0, c0)) => (r0.min(row), c0.min(col)),
None => (row, col),
});
end = Some(match end {
Some((r1, c1)) => (r1.max(row), c1.max(col)),
None => (row, col),
});
let value = value.into();
let text = display_text(&value);
entries.insert((row, col), RangeCell::Owned { value, text });
}
Self {
start,
end,
cells: entries,
}
}
pub fn set_value(&mut self, pos: (u32, u32), value: impl Into<Cell>) {
let col = u16::try_from(pos.1).expect("range column exceeds rxls worksheet grid");
let row = pos.0;
match (self.start, self.end) {
(Some((r0, c0)), Some((r1, c1))) => {
assert!(
row >= r0 && col >= c0,
"range value position must not be above or left of range start"
);
self.end = Some((r1.max(row), c1.max(col)));
}
_ => {
self.start = Some((row, col));
self.end = Some((row, col));
}
}
let value = value.into();
let text = display_text(&value);
self.cells
.insert((row, col), RangeCell::Owned { value, text });
}
fn from_sheet(sheet: &'a Sheet) -> Self {
let mut cells = BTreeMap::new();
for c in &sheet.cells {
cells.insert(
(c.row, c.col),
RangeCell::Borrowed {
value: &c.value,
text: c.text.as_str(),
},
);
}
let start = cells
.keys()
.fold(None, |acc: Option<(u32, u16)>, &(row, col)| match acc {
Some((r0, c0)) => Some((r0.min(row), c0.min(col))),
None => Some((row, col)),
});
let end = cells
.keys()
.fold(None, |acc: Option<(u32, u16)>, &(row, col)| match acc {
Some((r1, c1)) => Some((r1.max(row), c1.max(col))),
None => Some((row, col)),
});
Self { start, end, cells }
}
pub fn is_empty(&self) -> bool {
self.start.is_none() || self.end.is_none()
}
pub fn start(&self) -> Option<(u32, u32)> {
self.start.map(|(row, col)| (row, u32::from(col)))
}
pub fn end(&self) -> Option<(u32, u32)> {
self.end.map(|(row, col)| (row, u32::from(col)))
}
pub fn dimensions_info(&self) -> Option<Dimensions> {
match (self.start, self.end) {
(Some((r0, c0)), Some((r1, c1))) => {
Some(Dimensions::new((r0, u32::from(c0)), (r1, u32::from(c1))))
}
_ => None,
}
}
pub fn height(&self) -> usize {
match (self.start, self.end) {
(Some((r0, _)), Some((r1, _))) => row_span_len(r0, r1),
_ => 0,
}
}
pub fn width(&self) -> usize {
match (self.start, self.end) {
(Some((_, c0)), Some((_, c1))) => col_span_len(c0, c1),
_ => 0,
}
}
pub fn size(&self) -> (usize, usize) {
(self.height(), self.width())
}
pub fn get_size(&self) -> (usize, usize) {
self.size()
}
pub fn range(&self, start: (u32, u32), end: (u32, u32)) -> Self {
if start.0 > end.0 || start.1 > end.1 {
return Self::empty();
}
let Some(start_col) = u16::try_from(start.1).ok() else {
return Self::empty();
};
let end_col = u16::try_from(end.1).unwrap_or(u16::MAX);
if start_col > end_col {
return Self::empty();
}
let start = (start.0, start_col);
let end = (end.0, end_col);
let cells = self
.cells
.iter()
.filter(|&(&(row, col), _)| {
row >= start.0 && row <= end.0 && col >= start.1 && col <= end.1
})
.map(|(&(row, col), entry)| ((row, col), entry.clone()))
.collect();
Self {
start: Some(start),
end: Some(end),
cells,
}
}
pub fn get(&self, pos: (usize, usize)) -> Option<&Cell> {
let (r0, c0) = self.start?;
let (r1, c1) = self.end?;
let row = r0.checked_add(u32::try_from(pos.0).ok()?)?;
let col = c0.checked_add(u16::try_from(pos.1).ok()?)?;
if row > r1 || col > c1 {
return None;
}
self.get_abs(row, col)
}
pub fn get_abs(&self, row: u32, col: u16) -> Option<&Cell> {
self.entry_abs(row, col).map(RangeCell::value)
}
fn entry_abs(&self, row: u32, col: u16) -> Option<&RangeCell<'a>> {
self.cells.get(&(row, col))
}
pub fn get_value(&self, pos: (u32, u32)) -> Option<&Cell> {
let col = u16::try_from(pos.1).ok()?;
self.get_abs(pos.0, col)
}
pub fn formatted(&self, pos: (usize, usize)) -> Option<&str> {
let (r0, c0) = self.start?;
let (r1, c1) = self.end?;
let row = r0.checked_add(u32::try_from(pos.0).ok()?)?;
let col = c0.checked_add(u16::try_from(pos.1).ok()?)?;
if row > r1 || col > c1 {
return None;
}
self.formatted_abs(row, col)
}
pub fn formatted_abs(&self, row: u32, col: u16) -> Option<&str> {
self.entry_abs(row, col).map(RangeCell::text)
}
pub fn headers(&self) -> Option<Vec<String>> {
let (row, c0, c1) = match (self.start, self.end) {
(Some((row, c0)), Some((_, c1))) => (row, c0, c1),
_ => return None,
};
Some(
(c0..=c1)
.map(|col| {
self.formatted_abs(row, col)
.map(str::to_string)
.unwrap_or_default()
})
.collect(),
)
}
pub fn rows(
&self,
) -> impl DoubleEndedIterator<Item = Vec<Option<&Cell>>>
+ ExactSizeIterator
+ std::iter::FusedIterator
+ '_ {
let (r0, c0, r1, c1) = match (self.start, self.end) {
(Some((r0, c0)), Some((r1, c1))) => (r0, c0, r1, c1),
_ => (1, 1, 0, 0),
};
let row_count = row_span_len(r0, r1);
(0..row_count).map(move |row_idx| {
let row = r0 + row_idx as u32;
(c0..=c1).map(move |col| self.get_abs(row, col)).collect()
})
}
pub fn row_views(&self) -> RangeRows<'_, 'a> {
match (self.start, self.end) {
(Some((r0, c0)), Some((r1, c1))) => RangeRows {
range: self,
next_row: r0,
end_row: r1,
start_col: c0,
end_col: c1,
done: false,
},
_ => RangeRows {
range: self,
next_row: 0,
end_row: 0,
start_col: 0,
end_col: 0,
done: true,
},
}
}
pub fn used_cells(
&self,
) -> impl DoubleEndedIterator<Item = (u32, u16, &Cell)>
+ ExactSizeIterator
+ std::iter::FusedIterator
+ '_ {
let (r0, c0) = self.start.unwrap_or((0, 0));
self.cells
.iter()
.map(move |(&(row, col), entry)| (row - r0, col - c0, entry.value()))
}
pub fn used_cells_abs(
&self,
) -> impl DoubleEndedIterator<Item = (u32, u16, &Cell)>
+ ExactSizeIterator
+ std::iter::FusedIterator
+ '_ {
self.cells
.iter()
.map(|(&(row, col), entry)| (row, col, entry.value()))
}
pub fn cells(
&self,
) -> impl DoubleEndedIterator<Item = (usize, usize, Option<&Cell>)>
+ ExactSizeIterator
+ std::iter::FusedIterator
+ '_ {
let (r0, c0, r1, c1) = match (self.start, self.end) {
(Some((r0, c0)), Some((r1, c1))) => (r0, c0, r1, c1),
_ => (1, 1, 0, 0),
};
let row_count = row_span_len(r0, r1);
let width = col_span_len(c0, c1);
(0..row_count * width).map(move |idx| {
let row_idx = idx / width;
let col_idx = idx % width;
let row = r0 + row_idx as u32;
let col = c0 + col_idx as u16;
(row_idx, col_idx, self.get_abs(row, col))
})
}
#[cfg(feature = "serde")]
pub fn deserialize<D>(&'a self) -> std::result::Result<RangeDeserializer<'a, D>, DeError>
where
D: serde::Deserialize<'a>,
{
RangeDeserializerBuilder::new().from_range(self)
}
}
#[derive(Debug, Clone)]
pub struct RangeRows<'range, 'cell> {
range: &'range Range<'cell>,
next_row: u32,
end_row: u32,
start_col: u16,
end_col: u16,
done: bool,
}
impl<'range, 'cell> Iterator for RangeRows<'range, 'cell> {
type Item = RangeRow<'range, 'cell>;
fn next(&mut self) -> Option<Self::Item> {
if self.done || self.next_row > self.end_row {
return None;
}
let row = self.next_row;
if row == self.end_row {
self.done = true;
} else {
self.next_row = row + 1;
}
Some(RangeRow {
range: self.range,
row,
start_col: self.start_col,
end_col: self.end_col,
})
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = if self.done || self.next_row > self.end_row {
0
} else {
(self.end_row - self.next_row + 1) as usize
};
(len, Some(len))
}
}
impl<'range, 'cell> ExactSizeIterator for RangeRows<'range, 'cell> {}
impl<'range, 'cell> DoubleEndedIterator for RangeRows<'range, 'cell> {
fn next_back(&mut self) -> Option<Self::Item> {
if self.done || self.next_row > self.end_row {
return None;
}
let row = self.end_row;
if row == self.next_row {
self.done = true;
} else {
self.end_row = row - 1;
}
Some(RangeRow {
range: self.range,
row,
start_col: self.start_col,
end_col: self.end_col,
})
}
}
impl<'range, 'cell> std::iter::FusedIterator for RangeRows<'range, 'cell> {}
#[derive(Debug, Clone, Copy)]
pub struct RangeRow<'range, 'cell> {
range: &'range Range<'cell>,
row: u32,
start_col: u16,
end_col: u16,
}
impl<'range, 'cell> RangeRow<'range, 'cell> {
pub fn row(&self) -> u32 {
self.row
}
pub fn start_col(&self) -> u16 {
self.start_col
}
pub fn end_col(&self) -> u16 {
self.end_col
}
pub fn len(&self) -> usize {
col_span_len(self.start_col, self.end_col)
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn get(&self, col: usize) -> Option<&Cell> {
let col = self.start_col.checked_add(u16::try_from(col).ok()?)?;
if col > self.end_col {
return None;
}
self.range.get_abs(self.row, col)
}
pub fn get_abs(&self, col: u16) -> Option<&Cell> {
if col < self.start_col || col > self.end_col {
return None;
}
self.range.get_abs(self.row, col)
}
pub fn iter(&self) -> RangeRowCells<'range, 'cell> {
RangeRowCells {
range: self.range,
row: self.row,
next_col: self.start_col,
end_col: self.end_col,
done: false,
}
}
pub fn cells(
&self,
) -> impl DoubleEndedIterator<Item = (usize, Option<&'range Cell>)>
+ ExactSizeIterator
+ std::iter::FusedIterator
+ '_ {
self.iter().enumerate()
}
pub fn used_cells(&self) -> RangeRowUsedCells<'range, 'cell> {
let bounds = (self.row, self.start_col)..=(self.row, self.end_col);
let remaining = self.range.cells.range(bounds.clone()).count();
RangeRowUsedCells {
entries: self.range.cells.range(bounds),
remaining,
}
}
}
#[derive(Debug, Clone)]
pub struct RangeRowUsedCells<'range, 'cell> {
entries: BTreeMapRange<'range, (u32, u16), RangeCell<'cell>>,
remaining: usize,
}
impl<'range, 'cell> Iterator for RangeRowUsedCells<'range, 'cell> {
type Item = (u16, &'range Cell);
fn next(&mut self) -> Option<Self::Item> {
let (&(_, col), entry) = self.entries.next()?;
self.remaining = self.remaining.saturating_sub(1);
Some((col, entry.value()))
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining, Some(self.remaining))
}
}
impl<'range, 'cell> ExactSizeIterator for RangeRowUsedCells<'range, 'cell> {
fn len(&self) -> usize {
self.remaining
}
}
impl<'range, 'cell> DoubleEndedIterator for RangeRowUsedCells<'range, 'cell> {
fn next_back(&mut self) -> Option<Self::Item> {
let (&(_, col), entry) = self.entries.next_back()?;
self.remaining = self.remaining.saturating_sub(1);
Some((col, entry.value()))
}
}
impl<'range, 'cell> std::iter::FusedIterator for RangeRowUsedCells<'range, 'cell> {}
#[derive(Debug, Clone)]
pub struct RangeRowCells<'range, 'cell> {
range: &'range Range<'cell>,
row: u32,
next_col: u16,
end_col: u16,
done: bool,
}
impl<'range, 'cell> Iterator for RangeRowCells<'range, 'cell> {
type Item = Option<&'range Cell>;
fn next(&mut self) -> Option<Self::Item> {
if self.done || self.next_col > self.end_col {
return None;
}
let col = self.next_col;
if col == self.end_col {
self.done = true;
} else {
self.next_col = col + 1;
}
Some(self.range.get_abs(self.row, col))
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = if self.done || self.next_col > self.end_col {
0
} else {
usize::from(self.end_col - self.next_col) + 1
};
(len, Some(len))
}
}
impl<'range, 'cell> ExactSizeIterator for RangeRowCells<'range, 'cell> {}
impl<'range, 'cell> DoubleEndedIterator for RangeRowCells<'range, 'cell> {
fn next_back(&mut self) -> Option<Self::Item> {
if self.done || self.next_col > self.end_col {
return None;
}
let col = self.end_col;
if col == self.next_col {
self.done = true;
} else {
self.end_col = col - 1;
}
Some(self.range.get_abs(self.row, col))
}
}
impl<'range, 'cell> std::iter::FusedIterator for RangeRowCells<'range, 'cell> {}
impl<'a> FormulaRange<'a> {
pub fn new(start: (u32, u32), end: (u32, u32)) -> Self {
assert!(
start.0 <= end.0 && start.1 <= end.1,
"formula range start must not be after range end"
);
let start_col =
u16::try_from(start.1).expect("formula range start column exceeds rxls worksheet grid");
let end_col =
u16::try_from(end.1).expect("formula range end column exceeds rxls worksheet grid");
Self {
start: Some((start.0, start_col)),
end: Some((end.0, end_col)),
formulas: BTreeMap::new(),
}
}
pub fn empty() -> Self {
Self {
start: None,
end: None,
formulas: BTreeMap::new(),
}
}
pub fn from_sparse<I, S>(formulas: I) -> Self
where
I: IntoIterator<Item = ((u32, u32), S)>,
S: Into<String>,
{
let mut start: Option<(u32, u16)> = None;
let mut end: Option<(u32, u16)> = None;
let mut entries = BTreeMap::new();
for ((row, col), formula) in formulas {
let col = u16::try_from(col).expect("formula range column exceeds rxls worksheet grid");
start = Some(match start {
Some((r0, c0)) => (r0.min(row), c0.min(col)),
None => (row, col),
});
end = Some(match end {
Some((r1, c1)) => (r1.max(row), c1.max(col)),
None => (row, col),
});
entries.insert((row, col), FormulaEntry::Owned(formula.into()));
}
Self {
start,
end,
formulas: entries,
}
}
pub fn set_value(&mut self, pos: (u32, u32), formula: impl Into<String>) {
let col = u16::try_from(pos.1).expect("formula range column exceeds rxls worksheet grid");
let row = pos.0;
match (self.start, self.end) {
(Some((r0, c0)), Some((r1, c1))) => {
assert!(
row >= r0 && col >= c0,
"formula range value position must not be above or left of range start"
);
self.end = Some((r1.max(row), c1.max(col)));
}
_ => {
self.start = Some((row, col));
self.end = Some((row, col));
}
}
self.formulas
.insert((row, col), FormulaEntry::Owned(formula.into()));
}
fn from_sheet(sheet: &'a Sheet) -> Self {
let mut formulas = BTreeMap::new();
for c in &sheet.cells {
if let Cell::Formula { formula, .. } = &c.value {
formulas.insert((c.row, c.col), FormulaEntry::Borrowed(formula.as_str()));
}
}
let start = formulas
.keys()
.fold(None, |acc: Option<(u32, u16)>, &(row, col)| match acc {
Some((r0, c0)) => Some((r0.min(row), c0.min(col))),
None => Some((row, col)),
});
let end = formulas
.keys()
.fold(None, |acc: Option<(u32, u16)>, &(row, col)| match acc {
Some((r1, c1)) => Some((r1.max(row), c1.max(col))),
None => Some((row, col)),
});
Self {
start,
end,
formulas,
}
}
pub fn is_empty(&self) -> bool {
self.start.is_none() || self.end.is_none()
}
pub fn start(&self) -> Option<(u32, u32)> {
self.start.map(|(row, col)| (row, u32::from(col)))
}
pub fn end(&self) -> Option<(u32, u32)> {
self.end.map(|(row, col)| (row, u32::from(col)))
}
pub fn dimensions_info(&self) -> Option<Dimensions> {
match (self.start, self.end) {
(Some((r0, c0)), Some((r1, c1))) => {
Some(Dimensions::new((r0, u32::from(c0)), (r1, u32::from(c1))))
}
_ => None,
}
}
pub fn height(&self) -> usize {
match (self.start, self.end) {
(Some((r0, _)), Some((r1, _))) => row_span_len(r0, r1),
_ => 0,
}
}
pub fn width(&self) -> usize {
match (self.start, self.end) {
(Some((_, c0)), Some((_, c1))) => col_span_len(c0, c1),
_ => 0,
}
}
pub fn size(&self) -> (usize, usize) {
(self.height(), self.width())
}
pub fn get_size(&self) -> (usize, usize) {
self.size()
}
pub fn range(&self, start: (u32, u32), end: (u32, u32)) -> Self {
if start.0 > end.0 || start.1 > end.1 {
return Self::empty();
}
let Some(start_col) = u16::try_from(start.1).ok() else {
return Self::empty();
};
let end_col = u16::try_from(end.1).unwrap_or(u16::MAX);
if start_col > end_col {
return Self::empty();
}
let start = (start.0, start_col);
let end = (end.0, end_col);
let formulas = self
.formulas
.iter()
.filter(|&(&(row, col), _)| {
row >= start.0 && row <= end.0 && col >= start.1 && col <= end.1
})
.map(|(&(row, col), formula)| ((row, col), formula.clone()))
.collect();
Self {
start: Some(start),
end: Some(end),
formulas,
}
}
pub fn get(&self, pos: (usize, usize)) -> Option<&str> {
let (r0, c0) = self.start?;
let (r1, c1) = self.end?;
let row = r0.checked_add(u32::try_from(pos.0).ok()?)?;
let col = c0.checked_add(u16::try_from(pos.1).ok()?)?;
if row > r1 || col > c1 {
return None;
}
self.get_abs(row, col)
}
pub fn get_abs(&self, row: u32, col: u16) -> Option<&str> {
self.formulas.get(&(row, col)).map(FormulaEntry::as_str)
}
pub fn get_value(&self, pos: (u32, u32)) -> Option<&str> {
let col = u16::try_from(pos.1).ok()?;
self.get_abs(pos.0, col)
}
pub fn headers(&self) -> Option<Vec<String>> {
let (row, c0, c1) = match (self.start, self.end) {
(Some((row, c0)), Some((_, c1))) => (row, c0, c1),
_ => return None,
};
Some(
(c0..=c1)
.map(|col| {
self.get_abs(row, col)
.map(str::to_string)
.unwrap_or_default()
})
.collect(),
)
}
pub fn rows(
&self,
) -> impl DoubleEndedIterator<Item = Vec<Option<&str>>>
+ ExactSizeIterator
+ std::iter::FusedIterator
+ '_ {
let (r0, c0, r1, c1) = match (self.start, self.end) {
(Some((r0, c0)), Some((r1, c1))) => (r0, c0, r1, c1),
_ => (1, 1, 0, 0),
};
let row_count = row_span_len(r0, r1);
(0..row_count).map(move |row_idx| {
let row = r0 + row_idx as u32;
(c0..=c1).map(move |col| self.get_abs(row, col)).collect()
})
}
pub fn row_views(&self) -> FormulaRangeRows<'_, 'a> {
match (self.start, self.end) {
(Some((r0, c0)), Some((r1, c1))) => FormulaRangeRows {
range: self,
next_row: r0,
end_row: r1,
start_col: c0,
end_col: c1,
done: false,
},
_ => FormulaRangeRows {
range: self,
next_row: 0,
end_row: 0,
start_col: 0,
end_col: 0,
done: true,
},
}
}
pub fn cells(
&self,
) -> impl DoubleEndedIterator<Item = (usize, usize, Option<&str>)>
+ ExactSizeIterator
+ std::iter::FusedIterator
+ '_ {
let (r0, c0, r1, c1) = match (self.start, self.end) {
(Some((r0, c0)), Some((r1, c1))) => (r0, c0, r1, c1),
_ => (1, 1, 0, 0),
};
let row_count = row_span_len(r0, r1);
let width = col_span_len(c0, c1);
(0..row_count * width).map(move |idx| {
let row_idx = idx / width;
let col_idx = idx % width;
let row = r0 + row_idx as u32;
let col = c0 + col_idx as u16;
(row_idx, col_idx, self.get_abs(row, col))
})
}
pub fn used_cells(
&self,
) -> impl DoubleEndedIterator<Item = (u32, u16, &str)>
+ ExactSizeIterator
+ std::iter::FusedIterator
+ '_ {
let (r0, c0) = self.start.unwrap_or((0, 0));
self.formulas
.iter()
.map(move |(&(row, col), formula)| (row - r0, col - c0, formula.as_str()))
}
pub fn used_cells_abs(
&self,
) -> impl DoubleEndedIterator<Item = (u32, u16, &str)>
+ ExactSizeIterator
+ std::iter::FusedIterator
+ '_ {
self.formulas
.iter()
.map(|(&(row, col), formula)| (row, col, formula.as_str()))
}
}
#[derive(Debug, Clone)]
pub struct FormulaRangeRows<'range, 'formula> {
range: &'range FormulaRange<'formula>,
next_row: u32,
end_row: u32,
start_col: u16,
end_col: u16,
done: bool,
}
impl<'range, 'formula> Iterator for FormulaRangeRows<'range, 'formula> {
type Item = FormulaRangeRow<'range, 'formula>;
fn next(&mut self) -> Option<Self::Item> {
if self.done || self.next_row > self.end_row {
return None;
}
let row = self.next_row;
if row == self.end_row {
self.done = true;
} else {
self.next_row = row + 1;
}
Some(FormulaRangeRow {
range: self.range,
row,
start_col: self.start_col,
end_col: self.end_col,
})
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = if self.done || self.next_row > self.end_row {
0
} else {
(self.end_row - self.next_row + 1) as usize
};
(len, Some(len))
}
}
impl<'range, 'formula> ExactSizeIterator for FormulaRangeRows<'range, 'formula> {}
impl<'range, 'formula> DoubleEndedIterator for FormulaRangeRows<'range, 'formula> {
fn next_back(&mut self) -> Option<Self::Item> {
if self.done || self.next_row > self.end_row {
return None;
}
let row = self.end_row;
if row == self.next_row {
self.done = true;
} else {
self.end_row = row - 1;
}
Some(FormulaRangeRow {
range: self.range,
row,
start_col: self.start_col,
end_col: self.end_col,
})
}
}
impl<'range, 'formula> std::iter::FusedIterator for FormulaRangeRows<'range, 'formula> {}
#[derive(Debug, Clone, Copy)]
pub struct FormulaRangeRow<'range, 'formula> {
range: &'range FormulaRange<'formula>,
row: u32,
start_col: u16,
end_col: u16,
}
impl<'range, 'formula> FormulaRangeRow<'range, 'formula> {
pub fn row(&self) -> u32 {
self.row
}
pub fn start_col(&self) -> u16 {
self.start_col
}
pub fn end_col(&self) -> u16 {
self.end_col
}
pub fn len(&self) -> usize {
col_span_len(self.start_col, self.end_col)
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn get(&self, col: usize) -> Option<&str> {
let col = self.start_col.checked_add(u16::try_from(col).ok()?)?;
if col > self.end_col {
return None;
}
self.range.get_abs(self.row, col)
}
pub fn get_abs(&self, col: u16) -> Option<&str> {
if col < self.start_col || col > self.end_col {
return None;
}
self.range.get_abs(self.row, col)
}
pub fn iter(&self) -> FormulaRangeRowCells<'range, 'formula> {
FormulaRangeRowCells {
range: self.range,
row: self.row,
next_col: self.start_col,
end_col: self.end_col,
done: false,
}
}
pub fn cells(
&self,
) -> impl DoubleEndedIterator<Item = (usize, Option<&'range str>)>
+ ExactSizeIterator
+ std::iter::FusedIterator
+ '_ {
self.iter().enumerate()
}
pub fn used_cells(&self) -> FormulaRangeRowUsedCells<'range, 'formula> {
let bounds = (self.row, self.start_col)..=(self.row, self.end_col);
let remaining = self.range.formulas.range(bounds.clone()).count();
FormulaRangeRowUsedCells {
entries: self.range.formulas.range(bounds),
remaining,
}
}
}
#[derive(Debug, Clone)]
pub struct FormulaRangeRowUsedCells<'range, 'formula> {
entries: BTreeMapRange<'range, (u32, u16), FormulaEntry<'formula>>,
remaining: usize,
}
impl<'range, 'formula> Iterator for FormulaRangeRowUsedCells<'range, 'formula> {
type Item = (u16, &'range str);
fn next(&mut self) -> Option<Self::Item> {
let (&(_, col), formula) = self.entries.next()?;
self.remaining = self.remaining.saturating_sub(1);
Some((col, formula.as_str()))
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.remaining, Some(self.remaining))
}
}
impl<'range, 'formula> ExactSizeIterator for FormulaRangeRowUsedCells<'range, 'formula> {
fn len(&self) -> usize {
self.remaining
}
}
impl<'range, 'formula> DoubleEndedIterator for FormulaRangeRowUsedCells<'range, 'formula> {
fn next_back(&mut self) -> Option<Self::Item> {
let (&(_, col), formula) = self.entries.next_back()?;
self.remaining = self.remaining.saturating_sub(1);
Some((col, formula.as_str()))
}
}
impl<'range, 'formula> std::iter::FusedIterator for FormulaRangeRowUsedCells<'range, 'formula> {}
#[derive(Debug, Clone)]
pub struct FormulaRangeRowCells<'range, 'formula> {
range: &'range FormulaRange<'formula>,
row: u32,
next_col: u16,
end_col: u16,
done: bool,
}
impl<'range, 'formula> Iterator for FormulaRangeRowCells<'range, 'formula> {
type Item = Option<&'range str>;
fn next(&mut self) -> Option<Self::Item> {
if self.done || self.next_col > self.end_col {
return None;
}
let col = self.next_col;
if col == self.end_col {
self.done = true;
} else {
self.next_col = col + 1;
}
Some(self.range.get_abs(self.row, col))
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = if self.done || self.next_col > self.end_col {
0
} else {
usize::from(self.end_col - self.next_col) + 1
};
(len, Some(len))
}
}
impl<'range, 'formula> ExactSizeIterator for FormulaRangeRowCells<'range, 'formula> {}
impl<'range, 'formula> DoubleEndedIterator for FormulaRangeRowCells<'range, 'formula> {
fn next_back(&mut self) -> Option<Self::Item> {
if self.done || self.next_col > self.end_col {
return None;
}
let col = self.end_col;
if col == self.next_col {
self.done = true;
} else {
self.end_col = col - 1;
}
Some(self.range.get_abs(self.row, col))
}
}
impl<'range, 'formula> std::iter::FusedIterator for FormulaRangeRowCells<'range, 'formula> {}
#[cfg(feature = "serde")]
pub type DeError = serde::de::value::Error;
#[cfg(feature = "serde")]
pub fn deserialize_as_f64_or_none<'de, D>(
deserializer: D,
) -> std::result::Result<Option<f64>, D::Error>
where
D: serde::Deserializer<'de>,
{
let cell = <Option<Cell> as serde::Deserialize>::deserialize(deserializer)?;
Ok(cell.and_then(|cell| cell.as_f64()))
}
#[cfg(feature = "serde")]
pub fn deserialize_as_i64_or_none<'de, D>(
deserializer: D,
) -> std::result::Result<Option<i64>, D::Error>
where
D: serde::Deserializer<'de>,
{
let cell = <Option<Cell> as serde::Deserialize>::deserialize(deserializer)?;
Ok(cell.and_then(|cell| cell.as_i64()))
}
#[cfg(feature = "serde")]
pub fn deserialize_as_f64_or_string<'de, D>(
deserializer: D,
) -> std::result::Result<std::result::Result<f64, String>, D::Error>
where
D: serde::Deserializer<'de>,
{
let cell = <Option<Cell> as serde::Deserialize>::deserialize(deserializer)?;
Ok(match cell {
Some(cell) => cell.as_f64().ok_or_else(|| display_text(&cell)),
None => Err(String::new()),
})
}
#[cfg(feature = "serde")]
pub fn deserialize_as_i64_or_string<'de, D>(
deserializer: D,
) -> std::result::Result<std::result::Result<i64, String>, D::Error>
where
D: serde::Deserializer<'de>,
{
let cell = <Option<Cell> as serde::Deserialize>::deserialize(deserializer)?;
Ok(match cell {
Some(cell) => cell.as_i64().ok_or_else(|| display_text(&cell)),
None => Err(String::new()),
})
}
#[cfg(all(feature = "serde", feature = "chrono"))]
pub fn deserialize_as_duration_or_none<'de, D>(
deserializer: D,
) -> std::result::Result<Option<chrono::Duration>, D::Error>
where
D: serde::Deserializer<'de>,
{
let cell = <Option<Cell> as serde::Deserialize>::deserialize(deserializer)?;
Ok(cell.and_then(|cell| cell.as_duration()))
}
#[cfg(all(feature = "serde", feature = "chrono"))]
pub fn deserialize_as_duration_or_string<'de, D>(
deserializer: D,
) -> std::result::Result<std::result::Result<chrono::Duration, String>, D::Error>
where
D: serde::Deserializer<'de>,
{
let cell = <Option<Cell> as serde::Deserialize>::deserialize(deserializer)?;
Ok(match cell {
Some(cell) => cell.as_duration().ok_or_else(|| display_text(&cell)),
None => Err(String::new()),
})
}
#[cfg(all(feature = "serde", feature = "chrono"))]
fn deserialize_date_or_none_with_epoch<'de, D>(
deserializer: D,
date1904: bool,
) -> std::result::Result<Option<chrono::NaiveDate>, D::Error>
where
D: serde::Deserializer<'de>,
{
let cell = <Option<Cell> as serde::Deserialize>::deserialize(deserializer)?;
Ok(cell.and_then(|cell| cell.as_date(date1904)))
}
#[cfg(all(feature = "serde", feature = "chrono"))]
fn deserialize_date_or_string_with_epoch<'de, D>(
deserializer: D,
date1904: bool,
) -> std::result::Result<std::result::Result<chrono::NaiveDate, String>, D::Error>
where
D: serde::Deserializer<'de>,
{
let cell = <Option<Cell> as serde::Deserialize>::deserialize(deserializer)?;
Ok(match cell {
Some(cell) => cell.as_date(date1904).ok_or_else(|| display_text(&cell)),
None => Err(String::new()),
})
}
#[cfg(all(feature = "serde", feature = "chrono"))]
fn deserialize_time_or_none_with_epoch<'de, D>(
deserializer: D,
date1904: bool,
) -> std::result::Result<Option<chrono::NaiveTime>, D::Error>
where
D: serde::Deserializer<'de>,
{
let cell = <Option<Cell> as serde::Deserialize>::deserialize(deserializer)?;
Ok(cell.and_then(|cell| cell.as_time(date1904)))
}
#[cfg(all(feature = "serde", feature = "chrono"))]
fn deserialize_time_or_string_with_epoch<'de, D>(
deserializer: D,
date1904: bool,
) -> std::result::Result<std::result::Result<chrono::NaiveTime, String>, D::Error>
where
D: serde::Deserializer<'de>,
{
let cell = <Option<Cell> as serde::Deserialize>::deserialize(deserializer)?;
Ok(match cell {
Some(cell) => cell.as_time(date1904).ok_or_else(|| display_text(&cell)),
None => Err(String::new()),
})
}
#[cfg(all(feature = "serde", feature = "chrono"))]
fn deserialize_datetime_or_none_with_epoch<'de, D>(
deserializer: D,
date1904: bool,
) -> std::result::Result<Option<chrono::NaiveDateTime>, D::Error>
where
D: serde::Deserializer<'de>,
{
let cell = <Option<Cell> as serde::Deserialize>::deserialize(deserializer)?;
Ok(cell.and_then(|cell| cell.as_naive_datetime(date1904)))
}
#[cfg(all(feature = "serde", feature = "chrono"))]
fn deserialize_datetime_or_string_with_epoch<'de, D>(
deserializer: D,
date1904: bool,
) -> std::result::Result<std::result::Result<chrono::NaiveDateTime, String>, D::Error>
where
D: serde::Deserializer<'de>,
{
let cell = <Option<Cell> as serde::Deserialize>::deserialize(deserializer)?;
Ok(match cell {
Some(cell) => cell
.as_naive_datetime(date1904)
.ok_or_else(|| display_text(&cell)),
None => Err(String::new()),
})
}
#[cfg(all(feature = "serde", feature = "chrono"))]
pub fn deserialize_as_date_1900_or_none<'de, D>(
deserializer: D,
) -> std::result::Result<Option<chrono::NaiveDate>, D::Error>
where
D: serde::Deserializer<'de>,
{
deserialize_date_or_none_with_epoch(deserializer, false)
}
#[cfg(all(feature = "serde", feature = "chrono"))]
pub fn deserialize_as_date_1900_or_string<'de, D>(
deserializer: D,
) -> std::result::Result<std::result::Result<chrono::NaiveDate, String>, D::Error>
where
D: serde::Deserializer<'de>,
{
deserialize_date_or_string_with_epoch(deserializer, false)
}
#[cfg(all(feature = "serde", feature = "chrono"))]
pub fn deserialize_as_time_1900_or_none<'de, D>(
deserializer: D,
) -> std::result::Result<Option<chrono::NaiveTime>, D::Error>
where
D: serde::Deserializer<'de>,
{
deserialize_time_or_none_with_epoch(deserializer, false)
}
#[cfg(all(feature = "serde", feature = "chrono"))]
pub fn deserialize_as_time_1900_or_string<'de, D>(
deserializer: D,
) -> std::result::Result<std::result::Result<chrono::NaiveTime, String>, D::Error>
where
D: serde::Deserializer<'de>,
{
deserialize_time_or_string_with_epoch(deserializer, false)
}
#[cfg(all(feature = "serde", feature = "chrono"))]
pub fn deserialize_as_datetime_1900_or_none<'de, D>(
deserializer: D,
) -> std::result::Result<Option<chrono::NaiveDateTime>, D::Error>
where
D: serde::Deserializer<'de>,
{
deserialize_datetime_or_none_with_epoch(deserializer, false)
}
#[cfg(all(feature = "serde", feature = "chrono"))]
pub fn deserialize_as_datetime_1900_or_string<'de, D>(
deserializer: D,
) -> std::result::Result<std::result::Result<chrono::NaiveDateTime, String>, D::Error>
where
D: serde::Deserializer<'de>,
{
deserialize_datetime_or_string_with_epoch(deserializer, false)
}
#[cfg(all(feature = "serde", feature = "chrono"))]
pub fn deserialize_as_date_1904_or_none<'de, D>(
deserializer: D,
) -> std::result::Result<Option<chrono::NaiveDate>, D::Error>
where
D: serde::Deserializer<'de>,
{
deserialize_date_or_none_with_epoch(deserializer, true)
}
#[cfg(all(feature = "serde", feature = "chrono"))]
pub fn deserialize_as_date_1904_or_string<'de, D>(
deserializer: D,
) -> std::result::Result<std::result::Result<chrono::NaiveDate, String>, D::Error>
where
D: serde::Deserializer<'de>,
{
deserialize_date_or_string_with_epoch(deserializer, true)
}
#[cfg(all(feature = "serde", feature = "chrono"))]
pub fn deserialize_as_time_1904_or_none<'de, D>(
deserializer: D,
) -> std::result::Result<Option<chrono::NaiveTime>, D::Error>
where
D: serde::Deserializer<'de>,
{
deserialize_time_or_none_with_epoch(deserializer, true)
}
#[cfg(all(feature = "serde", feature = "chrono"))]
pub fn deserialize_as_time_1904_or_string<'de, D>(
deserializer: D,
) -> std::result::Result<std::result::Result<chrono::NaiveTime, String>, D::Error>
where
D: serde::Deserializer<'de>,
{
deserialize_time_or_string_with_epoch(deserializer, true)
}
#[cfg(all(feature = "serde", feature = "chrono"))]
pub fn deserialize_as_datetime_1904_or_none<'de, D>(
deserializer: D,
) -> std::result::Result<Option<chrono::NaiveDateTime>, D::Error>
where
D: serde::Deserializer<'de>,
{
deserialize_datetime_or_none_with_epoch(deserializer, true)
}
#[cfg(all(feature = "serde", feature = "chrono"))]
pub fn deserialize_as_datetime_1904_or_string<'de, D>(
deserializer: D,
) -> std::result::Result<std::result::Result<chrono::NaiveDateTime, String>, D::Error>
where
D: serde::Deserializer<'de>,
{
deserialize_datetime_or_string_with_epoch(deserializer, true)
}
#[cfg(feature = "serde")]
#[derive(Debug, Clone)]
pub struct RangeDeserializerBuilder {
has_headers: bool,
header_row: HeaderRow,
headers: Option<Vec<String>>,
skip_missing_headers: bool,
}
#[cfg(feature = "serde")]
impl Default for RangeDeserializerBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "serde")]
impl RangeDeserializerBuilder {
pub fn new() -> Self {
Self {
has_headers: true,
header_row: HeaderRow::FirstNonEmptyRow,
headers: None,
skip_missing_headers: false,
}
}
pub fn has_headers(&mut self, yes: bool) -> &mut Self {
self.has_headers = yes;
if !yes {
self.header_row = HeaderRow::FirstNonEmptyRow;
self.headers = None;
}
self
}
pub fn with_header_row(&mut self, row: impl Into<HeaderRow>) -> &mut Self {
self.has_headers = true;
self.header_row = row.into();
self
}
pub fn with_headers<H>(headers: &[H]) -> Self
where
H: AsRef<str>,
{
Self {
has_headers: true,
header_row: HeaderRow::FirstNonEmptyRow,
headers: Some(headers.iter().map(|h| h.as_ref().to_string()).collect()),
skip_missing_headers: false,
}
}
pub fn with_deserialize_headers<D>() -> Self
where
D: for<'de> serde::Deserialize<'de>,
{
let mut headers = Vec::new();
let _ = D::deserialize(HeaderExtractor {
headers: &mut headers,
});
Self {
has_headers: true,
header_row: HeaderRow::FirstNonEmptyRow,
headers: Some(headers),
skip_missing_headers: true,
}
}
pub fn from_range<'cell, D>(
&self,
range: &'cell Range<'cell>,
) -> std::result::Result<RangeDeserializer<'cell, D>, DeError>
where
D: serde::Deserialize<'cell>,
{
RangeDeserializer::new(
range,
self.has_headers,
self.header_row,
self.headers.as_deref(),
self.skip_missing_headers,
)
}
}
#[cfg(feature = "serde")]
#[derive(Debug)]
struct HeaderError;
#[cfg(feature = "serde")]
impl std::fmt::Display for HeaderError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("header extraction stopped")
}
}
#[cfg(feature = "serde")]
impl std::error::Error for HeaderError {}
#[cfg(feature = "serde")]
impl serde::de::Error for HeaderError {
fn custom<T>(_msg: T) -> Self
where
T: std::fmt::Display,
{
HeaderError
}
}
#[cfg(feature = "serde")]
struct HeaderExtractor<'a> {
headers: &'a mut Vec<String>,
}
#[cfg(feature = "serde")]
impl<'de, 'a> serde::de::Deserializer<'de> for HeaderExtractor<'a> {
type Error = HeaderError;
fn deserialize_any<V>(self, _visitor: V) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
Err(HeaderError)
}
fn deserialize_struct<V>(
self,
_name: &'static str,
fields: &'static [&'static str],
_visitor: V,
) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
self.headers
.extend(fields.iter().map(|field| (*field).to_string()));
Err(HeaderError)
}
serde::forward_to_deserialize_any! {
bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes byte_buf option unit
unit_struct newtype_struct seq tuple tuple_struct map enum identifier ignored_any
}
}
#[cfg(feature = "serde")]
#[derive(Debug, Clone)]
struct DeserColumn {
header: Option<String>,
offset: Option<usize>,
}
#[cfg(feature = "serde")]
fn first_non_empty_row(
range: &Range<'_>,
start_row: u32,
start_col: u16,
end_row: u32,
width: usize,
) -> Option<u32> {
if start_row > end_row || width == 0 {
return None;
}
(start_row..=end_row).find(|&row| {
(0..width).any(|idx| {
let col = start_col + idx as u16;
range.get_abs(row, col).is_some()
})
})
}
#[cfg(feature = "serde")]
#[derive(Debug)]
pub struct RangeDeserializer<'cell, D>
where
D: serde::Deserialize<'cell>,
{
range: &'cell Range<'cell>,
row: u32,
end_row: u32,
start_col: u16,
columns: Vec<DeserColumn>,
has_header_names: bool,
done: bool,
_marker: std::marker::PhantomData<D>,
}
#[cfg(feature = "serde")]
impl<'cell, D> RangeDeserializer<'cell, D>
where
D: serde::Deserialize<'cell>,
{
fn new(
range: &'cell Range<'cell>,
has_headers: bool,
header_row: HeaderRow,
selected_headers: Option<&[String]>,
skip_missing_headers: bool,
) -> std::result::Result<Self, DeError> {
let (start_row, start_col, end_row, end_col) = match (range.start, range.end) {
(Some((r0, c0)), Some((r1, c1))) => (r0, c0, r1, c1),
_ => (1, 1, 0, 0),
};
let width = if start_row <= end_row {
col_span_len(start_col, end_col)
} else {
0
};
let use_header_row = has_headers || selected_headers.is_some();
let header_row = if use_header_row {
match header_row {
HeaderRow::FirstNonEmptyRow => {
first_non_empty_row(range, start_row, start_col, end_row, width)
}
HeaderRow::Row(row) => Some(row),
}
} else {
None
};
let header_row_in_range = header_row.is_some_and(|row| start_row <= row && row <= end_row);
let source_headers = if use_header_row && header_row_in_range && width > 0 {
let header_row = header_row.expect("checked header row");
Some(
(0..width)
.map(|idx| {
let col = start_col + idx as u16;
range
.formatted_abs(header_row, col)
.map(str::to_string)
.unwrap_or_default()
})
.collect::<Vec<_>>(),
)
} else {
None
};
let has_source_headers = source_headers.is_some();
let columns: Vec<DeserColumn> = match selected_headers {
Some(headers) => {
let source = source_headers.as_deref().unwrap_or(&[]);
headers
.iter()
.filter_map(|header| {
let requested = header.trim();
let offset = source
.iter()
.position(|source_header| source_header.trim() == requested);
if skip_missing_headers && offset.is_none() {
return None;
}
let row_header = offset
.and_then(|idx| source.get(idx).cloned())
.unwrap_or_else(|| header.clone());
Some(DeserColumn {
header: Some(row_header),
offset,
})
})
.collect::<Vec<_>>()
}
None if has_headers && width > 0 => source_headers
.unwrap_or_default()
.into_iter()
.enumerate()
.map(|(offset, header)| DeserColumn {
header: Some(header),
offset: Some(offset),
})
.collect(),
None => (0..width)
.map(|offset| DeserColumn {
header: None,
offset: Some(offset),
})
.collect(),
};
if selected_headers.is_some() && !skip_missing_headers && has_source_headers {
if let Some(missing) = columns
.iter()
.find_map(|column| column.offset.is_none().then_some(column.header.as_deref()))
.flatten()
{
return Err(serde::de::Error::custom(format!(
"missing range header: {missing}"
)));
}
}
let has_header_names = columns.iter().any(|column| column.header.is_some());
let empty = start_row > end_row || width == 0;
let (row, done) = if empty {
(start_row, true)
} else if use_header_row {
let Some(header_row) = header_row else {
return Ok(Self {
range,
row: start_row,
end_row,
start_col,
columns,
has_header_names,
done: true,
_marker: std::marker::PhantomData,
});
};
match header_row.checked_add(1) {
Some(row) if header_row_in_range && row <= end_row => (row, false),
_ => (start_row, true),
}
} else {
(start_row, false)
};
Ok(Self {
range,
row,
end_row,
start_col,
columns,
has_header_names,
done,
_marker: std::marker::PhantomData,
})
}
fn remaining_len(&self) -> usize {
if self.done || self.row > self.end_row {
0
} else {
(self.end_row - self.row + 1) as usize
}
}
}
#[cfg(feature = "serde")]
impl<'cell, D> Iterator for RangeDeserializer<'cell, D>
where
D: serde::Deserialize<'cell>,
{
type Item = std::result::Result<D, DeError>;
fn next(&mut self) -> Option<Self::Item> {
if self.done || self.row > self.end_row {
return None;
}
let row = self.row;
if row == self.end_row {
self.done = true;
} else {
self.row = row + 1;
}
let de = RowDeserializer {
range: self.range,
row,
start_col: self.start_col,
columns: &self.columns,
has_header_names: self.has_header_names,
};
Some(D::deserialize(de))
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.remaining_len();
(len, Some(len))
}
}
#[cfg(feature = "serde")]
impl<'cell, D> ExactSizeIterator for RangeDeserializer<'cell, D> where D: serde::Deserialize<'cell> {}
#[cfg(feature = "serde")]
impl<'cell, D> std::iter::FusedIterator for RangeDeserializer<'cell, D> where
D: serde::Deserialize<'cell>
{
}
#[cfg(feature = "serde")]
#[derive(Clone, Copy)]
struct CellValue<'a> {
value: Option<&'a Cell>,
text: Option<&'a str>,
}
#[cfg(feature = "serde")]
impl<'a> CellValue<'a> {
fn empty() -> Self {
Self {
value: None,
text: None,
}
}
fn from_entry(entry: &'a RangeCell<'_>) -> Self {
Self {
value: Some(entry.value()),
text: Some(entry.text()),
}
}
fn from_formula_cached(cached: &'a Cell, text: Option<&'a str>) -> Self {
Self {
value: Some(cached),
text,
}
}
}
#[cfg(feature = "serde")]
impl<'de, 'a: 'de> serde::de::Deserializer<'de> for CellValue<'a> {
type Error = DeError;
fn deserialize_any<V>(self, visitor: V) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
match self.value {
None => visitor.visit_unit(),
Some(value) => match value {
Cell::Text(s) => visitor.visit_borrowed_str(s),
Cell::Number(n) | Cell::Date(n) => visitor.visit_f64(*n),
Cell::Bool(b) => visitor.visit_bool(*b),
Cell::Error(e) => visitor.visit_borrowed_str(e),
Cell::Formula { cached, .. } => {
CellValue::from_formula_cached(cached.as_ref(), self.text)
.deserialize_any(visitor)
}
},
}
}
fn deserialize_str<V>(self, visitor: V) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
match self.value {
Some(value) => match value {
Cell::Text(s) | Cell::Error(s) => visitor.visit_borrowed_str(s),
Cell::Number(_) | Cell::Date(_) | Cell::Bool(_) | Cell::Formula { .. } => {
visitor.visit_borrowed_str(self.text.unwrap_or_default())
}
},
None => Err(serde::de::Error::custom(
"expected text cell, got empty cell",
)),
}
}
fn deserialize_string<V>(self, visitor: V) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
self.deserialize_str(visitor)
}
fn deserialize_bool<V>(self, visitor: V) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
match self.value {
Some(value) => match value {
Cell::Bool(b) => visitor.visit_bool(*b),
Cell::Formula { cached, .. } => {
CellValue::from_formula_cached(cached.as_ref(), self.text)
.deserialize_bool(visitor)
}
other => Err(serde::de::Error::custom(format!(
"expected bool cell, got {other:?}"
))),
},
None => Err(serde::de::Error::custom(
"expected bool cell, got empty cell",
)),
}
}
fn deserialize_f64<V>(self, visitor: V) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
let n = cell_to_f64(self.value)?;
visitor.visit_f64(n)
}
fn deserialize_f32<V>(self, visitor: V) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
let n = cell_to_f64(self.value)?;
if !n.is_finite() || n < f64::from(f32::MIN) || n > f64::from(f32::MAX) {
return Err(serde::de::Error::custom(format!(
"numeric cell out of range for f32: {n}"
)));
}
visitor.visit_f32(n as f32)
}
fn deserialize_i8<V>(self, visitor: V) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
let n = cell_to_i64(self.value)?;
let n = i8::try_from(n).map_err(serde::de::Error::custom)?;
visitor.visit_i8(n)
}
fn deserialize_i16<V>(self, visitor: V) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
let n = cell_to_i64(self.value)?;
let n = i16::try_from(n).map_err(serde::de::Error::custom)?;
visitor.visit_i16(n)
}
fn deserialize_i32<V>(self, visitor: V) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
let n = cell_to_i64(self.value)?;
let n = i32::try_from(n).map_err(serde::de::Error::custom)?;
visitor.visit_i32(n)
}
fn deserialize_i64<V>(self, visitor: V) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
let n = cell_to_i64(self.value)?;
visitor.visit_i64(n)
}
fn deserialize_u8<V>(self, visitor: V) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
let n = cell_to_i64(self.value)?;
let n = u8::try_from(n).map_err(serde::de::Error::custom)?;
visitor.visit_u8(n)
}
fn deserialize_u16<V>(self, visitor: V) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
let n = cell_to_i64(self.value)?;
let n = u16::try_from(n).map_err(serde::de::Error::custom)?;
visitor.visit_u16(n)
}
fn deserialize_u32<V>(self, visitor: V) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
let n = cell_to_i64(self.value)?;
let n = u32::try_from(n).map_err(serde::de::Error::custom)?;
visitor.visit_u32(n)
}
fn deserialize_u64<V>(self, visitor: V) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
let n = cell_to_i64(self.value)?;
let n = u64::try_from(n).map_err(serde::de::Error::custom)?;
visitor.visit_u64(n)
}
fn deserialize_option<V>(self, visitor: V) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
match self.value {
None => visitor.visit_none(),
Some(_) => visitor.visit_some(self),
}
}
fn deserialize_enum<V>(
self,
name: &'static str,
_variants: &'static [&'static str],
visitor: V,
) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
if name == "Cell" {
let Some(value) = self.value else {
return Err(serde::de::Error::custom("expected cell, got empty cell"));
};
visitor.visit_enum(CellEnumAccess {
cell: value.clone(),
})
} else {
self.deserialize_any(visitor)
}
}
serde::forward_to_deserialize_any! {
char bytes byte_buf unit unit_struct
newtype_struct seq tuple tuple_struct map struct identifier ignored_any
}
}
#[cfg(feature = "serde")]
struct CellEnumAccess {
cell: Cell,
}
#[cfg(feature = "serde")]
impl<'de> serde::de::EnumAccess<'de> for CellEnumAccess {
type Error = DeError;
type Variant = CellVariantAccess;
fn variant_seed<V>(self, seed: V) -> std::result::Result<(V::Value, Self::Variant), Self::Error>
where
V: serde::de::DeserializeSeed<'de>,
{
let variant = match &self.cell {
Cell::Text(_) => "Text",
Cell::Number(_) => "Number",
Cell::Date(_) => "Date",
Cell::Bool(_) => "Bool",
Cell::Error(_) => "Error",
Cell::Formula { .. } => "Formula",
};
let value = seed.deserialize(variant.into_deserializer())?;
Ok((value, CellVariantAccess { cell: self.cell }))
}
}
#[cfg(feature = "serde")]
struct CellVariantAccess {
cell: Cell,
}
#[cfg(feature = "serde")]
impl<'de> serde::de::VariantAccess<'de> for CellVariantAccess {
type Error = DeError;
fn unit_variant(self) -> std::result::Result<(), Self::Error> {
Err(serde::de::Error::custom("rxls Cell variants carry values"))
}
fn newtype_variant_seed<T>(self, seed: T) -> std::result::Result<T::Value, Self::Error>
where
T: serde::de::DeserializeSeed<'de>,
{
match self.cell {
Cell::Text(s) | Cell::Error(s) => seed.deserialize(s.into_deserializer()),
Cell::Number(n) | Cell::Date(n) => seed.deserialize(n.into_deserializer()),
Cell::Bool(b) => seed.deserialize(b.into_deserializer()),
Cell::Formula { formula, cached } => seed.deserialize(FormulaTupleDeserializer {
formula,
cached: *cached,
}),
}
}
fn tuple_variant<V>(self, _len: usize, visitor: V) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
match self.cell {
Cell::Formula { formula, cached } => visitor.visit_seq(FormulaTupleAccess {
idx: 0,
formula: Some(formula),
cached: Some(*cached),
}),
_ => Err(serde::de::Error::custom(
"only Formula is represented as a tuple variant",
)),
}
}
fn struct_variant<V>(
self,
_fields: &'static [&'static str],
visitor: V,
) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
self.tuple_variant(2, visitor)
}
}
#[cfg(feature = "serde")]
struct FormulaTupleDeserializer {
formula: String,
cached: Cell,
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserializer<'de> for FormulaTupleDeserializer {
type Error = DeError;
fn deserialize_any<V>(self, visitor: V) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
visitor.visit_seq(FormulaTupleAccess {
idx: 0,
formula: Some(self.formula),
cached: Some(self.cached),
})
}
fn deserialize_tuple<V>(
self,
_len: usize,
visitor: V,
) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
self.deserialize_any(visitor)
}
fn deserialize_tuple_struct<V>(
self,
_name: &'static str,
_len: usize,
visitor: V,
) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
self.deserialize_any(visitor)
}
serde::forward_to_deserialize_any! {
bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes byte_buf
option unit unit_struct newtype_struct seq map struct enum identifier ignored_any
}
}
#[cfg(feature = "serde")]
struct FormulaTupleAccess {
idx: u8,
formula: Option<String>,
cached: Option<Cell>,
}
#[cfg(feature = "serde")]
impl<'de> serde::de::SeqAccess<'de> for FormulaTupleAccess {
type Error = DeError;
fn next_element_seed<T>(
&mut self,
seed: T,
) -> std::result::Result<Option<T::Value>, Self::Error>
where
T: serde::de::DeserializeSeed<'de>,
{
match self.idx {
0 => {
self.idx = 1;
let formula = self.formula.take().unwrap_or_default();
seed.deserialize(formula.into_deserializer()).map(Some)
}
1 => {
self.idx = 2;
let Some(cached) = self.cached.take() else {
return Ok(None);
};
seed.deserialize(CellOwnedDeserializer { cell: cached })
.map(Some)
}
_ => Ok(None),
}
}
fn size_hint(&self) -> Option<usize> {
Some(usize::from(2u8.saturating_sub(self.idx)))
}
}
#[cfg(feature = "serde")]
struct CellOwnedDeserializer {
cell: Cell,
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserializer<'de> for CellOwnedDeserializer {
type Error = DeError;
fn deserialize_any<V>(self, visitor: V) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
match self.cell {
Cell::Text(s) | Cell::Error(s) => visitor.visit_string(s),
Cell::Number(n) | Cell::Date(n) => visitor.visit_f64(n),
Cell::Bool(b) => visitor.visit_bool(b),
Cell::Formula { cached, .. } => {
CellOwnedDeserializer { cell: *cached }.deserialize_any(visitor)
}
}
}
fn deserialize_enum<V>(
self,
name: &'static str,
_variants: &'static [&'static str],
visitor: V,
) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
if name == "Cell" {
visitor.visit_enum(CellEnumAccess { cell: self.cell })
} else {
self.deserialize_any(visitor)
}
}
serde::forward_to_deserialize_any! {
bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes byte_buf
option unit unit_struct newtype_struct seq tuple tuple_struct map struct identifier ignored_any
}
}
#[cfg(feature = "serde")]
fn cell_to_f64(cell: Option<&Cell>) -> std::result::Result<f64, DeError> {
match cell {
Some(Cell::Number(n)) | Some(Cell::Date(n)) => Ok(*n),
Some(Cell::Formula { cached, .. }) => cell_to_f64(Some(cached.as_ref())),
Some(Cell::Text(s)) => s.parse::<f64>().map_err(serde::de::Error::custom),
Some(other) => Err(serde::de::Error::custom(format!(
"expected numeric cell, got {other:?}"
))),
None => Err(serde::de::Error::custom(
"expected numeric cell, got empty cell",
)),
}
}
#[cfg(feature = "serde")]
fn cell_to_i64(cell: Option<&Cell>) -> std::result::Result<i64, DeError> {
match cell {
Some(Cell::Number(n)) | Some(Cell::Date(n)) if n.is_finite() && n.fract() == 0.0 => {
Ok(*n as i64)
}
Some(Cell::Formula { cached, .. }) => cell_to_i64(Some(cached.as_ref())),
Some(Cell::Text(s)) => s.parse::<i64>().map_err(serde::de::Error::custom),
Some(other) => Err(serde::de::Error::custom(format!(
"expected integer cell, got {other:?}"
))),
None => Err(serde::de::Error::custom(
"expected integer cell, got empty cell",
)),
}
}
#[cfg(feature = "serde")]
fn cell_at_column<'a>(
range: &'a Range<'a>,
row: u32,
start_col: u16,
offset: Option<usize>,
) -> CellValue<'a> {
let Some(offset) = offset.and_then(|offset| u16::try_from(offset).ok()) else {
return CellValue::empty();
};
let Some(col) = start_col.checked_add(offset) else {
return CellValue::empty();
};
range
.entry_abs(row, col)
.map(CellValue::from_entry)
.unwrap_or_else(CellValue::empty)
}
#[cfg(feature = "serde")]
struct RowDeserializer<'cols, 'cell> {
range: &'cell Range<'cell>,
row: u32,
start_col: u16,
columns: &'cols [DeserColumn],
has_header_names: bool,
}
#[cfg(feature = "serde")]
impl<'de, 'cols, 'cell: 'de> serde::de::Deserializer<'de> for RowDeserializer<'cols, 'cell> {
type Error = DeError;
fn deserialize_any<V>(self, visitor: V) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
if self.has_header_names {
visitor.visit_map(RowMapAccess {
range: self.range,
row: self.row,
start_col: self.start_col,
columns: self.columns,
idx: 0,
pending: None,
})
} else {
visitor.visit_seq(RowSeqAccess {
range: self.range,
row: self.row,
start_col: self.start_col,
columns: self.columns,
idx: 0,
})
}
}
fn deserialize_seq<V>(self, visitor: V) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
visitor.visit_seq(RowSeqAccess {
range: self.range,
row: self.row,
start_col: self.start_col,
columns: self.columns,
idx: 0,
})
}
fn deserialize_tuple<V>(
self,
_len: usize,
visitor: V,
) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
self.deserialize_seq(visitor)
}
fn deserialize_tuple_struct<V>(
self,
_name: &'static str,
_len: usize,
visitor: V,
) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
self.deserialize_seq(visitor)
}
fn deserialize_map<V>(self, visitor: V) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
self.deserialize_any(visitor)
}
fn deserialize_struct<V>(
self,
_name: &'static str,
_fields: &'static [&'static str],
visitor: V,
) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
self.deserialize_any(visitor)
}
serde::forward_to_deserialize_any! {
bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes byte_buf
option unit unit_struct newtype_struct enum identifier ignored_any
}
}
#[cfg(feature = "serde")]
struct RowSeqAccess<'cols, 'cell> {
range: &'cell Range<'cell>,
row: u32,
start_col: u16,
columns: &'cols [DeserColumn],
idx: usize,
}
#[cfg(feature = "serde")]
impl<'de, 'cols, 'cell: 'de> serde::de::SeqAccess<'de> for RowSeqAccess<'cols, 'cell> {
type Error = DeError;
fn next_element_seed<T>(
&mut self,
seed: T,
) -> std::result::Result<Option<T::Value>, Self::Error>
where
T: serde::de::DeserializeSeed<'de>,
{
if self.idx >= self.columns.len() {
return Ok(None);
}
let column = &self.columns[self.idx];
self.idx += 1;
seed.deserialize(cell_at_column(
self.range,
self.row,
self.start_col,
column.offset,
))
.map(Some)
}
}
#[cfg(feature = "serde")]
struct RowMapAccess<'cols, 'cell> {
range: &'cell Range<'cell>,
row: u32,
start_col: u16,
columns: &'cols [DeserColumn],
idx: usize,
pending: Option<usize>,
}
#[cfg(feature = "serde")]
impl<'de, 'cols, 'cell: 'de> serde::de::MapAccess<'de> for RowMapAccess<'cols, 'cell> {
type Error = DeError;
fn next_key_seed<K>(&mut self, seed: K) -> std::result::Result<Option<K::Value>, Self::Error>
where
K: serde::de::DeserializeSeed<'de>,
{
while self.idx < self.columns.len() {
let idx = self.idx;
self.idx += 1;
let Some(header) = self.columns[idx].header.as_deref() else {
continue;
};
if header.is_empty() {
continue;
}
self.pending = Some(idx);
return seed.deserialize(header.into_deserializer()).map(Some);
}
Ok(None)
}
fn next_value_seed<V>(&mut self, seed: V) -> std::result::Result<V::Value, Self::Error>
where
V: serde::de::DeserializeSeed<'de>,
{
let idx = self
.pending
.take()
.ok_or_else(|| serde::de::Error::custom("range row value without key"))?;
seed.deserialize(cell_at_column(
self.range,
self.row,
self.start_col,
self.columns[idx].offset,
))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SheetType {
WorkSheet,
DialogSheet,
MacroSheet,
ChartSheet,
Vba,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SheetVisible {
Visible,
Hidden,
VeryHidden,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SheetMetadata {
pub name: String,
pub typ: SheetType,
pub visible: SheetVisible,
}
impl SheetMetadata {
pub fn name(&self) -> &str {
self.name.as_str()
}
pub fn sheet_type(&self) -> SheetType {
self.typ
}
pub fn visible(&self) -> SheetVisible {
self.visible
}
pub fn is_worksheet(&self) -> bool {
self.typ == SheetType::WorkSheet
}
pub fn is_visible(&self) -> bool {
self.visible == SheetVisible::Visible
}
pub fn is_hidden(&self) -> bool {
self.visible == SheetVisible::Hidden
}
pub fn is_very_hidden(&self) -> bool {
self.visible == SheetVisible::VeryHidden
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct SheetView {
pub freeze: Option<(u32, u16)>,
pub hide_gridlines: bool,
pub zoom: Option<u16>,
pub show_headers: Option<bool>,
pub right_to_left: bool,
}
impl SheetView {
pub fn new() -> Self {
SheetView::default()
}
pub fn with_freeze(mut self, row: u32, col: u16) -> Self {
self.freeze = Some((row, col));
self
}
pub fn with_hidden_gridlines(mut self) -> Self {
self.hide_gridlines = true;
self
}
pub fn with_zoom(mut self, percent: u16) -> Self {
self.zoom = Some(percent);
self
}
pub fn with_show_headers(mut self, show: bool) -> Self {
self.show_headers = Some(show);
self
}
pub fn with_right_to_left(mut self, right_to_left: bool) -> Self {
self.right_to_left = right_to_left;
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WorkbookMetadata<'a> {
pub date1904: bool,
pub text_truncated: bool,
pub structure_protected: bool,
pub active_sheet: Option<usize>,
pub active_sheet_name: Option<&'a str>,
pub properties: &'a DocProperties,
pub defined_names: &'a [(String, String)],
pub sheets: Vec<SheetMetadata>,
}
impl<'a> WorkbookMetadata<'a> {
pub fn has_1904_epoch(&self) -> bool {
self.date1904
}
pub fn is_text_truncated(&self) -> bool {
self.text_truncated
}
pub fn is_structure_protected(&self) -> bool {
self.structure_protected
}
pub fn active_sheet_index(&self) -> Option<usize> {
self.active_sheet
}
pub fn active_sheet_name(&self) -> Option<&'a str> {
self.active_sheet_name
}
pub fn document_properties(&self) -> &'a DocProperties {
self.properties
}
pub fn defined_names(&self) -> &'a [(String, String)] {
self.defined_names
}
pub fn sheets(&self) -> &[SheetMetadata] {
self.sheets.as_slice()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct WorksheetMetadata<'a> {
pub name: &'a str,
pub sheet_type: SheetType,
pub visible: SheetVisible,
pub dimensions: Option<(u32, u16, u32, u16)>,
pub merged_ranges: &'a [(u32, u16, u32, u16)],
pub hyperlinks: &'a [(u32, u16, String)],
pub comments: &'a [Comment],
pub tables: &'a [Table],
pub data_validations: &'a [DataValidation],
pub conditional_formats: &'a [CondFormat],
pub protected: bool,
pub protection_options: Option<ProtectionOptions>,
pub page_setup: Option<&'a PageSetup>,
pub sheet_view: SheetView,
pub autofilter_range: Option<(u32, u16, u32, u16)>,
pub tab_color: Option<Color>,
pub print_gridlines: bool,
pub print_headings: bool,
pub row_outline_levels: &'a BTreeMap<u32, u8>,
pub col_outline_levels: &'a BTreeMap<u16, u8>,
pub collapsed_rows: &'a BTreeSet<u32>,
pub outline_summary_below: bool,
pub outline_summary_right: bool,
pub images: &'a [Image],
pub charts: &'a [Chart],
pub sparklines: &'a [Sparkline],
}
impl<'a> WorksheetMetadata<'a> {
pub fn name(&self) -> &'a str {
self.name
}
pub fn sheet_type(&self) -> SheetType {
self.sheet_type
}
pub fn visible(&self) -> SheetVisible {
self.visible
}
pub fn is_worksheet(&self) -> bool {
self.sheet_type == SheetType::WorkSheet
}
pub fn is_visible(&self) -> bool {
self.visible == SheetVisible::Visible
}
pub fn is_hidden(&self) -> bool {
self.visible == SheetVisible::Hidden
}
pub fn is_very_hidden(&self) -> bool {
self.visible == SheetVisible::VeryHidden
}
pub fn is_protected(&self) -> bool {
self.protected
}
pub fn merged_ranges(&self) -> &'a [(u32, u16, u32, u16)] {
self.merged_ranges
}
pub fn hyperlinks(&self) -> &'a [(u32, u16, String)] {
self.hyperlinks
}
pub fn comments(&self) -> &'a [Comment] {
self.comments
}
pub fn tables(&self) -> &'a [Table] {
self.tables
}
pub fn data_validations(&self) -> &'a [DataValidation] {
self.data_validations
}
pub fn conditional_formats(&self) -> &'a [CondFormat] {
self.conditional_formats
}
pub fn protection_options(&self) -> Option<ProtectionOptions> {
self.protection_options
}
pub fn page_setup(&self) -> Option<&'a PageSetup> {
self.page_setup
}
pub fn sheet_view(&self) -> SheetView {
self.sheet_view
}
pub fn autofilter_range(&self) -> Option<(u32, u16, u32, u16)> {
self.autofilter_range
}
pub fn tab_color(&self) -> Option<Color> {
self.tab_color
}
pub fn print_gridlines(&self) -> bool {
self.print_gridlines
}
pub fn print_headings(&self) -> bool {
self.print_headings
}
pub fn row_outline_levels(&self) -> &'a BTreeMap<u32, u8> {
self.row_outline_levels
}
pub fn col_outline_levels(&self) -> &'a BTreeMap<u16, u8> {
self.col_outline_levels
}
pub fn collapsed_rows(&self) -> &'a BTreeSet<u32> {
self.collapsed_rows
}
pub fn outline_summary_below(&self) -> bool {
self.outline_summary_below
}
pub fn outline_summary_right(&self) -> bool {
self.outline_summary_right
}
pub fn images(&self) -> &'a [Image] {
self.images
}
pub fn charts(&self) -> &'a [Chart] {
self.charts
}
pub fn sparklines(&self) -> &'a [Sparkline] {
self.sparklines
}
pub fn dimensions_info(&self) -> Option<Dimensions> {
self.dimensions.map(Dimensions::from_range_tuple)
}
}
#[derive(Debug, Clone)]
pub struct Sheet {
pub name: String,
pub is_worksheet: bool,
pub(crate) sheet_type: Option<SheetType>,
pub(crate) cells: Vec<CellEntry>,
pub(crate) col_widths: BTreeMap<u16, f32>,
pub(crate) row_heights: BTreeMap<u32, f32>,
pub(crate) col_formats: BTreeMap<u16, CellStyle>,
pub(crate) row_formats: BTreeMap<u32, CellStyle>,
pub(crate) default_format: Option<CellStyle>,
pub(crate) blank_styles: BTreeMap<(u32, u16), CellStyle>,
pub(crate) default_row_height: Option<f32>,
pub(crate) default_col_width: Option<f32>,
pub(crate) merges: Vec<(u32, u16, u32, u16)>,
pub(crate) read_merges: Vec<(u32, u16, u32, u16)>,
pub(crate) read_hyperlinks: Vec<(u32, u16, String)>,
pub(crate) freeze: Option<(u32, u16)>,
pub(crate) autofilter: Option<(u32, u16, u32, u16)>,
pub(crate) page_setup: Option<PageSetup>,
pub(crate) tab_color: Option<Color>,
pub(crate) protect: bool,
pub(crate) protect_options: Option<ProtectionOptions>,
pub(crate) data_validations: Vec<DataValidation>,
pub(crate) cond_formats: Vec<CondFormat>,
pub(crate) images: Vec<Image>,
pub(crate) charts: Vec<Chart>,
pub(crate) sparklines: Vec<Sparkline>,
pub(crate) tables: Vec<Table>,
pub(crate) table_header_formats: BTreeMap<String, CellStyle>,
pub(crate) comments: Vec<Comment>,
pub(crate) rich: BTreeMap<(u32, u16), Vec<TextRun>>,
pub(crate) hide_gridlines: bool,
pub(crate) zoom: Option<u16>,
pub(crate) show_headers: Option<bool>,
pub(crate) right_to_left: bool,
pub(crate) hidden: bool,
pub(crate) very_hidden: bool,
pub(crate) autofit: bool,
pub(crate) row_outline: BTreeMap<u32, u8>,
pub(crate) col_outline: BTreeMap<u16, u8>,
pub(crate) print_gridlines: bool,
pub(crate) print_headings: bool,
pub(crate) outline_summary_below: bool,
pub(crate) outline_summary_right: bool,
pub(crate) collapsed_rows: BTreeSet<u32>,
}
impl Default for Sheet {
fn default() -> Self {
Sheet {
name: String::default(),
is_worksheet: bool::default(),
sheet_type: None,
cells: Vec::default(),
col_widths: BTreeMap::default(),
row_heights: BTreeMap::default(),
col_formats: BTreeMap::default(),
row_formats: BTreeMap::default(),
default_format: None,
blank_styles: BTreeMap::default(),
default_row_height: None,
default_col_width: None,
merges: Vec::default(),
read_merges: Vec::default(),
read_hyperlinks: Vec::default(),
freeze: None,
autofilter: None,
page_setup: None,
tab_color: None,
protect: bool::default(),
protect_options: None,
data_validations: Vec::default(),
cond_formats: Vec::default(),
images: Vec::default(),
charts: Vec::default(),
sparklines: Vec::default(),
tables: Vec::default(),
table_header_formats: BTreeMap::default(),
comments: Vec::default(),
rich: BTreeMap::default(),
hide_gridlines: bool::default(),
zoom: None,
show_headers: None,
right_to_left: bool::default(),
hidden: bool::default(),
very_hidden: bool::default(),
autofit: bool::default(),
row_outline: BTreeMap::default(),
col_outline: BTreeMap::default(),
print_gridlines: bool::default(),
print_headings: bool::default(),
outline_summary_below: true,
outline_summary_right: true,
collapsed_rows: BTreeSet::default(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Comment {
pub row: u32,
pub col: u16,
pub text: String,
pub author: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CommentAuthor(Option<String>);
impl From<Option<&str>> for CommentAuthor {
fn from(author: Option<&str>) -> Self {
Self(author.map(str::to_string))
}
}
impl From<&str> for CommentAuthor {
fn from(author: &str) -> Self {
Self(Some(author.to_string()))
}
}
impl From<&String> for CommentAuthor {
fn from(author: &String) -> Self {
Self(Some(author.to_string()))
}
}
impl From<String> for CommentAuthor {
fn from(author: String) -> Self {
Self(Some(author))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Table {
pub range: (u32, u16, u32, u16),
pub name: String,
pub columns: Vec<String>,
pub style: Option<String>,
}
impl Table {
pub fn new<I, S>(range: (u32, u16, u32, u16), name: impl AsRef<str>, columns: I) -> Self
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
Table {
range,
name: name.as_ref().to_string(),
columns: columns
.into_iter()
.map(|column| column.as_ref().to_string())
.collect(),
style: None,
}
}
pub fn with_style(mut self, style: impl AsRef<str>) -> Self {
self.style = Some(style.as_ref().to_string());
self
}
pub fn name(&self) -> &str {
self.name.as_str()
}
pub fn columns(&self) -> &[String] {
self.columns.as_slice()
}
pub fn range(&self) -> (u32, u16, u32, u16) {
self.range
}
pub fn data<'a>(&self, sheet: &'a Sheet) -> Range<'a> {
table_data_range(sheet, self)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Image {
pub data: Vec<u8>,
pub format: ImageFmt,
pub from: (u32, u16),
pub to: Option<(u32, u16)>,
}
impl Image {
pub fn new(data: impl Into<Vec<u8>>, format: ImageFmt, from: (u32, u16)) -> Self {
Image {
data: data.into(),
format,
from,
to: None,
}
}
pub fn with_to(mut self, to: (u32, u16)) -> Self {
self.to = Some(to);
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Picture {
pub row: u32,
pub col: u32,
pub sheet_name: String,
pub extension: String,
pub data: Vec<u8>,
pub name: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ImageFmt {
Png,
Jpeg,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SparklineKind {
Line,
Column,
WinLoss,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Sparkline {
pub location: (u32, u16),
pub range: String,
pub kind: SparklineKind,
}
impl Sparkline {
pub fn new(location: (u32, u16), range: impl AsRef<str>) -> Self {
Sparkline {
location,
range: range.as_ref().to_string(),
kind: SparklineKind::Line,
}
}
pub fn with_kind(mut self, kind: SparklineKind) -> Self {
self.kind = kind;
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Chart {
pub kind: ChartKind,
pub title: Option<String>,
pub series: Vec<Series>,
pub legend: bool,
pub data_labels: bool,
pub x_axis_title: Option<String>,
pub y_axis_title: Option<String>,
pub from: (u32, u16),
pub to: (u32, u16),
}
impl Chart {
pub fn new(kind: ChartKind, from: (u32, u16), to: (u32, u16)) -> Self {
Chart {
kind,
title: None,
series: Vec::new(),
legend: false,
data_labels: false,
x_axis_title: None,
y_axis_title: None,
from,
to,
}
}
pub fn with_title(mut self, title: impl AsRef<str>) -> Self {
self.title = Some(title.as_ref().to_string());
self
}
pub fn with_x_axis_title(mut self, title: impl AsRef<str>) -> Self {
self.x_axis_title = Some(title.as_ref().to_string());
self
}
pub fn with_y_axis_title(mut self, title: impl AsRef<str>) -> Self {
self.y_axis_title = Some(title.as_ref().to_string());
self
}
pub fn with_legend(mut self, show: bool) -> Self {
self.legend = show;
self
}
pub fn with_data_labels(mut self, show: bool) -> Self {
self.data_labels = show;
self
}
pub fn with_series<I>(mut self, series: I) -> Self
where
I: IntoIterator<Item = Series>,
{
self.series = series.into_iter().collect();
self
}
pub fn add_series(mut self, series: Series) -> Self {
self.series.push(series);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChartKind {
Bar,
Line,
Pie,
Scatter,
Area,
Doughnut,
Radar,
Bubble,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Series {
pub name: Option<String>,
pub categories: Option<String>,
pub values: String,
pub bubble_sizes: Option<String>,
}
impl Series {
pub fn new(values: impl AsRef<str>) -> Self {
Series {
name: None,
categories: None,
values: values.as_ref().to_string(),
bubble_sizes: None,
}
}
pub fn with_name(mut self, name: impl AsRef<str>) -> Self {
self.name = Some(name.as_ref().to_string());
self
}
pub fn with_categories(mut self, categories: impl AsRef<str>) -> Self {
self.categories = Some(categories.as_ref().to_string());
self
}
pub fn with_bubble_sizes(mut self, bubble_sizes: impl AsRef<str>) -> Self {
self.bubble_sizes = Some(bubble_sizes.as_ref().to_string());
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CondFormat {
pub sqref: (u32, u16, u32, u16),
pub rule: CfRule,
}
impl CondFormat {
pub fn new(sqref: (u32, u16, u32, u16), rule: CfRule) -> Self {
CondFormat { sqref, rule }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CfRule {
CellIs {
op: DvOp,
formula1: String,
formula2: Option<String>,
fill: Color,
},
ColorScale2 {
min: Color,
max: Color,
},
ColorScale3 {
min: Color,
mid: Color,
max: Color,
},
DataBar {
color: Color,
},
TopBottom {
rank: u32,
bottom: bool,
percent: bool,
fill: Color,
},
AboveAverage {
below: bool,
fill: Color,
},
DuplicateValues {
unique: bool,
fill: Color,
},
Expression {
formula: String,
fill: Color,
},
}
impl CfRule {
pub fn cell_is(
op: DvOp,
formula1: impl AsRef<str>,
formula2: Option<impl AsRef<str>>,
fill: impl Into<Color>,
) -> Self {
CfRule::CellIs {
op,
formula1: formula1.as_ref().to_string(),
formula2: formula2.map(|formula| formula.as_ref().to_string()),
fill: fill.into(),
}
}
pub fn color_scale2(min: impl Into<Color>, max: impl Into<Color>) -> Self {
CfRule::ColorScale2 {
min: min.into(),
max: max.into(),
}
}
pub fn color_scale3(
min: impl Into<Color>,
mid: impl Into<Color>,
max: impl Into<Color>,
) -> Self {
CfRule::ColorScale3 {
min: min.into(),
mid: mid.into(),
max: max.into(),
}
}
pub fn data_bar(color: impl Into<Color>) -> Self {
CfRule::DataBar {
color: color.into(),
}
}
pub fn top_bottom(rank: u32, bottom: bool, percent: bool, fill: impl Into<Color>) -> Self {
CfRule::TopBottom {
rank,
bottom,
percent,
fill: fill.into(),
}
}
pub fn above_average(below: bool, fill: impl Into<Color>) -> Self {
CfRule::AboveAverage {
below,
fill: fill.into(),
}
}
pub fn duplicate_values(unique: bool, fill: impl Into<Color>) -> Self {
CfRule::DuplicateValues {
unique,
fill: fill.into(),
}
}
pub fn expression(formula: impl AsRef<str>, fill: impl Into<Color>) -> Self {
CfRule::Expression {
formula: formula.as_ref().to_string(),
fill: fill.into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DataValidation {
pub sqref: (u32, u16, u32, u16),
pub kind: DvKind,
pub operator: DvOp,
pub formula1: String,
pub formula2: Option<String>,
pub allow_blank: bool,
pub show_input_message: bool,
pub show_error_message: bool,
pub prompt: Option<(String, String)>,
pub error: Option<(String, String)>,
}
impl DataValidation {
pub fn new(
sqref: (u32, u16, u32, u16),
kind: DvKind,
operator: DvOp,
formula1: impl AsRef<str>,
) -> Self {
DataValidation {
sqref,
kind,
operator,
formula1: formula1.as_ref().to_string(),
formula2: None,
allow_blank: true,
show_input_message: true,
show_error_message: true,
prompt: None,
error: None,
}
}
pub fn list(sqref: (u32, u16, u32, u16), source: impl AsRef<str>) -> Self {
DataValidation::new(sqref, DvKind::List, DvOp::Between, source)
}
pub fn with_formula2(mut self, formula2: impl AsRef<str>) -> Self {
self.formula2 = Some(formula2.as_ref().to_string());
self
}
pub fn with_allow_blank(mut self, allow_blank: bool) -> Self {
self.allow_blank = allow_blank;
self
}
pub fn with_prompt(mut self, title: impl AsRef<str>, message: impl AsRef<str>) -> Self {
self.show_input_message = true;
self.prompt = Some((title.as_ref().to_string(), message.as_ref().to_string()));
self
}
pub fn with_error(mut self, title: impl AsRef<str>, message: impl AsRef<str>) -> Self {
self.show_error_message = true;
self.error = Some((title.as_ref().to_string(), message.as_ref().to_string()));
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DvKind {
List,
Whole,
Decimal,
Date,
Time,
TextLength,
Custom,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DvOp {
Between,
NotBetween,
Equal,
NotEqual,
GreaterThan,
LessThan,
GreaterThanOrEqual,
LessThanOrEqual,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct ProtectionOptions {
pub sort: bool,
pub auto_filter: bool,
pub format_cells: bool,
pub format_columns: bool,
pub format_rows: bool,
pub insert_columns: bool,
pub insert_rows: bool,
pub insert_hyperlinks: bool,
pub delete_columns: bool,
pub delete_rows: bool,
pub pivot_tables: bool,
}
impl ProtectionOptions {
pub fn new() -> Self {
ProtectionOptions::default()
}
pub fn allow_sort(mut self) -> Self {
self.sort = true;
self
}
pub fn allow_auto_filter(mut self) -> Self {
self.auto_filter = true;
self
}
pub fn allow_format_cells(mut self) -> Self {
self.format_cells = true;
self
}
pub fn allow_format_columns(mut self) -> Self {
self.format_columns = true;
self
}
pub fn allow_format_rows(mut self) -> Self {
self.format_rows = true;
self
}
pub fn allow_insert_columns(mut self) -> Self {
self.insert_columns = true;
self
}
pub fn allow_insert_rows(mut self) -> Self {
self.insert_rows = true;
self
}
pub fn allow_insert_hyperlinks(mut self) -> Self {
self.insert_hyperlinks = true;
self
}
pub fn allow_delete_columns(mut self) -> Self {
self.delete_columns = true;
self
}
pub fn allow_delete_rows(mut self) -> Self {
self.delete_rows = true;
self
}
pub fn allow_pivot_tables(mut self) -> Self {
self.pivot_tables = true;
self
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct PageSetup {
pub landscape: bool,
pub margins: Option<(f64, f64, f64, f64, f64, f64)>,
pub print_area: Option<(u32, u16, u32, u16)>,
pub repeat_rows: Option<(u32, u32)>,
pub repeat_cols: Option<(u16, u16)>,
pub fit_to_width: Option<u16>,
pub fit_to_height: Option<u16>,
pub header: Option<String>,
pub footer: Option<String>,
pub paper_size: Option<u16>,
pub scale: Option<u16>,
pub center_horizontally: bool,
pub center_vertically: bool,
pub first_page_number: Option<u16>,
}
impl PageSetup {
pub fn new() -> Self {
PageSetup::default()
}
pub fn with_landscape(mut self) -> Self {
self.landscape = true;
self
}
pub fn with_margins(
mut self,
left: f64,
right: f64,
top: f64,
bottom: f64,
header: f64,
footer: f64,
) -> Self {
self.margins = Some((left, right, top, bottom, header, footer));
self
}
pub fn with_print_area(mut self, range: (u32, u16, u32, u16)) -> Self {
self.print_area = Some(range);
self
}
pub fn with_repeat_rows(mut self, first: u32, last: u32) -> Self {
self.repeat_rows = Some((first, last));
self
}
pub fn with_repeat_cols(mut self, first: u16, last: u16) -> Self {
self.repeat_cols = Some((first, last));
self
}
pub fn with_fit_to_pages(mut self, width: u16, height: u16) -> Self {
self.fit_to_width = Some(width);
self.fit_to_height = Some(height);
self
}
pub fn with_header(mut self, header: impl AsRef<str>) -> Self {
self.header = Some(header.as_ref().to_string());
self
}
pub fn with_footer(mut self, footer: impl AsRef<str>) -> Self {
self.footer = Some(footer.as_ref().to_string());
self
}
pub fn with_paper_size(mut self, paper_size: u16) -> Self {
self.paper_size = Some(paper_size);
self
}
pub fn with_scale(mut self, scale: u16) -> Self {
self.scale = Some(scale);
self
}
pub fn with_center_horizontally(mut self, center: bool) -> Self {
self.center_horizontally = center;
self
}
pub fn with_center_vertically(mut self, center: bool) -> Self {
self.center_vertically = center;
self
}
pub fn with_first_page_number(mut self, first_page_number: u16) -> Self {
self.first_page_number = Some(first_page_number);
self
}
}
impl Sheet {
pub fn to_text(&self) -> String {
let mut by_coord: BTreeMap<(u32, u16), &str> = BTreeMap::new();
for c in &self.cells {
by_coord.insert((c.row, c.col), c.text.as_str());
}
let mut out = String::new();
let mut cur_row: Option<u32> = None;
for ((row, _col), text) in &by_coord {
if text.is_empty() {
continue;
}
match cur_row {
Some(r) if r == *row => out.push('\t'),
_ => {
if cur_row.is_some() {
out.push('\n');
}
cur_row = Some(*row);
}
}
out.push_str(text);
}
out
}
pub fn to_csv(&self) -> String {
self.to_csv_with_delimiter(',')
}
pub fn to_csv_with_delimiter(&self, delimiter: char) -> String {
let delimiter = if delimiter == '"' { ',' } else { delimiter };
let mut by_row: BTreeMap<u32, BTreeMap<u16, &str>> = BTreeMap::new();
for cell in &self.cells {
by_row
.entry(cell.row)
.or_default()
.insert(cell.col, cell.text.as_str());
}
let mut out = String::new();
for (_row, cols) in by_row {
if !out.is_empty() {
out.push('\n');
}
let mut first = true;
let mut next_col: Option<u32> = None;
for (col, text) in cols {
let col = u32::from(col);
if let Some(mut expected) = next_col {
while expected < col {
if !first {
out.push(delimiter);
}
first = false;
expected += 1;
}
}
if !first {
out.push(delimiter);
}
push_csv_field(&mut out, text, delimiter);
first = false;
next_col = col.checked_add(1);
}
}
out
}
pub fn to_html(&self) -> String {
let mut by_row: BTreeMap<u32, BTreeMap<u16, &str>> = BTreeMap::new();
for cell in &self.cells {
by_row
.entry(cell.row)
.or_default()
.insert(cell.col, cell.text.as_str());
}
let merges = self.merged_ranges();
let mut out = String::from("<table>");
for (row, cols) in by_row {
out.push_str("<tr>");
let max_col = cols.keys().next_back().copied().unwrap_or(0);
for col in 0..=max_col {
let merge = html_merge_for_cell(merges, row, col);
if merge.is_some_and(|merge| merge.skip) {
continue;
}
let text = cols.get(&col).copied().unwrap_or_default();
out.push_str("<td");
if let Some(merge) = merge {
if merge.rowspan > 1 {
out.push_str(&format!(r#" rowspan="{}""#, merge.rowspan));
}
if merge.colspan > 1 {
out.push_str(&format!(r#" colspan="{}""#, merge.colspan));
}
}
out.push('>');
push_html_escaped(&mut out, text);
out.push_str("</td>");
}
out.push_str("</tr>");
}
out.push_str("</table>");
out
}
pub fn to_markdown(&self) -> String {
const MAX_MD_COLS: usize = 256;
if !self.merged_ranges().is_empty() {
return self.to_html();
}
let mut by_row: BTreeMap<u32, BTreeMap<u16, &str>> = BTreeMap::new();
for cell in &self.cells {
by_row
.entry(cell.row)
.or_default()
.insert(cell.col, cell.text.as_str());
}
let max_col = by_row
.values()
.filter_map(|cols| cols.keys().next_back().copied())
.max();
let Some(max_col) = max_col else {
return String::new();
};
let width = usize::from(max_col) + 1;
if width > MAX_MD_COLS {
return self.to_html();
}
let mut rows = Vec::new();
for (_row, cols) in by_row {
let mut row = Vec::with_capacity(width);
for col in 0..=max_col {
row.push(markdown_cell(cols.get(&col).copied().unwrap_or_default()));
}
rows.push(row);
}
if rows.is_empty() {
return String::new();
}
let mut out = String::new();
push_markdown_row(&mut out, &rows[0]);
out.push('\n');
let separators = vec!["---".to_string(); width];
push_markdown_row(&mut out, &separators);
for row in rows.iter().skip(1) {
out.push('\n');
push_markdown_row(&mut out, row);
}
out
}
pub fn metadata(&self) -> WorksheetMetadata<'_> {
WorksheetMetadata {
name: &self.name,
sheet_type: self.sheet_type(),
visible: self.visible(),
dimensions: self.dimensions(),
merged_ranges: self.merged_ranges(),
hyperlinks: self.hyperlinks(),
comments: self.comments(),
tables: self.tables(),
data_validations: self.data_validations(),
conditional_formats: self.conditional_formats(),
protected: self.is_protected(),
protection_options: self.protection_options(),
page_setup: self.page_setup(),
sheet_view: self.sheet_view(),
autofilter_range: self.autofilter_range(),
tab_color: self.tab_color(),
print_gridlines: self.print_gridlines(),
print_headings: self.print_headings(),
row_outline_levels: self.row_outline_levels(),
col_outline_levels: self.col_outline_levels(),
collapsed_rows: self.collapsed_rows(),
outline_summary_below: self.outline_summary_below(),
outline_summary_right: self.outline_summary_right(),
images: self.images(),
charts: self.charts(),
sparklines: self.sparklines(),
}
}
pub fn cells(&self) -> impl Iterator<Item = (u32, u16, &Cell)> {
self.cells.iter().map(|c| (c.row, c.col, &c.value))
}
pub fn cell(&self, row: u32, col: u16) -> Option<&Cell> {
self.cells
.iter()
.rev()
.find(|c| c.row == row && c.col == col)
.map(|c| &c.value)
}
pub fn formatted(&self, row: u32, col: u16) -> Option<&str> {
self.cells
.iter()
.rev()
.find(|c| c.row == row && c.col == col)
.map(|c| c.text.as_str())
}
pub fn tab_color(&self) -> Option<Color> {
self.tab_color
}
pub fn print_gridlines(&self) -> bool {
self.print_gridlines
}
pub fn print_headings(&self) -> bool {
self.print_headings
}
pub fn row_outline_levels(&self) -> &BTreeMap<u32, u8> {
&self.row_outline
}
pub fn col_outline_levels(&self) -> &BTreeMap<u16, u8> {
&self.col_outline
}
pub fn collapsed_rows(&self) -> &BTreeSet<u32> {
&self.collapsed_rows
}
pub fn outline_summary_below(&self) -> bool {
self.outline_summary_below
}
pub fn outline_summary_right(&self) -> bool {
self.outline_summary_right
}
pub fn merged_ranges(&self) -> &[(u32, u16, u32, u16)] {
if self.merges.is_empty() {
&self.read_merges
} else {
&self.merges
}
}
pub fn hyperlinks(&self) -> &[(u32, u16, String)] {
&self.read_hyperlinks
}
pub fn comments(&self) -> &[Comment] {
&self.comments
}
pub fn tables(&self) -> &[Table] {
&self.tables
}
pub fn data_validations(&self) -> &[DataValidation] {
&self.data_validations
}
pub fn conditional_formats(&self) -> &[CondFormat] {
&self.cond_formats
}
pub fn is_protected(&self) -> bool {
self.protect
}
pub fn protection_options(&self) -> Option<ProtectionOptions> {
self.protect_options
}
pub fn page_setup(&self) -> Option<&PageSetup> {
self.page_setup.as_ref()
}
pub fn sheet_view(&self) -> SheetView {
SheetView {
freeze: self.freeze,
hide_gridlines: self.hide_gridlines,
zoom: self.zoom,
show_headers: self.show_headers,
right_to_left: self.right_to_left,
}
}
pub fn autofilter_range(&self) -> Option<(u32, u16, u32, u16)> {
self.autofilter
}
pub fn images(&self) -> &[Image] {
&self.images
}
pub fn charts(&self) -> &[Chart] {
&self.charts
}
pub fn sparklines(&self) -> &[Sparkline] {
&self.sparklines
}
pub fn is_hidden(&self) -> bool {
self.hidden
}
pub fn is_very_hidden(&self) -> bool {
self.very_hidden
}
pub fn sheet_type(&self) -> SheetType {
self.sheet_type.unwrap_or(if self.is_worksheet {
SheetType::WorkSheet
} else {
SheetType::ChartSheet
})
}
pub fn visible(&self) -> SheetVisible {
if self.very_hidden {
SheetVisible::VeryHidden
} else if self.hidden {
SheetVisible::Hidden
} else {
SheetVisible::Visible
}
}
pub fn dimensions(&self) -> Option<(u32, u16, u32, u16)> {
let mut it = self.cells.iter();
let f = it.next()?;
let (mut r0, mut c0, mut r1, mut c1) = (f.row, f.col, f.row, f.col);
for c in it {
r0 = r0.min(c.row);
c0 = c0.min(c.col);
r1 = r1.max(c.row);
c1 = c1.max(c.col);
}
Some((r0, c0, r1, c1))
}
pub fn dimensions_info(&self) -> Option<Dimensions> {
self.dimensions().map(Dimensions::from_range_tuple)
}
pub fn rows(&self) -> impl Iterator<Item = (u32, Vec<(u16, &Cell)>)> {
let mut by_row: BTreeMap<u32, BTreeMap<u16, &Cell>> = BTreeMap::new();
for c in &self.cells {
by_row.entry(c.row).or_default().insert(c.col, &c.value);
}
by_row
.into_iter()
.map(|(r, cols)| (r, cols.into_iter().collect()))
}
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct Workbook {
pub sheets: Vec<Sheet>,
pub date1904: bool,
pub text_truncated: bool,
pub properties: DocProperties,
pub defined_names: Vec<(String, String)>,
pub(crate) active_sheet: usize,
pub(crate) protect_structure: bool,
pub(crate) header_row: HeaderRow,
}
pub trait Reader {
fn sheet_names(&self) -> Vec<&str>;
fn sheets_metadata(&self) -> Vec<SheetMetadata>;
fn defined_names(&self) -> &[(String, String)];
fn with_header_row(&mut self, header_row: HeaderRow) -> &mut Self
where
Self: Sized;
fn header_row(&self) -> HeaderRow;
fn worksheet_range(&self, name: &str) -> Option<Range<'_>>;
fn worksheet_range_ref(&self, name: &str) -> Option<Range<'_>> {
self.worksheet_range(name)
}
fn worksheet_range_at(&self, index: usize) -> Option<Range<'_>>;
fn worksheet_range_at_ref(&self, index: usize) -> Option<Range<'_>> {
self.worksheet_range_at(index)
}
fn worksheet_formula(&self, name: &str) -> Option<FormulaRange<'_>>;
fn worksheet_formula_ref(&self, name: &str) -> Option<FormulaRange<'_>> {
self.worksheet_formula(name)
}
fn worksheet_formula_at(&self, index: usize) -> Option<FormulaRange<'_>>;
fn worksheet_formula_at_ref(&self, index: usize) -> Option<FormulaRange<'_>> {
self.worksheet_formula_at(index)
}
fn worksheet_merge_cells(&self, name: &str) -> Option<&[(u32, u16, u32, u16)]>;
fn worksheet_merge_cells_at(&self, index: usize) -> Option<&[(u32, u16, u32, u16)]>;
fn merged_regions(&self) -> Vec<(&str, Dimensions)>;
fn merged_regions_by_sheet(&self, name: &str) -> Vec<Dimensions>;
fn worksheet_metadata(&self, name: &str) -> Option<WorksheetMetadata<'_>>;
fn worksheet_metadata_at(&self, index: usize) -> Option<WorksheetMetadata<'_>>;
fn worksheets_metadata(&self) -> Vec<WorksheetMetadata<'_>>;
fn worksheets(&self) -> Vec<(String, Range<'_>)>;
fn metadata(&self) -> WorkbookMetadata<'_>;
fn has_1904_epoch(&self) -> bool {
self.metadata().date1904
}
fn active_sheet_index(&self) -> Option<usize> {
self.metadata().active_sheet
}
fn active_sheet_name(&self) -> Option<&str> {
self.metadata().active_sheet_name
}
fn pictures(&self) -> Option<Vec<(String, Vec<u8>)>>;
fn pictures_with_metadata(&self) -> Vec<Picture>;
fn table_names(&self) -> Vec<&str>;
fn table_names_in_sheet(&self, sheet_name: &str) -> Vec<&str>;
fn table_by_name(&self, table_name: &str) -> Option<(&str, &Table)>;
fn table_by_name_ref(&self, table_name: &str) -> Option<(&str, &Table)> {
self.table_by_name(table_name)
}
fn table_data_by_name(&self, table_name: &str) -> Option<(&str, Range<'_>)>;
fn table_data_by_name_ref(&self, table_name: &str) -> Option<(&str, Range<'_>)> {
self.table_data_by_name(table_name)
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct DocProperties {
pub title: Option<String>,
pub subject: Option<String>,
pub creator: Option<String>,
pub keywords: Option<String>,
pub description: Option<String>,
pub last_modified_by: Option<String>,
pub company: Option<String>,
pub created: Option<String>,
}
impl DocProperties {
pub fn new() -> Self {
DocProperties::default()
}
pub fn with_title(mut self, title: impl AsRef<str>) -> Self {
self.title = Some(title.as_ref().to_string());
self
}
pub fn with_subject(mut self, subject: impl AsRef<str>) -> Self {
self.subject = Some(subject.as_ref().to_string());
self
}
pub fn with_creator(mut self, creator: impl AsRef<str>) -> Self {
self.creator = Some(creator.as_ref().to_string());
self
}
pub fn with_keywords(mut self, keywords: impl AsRef<str>) -> Self {
self.keywords = Some(keywords.as_ref().to_string());
self
}
pub fn with_description(mut self, description: impl AsRef<str>) -> Self {
self.description = Some(description.as_ref().to_string());
self
}
pub fn with_last_modified_by(mut self, last_modified_by: impl AsRef<str>) -> Self {
self.last_modified_by = Some(last_modified_by.as_ref().to_string());
self
}
pub fn with_company(mut self, company: impl AsRef<str>) -> Self {
self.company = Some(company.as_ref().to_string());
self
}
pub fn with_created(mut self, created: impl AsRef<str>) -> Self {
self.created = Some(created.as_ref().to_string());
self
}
}
fn image_extension(format: ImageFmt) -> &'static str {
match format {
ImageFmt::Png => "png",
ImageFmt::Jpeg => "jpg",
}
}
fn push_csv_field(out: &mut String, field: &str, delimiter: char) {
let quote = field.contains(delimiter)
|| field.contains('"')
|| field.contains('\n')
|| field.contains('\r');
if !quote {
out.push_str(field);
return;
}
out.push('"');
for ch in field.chars() {
if ch == '"' {
out.push('"');
}
out.push(ch);
}
out.push('"');
}
#[derive(Clone, Copy)]
struct HtmlMerge {
rowspan: u32,
colspan: u32,
skip: bool,
}
fn html_merge_for_cell(ranges: &[(u32, u16, u32, u16)], row: u32, col: u16) -> Option<HtmlMerge> {
for &(r0, c0, r1, c1) in ranges {
let (top, bottom) = (r0.min(r1), r0.max(r1));
let (left, right) = (c0.min(c1), c0.max(c1));
if top <= row && row <= bottom && left <= col && col <= right {
return Some(HtmlMerge {
rowspan: bottom.saturating_sub(top).saturating_add(1),
colspan: u32::from(right.saturating_sub(left).saturating_add(1)),
skip: row != top || col != left,
});
}
}
None
}
fn push_html_escaped(out: &mut String, text: &str) {
for ch in text.chars() {
match ch {
'&' => out.push_str("&"),
'<' => out.push_str("<"),
'>' => out.push_str(">"),
'"' => out.push_str("""),
_ => out.push(ch),
}
}
}
fn markdown_cell(text: &str) -> String {
let mut out = String::new();
for ch in text.chars() {
match ch {
'|' => out.push_str(r"\|"),
'\n' | '\r' => out.push_str("<br>"),
_ => out.push(ch),
}
}
out
}
fn push_markdown_row(out: &mut String, cells: &[String]) {
out.push('|');
for cell in cells {
out.push(' ');
out.push_str(cell);
out.push_str(" |");
}
}
impl Workbook {
pub fn text(&self) -> String {
let mut out = String::new();
for sheet in self.sheets.iter().filter(|s| s.is_worksheet) {
if !out.is_empty() {
out.push('\n');
}
out.push_str("# ");
out.push_str(&sheet.name);
out.push('\n');
out.push_str(&sheet.to_text());
out.push('\n');
}
out
}
pub fn to_csv(&self, sheet_index: usize) -> Option<String> {
self.to_csv_with_delimiter(sheet_index, ',')
}
pub fn to_csv_with_delimiter(&self, sheet_index: usize, delimiter: char) -> Option<String> {
if delimiter == '"' {
return None;
}
self.sheets
.get(sheet_index)
.filter(|sheet| sheet.is_worksheet)
.map(|sheet| sheet.to_csv_with_delimiter(delimiter))
}
pub fn to_html(&self, sheet_index: usize) -> Option<String> {
self.sheets
.get(sheet_index)
.filter(|sheet| sheet.is_worksheet)
.map(Sheet::to_html)
}
pub fn to_markdown(&self, sheet_index: usize) -> Option<String> {
self.sheets
.get(sheet_index)
.filter(|sheet| sheet.is_worksheet)
.map(Sheet::to_markdown)
}
pub fn is_partial(&self) -> bool {
self.text_truncated
}
pub fn has_1904_epoch(&self) -> bool {
self.date1904
}
pub fn with_header_row(&mut self, header_row: HeaderRow) -> &mut Self {
self.header_row = header_row;
self
}
pub fn header_row(&self) -> HeaderRow {
self.header_row
}
fn apply_header_row_to_range<'a>(&self, range: Range<'a>) -> Range<'a> {
match self.header_row {
HeaderRow::FirstNonEmptyRow => range,
HeaderRow::Row(header_row) => {
let (Some((_, start_col)), Some((end_row, end_col))) = (range.start(), range.end())
else {
return range;
};
if header_row > end_row {
Range::empty()
} else {
range.range((header_row, start_col), (end_row, end_col))
}
}
}
}
pub fn is_structure_protected(&self) -> bool {
self.protect_structure
}
pub fn active_sheet_index(&self) -> Option<usize> {
(self.active_sheet < self.sheets.len()).then_some(self.active_sheet)
}
pub fn active_sheet_name(&self) -> Option<&str> {
self.active_sheet_index()
.and_then(|index| self.sheets.get(index))
.map(|sheet| sheet.name.as_str())
}
pub fn metadata(&self) -> WorkbookMetadata<'_> {
WorkbookMetadata {
date1904: self.date1904,
text_truncated: self.text_truncated,
structure_protected: self.is_structure_protected(),
active_sheet: self.active_sheet_index(),
active_sheet_name: self.active_sheet_name(),
properties: &self.properties,
defined_names: &self.defined_names,
sheets: self.sheets_metadata(),
}
}
pub fn pictures(&self) -> Option<Vec<(String, Vec<u8>)>> {
let pictures: Vec<_> = self
.sheets
.iter()
.flat_map(|sheet| {
sheet.images.iter().map(|image| {
(
image_extension(image.format).to_string(),
image.data.clone(),
)
})
})
.collect();
(!pictures.is_empty()).then_some(pictures)
}
pub fn pictures_with_metadata(&self) -> Vec<Picture> {
self.sheets
.iter()
.flat_map(|sheet| {
sheet.images.iter().map(|image| Picture {
row: image.from.0,
col: u32::from(image.from.1),
sheet_name: sheet.name.clone(),
extension: image_extension(image.format).to_string(),
data: image.data.clone(),
name: String::new(),
})
})
.collect()
}
pub fn table_names(&self) -> Vec<&str> {
self.sheets
.iter()
.flat_map(|sheet| sheet.tables.iter().map(|table| table.name.as_str()))
.collect()
}
pub fn table_names_in_sheet(&self, sheet_name: &str) -> Vec<&str> {
self.sheet_by_name(sheet_name)
.map(|sheet| {
sheet
.tables
.iter()
.map(|table| table.name.as_str())
.collect()
})
.unwrap_or_default()
}
pub fn table_by_name(&self, table_name: &str) -> Option<(&str, &Table)> {
self.sheets.iter().find_map(|sheet| {
sheet
.tables
.iter()
.find(|table| table_name_eq(&table.name, table_name))
.map(|table| (sheet.name.as_str(), table))
})
}
pub fn table_by_name_ref(&self, table_name: &str) -> Option<(&str, &Table)> {
self.table_by_name(table_name)
}
pub fn table_data_by_name(&self, table_name: &str) -> Option<(&str, Range<'_>)> {
self.sheets.iter().find_map(|sheet| {
let table = sheet
.tables
.iter()
.find(|table| table_name_eq(&table.name, table_name))?;
Some((sheet.name.as_str(), table_data_range(sheet, table)))
})
}
pub fn table_data_by_name_ref(&self, table_name: &str) -> Option<(&str, Range<'_>)> {
self.table_data_by_name(table_name)
}
}
fn table_data_range<'a>(sheet: &'a Sheet, table: &Table) -> Range<'a> {
let Some(first_data_row) = table.range.0.checked_add(1) else {
return Range::empty();
};
if first_data_row > table.range.2 {
return Range::empty();
}
sheet.range().range(
(first_data_row, u32::from(table.range.1)),
(table.range.2, u32::from(table.range.3)),
)
}
fn table_name_eq(left: &str, right: &str) -> bool {
left.chars()
.flat_map(char::to_lowercase)
.eq(right.chars().flat_map(char::to_lowercase))
}
impl Cell {
pub fn text(value: impl Into<String>) -> Self {
Cell::Text(value.into())
}
pub fn string(value: impl Into<String>) -> Self {
Self::text(value)
}
pub fn int(value: impl Into<i64>) -> Self {
Cell::Number(value.into() as f64)
}
pub fn float(value: impl Into<f64>) -> Self {
Cell::Number(value.into())
}
pub fn boolean(value: bool) -> Self {
Cell::Bool(value)
}
pub fn error(error: CellErrorType) -> Self {
Cell::Error(error.as_str().to_string())
}
pub fn date(serial: f64) -> Self {
Self::date_time(serial)
}
pub fn date_time(serial: f64) -> Self {
Cell::Date(serial)
}
pub fn formula(formula: impl Into<String>, cached: impl Into<Cell>) -> Self {
Cell::Formula {
formula: formula.into(),
cached: Box::new(cached.into()),
}
}
}
impl From<&str> for Cell {
fn from(s: &str) -> Self {
Cell::Text(s.to_string())
}
}
impl From<String> for Cell {
fn from(s: String) -> Self {
Cell::Text(s)
}
}
impl From<&Cell> for Cell {
fn from(cell: &Cell) -> Self {
cell.clone()
}
}
impl From<f64> for Cell {
fn from(n: f64) -> Self {
Cell::Number(n)
}
}
impl From<f32> for Cell {
fn from(n: f32) -> Self {
Cell::Number(f64::from(n))
}
}
impl From<i64> for Cell {
fn from(n: i64) -> Self {
Cell::Number(n as f64)
}
}
impl From<i32> for Cell {
fn from(n: i32) -> Self {
Cell::Number(n as f64)
}
}
macro_rules! impl_cell_from_signed_int {
($($ty:ty),+ $(,)?) => {
$(
impl From<$ty> for Cell {
fn from(n: $ty) -> Self {
Cell::Number(n as f64)
}
}
)+
};
}
macro_rules! impl_cell_from_unsigned_int {
($($ty:ty),+ $(,)?) => {
$(
impl From<$ty> for Cell {
fn from(n: $ty) -> Self {
Cell::Number(n as f64)
}
}
)+
};
}
impl_cell_from_signed_int!(i8, i16, i128, isize);
impl_cell_from_unsigned_int!(u8, u16, u32, u64, u128, usize);
impl From<bool> for Cell {
fn from(b: bool) -> Self {
Cell::Bool(b)
}
}
impl From<CellErrorType> for Cell {
fn from(error: CellErrorType) -> Self {
Cell::Error(error.as_str().to_string())
}
}
fn display_text(v: &Cell) -> String {
match v {
Cell::Text(s) => s.clone(),
Cell::Number(n) | Cell::Date(n) => crate::format_number(*n),
Cell::Bool(b) => if *b { "TRUE" } else { "FALSE" }.to_string(),
Cell::Error(e) => e.clone(),
Cell::Formula { cached, .. } => display_text(cached),
}
}
impl std::fmt::Display for Cell {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&display_text(self))
}
}
impl PartialEq<&str> for Cell {
fn eq(&self, other: &&str) -> bool {
cell_eq_str(self, other)
}
}
impl PartialEq<str> for Cell {
fn eq(&self, other: &str) -> bool {
cell_eq_str(self, other)
}
}
impl PartialEq<String> for Cell {
fn eq(&self, other: &String) -> bool {
cell_eq_str(self, other)
}
}
impl PartialEq<&String> for Cell {
fn eq(&self, other: &&String) -> bool {
cell_eq_str(self, other)
}
}
impl PartialEq<Cell> for &str {
fn eq(&self, other: &Cell) -> bool {
cell_eq_str(other, self)
}
}
impl PartialEq<Cell> for String {
fn eq(&self, other: &Cell) -> bool {
cell_eq_str(other, self)
}
}
impl PartialEq<Cell> for &String {
fn eq(&self, other: &Cell) -> bool {
cell_eq_str(other, self)
}
}
impl PartialEq<f64> for Cell {
fn eq(&self, other: &f64) -> bool {
cell_eq_f64(self, *other)
}
}
impl PartialEq<f32> for Cell {
fn eq(&self, other: &f32) -> bool {
cell_eq_f64(self, f64::from(*other))
}
}
impl PartialEq<Cell> for f64 {
fn eq(&self, other: &Cell) -> bool {
cell_eq_f64(other, *self)
}
}
impl PartialEq<Cell> for f32 {
fn eq(&self, other: &Cell) -> bool {
cell_eq_f64(other, f64::from(*self))
}
}
macro_rules! impl_cell_partial_eq_signed_int {
($($ty:ty),+ $(,)?) => {
$(
impl PartialEq<$ty> for Cell {
fn eq(&self, other: &$ty) -> bool {
cell_eq_signed_int(self, *other as i128)
}
}
impl PartialEq<Cell> for $ty {
fn eq(&self, other: &Cell) -> bool {
cell_eq_signed_int(other, *self as i128)
}
}
)+
};
}
macro_rules! impl_cell_partial_eq_unsigned_int {
($($ty:ty),+ $(,)?) => {
$(
impl PartialEq<$ty> for Cell {
fn eq(&self, other: &$ty) -> bool {
cell_eq_unsigned_int(self, *other as u128)
}
}
impl PartialEq<Cell> for $ty {
fn eq(&self, other: &Cell) -> bool {
cell_eq_unsigned_int(other, *self as u128)
}
}
)+
};
}
impl_cell_partial_eq_signed_int!(i8, i16, i32, i64, i128, isize);
impl_cell_partial_eq_unsigned_int!(u8, u16, u32, u64, u128, usize);
impl PartialEq<bool> for Cell {
fn eq(&self, other: &bool) -> bool {
match self {
Cell::Bool(b) => b == other,
Cell::Formula { cached, .. } => cached.as_ref() == other,
_ => false,
}
}
}
impl PartialEq<Cell> for bool {
fn eq(&self, other: &Cell) -> bool {
match other {
Cell::Bool(b) => self == b,
Cell::Formula { cached, .. } => self == cached.as_ref(),
_ => false,
}
}
}
fn cell_eq_str(cell: &Cell, other: &str) -> bool {
match cell {
Cell::Text(s) => s == other,
Cell::Formula { cached, .. } => cell_eq_str(cached, other),
_ => false,
}
}
fn cell_eq_f64(cell: &Cell, other: f64) -> bool {
match cell {
Cell::Number(n) => *n == other,
Cell::Formula { cached, .. } => cell_eq_f64(cached, other),
_ => false,
}
}
fn cell_eq_signed_int(cell: &Cell, other: i128) -> bool {
match cell {
Cell::Number(n) => n.is_finite() && n.fract() == 0.0 && *n == other as f64,
Cell::Formula { cached, .. } => cell_eq_signed_int(cached, other),
_ => false,
}
}
fn cell_eq_unsigned_int(cell: &Cell, other: u128) -> bool {
match cell {
Cell::Number(n) => n.is_finite() && *n >= 0.0 && n.fract() == 0.0 && *n == other as f64,
Cell::Formula { cached, .. } => cell_eq_unsigned_int(cached, other),
_ => false,
}
}
impl Workbook {
pub fn new() -> Self {
Workbook::default()
}
#[cfg(feature = "xlsx")]
pub fn to_xlsx_checked(&self) -> Result<Vec<u8>, crate::WriteError> {
crate::write::validate(self)?;
Ok(self.to_xlsx())
}
pub fn add_sheet(&mut self, name: impl AsRef<str>) -> &mut Sheet {
self.sheets.push(Sheet::new(name));
self.sheets
.last_mut()
.expect("just pushed a sheet, so last_mut is Some")
}
pub fn define_name(&mut self, name: impl AsRef<str>, refers_to: impl AsRef<str>) {
self.defined_names
.push((name.as_ref().to_string(), refers_to.as_ref().to_string()));
}
pub fn set_properties(&mut self, properties: DocProperties) {
self.properties = properties;
}
pub fn set_active_sheet(&mut self, idx: usize) {
self.active_sheet = idx;
}
pub fn protect_structure(&mut self) {
self.protect_structure = true;
}
pub fn defined_names(&self) -> &[(String, String)] {
&self.defined_names
}
pub fn sheet_by_name(&self, name: &str) -> Option<&Sheet> {
self.sheets.iter().find(|s| s.name == name)
}
pub fn worksheet_range(&self, name: &str) -> Option<Range<'_>> {
self.sheet_by_name(name)
.filter(|sheet| sheet.is_worksheet)
.map(|sheet| self.apply_header_row_to_range(sheet.range()))
}
pub fn worksheet_range_ref(&self, name: &str) -> Option<Range<'_>> {
self.worksheet_range(name)
}
pub fn worksheet_range_at(&self, index: usize) -> Option<Range<'_>> {
self.sheets
.get(index)
.filter(|sheet| sheet.is_worksheet)
.map(|sheet| self.apply_header_row_to_range(sheet.range()))
}
pub fn worksheet_range_at_ref(&self, index: usize) -> Option<Range<'_>> {
self.worksheet_range_at(index)
}
pub fn worksheets(&self) -> Vec<(String, Range<'_>)> {
self.sheets
.iter()
.filter(|sheet| sheet.is_worksheet)
.map(|sheet| {
(
sheet.name.clone(),
self.apply_header_row_to_range(sheet.range()),
)
})
.collect()
}
pub fn worksheet_formula(&self, name: &str) -> Option<FormulaRange<'_>> {
self.sheet_by_name(name)
.filter(|sheet| sheet.is_worksheet)
.map(FormulaRange::from_sheet)
}
pub fn worksheet_formula_ref(&self, name: &str) -> Option<FormulaRange<'_>> {
self.worksheet_formula(name)
}
pub fn worksheet_formula_at(&self, index: usize) -> Option<FormulaRange<'_>> {
self.sheets
.get(index)
.filter(|sheet| sheet.is_worksheet)
.map(FormulaRange::from_sheet)
}
pub fn worksheet_formula_at_ref(&self, index: usize) -> Option<FormulaRange<'_>> {
self.worksheet_formula_at(index)
}
pub fn worksheet_merge_cells(&self, name: &str) -> Option<&[(u32, u16, u32, u16)]> {
self.sheet_by_name(name)
.filter(|sheet| sheet.is_worksheet)
.map(Sheet::merged_ranges)
}
pub fn worksheet_merge_cells_at(&self, index: usize) -> Option<&[(u32, u16, u32, u16)]> {
self.sheets
.get(index)
.filter(|sheet| sheet.is_worksheet)
.map(Sheet::merged_ranges)
}
pub fn merged_regions(&self) -> Vec<(&str, Dimensions)> {
self.sheets
.iter()
.filter(|sheet| sheet.is_worksheet)
.flat_map(|sheet| {
sheet
.merged_ranges()
.iter()
.map(move |&range| (sheet.name.as_str(), Dimensions::from_range_tuple(range)))
})
.collect()
}
pub fn merged_regions_by_sheet(&self, name: &str) -> Vec<Dimensions> {
self.sheet_by_name(name)
.filter(|sheet| sheet.is_worksheet)
.map(|sheet| {
sheet
.merged_ranges()
.iter()
.map(|&range| Dimensions::from_range_tuple(range))
.collect()
})
.unwrap_or_default()
}
pub fn worksheet_metadata(&self, name: &str) -> Option<WorksheetMetadata<'_>> {
self.sheet_by_name(name)
.filter(|sheet| sheet.is_worksheet)
.map(Sheet::metadata)
}
pub fn worksheet_metadata_at(&self, index: usize) -> Option<WorksheetMetadata<'_>> {
self.sheets
.get(index)
.filter(|sheet| sheet.is_worksheet)
.map(Sheet::metadata)
}
pub fn worksheets_metadata(&self) -> Vec<WorksheetMetadata<'_>> {
self.sheets
.iter()
.filter(|sheet| sheet.is_worksheet)
.map(Sheet::metadata)
.collect()
}
pub fn sheets_metadata(&self) -> Vec<SheetMetadata> {
self.sheets
.iter()
.map(|sheet| SheetMetadata {
name: sheet.name.clone(),
typ: sheet.sheet_type(),
visible: sheet.visible(),
})
.collect()
}
pub fn sheet_names(&self) -> Vec<&str> {
self.sheets.iter().map(|s| s.name.as_str()).collect()
}
}
impl Reader for Workbook {
fn sheet_names(&self) -> Vec<&str> {
Workbook::sheet_names(self)
}
fn sheets_metadata(&self) -> Vec<SheetMetadata> {
Workbook::sheets_metadata(self)
}
fn defined_names(&self) -> &[(String, String)] {
Workbook::defined_names(self)
}
fn with_header_row(&mut self, header_row: HeaderRow) -> &mut Self {
Workbook::with_header_row(self, header_row)
}
fn header_row(&self) -> HeaderRow {
Workbook::header_row(self)
}
fn worksheet_range(&self, name: &str) -> Option<Range<'_>> {
Workbook::worksheet_range(self, name)
}
fn worksheet_range_at(&self, index: usize) -> Option<Range<'_>> {
Workbook::worksheet_range_at(self, index)
}
fn worksheet_formula(&self, name: &str) -> Option<FormulaRange<'_>> {
Workbook::worksheet_formula(self, name)
}
fn worksheet_formula_at(&self, index: usize) -> Option<FormulaRange<'_>> {
Workbook::worksheet_formula_at(self, index)
}
fn worksheet_merge_cells(&self, name: &str) -> Option<&[(u32, u16, u32, u16)]> {
Workbook::worksheet_merge_cells(self, name)
}
fn worksheet_merge_cells_at(&self, index: usize) -> Option<&[(u32, u16, u32, u16)]> {
Workbook::worksheet_merge_cells_at(self, index)
}
fn merged_regions(&self) -> Vec<(&str, Dimensions)> {
Workbook::merged_regions(self)
}
fn merged_regions_by_sheet(&self, name: &str) -> Vec<Dimensions> {
Workbook::merged_regions_by_sheet(self, name)
}
fn worksheet_metadata(&self, name: &str) -> Option<WorksheetMetadata<'_>> {
Workbook::worksheet_metadata(self, name)
}
fn worksheet_metadata_at(&self, index: usize) -> Option<WorksheetMetadata<'_>> {
Workbook::worksheet_metadata_at(self, index)
}
fn worksheets_metadata(&self) -> Vec<WorksheetMetadata<'_>> {
Workbook::worksheets_metadata(self)
}
fn worksheets(&self) -> Vec<(String, Range<'_>)> {
Workbook::worksheets(self)
}
fn metadata(&self) -> WorkbookMetadata<'_> {
Workbook::metadata(self)
}
fn active_sheet_index(&self) -> Option<usize> {
Workbook::active_sheet_index(self)
}
fn active_sheet_name(&self) -> Option<&str> {
Workbook::active_sheet_name(self)
}
fn pictures(&self) -> Option<Vec<(String, Vec<u8>)>> {
Workbook::pictures(self)
}
fn pictures_with_metadata(&self) -> Vec<Picture> {
Workbook::pictures_with_metadata(self)
}
fn table_names(&self) -> Vec<&str> {
Workbook::table_names(self)
}
fn table_names_in_sheet(&self, sheet_name: &str) -> Vec<&str> {
Workbook::table_names_in_sheet(self, sheet_name)
}
fn table_by_name(&self, table_name: &str) -> Option<(&str, &Table)> {
Workbook::table_by_name(self, table_name)
}
fn table_data_by_name(&self, table_name: &str) -> Option<(&str, Range<'_>)> {
Workbook::table_data_by_name(self, table_name)
}
}
impl Sheet {
pub fn new(name: impl AsRef<str>) -> Self {
Sheet {
name: name.as_ref().to_string(),
is_worksheet: true,
sheet_type: Some(SheetType::WorkSheet),
..Default::default()
}
}
pub fn range(&self) -> Range<'_> {
Range::from_sheet(self)
}
pub fn write(&mut self, row: u32, col: u16, value: impl Into<Cell>) {
self.push_authored(row, col, value.into(), None, None);
}
pub fn write_string(&mut self, row: u32, col: u16, value: impl AsRef<str>) {
self.write(row, col, value.as_ref());
}
pub fn write_number(&mut self, row: u32, col: u16, value: impl Into<f64>) {
self.write(row, col, value.into());
}
pub fn write_boolean(&mut self, row: u32, col: u16, value: bool) {
self.write(row, col, value);
}
pub fn write_boolean_with_format(&mut self, row: u32, col: u16, value: bool, format: &Format) {
self.write_styled(row, col, value, format.as_cell_style());
}
pub fn write_error(&mut self, row: u32, col: u16, error: CellErrorType) {
self.write(row, col, error);
}
pub fn write_error_with_format(
&mut self,
row: u32,
col: u16,
error: CellErrorType,
format: &Format,
) {
self.write_styled(row, col, error, format.as_cell_style());
}
pub fn write_datetime(&mut self, row: u32, col: u16, serial: impl Into<f64>) {
self.write(row, col, Cell::Date(serial.into()));
}
pub fn write_datetime_with_format(
&mut self,
row: u32,
col: u16,
serial: impl Into<f64>,
format: &Format,
) {
self.write_styled(row, col, Cell::Date(serial.into()), format.as_cell_style());
}
pub fn write_styled(&mut self, row: u32, col: u16, value: impl Into<Cell>, style: &CellStyle) {
self.push_authored(row, col, value.into(), Some(style.clone()), None);
}
pub fn write_with_format(
&mut self,
row: u32,
col: u16,
value: impl Into<Cell>,
format: &Format,
) {
self.write_styled(row, col, value, format.as_cell_style());
}
pub fn write_string_with_format(
&mut self,
row: u32,
col: u16,
value: impl AsRef<str>,
format: &Format,
) {
self.write_styled(row, col, value.as_ref(), format.as_cell_style());
}
pub fn write_number_with_format(
&mut self,
row: u32,
col: u16,
value: impl Into<f64>,
format: &Format,
) {
self.write_styled(row, col, value.into(), format.as_cell_style());
}
pub fn write_formula(
&mut self,
row: u32,
col: u16,
formula: impl AsRef<str>,
cached: impl Into<Cell>,
) {
self.write(
row,
col,
Cell::Formula {
formula: formula.as_ref().to_string(),
cached: Box::new(cached.into()),
},
);
}
pub fn write_formula_with_format(
&mut self,
row: u32,
col: u16,
formula: impl AsRef<str>,
cached: impl Into<Cell>,
format: &Format,
) {
self.write_styled(
row,
col,
Cell::Formula {
formula: formula.as_ref().to_string(),
cached: Box::new(cached.into()),
},
format.as_cell_style(),
);
}
pub fn write_blank_styled(&mut self, row: u32, col: u16, style: &CellStyle) {
self.rich.remove(&(row, col));
self.cells
.retain(|entry| entry.row != row || entry.col != col);
self.blank_styles.insert((row, col), style.clone());
}
pub fn write_blank_with_format(&mut self, row: u32, col: u16, format: &Format) {
self.write_blank_styled(row, col, format.as_cell_style());
}
pub fn hide_gridlines(&mut self) {
self.hide_gridlines = true;
}
pub fn set_zoom(&mut self, percent: u16) {
self.zoom = Some(percent);
}
pub fn set_show_headers(&mut self, show: bool) {
self.show_headers = Some(show);
}
pub fn set_right_to_left(&mut self, rtl: bool) {
self.right_to_left = rtl;
}
pub fn set_sheet_view(&mut self, view: SheetView) {
self.freeze = view.freeze;
self.hide_gridlines = view.hide_gridlines;
self.zoom = view.zoom;
self.show_headers = view.show_headers;
self.right_to_left = view.right_to_left;
}
pub fn hide(&mut self) {
self.hidden = true;
}
pub fn hide_very(&mut self) {
self.very_hidden = true;
}
pub fn set_autofit(&mut self) {
self.autofit = true;
}
pub fn group_rows(&mut self, first: u32, last: u32, level: u8) {
let last = last.min(1_048_575);
for r in first..=last {
self.row_outline.insert(r, level);
}
}
pub fn group_cols(&mut self, first: u16, last: u16, level: u8) {
for c in first..=last {
self.col_outline.insert(c, level);
}
}
pub fn set_outline_summary(&mut self, below: bool, right: bool) {
self.outline_summary_below = below;
self.outline_summary_right = right;
}
pub fn collapse_row(&mut self, row: u32) {
self.collapsed_rows.insert(row);
}
pub fn set_print_gridlines(&mut self) {
self.print_gridlines = true;
}
pub fn set_print_headings(&mut self) {
self.print_headings = true;
}
pub fn write_rich<I>(&mut self, row: u32, col: u16, runs: I)
where
I: IntoIterator<Item = TextRun>,
{
self.push_rich(row, col, runs, None);
}
pub fn write_rich_styled<I>(&mut self, row: u32, col: u16, runs: I, style: &CellStyle)
where
I: IntoIterator<Item = TextRun>,
{
self.push_rich(row, col, runs, Some(style.clone()));
}
pub fn write_rich_with_format<I>(&mut self, row: u32, col: u16, runs: I, format: &Format)
where
I: IntoIterator<Item = TextRun>,
{
self.write_rich_styled(row, col, runs, format.as_cell_style());
}
fn push_rich<I>(&mut self, row: u32, col: u16, runs: I, style: Option<CellStyle>)
where
I: IntoIterator<Item = TextRun>,
{
let runs: Vec<TextRun> = runs.into_iter().filter(|r| !r.text.is_empty()).collect();
if runs.is_empty() {
return;
}
let joined: String = runs.iter().map(|r| r.text.as_str()).collect();
self.push_authored(row, col, Cell::Text(joined), style, None);
self.rich.insert((row, col), runs);
}
pub fn write_url(&mut self, row: u32, col: u16, url: impl AsRef<str>, text: impl AsRef<str>) {
self.push_authored(
row,
col,
Cell::Text(text.as_ref().to_string()),
None,
Some(url.as_ref().to_string()),
);
}
pub fn write_url_with_text(
&mut self,
row: u32,
col: u16,
url: impl AsRef<str>,
text: impl AsRef<str>,
) {
self.write_url(row, col, url, text);
}
pub fn write_url_with_format(
&mut self,
row: u32,
col: u16,
url: impl AsRef<str>,
format: &Format,
) {
let url = url.as_ref();
self.write_url_with_text_and_format(row, col, url, url, format);
}
pub fn write_url_with_text_and_format(
&mut self,
row: u32,
col: u16,
url: impl AsRef<str>,
text: impl AsRef<str>,
format: &Format,
) {
self.push_authored(
row,
col,
Cell::Text(text.as_ref().to_string()),
Some(format.as_cell_style().clone()),
Some(url.as_ref().to_string()),
);
}
pub fn merge(&mut self, r0: u32, c0: u16, r1: u32, c1: u16) {
self.merges.push((r0, c0, r1, c1));
}
pub fn merge_range(
&mut self,
r0: u32,
c0: u16,
r1: u32,
c1: u16,
text: impl AsRef<str>,
format: &Format,
) {
self.merge(r0, c0, r1, c1);
self.write_with_format(r0, c0, text.as_ref(), format);
}
pub fn set_col_width(&mut self, col: u16, chars: f32) {
self.col_widths.insert(col, chars);
}
pub fn set_row_height(&mut self, row: u32, points: f32) {
self.row_heights.insert(row, points);
}
pub fn set_row_format(&mut self, row: u32, format: &Format) {
self.row_formats.insert(row, format.as_cell_style().clone());
}
pub fn set_col_format(&mut self, col: u16, format: &Format) {
self.col_formats.insert(col, format.as_cell_style().clone());
}
pub fn set_default_format(&mut self, format: &Format) {
self.default_format = Some(format.as_cell_style().clone());
}
pub fn set_table_header_format(&mut self, table_name: impl AsRef<str>, format: &Format) {
self.table_header_formats.insert(
table_name.as_ref().to_string(),
format.as_cell_style().clone(),
);
}
pub fn set_default_row_height(&mut self, points: f32) {
self.default_row_height = Some(points);
}
pub fn set_default_col_width(&mut self, chars: f32) {
self.default_col_width = Some(chars);
}
pub fn freeze_panes(&mut self, row: u32, col: u16) {
self.freeze = Some((row, col));
}
pub fn autofilter(&mut self, r0: u32, c0: u16, r1: u32, c1: u16) {
self.autofilter = Some((r0, c0, r1, c1));
}
pub fn set_page_setup(&mut self, ps: PageSetup) {
self.page_setup = Some(ps);
}
pub fn set_tab_color(&mut self, color: impl Into<Color>) {
self.tab_color = Some(color.into());
}
pub fn protect(&mut self) {
self.protect = true;
}
pub fn protect_with(&mut self, opts: ProtectionOptions) {
self.protect = true;
self.protect_options = Some(opts);
}
pub fn add_data_validation(&mut self, dv: DataValidation) {
self.data_validations.push(dv);
}
pub fn add_conditional_format(&mut self, cf: CondFormat) {
self.cond_formats.push(cf);
}
pub fn add_image(&mut self, img: Image) {
self.images.push(img);
}
pub fn add_chart(&mut self, chart: Chart) {
self.charts.push(chart);
}
pub fn add_sparkline(&mut self, sparkline: Sparkline) {
self.sparklines.push(sparkline);
}
pub fn add_table(&mut self, table: Table) {
self.tables.push(table);
}
pub fn add_comment(
&mut self,
row: u32,
col: u16,
text: impl AsRef<str>,
author: impl Into<CommentAuthor>,
) {
self.comments.push(Comment {
row,
col,
text: text.as_ref().to_string(),
author: author.into().0,
});
}
fn push_authored(
&mut self,
row: u32,
col: u16,
value: Cell,
style: Option<CellStyle>,
hyperlink: Option<String>,
) {
self.rich.remove(&(row, col));
self.blank_styles.remove(&(row, col));
let text = display_text(&value);
self.cells.push(CellEntry {
row,
col,
value,
text,
style,
hyperlink,
});
}
}
impl CellStyle {
pub fn new() -> Self {
Self::default()
}
pub fn merge(&self, overlay: &CellStyle) -> Self {
let mut merged = self.clone();
merged.font = merge_font(self.font.as_ref(), overlay.font.as_ref());
if overlay.fill.is_some() {
merged.fill = overlay.fill;
}
if overlay.pattern_fill.is_some() {
merged.pattern_fill = overlay.pattern_fill;
}
merged.border = merge_border(self.border.as_ref(), overlay.border.as_ref());
if overlay.num_fmt.is_some() {
merged.num_fmt = overlay.num_fmt.clone();
}
merged.align = merge_alignment(self.align.as_ref(), overlay.align.as_ref());
merged.protection = merge_protection(self.protection.as_ref(), overlay.protection.as_ref());
merged
}
pub fn font_name(mut self, name: impl AsRef<str>) -> Self {
self.font.get_or_insert_with(Font::default).name = Some(name.as_ref().to_string());
self
}
pub fn size(mut self, points: u16) -> Self {
self.font.get_or_insert_with(Font::default).size_pt = Some(points);
self
}
pub fn color(mut self, color: impl Into<Color>) -> Self {
self.font.get_or_insert_with(Font::default).color = Some(color.into());
self
}
pub fn bold(mut self) -> Self {
self.font.get_or_insert_with(Font::default).bold = true;
self
}
pub fn italic(mut self) -> Self {
self.font.get_or_insert_with(Font::default).italic = true;
self
}
pub fn underline(mut self) -> Self {
self.font.get_or_insert_with(Font::default).underline = true;
self
}
pub fn strikethrough(mut self) -> Self {
self.font.get_or_insert_with(Font::default).strikethrough = true;
self
}
pub fn font_script(mut self, script: FormatScript) -> Self {
self.font.get_or_insert_with(Font::default).script = script;
self
}
pub fn fill(mut self, color: impl Into<Color>) -> Self {
let color = color.into();
self.fill = Some(color);
self.pattern_fill = Some(Fill::solid(color));
self
}
pub fn pattern(mut self, pattern: FormatPattern) -> Self {
self.pattern_fill.get_or_insert_with(Fill::default).pattern = pattern;
self
}
pub fn background_color(mut self, color: impl Into<Color>) -> Self {
let color = color.into();
self.fill = Some(color);
let fill = self.pattern_fill.get_or_insert_with(Fill::default);
fill.background = Some(color);
if fill.pattern == FormatPattern::None {
fill.pattern = FormatPattern::Solid;
}
self
}
pub fn foreground_color(mut self, color: impl Into<Color>) -> Self {
self.pattern_fill
.get_or_insert_with(Fill::default)
.foreground = Some(color.into());
self
}
pub fn pattern_fill(mut self, fill: Fill) -> Self {
self.fill = fill.background;
self.pattern_fill = Some(fill);
self
}
#[cfg_attr(not(feature = "xlsx"), allow(dead_code))]
pub(crate) fn effective_fill(&self) -> Option<Fill> {
self.pattern_fill
.or_else(|| self.fill.map(|c| Fill::solid(c.0)))
}
pub fn num_fmt(mut self, code: impl AsRef<str>) -> Self {
self.num_fmt = Some(code.as_ref().to_string());
self
}
pub fn wrap(mut self) -> Self {
self.align.get_or_insert_with(Alignment::default).wrap = true;
self
}
pub fn align(mut self, h: HAlign) -> Self {
self.align.get_or_insert_with(Alignment::default).horizontal = Some(h);
self
}
pub fn valign(mut self, v: VAlign) -> Self {
self.align.get_or_insert_with(Alignment::default).vertical = Some(v);
self
}
pub fn alignment(mut self, alignment: Alignment) -> Self {
self.align = Some(alignment);
self
}
pub fn indent(mut self, level: u8) -> Self {
self.align.get_or_insert_with(Alignment::default).indent = level;
self
}
pub fn shrink_to_fit(mut self) -> Self {
self.align
.get_or_insert_with(Alignment::default)
.shrink_to_fit = true;
self
}
pub fn text_rotation(mut self, degrees: i16) -> Self {
self.align.get_or_insert_with(Alignment::default).rotation = degrees.clamp(-90, 90);
self
}
pub fn locked(mut self) -> Self {
self.protection
.get_or_insert_with(CellProtection::default)
.locked = Some(true);
self
}
pub fn unlocked(mut self) -> Self {
self.protection
.get_or_insert_with(CellProtection::default)
.locked = Some(false);
self
}
pub fn hidden(mut self) -> Self {
self.protection
.get_or_insert_with(CellProtection::default)
.hidden = true;
self
}
pub fn border(mut self, b: Border) -> Self {
self.border = Some(b);
self
}
pub fn border_top(mut self, style: FormatBorder) -> Self {
self.border.get_or_insert_with(Border::default).top = style;
self
}
pub fn border_bottom(mut self, style: FormatBorder) -> Self {
self.border.get_or_insert_with(Border::default).bottom = style;
self
}
pub fn border_left(mut self, style: FormatBorder) -> Self {
self.border.get_or_insert_with(Border::default).left = style;
self
}
pub fn border_right(mut self, style: FormatBorder) -> Self {
self.border.get_or_insert_with(Border::default).right = style;
self
}
pub fn border_top_color(mut self, color: impl Into<Color>) -> Self {
self.border.get_or_insert_with(Border::default).top_color = Some(color.into());
self
}
pub fn border_bottom_color(mut self, color: impl Into<Color>) -> Self {
self.border.get_or_insert_with(Border::default).bottom_color = Some(color.into());
self
}
pub fn border_left_color(mut self, color: impl Into<Color>) -> Self {
self.border.get_or_insert_with(Border::default).left_color = Some(color.into());
self
}
pub fn border_right_color(mut self, color: impl Into<Color>) -> Self {
self.border.get_or_insert_with(Border::default).right_color = Some(color.into());
self
}
pub fn set_font_name(self, name: impl AsRef<str>) -> Self {
self.font_name(name)
}
pub fn set_font_size(self, points: u16) -> Self {
self.size(points)
}
pub fn set_font_color(self, color: impl Into<Color>) -> Self {
self.color(color)
}
pub fn set_bold(self) -> Self {
self.bold()
}
pub fn set_italic(self) -> Self {
self.italic()
}
pub fn set_underline(self) -> Self {
self.underline()
}
pub fn set_font_strikethrough(self) -> Self {
self.strikethrough()
}
pub fn set_strikethrough(self) -> Self {
self.strikethrough()
}
pub fn set_font_script(self, script: FormatScript) -> Self {
self.font_script(script)
}
pub fn set_bg_color(self, color: impl Into<Color>) -> Self {
self.fill(color)
}
pub fn set_background_color(self, color: impl Into<Color>) -> Self {
self.background_color(color)
}
pub fn set_foreground_color(self, color: impl Into<Color>) -> Self {
self.foreground_color(color)
}
pub fn set_pattern_fill(self, fill: Fill) -> Self {
self.pattern_fill(fill)
}
pub fn set_pattern(self, pattern: FormatPattern) -> Self {
self.pattern(pattern)
}
pub fn set_num_format(self, code: impl AsRef<str>) -> Self {
self.num_fmt(code)
}
pub fn set_align(self, h: FormatAlign) -> Self {
self.align(h)
}
pub fn set_valign(self, v: VAlign) -> Self {
self.valign(v)
}
pub fn set_alignment(self, alignment: Alignment) -> Self {
self.alignment(alignment)
}
pub fn set_text_wrap(self) -> Self {
self.wrap()
}
pub fn set_indent(self, level: u8) -> Self {
self.indent(level)
}
pub fn set_shrink_to_fit(self) -> Self {
self.shrink_to_fit()
}
pub fn set_text_rotation(self, degrees: i16) -> Self {
self.text_rotation(degrees)
}
pub fn set_locked(self) -> Self {
self.locked()
}
pub fn set_unlocked(self) -> Self {
self.unlocked()
}
pub fn set_hidden(self) -> Self {
self.hidden()
}
pub fn set_border(mut self, style: FormatBorder) -> Self {
let border = self.border.get_or_insert_with(Border::default);
border.left = style;
border.right = style;
border.top = style;
border.bottom = style;
self
}
pub fn set_border_top(self, style: FormatBorder) -> Self {
self.border_top(style)
}
pub fn set_border_bottom(self, style: FormatBorder) -> Self {
self.border_bottom(style)
}
pub fn set_border_left(self, style: FormatBorder) -> Self {
self.border_left(style)
}
pub fn set_border_right(self, style: FormatBorder) -> Self {
self.border_right(style)
}
pub fn set_border_top_color(self, color: impl Into<Color>) -> Self {
self.border_top_color(color)
}
pub fn set_border_bottom_color(self, color: impl Into<Color>) -> Self {
self.border_bottom_color(color)
}
pub fn set_border_left_color(self, color: impl Into<Color>) -> Self {
self.border_left_color(color)
}
pub fn set_border_right_color(self, color: impl Into<Color>) -> Self {
self.border_right_color(color)
}
pub fn set_border_color(mut self, color: impl Into<Color>) -> Self {
self.border.get_or_insert_with(Border::default).color = Some(color.into());
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn formatted_returns_display_text_while_cell_returns_typed_value() {
let mut s = Sheet::new("s");
s.write(0, 0, "공고명");
s.write(0, 1, 1000_i64);
s.write(0, 2, 0.5);
s.write(0, 3, true);
assert_eq!(s.formatted(0, 0), Some("공고명"));
assert_eq!(s.formatted(0, 1), Some("1000"));
assert_eq!(s.formatted(0, 2), Some("0.5"));
assert_eq!(s.formatted(0, 3), Some("TRUE"));
assert_eq!(s.cell(0, 0), Some(&Cell::Text("공고명".to_string())));
assert_eq!(s.cell(0, 1), Some(&Cell::Number(1000.0)));
assert_eq!(s.cell(0, 2), Some(&Cell::Number(0.5)));
assert_eq!(s.cell(0, 3), Some(&Cell::Bool(true)));
assert_eq!(s.formatted(9, 9), None);
}
#[test]
fn formatted_is_last_write_wins_like_cell() {
let mut s = Sheet::new("s");
s.write(0, 0, 1_i64);
s.write(0, 0, 2_i64);
assert_eq!(s.formatted(0, 0), Some("2"));
assert_eq!(s.cell(0, 0), Some(&Cell::Number(2.0)));
}
#[test]
fn to_html_fills_unwritten_gap_in_middle_of_row_so_columns_stay_aligned() {
let mut s = Sheet::new("s");
s.write(0, 0, "Name");
s.write(0, 1, "Age");
s.write(0, 2, "City");
s.write(1, 0, "Alice");
s.write(1, 2, "Seattle");
let html = s.to_html();
let data_row = html
.split("</tr>")
.find(|row| row.contains("Alice"))
.expect("data row present");
let tds: Vec<&str> = data_row.matches("<td").collect();
assert_eq!(
tds.len(),
3,
"expected exactly 3 <td> in the data row, got: {data_row}"
);
assert_eq!(
data_row, "<tr><td>Alice</td><td></td><td>Seattle</td>",
"Seattle must land in the 3rd <td>, not shift into the 2nd \
because the unwritten col1 was skipped entirely"
);
}
#[test]
fn to_html_merge_anchor_without_cell_entry_still_emits_td() {
let mut s = Sheet::new("s");
s.merge(0, 0, 0, 1);
s.write(0, 1, "stray");
let html = s.to_html();
assert_eq!(
html, "<table><tr><td colspan=\"2\"></td></tr></table>",
"the merge anchor must render an empty <td colspan=\"2\"> instead \
of vanishing (and the covered cell's stray text must stay \
hidden, matching real merge semantics)"
);
}
#[test]
fn sheet_to_csv_with_delimiter_normalizes_quote_delimiter_to_comma() {
let mut s = Sheet::new("s");
s.write(0, 0, "has \"quote\" inside");
s.write(0, 1, "plain");
let out = s.to_csv_with_delimiter('"');
assert_eq!(
out,
s.to_csv(),
"invalid '\"' delimiter should fall back to ','"
);
assert!(
!out.contains("\"\"\"\""),
"must not emit the ambiguous quadruple-quote output: {out}"
);
}
#[test]
fn workbook_to_csv_with_delimiter_rejects_quote_delimiter() {
let mut wb = Workbook::new();
{
let s = wb.add_sheet("CSV");
s.write(0, 0, "has \"quote\" inside");
}
assert_eq!(
wb.to_csv_with_delimiter(0, '"'),
None,
"'\"' is not a valid delimiter and should be rejected like an invalid sheet index"
);
}
}