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