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