--- /dev/null
+/* 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;
+}