X-Git-Url: http://git.tdb.fi/?a=blobdiff_plain;f=parse-xml.c;h=5eabc353c17c47e4e68db5e0db2ded912f2b1437;hb=93d07f631aec470fb9f920f66ccb03ce699942b5;hp=86ef31ade91369ba0a1f3750acf03e49097dbaee;hpb=22fcef2ec7fd1efd3d1df2aba2b7b3af5d463288;p=ext%2Fsubsurface.git diff --git a/parse-xml.c b/parse-xml.c index 86ef31a..5eabc35 100644 --- a/parse-xml.c +++ b/parse-xml.c @@ -3,11 +3,13 @@ #include #include #include +#define __USE_XOPEN #include #include #include #include "dive.h" +#include "uemis.h" int verbose; @@ -16,7 +18,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; @@ -29,7 +31,7 @@ static void record_dive(struct dive *dive) dive_table.dives = dives; dive_table.allocated = allocated; } - dives[nr] = dive; + dives[nr] = fixup_dive(dive); dive_table.nr = nr+1; } @@ -62,17 +64,55 @@ static int match(const char *pattern, int plen, return 1; } + +struct units input_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. + */ +const struct units SI_units = { + .length = METERS, + .volume = LITER, + .pressure = BAR, + .temperature = CELSIUS, + .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 suunto; -static int event_index, gasmix_index; - -static time_t utc_mktime(struct tm *tm) +static int cylinder_index; + +static enum import_source { + UNKNOWN, + LIBDIVECOMPUTER, + SUUNTO, + UEMIS, + DIVINGLOG, + UDDF, +} import_source; + +time_t utc_mktime(struct tm *tm) { static const int mdays[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 @@ -164,13 +204,11 @@ static void divedatetime(char *buffer, void *_when) } union int_or_float { - long i; double fp; }; enum number_type { NEITHER, - INTEGER, FLOAT }; @@ -195,38 +233,40 @@ static enum number_type integer_or_float(char *buffer, union int_or_float *res) } } - res->i = val; - return INTEGER; + res->fp = val; + return FLOAT; } static void pressure(char *buffer, void *_press) { + double mbar; pressure_t *pressure = _press; union int_or_float val; switch (integer_or_float(buffer, &val)) { case FLOAT: - /* Maybe it's in Bar? */ - if (val.fp < 500.0) { - pressure->mbar = val.fp * 1000 + 0.5; + /* Just ignore zero values */ + if (!val.fp) + break; + 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; + if (mbar < 5000) + mbar = mbar * 1000; + break; + case PSI: + mbar = val.fp * 68.95; break; } - printf("Unknown fractional pressure reading %s\n", buffer); - break; - - case INTEGER: - /* - * Random integer? Maybe in PSI? Or millibar already? - * - * We assume that 5 bar is a ridiculous tank pressure, - * so if it's smaller than 5000, it's in PSI.. - */ - if (val.i < 5000) { - pressure->mbar = val.i * 68.95; + if (mbar > 5 && mbar < 500000) { + pressure->mbar = mbar + 0.5; break; } - pressure->mbar = val.i; - break; + /* fallthrough */ default: printf("Strange pressure reading %s\n", buffer); } @@ -239,12 +279,15 @@ static void depth(char *buffer, void *_depth) union int_or_float val; switch (integer_or_float(buffer, &val)) { - /* All values are probably in meters */ - case INTEGER: - val.fp = val.i; - /* fallthrough */ case FLOAT: - depth->mm = val.fp * 1000 + 0.5; + switch (input_units.length) { + case METERS: + depth->mm = val.fp * 1000 + 0.5; + break; + case FEET: + depth->mm = val.fp * 304.8 + 0.5; + break; + } break; default: printf("Strange depth reading %s\n", buffer); @@ -258,28 +301,22 @@ static void temperature(char *buffer, void *_temperature) union int_or_float val; switch (integer_or_float(buffer, &val)) { - /* C or F? Who knows? Let's default to Celsius */ - case INTEGER: - val.fp = val.i; - /* Fallthrough */ case FLOAT: /* Ignore zero. It means "none" */ if (!val.fp) break; /* Celsius */ - if (val.fp < 50.0) { + switch (input_units.temperature) { + case KELVIN: + temperature->mkelvin = val.fp * 1000; + break; + case CELSIUS: temperature->mkelvin = (val.fp + 273.15) * 1000 + 0.5; break; - } - /* Fahrenheit */ - if (val.fp < 212.0) { + case FAHRENHEIT: temperature->mkelvin = (val.fp + 459.67) * 5000/9; break; } - /* Kelvin or already millikelvin */ - if (val.fp < 1000.0) - val.fp *= 1000; - temperature->mkelvin = val.fp; break; default: printf("Strange temperature reading %s\n", buffer); @@ -319,10 +356,6 @@ static void percent(char *buffer, void *_fraction) union int_or_float val; switch (integer_or_float(buffer, &val)) { - /* C or F? Who knows? Let's default to Celsius */ - case INTEGER: - val.fp = val.i; - /* Fallthrough */ case FLOAT: if (val.fp <= 100.0) fraction->permille = val.fp * 10 + 0.5; @@ -340,7 +373,7 @@ static void gasmix(char *buffer, void *_fraction) /* libdivecomputer does negative percentages. */ if (*buffer == '-') return; - if (gasmix_index < MAX_MIXES) + if (cylinder_index < MAX_CYLINDERS) percent(buffer, _fraction); } @@ -349,23 +382,229 @@ static void gasmix_nitrogen(char *buffer, void *_gasmix) /* Ignore n2 percentages. There's no value in them. */ } +static void cylindersize(char *buffer, void *_volume) +{ + volume_t *volume = _volume; + union int_or_float val; + + switch (integer_or_float(buffer, &val)) { + case FLOAT: + volume->mliter = val.fp * 1000 + 0.5; + break; + + default: + printf("Strange volume reading %s\n", buffer); + break; + } + free(buffer); +} + static void utf8_string(char *buffer, void *_res) { *(char **)_res = buffer; } +/* + * Uemis water_pressure. In centibar. And when converting to + * depth, I'm just going to always use saltwater, because I + * think "true depth" is just stupid. From a diving standpoint, + * "true depth" is pretty much completely pointless, unless + * you're doing some kind of underwater surveying work. + * + * So I give water depths in "pressure depth", always assuming + * salt water. So one atmosphere per 10m. + */ +static void water_pressure(char *buffer, void *_depth) +{ + depth_t *depth = _depth; + union int_or_float val; + double atm, cm; + + switch (integer_or_float(buffer, &val)) { + case FLOAT: + if (!val.fp) + break; + /* cbar to atm */ + atm = bar_to_atm(val.fp * 10); + /* + * atm to cm. Why not mm? The precision just isn't + * there. + */ + cm = 100 * atm + 0.5; + if (cm > 0) { + depth->mm = 10 * (long)cm; + break; + } + default: + fprintf(stderr, "Strange water pressure '%s'\n", buffer); + } + free(buffer); +} + #define MATCH(pattern, fn, dest) \ match(pattern, strlen(pattern), name, len, fn, buf, dest) +static void get_index(char *buffer, void *_i) +{ + int *i = _i; + *i = atoi(buffer); + free(buffer); +} + +static void centibar(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 * 10 + 0.5; + break; + default: + fprintf(stderr, "Strange centibar pressure '%s'\n", buffer); + } + free(buffer); +} + +static void decicelsius(char *buffer, void *_temp) +{ + temperature_t *temp = _temp; + union int_or_float val; + + switch (integer_or_float(buffer, &val)) { + case FLOAT: + temp->mkelvin = (val.fp/10 + 273.15) * 1000 + 0.5; + break; + default: + fprintf(stderr, "Strange julian date: %s", buffer); + } + free(buffer); +} + +static int uemis_fill_sample(struct sample *sample, const char *name, int len, char *buf) +{ + return MATCH(".reading.dive_time", sampletime, &sample->time) || + MATCH(".reading.water_pressure", water_pressure, &sample->depth) || + MATCH(".reading.active_tank", get_index, &sample->cylinderindex) || + MATCH(".reading.tank_pressure", centibar, &sample->cylinderpressure) || + MATCH(".reading.dive_temperature", decicelsius, &sample->temperature) || + 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; + if (MATCH(".value", get_index, &event.value)) + 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) { int len = strlen(name); start_match("sample", name, buf); - if (MATCH(".sample.pressure", pressure, &sample->tankpressure)) + if (MATCH(".sample.pressure", pressure, &sample->cylinderpressure)) + return; + if (MATCH(".sample.cylpress", pressure, &sample->cylinderpressure)) return; - if (MATCH(".sample.cylpress", pressure, &sample->tankpressure)) + if (MATCH(".sample.cylinderindex", get_index, &sample->cylinderindex)) return; if (MATCH(".sample.depth", depth, &sample->depth)) return; @@ -378,30 +617,391 @@ static void try_to_fill_sample(struct sample *sample, const char *name, char *bu if (MATCH(".sample.time", sampletime, &sample->time)) return; + switch (import_source) { + case UEMIS: + if (uemis_fill_sample(sample, name, len, buf)) + 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; + } + nonmatch("sample", name, buf); } /* * Crazy suunto xml. Look at how those o2/he things match up. */ -static int suunto_dive_match(struct dive *dive, const char *name, int len, char *buf) +static int suunto_dive_match(struct dive **divep, const char *name, int len, char *buf) +{ + struct dive *dive = *divep; + + return MATCH(".o2pct", percent, &dive->cylinder[0].gasmix.o2) || + MATCH(".hepct_0", percent, &dive->cylinder[0].gasmix.he) || + MATCH(".o2pct_2", percent, &dive->cylinder[1].gasmix.o2) || + MATCH(".hepct_1", percent, &dive->cylinder[1].gasmix.he) || + MATCH(".o2pct_3", percent, &dive->cylinder[2].gasmix.o2) || + MATCH(".hepct_2", percent, &dive->cylinder[2].gasmix.he) || + MATCH(".o2pct_4", percent, &dive->cylinder[3].gasmix.o2) || + MATCH(".hepct_3", percent, &dive->cylinder[3].gasmix.he) || + MATCH(".cylindersize", cylindersize, &dive->cylinder[0].type.size) || + MATCH(".cylinderworkpressure", pressure, &dive->cylinder[0].type.workingpressure) || + 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 **divep, const char *name, int len, char *buf) +{ + struct dive *dive = *divep; + + 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); + free(buffer); + return val; +} + +static void uemis_length_unit(char *buffer, void *_unused) +{ + input_units.length = buffer_value(buffer) ? FEET : METERS; +} + +static void uemis_volume_unit(char *buffer, void *_unused) +{ + input_units.volume = buffer_value(buffer) ? CUFT : LITER; +} + +static void uemis_pressure_unit(char *buffer, void *_unused) +{ +#if 0 + input_units.pressure = buffer_value(buffer) ? PSI : BAR; +#endif +} + +static void uemis_temperature_unit(char *buffer, void *_unused) +{ + input_units.temperature = buffer_value(buffer) ? FAHRENHEIT : CELSIUS; +} + +static void uemis_weight_unit(char *buffer, void *_unused) +{ + input_units.weight = buffer_value(buffer) ? LBS : KG; +} + +static void uemis_time_unit(char *buffer, void *_unused) +{ +} + +static void uemis_date_unit(char *buffer, void *_unused) { - return MATCH(".o2pct", percent, &dive->gasmix[0].o2) || - MATCH(".hepct_0", percent, &dive->gasmix[0].he) || - MATCH(".o2pct_2", percent, &dive->gasmix[1].o2) || - MATCH(".hepct_1", percent, &dive->gasmix[1].he) || - MATCH(".o2pct_3", percent, &dive->gasmix[2].o2) || - MATCH(".hepct_2", percent, &dive->gasmix[2].he) || - MATCH(".o2pct_4", percent, &dive->gasmix[3].o2) || - MATCH(".hepct_3", percent, &dive->gasmix[3].he); +} + +/* Modified julian day, yay! */ +static void uemis_date_time(char *buffer, void *_when) +{ + time_t *when = _when; + union int_or_float val; + + switch (integer_or_float(buffer, &val)) { + case FLOAT: + *when = (val.fp - 40587) * 86400; + break; + default: + fprintf(stderr, "Strange julian date: %s", buffer); + } + free(buffer); +} + +/* + * Uemis doesn't know time zones. You need to do them as + * minutes, not hours. + * + * But that's ok, we don't track timezones yet either. We + * just turn everything into "localtime expressed as UTC". + */ +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 +} + +static void uemis_ts(char *buffer, void *_when) +{ + struct tm tm; + time_t *when = _when; + + memset(&tm, 0, sizeof(tm)); + sscanf(buffer,"%d-%d-%dT%d:%d:%d", + &tm.tm_year, &tm.tm_mon, &tm.tm_mday, + &tm.tm_hour, &tm.tm_min, &tm.tm_sec); + tm.tm_mon -= 1; + tm.tm_year -= 1900; + *when = utc_mktime(&tm); + +} + +static void uemis_duration(char *buffer, void *_duration) +{ + duration_t *duration = _duration; + duration->seconds = atof(buffer) * 60 + 0.5; +} + +/* 0 - air ; 1 - nitrox1 ; 2 - nitrox2 ; 3 = nitrox3 */ +static int uemis_gas_template; + +/* + * Christ. Uemis tank data is a total mess. + * + * We're passed a "virtual cylinder" (0 - 6) for the different + * Uemis tank cases ("air", "nitrox_1", "nitrox_2.{bottom,deco}" + * and "nitrox_3.{bottom,deco,travel}". We need to turn that + * into the actual cylinder data depending on the gas template, + * and ignore the ones that are irrelevant for that template. + * + * So for "template 2" (nitrox_2), we ignore virtual tanks 0-1 + * (which are "air" and "nitrox_1" respectively), and tanks 4-6 + * (which are the three "nitrox_3" tanks), and we turn virtual + * tanks 2/3 into actual tanks 0/1. + * + * Confused yet? + */ +static int uemis_cylinder_index(void *_cylinder) +{ + cylinder_t *cylinder = _cylinder; + unsigned int index = cylinder - dive->cylinder; + + if (index > 6) { + fprintf(stderr, "Uemis cylinder pointer calculations broken\n"); + return -1; + } + switch(uemis_gas_template) { + case 1: /* Dive uses tank 1 */ + index -= 1; + /* Fallthrough */ + case 0: /* Dive uses tank 0 */ + if (index) + index = -1; + break; + case 2: /* Dive uses tanks 2-3 */ + index -= 2; + if (index > 1) + index = -1; + break; + case 3: /* Dive uses tanks 4-6 */ + index -= 4; + if (index > 2) + index = -1; + break; + } + return index; +} + +static void uemis_cylindersize(char *buffer, void *_cylinder) +{ + int index = uemis_cylinder_index(_cylinder); + if (index >= 0) + cylindersize(buffer, &dive->cylinder[index].type.size); +} + +static void uemis_percent(char *buffer, void *_cylinder) +{ + int index = uemis_cylinder_index(_cylinder); + if (index >= 0) + percent(buffer, &dive->cylinder[index].gasmix.o2); +} + +static int uemis_dive_match(struct dive **divep, const char *name, int len, char *buf) +{ + struct dive *dive = *divep; + + 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) || + MATCH(".gas.template", get_index, &uemis_gas_template) || + MATCH(".air.bottom_tank.size", uemis_cylindersize, dive->cylinder + 0) || + MATCH(".air.bottom_tank.oxygen", uemis_percent, dive->cylinder + 0) || + MATCH(".nitrox_1.bottom_tank.size", uemis_cylindersize, dive->cylinder + 1) || + MATCH(".nitrox_1.bottom_tank.oxygen", uemis_percent, dive->cylinder + 1) || + MATCH(".nitrox_2.bottom_tank.size", uemis_cylindersize, dive->cylinder + 2) || + MATCH(".nitrox_2.bottom_tank.oxygen", uemis_percent, dive->cylinder + 2) || + MATCH(".nitrox_2.deco_tank.size", uemis_cylindersize, dive->cylinder + 3) || + MATCH(".nitrox_2.deco_tank.oxygen", uemis_percent, dive->cylinder + 3) || + MATCH(".nitrox_3.bottom_tank.size", uemis_cylindersize, dive->cylinder + 4) || + MATCH(".nitrox_3.bottom_tank.oxygen", uemis_percent, dive->cylinder + 4) || + MATCH(".nitrox_3.deco_tank.size", uemis_cylindersize, dive->cylinder + 5) || + MATCH(".nitrox_3.deco_tank.oxygen", uemis_percent, dive->cylinder + 5) || + MATCH(".nitrox_3.travel_tank.size", uemis_cylindersize, dive->cylinder + 6) || + MATCH(".nitrox_3.travel_tank.oxygen", uemis_percent, dive->cylinder + 6) || + MATCH(".dive.val.float", uemis_duration, &dive->duration) || + MATCH(".dive.val.ts", uemis_ts, &dive->when) || + MATCH(".dive.val.bin", uemis_parse_divelog_binary, divep) || + 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 **divep, const char *name, int len, char *buf) +{ + struct dive *dive = *divep; + + 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) +static void try_to_fill_dive(struct dive **divep, const char *name, char *buf) { int len = strlen(name); start_match("dive", name, buf); + + switch (import_source) { + case SUUNTO: + if (suunto_dive_match(divep, name, len, buf)) + return; + break; + + case UEMIS: + if (uemis_dive_match(divep, name, len, buf)) + return; + break; + + case DIVINGLOG: + if (divinglog_dive_match(divep, name, len, buf)) + return; + break; + + case UDDF: + if (uddf_dive_match(divep, name, len, buf)) + return; + break; + + default: + break; + } + + struct dive *dive = *divep; + + if (MATCH(".number", get_index, &dive->number)) + return; if (MATCH(".date", divedate, &dive->when)) return; if (MATCH(".time", divetime, &dive->when)) @@ -412,6 +1012,10 @@ static void try_to_fill_dive(struct dive *dive, const char *name, char *buf) return; if (MATCH(".meandepth", depth, &dive->meandepth)) return; + if (MATCH(".depth.max", depth, &dive->maxdepth)) + return; + if (MATCH(".depth.mean", depth, &dive->meandepth)) + return; if (MATCH(".duration", duration, &dive->duration)) return; if (MATCH(".divetime", duration, &dive->duration)) @@ -424,34 +1028,46 @@ static void try_to_fill_dive(struct dive *dive, const char *name, char *buf) return; if (MATCH(".watertemp", temperature, &dive->watertemp)) return; - if (MATCH(".cylinderstartpressure", pressure, &dive->beginning_pressure)) + if (MATCH(".temperature.air", temperature, &dive->airtemp)) return; - if (MATCH(".cylinderendpressure", pressure, &dive->end_pressure)) + if (MATCH(".temperature.water", temperature, &dive->watertemp)) + return; + if (MATCH(".cylinderstartpressure", pressure, &dive->cylinder[0].start)) + 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(".o2", gasmix, &dive->gasmix[gasmix_index].o2)) + if (MATCH(".cylinder.size", cylindersize, &dive->cylinder[cylinder_index].type.size)) + return; + if (MATCH(".cylinder.workpressure", pressure, &dive->cylinder[cylinder_index].type.workingpressure)) return; - if (MATCH(".n2", gasmix_nitrogen, &dive->gasmix[gasmix_index])) + if (MATCH(".cylinder.description", utf8_string, &dive->cylinder[cylinder_index].type.description)) return; - if (MATCH(".he", gasmix, &dive->gasmix[gasmix_index].he)) + if (MATCH(".cylinder.start", pressure, &dive->cylinder[cylinder_index].start)) + return; + if (MATCH(".cylinder.end", pressure, &dive->cylinder[cylinder_index].end)) return; - /* Suunto XML files are some crazy sh*t. */ - if (suunto && suunto_dive_match(dive, name, len, buf)) + if (MATCH(".o2", gasmix, &dive->cylinder[cylinder_index].gasmix.o2)) + return; + if (MATCH(".n2", gasmix_nitrogen, &dive->cylinder[cylinder_index].gasmix)) + return; + if (MATCH(".he", gasmix, &dive->cylinder[cylinder_index].gasmix.he)) return; nonmatch("dive", name, buf); } -static unsigned int dive_size(int samples) -{ - return sizeof(struct dive) + samples*sizeof(struct sample); -} - /* * File boundaries are dive boundaries. But sometimes there are * multiple dives per file, so there can be other events too that @@ -460,69 +1076,125 @@ static unsigned int dive_size(int samples) */ 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 char *generate_name(struct dive *dive) +static void sanitize_gasmix(struct gasmix *mix) { - int len; - struct tm *tm; - char buffer[256], *p; + unsigned int o2, he; - tm = gmtime(&dive->when); + o2 = mix->o2.permille; + he = mix->he.permille; - len = snprintf(buffer, sizeof(buffer), - "%04d-%02d-%02d " - "%02d:%02d:%02d " - "(%d ft, %d min)", - tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, - tm->tm_hour, tm->tm_min, tm->tm_sec, - to_feet(dive->maxdepth), dive->duration.seconds / 60); + /* Regular air: leave empty */ + if (!he) { + if (!o2) + return; + /* 20.9% or 21% O2 is just air */ + if (o2 >= 209 && o2 <= 210) { + mix->o2.permille = 0; + return; + } + } + + /* Sane mix? */ + if (o2 <= 1000 && he <= 1000 && o2+he <= 1000) + return; + fprintf(stderr, "Odd gasmix: %d O2 %d He\n", o2, he); + memset(mix, 0, sizeof(*mix)); +} + +/* + * See if the size/workingpressure looks like some standard cylinder + * size, eg "AL80". + */ +static void match_standard_cylinder(cylinder_type_t *type) +{ + double cuft; + int psi, len; + const char *fmt; + char buffer[20], *p; + + /* Do we already have a cylinder description? */ + if (type->description) + return; + + cuft = ml_to_cuft(type->size.mliter); + cuft *= to_ATM(type->workingpressure); + psi = to_PSI(type->workingpressure); + + switch (psi) { + case 2300 ... 2500: /* 2400 psi: LP tank */ + fmt = "LP%d"; + break; + case 2600 ... 2700: /* 2640 psi: LP+10% */ + fmt = "LP%d"; + break; + case 2900 ... 3100: /* 3000 psi: ALx tank */ + fmt = "AL%d"; + break; + case 3400 ... 3500: /* 3442 psi: HP tank */ + fmt = "HP%d"; + break; + case 3700 ... 3850: /* HP+10% */ + fmt = "HP%d+"; + break; + default: + return; + } + len = snprintf(buffer, sizeof(buffer), fmt, (int) (cuft+0.5)); p = malloc(len+1); if (!p) - exit(1); + return; memcpy(p, buffer, len+1); - return p; + type->description = p; } -static void sanitize_gasmix(struct dive *dive) + +/* + * There are two ways to give cylinder size information: + * - total amount of gas in cuft (depends on working pressure and physical size) + * - physical size + * + * where "physical size" is the one that actually matters and is sane. + * + * We internally use physical size only. But we save the workingpressure + * so that we can do the conversion if required. + */ +static void sanitize_cylinder_type(cylinder_type_t *type) { - int i; + double volume_of_air, atm, volume; - for (i = 0; i < MAX_MIXES; i++) { - gasmix_t *mix = dive->gasmix+i; - unsigned int o2, he; - - o2 = mix->o2.permille; - he = mix->he.permille; - - /* Regular air: leave empty */ - if (!he) { - if (!o2) - continue; - /* 20.9% or 21% O2 is just air */ - if (o2 >= 209 && o2 <= 210) { - mix->o2.permille = 0; - continue; - } - } + /* If we have no working pressure, it had *better* be just a physical size! */ + if (!type->workingpressure.mbar) + return; + + /* No size either? Nothing to go on */ + if (!type->size.mliter) + return; + + if (input_units.volume == CUFT || import_source == SUUNTO) { + /* confusing - we don't really start from ml but millicuft !*/ + volume_of_air = cuft_to_l(type->size.mliter); + atm = to_ATM(type->workingpressure); /* 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); +} + +static void sanitize_cylinder_info(struct dive *dive) +{ + int i; - /* Sane mix? */ - if (o2 <= 1000 && he <= 1000 && o2+he <= 1000) - continue; - fprintf(stderr, "Odd gasmix: %d O2 %d He\n", o2, he); - memset(mix, 0, sizeof(*mix)); + for (i = 0; i < MAX_CYLINDERS; i++) { + sanitize_gasmix(&dive->cylinder[i].gasmix); + sanitize_cylinder_type(&dive->cylinder[i].type); } } @@ -530,61 +1202,37 @@ static void dive_end(void) { if (!dive) return; - if (!dive->name) - dive->name = generate_name(dive); - sanitize_gasmix(dive); + sanitize_cylinder_info(dive); record_dive(dive); dive = NULL; - gasmix_index = 0; -} - -static void suunto_start(void) -{ - suunto++; -} - -static void suunto_end(void) -{ - suunto--; + cylinder_index = 0; } static void event_start(void) { + memset(&event, 0, sizeof(event)); + event.active = 1; } static void event_end(void) { - event_index++; + if (event.name && strcmp(event.name, "surface") != 0) + add_event(dive, event.time.seconds, event.type, event.flags, event.value, event.name); + event.active = 0; } -static void gasmix_start(void) +static void cylinder_start(void) { } -static void gasmix_end(void) +static void cylinder_end(void) { - gasmix_index++; + cylinder_index++; } 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) @@ -592,16 +1240,8 @@ static void sample_end(void) if (!dive) return; - if (sample->time.seconds > dive->duration.seconds) { - if (sample->depth.mm) - dive->duration = sample->time; - } - - if (sample->depth.mm > dive->maxdepth.mm) - dive->maxdepth.mm = sample->depth.mm; - + finish_sample(dive, sample); sample = NULL; - dive->samples++; } static void entry(const char *name, int size, const char *raw) @@ -612,12 +1252,16 @@ 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; } if (dive) { - try_to_fill_dive(dive, name, buf); + try_to_fill_dive(&dive, name, buf); return; } } @@ -700,6 +1344,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. @@ -709,11 +1390,23 @@ 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", gasmix_start, gasmix_end }, + { "gasmix", cylinder_start, cylinder_end }, + { "cylinder", cylinder_start, cylinder_end }, + { "P", sample_start, sample_end }, + + /* Import type recognition */ + { "SUUNTO", suunto_importer }, + { "Divinglog", DivingLog_importer }, + { "pre_dive", uemis_importer }, + { "dives", uemis_importer }, + { "uddf", uddf_importer }, + { NULL, } }; @@ -738,16 +1431,40 @@ static void traverse(xmlNode *root) } } -void parse_xml_file(const char *filename) +/* Per-file reset */ +static void reset_all(void) +{ + /* + * We reset the units for each file. You'd think it was + * a per-dive property, but I'm not going to trust people + * to do per-dive setup. If the xml does have per-dive + * data within one file, we might have to reset it per + * dive for that format. + */ + input_units = SI_units; + import_source = UNKNOWN; +} + +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)); dive_end();