X-Git-Url: http://git.tdb.fi/?a=blobdiff_plain;f=parse-xml.c;h=6840610728e0e0f6da0f89d1218e11d7c9a101c5;hb=43530d397bd8d754202d0e97d13c32c1f0685f01;hp=b5f86fa8043c3d26c63e0d1537a3cbf809d97aab;hpb=1cc62d581158d52252f2ad7ef076811129510141;p=ext%2Fsubsurface.git diff --git a/parse-xml.c b/parse-xml.c index b5f86fa..6840610 100644 --- a/parse-xml.c +++ b/parse-xml.c @@ -3,11 +3,19 @@ #include #include #include +#include +#include +#include +#define __USE_XOPEN #include #include #include +#ifdef XSLT +#include +#endif #include "dive.h" +#include "uemis.h" int verbose; @@ -16,7 +24,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; @@ -90,22 +98,26 @@ const struct units IMPERIAL_units = { /* * 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 @@ -410,20 +422,20 @@ 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 */ - atm = (val.fp / 100) / 1.01325; + atm = bar_to_atm(val.fp * 10); /* * 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; @@ -462,10 +474,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: @@ -488,6 +500,16 @@ static int uemis_fill_sample(struct sample *sample, const char *name, int len, c * 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) { @@ -496,6 +518,9 @@ static void fahrenheit(char *buffer, void *_temperature) 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: @@ -504,12 +529,37 @@ static void fahrenheit(char *buffer, void *_temperature) 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", pressure, &sample->cylinderpressure) || + MATCH(".p.press1", psi, &sample->cylinderpressure) || 0; } @@ -521,6 +571,34 @@ static int uddf_fill_sample(struct sample *sample, const char *name, int len, ch 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) { @@ -531,6 +609,8 @@ static void try_to_fill_sample(struct sample *sample, const char *name, char *bu return; if (MATCH(".sample.cylpress", pressure, &sample->cylinderpressure)) return; + if (MATCH(".sample.cylinderindex", get_index, &sample->cylinderindex)) + return; if (MATCH(".sample.depth", depth, &sample->depth)) return; if (MATCH(".sample.temp", temperature, &sample->temperature)) @@ -565,24 +645,6 @@ static void try_to_fill_sample(struct sample *sample, const char *name, char *bu 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) -{ - 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) @@ -607,14 +669,17 @@ static void divinglog_place(char *place, void *_location) country = NULL; } -static int divinglog_dive_match(struct dive *dive, const char *name, int len, char *buf) +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(".tanktype", utf8_string, &dive->cylinder[0].type.description) || + 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) || @@ -667,10 +732,10 @@ 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: + switch (integer_or_float(buffer, &val)) { + case FLOAT: *when = (val.fp - 40587) * 86400; break; default: @@ -698,6 +763,27 @@ static void uemis_time_zone(char *buffer, void *_when) #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; @@ -762,8 +848,10 @@ static void uemis_percent(char *buffer, void *_cylinder) percent(buffer, &dive->cylinder[index].gasmix.o2); } -static int uemis_dive_match(struct dive *dive, const char *name, int len, char *buf) +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) || @@ -789,6 +877,9 @@ static int uemis_dive_match(struct dive *dive, const char *name, int len, char * 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; } @@ -836,39 +927,50 @@ success: free(buffer); } -static int uddf_dive_match(struct dive *dive, const char *name, int len, char *buf) +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(dive, name, len, buf)) - return; - break; - case UEMIS: - if (uemis_dive_match(dive, name, len, buf)) + if (uemis_dive_match(divep, name, len, buf)) return; break; case DIVINGLOG: - if (divinglog_dive_match(dive, name, len, buf)) + if (divinglog_dive_match(divep, name, len, buf)) return; break; case UDDF: - if (uddf_dive_match(dive, name, len, buf)) + if (uddf_dive_match(divep, name, len, buf)) return; break; @@ -876,6 +978,8 @@ static void try_to_fill_dive(struct dive *dive, const char *name, char *buf) break; } + struct dive *dive = *divep; + if (MATCH(".number", get_index, &dive->number)) return; if (MATCH(".date", divedate, &dive->when)) @@ -912,11 +1016,18 @@ 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(".rating", get_index, &dive->rating)) + return; if (MATCH(".cylinder.size", cylindersize, &dive->cylinder[cylinder_index].type.size)) return; if (MATCH(".cylinder.workpressure", pressure, &dive->cylinder[cylinder_index].type.workingpressure)) @@ -946,138 +1057,16 @@ 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) -{ - unsigned int o2, he; - - o2 = mix->o2.permille; - he = mix->he.permille; - - /* 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) -{ - int psi, cuft, len; - const char *fmt; - char buffer[20], *p; - - /* Do we already have a cylinder description? */ - if (type->description) - return; - - cuft = type->size.mliter / 1000; - psi = type->workingpressure.mbar / 68.95; - - 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, cuft); - p = malloc(len+1); - if (!p) - return; - memcpy(p, buffer, len+1); - type->description = p; -} - - -/* - * 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) -{ - double volume_of_air, atm, volume; - - /* 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; - - /* Ok, we have both size and pressure: try to match a description */ - match_standard_cylinder(type); - - 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; - } -} - -static void sanitize_cylinder_info(struct dive *dive) -{ - int i; - - for (i = 0; i < MAX_CYLINDERS; i++) { - sanitize_gasmix(&dive->cylinder[i].gasmix); - sanitize_cylinder_type(&dive->cylinder[i].type); - } -} - static void dive_end(void) { if (!dive) return; - sanitize_cylinder_info(dive); record_dive(dive); dive = NULL; cylinder_index = 0; @@ -1085,11 +1074,15 @@ static void dive_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 && strcmp(event.name, "surface") != 0) + add_event(dive, event.time.seconds, event.type, event.flags, event.value, event.name); + event.active = 0; } static void cylinder_start(void) @@ -1103,23 +1096,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) @@ -1127,8 +1104,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) @@ -1139,12 +1116,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; } } @@ -1227,12 +1208,6 @@ 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; @@ -1248,10 +1223,12 @@ static void DivingLog_importer(void) * * Temperatures are in C, except in samples, * when they are in Fahrenheit. Depths are in - * meters, but pressure is in PSI. + * 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; - input_units.pressure = PSI; } static void uddf_importer(void) @@ -1282,9 +1259,9 @@ static struct nesting { { "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, } @@ -1325,25 +1302,89 @@ static void reset_all(void) import_source = UNKNOWN; } +struct memblock { + void *buffer; + size_t size; +}; + +static int readfile(const char *filename, struct memblock *mem) +{ + int ret, fd = open(filename, O_RDONLY); + struct stat st; + + mem->buffer = NULL; + mem->size = 0; + + fd = open(filename, O_RDONLY); + if (fd < 0) + return fd; + ret = fstat(fd, &st); + if (ret < 0) + goto out; + ret = -EINVAL; + if (!S_ISREG(st.st_mode)) + goto out; + ret = 0; + if (!st.st_size) + goto out; + mem->buffer = malloc(st.st_size); + ret = -1; + errno = ENOMEM; + if (!mem->buffer) + goto out; + mem->size = st.st_size; + ret = read(fd, mem->buffer, mem->size); + if (ret < 0) + goto free; + if (ret == mem->size) + goto out; + errno = EIO; + ret = -1; +free: + free(mem->buffer); + mem->buffer = NULL; + mem->size = 0; +out: + close(fd); + return ret; +} + void parse_xml_file(const char *filename, GError **error) { xmlDoc *doc; + struct memblock mem; + + if (readfile(filename, &mem) < 0) { + fprintf(stderr, "Failed to read '%s'.\n", filename); + if (error) { + *error = g_error_new(g_quark_from_string("subsurface"), + DIVE_ERROR_PARSE, + "Failed to read '%s'", + filename); + } + return; + } - doc = xmlReadFile(filename, NULL, 0); + doc = xmlReadMemory(mem.buffer, mem.size, filename, NULL, 0); if (!doc) { fprintf(stderr, "Failed to parse '%s'.\n", filename); if (error != NULL) { - *error = g_error_new(g_quark_from_string("divelog"), - DIVE_ERROR_PARSE, - "Failed to parse '%s'", - filename); + *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(); +#ifdef XSLT + doc = test_xslt_transforms(doc); +#endif traverse(xmlDocGetRootElement(doc)); dive_end(); xmlFreeDoc(doc); @@ -1354,3 +1395,85 @@ void parse_xml_init(void) { LIBXML_TEST_VERSION } + +#ifdef XSLT + +/* Maybe we'll want a environment variable that can override this.. */ +static const char *xslt_path = XSLT ":xslt:."; + +static xsltStylesheetPtr try_get_stylesheet(const char *path, int len, const char *name) +{ + xsltStylesheetPtr ret; + int namelen = strlen(name); + char *filename = malloc(len+1+namelen+1); + + if (!filename) + return NULL; + + memcpy(filename, path, len); + filename[len] = G_DIR_SEPARATOR; + memcpy(filename + len + 1, name, namelen+1); + + ret = NULL; + if (!access(filename, R_OK)) + ret = xsltParseStylesheetFile(filename); + free(filename); + + return ret; +} + +static xsltStylesheetPtr get_stylesheet(const char *name) +{ + const char *path = xslt_path, *next; + + do { + int len; + xsltStylesheetPtr ret; + + next = strchr(path, ':'); + len = strlen(path); + if (next) { + len = next - path; + next++; + } + ret = try_get_stylesheet(path, len, name); + if (ret) + return ret; + } while ((path = next) != NULL); + + return NULL; +} + +static struct xslt_files { + const char *root; + const char *file; +} xslt_files[] = { + { "SUUNTO", "SuuntoSDM.xslt" }, + { "JDiveLog", "jdivelog2subsurface.xslt" }, + { NULL, } +}; + +xmlDoc *test_xslt_transforms(xmlDoc *doc) +{ + struct xslt_files *info = xslt_files; + xmlDoc *transformed; + xsltStylesheetPtr xslt = NULL; + xmlNode *root_element = xmlDocGetRootElement(doc); + + while ((info->root) && (strcasecmp(root_element->name, info->root) != 0)) { + info++; + } + + if (info->root) { + xmlSubstituteEntitiesDefault(1); + xslt = get_stylesheet(info->file); + if (xslt == NULL) + return doc; + transformed = xsltApplyStylesheet(xslt, doc, NULL); + xmlFreeDoc(doc); + xsltFreeStylesheet(xslt); + return transformed; + } + return doc; +} +#endif