leaf: &'a Leaf,
}
+pub type IndexVec = SmallVec<[usize; 4]>;
+
impl Dimension {
pub fn new(root: Group) -> Self {
Dimension {
self.root.leaf_path(index, SmallVec::new())
}
+ pub fn index_path(&self, index: usize) -> Option<IndexVec> {
+ self.root.index_path(index, SmallVec::new())
+ }
+
pub fn with_all_labels_hidden(self) -> Self {
Self {
hide_all_labels: true,
}
}
+/// Specifies a [Category] within a [Group].
+#[derive(Copy, Clone, Debug)]
+pub struct CategoryLocator {
+ /// The index of the leaf to start from.
+ pub leaf_index: usize,
+
+ /// The number of times to go up a level from the leaf. If this category is
+ /// a leaf, this is 0, otherwise it is positive.
+ pub level: usize,
+}
+
+impl CategoryLocator {
+ pub fn new_leaf(leaf_index: usize) -> Self {
+ Self {
+ leaf_index,
+ level: 0,
+ }
+ }
+
+ pub fn parent(&self) -> Self {
+ Self {
+ leaf_index: self.leaf_index,
+ level: self.level + 1,
+ }
+ }
+
+ pub fn as_leaf(&self) -> Option<usize> {
+ (self.level == 0).then_some(self.leaf_index)
+ }
+}
+
#[derive(Clone, Debug, Serialize)]
pub struct Group {
#[serde(skip)]
pub fn push(&mut self, child: impl Into<Category>) {
let mut child = child.into();
- if let Category::Group(group) = &mut child {
+ if let Some(group) = child.as_group_mut() {
group.show_label = true;
}
self.len += child.len();
None
}
+ fn index_path(&self, mut index: usize, mut path: IndexVec) -> Option<IndexVec> {
+ for (i, child) in self.children.iter().enumerate() {
+ let len = child.len();
+ if index < len {
+ path.push(i);
+ return child.index_path(index, path);
+ }
+ index -= len;
+ }
+ None
+ }
+
+ fn locator_path(&self, locator: CategoryLocator) -> Option<IndexVec> {
+ let mut path = self.index_path(locator.leaf_index, IndexVec::new())?;
+ path.truncate(path.len().checked_sub(locator.level)?);
+ Some(path)
+ }
+
+ /// Returns `None` if `locator` is invalid (that is, if `locator.leaf_idx >=
+ /// self.len` or `locator.level` is greater than the depth of the leaf) or
+ /// if `locator` designates `self`.
+ pub fn category(&self, locator: CategoryLocator) -> Option<&Category> {
+ let path = self.locator_path(locator)?;
+ let mut this = &self.children[*path.get(0)?];
+ for index in path[1..].iter().copied() {
+ this = &this.as_group().unwrap().children[index];
+ }
+ Some(this)
+ }
+
+ pub fn category_mut(&mut self, locator: CategoryLocator) -> Option<&mut Category> {
+ let path = self.locator_path(locator)?;
+ let mut this = &mut self.children[*path.get(0)?];
+ for index in path[1..].iter().copied() {
+ this = &mut this.as_group_mut().unwrap().children[index];
+ }
+ Some(this)
+ }
+
pub fn len(&self) -> usize {
self.len
}
Count,
}
-/// A pivot_category is a leaf (a category) or a group.
+/// A leaf category or a group of them.
#[derive(Clone, Debug, Serialize)]
pub enum Category {
Group(Group),
}
impl Category {
+ pub fn as_group(&self) -> Option<&Group> {
+ match self {
+ Category::Group(group) => Some(group),
+ Category::Leaf(leaf) => None,
+ }
+ }
+
+ pub fn as_group_mut(&mut self) -> Option<&mut Group> {
+ match self {
+ Category::Group(group) => Some(group),
+ Category::Leaf(leaf) => None,
+ }
+ }
+
+ pub fn as_leaf(&self) -> Option<&Leaf> {
+ match self {
+ Category::Group(group) => None,
+ Category::Leaf(leaf) => Some(leaf),
+ }
+ }
+
+ pub fn as_leaf_mut(&mut self) -> Option<&mut Leaf> {
+ match self {
+ Category::Group(group) => None,
+ Category::Leaf(leaf) => Some(leaf),
+ }
+ }
+
pub fn name(&self) -> &Value {
match self {
Category::Group(group) => &group.name,
}
}
+ pub fn name_mut(&mut self) -> &mut Value {
+ match self {
+ Category::Group(group) => &mut group.name,
+ Category::Leaf(leaf) => &mut leaf.0,
+ }
+ }
+
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn nth_leaf(&self, index: usize) -> Option<&Leaf> {
match self {
Category::Group(group) => group.nth_leaf(index),
- Category::Leaf(leaf) => {
- if index == 0 {
- Some(leaf)
- } else {
- None
- }
- }
+ Category::Leaf(leaf) if index == 0 => Some(leaf),
+ _ => None,
}
}
pub fn leaf_path<'a>(&'a self, index: usize, groups: GroupVec<'a>) -> Option<Path<'a>> {
match self {
Category::Group(group) => group.leaf_path(index, groups),
- Category::Leaf(leaf) => {
- if index == 0 {
- Some(Path { groups, leaf })
- } else {
- None
- }
- }
+ Category::Leaf(leaf) if index == 0 => Some(Path { groups, leaf }),
+ _ => None,
+ }
+ }
+
+ fn index_path(&self, index: usize, mut path: IndexVec) -> Option<IndexVec> {
+ match self {
+ Category::Group(group) => group.index_path(index, path),
+ Category::Leaf(leaf) if index == 0 => Some(path),
+ _ => None,
+ }
+ }
+
+ fn locator_path(&self, locator: CategoryLocator) -> Option<IndexVec> {
+ let mut path = self.index_path(locator.leaf_index, IndexVec::new())?;
+ path.truncate(path.len().checked_sub(locator.level)?);
+ Some(path)
+ }
+
+ /// Returns `None` if `locator` is invalid (that is, if `locator.leaf_idx >=
+ /// self.len` or `locator.level` is greater than the depth of the leaf).
+ pub fn category(&self, locator: CategoryLocator) -> Option<&Category> {
+ let mut this = self;
+ for index in this.locator_path(locator)? {
+ this = &this.as_group().unwrap().children[index];
+ }
+ Some(this)
+ }
+
+ pub fn category_mut(&mut self, locator: CategoryLocator) -> Option<&mut Category> {
+ let mut this = self;
+ for index in this.locator_path(locator)? {
+ this = &mut this.as_group_mut().unwrap().children[index];
}
+ Some(this)
}
pub fn show_label(&self) -> bool {
I: ExactSizeIterator<Item = usize>;
}
-impl<const N: usize> CellIndex for &[usize; N] {
- fn cell_index<I>(self, dimensions: I) -> usize
- where
- I: ExactSizeIterator<Item = usize>,
- {
- self.as_slice().cell_index(dimensions)
- }
-}
-
-impl<const N: usize> CellIndex for [usize; N] {
- fn cell_index<I>(self, dimensions: I) -> usize
- where
- I: ExactSizeIterator<Item = usize>,
- {
- self.as_slice().cell_index(dimensions)
- }
-}
-
-impl CellIndex for &[usize] {
+impl<T> CellIndex for T
+where
+ T: AsRef<[usize]>,
+{
fn cell_index<I>(self, dimensions: I) -> usize
where
I: ExactSizeIterator<Item = usize>,
{
- let data_indexes = self;
+ let data_indexes = self.as_ref();
let mut index = 0;
for (dimension, data_index) in dimensions.zip_eq(data_indexes.iter()) {
debug_assert!(*data_index < dimension);
ValueInner::Text(TextValue {
localized: local, ..
}) => {
- /*
- if self
- .inner
- .styling
- .as_ref()
- .is_some_and(|styling| styling.style.font_style.markup)
- {
- todo!();
- }*/
+ if self.markup {
+ dbg!(local);
+ }
f.write_str(local)
}
// this program. If not, see <http://www.gnu.org/licenses/>.
use std::{
+ cell::{Cell, RefCell},
collections::{BTreeMap, HashMap},
marker::PhantomData,
mem::take,
num::NonZeroUsize,
+ ops::Range,
+ sync::Arc,
};
+use chrono::{NaiveDateTime, NaiveTime};
use enum_map::{Enum, EnumMap};
+use hashbrown::HashSet;
+use itertools::Itertools;
use ordered_float::OrderedFloat;
use serde::Deserialize;
use crate::{
+ calendar::{date_time_to_pspp, time_to_pspp},
data::Datum,
- format::{Decimal::Dot, F8_0, F40_2, Type, UncheckedFormat},
+ format::{self, Decimal::Dot, F8_0, F40_2, Type, UncheckedFormat},
output::{
pivot::{
- self, Area, AreaStyle, Axis2, Axis3, Category, Color, Dimension, Group, HeadingRegion,
- HorzAlign, Leaf, Length, Look, NumberValue, PivotTable, RowParity, Value, ValueInner,
- VertAlign,
+ self, Area, AreaStyle, Axis2, Axis3, Category, CategoryLocator, CellStyle, Color,
+ Dimension, Group, HeadingRegion, HorzAlign, Leaf, Length, Look, NumberValue,
+ PivotTable, RowParity, Value, ValueInner, VertAlign,
},
spv::legacy_bin::DataValue,
+ table,
},
};
};
let mut axes = HashMap::new();
+ let mut major_ticks = HashMap::new();
for child in &graph.facet_layout.children {
if let FacetLayoutChild::FacetLevel(facet_level) = child {
axes.insert(facet_level.level, &facet_level.axis);
+ major_ticks.insert(
+ facet_level.axis.major_ticks.id.as_str(),
+ &facet_level.axis.major_ticks,
+ );
}
}
label.decode_style(&mut look.areas[area], &styles);
}
}
+ let title = LabelFrame::decode_label(&labels[Purpose::Title]);
+ let caption = LabelFrame::decode_label(&labels[Purpose::SubTitle]);
if let Some(style) = &graph.interval.labeling.style
&& let Some(style) = styles.get(style.references.as_str())
{
- Style::decode(
+ Style::decode_area(
Some(*style),
graph.cell_style.get(&styles),
&mut look.areas[Area::Data(RowParity::Even)],
look.areas[Area::Data(RowParity::Even)].clone();
}
- let mut _title = Value::empty();
- let mut _caption = Value::empty();
- //Label::decode_
-
let _show_grid_lines = extension
.as_ref()
.and_then(|extension| extension.show_gridline);
let out = &mut look.areas[Area::Labels(a)];
*out = Area::Labels(a).default_area_style();
let style = label.style.get(&styles);
- Style::decode(
+ Style::decode_area(
style,
label.text_frame_style.as_ref().and_then(|r| r.get(styles)),
out,
if a == Axis3::Y
&& let Some(axis) = axes.get(&(base_level + variables.len() - 1))
{
- Style::decode(
+ Style::decode_area(
axis.major_ticks.style.get(&styles),
axis.major_ticks.tick_frame_style.get(&styles),
&mut look.areas[Area::Labels(Axis2::Y)],
.map(|(series, _level)| *series)
.collect::<Vec<_>>();
+ #[derive(Clone)]
+ struct CatBuilder {
+ /// The category we've built so far.
+ category: Category,
+
+ /// The range of leaf indexes covered by `category`.
+ ///
+ /// If `category` is a leaf, the range has a length of 1.
+ /// If `category` is a group, the length is at least 1.
+ leaves: Range<usize>,
+
+ /// How to find this category in its dimension.
+ location: CategoryLocator,
+ }
+
// Make leaf categories.
let mut coordinate_to_index = HashMap::new();
let mut cats = Vec::new();
let Some(row) = value.category() else {
continue;
};
- coordinate_to_index.insert(row, index);
+ coordinate_to_index.insert(row, CategoryLocator::new_leaf(index));
let name = variables[0].new_name(value, footnotes);
- cats.push((Category::from(Leaf::new(name)), cats.len()..cats.len() + 1));
+ cats.push(CatBuilder {
+ category: Category::from(Leaf::new(name)),
+ leaves: cats.len()..cats.len() + 1,
+ location: CategoryLocator::new_leaf(cats.len()),
+ });
}
+ *variables[0].coordinate_to_index.borrow_mut() = coordinate_to_index;
// Now group them, in one pass per grouping variable, innermost first.
for j in 1..variables.len() {
+ let mut coordinate_to_index = HashMap::new();
let mut next_cats = Vec::with_capacity(cats.len());
let mut start = 0;
for end in 1..=cats.len() {
- let dv1 = &variables[j].values[cats[start].1.start];
+ let dv1 = &variables[j].values[cats[start].leaves.start];
if end < cats.len()
- && variables[j].values[cats[end].1.clone()]
+ && variables[j].values[cats[end].leaves.clone()]
.iter()
.all(|dv| &dv.value == &dv1.value)
{
} else {
let name = variables[j].map.lookup(dv1);
- if end - start > 1 || name.is_number_or(|s| s.is_empty()) {
+ let next_cat = if end - start > 1 || name.is_number_or(|s| s.is_empty()) {
let name = variables[j].new_name(dv1, footnotes);
let mut group = Group::new(name);
for i in start..end {
- group.push(cats[i].0.clone());
+ group.push(cats[i].category.clone());
+ }
+ CatBuilder {
+ category: Category::from(group),
+ leaves: cats[start].leaves.start..cats[end - 1].leaves.end,
+ location: cats[start].location.parent(),
}
- next_cats.push((
- Category::from(group),
- cats[start].1.start..cats[end - 1].1.end,
- ));
} else {
- next_cats.push(cats[start].clone());
- }
+ cats[start].clone()
+ };
+ coordinate_to_index
+ .insert(dv1.category().unwrap() /*XXX?*/, next_cat.location);
+ next_cats.push(next_cat);
start = end;
}
}
+ *variables[j].coordinate_to_index.borrow_mut() = coordinate_to_index;
cats = next_cats;
}
.as_ref()
.map_or_else(|| Value::empty(), |label| Value::new_user_text(label)),
)
- .with_multiple(cats.into_iter().map(|(category, _range)| category))
+ .with_multiple(cats.into_iter().map(|cb| cb.category))
.with_show_label(show_label),
);
+
+ for variable in &variables {
+ variable.dimension_index.set(Some(dims.len()));
+ }
dims.push(Dim {
axis: a,
- dimension: Some(dimension),
+ dimension,
coordinate: variables[0],
- coordinate_to_index,
});
}
struct Dim<'a> {
axis: Axis3,
- dimension: Option<pivot::Dimension>,
+ dimension: pivot::Dimension,
coordinate: &'a Series,
- coordinate_to_index: HashMap<usize, usize>,
}
let mut rotate_inner_column_labels = false;
level_ofs += layers.len();
}
- let dimensions = dims
- .iter_mut()
- .map(|dim| (dim.axis, dim.dimension.take().unwrap()))
- .collect::<Vec<_>>();
-
- let mut pivot_table = PivotTable::new(dimensions);
-
let cell = series.get("cell").unwrap()/*XXX*/;
let mut coords = Vec::with_capacity(dims.len());
let (cell_formats, format_map) = graph.interval.labeling.decode_format_map(&series);
LabelingChild::Footnotes(footnotes) => series.get(footnotes.variable.as_str()),
_ => None,
});
+ let mut data = HashMap::new();
for (i, cell) in cell.values.iter().enumerate() {
coords.clear();
for dim in &dims {
// XXX indexing of values, and unwrap
let coordinate = dim.coordinate.values[i].category().unwrap();
- let index = match dim.coordinate_to_index.get(&coordinate) {
- Some(index) => *index,
- None => panic!("can't find {coordinate}"),
+ let Some(index) = dim
+ .coordinate
+ .coordinate_to_index
+ .borrow()
+ .get(&coordinate)
+ .and_then(CategoryLocator::as_leaf)
+ else {
+ panic!("can't find {coordinate}") // XXX
};
coords.push(index);
}
// A system-missing value without a footnote represents an empty cell.
} else {
// XXX cell_index might be invalid?
- pivot_table.insert(coords.as_slice(), value);
+ data.insert(coords.clone(), value);
}
}
- // XXX decode_set_cell_properties
+ for child in &graph.facet_layout.children {
+ let FacetLayoutChild::SetCellProperties(scp) = child else {
+ continue;
+ };
+
+ #[derive(Copy, Clone, Debug, PartialEq)]
+ enum TargetType {
+ Graph,
+ Labeling,
+ Interval,
+ MajorTicks,
+ }
+
+ impl TargetType {
+ fn from_id(
+ target: &str,
+ graph: &Graph,
+ major_ticks: &HashMap<&str, &MajorTicks>,
+ ) -> Option<Self> {
+ if let Some(id) = &graph.id
+ && id == target
+ {
+ Some(Self::Graph)
+ } else if let Some(id) = &graph.interval.labeling.id
+ && id == target
+ {
+ Some(Self::Labeling)
+ } else if let Some(id) = &graph.interval.id
+ && id == target
+ {
+ Some(Self::Interval)
+ } else if major_ticks.contains_key(target) {
+ Some(Self::MajorTicks)
+ } else {
+ None
+ }
+ }
+ }
+
+ #[derive(Default)]
+ struct Target<'a> {
+ graph: Option<&'a Style>,
+ labeling: Option<&'a Style>,
+ interval: Option<&'a Style>,
+ major_ticks: Option<&'a Style>,
+ frame: Option<&'a Style>,
+ format: Option<(&'a SetFormat, Option<TargetType>)>,
+ }
+ impl<'a> Target<'a> {
+ fn decode(
+ &self,
+ intersect: &Intersect,
+ look: &mut Look,
+ series: &HashMap<&str, Series>,
+ dims: &mut [Dim],
+ data: &mut HashMap<Vec<usize>, Value>,
+ ) {
+ let mut wheres = Vec::new();
+ let mut alternating = false;
+ for child in &intersect.children {
+ match child {
+ IntersectChild::Where(w) => wheres.push(w),
+ IntersectChild::IntersectWhere(_) => {
+ // Presumably we should do something (but we don't).
+ }
+ IntersectChild::Alternating => alternating = true,
+ IntersectChild::Empty => (),
+ }
+ }
+
+ match self {
+ Self {
+ graph: Some(_),
+ labeling: Some(_),
+ interval: None,
+ major_ticks: None,
+ frame: None,
+ format: None,
+ } if alternating => {
+ let mut style = Area::Data(RowParity::Odd).default_area_style();
+ Style::decode_area(self.labeling, self.graph, &mut style);
+ let font_style = &mut look.areas[Area::Data(RowParity::Odd)].font_style;
+ font_style.fg = style.font_style.fg;
+ font_style.bg = style.font_style.bg;
+ }
+ Self {
+ graph: Some(_),
+ labeling: None,
+ interval: None,
+ major_ticks: None,
+ frame: None,
+ format: None,
+ } => {
+ // `graph.width` likely just sets the width of the table as a whole.
+ }
+ Self {
+ graph: None,
+ labeling: None,
+ interval: None,
+ major_ticks: None,
+ frame: None,
+ format: None,
+ } => {
+ // No-op. (Presumably there's a setMetaData we don't care about.)
+ }
+ Self {
+ format: Some((_, Some(TargetType::MajorTicks))),
+ ..
+ }
+ | Self {
+ major_ticks: Some(_),
+ ..
+ }
+ | Self { frame: Some(_), .. }
+ if !wheres.is_empty() =>
+ {
+ // Formatting for individual row or column labels.
+ for w in &wheres {
+ let Some(s) = series.get(w.variable.as_str()) else {
+ continue;
+ };
+ let Some(dim_index) = s.dimension_index.get() else {
+ continue;
+ };
+ let dimension = &mut dims[dim_index].dimension;
+ let Ok(axis) = Axis2::try_from(dims[dim_index].axis) else {
+ continue;
+ };
+ for index in
+ w.include.split(';').filter_map(|s| s.parse::<usize>().ok())
+ {
+ if let Some(locator) =
+ s.coordinate_to_index.borrow().get(&index).copied()
+ && let Some(category) = dimension.root.category_mut(locator)
+ {
+ Style::apply_to_value(
+ category.name_mut(),
+ self.format.map(|(sf, _)| sf),
+ self.major_ticks,
+ self.frame,
+ &look.areas[Area::Labels(axis)],
+ );
+ }
+ }
+ }
+ }
+ Self {
+ format: Some((_, Some(TargetType::Labeling))),
+ ..
+ }
+ | Self {
+ labeling: Some(_), ..
+ }
+ | Self {
+ interval: Some(_), ..
+ } => {
+ // Formatting for individual cells or groups of them
+ // with some dimensions in common.
+ let mut include = vec![HashSet::new(); dims.len()];
+ for w in &wheres {
+ let Some(s) = series.get(w.variable.as_str()) else {
+ continue;
+ };
+ let Some(dim_index) = s.dimension_index.get() else {
+ // Group indexes may be included even though
+ // they are redundant. Ignore them.
+ continue;
+ };
+ let dimension = &mut dims[dim_index].dimension;
+ for index in
+ w.include.split(';').filter_map(|s| s.parse::<usize>().ok())
+ {
+ if let Some(locator) =
+ s.coordinate_to_index.borrow().get(&index).copied()
+ && let Some(leaf_index) = locator.as_leaf()
+ {
+ include[dim_index].insert(leaf_index);
+ }
+ }
+ }
+
+ // XXX This is inefficient in the common case where
+ // all of the dimensions are matched. We should use
+ // a heuristic where if all of the dimensions are
+ // matched and the product of n[*] is less than the
+ // number of cells then iterate through all the
+ // possibilities rather than all the cells. Or even
+ // only do it if there is just one possibility.
+ for (indexes, value) in data {
+ let mut skip = false;
+ for (dimension, index) in indexes.iter().enumerate() {
+ if !include[dimension].is_empty()
+ && !include[dimension].contains(index)
+ {
+ skip = true;
+ break;
+ }
+ }
+ if !skip {
+ Style::apply_to_value(
+ value,
+ self.format.map(|(sf, _)| sf),
+ self.major_ticks,
+ self.frame,
+ &look.areas[Area::Data(RowParity::Even)],
+ );
+ }
+ }
+ }
+ _ => (),
+ }
+ }
+ }
+ let mut target = Target::default();
+ for set in &scp.sets {
+ match set {
+ Set::SetStyle(set_style) => {
+ if let Some(style) = set_style.style.get(&styles) {
+ match TargetType::from_id(&set_style.target, graph, &major_ticks) {
+ Some(TargetType::Graph) => target.graph = Some(style),
+ Some(TargetType::Interval) => target.interval = Some(style),
+ Some(TargetType::Labeling) => target.labeling = Some(style),
+ Some(TargetType::MajorTicks) => target.major_ticks = Some(style),
+ None => (),
+ }
+ }
+ }
+ Set::SetFrameStyle(set_frame_style) => {
+ target.frame = set_frame_style.style.get(&styles)
+ }
+ Set::SetFormat(sf) => {
+ let target_type = TargetType::from_id(&sf.target, graph, &major_ticks);
+ target.format = Some((sf, target_type))
+ }
+ Set::SetMetaData(_) => (),
+ }
+ }
+
+ match (
+ scp.union_.as_ref(),
+ scp.apply_to_converse.unwrap_or_default(),
+ ) {
+ (Some(union_), false) => {
+ for intersect in &union_.intersects {
+ target.decode(
+ intersect,
+ &mut look,
+ &series,
+ dims.as_mut_slice(),
+ &mut data,
+ );
+ }
+ }
+ (Some(_), true) => {
+ // Not implemented, not seen in the corpus.
+ }
+ (None, true) => {
+ if target
+ .format
+ .is_some_and(|(_sf, target_type)| target_type == Some(TargetType::Labeling))
+ || target.labeling.is_some()
+ || target.interval.is_some()
+ {
+ for value in data.values_mut() {
+ Style::apply_to_value(
+ value,
+ target.format.map(|(sf, _target_type)| sf),
+ None,
+ None,
+ &look.areas[Area::Data(RowParity::Even)],
+ );
+ }
+ }
+ }
+ (None, false) => {
+ // Appears to be used to set the font for something—but what?
+ }
+ }
+ }
+
+ let dimensions = dims
+ .into_iter()
+ .map(|dim| (dim.axis, dim.dimension))
+ .collect::<Vec<_>>();
+ let mut pivot_table = PivotTable::new(dimensions)
+ .with_look(Arc::new(look))
+ .with_data(data);
+ if let Some(title) = title {
+ pivot_table = pivot_table.with_title(title);
+ }
+ if let Some(caption) = caption {
+ pivot_table = pivot_table.with_caption(caption);
+ }
Ok(pivot_table)
}
}
struct Series {
+ name: String,
label: Option<String>,
format: crate::format::Format,
remapped: bool,
values: Vec<DataValue>,
map: Map,
affixes: Vec<Affix>,
+ coordinate_to_index: RefCell<HashMap<usize, CategoryLocator>>,
+ dimension_index: Cell<Option<usize>>,
}
impl Series {
+ fn new(name: String, values: Vec<DataValue>, map: Map) -> Self {
+ Self {
+ name,
+ label: None,
+ format: F8_0,
+ remapped: false,
+ values,
+ map,
+ affixes: Vec::new(),
+ coordinate_to_index: Default::default(),
+ dimension_index: Default::default(),
+ }
+ }
+ fn with_format(self, format: crate::format::Format) -> Self {
+ Self { format, ..self }
+ }
+ fn with_label(self, label: Option<String>) -> Self {
+ Self { label, ..self }
+ }
+ fn with_affixes(self, affixes: Vec<Affix>) -> Self {
+ Self { affixes, ..self }
+ }
fn add_affixes(&self, mut value: Value, footnotes: &pivot::Footnotes) -> Value {
for affix in &self.affixes {
if let Some(index) = affix.defines_reference.checked_sub(1)
} else if let Some(label_series) = label_series {
map.insert_labels(&data, label_series, format);
}
- Ok(Series {
- label: self.label.clone(),
- format,
- remapped: false,
- values: data,
- map,
- affixes,
- })
+ Ok(Series::new(self.id.clone(), data, map)
+ .with_format(format)
+ .with_affixes(affixes)
+ .with_label(self.label.clone()))
}
}
{
values.clear();
}
- Ok(Series {
- label: None,
- format: F8_0,
- remapped: false,
- values,
- map,
- affixes: Vec::new(),
- })
+ Ok(Series::new(self.id.clone(), values, map))
}
}
}
impl Style {
- fn decode(fg: Option<&Style>, bg: Option<&Style>, out: &mut AreaStyle) {
+ fn apply_to_value(
+ value: &mut Value,
+ sf: Option<&SetFormat>,
+ fg: Option<&Style>,
+ bg: Option<&Style>,
+ base_style: &AreaStyle,
+ ) {
+ if let Some(sf) = sf {
+ if sf.reset == Some(true) {
+ value.styling_mut().footnotes.clear();
+ }
+
+ let format = match &sf.child {
+ Some(SetFormatChild::Format(format)) => Some(format.decode()),
+ Some(SetFormatChild::NumberFormat(format)) => {
+ Some(SignificantNumberFormat::from(format).decode())
+ }
+ Some(SetFormatChild::StringFormat(format)) => None,
+ Some(SetFormatChild::DateTimeFormat(format)) => Some(format.decode()),
+ Some(SetFormatChild::ElapsedTimeFormat(format)) => Some(format.decode()),
+ None => None,
+ };
+ if let Some(format) = format {
+ match &mut value.inner {
+ ValueInner::Number(number) => {
+ number.format = format;
+ }
+ ValueInner::String(string) => {
+ if format.type_().category() == format::Category::Date
+ && let Ok(date_time) =
+ NaiveDateTime::parse_from_str(&string.s, "%Y-%m-%dT%H:%M:%S%.3f")
+ {
+ value.inner = ValueInner::Number(NumberValue {
+ show: None,
+ format,
+ honor_small: false,
+ value: Some(date_time_to_pspp(date_time)),
+ variable: None,
+ value_label: None,
+ })
+ } else if format.type_().category() == format::Category::Time
+ && let Ok(time) = NaiveTime::parse_from_str(&string.s, "%H:%M:%S%.3f")
+ {
+ value.inner = ValueInner::Number(NumberValue {
+ show: None,
+ format,
+ honor_small: false,
+ value: Some(time_to_pspp(time)),
+ variable: None,
+ value_label: None,
+ })
+ } else if let Ok(number) = string.s.parse::<f64>() {
+ value.inner = ValueInner::Number(NumberValue {
+ show: None,
+ format,
+ honor_small: false,
+ value: Some(number),
+ variable: None,
+ value_label: None,
+ })
+ }
+ }
+ _ => (),
+ }
+ }
+ }
+
+ if fg.is_some() || bg.is_some() {
+ let mut styling = value.styling_mut();
+ let font_style = styling
+ .font_style
+ .get_or_insert_with(|| base_style.font_style.clone());
+ let cell_style = styling
+ .cell_style
+ .get_or_insert_with(|| base_style.cell_style.clone());
+ Self::decode(fg, bg, cell_style, font_style);
+ }
+ }
+
+ fn decode(
+ fg: Option<&Style>,
+ bg: Option<&Style>,
+ cell_style: &mut CellStyle,
+ font_style: &mut pivot::FontStyle,
+ ) {
if let Some(fg) = fg {
if let Some(weight) = fg.font_weight {
- out.font_style.bold = weight.is_bold();
+ font_style.bold = weight.is_bold();
}
if let Some(style) = fg.font_style {
- out.font_style.italic = style.is_italic();
+ font_style.italic = style.is_italic();
}
if let Some(underline) = fg.font_underline {
- out.font_style.underline = underline.is_underline();
+ font_style.underline = underline.is_underline();
}
if let Some(color) = fg.color {
- out.font_style.fg = color;
+ font_style.fg = color;
}
if let Some(font_size) = &fg.font_size {
if let Ok(size) = font_size
.trim_end_matches(|c: char| c.is_alphabetic())
.parse()
{
- out.font_style.size = size;
+ font_style.size = size;
} else {
// XXX warn?
}
}
if let Some(alignment) = fg.text_alignment {
- out.cell_style.horz_align = alignment.as_horz_align(fg.decimal_offset);
+ cell_style.horz_align = alignment.as_horz_align(fg.decimal_offset);
}
if let Some(label_local_vertical) = fg.label_location_vertical {
- out.cell_style.vert_align = label_local_vertical.into();
+ cell_style.vert_align = label_local_vertical.into();
}
}
if let Some(bg) = bg {
if let Some(color) = bg.color {
- out.font_style.bg = color;
+ font_style.bg = color;
}
}
}
+
+ fn decode_area(fg: Option<&Style>, bg: Option<&Style>, out: &mut AreaStyle) {
+ Self::decode(fg, bg, &mut out.cell_style, &mut out.font_style);
+ }
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize)]
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct Graph {
+ #[serde(rename = "@id")]
+ id: Option<String>,
+
#[serde(rename = "@cellStyle")]
cell_style: Ref<Style>,
sets: Vec<Set>,
#[serde(rename = "union")]
- unions: Option<Union>,
+ union_: Option<Union>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct Intersect {
#[serde(default, rename = "$value")]
- child: Vec<IntersectChild>,
+ children: Vec<IntersectChild>,
}
#[derive(Deserialize, Debug)]
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct Labeling {
+ #[serde(rename = "@id")]
+ id: Option<String>,
+
#[serde(rename = "@style")]
style: Option<Ref<Style>>,
fn decode_style(&self, area_style: &mut AreaStyle, styles: &HashMap<&str, &Style>) {
let fg = self.style.get(styles);
let bg = self.text_frame_style.as_ref().and_then(|r| r.get(styles));
- Style::decode(fg, bg, area_style);
+ Style::decode_area(fg, bg, area_style);
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Enum)]
}
impl LabelFrame {
- fn decode(&self, look: &mut Look, styles: &HashMap<&str, &Style>) {
- let Some(label) = &self.label else { return };
- let Some(purpose) = label.purpose else { return };
- let area = match purpose {
- Purpose::Title => Area::Title,
- Purpose::SubTitle => Area::Caption,
- Purpose::SubSubTitle => return,
- Purpose::Layer => Area::Layers,
- Purpose::Footnote => Area::Footer,
- };
- Style::decode(
- label.style.get(styles),
- label.text_frame_style.as_ref().and_then(|r| r.get(styles)),
- &mut look.areas[area],
- );
- todo!()
+ fn decode_label(labels: &[&Label]) -> Option<Value> {
+ if !labels.is_empty() {
+ let mut s = String::new();
+ for t in labels {
+ if let LabelChild::Text(text) = &t.child {
+ for t in text {
+ if let Some(defines_reference) = t.defines_reference {
+ // XXX footnote
+ }
+ s += &t.text;
+ }
+ }
+ }
+ Some(Value::new_user_text(s))
+ } else {
+ None
+ }
}
}