]> git.tdb.fi Git - ext/subsurface.git/blob - parse.c
Fix up temperature conversion
[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         if (year < 70)
189                 year += 100;
190         else if (year > 1900)
191                 year -= 1900;
192
193         /* Normalized to Jan 1, 1970: unix time */
194         year -= 70;
195
196         if (year < 0 || year > 129) /* algo only works for 1970-2099 */
197                 return -1;
198         if (month < 0 || month > 11) /* array bounds */
199                 return -1;
200         if (month < 2 || (year + 2) % 4)
201                 day--;
202         if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_sec < 0)
203                 return -1;
204         return (year * 365 + (year + 1) / 4 + mdays[month] + day) * 24*60*60UL +
205                 tm->tm_hour * 60*60 + tm->tm_min * 60 + tm->tm_sec;
206 }
207
208 static void divedate(char *buffer, void *_when)
209 {
210         int d,m,y;
211         time_t *when = _when;
212
213         if (sscanf(buffer, "%d.%d.%d", &d, &m, &y) == 3) {
214                 tm.tm_year = y;
215                 tm.tm_mon = m-1;
216                 tm.tm_mday = d;
217                 if (tm.tm_sec | tm.tm_min | tm.tm_hour)
218                         *when = utc_mktime(&tm);
219         }
220         free(buffer);
221 }
222
223 static void divetime(char *buffer, void *_when)
224 {
225         int h,m,s = 0;
226         time_t *when = _when;
227
228         if (sscanf(buffer, "%d:%d:%d", &h, &m, &s) >= 2) {
229                 tm.tm_hour = h;
230                 tm.tm_min = m;
231                 tm.tm_sec = s;
232                 if (tm.tm_year)
233                         *when = utc_mktime(&tm);
234         }
235         free(buffer);
236 }
237
238 union int_or_float {
239         long i;
240         double fp;
241 };
242
243 enum number_type {
244         NEITHER,
245         INTEGER,
246         FLOAT
247 };
248
249 static enum number_type integer_or_float(char *buffer, union int_or_float *res)
250 {
251         char *end;
252         long val;
253         double fp;
254
255         /* Integer or floating point? */
256         val = strtol(buffer, &end, 10);
257         if (val < 0 || end == buffer)
258                 return NEITHER;
259
260         /* Looks like it might be floating point? */
261         if (*end == '.') {
262                 errno = 0;
263                 fp = strtod(buffer, &end);
264                 if (!errno) {
265                         res->fp = fp;
266                         return FLOAT;
267                 }
268         }
269
270         res->i = val;
271         return INTEGER;
272 }
273
274 static void pressure(char *buffer, void *_press)
275 {
276         pressure_t *pressure = _press;
277         union int_or_float val;
278
279         switch (integer_or_float(buffer, &val)) {
280         case FLOAT:
281                 /* Maybe it's in Bar? */
282                 if (val.fp < 500.0) {
283                         pressure->mbar = val.fp * 1000;
284                         break;
285                 }
286                 printf("Unknown fractional pressure reading %s\n", buffer);
287                 break;
288
289         case INTEGER:
290                 /*
291                  * Random integer? Maybe in PSI? Or millibar already?
292                  *
293                  * We assume that 5 bar is a ridiculous tank pressure,
294                  * so if it's smaller than 5000, it's in PSI..
295                  */
296                 if (val.i < 5000) {
297                         pressure->mbar = val.i * 68.95;
298                         break;
299                 }
300                 pressure->mbar = val.i;
301                 break;
302         default:
303                 printf("Strange pressure reading %s\n", buffer);
304         }
305         free(buffer);
306 }
307
308 static void depth(char *buffer, void *_depth)
309 {
310         depth_t *depth = _depth;
311         union int_or_float val;
312
313         switch (integer_or_float(buffer, &val)) {
314         /* Integer values are probably in feet */
315         case INTEGER:
316                 depth->mm = 304.8 * val.i;
317                 break;
318         /* Float? Probably meters.. */
319         case FLOAT:
320                 depth->mm = val.fp * 1000;
321                 break;
322         default:
323                 printf("Strange depth reading %s\n", buffer);
324         }
325         free(buffer);
326 }
327
328 static void temperature(char *buffer, void *_temperature)
329 {
330         temperature_t *temperature = _temperature;
331         union int_or_float val;
332
333         switch (integer_or_float(buffer, &val)) {
334         /* C or F? Who knows? Let's default to Celsius */
335         case INTEGER:
336                 val.fp = val.i;
337                 /* Fallthrough */
338         case FLOAT:
339                 /* Ignore zero. It means "none" */
340                 if (!val.fp)
341                         break;
342                 /* Celsius */
343                 if (val.fp < 50.0) {
344                         temperature->mkelvin = (val.fp + 273.16) * 1000;
345                         break;
346                 }
347                 /* Fahrenheit */
348                 if (val.fp < 212.0) {
349                         temperature->mkelvin = (val.fp + 459.67) * 5000/9;
350                         break;
351                 }
352                 /* Kelvin or already millikelvin */
353                 if (val.fp < 1000.0)
354                         val.fp *= 1000;
355                 temperature->mkelvin = val.fp;
356                 break;
357         default:
358                 printf("Strange temperature reading %s\n", buffer);
359         }
360         free(buffer);
361 }
362
363 static void sampletime(char *buffer, void *_time)
364 {
365         duration_t *time = _time;
366         union int_or_float val;
367
368         switch (integer_or_float(buffer, &val)) {
369         case INTEGER:
370                 time->seconds = val.i;
371                 break;
372         default:
373                 printf("Strange sample time reading %s\n", buffer);
374         }
375         free(buffer);
376 }
377
378 /* We're in samples - try to convert the random xml value to something useful */
379 static void try_to_fill_sample(struct sample *sample, const char *name, char *buf)
380 {
381         const char *last = last_part(name);
382
383         if (match("pressure", last, pressure, buf, &sample->tankpressure))
384                 return;
385         if (match("cylpress", last, pressure, buf, &sample->tankpressure))
386                 return;
387         if (match("depth", last, depth, buf, &sample->depth))
388                 return;
389         if (match("temperature", last, temperature, buf, &sample->temperature))
390                 return;
391         if (match("sampletime", last, sampletime, buf, &sample->time))
392                 return;
393         if (match("time", last, sampletime, buf, &sample->time))
394                 return;
395
396         nonmatch("sample", name, last, buf);
397 }
398
399 /* We're in the top-level dive xml. Try to convert whatever value to a dive value */
400 static void try_to_fill_dive(struct dive *dive, const char *name, char *buf)
401 {
402         const char *last = last_part(name);
403
404         if (match("date", last, divedate, buf, &dive->when))
405                 return;
406         if (match("time", last, divetime, buf, &dive->when))
407                 return;
408         nonmatch("dive", name, last, buf);
409 }
410
411 static unsigned int dive_size(int samples)
412 {
413         return sizeof(struct dive) + samples*sizeof(struct sample);
414 }
415
416 /*
417  * File boundaries are dive boundaries. But sometimes there are
418  * multiple dives per file, so there can be other events too that
419  * trigger a "new dive" marker and you may get some nesting due
420  * to that. Just ignore nesting levels.
421  */
422 static void dive_start(void)
423 {
424         unsigned int size;
425
426         if (dive)
427                 return;
428
429         alloc_samples = 5;
430         size = dive_size(alloc_samples);
431         dive = malloc(size);
432         if (!dive)
433                 exit(1);
434         memset(dive, 0, size);
435         memset(&tm, 0, sizeof(tm));
436 }
437
438 static void dive_end(void)
439 {
440         if (!dive)
441                 return;
442         record_dive(dive);
443         dive = NULL;
444 }
445
446 static void sample_start(void)
447 {
448         int nr;
449
450         if (!dive)
451                 return;
452         nr = dive->samples;
453         if (nr >= alloc_samples) {
454                 unsigned int size;
455
456                 alloc_samples = (alloc_samples * 3)/2 + 10;
457                 size = dive_size(alloc_samples);
458                 dive = realloc(dive, size);
459                 if (!dive)
460                         return;
461         }
462         sample = dive->sample + nr;
463         memset(sample, 0, sizeof(*sample));
464 }
465
466 static void sample_end(void)
467 {
468         sample = NULL;
469         if (!dive)
470                 return;
471         dive->samples++;
472 }
473
474 static void entry(const char *name, int size, const char *raw)
475 {
476         char *buf = malloc(size+1);
477
478         if (!buf)
479                 return;
480         memcpy(buf, raw, size);
481         buf[size] = 0;
482         if (sample) {
483                 try_to_fill_sample(sample, name, buf);
484                 return;
485         }
486         if (dive) {
487                 try_to_fill_dive(dive, name, buf);
488                 return;
489         }
490 }
491
492 static const char *nodename(xmlNode *node, char *buf, int len)
493 {
494
495         if (!node || !node->name)
496                 return "root";
497
498         buf += len;
499         *--buf = 0;
500         len--;
501
502         for(;;) {
503                 const char *name = node->name;
504                 int i = strlen(name);
505                 while (--i >= 0) {
506                         unsigned char c = name[i];
507                         *--buf = tolower(c);
508                         if (!--len)
509                                 return buf;
510                 }
511                 node = node->parent;
512                 if (!node || !node->name)
513                         return buf;
514                 *--buf = '.';
515                 if (!--len)
516                         return buf;
517         }
518 }
519
520 #define MAXNAME 64
521
522 static void visit_one_node(xmlNode *node)
523 {
524         int len;
525         const unsigned char *content;
526         char buffer[MAXNAME];
527         const char *name;
528
529         content = node->content;
530         if (!content)
531                 return;
532
533         /* Trim whitespace at beginning */
534         while (isspace(*content))
535                 content++;
536
537         /* Trim whitespace at end */
538         len = strlen(content);
539         while (len && isspace(content[len-1]))
540                 len--;
541
542         if (!len)
543                 return;
544
545         /* Don't print out the node name if it is "text" */
546         if (!strcmp(node->name, "text"))
547                 node = node->parent;
548
549         name = nodename(node, buffer, sizeof(buffer));
550
551         entry(name, len, content);
552 }
553
554 static void traverse(xmlNode *node)
555 {
556         xmlNode *n;
557
558         for (n = node; n; n = n->next) {
559                 /* XML from libdivecomputer: 'dive' per new dive */
560                 if (!strcmp(n->name, "dive")) {
561                         dive_start();
562                         traverse(n->children);
563                         dive_end();
564                         continue;
565                 }
566
567                 /*
568                  * At least both libdivecomputer and Suunto
569                  * agree on "sample".
570                  *
571                  * Well - almost. Ignore case.
572                  */
573                 if (!strcasecmp(n->name, "sample")) {
574                         sample_start();
575                         traverse(n->children);
576                         sample_end();
577                         continue;
578                 }
579
580                 /* Anything else - just visit it and recurse */
581                 visit_one_node(n);
582                 traverse(n->children);
583         }
584 }
585
586 static void parse(const char *filename)
587 {
588         xmlDoc *doc;
589
590         doc = xmlReadFile(filename, NULL, 0);
591         if (!doc) {
592                 fprintf(stderr, "Failed to parse '%s'.\n", filename);
593                 return;
594         }
595
596         dive_start();
597         traverse(xmlDocGetRootElement(doc));
598         dive_end();
599         xmlFreeDoc(doc);
600         xmlCleanupParser();
601 }
602
603 static void parse_argument(const char *arg)
604 {
605         const char *p = arg+1;
606
607         do {
608                 switch (*p) {
609                 case 'v':
610                         verbose++;
611                         continue;
612                 default:
613                         fprintf(stderr, "Bad argument '%s'\n", arg);
614                         exit(1);
615                 }
616         } while (*++p);
617 }
618
619 int main(int argc, char **argv)
620 {
621         int i;
622
623         LIBXML_TEST_VERSION
624
625         for (i = 1; i < argc; i++) {
626                 const char *a = argv[i];
627
628                 if (a[0] == '-') {
629                         parse_argument(a);
630                         continue;
631                 }
632                 parse(a);
633         }
634         return 0;
635 }