Add support for reading and writing SPV files.
[pspp] / src / output / spv / spv-select.c
diff --git a/src/output/spv/spv-select.c b/src/output/spv/spv-select.c
new file mode 100644 (file)
index 0000000..b3beda8
--- /dev/null
@@ -0,0 +1,217 @@
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 2018 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include "spv-select.h"
+
+#include <string.h>
+
+#include "libpspp/assertion.h"
+#include "output/spv/spv.h"
+
+#include "gl/c-strcase.h"
+#include "gl/xalloc.h"
+
+static bool
+is_descendant (const struct spv_item *ancestor,
+               const struct spv_item *descendant)
+{
+  for (; descendant; descendant = descendant->parent)
+    if (descendant == ancestor)
+      return true;
+  return false;
+}
+
+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);
+  return item;
+}
+
+void
+spv_select (const struct spv_reader *spv, const struct spv_criteria *c,
+            struct spv_item ***itemsp, size_t *n_itemsp)
+{
+  size_t n_items = 0;
+  size_t allocated_items = 0;
+  struct spv_item **items = NULL;
+
+  struct spv_item **nth_command = xcalloc (c->n_commands, sizeof *nth_command);
+  const struct spv_item *root = spv_get_root (spv);
+  for (size_t i = 0; i < c->n_commands; i++)
+    {
+      const struct spv_command_match *cm = &c->commands[i];
+      if (cm->instance < 0)
+        {
+          for (size_t j = root->n_children; j--; )
+            {
+              struct spv_item *item = root->children[j];
+              if (item->command_id
+                  && (!cm->name || !strcmp (item->command_id, cm->name)))
+                {
+                  nth_command[i] = item;
+                  break;
+                }
+            }
+        }
+      else if (cm->instance > 0)
+        {
+          size_t n = 0;
+          for (size_t j = 0; j < root->n_children; j++)
+            {
+              struct spv_item *item = root->children[j];
+              if (item->command_id
+                  && (!cm->name || !strcmp (item->command_id, cm->name))
+                  && ++n == cm->instance)
+                {
+                  nth_command[i] = item;
+                  break;
+                }
+            }
+        }
+    }
+
+  struct spv_item *item;
+  struct spv_item *command_item = NULL;
+  int instance_within_command = 0;
+  bool included_as_last_instance = false;
+  SPV_ITEM_FOR_EACH_SKIP_ROOT (item, spv_get_root (spv))
+    {
+      if (!((1u << spv_item_get_class (item)) & c->classes))
+        continue;
+
+      if (!c->include_hidden && !spv_item_is_visible (item))
+        continue;
+
+      if (c->error)
+        {
+          spv_item_load (item);
+          if (!item->error)
+            continue;
+        }
+
+      if (c->commands)
+        {
+          const char *id = spv_item_get_command_id (item);
+          if (!id)
+            continue;
+
+          for (size_t i = 0; i < c->n_commands; i++)
+            {
+              const struct spv_command_match *cm = &c->commands[i];
+              if ((!cm->name || !c_strcasecmp (cm->name, id))
+                  && (!cm->instance
+                      || (nth_command[i]
+                          && is_descendant (nth_command[i], item))))
+                goto ok;
+            }
+          continue;
+        ok:;
+        }
+
+      if (!string_set_is_empty (&c->subtypes))
+        {
+          const char *subtype = spv_item_get_subtype (item);
+          if (!subtype || !string_set_contains (&c->subtypes, subtype))
+            continue;
+        }
+
+      if (c->n_labels)
+        {
+          const char *label = spv_item_get_label (item);
+          if (!label)
+            continue;
+
+          size_t label_len = strlen (label);
+          bool match = false;
+          for (size_t i = 0; !match && i < c->n_labels; i++)
+            {
+              const char *arg = c->labels[i].arg;
+              size_t arg_len = strlen (arg);
+              switch (c->labels[i].op)
+                {
+                case SPV_LABEL_MATCH_EQUALS:
+                  match = !strcmp (label, arg);
+                  break;
+                case SPV_LABEL_MATCH_CONTAINS:
+                  match = strstr (label, arg);
+                  break;
+                case SPV_LABEL_MATCH_STARTS:
+                  match = !strncmp (label, arg, arg_len);
+                  break;
+                case SPV_LABEL_MATCH_ENDS:
+                  match = (label_len >= arg_len
+                           && !memcmp (label + (label_len - arg_len), arg,
+                                       arg_len));
+                  break;
+                default:
+                  NOT_REACHED ();
+                }
+            }
+          if (!match)
+            continue;
+        }
+
+      if (c->n_instances)
+        {
+          struct spv_item *new_command_item = find_command_item (item);
+          if (new_command_item != command_item)
+            {
+              command_item = new_command_item;
+              instance_within_command = 0;
+              included_as_last_instance = false;
+            }
+          if (!command_item)
+            continue;
+          instance_within_command++;
+
+          bool include_last = false;
+          for (size_t i = 0; i < c->n_instances; i++)
+            if (instance_within_command == c->instances[i])
+              goto ok2;
+            else if (c->instances[i] == -1)
+              include_last = true;
+
+          if (!include_last)
+            continue;
+          if (included_as_last_instance)
+            n_items--;
+          else
+            included_as_last_instance = true;
+
+        ok2:;
+        }
+
+      if (n_items >= allocated_items)
+        items = x2nrealloc (items, &allocated_items, sizeof *items);
+      items[n_items++] = item;
+    }
+
+  free (nth_command);
+
+  *itemsp = items;
+  *n_itemsp = n_items;
+}