]> git.tdb.fi Git - ext/subsurface.git/blob - parse-xml.c
Rename some files to be more appropriate
[ext/subsurface.git] / parse-xml.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         /* All values are probably in meters */
226         case INTEGER:
227                 val.fp = val.i;
228                 /* fallthrough */
229         case FLOAT:
230                 depth->mm = val.fp * 1000;
231                 break;
232         default:
233                 printf("Strange depth reading %s\n", buffer);
234         }
235         free(buffer);
236 }
237
238 static void temperature(char *buffer, void *_temperature)
239 {
240         temperature_t *temperature = _temperature;
241         union int_or_float val;
242
243         switch (integer_or_float(buffer, &val)) {
244         /* C or F? Who knows? Let's default to Celsius */
245         case INTEGER:
246                 val.fp = val.i;
247                 /* Fallthrough */
248         case FLOAT:
249                 /* Ignore zero. It means "none" */
250                 if (!val.fp)
251                         break;
252                 /* Celsius */
253                 if (val.fp < 50.0) {
254                         temperature->mkelvin = (val.fp + 273.16) * 1000;
255                         break;
256                 }
257                 /* Fahrenheit */
258                 if (val.fp < 212.0) {
259                         temperature->mkelvin = (val.fp + 459.67) * 5000/9;
260                         break;
261                 }
262                 /* Kelvin or already millikelvin */
263                 if (val.fp < 1000.0)
264                         val.fp *= 1000;
265                 temperature->mkelvin = val.fp;
266                 break;
267         default:
268                 printf("Strange temperature reading %s\n", buffer);
269         }
270         free(buffer);
271 }
272
273 static void sampletime(char *buffer, void *_time)
274 {
275         int i;
276         int min, sec;
277         duration_t *time = _time;
278
279         i = sscanf(buffer, "%d:%d", &min, &sec);
280         switch (i) {
281         case 1:
282                 sec = min;
283                 min = 0;
284         /* fallthrough */
285         case 2:
286                 time->seconds = sec + min*60;
287                 break;
288         default:
289                 printf("Strange sample time reading %s\n", buffer);
290         }
291         free(buffer);
292 }
293
294 static void duration(char *buffer, void *_time)
295 {
296         sampletime(buffer, _time);
297 }
298
299 static void ignore(char *buffer, void *_time)
300 {
301 }
302
303 /* We're in samples - try to convert the random xml value to something useful */
304 static void try_to_fill_sample(struct sample *sample, const char *name, char *buf)
305 {
306         const char *last = last_part(name);
307
308         if (match("pressure", last, pressure, buf, &sample->tankpressure))
309                 return;
310         if (match("cylpress", last, pressure, buf, &sample->tankpressure))
311                 return;
312         if (match("depth", last, depth, buf, &sample->depth))
313                 return;
314         if (match("temperature", last, temperature, buf, &sample->temperature))
315                 return;
316         if (match("sampletime", last, sampletime, buf, &sample->time))
317                 return;
318         if (match("time", last, sampletime, buf, &sample->time))
319                 return;
320
321         nonmatch("sample", name, last, buf);
322 }
323
324 /* We're in the top-level dive xml. Try to convert whatever value to a dive value */
325 static void try_to_fill_dive(struct dive *dive, const char *name, char *buf)
326 {
327         const char *last = last_part(name);
328
329         if (match("date", last, divedate, buf, &dive->when))
330                 return;
331         if (match("time", last, divetime, buf, &dive->when))
332                 return;
333         if (match("datetime", last, divedatetime, buf, &dive->when))
334                 return;
335         if (match("maxdepth", last, depth, buf, &dive->maxdepth))
336                 return;
337         if (match("meandepth", last, depth, buf, &dive->meandepth))
338                 return;
339         if (match("divetime", last, duration, buf, &dive->duration))
340                 return;
341         if (match("divetimesec", last, duration, buf, &dive->duration))
342                 return;
343         if (match("surfacetime", last, duration, buf, &dive->surfacetime))
344                 return;
345         if (match("airtemp", last, temperature, buf, &dive->airtemp))
346                 return;
347         if (match("watertemp", last, temperature, buf, &dive->watertemp))
348                 return;
349         if (match("cylinderstartpressure", last, pressure, buf, &dive->beginning_pressure))
350                 return;
351         if (match("cylinderendpressure", last, pressure, buf, &dive->end_pressure))
352                 return;
353         if (match("divenumber", last, ignore, buf, NULL))
354                 return;
355         if (match("diveseries", last, ignore, buf, NULL))
356                 return;
357         if (match("number", last, ignore, buf, NULL))
358                 return;
359         if (match("size", last, ignore, buf, NULL))
360                 return;
361         if (match("fingerprint", last, ignore, buf, NULL))
362                 return;
363         nonmatch("dive", name, last, buf);
364 }
365
366 static unsigned int dive_size(int samples)
367 {
368         return sizeof(struct dive) + samples*sizeof(struct sample);
369 }
370
371 /*
372  * File boundaries are dive boundaries. But sometimes there are
373  * multiple dives per file, so there can be other events too that
374  * trigger a "new dive" marker and you may get some nesting due
375  * to that. Just ignore nesting levels.
376  */
377 static void dive_start(void)
378 {
379         unsigned int size;
380
381         if (dive)
382                 return;
383
384         alloc_samples = 5;
385         size = dive_size(alloc_samples);
386         dive = malloc(size);
387         if (!dive)
388                 exit(1);
389         memset(dive, 0, size);
390         memset(&tm, 0, sizeof(tm));
391 }
392
393 static char *generate_name(struct dive *dive)
394 {
395         int len;
396         struct tm *tm;
397         char buffer[256], *p;
398
399         tm = gmtime(&dive->when);
400
401         len = snprintf(buffer, sizeof(buffer),
402                 "%04d-%02d-%02d "
403                 "%02d:%02d:%02d "
404                 "(%d ft, %d min)",
405                 tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday,
406                 tm->tm_hour, tm->tm_min, tm->tm_sec,
407                 to_feet(dive->maxdepth), dive->duration.seconds / 60);
408         p = malloc(len+1);
409         if (!p)
410                 exit(1);
411         memcpy(p, buffer, len+1);
412         return p;
413 }
414
415 static void dive_end(void)
416 {
417         if (!dive)
418                 return;
419         if (!dive->name)
420                 dive->name = generate_name(dive);
421         record_dive(dive);
422         dive = NULL;
423 }
424
425 static void sample_start(void)
426 {
427         int nr;
428
429         if (!dive)
430                 return;
431         nr = dive->samples;
432         if (nr >= alloc_samples) {
433                 unsigned int size;
434
435                 alloc_samples = (alloc_samples * 3)/2 + 10;
436                 size = dive_size(alloc_samples);
437                 dive = realloc(dive, size);
438                 if (!dive)
439                         return;
440         }
441         sample = dive->sample + nr;
442         memset(sample, 0, sizeof(*sample));
443 }
444
445 static void sample_end(void)
446 {
447         if (!dive)
448                 return;
449
450         if (sample->time.seconds > dive->duration.seconds) {
451                 if (sample->depth.mm)
452                         dive->duration = sample->time;
453         }
454
455         if (sample->depth.mm > dive->maxdepth.mm)
456                 dive->maxdepth.mm = sample->depth.mm;
457
458         sample = NULL;
459         dive->samples++;
460 }
461
462 static void entry(const char *name, int size, const char *raw)
463 {
464         char *buf = malloc(size+1);
465
466         if (!buf)
467                 return;
468         memcpy(buf, raw, size);
469         buf[size] = 0;
470         if (sample) {
471                 try_to_fill_sample(sample, name, buf);
472                 return;
473         }
474         if (dive) {
475                 try_to_fill_dive(dive, name, buf);
476                 return;
477         }
478 }
479
480 static const char *nodename(xmlNode *node, char *buf, int len)
481 {
482         if (!node || !node->name)
483                 return "root";
484
485         buf += len;
486         *--buf = 0;
487         len--;
488
489         for(;;) {
490                 const char *name = node->name;
491                 int i = strlen(name);
492                 while (--i >= 0) {
493                         unsigned char c = name[i];
494                         *--buf = tolower(c);
495                         if (!--len)
496                                 return buf;
497                 }
498                 node = node->parent;
499                 if (!node || !node->name)
500                         return buf;
501                 *--buf = '.';
502                 if (!--len)
503                         return buf;
504         }
505 }
506
507 #define MAXNAME 64
508
509 static void visit_one_node(xmlNode *node)
510 {
511         int len;
512         const unsigned char *content;
513         char buffer[MAXNAME];
514         const char *name;
515
516         content = node->content;
517         if (!content)
518                 return;
519
520         /* Trim whitespace at beginning */
521         while (isspace(*content))
522                 content++;
523
524         /* Trim whitespace at end */
525         len = strlen(content);
526         while (len && isspace(content[len-1]))
527                 len--;
528
529         if (!len)
530                 return;
531
532         /* Don't print out the node name if it is "text" */
533         if (!strcmp(node->name, "text"))
534                 node = node->parent;
535
536         name = nodename(node, buffer, sizeof(buffer));
537
538         entry(name, len, content);
539 }
540
541 static void traverse(xmlNode *node)
542 {
543         xmlNode *n;
544
545         for (n = node; n; n = n->next) {
546                 /* XML from libdivecomputer: 'dive' per new dive */
547                 if (!strcmp(n->name, "dive")) {
548                         dive_start();
549                         traverse(n->children);
550                         dive_end();
551                         continue;
552                 }
553
554                 /*
555                  * At least both libdivecomputer and Suunto
556                  * agree on "sample".
557                  *
558                  * Well - almost. Ignore case.
559                  */
560                 if (!strcasecmp(n->name, "sample")) {
561                         sample_start();
562                         traverse(n->children);
563                         sample_end();
564                         continue;
565                 }
566
567                 /* Anything else - just visit it and recurse */
568                 visit_one_node(n);
569                 traverse(n->children);
570         }
571 }
572
573 void parse_xml_file(const char *filename)
574 {
575         xmlDoc *doc;
576
577         doc = xmlReadFile(filename, NULL, 0);
578         if (!doc) {
579                 fprintf(stderr, "Failed to parse '%s'.\n", filename);
580                 return;
581         }
582
583         dive_start();
584         traverse(xmlDocGetRootElement(doc));
585         dive_end();
586         xmlFreeDoc(doc);
587         xmlCleanupParser();
588 }
589
590 void parse_xml_init(void)
591 {
592         LIBXML_TEST_VERSION
593 }