better tests
[pspp] / src / data / value-labels.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-9, 2000, 2009, 2010, 2011, 2012 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 "data/value-labels.h"
20
21 #include <stdlib.h>
22
23 #include "data/data-out.h"
24 #include "data/value.h"
25 #include "data/variable.h"
26 #include "libpspp/array.h"
27 #include "libpspp/cast.h"
28 #include "libpspp/compiler.h"
29 #include "libpspp/hash-functions.h"
30 #include "libpspp/hmap.h"
31 #include "libpspp/intern.h"
32 #include "libpspp/message.h"
33 #include "libpspp/str.h"
34
35 #include "gl/xalloc.h"
36
37 /* Creates and returns a new, empty set of value labels with the
38    given WIDTH. */
39 struct val_labs *
40 val_labs_create (int width)
41 {
42   struct val_labs *vls = xmalloc (sizeof *vls);
43   vls->width = width;
44   hmap_init (&vls->labels);
45   return vls;
46 }
47
48 /* Creates and returns a new set of value labels identical to
49    VLS.  Returns a null pointer if VLS is null. */
50 struct val_labs *
51 val_labs_clone (const struct val_labs *vls)
52 {
53   struct val_labs *copy;
54   struct val_lab *label;
55
56   if (vls == NULL)
57     return NULL;
58
59   copy = val_labs_create (vls->width);
60   HMAP_FOR_EACH (label, struct val_lab, node, &vls->labels)
61     val_labs_add (copy, &label->value, label->escaped_label);
62   return copy;
63 }
64
65 /* Determines whether VLS's width can be changed to NEW_WIDTH,
66    using the rules checked by value_is_resizable. */
67 bool
68 val_labs_can_set_width (const struct val_labs *vls, int new_width)
69 {
70   struct val_lab *label;
71
72   HMAP_FOR_EACH (label, struct val_lab, node, &vls->labels)
73     if (!value_is_resizable (&label->value, vls->width, new_width))
74       return false;
75
76   return true;
77 }
78
79 /* Changes the width of VLS to NEW_WIDTH.  The original and new
80    width must be both numeric or both string. */
81 void
82 val_labs_set_width (struct val_labs *vls, int new_width)
83 {
84   assert (val_labs_can_set_width (vls, new_width));
85   if (value_needs_resize (vls->width, new_width))
86     {
87       struct val_lab *label;
88       HMAP_FOR_EACH (label, struct val_lab, node, &vls->labels)
89         value_resize (&label->value, vls->width, new_width);
90     }
91   vls->width = new_width;
92 }
93
94 /* Destroys VLS. */
95 void
96 val_labs_destroy (struct val_labs *vls)
97 {
98   if (vls != NULL)
99     {
100       val_labs_clear (vls);
101       hmap_destroy (&vls->labels);
102       free (vls);
103     }
104 }
105
106 /* Removes all the value labels from VLS. */
107 void
108 val_labs_clear (struct val_labs *vls)
109 {
110   struct val_lab *label, *next;
111
112   HMAP_FOR_EACH_SAFE (label, next, struct val_lab, node, &vls->labels)
113     {
114       hmap_delete (&vls->labels, &label->node);
115       value_destroy (&label->value, vls->width);
116       intern_unref (label->label);
117       intern_unref (label->escaped_label);
118       free (label);
119     }
120 }
121
122 /* Returns the width of VLS. */
123 int
124 val_labs_get_width (const struct val_labs *vls)
125 {
126   return vls->width;
127 }
128
129 /* Returns the number of value labels in VLS.
130    Returns 0 if VLS is null. */
131 size_t
132 val_labs_count (const struct val_labs *vls)
133 {
134   return vls == NULL ? 0 : hmap_count (&vls->labels);
135 }
136 \f
137 static void
138 set_label (struct val_lab *lab, const char *escaped_label)
139 {
140   lab->escaped_label = intern_new (escaped_label);
141   if (strstr (escaped_label, "\\n") == NULL)
142     lab->label = intern_ref (lab->escaped_label);
143   else
144     {
145       struct string s;
146       const char *p;
147
148       ds_init_empty (&s);
149       ds_extend (&s, intern_strlen (lab->escaped_label));
150       for (p = escaped_label; *p != '\0'; p++)
151         {
152           char c = *p;
153           if (c == '\\' && p[1] == 'n')
154             {
155               c = '\n';
156               p++;
157             }
158           ds_put_byte (&s, c);
159         }
160       lab->label = intern_new (ds_cstr (&s));
161       ds_destroy (&s);
162     }
163 }
164
165 static void
166 do_add_val_lab (struct val_labs *vls, const union value *value,
167                 const char *escaped_label)
168 {
169   struct val_lab *lab = xmalloc (sizeof *lab);
170   value_clone (&lab->value, value, vls->width);
171   set_label (lab, escaped_label);
172   hmap_insert (&vls->labels, &lab->node, value_hash (value, vls->width, 0));
173 }
174
175 /* If VLS does not already contain a value label for VALUE, adds the UTF-8
176    encoded LABEL for it and returns true.  Otherwise, returns false.
177
178    In LABEL, the two-byte sequence "\\n" is interpreted as a new-line. */
179 bool
180 val_labs_add (struct val_labs *vls, const union value *value,
181               const char *label)
182 {
183   const struct val_lab *lab = val_labs_lookup (vls, value);
184   if (lab == NULL)
185     {
186       do_add_val_lab (vls, value, label);
187       return true;
188     }
189   else
190     return false;
191 }
192
193 /* Sets LABEL as the value label for VALUE in VLS, replacing any
194    existing label for VALUE.
195
196    In LABEL, the two-byte sequence "\\n" is interpreted as a new-line. */
197 void
198 val_labs_replace (struct val_labs *vls, const union value *value,
199                   const char *label)
200 {
201   struct val_lab *vl = val_labs_lookup (vls, value);
202   if (vl != NULL)
203     {
204       intern_unref (vl->label);
205       intern_unref (vl->escaped_label);
206       set_label (vl, label);
207     }
208   else
209     do_add_val_lab (vls, value, label);
210 }
211
212 /* Removes LABEL from VLS. */
213 void
214 val_labs_remove (struct val_labs *vls, struct val_lab *label)
215 {
216   hmap_delete (&vls->labels, &label->node);
217   value_destroy (&label->value, vls->width);
218   intern_unref (label->label);
219   intern_unref (label->escaped_label);
220   free (label);
221 }
222
223 /* Searches VLS for a value label for VALUE.  If successful, returns the string
224    used as the label, as a UTF-8 encoded string in a format suitable for
225    output.  Otherwise, returns a null pointer.  Returns a null pointer if VLS
226    is null. */
227 const char *
228 val_labs_find (const struct val_labs *vls, const union value *value)
229 {
230   const struct val_lab *label = val_labs_lookup (vls, value);
231   return label ? label->label : NULL;
232 }
233
234 /* Searches VLS for a value label for VALUE.  If successful,
235    returns the value label; otherwise, returns a null pointer.
236    Returns a null pointer if VLS is null. */
237 static struct val_lab *
238 val_labs_lookup__ (const struct val_labs *vls, const union value *value,
239                    unsigned int hash)
240 {
241   struct val_lab *label;
242
243   HMAP_FOR_EACH_WITH_HASH (label, struct val_lab, node, hash, &vls->labels)
244     if (value_equal (&label->value, value, vls->width))
245       return label;
246
247   return NULL;
248 }
249
250 /* Searches VLS for a value label for VALUE.  If successful,
251    returns the value label; otherwise, returns a null pointer.
252    Returns a null pointer if VLS is null. */
253 struct val_lab *
254 val_labs_lookup (const struct val_labs *vls, const union value *value)
255 {
256   return (vls == NULL ? NULL
257           : val_labs_lookup__ (vls, value, value_hash (value, vls->width, 0)));
258 }
259
260 /* Searches VLS for a value label whose label is exactly LABEL.  If successful,
261    returns the corresponding value.  Otherwise, returns a null pointer.
262
263    Returns a null pointer if VLS is null.
264
265    This function is O(n) in the number of labels in VLS. */
266 const union value *
267 val_labs_find_value (const struct val_labs *vls, const char *label_)
268 {
269   const union value *value = NULL;
270
271   if (vls != NULL)
272     {
273       const struct val_lab *vl;
274       const char *label;
275
276       label = intern_new (label_);
277       HMAP_FOR_EACH (vl, struct val_lab, node, &vls->labels)
278         if (vl->label == label)
279           {
280             value = &vl->value;
281             break;
282           }
283       intern_unref (label);
284     }
285
286   return value;
287 }
288 \f
289 /* Returns the first value label in VLS, in arbitrary order, or a
290    null pointer if VLS is empty or if VLS is a null pointer.  If
291    the return value is non-null, then val_labs_next() may be used
292    to continue iterating. */
293 const struct val_lab *
294 val_labs_first (const struct val_labs *vls)
295 {
296   return vls ? HMAP_FIRST (struct val_lab, node, &vls->labels) : NULL;
297 }
298
299 /* Returns the next value label in an iteration begun by
300    val_labs_first().  If the return value is non-null, then
301    val_labs_next() may be used to continue iterating. */
302 const struct val_lab *
303 val_labs_next (const struct val_labs *vls, const struct val_lab *label)
304 {
305   return HMAP_NEXT (label, struct val_lab, node, &vls->labels);
306 }
307
308 static int
309 compare_labels_by_value_3way (const void *a_, const void *b_, const void *vls_)
310 {
311   const struct val_lab *const *a = a_;
312   const struct val_lab *const *b = b_;
313   const struct val_labs *vls = vls_;
314   return value_compare_3way (&(*a)->value, &(*b)->value, vls->width);
315 }
316
317 /* Allocates and returns an array of pointers to value labels
318    that is sorted in increasing order by value.  The array has
319    val_labs_count(VLS) elements.  The caller is responsible for
320    freeing the array. */
321 const struct val_lab **
322 val_labs_sorted (const struct val_labs *vls)
323 {
324   if (vls != NULL)
325     {
326       const struct val_lab *label;
327       const struct val_lab **labels;
328       size_t i;
329
330       labels = xmalloc (val_labs_count (vls) * sizeof *labels);
331       i = 0;
332       HMAP_FOR_EACH (label, struct val_lab, node, &vls->labels)
333         labels[i++] = label;
334       assert (i == val_labs_count (vls));
335       sort (labels, val_labs_count (vls), sizeof *labels,
336             compare_labels_by_value_3way, vls);
337       return labels;
338     }
339   else
340     return NULL;
341 }
342
343 /* Returns a hash value that represents all of the labels in VLS, starting from
344    BASIS. */
345 unsigned int
346 val_labs_hash (const struct val_labs *vls, unsigned int basis)
347 {
348   const struct val_lab *label;
349   unsigned int hash;
350
351   hash = hash_int (val_labs_count (vls), basis);
352   HMAP_FOR_EACH (label, struct val_lab, node, &vls->labels)
353     hash ^= value_hash (&label->value, vls->width,
354                         hash_string (label->label, basis));
355   return hash;
356 }
357
358 /* Returns true if A and B contain the same values with the same labels,
359    false if they differ in some way. */
360 bool
361 val_labs_equal (const struct val_labs *a, const struct val_labs *b)
362 {
363   const struct val_lab *label;
364
365   if (val_labs_count (a) != val_labs_count (b))
366     return false;
367
368   if (a == NULL || b == NULL)
369     return true;
370
371   if (a->width != b->width)
372     return false;
373
374   HMAP_FOR_EACH (label, struct val_lab, node, &a->labels)
375     {
376       struct val_lab *label2 = val_labs_lookup__ (b, &label->value,
377                                                   label->node.hash);
378       if (!label2 || label->label != label2->label)
379         return false;
380     }
381
382   return true;
383 }