use std::{
+ cell::RefCell,
fmt::Debug,
io::{Read, Seek},
ops::Deref,
+ rc::Rc,
str::FromStr,
sync::Arc,
};
use crate::{
data::Datum,
format::{
- CC, Decimal, Decimals, Epoch, F40, Format, NumberStyle, Settings, Type, UncheckedFormat,
- Width,
+ CC, Decimal, Decimals, Epoch, F40, F40_2, Format, NumberStyle, Settings, Type,
+ UncheckedFormat, Width,
},
output::pivot::{
self, Axis2, Axis3, FootnoteMarkerPosition, FootnoteMarkerType, Footnotes, Group,
settings::Show,
};
+/// A warning decoding a light detail member.
#[derive(Debug, Display, thiserror::Error)]
-pub enum LightError {
+pub enum LightWarning {
+ /// Unknown encoding {0:?}.
+ UnknownEncoding(String),
+
+ /// Unknown "show" value {0}.
+ UnknownShow(u8),
+
+ /// Invalid decimal point {0:?}.
+ InvalidDecimal(char),
+
+ /// Invalid custom currency definition {0:?}.
+ InvalidCustomCurrency(String),
+
+ /// Invalid format type {0}.
+ InvalidFormat(u16),
+
/// Expected {expected} dimensions along axes, found {actual} dimensions ({n_layers} layers + {n_rows} rows + {n_columns} columns).
WrongAxisCount {
+ /// Expected number of dimensions.
expected: usize,
+ /// Actual number of dimensions.
actual: usize,
+ /// Actual number of layer dimensions.
n_layers: usize,
+ /// Actual number of row dimensions.
n_rows: usize,
+ /// Actual number of column dimensions.
n_columns: usize,
},
DuplicateDimensionIndex(usize),
}
+struct Context {
+ version: Version,
+ warn: Rc<RefCell<Box<dyn FnMut(LightWarning)>>>,
+}
+
+impl Context {
+ fn new(version: Version, warn: Box<dyn FnMut(LightWarning)>) -> Self {
+ Self {
+ version: version,
+ warn: Rc::new(RefCell::new(Box::new(warn))),
+ }
+ }
+ fn warn(&self, warning: LightWarning) {
+ (self.warn.borrow_mut())(warning);
+ }
+}
+
#[binread]
-#[br(little)]
+#[br(little, import(warn: Box<dyn FnMut(LightWarning)>))]
#[derive(Debug)]
pub struct LightTable {
header: Header,
- #[br(args(header.version))]
+ #[br(temp, calc(Context::new(header.version, warn)))]
+ context: Context,
+ #[br(args(&context))]
titles: Titles,
- #[br(parse_with(parse_vec), args(header.version))]
+ #[br(parse_with(parse_vec), args(&context))]
footnotes: Vec<Footnote>,
- #[br(args(header.version))]
+ #[br(args(&context))]
areas: Areas,
#[br(parse_with(parse_counted))]
borders: Borders,
table_settings: TableSettings,
#[br(if(header.version == Version::V1), temp)]
_ts: Option<Counted<Sponge>>,
- #[br(args(header.version))]
+ #[br(args(&context))]
formats: Formats,
- #[br(parse_with(parse_vec), args(header.version))]
+ #[br(parse_with(parse_vec), args(&context))]
dimensions: Vec<Dimension>,
axes: Axes,
- #[br(parse_with(parse_vec), args(header.version))]
+ #[br(parse_with(parse_vec), args(&context))]
cells: Vec<Cell>,
}
}
}
- pub fn decode(&self) -> Result<PivotTable, LightError> {
- let encoding = self.formats.encoding();
+ pub fn decode(&self, warn: &mut dyn FnMut(LightWarning)) -> PivotTable {
+ let encoding = self.formats.encoding(warn);
let n1 = self.formats.n1();
let n2 = self.formats.n2();
}
})
.collect::<Vec<_>>();
- let pivot_table = PivotTable::new(self.axes.decode(dimensions)?)
+ let dimensions = match self.axes.decode(dimensions) {
+ Ok(dimensions) => dimensions,
+ Err((warning, dimensions)) => {
+ warn(warning);
+ dimensions
+ .into_iter()
+ .map(|dimension| (Axis3::Y, dimension))
+ .collect()
+ }
+ };
+ let pivot_table = PivotTable::new(dimensions)
.with_style(PivotTableStyle {
look: Arc::new(self.decode_look(encoding)),
rotate_inner_column_labels: self.header.rotate_inner_column_labels,
),
settings: Settings {
epoch: self.formats.y0.epoch(),
- decimal: self.formats.y0.decimal(),
+ decimal: self.formats.y0.decimal(warn),
leading_zero: y1.map_or(false, |y1| y1.include_leading_zero),
- ccs: self.formats.custom_currency.decode(encoding),
+ ccs: self.formats.custom_currency.decode(encoding, warn),
},
grouping: {
let grouping = self.formats.y0.grouping;
})
.with_footnotes(footnotes)
.with_data(cells);
- Ok(pivot_table)
+ pivot_table
}
}
}
#[binread]
-#[br(little, import(version: Version))]
+#[br(little, import(context: &Context))]
#[derive(Debug)]
struct Titles {
- #[br(args(version))]
+ #[br(args(context))]
title: Value,
#[br(temp)]
_1: Optional<One>,
- #[br(args(version))]
+ #[br(args(context))]
subtype: Value,
#[br(temp)]
_2: Optional<One>,
#[br(magic = b'1')]
- #[br(args(version))]
+ #[br(args(context))]
user_title: Value,
#[br(temp)]
_3: Optional<One>,
- #[br(parse_with(parse_explicit_optional), args(version))]
+ #[br(parse_with(parse_explicit_optional), args(context))]
corner_text: Option<Value>,
- #[br(parse_with(parse_explicit_optional), args(version))]
+ #[br(parse_with(parse_explicit_optional), args(context))]
caption: Option<Value>,
}
}
#[binrw::parser(reader, endian)]
-pub(super) fn parse_vec<T, A>(inner: A, ...) -> BinResult<Vec<T>>
+pub(super) fn parse_vec<'a, T, A>(inner: A, ...) -> BinResult<Vec<T>>
where
- for<'a> T: BinRead<Args<'a> = A>,
+ T: BinRead<Args<'a> = A>,
A: Clone,
T: 'static,
{
let count = u32::read_options(reader, endian, ())? as usize;
- <Vec<T>>::read_options(reader, endian, VecArgs { count, inner })
+ let mut vec = Vec::with_capacity(count);
+ for _ in 0..count {
+ vec.push(T::read_options(reader, endian, inner.clone())?);
+ }
+ Ok(vec)
}
#[binread]
-#[br(little, import(version: Version))]
+#[br(little, import(context: &Context))]
#[derive(Debug)]
struct Footnote {
- #[br(args(version))]
+ #[br(args(context))]
text: Value,
#[br(parse_with(parse_explicit_optional))]
- #[br(args(version))]
+ #[br(args(context))]
marker: Option<Value>,
show: i32,
}
}
#[binread]
-#[br(little, import(version: Version))]
+#[br(little, import(context: &Context))]
#[derive(Debug)]
struct Areas {
#[br(temp)]
_1: Optional<Zero>,
- #[br(args(version))]
+ #[br(args(context))]
areas: [Area; 8],
}
}
#[binread]
-#[br(little, import(version: Version))]
+#[br(little, import(context: &Context))]
#[derive(Debug)]
struct Area {
#[br(temp)]
alt_fg: Color,
#[br(parse_with(parse_color))]
alt_bg: Color,
- #[br(if(version == Version::V3))]
+ #[br(if(context.version == Version::V3))]
margins: Margins,
}
#[binread]
#[br(little)]
-#[br(import(version: Version))]
+#[br(import(context: &Context))]
#[derive(Debug)]
struct Formats {
#[br(parse_with(parse_vec))]
_x9: bool,
y0: Y0,
custom_currency: CustomCurrency,
- #[br(if(version == Version::V1))]
+ #[br(if(context.version == Version::V1))]
v1: Counted<Optional<N0>>,
- #[br(if(version == Version::V3))]
+ #[br(if(context.version == Version::V3))]
v3: Option<Counted<FormatsV3>>,
}
self.y1().map(|y1| &y1.charset)
}
- fn encoding(&self) -> &'static Encoding {
- // XXX We should probably warn for unknown encodings below
- if let Some(charset) = self.charset()
- && let Some(encoding) = Encoding::for_label(&charset.string)
- {
- encoding
- } else if let Ok(locale) = str::from_utf8(&self.locale.string)
+ fn encoding(&self, warn: &mut dyn FnMut(LightWarning)) -> &'static Encoding {
+ if let Some(charset) = self.charset() {
+ if let Some(encoding) = Encoding::for_label(&charset.string) {
+ return encoding;
+ }
+ warn(LightWarning::UnknownEncoding(
+ String::from_utf8_lossy(&charset.string).into_owned(),
+ ));
+ }
+
+ if let Ok(locale) = str::from_utf8(&self.locale.string)
&& let Some(dot) = locale.find('.')
&& let Some(encoding) = Encoding::for_label(locale[dot + 1..].as_bytes())
{
#[br(temp, parse_with(parse_bool))]
_x16: bool,
_lang: u8,
- #[br(parse_with(parse_show))]
+ #[br(parse_with(parse_show), args(|warning| eprintln!("{warning}")))]
show_variables: Option<Show>,
- #[br(parse_with(parse_show))]
+ #[br(parse_with(parse_show), args(|warning| eprintln!("{warning}")))]
show_values: Option<Show>,
#[br(temp)]
_x18: i32,
}
#[binrw::parser(reader, endian)]
-fn parse_show() -> BinResult<Option<Show>> {
+fn parse_show<F>(mut warn: F) -> BinResult<Option<Show>>
+where
+ F: FnMut(LightWarning),
+{
match <u8>::read_options(reader, endian, ())? {
0 => Ok(None),
1 => Ok(Some(Show::Value)),
2 => Ok(Some(Show::Label)),
3 => Ok(Some(Show::Both)),
- _ => {
- // XXX warn about invalid value
+ other => {
+ warn(LightWarning::UnknownShow(other));
Ok(None)
}
}
}
}
- fn decimal(&self) -> Decimal {
- // XXX warn about bad decimal point?
- Decimal::try_from(self.decimal as char).unwrap_or_default()
+ fn decimal(&self, warn: &mut dyn FnMut(LightWarning)) -> Decimal {
+ let c = self.decimal as char;
+ match Decimal::try_from(c) {
+ Ok(decimal) => decimal,
+ Err(_) => {
+ warn(LightWarning::InvalidDecimal(c));
+ Decimal::default()
+ }
+ }
}
}
}
impl CustomCurrency {
- fn decode(&self, encoding: &'static Encoding) -> EnumMap<CC, Option<Box<NumberStyle>>> {
+ fn decode(
+ &self,
+ encoding: &'static Encoding,
+ warn: &mut dyn FnMut(LightWarning),
+ ) -> EnumMap<CC, Option<Box<NumberStyle>>> {
let mut ccs = EnumMap::default();
for (cc, string) in enum_iterator::all().zip(&self.ccs) {
- if let Ok(style) = NumberStyle::from_str(&string.decode(encoding)) {
+ let string = string.decode(encoding);
+ if let Ok(style) = NumberStyle::from_str(&string) {
ccs[cc] = Some(Box::new(style));
} else {
- // XXX warning
+ warn(LightWarning::InvalidCustomCurrency(string));
}
}
ccs
}
#[binread]
-#[br(little, import(version: Version))]
+#[br(little, import(context: &Context))]
#[derive(Debug)]
struct ValueNumber {
- #[br(parse_with(parse_explicit_optional), args(version))]
+ #[br(parse_with(parse_explicit_optional), args(context))]
mods: Option<ValueMods>,
- #[br(parse_with(parse_format))]
+ #[br(parse_with(parse_format), args(context))]
format: Format,
x: f64,
}
#[binread]
-#[br(little, import(version: Version))]
+#[br(little, import(context: &Context))]
#[derive(Debug)]
struct ValueVarNumber {
- #[br(parse_with(parse_explicit_optional), args(version))]
+ #[br(parse_with(parse_explicit_optional), args(context))]
mods: Option<ValueMods>,
- #[br(parse_with(parse_format))]
+ #[br(parse_with(parse_format), args(context))]
format: Format,
x: f64,
var_name: U32String,
value_label: U32String,
- #[br(parse_with(parse_show))]
+ #[br(parse_with(parse_show), args(|warning| eprintln!("{warning}")))]
show: Option<Show>,
}
#[binread]
-#[br(little, import(version: Version))]
+#[br(little, import(context: &Context))]
#[derive(Debug)]
struct ValueText {
local: U32String,
- #[br(parse_with(parse_explicit_optional), args(version))]
+ #[br(parse_with(parse_explicit_optional), args(context))]
mods: Option<ValueMods>,
id: U32String,
c: U32String,
}
#[binread]
-#[br(little, import(version: Version))]
+#[br(little, import(context: &Context))]
#[derive(Debug)]
struct ValueString {
- #[br(parse_with(parse_explicit_optional), args(version))]
+ #[br(parse_with(parse_explicit_optional), args(context))]
mods: Option<ValueMods>,
- #[br(parse_with(parse_format))]
+ #[br(parse_with(parse_format), args(context))]
format: Format,
value_label: U32String,
var_name: U32String,
- #[br(parse_with(parse_show))]
+ #[br(parse_with(parse_show), args(|warning| eprintln!("{warning}")))]
show: Option<Show>,
s: U32String,
}
#[binread]
-#[br(little, import(version: Version))]
+#[br(little, import(context: &Context))]
#[derive(Debug)]
struct ValueVarName {
- #[br(parse_with(parse_explicit_optional), args(version))]
+ #[br(parse_with(parse_explicit_optional), args(context))]
mods: Option<ValueMods>,
var_name: U32String,
var_label: U32String,
- #[br(parse_with(parse_show))]
+ #[br(parse_with(parse_show), args(|warning| eprintln!("{warning}")))]
show: Option<Show>,
}
#[binread]
-#[br(little, import(version: Version))]
+#[br(little, import(context: &Context))]
#[derive(Debug)]
struct ValueFixedText {
local: U32String,
- #[br(parse_with(parse_explicit_optional), args(version))]
+ #[br(parse_with(parse_explicit_optional), args(context))]
mods: Option<ValueMods>,
id: U32String,
c: U32String,
}
#[binread]
-#[br(little, import(version: Version))]
+#[br(little, import(context: &Context))]
#[derive(Debug)]
struct ValueTemplate {
- #[br(parse_with(parse_explicit_optional), args(version))]
+ #[br(parse_with(parse_explicit_optional), args(context))]
mods: Option<ValueMods>,
template: U32String,
- #[br(parse_with(parse_vec), args(version))]
+ #[br(parse_with(parse_vec), args(context))]
args: Vec<Argument>,
}
}
impl BinRead for Value {
- type Args<'a> = (Version,);
+ type Args<'a> = (&'a Context,);
fn read_options<R: Read + Seek>(
reader: &mut R,
}
}
-pub(super) fn decode_format(raw: u32) -> Format {
+pub(super) fn decode_format(raw: u32, warn: &mut dyn FnMut(LightWarning)) -> Format {
if raw == 0 || raw == 0x10000 || raw == 1 {
- return Format::new(Type::F, 40, 2).unwrap();
+ return F40_2;
}
let raw_type = (raw >> 16) as u16;
} else if let Ok(type_) = Type::try_from(raw_type) {
type_
} else {
- // XXX warn
+ warn(LightWarning::InvalidFormat(raw_type));
Type::F
};
let w = ((raw >> 8) & 0xff) as Width;
}
#[binrw::parser(reader, endian)]
-fn parse_format() -> BinResult<Format> {
- Ok(decode_format(u32::read_options(reader, endian, ())?))
+fn parse_format(context: &Context) -> BinResult<Format> {
+ Ok(decode_format(
+ u32::read_options(reader, endian, ())?,
+ &mut *context.warn.borrow_mut(),
+ ))
}
impl ValueNumber {
struct Argument(Vec<Value>);
impl BinRead for Argument {
- type Args<'a> = (Version,);
+ type Args<'a> = (&'a Context,);
fn read_options<R: Read + Seek>(
reader: &mut R,
endian: Endian,
- (version,): (Version,),
+ context: Self::Args<'_>,
) -> BinResult<Self> {
let count = u32::read_options(reader, endian, ())? as usize;
if count == 0 {
- Ok(Self(vec![Value::read_options(reader, endian, (version,))?]))
+ Ok(Self(vec![Value::read_options(reader, endian, context)?]))
} else {
let zero = u32::read_options(reader, endian, ())?;
assert_eq!(zero, 0);
endian,
VecArgs {
count,
- inner: (version,),
+ inner: context,
},
)?;
Ok(Self(values))
}
#[binread]
-#[br(little, import(version: Version))]
+#[br(little, import(context: &Context))]
#[derive(Debug)]
struct ValueMods {
#[br(parse_with(parse_vec))]
refs: Vec<i16>,
#[br(parse_with(parse_vec))]
subscripts: Vec<U32String>,
- #[br(if(version == Version::V1))]
+ #[br(if(context.version == Version::V1))]
v1: Option<ValueModsV1>,
- #[br(if(version == Version::V3), parse_with(parse_counted))]
+ #[br(if(context.version == Version::V3), parse_with(parse_counted))]
v3: ValueModsV3,
}
bg: font_style.bg,
size: (font_style.size as i32) * 4 / 3,
});
- let cell_style = self.v3.style_pair.cell_style.as_ref().map(|cell_style| {
- look::CellStyle {
+ let cell_style = self
+ .v3
+ .style_pair
+ .cell_style
+ .as_ref()
+ .map(|cell_style| look::CellStyle {
horz_align: match cell_style.halign {
0 => Some(HorzAlign::Center),
2 => Some(HorzAlign::Left),
4 => Some(HorzAlign::Right),
6 => Some(HorzAlign::Decimal {
offset: cell_style.decimal_offset,
- decimal: Decimal::Dot, /*XXX*/
}),
_ => None,
},
Axis2::X => [cell_style.left_margin as i32, cell_style.right_margin as i32],
Axis2::Y => [cell_style.top_margin as i32, cell_style.bottom_margin as i32],
},
- }
- });
+ });
ValueStyle {
cell_style,
font_style,
#[binread]
#[br(little)]
-#[br(import(version: Version))]
+#[br(import(context: &Context))]
#[derive(Debug)]
struct Dimension {
- #[br(args(version))]
+ #[br(args(context))]
name: Value,
#[br(temp)]
_x1: u8,
hide_all_labels: bool,
#[br(magic(1u8), temp)]
_dim_index: i32,
- #[br(parse_with(parse_vec), args(version))]
+ #[br(parse_with(parse_vec), args(context))]
categories: Vec<Category>,
}
#[binread]
-#[br(little, import(version: Version))]
+#[br(little, import(context: &Context))]
#[derive(Debug)]
struct Category {
- #[br(args(version))]
+ #[br(args(context))]
name: Value,
- #[br(args(version))]
+ #[br(args(context))]
child: Child,
}
}
#[binread]
-#[br(little, import(version: Version))]
+#[br(little, import(context: &Context))]
#[derive(Debug)]
enum Child {
Leaf {
merge: bool,
#[br(temp, magic(b"\0\x01"))]
_x23: i32,
- #[br(magic(-1i32), parse_with(parse_vec), args(version))]
+ #[br(magic(-1i32), parse_with(parse_vec), args(context))]
subcategories: Vec<Box<Category>>,
},
}
fn decode(
&self,
dimensions: Vec<pivot::Dimension>,
- ) -> Result<Vec<(Axis3, pivot::Dimension)>, LightError> {
+ ) -> Result<Vec<(Axis3, pivot::Dimension)>, (LightWarning, Vec<pivot::Dimension>)> {
let n = self.layers.len() + self.rows.len() + self.columns.len();
if n != dimensions.len() {
- /* XXX warn
- return Err(LightError::WrongAxisCount {
+ // Warn, then treat all of the dimensions as rows.
+ return Err((
+ LightWarning::WrongAxisCount {
expected: dimensions.len(),
actual: n,
n_layers: self.layers.len(),
n_rows: self.rows.len(),
n_columns: self.columns.len(),
- });*/
- return Ok(dimensions
- .into_iter()
- .map(|dimension| (Axis3::Y, dimension))
- .collect());
+ },
+ dimensions,
+ ));
}
fn axis_dims(axis: Axis3, dimensions: &[u32]) -> impl Iterator<Item = (Axis3, usize)> {
.chain(axis_dims(Axis3::X, &self.columns))
{
if index >= n {
- return Err(LightError::InvalidDimensionIndex { index, n });
+ return Err((LightWarning::InvalidDimensionIndex { index, n }, dimensions));
} else if axes[index].is_some() {
- return Err(LightError::DuplicateDimensionIndex(index));
+ return Err((LightWarning::DuplicateDimensionIndex(index), dimensions));
}
axes[index] = Some(axis);
}
}
#[binread]
-#[br(little, import(version: Version))]
+#[br(little, import(context: &Context))]
#[derive(Debug)]
struct Cell {
index: u64,
- #[br(if(version == Version::V1), temp)]
+ #[br(if(context.version == Version::V1), temp)]
_zero: Optional<Zero>,
- #[br(args(version))]
+ #[br(args(context))]
value: Value,
}