Fix problems with uniqueness of short names in system files with very
[pspp] / src / data / short-names.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2007 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/short-names.h>
20
21 #include <data/dictionary.h>
22 #include <data/sys-file-private.h>
23 #include <data/variable.h>
24 #include <libpspp/assertion.h>
25 #include <libpspp/compiler.h>
26 #include <libpspp/hash.h>
27 #include <libpspp/message.h>
28 #include <libpspp/str.h>
29
30 #include "gettext.h"
31 #define _(msgid) gettext (msgid)
32
33 /* Compares two strings. */
34 static int
35 compare_strings (const void *a, const void *b, const void *aux UNUSED)
36 {
37   return strcmp (a, b);
38 }
39
40 /* Hashes a string. */
41 static unsigned
42 hash_string (const void *s, const void *aux UNUSED)
43 {
44   return hsh_hash_string (s);
45 }
46
47 /* Sets V's short name to BASE, followed by a suffix of the form
48    _A, _B, _C, ..., _AA, _AB, etc. according to the value of
49    SUFFIX_NUMBER.  Truncates BASE as necessary to fit. */
50 static void
51 set_var_short_name_suffix (struct variable *v, size_t i,
52                            const char *base, int suffix_number)
53 {
54   char suffix[SHORT_NAME_LEN + 1];
55   char short_name[SHORT_NAME_LEN + 1];
56   char *start, *end;
57   int len, ofs;
58
59   assert (suffix_number >= 0);
60
61   /* Set base name. */
62   var_set_short_name (v, i, base);
63
64   /* Compose suffix. */
65   start = end = suffix + sizeof suffix - 1;
66   *end = '\0';
67   do
68     {
69       *--start = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[suffix_number % 26];
70       if (start <= suffix + 1)
71         msg (SE, _("Variable suffix too large."));
72       suffix_number /= 26;
73     }
74   while (suffix_number > 0);
75   *--start = '_';
76
77   /* Append suffix to V's short name. */
78   str_copy_trunc (short_name, sizeof short_name, base);
79   len = end - start;
80   if (len + strlen (short_name) > SHORT_NAME_LEN)
81     ofs = SHORT_NAME_LEN - len;
82   else
83     ofs = strlen (short_name);
84   strcpy (short_name + ofs, start);
85
86   /* Set name. */
87   var_set_short_name (v, i, short_name);
88 }
89
90 static void
91 claim_short_name (struct variable *v, size_t i, struct hsh_table *short_names)
92 {
93   const char *short_name = var_get_short_name (v, i);
94   if (short_name != NULL
95       && hsh_insert (short_names, (char *) short_name) != NULL)
96     var_set_short_name (v, i, NULL);
97 }
98
99 /* Form initial short_name from the variable name, then try _A,
100    _B, ... _AA, _AB, etc., if needed. */
101 static void
102 assign_short_name (struct variable *v, size_t i, struct hsh_table *short_names)
103 {
104   int trial;
105
106   if (var_get_short_name (v, i) != NULL)
107     return;
108
109   for (trial = 0; ; trial++)
110     {
111       if (trial == 0)
112         var_set_short_name (v, i, var_get_name (v));
113       else
114         set_var_short_name_suffix (v, i, var_get_name (v), trial - 1);
115
116       if (hsh_insert (short_names, (char *) var_get_short_name (v, i)) == NULL)
117         break;
118     }
119 }
120
121 /* Assigns a valid, unique short_name[] to each variable in D.
122    Each variable whose actual name is short has highest priority
123    for that short name.  Otherwise, variables with an existing
124    short_name[] have the next highest priority for a given short
125    name; if it is already taken, then the variable is treated as
126    if short_name[] had been empty.  Otherwise, long names are
127    truncated to form short names.  If that causes conflicts,
128    variables are renamed as PREFIX_A, PREFIX_B, and so on. */
129 void
130 short_names_assign (struct dictionary *d)
131 {
132   size_t var_cnt = dict_get_var_cnt (d);
133   struct hsh_table *short_names;
134   size_t i, j;
135
136   /* Create hash used for detecting conflicts.  The entries in
137      the hash table point to strings owned by dictionary
138      variables, not by us, so we don't need to provide a free
139      function. */
140   short_names = hsh_create (var_cnt, compare_strings, hash_string,
141                             NULL, NULL);
142
143   /* Clear short names that conflict with a variable name. */
144   for (i = 0; i < var_cnt; i++)
145     {
146       struct variable *v = dict_get_var (d, i);
147       int segment_cnt = sfm_width_to_segments (var_get_width (v));
148       for (j = 0; j < segment_cnt; j++)
149         {
150           const char *name = var_get_short_name (v, j);
151           if (name != NULL)
152             {
153               struct variable *ov = dict_lookup_var (d, name);
154               if (ov != NULL && (ov != v || j > 0))
155                 var_set_short_name (v, j, NULL);
156             }
157         }
158     }
159
160   /* Give variables whose names are short the corresponding short
161      name. */
162   for (i = 0; i < var_cnt; i++)
163     {
164       struct variable *v = dict_get_var (d, i);
165       if (strlen (var_get_name (v)) <= SHORT_NAME_LEN)
166         var_set_short_name (v, 0, var_get_name (v));
167     }
168
169   /* Each variable with an assigned short name for its first
170      segment now gets it unless there is a conflict.  In case of
171      conflict, the claimant earlier in dictionary order wins.
172      Then similarly for additional segments of very long
173      strings. */
174   for (i = 0; i < var_cnt; i++)
175     {
176       struct variable *v = dict_get_var (d, i);
177       claim_short_name (v, 0, short_names);
178     }
179   for (i = 0; i < var_cnt; i++)
180     {
181       struct variable *v = dict_get_var (d, i);
182       int segment_cnt = sfm_width_to_segments (var_get_width (v));
183       for (j = 1; j < segment_cnt; j++)
184         claim_short_name (v, i, short_names);
185     }
186
187   /* Assign short names to first segment of remaining variables,
188      then similarly for additional segments. */
189   for (i = 0; i < var_cnt; i++)
190     {
191       struct variable *v = dict_get_var (d, i);
192       assign_short_name (v, 0, short_names);
193     }
194   for (i = 0; i < var_cnt; i++)
195     {
196       struct variable *v = dict_get_var (d, i);
197       int segment_cnt = sfm_width_to_segments (var_get_width (v));
198       for (j = 1; j < segment_cnt; j++)
199         assign_short_name (v, j, short_names);
200     }
201
202   /* Get rid of hash table. */
203   hsh_destroy (short_names);
204 }