From 20cac2d45961e89eef19ddcc2662fa86fee69f80 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Wed, 31 Dec 2025 08:44:50 -0800 Subject: [PATCH] work on outlines --- rust/pspp/src/cli/show_spv.rs | 2 +- rust/pspp/src/output.rs | 206 ++++++++++++------- rust/pspp/src/output/drivers/cairo/driver.rs | 4 +- rust/pspp/src/output/drivers/text.rs | 2 +- rust/pspp/src/spv/read.rs | 13 +- rust/pspp/src/spv/read/structure.rs | 15 +- rust/pspp/src/spv/write.rs | 2 +- 7 files changed, 149 insertions(+), 95 deletions(-) diff --git a/rust/pspp/src/cli/show_spv.rs b/rust/pspp/src/cli/show_spv.rs index 1a28c46f12..4bf125bf85 100644 --- a/rust/pspp/src/cli/show_spv.rs +++ b/rust/pspp/src/cli/show_spv.rs @@ -17,7 +17,7 @@ use anyhow::Result; use clap::{Args, ValueEnum}; use pspp::{ - output::{Criteria, Item}, + output::{Criteria, Item, ItemInfo}, spv::SpvArchive, }; use std::{fmt::Display, path::PathBuf, sync::Arc}; diff --git a/rust/pspp/src/output.rs b/rust/pspp/src/output.rs index 7ef4d325ad..a915914a75 100644 --- a/rust/pspp/src/output.rs +++ b/rust/pspp/src/output.rs @@ -80,6 +80,88 @@ pub struct Item { pub spv_info: Option>, } +pub trait ItemInfo { + fn label(&self) -> Cow<'static, str>; + fn command_name(&self) -> Option<&str>; + fn subtype(&self) -> Option; + fn is_shown(&self) -> bool; + fn spv_info(&self) -> Option<&SpvInfo>; + fn iter_in_order(&self) -> ItemRefIterator<'_, Self> + where + Self: Sized; + fn kind(&self) -> ItemKind; + fn class(&self) -> Class { + match self.kind() { + ItemKind::Graph => Class::Graphs, + ItemKind::Image => Class::Other, + ItemKind::Heading => Class::OutlineHeaders, + ItemKind::Message(severity) => match severity { + Severity::Note => Class::Notes, + Severity::Error | Severity::Warning => Class::Warnings, + }, + ItemKind::PageBreak => Class::Other, + ItemKind::Table => match self.label().as_ref() { + "Warnings" => Class::Warnings, + "Notes" => Class::Notes, + _ => Class::Tables, + }, + ItemKind::Text => match self.label().as_ref() { + "Title" => Class::Headings, + "Log" => Class::Logs, + "Page Title" => Class::PageTitle, + _ => Class::Texts, + }, + } + } + + type Child: AsRef; + fn children(&self) -> &[Self::Child]; +} + +impl ItemInfo for Item { + fn label(&self) -> Cow<'static, str> { + match &self.label { + Some(label) => Cow::from(label.clone()), + None => self.details.label(), + } + } + + fn command_name(&self) -> Option<&str> { + self.command_name.as_deref() + } + + fn subtype(&self) -> Option { + self.details + .as_table() + .map(|table| table.subtype().display(table).to_string()) + } + + /// Should the item be shown? + /// + /// This always returns true for headings because their contents are always + /// shown (although headings can be collapsed in an outline view). + fn is_shown(&self) -> bool { + self.details.is_heading() || self.show + } + + fn spv_info(&self) -> Option<&SpvInfo> { + self.spv_info.as_deref() + } + + fn iter_in_order(&self) -> ItemRefIterator<'_, Item> { + ItemRefIterator::new(self) + } + + type Child = Arc; + fn children(&self) -> &[Self::Child] { + self.details.children() + } + + fn kind(&self) -> ItemKind { + self.details.kind() + } +} + impl Item { pub fn new(details: impl Into
) -> Self { let details = details.into(); @@ -92,13 +174,6 @@ impl Item { } } - pub fn label(&self) -> Cow<'static, str> { - match &self.label { - Some(label) => Cow::from(label.clone()), - None => self.details.label(), - } - } - pub fn subtype(&self) -> Option { self.details .as_table() @@ -134,16 +209,8 @@ impl Item { } } - /// Should the item be shown? - /// - /// This always returns true for headings because their contents are always - /// shown (although headings can be collapsed in an outline view). - pub fn is_shown(&self) -> bool { - self.details.is_heading() || self.show - } - - pub fn iter_in_order(&self) -> ItemRefIterator<'_> { - ItemRefIterator::new(self) + pub fn cursor(self: Arc) -> ItemCursor { + ItemCursor::new(self) } } @@ -203,7 +270,7 @@ pub enum ItemKind { Graph, Image, Heading, - Message, + Message(Severity), PageBreak, Table, Text, @@ -215,7 +282,7 @@ impl ItemKind { ItemKind::Graph => "graph", ItemKind::Image => "image", ItemKind::Heading => "heading", - ItemKind::Message => "message", + ItemKind::Message(_) => "message", ItemKind::PageBreak => "page break", ItemKind::Table => "table", ItemKind::Text => "text", @@ -336,7 +403,7 @@ impl Details { Details::Graph => ItemKind::Graph, Details::Image(_) => ItemKind::Image, Details::Heading(_) => ItemKind::Heading, - Details::Message(_) => ItemKind::Message, + Details::Message(diagnostic) => ItemKind::Message(diagnostic.severity), Details::PageBreak => ItemKind::PageBreak, Details::Table(_) => ItemKind::Table, Details::Text(_) => ItemKind::Text, @@ -493,17 +560,20 @@ impl TextType { } } -pub struct ItemRefIterator<'a> { - next: Option<&'a Item>, - stack: Vec<(&'a Item, usize)>, +pub struct ItemRefIterator<'a, T> { + next: Option<&'a T>, + stack: Vec<(&'a T, usize)>, } -impl<'a> ItemRefIterator<'a> { - pub fn without_hidden(self) -> impl Iterator { +impl<'a, T> ItemRefIterator<'a, T> { + pub fn without_hidden(self) -> impl Iterator + where + T: ItemInfo, + { self.filter(|item| item.is_shown()) } - pub fn new(start: &'a Item) -> Self { + pub fn new(start: &'a T) -> Self { Self { next: Some(start), stack: Vec::new(), @@ -511,18 +581,21 @@ impl<'a> ItemRefIterator<'a> { } } -impl<'a> Iterator for ItemRefIterator<'a> { - type Item = &'a Item; +impl<'a, T> Iterator for ItemRefIterator<'a, T> +where + T: ItemInfo, +{ + type Item = &'a T; fn next(&mut self) -> Option { let cur = self.next.take()?; - if let Some(first_child) = cur.details.children().first() { - self.next = Some(&*first_child); + if let Some(first_child) = cur.children().first() { + self.next = Some(first_child.as_ref()); self.stack.push((cur, 1)); } else { while let Some((item, index)) = self.stack.pop() { - if let Some(child) = item.details.children().get(index) { - self.next = Some(&*child); + if let Some(child) = item.children().get(index) { + self.next = Some(child.as_ref()); self.stack.push((item, index + 1)); return Some(cur); } @@ -532,22 +605,30 @@ impl<'a> Iterator for ItemRefIterator<'a> { } } -pub struct ItemCursor { - cur: Option>, - stack: Vec<(Arc, usize)>, +pub struct ItemCursor +where + T: ItemInfo, + T::Child: Clone, +{ + cur: Option, + stack: Vec<(T::Child, usize)>, include_hidden: bool, } -impl ItemCursor { - pub fn new(start: Arc) -> Self { +impl ItemCursor +where + T: ItemInfo, + T::Child: Clone, +{ + pub fn new(start: T::Child) -> Self { Self { - cur: start.is_shown().then_some(start), + cur: start.as_ref().is_shown().then_some(start), stack: Vec::new(), include_hidden: false, } } - pub fn with_hidden(start: Arc) -> Self { + pub fn with_hidden(start: T::Child) -> Self { Self { cur: Some(start), stack: Vec::new(), @@ -555,21 +636,25 @@ impl ItemCursor { } } - pub fn cur(&self) -> Option<&Arc> { + pub fn cur(&self) -> Option<&T::Child> { self.cur.as_ref() } pub fn next(&mut self) { - fn inner(this: &mut ItemCursor) { + fn inner(this: &mut ItemCursor) + where + T: ItemInfo, + T::Child: Clone, + { let Some(cur) = this.cur.take() else { return; }; - if let Some(first_child) = cur.details.children().first() { + if let Some(first_child) = cur.as_ref().children().first() { this.cur = Some(first_child.clone()); this.stack.push((cur, 1)); } else { while let Some((item, index)) = this.stack.pop() { - if let Some(child) = item.details.children().get(index) { + if let Some(child) = item.as_ref().children().get(index) { this.cur = Some(child.clone()); this.stack.push((item, index + 1)); return; @@ -580,7 +665,7 @@ impl ItemCursor { inner(self); while let Some(cur) = &self.cur - && !cur.is_shown() + && !cur.as_ref().is_shown() { inner(self); } @@ -589,12 +674,12 @@ impl ItemCursor { // Returns the label for the heading with the given `level` in the stack // above the current item. Level 0 is the top level. Levels without a // label are skipped. - pub fn heading(&self, level: usize) -> Option<&str> { + pub fn heading(&self, level: usize) -> Option> { self.stack .iter() - .filter_map(|(item, _index)| item.label.as_ref()) + .map(|(item, _index)| item.as_ref().label()) + .filter(|label| !label.is_empty()) .nth(level) - .map(|s| s.as_str()) } } @@ -729,33 +814,6 @@ impl FromStr for Class { } } -impl Item { - fn class(&self) -> Class { - let label = self.label.as_ref().map(|s| s.as_str()); - match &self.details { - Details::Graph => Class::Graphs, - Details::Image(_) => Class::Other, - Details::Heading(_) => Class::OutlineHeaders, - Details::Message(diagnostic) => match diagnostic.severity { - Severity::Note => Class::Notes, - Severity::Error | Severity::Warning => Class::Warnings, - }, - Details::PageBreak => Class::Other, - Details::Table(_) => match label { - Some("Warnings") => Class::Warnings, - Some("Notes") => Class::Notes, - _ => Class::Tables, - }, - Details::Text(_) => match label { - Some("Title") => Class::Headings, - Some("Log") => Class::Logs, - Some("Page Title") => Class::PageTitle, - _ => Class::Texts, - }, - } - } -} - #[derive(Clone, Debug, PartialEq, Eq)] pub struct Selection { /// - `None`: Include all objects. diff --git a/rust/pspp/src/output/drivers/cairo/driver.rs b/rust/pspp/src/output/drivers/cairo/driver.rs index fe783a0b04..623b4631f9 100644 --- a/rust/pspp/src/output/drivers/cairo/driver.rs +++ b/rust/pspp/src/output/drivers/cairo/driver.rs @@ -29,7 +29,7 @@ use serde::{Deserialize, Serialize}; use crate::{ output::{ - Item, ItemCursor, TextType, + Item, TextType, drivers::{ Driver, cairo::{ @@ -138,7 +138,7 @@ impl Driver for CairoDriver { let pager = self.pager.get_or_insert_with(|| { CairoPager::new(self.page_style.clone(), self.fsm_style.clone()) }); - let mut cursor = ItemCursor::new(item.clone()); + let mut cursor = item.clone().cursor(); while let Some(item) = cursor.cur() { if let Some(text) = item.details.as_text() && text.type_ == TextType::PageTitle diff --git a/rust/pspp/src/output/drivers/text.rs b/rust/pspp/src/output/drivers/text.rs index dadcec87bd..f43796b058 100644 --- a/rust/pspp/src/output/drivers/text.rs +++ b/rust/pspp/src/output/drivers/text.rs @@ -29,7 +29,7 @@ use serde::{Deserialize, Serialize}; use unicode_linebreak::{BreakOpportunity, linebreaks}; use unicode_width::UnicodeWidthStr; -use crate::output::{render::Extreme, table::DrawCell}; +use crate::output::{ItemInfo, render::Extreme, table::DrawCell}; use crate::output::{ Details, Item, diff --git a/rust/pspp/src/spv/read.rs b/rust/pspp/src/spv/read.rs index 2f1fe041a8..804a286e20 100644 --- a/rust/pspp/src/spv/read.rs +++ b/rust/pspp/src/spv/read.rs @@ -128,16 +128,17 @@ where where F: FnMut(Warning), { + todo!() /* // Read all the items. let mut members = Vec::new(); for i in 0..self.0.len() { - let name = String::from(self.0.name_for_index(i).unwrap()); - if name.starts_with("outputViewer") && name.ends_with(".xml") { - let member = BufReader::new(self.0.by_index(i)?); - members.push(StructureMember::read(member, &name, &mut warn)?); - } + let name = String::from(self.0.name_for_index(i).unwrap()); + if name.starts_with("outputViewer") && name.ends_with(".xml") { + let member = BufReader::new(self.0.by_index(i)?); + members.push(StructureMember::read(member, &name, &mut warn)?); } - Ok(SpvOutline { members }) + } + Ok(SpvOutline { members })*/ } /// Reads and returns the whole SPV file contents. diff --git a/rust/pspp/src/spv/read/structure.rs b/rust/pspp/src/spv/read/structure.rs index ba10e98fbf..4f5952493e 100644 --- a/rust/pspp/src/spv/read/structure.rs +++ b/rust/pspp/src/spv/read/structure.rs @@ -50,29 +50,23 @@ impl StructureMember { reader: R, member_name: &str, warn: &mut dyn FnMut(Warning), - ) -> Result + ) -> Result where R: BufRead, { - let mut heading: raw::Heading = + let heading: raw::Heading = serde_path_to_error::deserialize(&mut quick_xml::de::Deserializer::from_reader(reader)) .map_err(|error| Error::DeserializeError { member_name: member_name.into(), error, })?; - Ok(Self { - member_name: member_name.into(), - page_setup: heading - .page_setup - .take() - .map(|ps| ps.decode(warn, member_name)), - root: heading.decode(member_name, warn), - }) + Ok(heading.decode(member_name, warn)) } } #[derive(Clone, Debug)] pub struct Heading { + structure_member: String, page_setup: Option, expand: bool, label: String, @@ -416,6 +410,7 @@ mod raw { } } super::Heading { + structure_member: structure_member.into(), page_setup: self .page_setup .map(|page_setup| page_setup.decode(warn, structure_member)), diff --git a/rust/pspp/src/spv/write.rs b/rust/pspp/src/spv/write.rs index c0cd91050f..1776bd96d9 100644 --- a/rust/pspp/src/spv/write.rs +++ b/rust/pspp/src/spv/write.rs @@ -33,7 +33,7 @@ use crate::{ data::{Datum, EncodedString}, format::{Format, Type}, output::{ - Details, Item, Text, + Details, Item, ItemInfo, Text, page::{ChartSize, PageSetup}, pivot::{ Axis2, Axis3, Category, Dimension, Footnote, FootnoteMarkerPosition, -- 2.30.2