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