usually in a different format. The basic syntax is:
```
-pspp convert <INPUT> [OUTPUT]
+pspp convert <INPUT>... [-o <OUTPUT>]
```
-which reads `<INPUT>` and writes a copy of it to `[OUTPUT]`, or to the
-terminal if `[OUTPUT]` is omitted.
+which reads each `<INPUT>` and writes a copy to `<OUTPUT>` if the `-o`
+option is specified, or to the terminal otherwise.
`pspp convert` can convert following kinds of files:
* Data files:
- When `<INPUT>` is an SPSS system file or portable file or an SPSS/PC+
- system file, then `[OUTPUT]` can be a data file format. The
- currently supported data file formats are:
+ When `<INPUT>` is a single SPSS system file or portable file or
+ SPSS/PC+ system file, the currently supported output file formats
+ are:
- [Comma-separated value (CSV) files]
- [SPSS system files]
- [SPSS portable files]
- If `[OUTPUT]` is omitted, the default is to write the data to stdout
- in CSV format.
+ If `-o <OUTPUT>` is omitted, the default is to write the data to
+ stdout in CSV format.
* Viewer files:
- When `<INPUT>` is an SPSS viewer (SPV) file, then `[OUTPUT]` may be
- any [PSPP output format], including:
+ When `<INPUT>` is one or more SPSS viewer (SPV) files, then the
+ output may be any [PSPP output format], including:
- [plain text]
- [PDF]
- [HTML]
- If `[OUTPUT]` is omitted, the default is to write the viewer file to
- stdout in plain text format.
+ If `-o <OUTPUT>` is omitted, the default is to write the viewer file
+ to stdout in plain text format.
For viewer file input, [selection
options](#viewer-file-selection-options) can select a subset of the
viewer file for output.
+ If more than one viewer file is specified as input, they are
+ concatenated in the order specified to produce the output.
+
* Chart XML files:
The SPSS graph editor can export charts as individual XML files.
PSPP can read and convert these to any of the support output
formats.
+ Any number of viewer files or chart XML files may be specified as
+ input.
+
[Converting SPSS Viewer Files]: pspp-convert-spv.md
[Comma-separated value (CSV) files]: output.md#comma-separated-value-output-csv
[SPSS system files]: output.md#system-file-output-sav
`pspp convert` accepts the following options:
+* `-o <OUTPUT>`
+ Sets the output file name. The extension on `<OUTPUT>` determines
+ the default output format. Without this option, PSPP writes output
+ to standard output.
+
* `-e <ENCODING>`
`--encoding=<ENCODING>`
Sets the character encoding used to read text strings in the input
> ⚠️ The password (and other command-line options) may be visible to
> other users on multiuser systems.
-* `-o <OUTPUT_OPTIONS>`
+* `-O <OUTPUT_OPTIONS>`
Adds `<OUTPUT_OPTIONS>` to the output engine configuration. See
[Output Drivers](output.md) for information on how to configure
output.
///
/// * SPSS exported chart files (usually `.xml`).
#[arg(verbatim_doc_comment)]
- input: PathBuf,
+ input: Vec<PathBuf>,
/// Output file name (if omitted, output is written to stdout).
///
/// * plain text
/// * PDF
/// * HTML
- #[arg(verbatim_doc_comment)]
+ #[arg(verbatim_doc_comment, long, short = 'o')]
output: Option<PathBuf>,
/// The encoding to use for reading the input file.
to_unicode: bool,
/// Output driver configuration options.
- #[arg(short = 'o')]
+ #[arg(short = 'O')]
output_options: Vec<String>,
/// Selection options for SPV input files.
dictionary: Dictionary,
cases: Box<dyn Iterator<Item = Result<Case<Vec<Datum<ByteString>>>, AnyError>>>,
) -> Result<()> {
+ if self.input.len() > 1 {
+ bail!("'convert' only supports a single data file input");
+ }
+
// Take only the first `self.max_cases` cases.
let cases = cases.take(self.max_cases.unwrap_or(usize::MAX));
}
pub fn run(self) -> Result<()> {
- match FileType::from_file(&self.input)? {
- Some(FileType::System { .. }) => {
- fn warn(warning: anyhow::Error) {
- eprintln!("warning: {warning}");
- }
+ let mut data = None;
+ let mut page_setup = None;
+ let mut items = Vec::new();
- let mut system_file = ReadOptions::new(warn)
- .with_encoding(self.encoding)
- .with_password(self.password.clone())
- .open_file(&self.input)?;
- if self.to_unicode {
- system_file = system_file.into_unicode();
- }
- let (dictionary, _, cases) = system_file.into_parts();
- let cases = cases.map(|result| result.map_err(AnyError::from));
- let cases = Box::new(cases)
- as Box<dyn Iterator<Item = Result<Case<Vec<Datum<ByteString>>>, AnyError>>>;
- self.write_data(dictionary, cases)
+ for input in &self.input {
+ if data.is_some() {
+ bail!("convert: Only one data file may be specified.");
}
- Some(FileType::Portable) => {
- fn warn_portable(warning: pspp::por::Warning) {
- eprintln!("warning: {warning}");
- }
- let portable_file = PortableFile::open_file(&self.input, warn_portable)?;
- let (dictionary, _, cases) = portable_file.into_parts();
- let cases = cases.map(|result| result.map_err(AnyError::from));
- let cases = Box::new(cases)
- as Box<dyn Iterator<Item = Result<Case<Vec<Datum<ByteString>>>, AnyError>>>;
- self.write_data(dictionary, cases)
- }
- Some(FileType::Pc) => {
- fn warn_pc(warning: pspp::pc::Warning) {
- eprintln!("warning: {warning}");
+ match FileType::from_file(input)? {
+ Some(FileType::System { .. }) => {
+ fn warn(warning: anyhow::Error) {
+ eprintln!("warning: {warning}");
+ }
+
+ let mut system_file = ReadOptions::new(warn)
+ .with_encoding(self.encoding)
+ .with_password(self.password.clone())
+ .open_file(input)?;
+ if self.to_unicode {
+ system_file = system_file.into_unicode();
+ }
+ let (dictionary, _, cases) = system_file.into_parts();
+ let cases = cases.map(|result| result.map_err(AnyError::from));
+ let cases = Box::new(cases)
+ as Box<dyn Iterator<Item = Result<Case<Vec<Datum<ByteString>>>, AnyError>>>;
+ data = Some((dictionary, cases));
}
-
- let pc_file = PcFile::open_file(&self.input, warn_pc)?;
- let (dictionary, _, cases) = pc_file.into_parts();
- let cases = cases.map(|result| result.map_err(AnyError::from));
- let cases = Box::new(cases)
- as Box<dyn Iterator<Item = Result<Case<Vec<Datum<ByteString>>>, AnyError>>>;
- self.write_data(dictionary, cases)
- }
- Some(FileType::Viewer { .. }) => {
- let mut archive = SpvArchive::open_file(&self.input, self.password.as_deref())?;
- let (items, page_setup) = archive
- .read_outline(|e| eprintln!("{e}"))?
- .filter(&self.criteria)
- .read(&mut archive, &mut |e| eprintln!("{e}"))?
- .into_contents();
- let mut output = self.open_driver("text")?;
- if let Some(page_setup) = &page_setup {
- output.setup(page_setup);
+ Some(FileType::Portable) => {
+ fn warn_portable(warning: pspp::por::Warning) {
+ eprintln!("warning: {warning}");
+ }
+
+ let portable_file = PortableFile::open_file(input, warn_portable)?;
+ let (dictionary, _, cases) = portable_file.into_parts();
+ let cases = cases.map(|result| result.map_err(AnyError::from));
+ let cases = Box::new(cases)
+ as Box<dyn Iterator<Item = Result<Case<Vec<Datum<ByteString>>>, AnyError>>>;
+ data = Some((dictionary, cases));
+ }
+ Some(FileType::Pc) => {
+ fn warn_pc(warning: pspp::pc::Warning) {
+ eprintln!("warning: {warning}");
+ }
+
+ let pc_file = PcFile::open_file(input, warn_pc)?;
+ let (dictionary, _, cases) = pc_file.into_parts();
+ let cases = cases.map(|result| result.map_err(AnyError::from));
+ let cases = Box::new(cases)
+ as Box<dyn Iterator<Item = Result<Case<Vec<Datum<ByteString>>>, AnyError>>>;
+ data = Some((dictionary, cases));
}
- for item in items {
- output.write(&Arc::new(item));
+ Some(FileType::Viewer { .. }) => {
+ let mut archive = SpvArchive::open_file(input, self.password.as_deref())?;
+ let (mut archive_items, archive_page_setup) = archive
+ .read_outline(|e| eprintln!("{e}"))?
+ .filter(&self.criteria)
+ .read(&mut archive, &mut |e| eprintln!("{e}"))?
+ .into_contents();
+ page_setup = page_setup.or(archive_page_setup);
+ items.append(&mut archive_items);
}
- Ok(())
+ Some(FileType::Graph) => {
+ let graph = Item::read_graph(input)?;
+ items.push(graph);
+ }
+ Some(other) => bail!("{}: cannot convert {other} file", input.display()),
+ _ => bail!("{}: unknown input file format", input.display()),
+ }
+ }
+
+ if data.is_some() && !items.is_empty() {
+ bail!("convert: Data and viewer files may not both be specified.");
+ }
+
+ if let Some((dictionary, cases)) = data {
+ self.write_data(dictionary, cases)
+ } else {
+ let mut output = self.open_driver("text")?;
+ if let Some(page_setup) = &page_setup {
+ output.setup(page_setup);
}
- Some(FileType::Graph) => {
- let graph = Item::read_graph(&self.input)?;
- let mut output = self.open_driver("text")?;
- output.write(&Arc::new(graph));
- Ok(())
+ for item in items {
+ output.write(&Arc::new(item));
}
- Some(other) => bail!("{}: cannot convert {other} file", self.input.display()),
- _ => bail!("{}: unknown input file format", self.input.display()),
+ Ok(())
}
}
}