52ab7f29bd07a00a110b0d5f349777380f6268ee
[pspp] / src / output / 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 "output/select.h"
20
21 #include <string.h>
22
23 #include "libpspp/assertion.h"
24 #include "libpspp/bit-vector.h"
25 #include "libpspp/message.h"
26
27 #include "gl/c-ctype.h"
28 #include "gl/xalloc.h"
29
30 const char *
31 output_item_class_to_string (enum output_item_class class)
32 {
33   switch (class)
34     {
35 #define OUTPUT_CLASS(ENUM, NAME) case OUTPUT_CLASS_##ENUM: return NAME;
36       OUTPUT_CLASSES
37 #undef OUTPUT_CLASS
38     default: return NULL;
39     }
40 }
41
42 enum output_item_class
43 output_item_class_from_string (const char *name)
44 {
45 #define OUTPUT_CLASS(ENUM, NAME) \
46   if (!strcmp (name, NAME)) return OUTPUT_CLASS_##ENUM;
47   OUTPUT_CLASSES
48 #undef OUTPUT_CLASS
49
50   return (enum output_item_class) OUTPUT_N_CLASSES;
51 }
52
53 enum output_item_class
54 output_item_classify (const struct output_item *item)
55 {
56   const char *label = output_item_get_label (item);
57   if (!label)
58     label = "";
59
60   switch (item->type)
61     {
62     case OUTPUT_ITEM_CHART:
63       return OUTPUT_CLASS_CHARTS;
64
65     case OUTPUT_ITEM_GROUP:
66       return OUTPUT_CLASS_OUTLINEHEADERS;
67
68     case OUTPUT_ITEM_IMAGE:
69       return OUTPUT_CLASS_OTHER;
70
71     case OUTPUT_ITEM_MESSAGE:
72       return (item->message->severity == MSG_S_NOTE
73               ? OUTPUT_CLASS_NOTES
74               : OUTPUT_CLASS_WARNINGS);
75
76     case OUTPUT_ITEM_PAGE_BREAK:
77       return OUTPUT_CLASS_OTHER;
78
79     case OUTPUT_ITEM_PAGE_SETUP:
80       return OUTPUT_CLASS_OTHER;
81
82     case OUTPUT_ITEM_TABLE:
83       return (!strcmp (label, "Warnings") ? OUTPUT_CLASS_WARNINGS
84               : !strcmp (label, "Notes") ? OUTPUT_CLASS_NOTES
85               : OUTPUT_CLASS_TABLES);
86
87     case OUTPUT_ITEM_TEXT:
88       return (!strcmp (label, "Title") ? OUTPUT_CLASS_HEADINGS
89               : !strcmp (label, "Log") ? OUTPUT_CLASS_LOGS
90               : !strcmp (label, "Page Title") ? OUTPUT_CLASS_PAGETITLE
91               : OUTPUT_CLASS_TEXTS);
92
93     default:
94       return OUTPUT_CLASS_UNKNOWN;
95     }
96 }
97 \f
98 static bool
99 string_matches (const char *pattern, const char *s)
100 {
101   /* XXX This should be a Unicode case insensitive comparison. */
102   while (c_tolower (*pattern) == c_tolower (*s))
103     {
104       if (*pattern == '\0')
105         return true;
106
107       pattern++;
108       s++;
109     }
110
111   return pattern[0] == '*' && pattern[1] == '\0';
112 }
113
114 static int
115 string_array_matches (const char *name, const struct string_array *array)
116 {
117   if (!array->n)
118     return -1;
119   else if (!name)
120     return false;
121
122   for (size_t i = 0; i < array->n; i++)
123     if (string_matches (array->strings[i], name))
124       return true;
125
126   return false;
127 }
128
129 static bool
130 match (const char *name,
131        const struct string_array *white,
132        const struct string_array *black)
133 {
134   return (string_array_matches (name, white) != false
135           && string_array_matches (name, black) != true);
136 }
137
138 static int
139 match_instance (const int *instances, size_t n_instances,
140                 int instance_within_command)
141 {
142   int retval = false;
143   for (size_t i = 0; i < n_instances; i++)
144     {
145       if (instances[i] == instance_within_command)
146         return true;
147       else if (instances[i] == -1)
148         retval = -1;
149     }
150   return retval;
151 }
152
153 static bool
154 match_command (size_t nth_command, size_t *commands, size_t n_commands)
155 {
156   for (size_t i = 0; i < n_commands; i++)
157     if (nth_command == commands[i])
158       return true;
159   return false;
160 }
161
162 static void
163 select_matches (const struct output_item **items,
164                 unsigned int *depths, size_t n_items,
165                 const struct output_criteria *c, unsigned long int *include)
166 {
167   /* Counting instances within a command. */
168   int instance_within_command = 0;
169   int last_instance = -1;
170
171   /* Counting commands. */
172   const struct output_item *command_item = NULL;
173   const struct output_item *command_command_item = NULL;
174   size_t nth_command = 0;
175
176   for (size_t i = 0; i < n_items; i++)
177     {
178       const struct output_item *item = items[i];
179
180       if (depths[i] == 0)
181         {
182           command_item = item;
183           if (last_instance >= 0)
184             {
185               bitvector_set1 (include, last_instance);
186               last_instance = -1;
187             }
188           instance_within_command = 0;
189         }
190
191       if (!((1u << output_item_classify (item)) & c->classes))
192         continue;
193
194       if (!c->include_hidden && item->type != OUTPUT_ITEM_GROUP && !item->show)
195         continue;
196
197       if (c->error && (!item->spv_info || !item->spv_info->error))
198         continue;
199
200       if (!match (item->command_name,
201                   &c->include.commands, &c->exclude.commands))
202         continue;
203
204       if (c->n_commands)
205         {
206           if (command_item != command_command_item)
207             {
208               command_command_item = command_item;
209               nth_command++;
210             }
211
212           if (!match_command (nth_command, c->commands, c->n_commands))
213             continue;
214         }
215
216       char *subtype = output_item_get_subtype (item);
217       bool match_subtype = match (subtype,
218                                   &c->include.subtypes, &c->exclude.subtypes);
219       if (!match_subtype)
220         continue;
221
222       if (!match (output_item_get_label (item),
223                   &c->include.labels, &c->exclude.labels))
224         continue;
225
226       if (c->members.n)
227         {
228           const char *members[4];
229           size_t n = spv_info_get_members (item->spv_info, members,
230                                            sizeof members / sizeof *members);
231
232           bool found = false;
233           for (size_t j = 0; j < n; j++)
234             if (string_array_matches (members[j], &c->members) == true)
235               {
236                 found = true;
237                 break;
238               }
239           if (!found)
240             continue;
241         }
242
243       if (c->n_instances)
244         {
245           if (!depths[i])
246             continue;
247           instance_within_command++;
248
249           int include_instance = match_instance (c->instances, c->n_instances,
250                                                  instance_within_command);
251           if (!include_instance)
252             continue;
253           else if (include_instance < 0)
254             {
255               last_instance = i;
256               continue;
257             }
258         }
259
260       bitvector_set1 (include, i);
261     }
262
263   if (last_instance >= 0)
264     bitvector_set1 (include, last_instance);
265 }
266
267 static size_t
268 count_items (const struct output_item *item)
269 {
270   size_t n = 1;
271   if (item->type == OUTPUT_ITEM_GROUP)
272     for (size_t i = 0; i < item->group.n_children; i++)
273       n += count_items (item->group.children[i]);
274   return n;
275 }
276
277 static size_t
278 flatten_items (const struct output_item *item, size_t index, size_t depth,
279                const struct output_item **items, unsigned int *depths)
280 {
281   items[index] = item;
282   depths[index] = depth;
283   index++;
284
285   if (item->type == OUTPUT_ITEM_GROUP)
286     for (size_t i = 0; i < item->group.n_children; i++)
287       index = flatten_items (item->group.children[i], index, depth + 1,
288                              items, depths);
289
290   return index;
291 }
292
293 static size_t
294 unflatten_items (const struct output_item *in, size_t index,
295                  unsigned long *include, struct output_item *out)
296 {
297   bool include_item = bitvector_is_set (include, index++);
298   if (in->type == OUTPUT_ITEM_GROUP)
299     {
300       /* If we should include the group itself, then clone IN inside OUT, and
301          add any children to the clone instead to OUT directly. */
302       if (include_item)
303         {
304           struct output_item *group = group_item_clone_empty (in);
305           group_item_add_child (out, group);
306           out = group;
307         }
308
309       for (size_t i = 0; i < in->group.n_children; i++)
310         index = unflatten_items (in->group.children[i], index, include, out);
311     }
312   else
313     {
314       if (include_item)
315         group_item_add_child (out, output_item_ref (in));
316     }
317   return index;
318 }
319
320 /* Consumes IN, which must be a group, and returns a new output item whose
321    children are all the (direct and indirect) children of IN that meet the NC
322    criteria in C[]. */
323 struct output_item *
324 output_select (struct output_item *in,
325                const struct output_criteria c[], size_t nc)
326 {
327   assert (in->type == OUTPUT_ITEM_GROUP);
328   if (!nc)
329     return in;
330
331   /* Number of items (not counting the root). */
332   size_t n_items = count_items (in) - 1;
333
334   const struct output_item **items = xnmalloc (n_items, sizeof *items);
335   unsigned int *depths = xnmalloc (n_items, sizeof *depths);
336   size_t n_flattened = 0;
337   for (size_t i = 0; i < in->group.n_children; i++)
338     n_flattened = flatten_items (in->group.children[i], n_flattened,
339                                  0, items, depths);
340   assert (n_flattened == n_items);
341
342   unsigned long int *include = bitvector_allocate (n_items);
343   for (size_t i = 0; i < nc; i++)
344     select_matches (items, depths, n_items, &c[i], include);
345
346   struct output_item *out = root_item_create ();
347   size_t n_unflattened = 0;
348   for (size_t i = 0; i < in->group.n_children; i++)
349     n_unflattened = unflatten_items (in->group.children[i], n_unflattened,
350                                      include, out);
351   assert (n_unflattened == n_items);
352
353   free (items);
354   free (depths);
355   free (include);
356
357   output_item_unref (in);
358   return out;
359 }