* Not ideal, but it is functional.
[scdataviz] / matdb-dotcode.c
1 /*
2  * matdb-dotcode: reads in Craig's Dotcode materials database.
3
4
5
6  Copyright (C) 2008 Joseph Pingenot
7
8  This program is free software: you can redistribute it and/or modify
9  it under the terms of the GNU Affero General Public License as published by
10  the Free Software Foundation, either version 3 of the License, or
11  (at your option) any later version.
12
13  This program is distributed in the hope that it will be useful,
14  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  GNU Affero General Public License for more details.
17
18  You should have received a copy of the GNU Affero General Public License
19  along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
21 */
22
23 #include <matdb-dotcode.h>
24 #define _GNU_SOURCE
25 #include <stdio.h>
26 #include <strings.h>
27 #include <stdlib.h>
28
29 #ifdef DEBUG
30 #undef DEBUG
31 #endif
32 #ifdef DEBUG_2
33 #undef DEBUG_2
34 #endif
35
36 /*Functions to be used by the hash.*/
37 static void destroy_string(gpointer data) {
38   free((char*)data);
39 }
40 static void destroy_double(gpointer data){
41   free((double*)data);
42 }
43 static void destroy_inner_bowhash(gpointer data) {
44   g_hash_table_unref(data);
45 }
46
47 int insert_into_matdb(struct matdb *mdb, struct matdb_material **mat, struct matdb_bowing **bow) {
48   if((*mat) != NULL) {
49 #ifdef DEBUG
50     fprintf(stderr, "inserting material %s\n", (*mat)->name->str);
51 #endif
52     g_hash_table_insert(mdb->materials, (gpointer)g_strdup((*mat)->name->str), (gpointer)(*mat));
53     (*mat) = NULL;
54   }
55   if((*bow) != NULL) {
56 #ifdef DEBUG
57     fprintf(stderr, "inserting bowing %s:%s\n", (*bow)->from->str, (*bow)->to->str);
58 #endif
59     /*Inner table*/
60     GHashTable *it;
61     if((it=g_hash_table_lookup(mdb->bowings, (*bow)->from->str)) == NULL) {
62       if((it = g_hash_table_new_full(&g_str_hash, &g_str_equal, &destroy_string, &destroy_inner_bowhash)) == NULL) {
63         (*bow) = NULL;
64         return 1;
65       }
66       g_hash_table_insert(mdb->bowings, (*bow)->from->str, it);
67       #ifdef DEBUG
68       fprintf(stderr, "Got new hash table (from=%s)\n", (*bow)->from->str);
69       #endif
70     }
71     g_hash_table_insert(it, (gpointer)g_strdup((*bow)->to->str), (gpointer)(*bow));
72     (*bow) = NULL;
73   }
74   return 0;
75 }
76
77 /*Removes leading and trailing whitespace, comments, and reduces
78   redundant whitespace down to one tab, makes an empty line an empty string.*/
79 static void prettify_line(char *line) {
80   register char *look;
81   register char *write;
82   register int in_whitespace;
83   register int leading_whitespace;
84   for(look=write=line, in_whitespace=0, leading_whitespace=1; *look != '\0'; look++) {
85 #ifdef DEBUG_2
86     fprintf(stderr, " look=%d(%c) write=%d(%c) in_whitespace=%d leading_whitespace=%d: ", (look-line), *look, (write-line), *write, in_whitespace, leading_whitespace);
87 #endif
88     if(*look == '#') {
89 #ifdef DEBUG_2
90       fprintf(stderr, "comment\n");
91 #endif
92       *write = '\0';
93       break;
94     }
95     switch(*look) {
96     case ' ':
97     case '\n':
98     case '\t':
99 #ifdef DEBUG_2
100       fprintf(stderr, "whitespace\n");
101 #endif
102       if((!leading_whitespace) && (!in_whitespace)) *write++ = '\t';
103       in_whitespace=1;
104       break;
105     default:
106 #ifdef DEBUG_2
107       fprintf(stderr, "default\n");
108 #endif
109       *write++ = *look;
110       in_whitespace=leading_whitespace=0;
111     }
112   }
113   *write='\0';
114   for(look=write=line; *look != '\0'; look++) {
115     switch(*look) {
116     case ' ':
117     case '\n':
118     case '\t':
119       if(*(look+1) == '\0') {
120         *look = '\0';
121       }
122     }
123   }
124 }
125
126 /*Error values:
127  *   0: success
128  *   1: failure malloc'ing / initializing mdb struct
129  *   2: failure opening file.
130  *   4: failure reading line.
131  *   8: warning reading line: no tab separator (line parsing error)
132  *  16: warning reading line: bad section declaration
133  *  32: warning reading line: unable to allocate material struct.
134  *  64: warning reading line: ignored line (unallocated material struct)
135  * 128: warning reading line: unable to allocate bowing struct
136  * 256: warning reading line: ignored line (unallocated bowing struct)
137  * 512: warning reading file: improper section detected (programming
138  *   error)
139  *1024: warning reading file: no : separator in bowing materials
140  *   description.
141  *2048: warning reading file: unable to read numeric value for property.
142  */
143 struct matdb* read_matdb_dotcode(const GString *name, int* err) {
144 #ifdef DEBUG_2
145   fprintf(stderr, "in read_matdb_dotcode(%s, %d)\n", name->str, *err);
146 #endif
147   *err=0;
148   struct matdb *mdb = (struct matdb*)malloc(sizeof(struct matdb));
149   if(mdb == NULL) {*err = 1; return NULL;}
150
151   double *value;
152   if((mdb->materials = g_hash_table_new_full(&g_str_hash, &g_str_equal, &destroy_string, &destroy_material_gpointer)) == NULL) {
153     *err = 1;
154     free(mdb);
155     return NULL;
156   }
157   if((mdb->bowings = g_hash_table_new_full(&g_str_hash, &g_str_equal, &destroy_string, &destroy_bowing_gpointer)) == NULL) {
158     *err = 1;
159     g_hash_table_unref(mdb->materials);
160     free(mdb);
161     return NULL;
162   }
163   struct matdb_material *mat = NULL;
164   struct matdb_bowing *bow = NULL;
165   /*Valid sections: 
166    * 0 (no/global section)
167    * 1 (material)
168    * 2 (bow)
169    */
170   int section=0;
171   int n;
172   char *line;
173   FILE *infile = fopen(name->str, "r");
174   if(infile == NULL) {
175     g_hash_table_unref(mdb->materials);
176     g_hash_table_unref(mdb->bowings);
177     free(mdb);
178     *err=2;
179     return NULL;
180 #ifdef DEBUG_2
181   }else{
182     fprintf(stderr, "infile=%x\n", (unsigned int)infile);
183 #endif
184   }
185   int val;
186   while(!feof(infile)) {
187 #ifdef DEBUG
188     fprintf(stderr, "mat=%x(%s), bow=%x(%s:%s)\n", (unsigned int)mat, mat?mat->name->str:"", (unsigned int)bow, bow?bow->from->str:"", bow?bow->to->str:"");
189 #endif
190     line=NULL;
191     if((val = getline(&line, &n, infile)) == -1) {
192 #ifdef DEBUG_2
193       fprintf(stderr, "getline returned %d\n", val); 
194 #endif
195       if(!feof(infile)) *err = 4;
196       //fclose(infile);
197       break;
198     }
199 #ifdef DEBUG_2
200     fprintf(stderr, "line=(%s)\n", line);
201 #endif
202     prettify_line(line);
203 #ifdef DEBUG
204     fprintf(stderr, "%d: prettified line=(%s)\n", section, line);
205 #endif
206     if(*line == '\0') {
207       free(line);
208       continue;
209     }
210     char *i = index(line, '\t');
211     if(i == NULL) {
212       *err &= 8;
213     }
214     *i++ = '\0';
215     /*At this point, we have line which stores the first word on the
216       line, and i which stores the second word on the line.
217     */
218     char *to;
219     GHashTable *ht;
220 #ifdef DEBUG_2
221     fprintf(stderr, "part_a=(%s) part_b=(%s)\n", line, i);
222 #endif
223     /*If we have a material or bowing underway, save it off*/
224     if(strcasecmp(line, "material") == 0) {
225       insert_into_matdb(mdb, &mat, &bow);
226       if((mat = (struct matdb_material*)malloc(sizeof(struct matdb_material))) == NULL) {
227         *err &= 32;
228       }
229       if((mat->name = g_string_new(i)) == NULL) {
230         *err &= 32;
231         free(mat);
232         section=0;
233         continue;
234       }
235       if((mat->properties = g_hash_table_new_full(&g_str_hash, &g_str_equal, &destroy_string, &destroy_double)) == NULL) {
236         *err &= 32;
237         g_string_free(mat->name, TRUE);
238         free(bow);
239         section=0;
240         continue;
241       }
242 #ifdef DEBUG_2
243       fprintf(stderr, "new material (%s):\n", i);
244 #endif
245       section=1;
246     }else if(strcasecmp(line, "bow") == 0) {
247       insert_into_matdb(mdb, &mat, &bow);
248       if((bow = (struct matdb_bowing*)malloc(sizeof(struct matdb_bowing))) == NULL) {
249         *err &= 128;
250       }
251       if((to = index(i, ':')) == NULL) {
252         *err &= 1024;
253         free(bow);
254         section=0;
255         continue;
256       }
257       *to++ = '\0';
258       /*Same trick as before, but i now stores the from material,
259         and to the to material
260       */
261       if((bow->from = g_string_new(i)) == NULL) {
262         *err &= 128;
263         free(bow);
264         section=0;
265         continue;
266       }
267       if((bow->to = g_string_new(to)) == NULL) {
268         *err &= 128;
269         g_string_free(bow->from, TRUE);
270         free(bow);
271         section=0;
272         continue;
273       }
274       if((bow->properties = g_hash_table_new_full(&g_str_hash, &g_str_equal, &destroy_string, &destroy_double)) == NULL) {
275         *err &= 128;
276         g_string_free(bow->to, TRUE);
277         g_string_free(bow->from, TRUE);
278         free(bow);
279         section=0;
280         continue;
281       }
282 #ifdef DEBUG_2
283       fprintf(stderr, "new bowing (%s:%s):\n", i, to);
284 #endif
285       section=2;
286     }else{
287       #ifdef DEBUG
288       fprintf(stderr, "\t%d/%s\t", section, line);
289       #endif
290       /*Process a property.*/
291       switch(section) {
292       case 1:
293         #ifdef DEBUG
294         fprintf(stderr, "(1)\t");
295         #endif
296         ht = mat->properties;
297         break;
298       case 2:
299         #ifdef DEBUG
300         fprintf(stderr, "(2)\t");
301         #endif
302         ht = bow->properties;
303         break;
304       default:
305         #ifdef DEBUG
306         fprintf(stderr, "(default)\t");
307         #endif
308         *err &= 16;
309         section = 0;
310       }
311       if(section == 0) {
312         #ifdef DEBUG
313         fprintf(stderr, "section was 0\n");
314         #endif
315         continue;
316       }
317       if((value = (double*)malloc(sizeof(double))) == NULL) {
318         *err &= 2048;
319         #ifdef DEBUG
320         fprintf(stderr, "malloc of value failed\n");
321         #endif
322         continue;
323       }
324       int num;
325       if((num=sscanf(i, " %lg", value)) != 1) {
326         *err &= 2048;
327         free(value);
328         #ifdef DEBUG
329         fprintf(stderr, "bad sscanf to get value (scanned \"%s\", returned %d)\n", i, num);
330         #endif
331         continue;
332       }
333       g_hash_table_insert(ht, g_strdup(line), value);
334 #ifdef DEBUG
335       fprintf(stderr, "%g(%s)\n", *value, line);
336 #endif
337     }
338     free(line);
339     line=i=to=NULL;
340     value=NULL;
341   }
342   fclose(infile);
343   insert_into_matdb(mdb, &mat, &bow);
344   return mdb;
345 }
346