1 /*
2 Teem: Tools to process and visualize scientific data and images .
3 Copyright (C) 2012, 2011, 2010, 2009 University of Chicago
4 Copyright (C) 2008, 2007, 2006, 2005 Gordon Kindlmann
5 Copyright (C) 2004, 2003, 2002, 2001, 2000, 1999, 1998 University of Utah
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Lesser General Public License
9 (LGPL) as published by the Free Software Foundation; either
10 version 2.1 of the License, or (at your option) any later version.
11 The terms of redistributing and/or modifying this software also
12 include exceptions to the LGPL that facilitate static linking.
13
14 This library is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
18
19 You should have received a copy of the GNU Lesser General Public License
20 along with this library; if not, write to Free Software Foundation, Inc.,
21 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 */
23
24 #include "air.h"
25
26 /*
27 ** Until Teem has its own printf implementation, this will have to do;
28 ** it is imperfect because these are not functionally identical.
29 */
30 #if defined(WIN32) || defined(_WIN32)
31 # define snprintf _snprintf
32 #endif
33
34 /*
35 ******** airEnumUnknown
36 **
37 ** return the value representing "unknown" in an enum
38 */
39 int
airEnumUnknown(const airEnum * enm)40 airEnumUnknown(const airEnum *enm) {
41
42 if (enm && enm->val) {
43 return enm->val[0];
44 } else {
45 return 0;
46 }
47 }
48
49 /*
50 ** _airEnumIndex()
51 **
52 ** given an enum "enm" and value "val", return the index into enm->str[]
53 ** and enm->desc[] which correspond to that value. To be safe, when
54 ** given an invalid enum value, we return zero.
55 */
56 static unsigned int
_airEnumIndex(const airEnum * enm,int val)57 _airEnumIndex(const airEnum *enm, int val) {
58 unsigned int ii, ret;
59
60 ret = 0;
61 if (enm->val) {
62 for (ii=1; ii<=enm->M; ii++) {
63 if (val == enm->val[ii]) {
64 ret = ii;
65 break;
66 }
67 }
68 } else {
69 unsigned int uval;
70 uval = AIR_UINT(val);
71 ret = (0 <= val && uval <= enm->M) ? uval : 0;
72 }
73 return ret;
74 }
75
76 /*
77 ** returns non-zero if there is an error: given "val" is *not*
78 ** a valid value of the airEnum "enm"
79 */
80 int
airEnumValCheck(const airEnum * enm,int val)81 airEnumValCheck(const airEnum *enm, int val) {
82
83 return (0 == _airEnumIndex(enm, val));
84 }
85
86 const char *
airEnumStr(const airEnum * enm,int val)87 airEnumStr(const airEnum *enm, int val) {
88 unsigned int idx;
89
90 idx = _airEnumIndex(enm, val);
91 return enm->str[idx];
92 }
93
94 const char *
airEnumDesc(const airEnum * enm,int val)95 airEnumDesc(const airEnum *enm, int val) {
96 unsigned int idx;
97
98 idx = _airEnumIndex(enm, val);
99 return enm->desc[idx];
100 }
101
102 int
airEnumVal(const airEnum * enm,const char * str)103 airEnumVal(const airEnum *enm, const char *str) {
104 char *strCpy, test[AIR_STRLEN_SMALL];
105 unsigned int ii;
106
107 if (!str) {
108 return airEnumUnknown(enm);
109 }
110
111 strCpy = airStrdup(str);
112 if (!enm->sense) {
113 airToLower(strCpy);
114 }
115
116 if (enm->strEqv) {
117 /* want strlen and not airStrlen here because the strEqv array
118 should be terminated by a non-null empty string */
119 for (ii=0; strlen(enm->strEqv[ii]); ii++) {
120 airStrcpy(test, AIR_STRLEN_SMALL, enm->strEqv[ii]);
121 if (!enm->sense) {
122 airToLower(test);
123 }
124 if (!strcmp(test, strCpy)) {
125 free(strCpy);
126 return enm->valEqv[ii];
127 }
128 }
129 } else {
130 /* enm->strEqv NULL */
131 for (ii=1; ii<=enm->M; ii++) {
132 airStrcpy(test, AIR_STRLEN_SMALL, enm->str[ii]);
133 if (!enm->sense) {
134 airToLower(test);
135 }
136 if (!strcmp(test, strCpy)) {
137 free(strCpy);
138 return enm->val ? enm->val[ii] : (int)ii; /* HEY scrutinize cast */
139 }
140 }
141 }
142
143 /* else we never matched a string */
144 free(strCpy);
145 return airEnumUnknown(enm);
146 }
147
148 /*
149 ******** airEnumFmtDesc()
150 **
151 ** Formats a description line for one element "val" of airEnum "enm",
152 ** and puts the result in a NEWLY ALLOCATED string which is the return
153 ** of this function. The formatting is done via sprintf(), as governed
154 ** by "fmt", which should contain to "%s" conversion sequences, the
155 ** first for the string version "val", and the second for the
156 ** description If "canon", then the canonical string representation
157 ** will be used (the one in enm->str[]), otherwise the shortest string
158 ** representation will be used (which differs from the canonical one
159 ** when there is a strEqv[]/valEqv[] pair defining a shorter string)
160 */
161 char *
airEnumFmtDesc(const airEnum * enm,int val,int canon,const char * fmt)162 airEnumFmtDesc(const airEnum *enm, int val, int canon, const char *fmt) {
163 const char *desc;
164 char *buff, ident[AIR_STRLEN_SMALL];
165 const char *_ident;
166 int i;
167 size_t len;
168
169 if (!(enm && enm->desc && fmt)) {
170 return airStrdup("(airEnumDesc: invalid args)");
171 }
172 if (airEnumValCheck(enm, val)) {
173 val = airEnumUnknown(enm);
174 }
175 _ident = airEnumStr(enm, val);
176 if (!canon && enm->strEqv) {
177 len = airStrlen(_ident);
178 for (i=0; airStrlen(enm->strEqv[i]); i++) {
179 if (val != enm->valEqv[i]) {
180 /* this isn't a string representing the value we care about */
181 continue;
182 }
183 if (airStrlen(enm->strEqv[i]) < len) {
184 /* this one is shorter */
185 len = airStrlen(enm->strEqv[i]);
186 _ident = enm->strEqv[i];
187 }
188 }
189 }
190 airStrcpy(ident, AIR_STRLEN_SMALL, _ident);
191 if (!enm->sense) {
192 airToLower(ident);
193 }
194 desc = enm->desc[_airEnumIndex(enm, val)];
195 buff = AIR_CALLOC(airStrlen(fmt) + airStrlen(ident) +
196 airStrlen(desc) + 1, char);
197 if (buff) {
198 sprintf(buff, fmt, ident, desc);
199 }
200 return buff;
201 }
202
203 static void
_enumPrintVal(FILE * file,const airEnum * enm,int ii)204 _enumPrintVal(FILE *file, const airEnum *enm, int ii) {
205
206 if (enm->desc) {
207 fprintf(file, "desc: %s\n", enm->desc[ii]);
208 }
209 if (enm->strEqv) {
210 unsigned int jj;
211 fprintf(file, "eqv:"); fflush(file);
212 jj = 0;
213 while (airStrlen(enm->strEqv[jj])) {
214 if (enm->valEqv[jj] == (enm->val
215 ? enm->val[ii]
216 : ii)) {
217 fprintf(file, " \"%s\"", enm->strEqv[jj]);
218 }
219 jj++;
220 }
221 fprintf(file, "\n");
222 }
223 }
224
225 void
airEnumPrint(FILE * file,const airEnum * enm)226 airEnumPrint(FILE *file, const airEnum *enm) {
227 int ii; /* this should arguable be unsigned int, but
228 airEnum values were kept as "int", even after
229 the great unsigned conversion */
230
231 if (!(file && enm)) {
232 return;
233 }
234
235 if (airStrlen(enm->name)) {
236 fprintf(file, "airEnum \"%s\":\n", enm->name);
237 } else {
238 fprintf(file, "airEnum (NO NAME!):\n");
239 }
240 fprintf(file, "(%s case sensitive)\n", (enm->sense ? "yes, is" : "is not"));
241 if (enm->val) {
242 fprintf(file, "Values (%u valid) given explicitly\n", enm->M);
243 fprintf(file, "--- (0) %d: \"%s\"\n", enm->val[0], enm->str[0]);
244 for (ii=1; ii<=AIR_CAST(int, enm->M); ii++) {
245 fprintf(file, "--- (%d) %d: \"%s\" == \"%s\"\n", ii,
246 enm->val[ii], enm->str[ii],
247 airEnumStr(enm, enm->val[ii]));
248 _enumPrintVal(file, enm, ii);
249 }
250 } else {
251 /* enm->val NULL */
252 fprintf(file, "Values implicit; [1,%u] valid\n", enm->M);
253 fprintf(file, "--- 0: \"%s\"\n", enm->str[0]);
254 for (ii=1; ii<=AIR_CAST(int, enm->M); ii++) {
255 fprintf(file, "--- %d: %s == %s\n", ii, enm->str[ii],
256 airEnumStr(enm, ii));
257 _enumPrintVal(file, enm, ii);
258 }
259 }
260 return;
261 }
262
263 /* ---- BEGIN non-NrrdIO */
264 /*
265 ******** airEnumCheck
266 **
267 ** tries various things to check on the construction and internal
268 ** consistency of an airEnum; returns 1 if there is a problem, and 0
269 ** if all is well. we're in air, so there's no biff, but we sprintf a
270 ** description of the error into "err", if given
271 **
272 ** The requirement that the strings have strlen <= AIR_STRLEN_SMALL-1
273 ** is a reflection of the cheap implementation of the airEnum
274 ** functions in this file, rather than an actual restriction on what an
275 ** airEnum could be.
276 */
277 int
airEnumCheck(char err[AIR_STRLEN_LARGE],const airEnum * enm)278 airEnumCheck(char err[AIR_STRLEN_LARGE], const airEnum *enm) {
279 static const char me[]="airEnumCheck";
280 unsigned int ii, jj;
281 size_t slen, ASL;
282
283 ASL = AIR_STRLEN_LARGE;
284 if (!enm) {
285 if (err) {
286 snprintf(err, ASL, "%s: got NULL enm", me);
287 }
288 return 1;
289 }
290 if (!enm->name) {
291 if (err) {
292 snprintf(err, ASL, "%s: enm->name NULL", me);
293 }
294 return 1;
295 }
296 if (0 == enm->M) {
297 if (err) {
298 snprintf(err, ASL, "%s(%s): enm->M zero; no values in enum",
299 me, enm->name);
300 }
301 return 1;
302 }
303 for (ii=0; ii<=enm->M; ii++) {
304 if (!enm->str[ii]) {
305 if (err) {
306 snprintf(err, ASL, "%s(%s): enm->str[%u] NULL", me, enm->name, ii);
307 }
308 return 1;
309 }
310 slen = airStrlen(enm->str[ii]);
311 if (!( slen >= 1 && slen <= AIR_STRLEN_SMALL-1 )) {
312 if (err) {
313 char stmp[AIR_STRLEN_SMALL];
314 snprintf(err, ASL, "%s(%s): strlen(enm->str[%u] \"%s\") "
315 "%s not in range [1,%u]", me,
316 enm->name, ii, enm->str[ii],
317 airSprintSize_t(stmp, slen), AIR_STRLEN_SMALL-1);
318 }
319 return 1;
320 }
321 /* make sure there are no duplicates among the strings,
322 including remapping the case in case of case insensitivity */
323 for (jj=ii+1; jj<=enm->M; jj++) {
324 if (!strcmp(enm->str[ii], enm->str[jj])) {
325 if (err) {
326 snprintf(err, ASL, "%s(%s): str[%d] and [%u] both \"%s\"",
327 me, enm->name, ii, jj, enm->str[jj]);
328 }
329 return 1;
330 }
331 if (!enm->sense) {
332 char bb1[AIR_STRLEN_SMALL], bb2[AIR_STRLEN_SMALL];
333 strcpy(bb1, enm->str[ii]);
334 airToLower(bb1);
335 strcpy(bb2, enm->str[jj]);
336 airToLower(bb2);
337 if (!strcmp(bb1, bb2)) {
338 if (err) {
339 snprintf(err, ASL, "%s(%s): after case-lowering, "
340 "str[%d] and [%u] both \"%s\"",
341 me, enm->name, ii, jj, bb1);
342 }
343 return 1;
344 }
345 }
346 }
347 }
348 if (enm->val) {
349 for (ii=1; ii<=enm->M; ii++) {
350 if (enm->val[0] == enm->val[ii]) {
351 if (err) {
352 snprintf(err, ASL, "%s(%s): val[%u] %u same as "
353 "unknown/invalid val[0] %u",
354 me, enm->name, ii, enm->val[ii], enm->val[0]);
355 }
356 return 1;
357 }
358 for (jj=ii+1; jj<=enm->M; jj++) {
359 if (enm->val[jj] == enm->val[ii]) {
360 if (err) {
361 snprintf(err, ASL, "%s(%s): val[%u] %u same as val[%u] %u", me,
362 enm->name, ii, enm->val[ii], jj, enm->val[jj]);
363 }
364 return 1;
365 }
366 }
367 }
368 }
369 if (enm->desc) {
370 for (ii=0; ii<=enm->M; ii++) {
371 if (!enm->desc[ii]) {
372 if (err) {
373 snprintf(err, ASL, "%s(%s): enm->desc[%u] NULL", me, enm->name, ii);
374 }
375 return 1;
376 }
377 /* we don't really care about slen, but learning it will
378 hopefully produce some memory error if the array is not valid */
379 slen = airStrlen(enm->desc[ii]);
380 if (!( slen > 0 )) {
381 if (err) {
382 snprintf(err, ASL, "%s(%s): enm->desc[%u] empty", me, enm->name, ii);
383 }
384 return 1;
385 }
386 }
387 }
388 if (enm->strEqv) {
389 /* the strEqv array is one of the easiest ways to mess up an
390 airEnum definition; it deserves these tests and maybe more */
391 if (!enm->valEqv) {
392 if (err) {
393 snprintf(err, ASL, "%s(%s): non-NULL strEqv but NULL valEqv",
394 me, enm->name);
395 }
396 return 1;
397 }
398 if (!( airStrlen(enm->strEqv[0]) )) {
399 if (err) {
400 snprintf(err, ASL, "%s(%s): strEqv[0] is NULL or empty",
401 me, enm->name);
402 }
403 return 1;
404 }
405 /* check length and see if any string maps to an invalid value */
406 for (ii=0; (slen = strlen(enm->strEqv[ii])); ii++) {
407 if (!( slen <= AIR_STRLEN_SMALL-1 )) {
408 if (err) {
409 char stmp[AIR_STRLEN_SMALL];
410 snprintf(err, ASL, "%s(%s): strlen(enm->strEqv[%u] \"%s\") "
411 "%s not <= %u", me, enm->name, ii, enm->strEqv[ii],
412 airSprintSize_t(stmp, slen), AIR_STRLEN_SMALL-1);
413 }
414 return 1;
415 }
416 /* see if a string maps to an invalid value */
417 if (airEnumValCheck(enm, enm->valEqv[ii])) {
418 if (err) {
419 snprintf(err, ASL, "%s(%s): valEqv[%u] %u (with strEqv[%u] \"%s\")"
420 " not valid",
421 me, enm->name, ii, enm->valEqv[ii], ii, enm->strEqv[ii]);
422 }
423 return 1;
424 }
425 }
426 /* make sure eqv strings contain the canonical string */
427 for (ii=1; ii<=enm->M; ii++) {
428 int eval, rval;
429 eval = (enm->val ? enm->val[ii] : AIR_CAST(int, ii));
430 rval = airEnumVal(enm, enm->str[ii]);
431 if (eval != rval) {
432 if (err) {
433 snprintf(err, ASL, "%s(%s): ii=%u->eval=%d->str=\"%s\"->%d != %d "
434 "(i.e. canonical string didn't map to its own value)",
435 me, enm->name, ii, eval, enm->str[ii], rval, eval);
436 }
437 return 1;
438 }
439 }
440 /* make sure there are no duplicates among the strEqv,
441 including remapping the case in case of case insensitivity */
442 for (ii=0; strlen(enm->strEqv[ii]); ii++) {
443 for (jj=ii+1; strlen(enm->strEqv[jj]); jj++) {
444 if (!strcmp(enm->strEqv[ii], enm->strEqv[jj])) {
445 if (err) {
446 snprintf(err, ASL, "%s(%s): strEqv[%d] and [%u] both \"%s\"",
447 me, enm->name, ii, jj, enm->strEqv[jj]);
448 }
449 return 1;
450 }
451 if (!enm->sense) {
452 char bb1[AIR_STRLEN_SMALL], bb2[AIR_STRLEN_SMALL];
453 strcpy(bb1, enm->strEqv[ii]);
454 airToLower(bb1);
455 strcpy(bb2, enm->strEqv[jj]);
456 airToLower(bb2);
457 if (!strcmp(bb1, bb2)) {
458 if (err) {
459 snprintf(err, ASL, "%s(%s): after case-lowering, "
460 "strEqv[%d] and [%u] both \"%s\"",
461 me, enm->name, ii, jj, bb1);
462 }
463 return 1;
464 }
465 }
466 }
467 }
468 }
469 return 0;
470 }
471 /* ---- END non-NrrdIO */
472 /* this is the end */
473