pspp-output: Add new --nth-commands option.
authorBen Pfaff <blp@cs.stanford.edu>
Sun, 25 Oct 2020 21:28:55 +0000 (14:28 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Sun, 25 Oct 2020 21:29:59 +0000 (14:29 -0700)
NEWS
doc/pspp-output.texi
src/output/spv/spv-select.c
src/output/spv/spv-select.h
tests/utilities/pspp-output.at
utilities/pspp-output.1
utilities/pspp-output.c

diff --git a/NEWS b/NEWS
index 2b5cfd4ef7698e3b56119bbd2a482dd7ec40d67e..00b3c38e8c3dbab8f07acb51e8b80038e00fe061 100644 (file)
--- 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 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.
 Changes from 1.4.0 to 1.4.1:
 
  * Bug fixes.
index 4b9aeb203e4e944b80b524d66c55456688ca262c..29c625e0cd6cf1e0e5f15f2881dc395113130e08 100644 (file)
@@ -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.
 
 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
 @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
index dacd53ec91b34d4a0375a1a45d030707f8486fae..6be9742795311a225be2b00df14d927238b1fa79 100644 (file)
 #include "gl/c-ctype.h"
 #include "gl/xalloc.h"
 
 #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)
 {
 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;
 }
 
   return item;
 }
 
@@ -97,21 +96,36 @@ match_instance (const int *instances, size_t n_instances,
   return retval;
 }
 
   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)
 {
 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;
   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);
   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)
             {
         {
           if (last_instance >= 0)
             {
@@ -119,7 +133,7 @@ select_matches (const struct spv_reader *spv, const struct spv_criteria *c,
               last_instance = -1;
             }
 
               last_instance = -1;
             }
 
-          command_item = new_command_item;
+          instance_command_item = new_command_item;
           instance_within_command = 0;
         }
 
           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;
 
                   &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;
       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 (c->n_instances)
         {
-          if (!command_item)
+          if (is_command_item (item))
             continue;
           instance_within_command++;
 
             continue;
           instance_within_command++;
 
index b8bcd98e4e03d0743bfd3125b9e4b8cb8fcc0c72..b2a1e0e7c8a149284ceab825a2d718d0bed21586 100644 (file)
@@ -52,6 +52,11 @@ struct spv_criteria
     struct spv_criteria_match include;
     struct spv_criteria_match exclude;
 
     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;
     /* 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);
 
                  const struct spv_criteria[], size_t n_criteria,
                  struct spv_item ***items, size_t *n_items);
 
-
 #endif /* output/spv/spv-select.h */
 #endif /* output/spv/spv-select.h */
index a577867f76269c38b8b8e21859cc879fcced30cb..014a7830a406e2f90f438b17f9d0799bb3023fb7 100644 (file)
@@ -80,6 +80,20 @@ AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --commands='^reg*'],
 ])
 AT_CLEANUP
 
 ])
 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
 AT_SETUP([pspp-output --subtypes equal])
 AT_CHECK([pspp-output dir $srcdir/utilities/regress.spv --subtypes='freq*'],
   [0], [dnl
index ac92efcb1cc19449a428a4449627457212696a8d..4de1e294dbe0a3cd6a9ebb6fd4e114fe97f1f3b6 100644 (file)
@@ -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.
 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
 .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
index bd4e895f7a0840a5dc1ae0c8979ed7c8728d0bfb..58547196bec6451e2b7a1f5b755d4ca1109887a5 100644 (file)
@@ -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)
 {
 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_SHOW_HIDDEN,
           OPT_SELECT,
           OPT_COMMANDS,
+          OPT_NTH_COMMANDS,
           OPT_SUBTYPES,
           OPT_LABELS,
           OPT_INSTANCES,
           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 },
           { "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 },
           { "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;
 
           parse_commands (optarg);
           break;
 
+        case OPT_NTH_COMMANDS:
+          parse_nth_commands (optarg);
+          break;
+
         case OPT_SUBTYPES:
           parse_subtypes (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\
   --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\
   --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\