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