work
authorBen Pfaff <blp@cs.stanford.edu>
Mon, 29 Dec 2025 15:22:54 +0000 (07:22 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Mon, 29 Dec 2025 15:22:54 +0000 (07:22 -0800)
rust/doc/src/spv/legacy-detail-xml.md
rust/pspp/src/cli/show_spv.rs

index a8f158b3807076fe81ac3d8951aca4b8c64f589b..9041aff0723f3a211f29087b7dd764ea81e3e824 100644 (file)
@@ -31,6 +31,20 @@ Instead, write tables in the light binary format.
 
 <!-- toc -->
 
+## Overview
+
+The legacy table format represents the pivot table as a collection of
+1-dimensional data series with equal length, forming a 2-dimensional
+table.  The data series include:
+
+* `cell`, which contains a data element in the table.
+
+* `dimension#categories`, one per dimension, which contain integer
+  indexes into the categories in the dimension, counting up from 0.
+
+* `dimension#group#`, one for each level of grouping within a
+  dimension, 
+
 ## The `visualization` Element
 
 ```
index 494e8a235210a1ea3940a7d62a30c1b8a75905c5..dd97506da297939ebdfb1aaf80d6b1bb95b14098 100644 (file)
@@ -105,91 +105,100 @@ impl Display for Mode {
 impl ShowSpv {
     pub fn run(self) -> Result<()> {
         match self.mode {
-            Mode::Directory => {
-                let item = pspp::spv::ReadOptions::new(|e| eprintln!("{e}"))
-                    .with_password(self.password)
-                    .open_file(&self.input)?
-                    .into_items();
-                let items = self.criteria.apply(item);
-                for child in items {
-                    print_item_directory(&child, 0, self.show_member_names);
-                }
-                Ok(())
-            }
-            Mode::View => {
-                let item = pspp::spv::ReadOptions::new(|e| eprintln!("{e}"))
-                    .with_password(self.password)
-                    .open_file(&self.input)?
-                    .into_items();
-                let items = self.criteria.apply(item);
-                for child in items {
-                    println!("{child}");
-                }
-                Ok(())
-            }
-            Mode::LegacyData => {
-                let mut spv_file = pspp::spv::ReadOptions::new(|e| eprintln!("{e}"))
-                    .with_password(self.password)
-                    .open_file(&self.input)?;
-
-                let items = self.criteria.apply(spv_file.items);
-                for item in items {
-                    for item in ItemRefIterator::with_hidden(&item) {
-                        if let Some(spv_info) = dbg!(&item.spv_info)
-                            && let Some(members) = &spv_info.members
-                            && let SpvMembers::LegacyTable { xml: _, binary } = &members
-                        {
-                            let mut bin_member = spv_file.archive.by_name(&binary)?;
-                            let mut bin_data = Vec::with_capacity(bin_member.size() as usize);
-                            bin_member.read_to_end(&mut bin_data)?;
-                            let mut cursor = Cursor::new(bin_data);
-                            let legacy_bin = LegacyBin::read(&mut cursor).map_err(|e| {
-                                e.with_message(format!(
-                                    "While parsing {binary:?} as legacy binary SPV member"
-                                ))
-                            })?;
-                            let data = legacy_bin.decode();
-                            let n_values = data
-                                .values()
-                                .flat_map(|map| map.values())
-                                .map(|values| values.len())
-                                .max()
-                                .unwrap_or(0);
-                            let index = Dimension::new(
-                                Group::new("Index")
-                                    .with_multiple(Leaf::numbers(0..n_values))
-                                    .with_label_shown(),
+            Mode::Directory => self.directory(),
+            Mode::View => self.view(),
+            Mode::LegacyData => self.legacy_data(),
+            Mode::GetTableLook => todo!(),
+            Mode::ConvertTableLook => todo!(),
+        }
+    }
+
+    fn directory(self) -> Result<()> {
+        let item = pspp::spv::ReadOptions::new(|e| eprintln!("{e}"))
+            .with_password(self.password)
+            .open_file(&self.input)?
+            .into_items();
+        let items = self.criteria.apply(item);
+        for child in items {
+            print_item_directory(&child, 0, self.show_member_names);
+        }
+        Ok(())
+    }
+
+    fn view(self) -> Result<()> {
+        let item = pspp::spv::ReadOptions::new(|e| eprintln!("{e}"))
+            .with_password(self.password)
+            .open_file(&self.input)?
+            .into_items();
+        let items = self.criteria.apply(item);
+        for child in items {
+            println!("{child}");
+        }
+        Ok(())
+    }
+
+    fn legacy_data(self) -> Result<()> {
+        let mut spv_file = pspp::spv::ReadOptions::new(|e| eprintln!("{e}"))
+            .with_password(self.password)
+            .open_file(&self.input)?;
+
+        let items = self.criteria.apply(spv_file.items);
+        for item in items {
+            for item in ItemRefIterator::with_hidden(&item) {
+                if let Some(spv_info) = dbg!(&item.spv_info)
+                    && let Some(members) = &spv_info.members
+                    && let SpvMembers::LegacyTable { xml: _, binary } = &members
+                {
+                    let mut bin_member = spv_file.archive.by_name(&binary)?;
+                    let mut bin_data = Vec::with_capacity(bin_member.size() as usize);
+                    bin_member.read_to_end(&mut bin_data)?;
+                    let mut cursor = Cursor::new(bin_data);
+                    let legacy_bin = LegacyBin::read(&mut cursor).map_err(|e| {
+                        e.with_message(format!(
+                            "While parsing {binary:?} as legacy binary SPV member"
+                        ))
+                    })?;
+                    let data = legacy_bin.decode();
+                    let n_values = data
+                        .values()
+                        .flat_map(|map| map.values())
+                        .map(|values| values.len())
+                        .max()
+                        .unwrap_or(0);
+                    let index = Dimension::new(
+                        Group::new("Index")
+                            .with_multiple(Leaf::numbers(0..n_values))
+                            .with_label_shown(),
+                    );
+                    let variables = Dimension::new(Group::new("Variables").with_multiple(
+                        data.iter().map(|(name, contents)| {
+                            Group::new(name.as_str()).with_multiple(contents.keys().map(|name| {
+                                name.replace("categories", "\ncategories")
+                                    .replace("labels", "\nlabels")
+                                    .replace("group", "\ngroup")
+                                    .replace("Label", "\nLabel")
+                            }))
+                        }),
+                    ));
+                    let mut pivot_table =
+                        PivotTable::new([(Axis3::Y, index), (Axis3::X, variables)]);
+                    let formats = HashMap::new();
+                    for (variable_index, (variable_name, values)) in
+                        data.values().flat_map(|map| map.iter()).enumerate()
+                    {
+                        for (value_index, data_value) in values.iter().enumerate() {
+                            let value = Value::new_datum(&data_value.value).with_value_label(
+                                (variable_name == "cellFormat")
+                                    .then(|| data_value.as_format(&formats).to_string()),
                             );
-                            let variables = Dimension::new(Group::new("Variables").with_multiple(
-                                data.iter().map(|(name, contents)| {
-                                    Group::new(name.as_str()).with_multiple(contents.keys())
-                                }),
-                            ));
-                            let mut pivot_table =
-                                PivotTable::new([(Axis3::Y, index), (Axis3::X, variables)]);
-                            let formats = HashMap::new();
-                            for (variable_index, (variable_name, values)) in
-                                data.values().flat_map(|map| map.iter()).enumerate()
-                            {
-                                for (value_index, data_value) in values.iter().enumerate() {
-                                    let value = Value::new_datum(&data_value.value)
-                                        .with_value_label(
-                                            (variable_name == "cellFormat").then(|| {
-                                                data_value.as_format(&formats).to_string()
-                                            }),
-                                        );
-                                    pivot_table.insert([value_index, variable_index], value);
-                                }
-                            }
-                            println!("{pivot_table}");
+                            pivot_table.insert([value_index, variable_index], value);
                         }
                     }
+                    println!("{pivot_table}");
                 }
-                Ok(())
             }
-            Mode::GetTableLook => todo!(),
-            Mode::ConvertTableLook => todo!(),
         }
+        Ok(())
     }
 }