Add support for reading and writing SPV files.
[pspp] / src / output / spv / spv-select.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2018 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include "spv-select.h"
20
21 #include <string.h>
22
23 #include "libpspp/assertion.h"
24 #include "output/spv/spv.h"
25
26 #include "gl/c-strcase.h"
27 #include "gl/xalloc.h"
28
29 static bool
30 is_descendant (const struct spv_item *ancestor,
31                const struct spv_item *descendant)
32 {
33   for (; descendant; descendant = descendant->parent)
34     if (descendant == ancestor)
35       return true;
36   return false;
37 }
38
39 static struct spv_item *
40 find_command_item (struct spv_item *item)
41 {
42   /* A command item itself does not have a command item. */
43   if (!item->parent || !item->parent->parent)
44     return NULL;
45
46   do
47     {
48       item = item->parent;
49     }
50   while (item->parent && item->parent->parent);
51   return item;
52 }
53
54 void
55 spv_select (const struct spv_reader *spv, const struct spv_criteria *c,
56             struct spv_item ***itemsp, size_t *n_itemsp)
57 {
58   size_t n_items = 0;
59   size_t allocated_items = 0;
60   struct spv_item **items = NULL;
61
62   struct spv_item **nth_command = xcalloc (c->n_commands, sizeof *nth_command);
63   const struct spv_item *root = spv_get_root (spv);
64   for (size_t i = 0; i < c->n_commands; i++)
65     {
66       const struct spv_command_match *cm = &c->commands[i];
67       if (cm->instance < 0)
68         {
69           for (size_t j = root->n_children; j--; )
70             {
71               struct spv_item *item = root->children[j];
72               if (item->command_id
73                   && (!cm->name || !strcmp (item->command_id, cm->name)))
74                 {
75                   nth_command[i] = item;
76                   break;
77                 }
78             }
79         }
80       else if (cm->instance > 0)
81         {
82           size_t n = 0;
83           for (size_t j = 0; j < root->n_children; j++)
84             {
85               struct spv_item *item = root->children[j];
86               if (item->command_id
87                   && (!cm->name || !strcmp (item->command_id, cm->name))
88                   && ++n == cm->instance)
89                 {
90                   nth_command[i] = item;
91                   break;
92                 }
93             }
94         }
95     }
96
97   struct spv_item *item;
98   struct spv_item *command_item = NULL;
99   int instance_within_command = 0;
100   bool included_as_last_instance = false;
101   SPV_ITEM_FOR_EACH_SKIP_ROOT (item, spv_get_root (spv))
102     {
103       if (!((1u << spv_item_get_class (item)) & c->classes))
104         continue;
105
106       if (!c->include_hidden && !spv_item_is_visible (item))
107         continue;
108
109       if (c->error)
110         {
111           spv_item_load (item);
112           if (!item->error)
113             continue;
114         }
115
116       if (c->commands)
117         {
118           const char *id = spv_item_get_command_id (item);
119           if (!id)
120             continue;
121
122           for (size_t i = 0; i < c->n_commands; i++)
123             {
124               const struct spv_command_match *cm = &c->commands[i];
125               if ((!cm->name || !c_strcasecmp (cm->name, id))
126                   && (!cm->instance
127                       || (nth_command[i]
128                           && is_descendant (nth_command[i], item))))
129                 goto ok;
130             }
131           continue;
132         ok:;
133         }
134
135       if (!string_set_is_empty (&c->subtypes))
136         {
137           const char *subtype = spv_item_get_subtype (item);
138           if (!subtype || !string_set_contains (&c->subtypes, subtype))
139             continue;
140         }
141
142       if (c->n_labels)
143         {
144           const char *label = spv_item_get_label (item);
145           if (!label)
146             continue;
147
148           size_t label_len = strlen (label);
149           bool match = false;
150           for (size_t i = 0; !match && i < c->n_labels; i++)
151             {
152               const char *arg = c->labels[i].arg;
153               size_t arg_len = strlen (arg);
154               switch (c->labels[i].op)
155                 {
156                 case SPV_LABEL_MATCH_EQUALS:
157                   match = !strcmp (label, arg);
158                   break;
159                 case SPV_LABEL_MATCH_CONTAINS:
160                   match = strstr (label, arg);
161                   break;
162                 case SPV_LABEL_MATCH_STARTS:
163                   match = !strncmp (label, arg, arg_len);
164                   break;
165                 case SPV_LABEL_MATCH_ENDS:
166                   match = (label_len >= arg_len
167                            && !memcmp (label + (label_len - arg_len), arg,
168                                        arg_len));
169                   break;
170                 default:
171                   NOT_REACHED ();
172                 }
173             }
174           if (!match)
175             continue;
176         }
177
178       if (c->n_instances)
179         {
180           struct spv_item *new_command_item = find_command_item (item);
181           if (new_command_item != command_item)
182             {
183               command_item = new_command_item;
184               instance_within_command = 0;
185               included_as_last_instance = false;
186             }
187           if (!command_item)
188             continue;
189           instance_within_command++;
190
191           bool include_last = false;
192           for (size_t i = 0; i < c->n_instances; i++)
193             if (instance_within_command == c->instances[i])
194               goto ok2;
195             else if (c->instances[i] == -1)
196               include_last = true;
197
198           if (!include_last)
199             continue;
200           if (included_as_last_instance)
201             n_items--;
202           else
203             included_as_last_instance = true;
204
205         ok2:;
206         }
207
208       if (n_items >= allocated_items)
209         items = x2nrealloc (items, &allocated_items, sizeof *items);
210       items[n_items++] = item;
211     }
212
213   free (nth_command);
214
215   *itemsp = items;
216   *n_itemsp = n_items;
217 }