X-Git-Url: http://git.tdb.fi/?a=blobdiff_plain;f=parse-xml.c;h=e6472735dcdd40ca91bf69daebcb035ac7ed9be7;hb=3a77eb85101a5fb1dc186b88a3a02d2ae27690c7;hp=ce0ae2844e136a9a95671229616322f6b37616ed;hpb=89593a542aaf0554cceda7f92ad51e9df7e4cbde;p=ext%2Fsubsurface.git diff --git a/parse-xml.c b/parse-xml.c index ce0ae28..e647273 100644 --- a/parse-xml.c +++ b/parse-xml.c @@ -16,7 +16,7 @@ struct dive_table dive_table; /* * Add a dive into the dive_table array */ -static void record_dive(struct dive *dive) +void record_dive(struct dive *dive) { int nr = dive_table.nr, allocated = dive_table.allocated; struct dive **dives = dive_table.dives; @@ -62,21 +62,16 @@ static int match(const char *pattern, int plen, return 1; } + +struct units input_units; + /* - * We keep our internal data in well-specified units, but - * the input may come in some random format. This keeps track - * of the incoming units. + * We're going to default to SI units for input. Yes, + * technically the SI unit for pressure is Pascal, but + * we default to bar (10^5 pascal), which people + * actually use. Similarly, C instead of Kelvin. */ -static struct units { - enum { METERS, FEET } length; - enum { LITER, CUFT } volume; - enum { BAR, PSI } pressure; - enum { CELSIUS, FAHRENHEIT } temperature; - enum { KG, LBS } weight; -} units; - -/* We're going to default to SI units for input */ -static const struct units SI_units = { +const struct units SI_units = { .length = METERS, .volume = LITER, .pressure = BAR, @@ -84,23 +79,38 @@ static const struct units SI_units = { .weight = KG }; +const struct units IMPERIAL_units = { + .length = FEET, + .volume = CUFT, + .pressure = PSI, + .temperature = FAHRENHEIT, + .weight = LBS +}; + /* * Dive info as it is being built up.. */ -static int alloc_samples; static struct dive *dive; static struct sample *sample; +static struct { + int active; + duration_t time; + int type, flags, value; + const char *name; +} event; static struct tm tm; -static int event_index, cylinder_index; +static int cylinder_index; static enum import_source { UNKNOWN, LIBDIVECOMPUTER, SUUNTO, UEMIS, + DIVINGLOG, + UDDF, } import_source; -static time_t utc_mktime(struct tm *tm) +time_t utc_mktime(struct tm *tm) { static const int mdays[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 @@ -236,7 +246,10 @@ static void pressure(char *buffer, void *_press) /* Just ignore zero values */ if (!val.fp) break; - switch (units.pressure) { + switch (input_units.pressure) { + case PASCAL: + mbar = val.fp / 100; + break; case BAR: /* Assume mbar, but if it's really small, it's bar */ mbar = val.fp; @@ -265,7 +278,7 @@ static void depth(char *buffer, void *_depth) switch (integer_or_float(buffer, &val)) { case FLOAT: - switch (units.length) { + switch (input_units.length) { case METERS: depth->mm = val.fp * 1000 + 0.5; break; @@ -291,7 +304,10 @@ static void temperature(char *buffer, void *_temperature) if (!val.fp) break; /* Celsius */ - switch (units.temperature) { + switch (input_units.temperature) { + case KELVIN: + temperature->mkelvin = val.fp * 1000; + break; case CELSIUS: temperature->mkelvin = (val.fp + 273.15) * 1000 + 0.5; break; @@ -399,11 +415,11 @@ static void utf8_string(char *buffer, void *_res) static void water_pressure(char *buffer, void *_depth) { depth_t *depth = _depth; - union int_or_float val; + union int_or_float val; double atm, cm; - switch (integer_or_float(buffer, &val)) { - case FLOAT: + switch (integer_or_float(buffer, &val)) { + case FLOAT: if (!val.fp) break; /* cbar to atm */ @@ -412,7 +428,7 @@ static void water_pressure(char *buffer, void *_depth) * atm to cm. Why not mm? The precision just isn't * there. */ - cm = 100 * (atm - 1) + 0.5; + cm = 100 * atm + 0.5; if (cm > 0) { depth->mm = 10 * (long)cm; break; @@ -451,10 +467,10 @@ static void centibar(char *buffer, void *_pressure) static void decicelsius(char *buffer, void *_temp) { temperature_t *temp = _temp; - union int_or_float val; + union int_or_float val; - switch (integer_or_float(buffer, &val)) { - case FLOAT: + switch (integer_or_float(buffer, &val)) { + case FLOAT: temp->mkelvin = (val.fp/10 + 273.15) * 1000 + 0.5; break; default: @@ -473,6 +489,107 @@ static int uemis_fill_sample(struct sample *sample, const char *name, int len, c 0; } +/* + * Divinglog is crazy. The temperatures are in celsius. EXCEPT + * for the sample temperatures, that are in Fahrenheit. + * WTF? + * + * Oh, and I think Diving Log *internally* probably kept them + * in celsius, because I'm seeing entries like + * + * 32.0 + * + * in there. Which is freezing, aka 0 degC. I bet the "0" is + * what Diving Log uses for "no temperature". + * + * So throw away crap like that. + */ +static void fahrenheit(char *buffer, void *_temperature) +{ + temperature_t *temperature = _temperature; + union int_or_float val; + + switch (integer_or_float(buffer, &val)) { + case FLOAT: + /* Floating point equality is evil, but works for small integers */ + if (val.fp == 32.0) + break; + temperature->mkelvin = (val.fp + 459.67) * 5000/9; + break; + default: + fprintf(stderr, "Crazy Diving Log temperature reading %s\n", buffer); + } + free(buffer); +} + +/* + * Did I mention how bat-shit crazy divinglog is? The sample + * pressures are in PSI. But the tank working pressure is in + * bar. WTF^2? + * + * Crazy stuff like this is why subsurface has everything in + * these inconvenient typed structures, and you have to say + * "pressure->mbar" to get the actual value. Exactly so that + * you can never have unit confusion. + */ +static void psi(char *buffer, void *_pressure) +{ + pressure_t *pressure = _pressure; + union int_or_float val; + + switch (integer_or_float(buffer, &val)) { + case FLOAT: + pressure->mbar = val.fp * 68.95 + 0.5; + break; + default: + fprintf(stderr, "Crazy Diving Log PSI reading %s\n", buffer); + } + free(buffer); +} + +static int divinglog_fill_sample(struct sample *sample, const char *name, int len, char *buf) +{ + return MATCH(".p.time", sampletime, &sample->time) || + MATCH(".p.depth", depth, &sample->depth) || + MATCH(".p.temp", fahrenheit, &sample->temperature) || + MATCH(".p.press1", psi, &sample->cylinderpressure) || + 0; +} + +static int uddf_fill_sample(struct sample *sample, const char *name, int len, char *buf) +{ + return MATCH(".divetime", sampletime, &sample->time) || + MATCH(".depth", depth, &sample->depth) || + MATCH(".temperature", temperature, &sample->temperature) || + 0; +} + +static void eventtime(char *buffer, void *_duration) +{ + duration_t *duration = _duration; + sampletime(buffer, duration); + if (sample) + duration->seconds += sample->time.seconds; +} + +static void try_to_fill_event(const char *name, char *buf) +{ + int len = strlen(name); + + start_match("event", name, buf); + if (MATCH(".event", utf8_string, &event.name)) + return; + if (MATCH(".name", utf8_string, &event.name)) + return; + if (MATCH(".time", eventtime, &event.time)) + return; + if (MATCH(".type", get_index, &event.type)) + return; + if (MATCH(".flags", get_index, &event.flags)) + return; + nonmatch("event", name, buf); +} + /* We're in samples - try to convert the random xml value to something useful */ static void try_to_fill_sample(struct sample *sample, const char *name, char *buf) { @@ -500,6 +617,16 @@ static void try_to_fill_sample(struct sample *sample, const char *name, char *bu return; break; + case DIVINGLOG: + if (divinglog_fill_sample(sample, name, len, buf)) + return; + break; + + case UDDF: + if (uddf_fill_sample(sample, name, len, buf)) + return; + break; + default: break; } @@ -525,6 +652,45 @@ static int suunto_dive_match(struct dive *dive, const char *name, int len, char 0; } +static const char *country, *city; + +static void divinglog_place(char *place, void *_location) +{ + char **location = _location; + char buffer[256], *p; + int len; + + len = snprintf(buffer, sizeof(buffer), + "%s%s%s%s%s", + place, + city ? ", " : "", + city ? city : "", + country ? ", " : "", + country ? country : ""); + + p = malloc(len+1); + memcpy(p, buffer, len+1); + *location = p; + + city = NULL; + country = NULL; +} + +static int divinglog_dive_match(struct dive *dive, const char *name, int len, char *buf) +{ + return MATCH(".divedate", divedate, &dive->when) || + MATCH(".entrytime", divetime, &dive->when) || + MATCH(".depth", depth, &dive->maxdepth) || + MATCH(".tanksize", cylindersize, &dive->cylinder[0].type.size) || + MATCH(".presw", pressure, &dive->cylinder[0].type.workingpressure) || + MATCH(".comments", utf8_string, &dive->notes) || + MATCH(".buddy.names", utf8_string, &dive->buddy) || + MATCH(".country.name", utf8_string, &country) || + MATCH(".city.name", utf8_string, &city) || + MATCH(".place.name", divinglog_place, &dive->location) || + 0; +} + static int buffer_value(char *buffer) { int val = atoi(buffer); @@ -534,29 +700,29 @@ static int buffer_value(char *buffer) static void uemis_length_unit(char *buffer, void *_unused) { - units.length = buffer_value(buffer) ? FEET : METERS; + input_units.length = buffer_value(buffer) ? FEET : METERS; } static void uemis_volume_unit(char *buffer, void *_unused) { - units.volume = buffer_value(buffer) ? CUFT : LITER; + input_units.volume = buffer_value(buffer) ? CUFT : LITER; } static void uemis_pressure_unit(char *buffer, void *_unused) { #if 0 - units.pressure = buffer_value(buffer) ? PSI : BAR; + input_units.pressure = buffer_value(buffer) ? PSI : BAR; #endif } static void uemis_temperature_unit(char *buffer, void *_unused) { - units.temperature = buffer_value(buffer) ? FAHRENHEIT : CELSIUS; + input_units.temperature = buffer_value(buffer) ? FAHRENHEIT : CELSIUS; } static void uemis_weight_unit(char *buffer, void *_unused) { - units.weight = buffer_value(buffer) ? LBS : KG; + input_units.weight = buffer_value(buffer) ? LBS : KG; } static void uemis_time_unit(char *buffer, void *_unused) @@ -571,11 +737,11 @@ static void uemis_date_unit(char *buffer, void *_unused) static void uemis_date_time(char *buffer, void *_when) { time_t *when = _when; - union int_or_float val; + union int_or_float val; - switch (integer_or_float(buffer, &val)) { - case FLOAT: - *when = (val.fp - 40587.5) * 86400; + switch (integer_or_float(buffer, &val)) { + case FLOAT: + *when = (val.fp - 40587) * 86400; break; default: fprintf(stderr, "Strange julian date: %s", buffer); @@ -592,10 +758,14 @@ static void uemis_date_time(char *buffer, void *_when) */ static void uemis_time_zone(char *buffer, void *_when) { +#if 0 /* seems like this is only used to display it correctly + * the stored time appears to be UTC */ + time_t *when = _when; signed char tz = atoi(buffer); *when += tz * 3600; +#endif } /* 0 - air ; 1 - nitrox1 ; 2 - nitrox2 ; 3 = nitrox3 */ @@ -664,13 +834,13 @@ static void uemis_percent(char *buffer, void *_cylinder) static int uemis_dive_match(struct dive *dive, const char *name, int len, char *buf) { - return MATCH(".units.length", uemis_length_unit, &units) || - MATCH(".units.volume", uemis_volume_unit, &units) || - MATCH(".units.pressure", uemis_pressure_unit, &units) || - MATCH(".units.temperature", uemis_temperature_unit, &units) || - MATCH(".units.weight", uemis_weight_unit, &units) || - MATCH(".units.time", uemis_time_unit, &units) || - MATCH(".units.date", uemis_date_unit, &units) || + return MATCH(".units.length", uemis_length_unit, &input_units) || + MATCH(".units.volume", uemis_volume_unit, &input_units) || + MATCH(".units.pressure", uemis_pressure_unit, &input_units) || + MATCH(".units.temperature", uemis_temperature_unit, &input_units) || + MATCH(".units.weight", uemis_weight_unit, &input_units) || + MATCH(".units.time", uemis_time_unit, &input_units) || + MATCH(".units.date", uemis_date_unit, &input_units) || MATCH(".date_time", uemis_date_time, &dive->when) || MATCH(".time_zone", uemis_time_zone, &dive->when) || MATCH(".ambient.temperature", decicelsius, &dive->airtemp) || @@ -692,12 +862,106 @@ static int uemis_dive_match(struct dive *dive, const char *name, int len, char * 0; } +/* + * Uddf specifies ISO 8601 time format. + * + * There are many variations on that. This handles the useful cases. + */ +static void uddf_datetime(char *buffer, void *_when) +{ + char c; + int y,m,d,hh,mm,ss; + time_t *when = _when; + struct tm tm = { 0 }; + int i; + + i = sscanf(buffer, "%d-%d-%d%c%d:%d:%d", &y, &m, &d, &c, &hh, &mm, &ss); + if (i == 7) + goto success; + ss = 0; + if (i == 6) + goto success; + + i = sscanf(buffer, "%04d%02d%02d%c%02d%02d%02d", &y, &m, &d, &c, &hh, &mm, &ss); + if (i == 7) + goto success; + ss = 0; + if (i == 6) + goto success; +bad_date: + printf("Bad date time %s\n", buffer); + free(buffer); + return; + +success: + if (c != 'T' && c != ' ') + goto bad_date; + tm.tm_year = y; + tm.tm_mon = m - 1; + tm.tm_mday = d; + tm.tm_hour = hh; + tm.tm_min = mm; + tm.tm_sec = ss; + *when = utc_mktime(&tm); + free(buffer); +} + +static int uddf_dive_match(struct dive *dive, const char *name, int len, char *buf) +{ + return MATCH(".datetime", uddf_datetime, &dive->when) || + MATCH(".diveduration", duration, &dive->duration) || + MATCH(".greatestdepth", depth, &dive->maxdepth) || + 0; +} + +static void gps_location(char *buffer, void *_dive) +{ + int i; + struct dive *dive = _dive; + double latitude, longitude; + + i = sscanf(buffer, "%lf %lf", &latitude, &longitude); + if (i == 2) { + dive->latitude = latitude; + dive->longitude = longitude; + } + free(buffer); +} + /* We're in the top-level dive xml. Try to convert whatever value to a dive value */ static void try_to_fill_dive(struct dive *dive, const char *name, char *buf) { int len = strlen(name); start_match("dive", name, buf); + + switch (import_source) { + case SUUNTO: + if (suunto_dive_match(dive, name, len, buf)) + return; + break; + + case UEMIS: + if (uemis_dive_match(dive, name, len, buf)) + return; + break; + + case DIVINGLOG: + if (divinglog_dive_match(dive, name, len, buf)) + return; + break; + + case UDDF: + if (uddf_dive_match(dive, name, len, buf)) + return; + break; + + default: + break; + } + + if (MATCH(".number", get_index, &dive->number)) + return; if (MATCH(".date", divedate, &dive->when)) return; if (MATCH(".time", divetime, &dive->when)) @@ -732,10 +996,16 @@ static void try_to_fill_dive(struct dive *dive, const char *name, char *buf) return; if (MATCH(".cylinderendpressure", pressure, &dive->cylinder[0].end)) return; + if (MATCH(".gps", gps_location, dive)) + return; if (MATCH(".location", utf8_string, &dive->location)) return; if (MATCH(".notes", utf8_string, &dive->notes)) return; + if (MATCH(".divemaster", utf8_string, &dive->divemaster)) + return; + if (MATCH(".buddy", utf8_string, &dive->buddy)) + return; if (MATCH(".cylinder.size", cylindersize, &dive->cylinder[cylinder_index].type.size)) return; @@ -755,20 +1025,6 @@ static void try_to_fill_dive(struct dive *dive, const char *name, char *buf) if (MATCH(".he", gasmix, &dive->cylinder[cylinder_index].gasmix.he)) return; - switch (import_source) { - case SUUNTO: - if (suunto_dive_match(dive, name, len, buf)) - return; - break; - - case UEMIS: - if (uemis_dive_match(dive, name, len, buf)) - return; - break; - - default: - break; - } nonmatch("dive", name, buf); } @@ -780,21 +1036,13 @@ static void try_to_fill_dive(struct dive *dive, const char *name, char *buf) */ static void dive_start(void) { - unsigned int size; - if (dive) return; - - alloc_samples = 5; - size = dive_size(alloc_samples); - dive = malloc(size); - if (!dive) - exit(1); - memset(dive, 0, size); + dive = alloc_dive(); memset(&tm, 0, sizeof(tm)); } -static void sanitize_gasmix(gasmix_t *mix) +static void sanitize_gasmix(struct gasmix *mix) { unsigned int o2, he; @@ -825,7 +1073,8 @@ static void sanitize_gasmix(gasmix_t *mix) */ static void match_standard_cylinder(cylinder_type_t *type) { - int psi, cuft, len; + double cuft; + int psi, len; const char *fmt; char buffer[20], *p; @@ -833,7 +1082,8 @@ static void match_standard_cylinder(cylinder_type_t *type) if (type->description) return; - cuft = type->size.mliter / 1000; + cuft = type->size.mliter / 28317.0; + cuft *= type->workingpressure.mbar / 1013.25; psi = type->workingpressure.mbar / 68.95; switch (psi) { @@ -841,7 +1091,7 @@ static void match_standard_cylinder(cylinder_type_t *type) fmt = "LP%d"; break; case 2600 ... 2700: /* 2640 psi: LP+10% */ - fmt = "LP%d+"; + fmt = "LP%d"; break; case 2900 ... 3100: /* 3000 psi: ALx tank */ fmt = "AL%d"; @@ -855,7 +1105,7 @@ static void match_standard_cylinder(cylinder_type_t *type) default: return; } - len = snprintf(buffer, sizeof(buffer), fmt, cuft); + len = snprintf(buffer, sizeof(buffer), fmt, (int) (cuft+0.5)); p = malloc(len+1); if (!p) return; @@ -886,14 +1136,15 @@ static void sanitize_cylinder_type(cylinder_type_t *type) if (!type->size.mliter) return; + if (input_units.volume == CUFT || import_source == SUUNTO) { + volume_of_air = type->size.mliter * 28.317; /* milli-cu ft to milliliter */ + atm = type->workingpressure.mbar / 1013.25; /* working pressure in atm */ + volume = volume_of_air / atm; /* milliliters at 1 atm: "true size" */ + type->size.mliter = volume + 0.5; + } + /* Ok, we have both size and pressure: try to match a description */ match_standard_cylinder(type); - - /* .. and let's assume that the 'size' was cu ft of air */ - volume_of_air = type->size.mliter * 28.317; /* milli-cu ft to milliliter */ - atm = type->workingpressure.mbar / 1013.25; /* working pressure in atm */ - volume = volume_of_air / atm; /* milliliters at 1 atm: "true size" */ - type->size.mliter = volume + 0.5; } static void sanitize_cylinder_info(struct dive *dive) @@ -916,33 +1167,17 @@ static void dive_end(void) cylinder_index = 0; } -static void suunto_start(void) -{ - import_source = SUUNTO; - units = SI_units; -} - -static void suunto_end(void) -{ -} - -static void uemis_start(void) -{ - import_source = UEMIS; - units = SI_units; -} - -static void uemis_end(void) -{ -} - static void event_start(void) { + memset(&event, 0, sizeof(event)); + event.active = 1; } static void event_end(void) { - event_index++; + if (event.name) + add_event(dive, event.time.seconds, event.type, event.flags, event.value, event.name); + event.active = 0; } static void cylinder_start(void) @@ -956,23 +1191,7 @@ static void cylinder_end(void) static void sample_start(void) { - int nr; - - if (!dive) - return; - nr = dive->samples; - if (nr >= alloc_samples) { - unsigned int size; - - alloc_samples = (alloc_samples * 3)/2 + 10; - size = dive_size(alloc_samples); - dive = realloc(dive, size); - if (!dive) - return; - } - sample = dive->sample + nr; - memset(sample, 0, sizeof(*sample)); - event_index = 0; + sample = prepare_sample(&dive); } static void sample_end(void) @@ -980,8 +1199,8 @@ static void sample_end(void) if (!dive) return; + finish_sample(dive, sample); sample = NULL; - dive->samples++; } static void entry(const char *name, int size, const char *raw) @@ -992,6 +1211,10 @@ static void entry(const char *name, int size, const char *raw) return; memcpy(buf, raw, size); buf[size] = 0; + if (event.active) { + try_to_fill_event(name, buf); + return; + } if (sample) { try_to_fill_sample(sample, name, buf); return; @@ -1080,6 +1303,43 @@ static void visit(xmlNode *n) traverse(n->children); } +static void suunto_importer(void) +{ + import_source = SUUNTO; + input_units = SI_units; +} + +static void uemis_importer(void) +{ + import_source = UEMIS; + input_units = SI_units; +} + +static void DivingLog_importer(void) +{ + import_source = DIVINGLOG; + + /* + * Diving Log units are really strange. + * + * Temperatures are in C, except in samples, + * when they are in Fahrenheit. Depths are in + * meters, an dpressure is in PSI in the samples, + * but in bar when it comes to working pressure. + * + * Crazy f*%^ morons. + */ + input_units = SI_units; +} + +static void uddf_importer(void) +{ + import_source = UDDF; + input_units = SI_units; + input_units.pressure = PASCAL; + input_units.temperature = KELVIN; +} + /* * I'm sure this could be done as some fancy DTD rules. * It's just not worth the headache. @@ -1089,14 +1349,22 @@ static struct nesting { void (*start)(void), (*end)(void); } nesting[] = { { "dive", dive_start, dive_end }, - { "SUUNTO", suunto_start, suunto_end }, + { "Dive", dive_start, dive_end }, { "sample", sample_start, sample_end }, + { "waypoint", sample_start, sample_end }, { "SAMPLE", sample_start, sample_end }, { "reading", sample_start, sample_end }, { "event", event_start, event_end }, { "gasmix", cylinder_start, cylinder_end }, { "cylinder", cylinder_start, cylinder_end }, - { "pre_dive", uemis_start, uemis_end }, + { "P", sample_start, sample_end }, + + /* Import type recognition */ + { "SUUNTO", suunto_importer }, + { "Divinglog", DivingLog_importer }, + { "pre_dive", uemis_importer }, + { "uddf", uddf_importer }, + { NULL, } }; @@ -1131,20 +1399,29 @@ static void reset_all(void) * data within one file, we might have to reset it per * dive for that format. */ - units = SI_units; + input_units = SI_units; import_source = UNKNOWN; } -void parse_xml_file(const char *filename) +void parse_xml_file(const char *filename, GError **error) { xmlDoc *doc; doc = xmlReadFile(filename, NULL, 0); if (!doc) { fprintf(stderr, "Failed to parse '%s'.\n", filename); + if (error != NULL) + { + *error = g_error_new(g_quark_from_string("subsurface"), + DIVE_ERROR_PARSE, + "Failed to parse '%s'", + filename); + } return; } - + /* we assume that the last (or only) filename passed as argument is a + * great filename to use as default when saving the dives */ + set_filename(filename); reset_all(); dive_start(); traverse(xmlDocGetRootElement(doc));