7 #include <libxml/parser.h>
8 #include <libxml/tree.h>
13 * Some silly typedefs to make our units very explicit.
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
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".
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.
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.
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.
44 * We don't actually use these all yet, so maybe they'll change, but
45 * I made a number of types as guidelines.
86 static int to_feet(depth_t depth)
88 return depth.mm * 0.00328084 + 0.5;
91 static int to_C(temperature_t temp)
95 return (temp.mkelvin - 273150) / 1000;
98 static int to_PSI(pressure_t pressure)
100 return pressure.mbar * 0.0145037738 + 0.5;
106 temperature_t temperature;
107 pressure_t tankpressure;
113 depth_t maxdepth, meandepth;
114 duration_t duration, surfacetime;
116 temperature_t airtemp, watertemp;
117 pressure_t beginning_pressure, end_pressure;
119 struct sample sample[];
122 static struct dive **dive_table;
123 static int nr_dives, nr_allocated;
125 static void record_dive(struct dive *dive)
127 if (nr_dives >= nr_allocated) {
128 nr_allocated = (nr_dives + 32) * 3 / 2;
129 dive_table = realloc(dive_table, nr_allocated * sizeof(struct dive *));
133 dive_table[nr_dives++] = dive;
136 static void show_dive(int nr, struct dive *dive)
141 tm = gmtime(&dive->when);
143 printf("Dive %d with %d samples at %02d:%02d:%02d %04d-%02d-%02d\n",
145 tm->tm_hour, tm->tm_min, tm->tm_sec,
146 tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday);
151 for (i = 0; i < dive->samples; i++) {
152 struct sample *s = dive->sample + i;
154 printf("%4d:%02d: %3d ft, %2d C, %4d PSI\n",
155 s->time.seconds / 60,
156 s->time.seconds % 60,
158 to_C(s->temperature),
159 to_PSI(s->tankpressure));
163 static int sortfn(const void *_a, const void *_b)
165 const struct dive *a = *(void **)_a;
166 const struct dive *b = *(void **)_b;
168 if (a->when < b->when)
170 if (a->when > b->when)
175 static void report_dives(void)
178 qsort(dive_table, nr_dives, sizeof(struct dive *), sortfn);
180 for (i = 0; i < nr_dives; i++)
181 show_dive(i+1, dive_table[i]);
184 static void nonmatch(const char *type, const char *fullname, const char *name, char *buffer)
187 printf("Unable to match %s '(%.*s)%s' (%s)\n", type,
188 (int) (name - fullname), fullname, name,
193 static const char *last_part(const char *name)
195 const char *p = strrchr(name, '.');
196 return p ? p+1 : name;
199 typedef void (*matchfn_t)(char *buffer, void *);
201 static int match(const char *pattern, const char *name, matchfn_t fn, char *buf, void *data)
203 if (strcasecmp(pattern, name))
210 * Dive info as it is being built up..
212 static int alloc_samples;
213 static struct dive *dive;
214 static struct sample *sample;
217 static time_t utc_mktime(struct tm *tm)
219 static const int mdays[] = {
220 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
222 int year = tm->tm_year;
223 int month = tm->tm_mon;
224 int day = tm->tm_mday;
226 /* First normalize relative to 1900 */
229 else if (year > 1900)
232 /* Normalized to Jan 1, 1970: unix time */
235 if (year < 0 || year > 129) /* algo only works for 1970-2099 */
237 if (month < 0 || month > 11) /* array bounds */
239 if (month < 2 || (year + 2) % 4)
241 if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_sec < 0)
243 return (year * 365 + (year + 1) / 4 + mdays[month] + day) * 24*60*60UL +
244 tm->tm_hour * 60*60 + tm->tm_min * 60 + tm->tm_sec;
247 static void divedate(char *buffer, void *_when)
250 time_t *when = _when;
252 if (sscanf(buffer, "%d.%d.%d", &d, &m, &y) == 3) {
256 if (tm.tm_sec | tm.tm_min | tm.tm_hour)
257 *when = utc_mktime(&tm);
262 static void divetime(char *buffer, void *_when)
265 time_t *when = _when;
267 if (sscanf(buffer, "%d:%d:%d", &h, &m, &s) >= 2) {
272 *when = utc_mktime(&tm);
277 /* Libdivecomputer: "2011-03-20 10:22:38" */
278 static void divedatetime(char *buffer, void *_when)
282 time_t *when = _when;
284 if (sscanf(buffer, "%d-%d-%d %d:%d:%d",
285 &y, &m, &d, &hr, &min, &sec) == 6) {
292 *when = utc_mktime(&tm);
308 static enum number_type integer_or_float(char *buffer, union int_or_float *res)
314 /* Integer or floating point? */
315 val = strtol(buffer, &end, 10);
316 if (val < 0 || end == buffer)
319 /* Looks like it might be floating point? */
322 fp = strtod(buffer, &end);
333 static void pressure(char *buffer, void *_press)
335 pressure_t *pressure = _press;
336 union int_or_float val;
338 switch (integer_or_float(buffer, &val)) {
340 /* Maybe it's in Bar? */
341 if (val.fp < 500.0) {
342 pressure->mbar = val.fp * 1000;
345 printf("Unknown fractional pressure reading %s\n", buffer);
350 * Random integer? Maybe in PSI? Or millibar already?
352 * We assume that 5 bar is a ridiculous tank pressure,
353 * so if it's smaller than 5000, it's in PSI..
356 pressure->mbar = val.i * 68.95;
359 pressure->mbar = val.i;
362 printf("Strange pressure reading %s\n", buffer);
367 static void depth(char *buffer, void *_depth)
369 depth_t *depth = _depth;
370 union int_or_float val;
372 switch (integer_or_float(buffer, &val)) {
373 /* Integer values are probably in feet */
375 depth->mm = 304.8 * val.i;
377 /* Float? Probably meters.. */
379 depth->mm = val.fp * 1000;
382 printf("Strange depth reading %s\n", buffer);
387 static void temperature(char *buffer, void *_temperature)
389 temperature_t *temperature = _temperature;
390 union int_or_float val;
392 switch (integer_or_float(buffer, &val)) {
393 /* C or F? Who knows? Let's default to Celsius */
398 /* Ignore zero. It means "none" */
403 temperature->mkelvin = (val.fp + 273.16) * 1000;
407 if (val.fp < 212.0) {
408 temperature->mkelvin = (val.fp + 459.67) * 5000/9;
411 /* Kelvin or already millikelvin */
414 temperature->mkelvin = val.fp;
417 printf("Strange temperature reading %s\n", buffer);
422 static void sampletime(char *buffer, void *_time)
426 duration_t *time = _time;
428 i = sscanf(buffer, "%d:%d", &min, &sec);
435 time->seconds = sec + min*60;
438 printf("Strange sample time reading %s\n", buffer);
443 static void duration(char *buffer, void *_time)
445 sampletime(buffer, _time);
448 static void ignore(char *buffer, void *_time)
452 /* We're in samples - try to convert the random xml value to something useful */
453 static void try_to_fill_sample(struct sample *sample, const char *name, char *buf)
455 const char *last = last_part(name);
457 if (match("pressure", last, pressure, buf, &sample->tankpressure))
459 if (match("cylpress", last, pressure, buf, &sample->tankpressure))
461 if (match("depth", last, depth, buf, &sample->depth))
463 if (match("temperature", last, temperature, buf, &sample->temperature))
465 if (match("sampletime", last, sampletime, buf, &sample->time))
467 if (match("time", last, sampletime, buf, &sample->time))
470 nonmatch("sample", name, last, buf);
473 /* We're in the top-level dive xml. Try to convert whatever value to a dive value */
474 static void try_to_fill_dive(struct dive *dive, const char *name, char *buf)
476 const char *last = last_part(name);
478 if (match("date", last, divedate, buf, &dive->when))
480 if (match("time", last, divetime, buf, &dive->when))
482 if (match("datetime", last, divedatetime, buf, &dive->when))
484 if (match("maxdepth", last, depth, buf, &dive->maxdepth))
486 if (match("meandepth", last, depth, buf, &dive->meandepth))
488 if (match("divetime", last, duration, buf, &dive->duration))
490 if (match("divetimesec", last, duration, buf, &dive->duration))
492 if (match("surfacetime", last, duration, buf, &dive->surfacetime))
494 if (match("airtemp", last, temperature, buf, &dive->airtemp))
496 if (match("watertemp", last, temperature, buf, &dive->watertemp))
498 if (match("cylinderstartpressure", last, pressure, buf, &dive->beginning_pressure))
500 if (match("cylinderendpressure", last, pressure, buf, &dive->end_pressure))
502 if (match("divenumber", last, ignore, buf, NULL))
504 if (match("diveseries", last, ignore, buf, NULL))
506 if (match("number", last, ignore, buf, NULL))
508 if (match("size", last, ignore, buf, NULL))
510 if (match("fingerprint", last, ignore, buf, NULL))
512 nonmatch("dive", name, last, buf);
515 static unsigned int dive_size(int samples)
517 return sizeof(struct dive) + samples*sizeof(struct sample);
521 * File boundaries are dive boundaries. But sometimes there are
522 * multiple dives per file, so there can be other events too that
523 * trigger a "new dive" marker and you may get some nesting due
524 * to that. Just ignore nesting levels.
526 static void dive_start(void)
534 size = dive_size(alloc_samples);
538 memset(dive, 0, size);
539 memset(&tm, 0, sizeof(tm));
542 static void dive_end(void)
550 static void sample_start(void)
557 if (nr >= alloc_samples) {
560 alloc_samples = (alloc_samples * 3)/2 + 10;
561 size = dive_size(alloc_samples);
562 dive = realloc(dive, size);
566 sample = dive->sample + nr;
567 memset(sample, 0, sizeof(*sample));
570 static void sample_end(void)
578 static void entry(const char *name, int size, const char *raw)
580 char *buf = malloc(size+1);
584 memcpy(buf, raw, size);
587 try_to_fill_sample(sample, name, buf);
591 try_to_fill_dive(dive, name, buf);
596 static const char *nodename(xmlNode *node, char *buf, int len)
599 if (!node || !node->name)
607 const char *name = node->name;
608 int i = strlen(name);
610 unsigned char c = name[i];
616 if (!node || !node->name)
626 static void visit_one_node(xmlNode *node)
629 const unsigned char *content;
630 char buffer[MAXNAME];
633 content = node->content;
637 /* Trim whitespace at beginning */
638 while (isspace(*content))
641 /* Trim whitespace at end */
642 len = strlen(content);
643 while (len && isspace(content[len-1]))
649 /* Don't print out the node name if it is "text" */
650 if (!strcmp(node->name, "text"))
653 name = nodename(node, buffer, sizeof(buffer));
655 entry(name, len, content);
658 static void traverse(xmlNode *node)
662 for (n = node; n; n = n->next) {
663 /* XML from libdivecomputer: 'dive' per new dive */
664 if (!strcmp(n->name, "dive")) {
666 traverse(n->children);
672 * At least both libdivecomputer and Suunto
675 * Well - almost. Ignore case.
677 if (!strcasecmp(n->name, "sample")) {
679 traverse(n->children);
684 /* Anything else - just visit it and recurse */
686 traverse(n->children);
690 static void parse(const char *filename)
694 doc = xmlReadFile(filename, NULL, 0);
696 fprintf(stderr, "Failed to parse '%s'.\n", filename);
701 traverse(xmlDocGetRootElement(doc));
707 static void parse_argument(const char *arg)
709 const char *p = arg+1;
717 fprintf(stderr, "Bad argument '%s'\n", arg);
723 int main(int argc, char **argv)
729 for (i = 1; i < argc; i++) {
730 const char *a = argv[i];