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