pspp-output: Add new --nth-commands option.
[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 "libpspp/bit-vector.h"
25 #include "output/spv/spv.h"
26
27 #include "gl/c-ctype.h"
28 #include "gl/xalloc.h"
29
30 static bool
31 is_command_item (const struct spv_item *item)
32 {
33   return !item->parent || !item->parent->parent;
34 }
35
36 static struct spv_item *
37 find_command_item (struct spv_item *item)
38 {
39   while (!is_command_item (item))
40     item = item->parent;
41   return item;
42 }
43
44 static bool
45 string_matches (const char *pattern, const char *s)
46 {
47   /* XXX This should be a Unicode case insensitive comparison. */
48   while (c_tolower (*pattern) == c_tolower (*s))
49     {
50       if (*pattern == '\0')
51         return true;
52
53       pattern++;
54       s++;
55     }
56
57   return pattern[0] == '*' && pattern[1] == '\0';
58 }
59
60 static int
61 string_array_matches (const char *name, const struct string_array *array)
62 {
63   if (!array->n)
64     return -1;
65   else if (!name)
66     return false;
67
68   for (size_t i = 0; i < array->n; i++)
69     if (string_matches (array->strings[i], name))
70       return true;
71
72   return false;
73 }
74
75 static bool
76 match (const char *name,
77        const struct string_array *white,
78        const struct string_array *black)
79 {
80   return (string_array_matches (name, white) != false
81           && string_array_matches (name, black) != true);
82 }
83
84 static int
85 match_instance (const int *instances, size_t n_instances,
86                 int instance_within_command)
87 {
88   int retval = false;
89   for (size_t i = 0; i < n_instances; i++)
90     {
91       if (instances[i] == instance_within_command)
92         return true;
93       else if (instances[i] == -1)
94         retval = -1;
95     }
96   return retval;
97 }
98
99 static bool
100 match_command (size_t nth_command, size_t *commands, size_t n_commands)
101 {
102   for (size_t i = 0; i < n_commands; i++)
103     if (nth_command == commands[i])
104       return true;
105   return false;
106 }
107
108 static void
109 select_matches (const struct spv_reader *spv, const struct spv_criteria *c,
110                 unsigned long int *include)
111 {
112   /* Counting instances within a command. */
113   struct spv_item *instance_command_item = NULL;
114   int instance_within_command = 0;
115   int last_instance = -1;
116
117   /* Counting commands. */
118   struct spv_item *command_command_item = NULL;
119   size_t nth_command = 0;
120
121   struct spv_item *item;
122   ssize_t index = -1;
123   SPV_ITEM_FOR_EACH_SKIP_ROOT (item, spv_get_root (spv))
124     {
125       index++;
126
127       struct spv_item *new_command_item = find_command_item (item);
128       if (new_command_item != instance_command_item)
129         {
130           if (last_instance >= 0)
131             {
132               bitvector_set1 (include, last_instance);
133               last_instance = -1;
134             }
135
136           instance_command_item = new_command_item;
137           instance_within_command = 0;
138         }
139
140       if (!((1u << spv_item_get_class (item)) & c->classes))
141         continue;
142
143       if (!c->include_hidden && !spv_item_is_visible (item))
144         continue;
145
146       if (c->error)
147         {
148           spv_item_load (item);
149           if (!item->error)
150             continue;
151         }
152
153       if (!match (spv_item_get_command_id (item),
154                   &c->include.commands, &c->exclude.commands))
155         continue;
156
157       if (c->n_commands)
158         {
159           if (new_command_item != command_command_item)
160             {
161               command_command_item = new_command_item;
162               nth_command++;
163             }
164
165           if (!match_command (nth_command, c->commands, c->n_commands))
166             continue;
167         }
168
169       if (!match (spv_item_get_subtype (item),
170                   &c->include.subtypes, &c->exclude.subtypes))
171         continue;
172
173       if (!match (spv_item_get_label (item),
174                   &c->include.labels, &c->exclude.labels))
175         continue;
176
177       if (c->members.n
178           && !((item->xml_member
179                 && string_array_matches (item->xml_member, &c->members)) ||
180                (item->bin_member
181                 && string_array_matches (item->bin_member, &c->members))))
182         continue;
183
184       if (c->n_instances)
185         {
186           if (is_command_item (item))
187             continue;
188           instance_within_command++;
189
190           int include_instance = match_instance (c->instances, c->n_instances,
191                                                  instance_within_command);
192           if (!include_instance)
193             continue;
194           else if (include_instance < 0)
195             {
196               last_instance = index;
197               continue;
198             }
199         }
200
201       bitvector_set1 (include, index);
202     }
203
204   if (last_instance >= 0)
205     bitvector_set1 (include, last_instance);
206 }
207
208 void
209 spv_select (const struct spv_reader *spv,
210             const struct spv_criteria c[], size_t nc,
211             struct spv_item ***itemsp, size_t *n_itemsp)
212 {
213   struct spv_item *item;
214
215   struct spv_criteria default_criteria = SPV_CRITERIA_INITIALIZER;
216   if (!nc)
217     {
218       nc = 1;
219       c = &default_criteria;
220     }
221
222   /* Count items. */
223   size_t max_items = 0;
224   SPV_ITEM_FOR_EACH_SKIP_ROOT (item, spv_get_root (spv))
225     max_items++;
226
227   /* Allocate bitmap for items then fill it in with selected items. */
228   unsigned long int *include = bitvector_allocate (max_items);
229   for (size_t i = 0; i < nc; i++)
230     select_matches (spv, &c[i], include);
231
232   /* Copy selected items into output array. */
233   size_t n_items = 0;
234   struct spv_item **items = xnmalloc (bitvector_count (include, max_items),
235                                       sizeof *items);
236   size_t i = 0;
237   SPV_ITEM_FOR_EACH_SKIP_ROOT (item, spv_get_root (spv))
238     if (bitvector_is_set (include, i++))
239       items[n_items++] = item;
240   *itemsp = items;
241   *n_itemsp = n_items;
242
243   /* Free memory. */
244   free (include);
245 }