From 52afd109c4f1e4a524ff068c0389388592055455 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Sun, 25 Oct 2020 14:28:55 -0700 Subject: [PATCH] pspp-output: Add new --nth-commands option. --- NEWS | 2 ++ doc/pspp-output.texi | 6 ++++ src/output/spv/spv-select.c | 54 +++++++++++++++++++++++++--------- src/output/spv/spv-select.h | 6 +++- tests/utilities/pspp-output.at | 14 +++++++++ utilities/pspp-output.1 | 5 ++++ utilities/pspp-output.c | 23 +++++++++++++++ 7 files changed, 95 insertions(+), 15 deletions(-) diff --git a/NEWS b/NEWS index 2b5cfd4ef7..00b3c38e8c 100644 --- a/NEWS +++ b/NEWS @@ -17,6 +17,8 @@ Changes from 1.4.1 to 1.5.2: The new interface provides the user with a preview of the data to be imported and interactive methods to select the desired ranges. + * The pspp-output utility has a new --nth-commands option. + Changes from 1.4.0 to 1.4.1: * Bug fixes. diff --git a/doc/pspp-output.texi b/doc/pspp-output.texi index 4b9aeb203e..29c625e0cd 100644 --- a/doc/pspp-output.texi +++ b/doc/pspp-output.texi @@ -159,6 +159,12 @@ The @option{--labels} option matches the labels in table output (that is, the table titles). Labels are affected by the output language, variable names and labels, split file settings, and other factors. +@item --nth-commands=@var{n}@dots{} +Include only objects from the @var{n}th command that matches +@option{--command} (or the @var{n}th command overall if +@option{--command} is not specified), where @var{n} is 1 for the first +command, 2 for the second, and so on. + @item --instances=@var{instance}@dots{} Include the specified @var{instance} of an object that matches the other criteria within a single command. The @var{instance} may be a diff --git a/src/output/spv/spv-select.c b/src/output/spv/spv-select.c index dacd53ec91..6be9742795 100644 --- a/src/output/spv/spv-select.c +++ b/src/output/spv/spv-select.c @@ -27,18 +27,17 @@ #include "gl/c-ctype.h" #include "gl/xalloc.h" +static bool +is_command_item (const struct spv_item *item) +{ + return !item->parent || !item->parent->parent; +} + static struct spv_item * find_command_item (struct spv_item *item) { - /* A command item itself does not have a command item. */ - if (!item->parent || !item->parent->parent) - return NULL; - - do - { - item = item->parent; - } - while (item->parent && item->parent->parent); + while (!is_command_item (item)) + item = item->parent; return item; } @@ -97,21 +96,36 @@ match_instance (const int *instances, size_t n_instances, return retval; } +static bool +match_command (size_t nth_command, size_t *commands, size_t n_commands) +{ + for (size_t i = 0; i < n_commands; i++) + if (nth_command == commands[i]) + return true; + return false; +} + static void select_matches (const struct spv_reader *spv, const struct spv_criteria *c, unsigned long int *include) { - struct spv_item *item; - struct spv_item *command_item = NULL; + /* Counting instances within a command. */ + struct spv_item *instance_command_item = NULL; int instance_within_command = 0; int last_instance = -1; + + /* Counting commands. */ + struct spv_item *command_command_item = NULL; + size_t nth_command = 0; + + struct spv_item *item; ssize_t index = -1; SPV_ITEM_FOR_EACH_SKIP_ROOT (item, spv_get_root (spv)) { index++; struct spv_item *new_command_item = find_command_item (item); - if (new_command_item != command_item) + if (new_command_item != instance_command_item) { if (last_instance >= 0) { @@ -119,7 +133,7 @@ select_matches (const struct spv_reader *spv, const struct spv_criteria *c, last_instance = -1; } - command_item = new_command_item; + instance_command_item = new_command_item; instance_within_command = 0; } @@ -140,6 +154,18 @@ select_matches (const struct spv_reader *spv, const struct spv_criteria *c, &c->include.commands, &c->exclude.commands)) continue; + if (c->n_commands) + { + if (new_command_item != command_command_item) + { + command_command_item = new_command_item; + nth_command++; + } + + if (!match_command (nth_command, c->commands, c->n_commands)) + continue; + } + if (!match (spv_item_get_subtype (item), &c->include.subtypes, &c->exclude.subtypes)) continue; @@ -157,7 +183,7 @@ select_matches (const struct spv_reader *spv, const struct spv_criteria *c, if (c->n_instances) { - if (!command_item) + if (is_command_item (item)) continue; instance_within_command++; diff --git a/src/output/spv/spv-select.h b/src/output/spv/spv-select.h index b8bcd98e4e..b2a1e0e7c8 100644 --- a/src/output/spv/spv-select.h +++ b/src/output/spv/spv-select.h @@ -52,6 +52,11 @@ struct spv_criteria struct spv_criteria_match include; struct spv_criteria_match exclude; + /* Include objects under commands with indexes listed in COMMANDS. Indexes + are 1-based. Everything is included if N_COMMANDS is 0. */ + size_t *commands; + size_t n_commands; + /* Include XML and binary member names that match (except that everything is included by default if empty). */ struct string_array members; @@ -69,5 +74,4 @@ void spv_select (const struct spv_reader *, const struct spv_criteria[], size_t n_criteria, struct spv_item ***items, size_t *n_items); - #endif /* output/spv/spv-select.h */ diff --git a/tests/utilities/pspp-output.at b/tests/utilities/pspp-output.at index a577867f76..014a7830a4 100644 --- a/tests/utilities/pspp-output.at +++ b/tests/utilities/pspp-output.at @@ -80,6 +80,20 @@ AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --commands='^reg*'], ]) AT_CLEANUP +AT_SETUP([pspp-output --nth-commands]) +AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --nth-commands=2,4,6], + [0], [dnl +- heading "Title" command "Title" + - text "Page Title" command "Title" +- heading "Begin Data" command "Begin Data" +- heading "Frequencies" command "Frequencies" + - table "Statistics" command "Frequencies" + - table "v0" command "Frequencies" subtype "Frequencies" + - table "v1" command "Frequencies" subtype "Frequencies" + - table "v2" command "Frequencies" subtype "Frequencies" +]) +AT_CLEANUP + AT_SETUP([pspp-output --subtypes equal]) AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --subtypes='freq*'], [0], [dnl diff --git a/utilities/pspp-output.1 b/utilities/pspp-output.1 index ac92efcb1c..4de1e294db 100644 --- a/utilities/pspp-output.1 +++ b/utilities/pspp-output.1 @@ -121,6 +121,11 @@ Subtypes are always in English and \fBdir\fR will print them. The \fB\-\-labels\fR option matches the labels in table output (that is, the table titles). Labels are affected by the output language, variable names and labels, split file settings, and other factors. +.IP "\-\-nth-commands=\fIn\fR..." +Include only objects from the \fIn\fRth command that matches +\fB\-\-commands\fR (or the \fIn\fRth command overall if +\fB\-\-commands\fR is not specified), where \fIn\fR is 1 for the first +command, 2 for the second, and so on. .IP "\fB\-\-instances=\fIinstance\fR..." Include the specified \fIinstance\fR of an object that matches the other criteria within a single command. The \fIinstance\fR may be a diff --git a/utilities/pspp-output.c b/utilities/pspp-output.c index bd4e895f7a..58547196be 100644 --- a/utilities/pspp-output.c +++ b/utilities/pspp-output.c @@ -849,6 +849,22 @@ parse_instances (char *arg) } } +static void +parse_nth_commands (char *arg) +{ + struct spv_criteria *c = get_criteria (); + size_t allocated_commands = c->n_commands; + + for (char *token = strtok (arg, ","); token; token = strtok (NULL, ",")) + { + if (c->n_commands >= allocated_commands) + c->commands = x2nrealloc (c->commands, &allocated_commands, + sizeof *c->commands); + + c->commands[c->n_commands++] = atoi (token); + } +} + static void parse_members (const char *arg) { @@ -867,6 +883,7 @@ parse_options (int argc, char *argv[]) OPT_SHOW_HIDDEN, OPT_SELECT, OPT_COMMANDS, + OPT_NTH_COMMANDS, OPT_SUBTYPES, OPT_LABELS, OPT_INSTANCES, @@ -882,6 +899,7 @@ parse_options (int argc, char *argv[]) { "show-hidden", no_argument, NULL, OPT_SHOW_HIDDEN }, { "select", required_argument, NULL, OPT_SELECT }, { "commands", required_argument, NULL, OPT_COMMANDS }, + { "nth-commands", required_argument, NULL, OPT_NTH_COMMANDS }, { "subtypes", required_argument, NULL, OPT_SUBTYPES }, { "labels", required_argument, NULL, OPT_LABELS }, { "instances", required_argument, NULL, OPT_INSTANCES }, @@ -933,6 +951,10 @@ parse_options (int argc, char *argv[]) parse_commands (optarg); break; + case OPT_NTH_COMMANDS: + parse_nth_commands (optarg); + break; + case OPT_SUBTYPES: parse_subtypes (optarg); break; @@ -1013,6 +1035,7 @@ Input selection options for \"dir\" and \"convert\":\n\ --select=CLASS... include only some kinds of objects\n\ --select=help print known object classes\n\ --commands=COMMAND... include only specified COMMANDs\n\ + --nth-commands=N... include only the Nth instance of selected commands\n\ --subtypes=SUBTYPE... include only specified SUBTYPEs of output\n\ --labels=LABEL... include only output objects with the given LABELs\n\ --instances=INSTANCE... include only the given object INSTANCEs\n\ -- 2.30.2