]> git.tdb.fi Git - ext/subsurface.git/blob - parse.c
Start parsing numeric fields
[ext/subsurface.git] / parse.c
1 #include <stdio.h>
2 #include <ctype.h>
3 #include <string.h>
4 #include <stdlib.h>
5 #include <errno.h>
6 #include <time.h>
7 #include <libxml/parser.h>
8 #include <libxml/tree.h>
9
10 /*
11  * Some silly typedefs to make our units very explicit.
12  *
13  * Also, the units are chosen so that values can be expressible as
14  * integers, so that we never have FP rounding issues. And they
15  * are small enough that converting to/from imperial units doesn't
16  * really matter.
17  *
18  * We also strive to make '0' a meaningless number saying "not
19  * initialized", since many values are things that may not have
20  * been reported (eg tank pressure or temperature from dive
21  * computers that don't support them). But sometimes -1 is an even
22  * more explicit way of saying "not there".
23  *
24  * Thus "millibar" for pressure, for example, or "millikelvin" for
25  * temperatures. Doing temperatures in celsius or fahrenheit would
26  * make for loss of precision when converting from one to the other,
27  * and using millikelvin is SI-like but also means that a temperature
28  * of '0' is clearly just a missing temperature or tank pressure.
29  *
30  * Also strive to use units that can not possibly be mistaken for a
31  * valid value in a "normal" system without conversion. If the max
32  * depth of a dive is '20000', you probably didn't convert from mm on
33  * output, or if the max depth gets reported as "0.2ft" it was either
34  * a really boring dive, or there was some missing input conversion,
35  * and a 60-ft dive got recorded as 60mm.
36  *
37  * Doing these as "structs containing value" means that we always
38  * have to explicitly write out those units in order to get at the
39  * actual value. So there is hopefully little fear of using a value
40  * in millikelvin as Fahrenheit by mistake.
41  *
42  * We don't actually use these all yet, so maybe they'll change, but
43  * I made a number of types as guidelines.
44  */
45 typedef struct {
46         int seconds;
47 } duration_t;
48
49 typedef struct {
50         int mm;
51 } depth_t;
52
53 typedef struct {
54         int mbar;
55 } pressure_t;
56
57 typedef struct {
58         int mkelvin;
59 } temperature_t;
60
61 typedef struct {
62         int mliter;
63 } volume_t;
64
65 typedef struct {
66         int permille;
67 } fraction_t;
68
69 typedef struct {
70         int grams;
71 } weight_t;
72
73 typedef struct {
74         fraction_t o2;
75         fraction_t n2;
76         fraction_t he2;
77 } gasmix_t;
78
79 typedef struct {
80         volume_t size;
81         pressure_t pressure;
82 } tank_type_t;
83
84 struct sample {
85         duration_t time;
86         depth_t depth;
87         temperature_t temperature;
88         pressure_t tankpressure;
89         int tankindex;
90 };
91
92 struct dive {
93         time_t when;
94         depth_t maxdepth, meandepth;
95         duration_t duration, surfacetime;
96         depth_t visibility;
97         temperature_t airtemp, watertemp;
98         pressure_t beginning_pressure, end_pressure;
99         int samples;
100         struct sample sample[];
101 };
102
103 static void record_dive(struct dive *dive)
104 {
105         static int nr;
106         struct tm *tm;
107
108         tm = gmtime(&dive->when);
109
110         printf("Dive %d with %d samples at %02d:%02d:%02d %04d-%02d-%02d\n",
111                 ++nr, dive->samples,
112                 tm->tm_hour, tm->tm_min, tm->tm_sec,
113                 tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday);
114 }
115
116 static void nonmatch(const char *type, const char *fullname, const char *name, char *buffer)
117 {
118         printf("Unable to match %s '(%.*s)%s' (%s)\n", type,
119                 (int) (name - fullname), fullname, name,
120                 buffer);
121         free(buffer);
122 }
123
124 static const char *last_part(const char *name)
125 {
126         const char *p = strrchr(name, '.');
127         return p ? p+1 : name;
128 }
129
130 typedef void (*matchfn_t)(char *buffer, void *);
131
132 static int match(const char *pattern, const char *name, matchfn_t fn, char *buf, void *data)
133 {
134         if (strcasecmp(pattern, name))
135                 return 0;
136         fn(buf, data);
137         return 1;
138 }
139
140 /*
141  * Dive info as it is being built up..
142  */
143 static int alloc_samples;
144 static struct dive *dive;
145 static struct sample *sample;
146 static struct tm tm;
147
148 static time_t utc_mktime(struct tm *tm)
149 {
150         static const int mdays[] = {
151             0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
152         };
153         int year = tm->tm_year;
154         int month = tm->tm_mon;
155         int day = tm->tm_mday;
156
157         if (year < 70)
158                 year += 100;
159         else if (year > 1900)
160                 year -= 1900;
161
162         /* Normalized to Jan 1, 1970: unix time */
163         year -= 70;
164
165         if (year < 0 || year > 129) /* algo only works for 1970-2099 */
166                 return -1;
167         if (month < 0 || month > 11) /* array bounds */
168                 return -1;
169         if (month < 2 || (year + 2) % 4)
170                 day--;
171         if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_sec < 0)
172                 return -1;
173         return (year * 365 + (year + 1) / 4 + mdays[month] + day) * 24*60*60UL +
174                 tm->tm_hour * 60*60 + tm->tm_min * 60 + tm->tm_sec;
175 }
176
177 static void divedate(char *buffer, void *_when)
178 {
179         int d,m,y;
180         time_t *when = _when;
181
182         if (sscanf(buffer, "%d.%d.%d", &d, &m, &y) == 3) {
183                 tm.tm_year = y;
184                 tm.tm_mon = m-1;
185                 tm.tm_mday = d;
186                 if (tm.tm_sec | tm.tm_min | tm.tm_hour)
187                         *when = utc_mktime(&tm);
188         }
189         free(buffer);
190 }
191
192 static void divetime(char *buffer, void *_when)
193 {
194         int h,m,s = 0;
195         time_t *when = _when;
196
197         if (sscanf(buffer, "%d:%d:%d", &h, &m, &s) >= 2) {
198                 tm.tm_hour = h;
199                 tm.tm_min = m;
200                 tm.tm_sec = s;
201                 if (tm.tm_year)
202                         *when = utc_mktime(&tm);
203         }
204         free(buffer);
205 }
206
207 union int_or_float {
208         long i;
209         double fp;
210 };
211
212 enum number_type {
213         NEITHER,
214         INTEGER,
215         FLOAT
216 };
217
218 static enum number_type integer_or_float(char *buffer, union int_or_float *res)
219 {
220         char *end;
221         long val;
222         double fp;
223
224         /* Integer or floating point? */
225         val = strtol(buffer, &end, 10);
226         if (val < 0 || end == buffer)
227                 return NEITHER;
228
229         /* Looks like it might be floating point? */
230         if (*end == '.') {
231                 errno = 0;
232                 fp = strtod(buffer, &end);
233                 if (!errno) {
234                         res->fp = fp;
235                         return FLOAT;
236                 }
237         }
238
239         res->i = val;
240         return INTEGER;
241 }
242
243 static void pressure(char *buffer, void *_press)
244 {
245         pressure_t *pressure = _press;
246         union int_or_float val;
247
248         switch (integer_or_float(buffer, &val)) {
249         case FLOAT:
250                 /* Maybe it's in Bar? */
251                 if (val.fp < 500.0) {
252                         pressure->mbar = val.fp * 1000;
253                         break;
254                 }
255                 printf("Unknown fractional pressure reading %s\n", buffer);
256                 break;
257
258         case INTEGER:
259                 /*
260                  * Random integer? Maybe in PSI? Or millibar already?
261                  *
262                  * We assume that 5 bar is a ridiculous tank pressure,
263                  * so if it's smaller than 5000, it's in PSI..
264                  */
265                 if (val.i < 5000) {
266                         pressure->mbar = val.i * 68.95;
267                         break;
268                 }
269                 pressure->mbar = val.i;
270                 break;
271         default:
272                 printf("Strange pressure reading %s\n", buffer);
273         }
274         free(buffer);
275 }
276
277 static void depth(char *buffer, void *_depth)
278 {
279         depth_t *depth = _depth;
280         union int_or_float val;
281
282         switch (integer_or_float(buffer, &val)) {
283         /* Integer values are probably in feet */
284         case INTEGER:
285                 depth->mm = 304.8 * val.i;
286                 break;
287         /* Float? Probably meters.. */
288         case FLOAT:
289                 depth->mm = val.fp * 1000;
290                 break;
291         default:
292                 printf("Strange depth reading %s\n", buffer);
293         }
294         free(buffer);
295 }
296
297 static void temperature(char *buffer, void *_temperature)
298 {
299         temperature_t *temperature = _temperature;
300         union int_or_float val;
301
302         switch (integer_or_float(buffer, &val)) {
303         /* C or F? Who knows? Let's default to Celsius */
304         case INTEGER:
305                 val.fp = val.i;
306                 /* Fallthrough */
307         case FLOAT:
308                 /* Ignore zero. It means "none" */
309                 if (!val.fp)
310                         break;
311                 /* Celsius */
312                 if (val.fp < 50.0) {
313                         temperature->mkelvin = (val.fp + 273.16) * 1000;
314                         break;
315                 }
316                 /* Fahrenheit */
317                 if (val.fp < 212.0) {
318                         temperature->mkelvin = (val.fp + 459.67) * 5000/9;
319                         break;
320                 }
321                 /* Kelvin or already millikelvin */
322                 if (val.fp < 1000.0)
323                         val.fp *= 1000;
324                 temperature->mkelvin = val.fp;
325                 break;
326         default:
327                 printf("Strange temperature reading %s\n", buffer);
328         }
329         free(buffer);
330 }
331
332 static void sampletime(char *buffer, void *_time)
333 {
334         duration_t *time = _time;
335         union int_or_float val;
336
337         switch (integer_or_float(buffer, &val)) {
338         /* C or F? Who knows? Let's default to Celsius */
339         case INTEGER:
340                 time->seconds = val.i * 1000;
341                 break;
342         default:
343                 printf("Strange sample time reading %s\n", buffer);
344         }
345         free(buffer);
346 }
347
348 /* We're in samples - try to convert the random xml value to something useful */
349 static void try_to_fill_sample(struct sample *sample, const char *name, char *buf)
350 {
351         const char *last = last_part(name);
352
353         if (match("pressure", last, pressure, buf, &sample->tankpressure))
354                 return;
355         if (match("cylpress", last, pressure, buf, &sample->tankpressure))
356                 return;
357         if (match("depth", last, depth, buf, &sample->depth))
358                 return;
359         if (match("temperature", last, temperature, buf, &sample->temperature))
360                 return;
361         if (match("sampletime", last, sampletime, buf, &sample->time))
362                 return;
363         if (match("time", last, sampletime, buf, &sample->time))
364                 return;
365
366         nonmatch("sample", name, last, buf);
367 }
368
369 /* We're in the top-level dive xml. Try to convert whatever value to a dive value */
370 static void try_to_fill_dive(struct dive *dive, const char *name, char *buf)
371 {
372         const char *last = last_part(name);
373
374         if (match("date", last, divedate, buf, &dive->when))
375                 return;
376         if (match("time", last, divetime, buf, &dive->when))
377                 return;
378         nonmatch("dive", name, last, buf);
379 }
380
381 static unsigned int dive_size(int samples)
382 {
383         return sizeof(struct dive) + samples*sizeof(struct sample);
384 }
385
386 /*
387  * File boundaries are dive boundaries. But sometimes there are
388  * multiple dives per file, so there can be other events too that
389  * trigger a "new dive" marker and you may get some nesting due
390  * to that. Just ignore nesting levels.
391  */
392 static void dive_start(void)
393 {
394         unsigned int size;
395
396         if (dive)
397                 return;
398
399         alloc_samples = 5;
400         size = dive_size(alloc_samples);
401         dive = malloc(size);
402         if (!dive)
403                 exit(1);
404         memset(dive, 0, size);
405         memset(&tm, 0, sizeof(tm));
406 }
407
408 static void dive_end(void)
409 {
410         if (!dive)
411                 return;
412         record_dive(dive);
413         dive = NULL;
414 }
415
416 static void sample_start(void)
417 {
418         int nr;
419
420         if (!dive)
421                 return;
422         nr = dive->samples;
423         if (nr >= alloc_samples) {
424                 unsigned int size;
425
426                 alloc_samples = (alloc_samples * 3)/2 + 10;
427                 size = dive_size(alloc_samples);
428                 dive = realloc(dive, size);
429                 if (!dive)
430                         return;
431         }
432         sample = dive->sample + nr;
433         memset(sample, 0, sizeof(*sample));
434 }
435
436 static void sample_end(void)
437 {
438         sample = NULL;
439         if (!dive)
440                 return;
441         dive->samples++;
442 }
443
444 static void entry(const char *name, int size, const char *raw)
445 {
446         char *buf = malloc(size+1);
447
448         if (!buf)
449                 return;
450         memcpy(buf, raw, size);
451         buf[size] = 0;
452         if (sample) {
453                 try_to_fill_sample(sample, name, buf);
454                 return;
455         }
456         if (dive) {
457                 try_to_fill_dive(dive, name, buf);
458                 return;
459         }
460 }
461
462 static const char *nodename(xmlNode *node, char *buf, int len)
463 {
464
465         if (!node || !node->name)
466                 return "root";
467
468         buf += len;
469         *--buf = 0;
470         len--;
471
472         for(;;) {
473                 const char *name = node->name;
474                 int i = strlen(name);
475                 while (--i >= 0) {
476                         unsigned char c = name[i];
477                         *--buf = tolower(c);
478                         if (!--len)
479                                 return buf;
480                 }
481                 node = node->parent;
482                 if (!node || !node->name)
483                         return buf;
484                 *--buf = '.';
485                 if (!--len)
486                         return buf;
487         }
488 }
489
490 #define MAXNAME 64
491
492 static void visit_one_node(xmlNode *node)
493 {
494         int len;
495         const unsigned char *content;
496         char buffer[MAXNAME];
497         const char *name;
498
499         content = node->content;
500         if (!content)
501                 return;
502
503         /* Trim whitespace at beginning */
504         while (isspace(*content))
505                 content++;
506
507         /* Trim whitespace at end */
508         len = strlen(content);
509         while (len && isspace(content[len-1]))
510                 len--;
511
512         if (!len)
513                 return;
514
515         /* Don't print out the node name if it is "text" */
516         if (!strcmp(node->name, "text"))
517                 node = node->parent;
518
519         name = nodename(node, buffer, sizeof(buffer));
520
521         entry(name, len, content);
522 }
523
524 static void traverse(xmlNode *node)
525 {
526         xmlNode *n;
527
528         for (n = node; n; n = n->next) {
529                 /* XML from libdivecomputer: 'dive' per new dive */
530                 if (!strcmp(n->name, "dive")) {
531                         dive_start();
532                         traverse(n->children);
533                         dive_end();
534                         continue;
535                 }
536
537                 /*
538                  * At least both libdivecomputer and Suunto
539                  * agree on "sample".
540                  *
541                  * Well - almost. Ignore case.
542                  */
543                 if (!strcasecmp(n->name, "sample")) {
544                         sample_start();
545                         traverse(n->children);
546                         sample_end();
547                         continue;
548                 }
549
550                 /* Anything else - just visit it and recurse */
551                 visit_one_node(n);
552                 traverse(n->children);
553         }
554 }
555
556 static void parse(const char *filename)
557 {
558         xmlDoc *doc;
559
560         doc = xmlReadFile(filename, NULL, 0);
561         if (!doc) {
562                 fprintf(stderr, "Failed to parse '%s'.\n", filename);
563                 return;
564         }
565
566         dive_start();
567         traverse(xmlDocGetRootElement(doc));
568         dive_end();
569         xmlFreeDoc(doc);
570         xmlCleanupParser();
571 }
572
573 int main(int argc, char **argv)
574 {
575         int i;
576
577         LIBXML_TEST_VERSION
578
579         for (i = 1; i < argc; i++)
580                 parse(argv[i]);
581         return 0;
582 }