summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Miika Turkia <miika.turkia@gmail.com>2018-01-06 22:24:38 +0200
committerGravatar mturkia <miika.turkia@gmail.com>2018-01-08 06:06:12 +0200
commitb808723f9cf6d65da0d662dd523d8088892dfdfc (patch)
treed200a975565640affb34095724ca58cb8c2fe496
parentfbbca93d53d25560711c556b6dfda3d84f28b3bd (diff)
downloadsubsurface-b808723f9cf6d65da0d662dd523d8088892dfdfc.tar.gz
Refactor CSV import
Move CSV import related functions into import-csv.c. Signed-off-by: Miika Turkia <miika.turkia@gmail.com>
-rw-r--r--core/CMakeLists.txt1
-rw-r--r--core/file.c854
-rw-r--r--core/import-csv.c854
-rw-r--r--core/import-csv.h33
-rw-r--r--desktop-widgets/divelogimportdialog.cpp1
5 files changed, 890 insertions, 853 deletions
diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt
index 87b276872..564dfe6ac 100644
--- a/core/CMakeLists.txt
+++ b/core/CMakeLists.txt
@@ -55,6 +55,7 @@ set(SUBSURFACE_CORE_LIB_SRCS
import-shearwater.c
import-cobalt.c
import-divinglog.c
+ import-csv.c
planner.c
plannernotes.c
profile.c
diff --git a/core/file.c b/core/file.c
index d604f94cd..9682bc0c3 100644
--- a/core/file.c
+++ b/core/file.c
@@ -14,6 +14,7 @@
#include "file.h"
#include "git-access.h"
#include "qthelperfromc.h"
+#include "import-csv.h"
/* For SAMPLE_* */
#include <libdivecomputer/parser.h>
@@ -114,55 +115,6 @@ int try_to_open_zip(const char *filename)
return success;
}
-static int try_to_xslt_open_csv(const char *filename, struct memblock *mem, const char *tag)
-{
- char *buf;
-
- if (mem->size == 0 && readfile(filename, mem) < 0)
- return report_error(translate("gettextFromC", "Failed to read '%s'"), filename);
-
- /* Surround the CSV file content with XML tags to enable XSLT
- * parsing
- *
- * Tag markers take: strlen("<></>") = 5
- */
- buf = realloc(mem->buffer, mem->size + 7 + strlen(tag) * 2);
- if (buf != NULL) {
- char *starttag = NULL;
- char *endtag = NULL;
-
- starttag = malloc(3 + strlen(tag));
- endtag = malloc(5 + strlen(tag));
-
- if (starttag == NULL || endtag == NULL) {
- /* this is fairly silly - so the malloc fails, but we strdup the error?
- * let's complete the silliness by freeing the two pointers in case one malloc succeeded
- * and the other one failed - this will make static analysis tools happy */
- free(starttag);
- free(endtag);
- free(buf);
- return report_error("Memory allocation failed in %s", __func__);
- }
-
- sprintf(starttag, "<%s>", tag);
- sprintf(endtag, "\n</%s>", tag);
-
- memmove(buf + 2 + strlen(tag), buf, mem->size);
- memcpy(buf, starttag, 2 + strlen(tag));
- memcpy(buf + mem->size + 2 + strlen(tag), endtag, 5 + strlen(tag));
- mem->size += (6 + 2 * strlen(tag));
- mem->buffer = buf;
-
- free(starttag);
- free(endtag);
- } else {
- free(mem->buffer);
- return report_error("realloc failed in %s", __func__);
- }
-
- return 0;
-}
-
int db_test_func(void *param, int columns, char **data, char **column)
{
(void) param;
@@ -266,55 +218,6 @@ timestamp_t parse_date(const char *date)
return utc_mktime(&tm);
}
-enum csv_format {
- CSV_DEPTH,
- CSV_TEMP,
- CSV_PRESSURE,
- POSEIDON_DEPTH,
- POSEIDON_TEMP,
- POSEIDON_SETPOINT,
- POSEIDON_SENSOR1,
- POSEIDON_SENSOR2,
- POSEIDON_NDL,
- POSEIDON_CEILING
-};
-
-static void add_sample_data(struct sample *sample, enum csv_format type, double val)
-{
- switch (type) {
- case CSV_DEPTH:
- sample->depth.mm = feet_to_mm(val);
- break;
- case CSV_TEMP:
- sample->temperature.mkelvin = F_to_mkelvin(val);
- break;
- case CSV_PRESSURE:
- sample->pressure[0].mbar = psi_to_mbar(val * 4);
- break;
- case POSEIDON_DEPTH:
- sample->depth.mm = lrint(val * 0.5 * 1000);
- break;
- case POSEIDON_TEMP:
- sample->temperature.mkelvin = C_to_mkelvin(val * 0.2);
- break;
- case POSEIDON_SETPOINT:
- sample->setpoint.mbar = lrint(val * 10);
- break;
- case POSEIDON_SENSOR1:
- sample->o2sensor[0].mbar = lrint(val * 10);
- break;
- case POSEIDON_SENSOR2:
- sample->o2sensor[1].mbar = lrint(val * 10);
- break;
- case POSEIDON_NDL:
- sample->ndl.seconds = lrint(val * 60);
- break;
- case POSEIDON_CEILING:
- sample->stopdepth.mm = lrint(val * 1000);
- break;
- }
-}
-
/*
* Cochran comma-separated values: depth in feet, temperature in F, pressure in psi.
*
@@ -331,60 +234,6 @@ static void add_sample_data(struct sample *sample, enum csv_format type, double
*
* Followed by the data values (all comma-separated, all one long line).
*/
-static int try_to_open_csv(struct memblock *mem, enum csv_format type)
-{
- char *p = mem->buffer;
- char *header[8];
- int i, time;
- timestamp_t date;
- struct dive *dive;
- struct divecomputer *dc;
-
- for (i = 0; i < 8; i++) {
- header[i] = p;
- p = strchr(p, ',');
- if (!p)
- return 0;
- p++;
- }
-
- date = parse_date(header[2]);
- if (!date)
- return 0;
-
- dive = alloc_dive();
- dive->when = date;
- dive->number = atoi(header[1]);
- dc = &dive->dc;
-
- time = 0;
- for (;;) {
- char *end;
- double val;
- struct sample *sample;
-
- errno = 0;
- val = strtod(p, &end); // FIXME == localization issue
- if (end == p)
- break;
- if (errno)
- break;
-
- sample = prepare_sample(dc);
- sample->time.seconds = time;
- add_sample_data(sample, type, val);
- finish_sample(dc);
-
- time++;
- dc->duration.seconds = time;
- if (*end != ',')
- break;
- p = end + 1;
- }
- record_dive(dive);
- return 1;
-}
-
static int open_by_filename(const char *filename, const char *fmt, struct memblock *mem)
{
// hack to be able to provide a comment for the translated string
@@ -541,704 +390,3 @@ int parse_file(const char *filename)
return ret;
}
-#define MATCH(buffer, pattern) \
- memcmp(buffer, pattern, strlen(pattern))
-
-char *parse_mkvi_value(const char *haystack, const char *needle)
-{
- char *lineptr, *valueptr, *endptr, *ret = NULL;
-
- if ((lineptr = strstr(haystack, needle)) != NULL) {
- if ((valueptr = strstr(lineptr, ": ")) != NULL) {
- valueptr += 2;
- }
- if ((endptr = strstr(lineptr, "\n")) != NULL) {
- char terminator = '\n';
- if (*(endptr - 1) == '\r') {
- --endptr;
- terminator = '\r';
- }
- *endptr = 0;
- ret = copy_string(valueptr);
- *endptr = terminator;
-
- }
- }
- return ret;
-}
-
-char *next_mkvi_key(const char *haystack)
-{
- char *valueptr, *endptr, *ret = NULL;
-
- if ((valueptr = strstr(haystack, "\n")) != NULL) {
- valueptr += 1;
- if ((endptr = strstr(valueptr, ": ")) != NULL) {
- *endptr = 0;
- ret = strdup(valueptr);
- *endptr = ':';
- }
- }
- return ret;
-}
-
-int parse_txt_file(const char *filename, const char *csv)
-{
- struct memblock memtxt, memcsv;
-
- if (readfile(filename, &memtxt) < 0) {
- return report_error(translate("gettextFromC", "Failed to read '%s'"), filename);
- }
-
- /*
- * MkVI stores some information in .txt file but the whole profile and events are stored in .csv file. First
- * make sure the input .txt looks like proper MkVI file, then start parsing the .csv.
- */
- if (MATCH(memtxt.buffer, "MkVI_Config") == 0) {
- int d, m, y, he;
- int hh = 0, mm = 0, ss = 0;
- int prev_depth = 0, cur_sampletime = 0, prev_setpoint = -1, prev_ndl = -1;
- bool has_depth = false, has_setpoint = false, has_ndl = false;
- char *lineptr, *key, *value;
- int cur_cylinder_index = 0;
- unsigned int prev_time = 0;
-
- struct dive *dive;
- struct divecomputer *dc;
- struct tm cur_tm;
-
- value = parse_mkvi_value(memtxt.buffer, "Dive started at");
- if (sscanf(value, "%d-%d-%d %d:%d:%d", &y, &m, &d, &hh, &mm, &ss) != 6) {
- free(value);
- return -1;
- }
- free(value);
- cur_tm.tm_year = y;
- cur_tm.tm_mon = m - 1;
- cur_tm.tm_mday = d;
- cur_tm.tm_hour = hh;
- cur_tm.tm_min = mm;
- cur_tm.tm_sec = ss;
-
- dive = alloc_dive();
- dive->when = utc_mktime(&cur_tm);;
- dive->dc.model = strdup("Poseidon MkVI Discovery");
- value = parse_mkvi_value(memtxt.buffer, "Rig Serial number");
- dive->dc.deviceid = atoi(value);
- free(value);
- dive->dc.divemode = CCR;
- dive->dc.no_o2sensors = 2;
-
- dive->cylinder[cur_cylinder_index].cylinder_use = OXYGEN;
- dive->cylinder[cur_cylinder_index].type.size.mliter = 3000;
- dive->cylinder[cur_cylinder_index].type.workingpressure.mbar = 200000;
- dive->cylinder[cur_cylinder_index].type.description = strdup("3l Mk6");
- dive->cylinder[cur_cylinder_index].gasmix.o2.permille = 1000;
- cur_cylinder_index++;
-
- dive->cylinder[cur_cylinder_index].cylinder_use = DILUENT;
- dive->cylinder[cur_cylinder_index].type.size.mliter = 3000;
- dive->cylinder[cur_cylinder_index].type.workingpressure.mbar = 200000;
- dive->cylinder[cur_cylinder_index].type.description = strdup("3l Mk6");
- value = parse_mkvi_value(memtxt.buffer, "Helium percentage");
- he = atoi(value);
- free(value);
- value = parse_mkvi_value(memtxt.buffer, "Nitrogen percentage");
- dive->cylinder[cur_cylinder_index].gasmix.o2.permille = (100 - atoi(value) - he) * 10;
- free(value);
- dive->cylinder[cur_cylinder_index].gasmix.he.permille = he * 10;
- cur_cylinder_index++;
-
- lineptr = strstr(memtxt.buffer, "Dive started at");
- while (lineptr && *lineptr && (lineptr = strchr(lineptr, '\n')) && ++lineptr) {
- key = next_mkvi_key(lineptr);
- if (!key)
- break;
- value = parse_mkvi_value(lineptr, key);
- if (!value) {
- free(key);
- break;
- }
- add_extra_data(&dive->dc, key, value);
- free(key);
- free(value);
- }
- dc = &dive->dc;
-
- /*
- * Read samples from the CSV file. A sample contains all the lines with same timestamp. The CSV file has
- * the following format:
- *
- * timestamp, type, value
- *
- * And following fields are of interest to us:
- *
- * 6 sensor1
- * 7 sensor2
- * 8 depth
- * 13 o2 tank pressure
- * 14 diluent tank pressure
- * 20 o2 setpoint
- * 39 water temp
- */
-
- if (readfile(csv, &memcsv) < 0) {
- free(dive);
- return report_error(translate("gettextFromC", "Poseidon import failed: unable to read '%s'"), csv);
- }
- lineptr = memcsv.buffer;
- for (;;) {
- struct sample *sample;
- int type;
- int value;
- int sampletime;
- int gaschange = 0;
-
- /* Collect all the information for one sample */
- sscanf(lineptr, "%d,%d,%d", &cur_sampletime, &type, &value);
-
- has_depth = false;
- has_setpoint = false;
- has_ndl = false;
- sample = prepare_sample(dc);
-
- /*
- * There was a bug in MKVI download tool that resulted in erroneous sample
- * times. This fix should work similarly as the vendor's own.
- */
-
- sample->time.seconds = cur_sampletime < 0xFFFF * 3 / 4 ? cur_sampletime : prev_time;
- prev_time = sample->time.seconds;
-
- do {
- int i = sscanf(lineptr, "%d,%d,%d", &sampletime, &type, &value);
- switch (i) {
- case 3:
- switch (type) {
- case 0:
- //Mouth piece position event: 0=OC, 1=CC, 2=UN, 3=NC
- switch (value) {
- case 0:
- add_event(dc, cur_sampletime, 0, 0, 0,
- QT_TRANSLATE_NOOP("gettextFromC", "Mouth piece position OC"));
- break;
- case 1:
- add_event(dc, cur_sampletime, 0, 0, 0,
- QT_TRANSLATE_NOOP("gettextFromC", "Mouth piece position CC"));
- break;
- case 2:
- add_event(dc, cur_sampletime, 0, 0, 0,
- QT_TRANSLATE_NOOP("gettextFromC", "Mouth piece position unknown"));
- break;
- case 3:
- add_event(dc, cur_sampletime, 0, 0, 0,
- QT_TRANSLATE_NOOP("gettextFromC", "Mouth piece position not connected"));
- break;
- }
- break;
- case 3:
- //Power Off event
- add_event(dc, cur_sampletime, 0, 0, 0,
- QT_TRANSLATE_NOOP("gettextFromC", "Power off"));
- break;
- case 4:
- //Battery State of Charge in %
-#ifdef SAMPLE_EVENT_BATTERY
- add_event(dc, cur_sampletime, SAMPLE_EVENT_BATTERY, 0,
- value, QT_TRANSLATE_NOOP("gettextFromC", "battery"));
-#endif
- break;
- case 6:
- //PO2 Cell 1 Average
- add_sample_data(sample, POSEIDON_SENSOR1, value);
- break;
- case 7:
- //PO2 Cell 2 Average
- add_sample_data(sample, POSEIDON_SENSOR2, value);
- break;
- case 8:
- //Depth * 2
- has_depth = true;
- prev_depth = value;
- add_sample_data(sample, POSEIDON_DEPTH, value);
- break;
- //9 Max Depth * 2
- //10 Ascent/Descent Rate * 2
- case 11:
- //Ascent Rate Alert >10 m/s
- add_event(dc, cur_sampletime, SAMPLE_EVENT_ASCENT, 0, 0,
- QT_TRANSLATE_NOOP("gettextFromC", "ascent"));
- break;
- case 13:
- //O2 Tank Pressure
- add_sample_pressure(sample, 0, lrint(value * 1000));
- break;
- case 14:
- //Diluent Tank Pressure
- add_sample_pressure(sample, 1, lrint(value * 1000));
- break;
- //16 Remaining dive time #1?
- //17 related to O2 injection
- case 20:
- //PO2 Setpoint
- has_setpoint = true;
- prev_setpoint = value;
- add_sample_data(sample, POSEIDON_SETPOINT, value);
- break;
- case 22:
- //End of O2 calibration Event: 0 = OK, 2 = Failed, rest of dive setpoint 1.0
- if (value == 2)
- add_event(dc, cur_sampletime, 0, SAMPLE_FLAGS_END, 0,
- QT_TRANSLATE_NOOP("gettextFromC", "O₂ calibration failed"));
- add_event(dc, cur_sampletime, 0, SAMPLE_FLAGS_END, 0,
- QT_TRANSLATE_NOOP("gettextFromC", "O₂ calibration"));
- break;
- case 25:
- //25 Max Ascent depth
- add_sample_data(sample, POSEIDON_CEILING, value);
- break;
- case 31:
- //Start of O2 calibration Event
- add_event(dc, cur_sampletime, 0, SAMPLE_FLAGS_BEGIN, 0,
- QT_TRANSLATE_NOOP("gettextFromC", "O₂ calibration"));
- break;
- case 37:
- //Remaining dive time #2?
- has_ndl = true;
- prev_ndl = value;
- add_sample_data(sample, POSEIDON_NDL, value);
- break;
- case 39:
- // Water Temperature in Celcius
- add_sample_data(sample, POSEIDON_TEMP, value);
- break;
- case 85:
- //He diluent part in %
- gaschange += value << 16;
- break;
- case 86:
- //O2 diluent part in %
- gaschange += value;
- break;
- //239 Unknown, maybe PO2 at sensor validation?
- //240 Unknown, maybe PO2 at sensor validation?
- //247 Unknown, maybe PO2 Cell 1 during pressure test
- //248 Unknown, maybe PO2 Cell 2 during pressure test
- //250 PO2 Cell 1
- //251 PO2 Cell 2
- default:
- break;
- } /* sample types */
- break;
- case EOF:
- break;
- default:
- printf("Unable to parse input: %s\n", lineptr);
- break;
- }
-
- lineptr = strchr(lineptr, '\n');
- if (!lineptr || !*lineptr)
- break;
- lineptr++;
-
- /* Grabbing next sample time */
- sscanf(lineptr, "%d,%d,%d", &cur_sampletime, &type, &value);
- } while (sampletime == cur_sampletime);
-
- if (gaschange)
- add_event(dc, cur_sampletime, SAMPLE_EVENT_GASCHANGE2, 0, gaschange,
- QT_TRANSLATE_NOOP("gettextFromC", "gaschange"));
- if (!has_depth)
- add_sample_data(sample, POSEIDON_DEPTH, prev_depth);
- if (!has_setpoint && prev_setpoint >= 0)
- add_sample_data(sample, POSEIDON_SETPOINT, prev_setpoint);
- if (!has_ndl && prev_ndl >= 0)
- add_sample_data(sample, POSEIDON_NDL, prev_ndl);
- finish_sample(dc);
-
- if (!lineptr || !*lineptr)
- break;
- }
- record_dive(dive);
- return 1;
- } else {
- return 0;
- }
-
- return 0;
-}
-
-#define MAXCOLDIGITS 10
-#define DATESTR 9
-#define TIMESTR 6
-
-int parse_dan_format(const char *filename, char **params, int pnr)
-{
- int ret = 0, i;
- size_t end_ptr = 0;
- struct memblock mem, mem_csv;
- char tmpbuf[MAXCOLDIGITS];
-
- char *ptr = NULL;
- char *NL = NULL;
- char *iter = NULL;
-
- if (readfile(filename, &mem) < 0)
- return report_error(translate("gettextFromC", "Failed to read '%s'"), filename);
-
- /* Determine NL (new line) character and the start of CSV data */
- if ((ptr = strstr(mem.buffer, "\r\n")) != NULL) {
- NL = "\r\n";
- } else if ((ptr = strstr(mem.buffer, "\n")) != NULL) {
- NL = "\n";
- } else {
- fprintf(stderr, "DEBUG: failed to detect NL\n");
- return -1;
- }
-
- while ((end_ptr < mem.size) && (ptr = strstr(mem.buffer + end_ptr, "ZDH"))) {
-
- /*
- * Process the dives, but let the last round be parsed
- * from C++ code
- */
-
- if (end_ptr)
- process_dives(true, false);
-
- mem_csv.buffer = malloc(mem.size + 1);
- mem_csv.size = mem.size;
-
- iter = ptr + 4;
- iter = strchr(iter, '|');
- if (iter) {
- memcpy(tmpbuf, ptr + 4, iter - ptr - 4);
- tmpbuf[iter - ptr - 4] = 0;
- params[pnr] = "diveNro";
- params[pnr + 1] = strdup(tmpbuf);
- }
-
- //fprintf(stderr, "DEBUG: BEGIN end_ptr %d round %d <%s>\n", end_ptr, j++, ptr);
- iter = ptr + 1;
- for (i = 0; i <= 4 && iter; ++i) {
- iter = strchr(iter, '|');
- if (iter)
- ++iter;
- }
-
- if (!iter) {
- fprintf(stderr, "DEBUG: Data corrupt");
- return -1;
- }
-
- /* Setting date */
- memcpy(tmpbuf, iter, 8);
- tmpbuf[8] = 0;
- params[pnr + 2] = "date";
- params[pnr + 3] = strdup(tmpbuf);
-
- /* Setting time, gotta prepend it with 1 to
- * avoid octal parsing (this is stripped out in
- * XSLT */
- tmpbuf[0] = '1';
- memcpy(tmpbuf + 1, iter + 8, 6);
- tmpbuf[7] = 0;
- params[pnr + 4] = "time";
- params[pnr + 5] = strdup(tmpbuf);
- params[pnr + 6] = NULL;
-
- ptr = strstr(ptr, "ZDP{");
- if (ptr && ptr[4] == '}') {
- end_ptr += ptr - (char *)mem_csv.buffer;
- return report_error(translate("gettextFromC", "No dive profile found from '%s'"), filename);
- }
- if (ptr)
- ptr = strstr(ptr, NL);
- if (ptr) {
- ptr += strlen(NL);
- } else {
- fprintf(stderr, "DEBUG: Data corrupt");
- return -1;
- }
- end_ptr = ptr - (char *)mem.buffer;
-
- /* Copy the current dive data to start of mem_csv buffer */
- memcpy(mem_csv.buffer, ptr, mem.size - (ptr - (char *)mem.buffer));
- ptr = strstr(mem_csv.buffer, "ZDP}");
- if (ptr) {
- *ptr = 0;
- } else {
- fprintf(stderr, "DEBUG: failed to find end ZDP\n");
- return -1;
- }
- mem_csv.size = ptr - (char*)mem_csv.buffer;
-
- if (try_to_xslt_open_csv(filename, &mem_csv, "csv"))
- return -1;
-
- ret |= parse_xml_buffer(filename, mem_csv.buffer, mem_csv.size, &dive_table, (const char **)params);
- end_ptr += ptr - (char *)mem_csv.buffer;
- free(mem_csv.buffer);
- }
-
- free(mem.buffer);
- for (i = 0; params[i]; i += 2)
- free(params[i + 1]);
-
- return ret;
-}
-
-int parse_csv_file(const char *filename, char **params, int pnr, const char *csvtemplate)
-{
- int ret, i;
- struct memblock mem;
- time_t now;
- struct tm *timep = NULL;
- char tmpbuf[MAXCOLDIGITS];
-
- /* Increase the limits for recursion and variables on XSLT
- * parsing */
- xsltMaxDepth = 30000;
-#if LIBXSLT_VERSION > 10126
- xsltMaxVars = 150000;
-#endif
-
- if (filename == NULL)
- return report_error("No CSV filename");
-
- mem.size = 0;
- if (!strcmp("DL7", csvtemplate)) {
- return parse_dan_format(filename, params, pnr);
- } else if (strcmp(params[0], "date")) {
- time(&now);
- timep = localtime(&now);
-
- strftime(tmpbuf, MAXCOLDIGITS, "%Y%m%d", timep);
- params[pnr++] = "date";
- params[pnr++] = strdup(tmpbuf);
-
- /* As the parameter is numeric, we need to ensure that the leading zero
- * is not discarded during the transform, thus prepend time with 1 */
-
- strftime(tmpbuf, MAXCOLDIGITS, "1%H%M", timep);
- params[pnr++] = "time";
- params[pnr++] = strdup(tmpbuf);
- params[pnr++] = NULL;
- }
-
- if (try_to_xslt_open_csv(filename, &mem, csvtemplate))
- return -1;
-
- /*
- * Lets print command line for manual testing with xsltproc if
- * verbosity level is high enough. The printed line needs the
- * input file added as last parameter.
- */
-
-#ifndef SUBSURFACE_MOBILE
- if (verbose >= 2) {
- fprintf(stderr, "(echo '<csv>'; cat %s;echo '</csv>') | xsltproc ", filename);
- for (i=0; params[i]; i+=2)
- fprintf(stderr, "--stringparam %s %s ", params[i], params[i+1]);
- fprintf(stderr, "%s/xslt/csv2xml.xslt -\n", SUBSURFACE_SOURCE);
- }
-#endif
- ret = parse_xml_buffer(filename, mem.buffer, mem.size, &dive_table, (const char **)params);
-
- free(mem.buffer);
- for (i = 0; params[i]; i += 2)
- free(params[i + 1]);
-
- return ret;
-}
-
-#define SBPARAMS 40
-int parse_seabear_log(const char *filename)
-{
- char *params[SBPARAMS];
- int pnr = 0;
-
- pnr = parse_seabear_header(filename, params, pnr);
-
- if (parse_seabear_csv_file(filename, params, pnr, "csv") < 0) {
- return -1;
- }
-
- return 0;
-}
-
-int parse_seabear_csv_file(const char *filename, char **params, int pnr, const char *csvtemplate)
-{
- int ret, i;
- struct memblock mem;
- time_t now;
- struct tm *timep = NULL;
- char *ptr, *ptr_old = NULL;
- char *NL = NULL;
- char tmpbuf[MAXCOLDIGITS];
-
- /* Increase the limits for recursion and variables on XSLT
- * parsing */
- xsltMaxDepth = 30000;
-#if LIBXSLT_VERSION > 10126
- xsltMaxVars = 150000;
-#endif
-
- time(&now);
- timep = localtime(&now);
-
- strftime(tmpbuf, MAXCOLDIGITS, "%Y%m%d", timep);
- params[pnr++] = "date";
- params[pnr++] = strdup(tmpbuf);
-
- /* As the parameter is numeric, we need to ensure that the leading zero
- * is not discarded during the transform, thus prepend time with 1 */
- strftime(tmpbuf, MAXCOLDIGITS, "1%H%M", timep);
- params[pnr++] = "time";
- params[pnr++] = strdup(tmpbuf);
-
-
- if (filename == NULL)
- return report_error("No CSV filename");
-
- if (readfile(filename, &mem) < 0)
- return report_error(translate("gettextFromC", "Failed to read '%s'"), filename);
-
- /* Determine NL (new line) character and the start of CSV data */
- ptr = mem.buffer;
- while ((ptr = strstr(ptr, "\r\n\r\n")) != NULL) {
- ptr_old = ptr;
- ptr += 1;
- NL = "\r\n";
- }
-
- if (!ptr_old) {
- ptr = mem.buffer;
- while ((ptr = strstr(ptr, "\n\n")) != NULL) {
- ptr_old = ptr;
- ptr += 1;
- NL = "\n";
- }
- ptr_old += 2;
- } else
- ptr_old += 4;
-
- /*
- * If file does not contain empty lines, it is not a valid
- * Seabear CSV file.
- */
- if (NL == NULL)
- return -1;
-
- /*
- * On my current sample of Seabear DC log file, the date is
- * without any identifier. Thus we must search for the previous
- * line and step through from there. That is the line after
- * Serial number.
- */
- ptr = strstr(mem.buffer, "Serial number:");
- if (ptr)
- ptr = strstr(ptr, NL);
-
- /*
- * Write date and time values to params array, if available in
- * the CSV header
- */
-
- if (ptr) {
- ptr += strlen(NL) + 2;
- /*
- * pnr is the index of NULL on the params as filled by
- * the init function. The two last entries should be
- * date and time. Here we overwrite them with the data
- * from the CSV header.
- */
-
- memcpy(params[pnr - 3], ptr, 4);
- memcpy(params[pnr - 3] + 4, ptr + 5, 2);
- memcpy(params[pnr - 3] + 6, ptr + 8, 2);
- params[pnr - 3][8] = 0;
-
- memcpy(params[pnr - 1] + 1, ptr + 11, 2);
- memcpy(params[pnr - 1] + 3, ptr + 14, 2);
- params[pnr - 1][5] = 0;
- }
-
- params[pnr++] = NULL;
-
- /* Move the CSV data to the start of mem buffer */
- memmove(mem.buffer, ptr_old, mem.size - (ptr_old - (char*)mem.buffer));
- mem.size = (int)mem.size - (ptr_old - (char*)mem.buffer);
-
- if (try_to_xslt_open_csv(filename, &mem, csvtemplate))
- return -1;
-
- /*
- * Lets print command line for manual testing with xsltproc if
- * verbosity level is high enough. The printed line needs the
- * input file added as last parameter.
- */
-
- if (verbose >= 2) {
- fprintf(stderr, "xsltproc ");
- for (i=0; params[i]; i+=2)
- fprintf(stderr, "--stringparam %s %s ", params[i], params[i+1]);
- fprintf(stderr, "xslt/csv2xml.xslt\n");
- }
-
- ret = parse_xml_buffer(filename, mem.buffer, mem.size, &dive_table, (const char **)params);
- free(mem.buffer);
- for (i = 0; params[i]; i += 2)
- free(params[i + 1]);
-
- return ret;
-}
-
-int parse_manual_file(const char *filename, char **params, int pnr)
-{
- struct memblock mem;
- time_t now;
- struct tm *timep;
- char curdate[9];
- char curtime[6];
- int ret, i;
-
-
- time(&now);
- timep = localtime(&now);
- strftime(curdate, DATESTR, "%Y%m%d", timep);
-
- /* As the parameter is numeric, we need to ensure that the leading zero
- * is not discarded during the transform, thus prepend time with 1 */
- strftime(curtime, TIMESTR, "1%H%M", timep);
-
-
- params[pnr++] = strdup("date");
- params[pnr++] = strdup(curdate);
- params[pnr++] = strdup("time");
- params[pnr++] = strdup(curtime);
- params[pnr++] = NULL;
-
- if (filename == NULL)
- return report_error("No manual CSV filename");
-
- mem.size = 0;
- if (try_to_xslt_open_csv(filename, &mem, "manualCSV"))
- return -1;
-
-#ifndef SUBSURFACE_MOBILE
- if (verbose >= 2) {
- fprintf(stderr, "(echo '<manualCSV>'; cat %s;echo '</manualCSV>') | xsltproc ", filename);
- for (i=0; params[i]; i+=2)
- fprintf(stderr, "--stringparam %s %s ", params[i], params[i+1]);
- fprintf(stderr, "%s/xslt/manualcsv2xml.xslt -\n", SUBSURFACE_SOURCE);
- }
-#endif
- ret = parse_xml_buffer(filename, mem.buffer, mem.size, &dive_table, (const char **)params);
-
- free(mem.buffer);
- for (i = 0; i < pnr - 2; ++i)
- free(params[i]);
- return ret;
-}
diff --git a/core/import-csv.c b/core/import-csv.c
new file mode 100644
index 000000000..c5b4ff8f3
--- /dev/null
+++ b/core/import-csv.c
@@ -0,0 +1,854 @@
+#include <unistd.h>
+#include <errno.h>
+#include <libdivecomputer/parser.h>
+
+#include "dive.h"
+#include "divelist.h"
+#include "file.h"
+#include "parse.h"
+#include "divelist.h"
+#include "gettext.h"
+#include "import-csv.h"
+#include "qthelperfromc.h"
+
+#define MATCH(buffer, pattern) \
+ memcmp(buffer, pattern, strlen(pattern))
+
+void add_sample_data(struct sample *sample, enum csv_format type, double val)
+{
+ switch (type) {
+ case CSV_DEPTH:
+ sample->depth.mm = feet_to_mm(val);
+ break;
+ case CSV_TEMP:
+ sample->temperature.mkelvin = F_to_mkelvin(val);
+ break;
+ case CSV_PRESSURE:
+ sample->pressure[0].mbar = psi_to_mbar(val * 4);
+ break;
+ case POSEIDON_DEPTH:
+ sample->depth.mm = lrint(val * 0.5 * 1000);
+ break;
+ case POSEIDON_TEMP:
+ sample->temperature.mkelvin = C_to_mkelvin(val * 0.2);
+ break;
+ case POSEIDON_SETPOINT:
+ sample->setpoint.mbar = lrint(val * 10);
+ break;
+ case POSEIDON_SENSOR1:
+ sample->o2sensor[0].mbar = lrint(val * 10);
+ break;
+ case POSEIDON_SENSOR2:
+ sample->o2sensor[1].mbar = lrint(val * 10);
+ break;
+ case POSEIDON_NDL:
+ sample->ndl.seconds = lrint(val * 60);
+ break;
+ case POSEIDON_CEILING:
+ sample->stopdepth.mm = lrint(val * 1000);
+ break;
+ }
+}
+
+int parse_dan_format(const char *filename, char **params, int pnr)
+{
+ int ret = 0, i;
+ size_t end_ptr = 0;
+ struct memblock mem, mem_csv;
+ char tmpbuf[MAXCOLDIGITS];
+
+ char *ptr = NULL;
+ char *NL = NULL;
+ char *iter = NULL;
+
+ if (readfile(filename, &mem) < 0)
+ return report_error(translate("gettextFromC", "Failed to read '%s'"), filename);
+
+ /* Determine NL (new line) character and the start of CSV data */
+ if ((ptr = strstr(mem.buffer, "\r\n")) != NULL) {
+ NL = "\r\n";
+ } else if ((ptr = strstr(mem.buffer, "\n")) != NULL) {
+ NL = "\n";
+ } else {
+ fprintf(stderr, "DEBUG: failed to detect NL\n");
+ return -1;
+ }
+
+ while ((end_ptr < mem.size) && (ptr = strstr(mem.buffer + end_ptr, "ZDH"))) {
+
+ /*
+ * Process the dives, but let the last round be parsed
+ * from C++ code
+ */
+
+ if (end_ptr)
+ process_dives(true, false);
+
+ mem_csv.buffer = malloc(mem.size + 1);
+ mem_csv.size = mem.size;
+
+ iter = ptr + 4;
+ iter = strchr(iter, '|');
+ if (iter) {
+ memcpy(tmpbuf, ptr + 4, iter - ptr - 4);
+ tmpbuf[iter - ptr - 4] = 0;
+ params[pnr] = "diveNro";
+ params[pnr + 1] = strdup(tmpbuf);
+ }
+
+ //fprintf(stderr, "DEBUG: BEGIN end_ptr %d round %d <%s>\n", end_ptr, j++, ptr);
+ iter = ptr + 1;
+ for (i = 0; i <= 4 && iter; ++i) {
+ iter = strchr(iter, '|');
+ if (iter)
+ ++iter;
+ }
+
+ if (!iter) {
+ fprintf(stderr, "DEBUG: Data corrupt");
+ return -1;
+ }
+
+ /* Setting date */
+ memcpy(tmpbuf, iter, 8);
+ tmpbuf[8] = 0;
+ params[pnr + 2] = "date";
+ params[pnr + 3] = strdup(tmpbuf);
+
+ /* Setting time, gotta prepend it with 1 to
+ * avoid octal parsing (this is stripped out in
+ * XSLT */
+ tmpbuf[0] = '1';
+ memcpy(tmpbuf + 1, iter + 8, 6);
+ tmpbuf[7] = 0;
+ params[pnr + 4] = "time";
+ params[pnr + 5] = strdup(tmpbuf);
+ params[pnr + 6] = NULL;
+
+ ptr = strstr(ptr, "ZDP{");
+ if (ptr && ptr[4] == '}') {
+ end_ptr += ptr - (char *)mem_csv.buffer;
+ return report_error(translate("gettextFromC", "No dive profile found from '%s'"), filename);
+ }
+ if (ptr)
+ ptr = strstr(ptr, NL);
+ if (ptr) {
+ ptr += strlen(NL);
+ } else {
+ fprintf(stderr, "DEBUG: Data corrupt");
+ return -1;
+ }
+ end_ptr = ptr - (char *)mem.buffer;
+
+ /* Copy the current dive data to start of mem_csv buffer */
+ memcpy(mem_csv.buffer, ptr, mem.size - (ptr - (char *)mem.buffer));
+ ptr = strstr(mem_csv.buffer, "ZDP}");
+ if (ptr) {
+ *ptr = 0;
+ } else {
+ fprintf(stderr, "DEBUG: failed to find end ZDP\n");
+ return -1;
+ }
+ mem_csv.size = ptr - (char*)mem_csv.buffer;
+
+ if (try_to_xslt_open_csv(filename, &mem_csv, "csv"))
+ return -1;
+
+ ret |= parse_xml_buffer(filename, mem_csv.buffer, mem_csv.size, &dive_table, (const char **)params);
+ end_ptr += ptr - (char *)mem_csv.buffer;
+ free(mem_csv.buffer);
+ }
+
+ free(mem.buffer);
+ for (i = 0; params[i]; i += 2)
+ free(params[i + 1]);
+
+ return ret;
+}
+
+int parse_csv_file(const char *filename, char **params, int pnr, const char *csvtemplate)
+{
+ int ret, i;
+ struct memblock mem;
+ time_t now;
+ struct tm *timep = NULL;
+ char tmpbuf[MAXCOLDIGITS];
+
+ /* Increase the limits for recursion and variables on XSLT
+ * parsing */
+ xsltMaxDepth = 30000;
+#if LIBXSLT_VERSION > 10126
+ xsltMaxVars = 150000;
+#endif
+
+ if (filename == NULL)
+ return report_error("No CSV filename");
+
+ mem.size = 0;
+ if (!strcmp("DL7", csvtemplate)) {
+ return parse_dan_format(filename, params, pnr);
+ } else if (strcmp(params[0], "date")) {
+ time(&now);
+ timep = localtime(&now);
+
+ strftime(tmpbuf, MAXCOLDIGITS, "%Y%m%d", timep);
+ params[pnr++] = "date";
+ params[pnr++] = strdup(tmpbuf);
+
+ /* As the parameter is numeric, we need to ensure that the leading zero
+ * is not discarded during the transform, thus prepend time with 1 */
+
+ strftime(tmpbuf, MAXCOLDIGITS, "1%H%M", timep);
+ params[pnr++] = "time";
+ params[pnr++] = strdup(tmpbuf);
+ params[pnr++] = NULL;
+ }
+
+ if (try_to_xslt_open_csv(filename, &mem, csvtemplate))
+ return -1;
+
+ /*
+ * Lets print command line for manual testing with xsltproc if
+ * verbosity level is high enough. The printed line needs the
+ * input file added as last parameter.
+ */
+
+#ifndef SUBSURFACE_MOBILE
+ if (verbose >= 2) {
+ fprintf(stderr, "(echo '<csv>'; cat %s;echo '</csv>') | xsltproc ", filename);
+ for (i=0; params[i]; i+=2)
+ fprintf(stderr, "--stringparam %s %s ", params[i], params[i+1]);
+ fprintf(stderr, "%s/xslt/csv2xml.xslt -\n", SUBSURFACE_SOURCE);
+ }
+#endif
+ ret = parse_xml_buffer(filename, mem.buffer, mem.size, &dive_table, (const char **)params);
+
+ free(mem.buffer);
+ for (i = 0; params[i]; i += 2)
+ free(params[i + 1]);
+
+ return ret;
+}
+
+
+int try_to_xslt_open_csv(const char *filename, struct memblock *mem, const char *tag)
+{
+ char *buf;
+
+ if (mem->size == 0 && readfile(filename, mem) < 0)
+ return report_error(translate("gettextFromC", "Failed to read '%s'"), filename);
+
+ /* Surround the CSV file content with XML tags to enable XSLT
+ * parsing
+ *
+ * Tag markers take: strlen("<></>") = 5
+ */
+ buf = realloc(mem->buffer, mem->size + 7 + strlen(tag) * 2);
+ if (buf != NULL) {
+ char *starttag = NULL;
+ char *endtag = NULL;
+
+ starttag = malloc(3 + strlen(tag));
+ endtag = malloc(5 + strlen(tag));
+
+ if (starttag == NULL || endtag == NULL) {
+ /* this is fairly silly - so the malloc fails, but we strdup the error?
+ * let's complete the silliness by freeing the two pointers in case one malloc succeeded
+ * and the other one failed - this will make static analysis tools happy */
+ free(starttag);
+ free(endtag);
+ free(buf);
+ return report_error("Memory allocation failed in %s", __func__);
+ }
+
+ sprintf(starttag, "<%s>", tag);
+ sprintf(endtag, "\n</%s>", tag);
+
+ memmove(buf + 2 + strlen(tag), buf, mem->size);
+ memcpy(buf, starttag, 2 + strlen(tag));
+ memcpy(buf + mem->size + 2 + strlen(tag), endtag, 5 + strlen(tag));
+ mem->size += (6 + 2 * strlen(tag));
+ mem->buffer = buf;
+
+ free(starttag);
+ free(endtag);
+ } else {
+ free(mem->buffer);
+ return report_error("realloc failed in %s", __func__);
+ }
+
+ return 0;
+}
+
+int try_to_open_csv(struct memblock *mem, enum csv_format type)
+{
+ char *p = mem->buffer;
+ char *header[8];
+ int i, time;
+ timestamp_t date;
+ struct dive *dive;
+ struct divecomputer *dc;
+
+ for (i = 0; i < 8; i++) {
+ header[i] = p;
+ p = strchr(p, ',');
+ if (!p)
+ return 0;
+ p++;
+ }
+
+ date = parse_date(header[2]);
+ if (!date)
+ return 0;
+
+ dive = alloc_dive();
+ dive->when = date;
+ dive->number = atoi(header[1]);
+ dc = &dive->dc;
+
+ time = 0;
+ for (;;) {
+ char *end;
+ double val;
+ struct sample *sample;
+
+ errno = 0;
+ val = strtod(p, &end); // FIXME == localization issue
+ if (end == p)
+ break;
+ if (errno)
+ break;
+
+ sample = prepare_sample(dc);
+ sample->time.seconds = time;
+ add_sample_data(sample, type, val);
+ finish_sample(dc);
+
+ time++;
+ dc->duration.seconds = time;
+ if (*end != ',')
+ break;
+ p = end + 1;
+ }
+ record_dive(dive);
+ return 1;
+}
+
+char *parse_mkvi_value(const char *haystack, const char *needle)
+{
+ char *lineptr, *valueptr, *endptr, *ret = NULL;
+
+ if ((lineptr = strstr(haystack, needle)) != NULL) {
+ if ((valueptr = strstr(lineptr, ": ")) != NULL) {
+ valueptr += 2;
+ }
+ if ((endptr = strstr(lineptr, "\n")) != NULL) {
+ char terminator = '\n';
+ if (*(endptr - 1) == '\r') {
+ --endptr;
+ terminator = '\r';
+ }
+ *endptr = 0;
+ ret = copy_string(valueptr);
+ *endptr = terminator;
+
+ }
+ }
+ return ret;
+}
+
+char *next_mkvi_key(const char *haystack)
+{
+ char *valueptr, *endptr, *ret = NULL;
+
+ if ((valueptr = strstr(haystack, "\n")) != NULL) {
+ valueptr += 1;
+ if ((endptr = strstr(valueptr, ": ")) != NULL) {
+ *endptr = 0;
+ ret = strdup(valueptr);
+ *endptr = ':';
+ }
+ }
+ return ret;
+}
+
+int parse_txt_file(const char *filename, const char *csv)
+{
+ struct memblock memtxt, memcsv;
+
+ if (readfile(filename, &memtxt) < 0) {
+ return report_error(translate("gettextFromC", "Failed to read '%s'"), filename);
+ }
+
+ /*
+ * MkVI stores some information in .txt file but the whole profile and events are stored in .csv file. First
+ * make sure the input .txt looks like proper MkVI file, then start parsing the .csv.
+ */
+ if (MATCH(memtxt.buffer, "MkVI_Config") == 0) {
+ int d, m, y, he;
+ int hh = 0, mm = 0, ss = 0;
+ int prev_depth = 0, cur_sampletime = 0, prev_setpoint = -1, prev_ndl = -1;
+ bool has_depth = false, has_setpoint = false, has_ndl = false;
+ char *lineptr, *key, *value;
+ int cur_cylinder_index = 0;
+ unsigned int prev_time = 0;
+
+ struct dive *dive;
+ struct divecomputer *dc;
+ struct tm cur_tm;
+
+ value = parse_mkvi_value(memtxt.buffer, "Dive started at");
+ if (sscanf(value, "%d-%d-%d %d:%d:%d", &y, &m, &d, &hh, &mm, &ss) != 6) {
+ free(value);
+ return -1;
+ }
+ free(value);
+ cur_tm.tm_year = y;
+ cur_tm.tm_mon = m - 1;
+ cur_tm.tm_mday = d;
+ cur_tm.tm_hour = hh;
+ cur_tm.tm_min = mm;
+ cur_tm.tm_sec = ss;
+
+ dive = alloc_dive();
+ dive->when = utc_mktime(&cur_tm);;
+ dive->dc.model = strdup("Poseidon MkVI Discovery");
+ value = parse_mkvi_value(memtxt.buffer, "Rig Serial number");
+ dive->dc.deviceid = atoi(value);
+ free(value);
+ dive->dc.divemode = CCR;
+ dive->dc.no_o2sensors = 2;
+
+ dive->cylinder[cur_cylinder_index].cylinder_use = OXYGEN;
+ dive->cylinder[cur_cylinder_index].type.size.mliter = 3000;
+ dive->cylinder[cur_cylinder_index].type.workingpressure.mbar = 200000;
+ dive->cylinder[cur_cylinder_index].type.description = strdup("3l Mk6");
+ dive->cylinder[cur_cylinder_index].gasmix.o2.permille = 1000;
+ cur_cylinder_index++;
+
+ dive->cylinder[cur_cylinder_index].cylinder_use = DILUENT;
+ dive->cylinder[cur_cylinder_index].type.size.mliter = 3000;
+ dive->cylinder[cur_cylinder_index].type.workingpressure.mbar = 200000;
+ dive->cylinder[cur_cylinder_index].type.description = strdup("3l Mk6");
+ value = parse_mkvi_value(memtxt.buffer, "Helium percentage");
+ he = atoi(value);
+ free(value);
+ value = parse_mkvi_value(memtxt.buffer, "Nitrogen percentage");
+ dive->cylinder[cur_cylinder_index].gasmix.o2.permille = (100 - atoi(value) - he) * 10;
+ free(value);
+ dive->cylinder[cur_cylinder_index].gasmix.he.permille = he * 10;
+ cur_cylinder_index++;
+
+ lineptr = strstr(memtxt.buffer, "Dive started at");
+ while (lineptr && *lineptr && (lineptr = strchr(lineptr, '\n')) && ++lineptr) {
+ key = next_mkvi_key(lineptr);
+ if (!key)
+ break;
+ value = parse_mkvi_value(lineptr, key);
+ if (!value) {
+ free(key);
+ break;
+ }
+ add_extra_data(&dive->dc, key, value);
+ free(key);
+ free(value);
+ }
+ dc = &dive->dc;
+
+ /*
+ * Read samples from the CSV file. A sample contains all the lines with same timestamp. The CSV file has
+ * the following format:
+ *
+ * timestamp, type, value
+ *
+ * And following fields are of interest to us:
+ *
+ * 6 sensor1
+ * 7 sensor2
+ * 8 depth
+ * 13 o2 tank pressure
+ * 14 diluent tank pressure
+ * 20 o2 setpoint
+ * 39 water temp
+ */
+
+ if (readfile(csv, &memcsv) < 0) {
+ free(dive);
+ return report_error(translate("gettextFromC", "Poseidon import failed: unable to read '%s'"), csv);
+ }
+ lineptr = memcsv.buffer;
+ for (;;) {
+ struct sample *sample;
+ int type;
+ int value;
+ int sampletime;
+ int gaschange = 0;
+
+ /* Collect all the information for one sample */
+ sscanf(lineptr, "%d,%d,%d", &cur_sampletime, &type, &value);
+
+ has_depth = false;
+ has_setpoint = false;
+ has_ndl = false;
+ sample = prepare_sample(dc);
+
+ /*
+ * There was a bug in MKVI download tool that resulted in erroneous sample
+ * times. This fix should work similarly as the vendor's own.
+ */
+
+ sample->time.seconds = cur_sampletime < 0xFFFF * 3 / 4 ? cur_sampletime : prev_time;
+ prev_time = sample->time.seconds;
+
+ do {
+ int i = sscanf(lineptr, "%d,%d,%d", &sampletime, &type, &value);
+ switch (i) {
+ case 3:
+ switch (type) {
+ case 0:
+ //Mouth piece position event: 0=OC, 1=CC, 2=UN, 3=NC
+ switch (value) {
+ case 0:
+ add_event(dc, cur_sampletime, 0, 0, 0,
+ QT_TRANSLATE_NOOP("gettextFromC", "Mouth piece position OC"));
+ break;
+ case 1:
+ add_event(dc, cur_sampletime, 0, 0, 0,
+ QT_TRANSLATE_NOOP("gettextFromC", "Mouth piece position CC"));
+ break;
+ case 2:
+ add_event(dc, cur_sampletime, 0, 0, 0,
+ QT_TRANSLATE_NOOP("gettextFromC", "Mouth piece position unknown"));
+ break;
+ case 3:
+ add_event(dc, cur_sampletime, 0, 0, 0,
+ QT_TRANSLATE_NOOP("gettextFromC", "Mouth piece position not connected"));
+ break;
+ }
+ break;
+ case 3:
+ //Power Off event
+ add_event(dc, cur_sampletime, 0, 0, 0,
+ QT_TRANSLATE_NOOP("gettextFromC", "Power off"));
+ break;
+ case 4:
+ //Battery State of Charge in %
+#ifdef SAMPLE_EVENT_BATTERY
+ add_event(dc, cur_sampletime, SAMPLE_EVENT_BATTERY, 0,
+ value, QT_TRANSLATE_NOOP("gettextFromC", "battery"));
+#endif
+ break;
+ case 6:
+ //PO2 Cell 1 Average
+ add_sample_data(sample, POSEIDON_SENSOR1, value);
+ break;
+ case 7:
+ //PO2 Cell 2 Average
+ add_sample_data(sample, POSEIDON_SENSOR2, value);
+ break;
+ case 8:
+ //Depth * 2
+ has_depth = true;
+ prev_depth = value;
+ add_sample_data(sample, POSEIDON_DEPTH, value);
+ break;
+ //9 Max Depth * 2
+ //10 Ascent/Descent Rate * 2
+ case 11:
+ //Ascent Rate Alert >10 m/s
+ add_event(dc, cur_sampletime, SAMPLE_EVENT_ASCENT, 0, 0,
+ QT_TRANSLATE_NOOP("gettextFromC", "ascent"));
+ break;
+ case 13:
+ //O2 Tank Pressure
+ add_sample_pressure(sample, 0, lrint(value * 1000));
+ break;
+ case 14:
+ //Diluent Tank Pressure
+ add_sample_pressure(sample, 1, lrint(value * 1000));
+ break;
+ //16 Remaining dive time #1?
+ //17 related to O2 injection
+ case 20:
+ //PO2 Setpoint
+ has_setpoint = true;
+ prev_setpoint = value;
+ add_sample_data(sample, POSEIDON_SETPOINT, value);
+ break;
+ case 22:
+ //End of O2 calibration Event: 0 = OK, 2 = Failed, rest of dive setpoint 1.0
+ if (value == 2)
+ add_event(dc, cur_sampletime, 0, SAMPLE_FLAGS_END, 0,
+ QT_TRANSLATE_NOOP("gettextFromC", "O₂ calibration failed"));
+ add_event(dc, cur_sampletime, 0, SAMPLE_FLAGS_END, 0,
+ QT_TRANSLATE_NOOP("gettextFromC", "O₂ calibration"));
+ break;
+ case 25:
+ //25 Max Ascent depth
+ add_sample_data(sample, POSEIDON_CEILING, value);
+ break;
+ case 31:
+ //Start of O2 calibration Event
+ add_event(dc, cur_sampletime, 0, SAMPLE_FLAGS_BEGIN, 0,
+ QT_TRANSLATE_NOOP("gettextFromC", "O₂ calibration"));
+ break;
+ case 37:
+ //Remaining dive time #2?
+ has_ndl = true;
+ prev_ndl = value;
+ add_sample_data(sample, POSEIDON_NDL, value);
+ break;
+ case 39:
+ // Water Temperature in Celcius
+ add_sample_data(sample, POSEIDON_TEMP, value);
+ break;
+ case 85:
+ //He diluent part in %
+ gaschange += value << 16;
+ break;
+ case 86:
+ //O2 diluent part in %
+ gaschange += value;
+ break;
+ //239 Unknown, maybe PO2 at sensor validation?
+ //240 Unknown, maybe PO2 at sensor validation?
+ //247 Unknown, maybe PO2 Cell 1 during pressure test
+ //248 Unknown, maybe PO2 Cell 2 during pressure test
+ //250 PO2 Cell 1
+ //251 PO2 Cell 2
+ default:
+ break;
+ } /* sample types */
+ break;
+ case EOF:
+ break;
+ default:
+ printf("Unable to parse input: %s\n", lineptr);
+ break;
+ }
+
+ lineptr = strchr(lineptr, '\n');
+ if (!lineptr || !*lineptr)
+ break;
+ lineptr++;
+
+ /* Grabbing next sample time */
+ sscanf(lineptr, "%d,%d,%d", &cur_sampletime, &type, &value);
+ } while (sampletime == cur_sampletime);
+
+ if (gaschange)
+ add_event(dc, cur_sampletime, SAMPLE_EVENT_GASCHANGE2, 0, gaschange,
+ QT_TRANSLATE_NOOP("gettextFromC", "gaschange"));
+ if (!has_depth)
+ add_sample_data(sample, POSEIDON_DEPTH, prev_depth);
+ if (!has_setpoint && prev_setpoint >= 0)
+ add_sample_data(sample, POSEIDON_SETPOINT, prev_setpoint);
+ if (!has_ndl && prev_ndl >= 0)
+ add_sample_data(sample, POSEIDON_NDL, prev_ndl);
+ finish_sample(dc);
+
+ if (!lineptr || !*lineptr)
+ break;
+ }
+ record_dive(dive);
+ return 1;
+ } else {
+ return 0;
+ }
+
+ return 0;
+}
+
+#define DATESTR 9
+#define TIMESTR 6
+
+#define SBPARAMS 40
+int parse_seabear_log(const char *filename)
+{
+ char *params[SBPARAMS];
+ int pnr = 0;
+
+ pnr = parse_seabear_header(filename, params, pnr);
+
+ if (parse_seabear_csv_file(filename, params, pnr, "csv") < 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+
+int parse_seabear_csv_file(const char *filename, char **params, int pnr, const char *csvtemplate)
+{
+ int ret, i;
+ struct memblock mem;
+ time_t now;
+ struct tm *timep = NULL;
+ char *ptr, *ptr_old = NULL;
+ char *NL = NULL;
+ char tmpbuf[MAXCOLDIGITS];
+
+ /* Increase the limits for recursion and variables on XSLT
+ * parsing */
+ xsltMaxDepth = 30000;
+#if LIBXSLT_VERSION > 10126
+ xsltMaxVars = 150000;
+#endif
+
+ time(&now);
+ timep = localtime(&now);
+
+ strftime(tmpbuf, MAXCOLDIGITS, "%Y%m%d", timep);
+ params[pnr++] = "date";
+ params[pnr++] = strdup(tmpbuf);
+
+ /* As the parameter is numeric, we need to ensure that the leading zero
+ * is not discarded during the transform, thus prepend time with 1 */
+ strftime(tmpbuf, MAXCOLDIGITS, "1%H%M", timep);
+ params[pnr++] = "time";
+ params[pnr++] = strdup(tmpbuf);
+
+
+ if (filename == NULL)
+ return report_error("No CSV filename");
+
+ if (readfile(filename, &mem) < 0)
+ return report_error(translate("gettextFromC", "Failed to read '%s'"), filename);
+
+ /* Determine NL (new line) character and the start of CSV data */
+ ptr = mem.buffer;
+ while ((ptr = strstr(ptr, "\r\n\r\n")) != NULL) {
+ ptr_old = ptr;
+ ptr += 1;
+ NL = "\r\n";
+ }
+
+ if (!ptr_old) {
+ ptr = mem.buffer;
+ while ((ptr = strstr(ptr, "\n\n")) != NULL) {
+ ptr_old = ptr;
+ ptr += 1;
+ NL = "\n";
+ }
+ ptr_old += 2;
+ } else
+ ptr_old += 4;
+
+ /*
+ * If file does not contain empty lines, it is not a valid
+ * Seabear CSV file.
+ */
+ if (NL == NULL)
+ return -1;
+
+ /*
+ * On my current sample of Seabear DC log file, the date is
+ * without any identifier. Thus we must search for the previous
+ * line and step through from there. That is the line after
+ * Serial number.
+ */
+ ptr = strstr(mem.buffer, "Serial number:");
+ if (ptr)
+ ptr = strstr(ptr, NL);
+
+ /*
+ * Write date and time values to params array, if available in
+ * the CSV header
+ */
+
+ if (ptr) {
+ ptr += strlen(NL) + 2;
+ /*
+ * pnr is the index of NULL on the params as filled by
+ * the init function. The two last entries should be
+ * date and time. Here we overwrite them with the data
+ * from the CSV header.
+ */
+
+ memcpy(params[pnr - 3], ptr, 4);
+ memcpy(params[pnr - 3] + 4, ptr + 5, 2);
+ memcpy(params[pnr - 3] + 6, ptr + 8, 2);
+ params[pnr - 3][8] = 0;
+
+ memcpy(params[pnr - 1] + 1, ptr + 11, 2);
+ memcpy(params[pnr - 1] + 3, ptr + 14, 2);
+ params[pnr - 1][5] = 0;
+ }
+
+ params[pnr++] = NULL;
+
+ /* Move the CSV data to the start of mem buffer */
+ memmove(mem.buffer, ptr_old, mem.size - (ptr_old - (char*)mem.buffer));
+ mem.size = (int)mem.size - (ptr_old - (char*)mem.buffer);
+
+ if (try_to_xslt_open_csv(filename, &mem, csvtemplate))
+ return -1;
+
+ /*
+ * Lets print command line for manual testing with xsltproc if
+ * verbosity level is high enough. The printed line needs the
+ * input file added as last parameter.
+ */
+
+ if (verbose >= 2) {
+ fprintf(stderr, "xsltproc ");
+ for (i=0; params[i]; i+=2)
+ fprintf(stderr, "--stringparam %s %s ", params[i], params[i+1]);
+ fprintf(stderr, "xslt/csv2xml.xslt\n");
+ }
+
+ ret = parse_xml_buffer(filename, mem.buffer, mem.size, &dive_table, (const char **)params);
+ free(mem.buffer);
+ for (i = 0; params[i]; i += 2)
+ free(params[i + 1]);
+
+ return ret;
+}
+
+int parse_manual_file(const char *filename, char **params, int pnr)
+{
+ struct memblock mem;
+ time_t now;
+ struct tm *timep;
+ char curdate[9];
+ char curtime[6];
+ int ret, i;
+
+
+ time(&now);
+ timep = localtime(&now);
+ strftime(curdate, DATESTR, "%Y%m%d", timep);
+
+ /* As the parameter is numeric, we need to ensure that the leading zero
+ * is not discarded during the transform, thus prepend time with 1 */
+ strftime(curtime, TIMESTR, "1%H%M", timep);
+
+
+ params[pnr++] = strdup("date");
+ params[pnr++] = strdup(curdate);
+ params[pnr++] = strdup("time");
+ params[pnr++] = strdup(curtime);
+ params[pnr++] = NULL;
+
+ if (filename == NULL)
+ return report_error("No manual CSV filename");
+
+ mem.size = 0;
+ if (try_to_xslt_open_csv(filename, &mem, "manualCSV"))
+ return -1;
+
+#ifndef SUBSURFACE_MOBILE
+ if (verbose >= 2) {
+ fprintf(stderr, "(echo '<manualCSV>'; cat %s;echo '</manualCSV>') | xsltproc ", filename);
+ for (i=0; params[i]; i+=2)
+ fprintf(stderr, "--stringparam %s %s ", params[i], params[i+1]);
+ fprintf(stderr, "%s/xslt/manualcsv2xml.xslt -\n", SUBSURFACE_SOURCE);
+ }
+#endif
+ ret = parse_xml_buffer(filename, mem.buffer, mem.size, &dive_table, (const char **)params);
+
+ free(mem.buffer);
+ for (i = 0; i < pnr - 2; ++i)
+ free(params[i]);
+ return ret;
+}
diff --git a/core/import-csv.h b/core/import-csv.h
new file mode 100644
index 000000000..4d8c7a688
--- /dev/null
+++ b/core/import-csv.h
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+#ifndef IMPORTCSV_H
+#define IMPORTCSV_H
+
+enum csv_format {
+ CSV_DEPTH,
+ CSV_TEMP,
+ CSV_PRESSURE,
+ POSEIDON_DEPTH,
+ POSEIDON_TEMP,
+ POSEIDON_SETPOINT,
+ POSEIDON_SENSOR1,
+ POSEIDON_SENSOR2,
+ POSEIDON_NDL,
+ POSEIDON_CEILING
+};
+
+#define MAXCOLDIGITS 10
+
+void add_sample_data(struct sample *sample, enum csv_format type, double val);
+int parse_dan_format(const char *filename, char **params, int pnr);
+int parse_csv_file(const char *filename, char **params, int pnr, const char *csvtemplate);
+int try_to_xslt_open_csv(const char *filename, struct memblock *mem, const char *tag);
+int try_to_open_csv(struct memblock *mem, enum csv_format type);
+char *parse_mkvi_value(const char *haystack, const char *needle);
+char *next_mkvi_key(const char *haystack);
+int parse_txt_file(const char *filename, const char *csv);
+
+int parse_seabear_log(const char *filename);
+int parse_seabear_csv_file(const char *filename, char **params, int pnr, const char *csvtemplate);
+int parse_manual_file(const char *filename, char **params, int pnr);
+
+#endif // IMPORTCSV_H
diff --git a/desktop-widgets/divelogimportdialog.cpp b/desktop-widgets/divelogimportdialog.cpp
index 6473e46ed..e05a71878 100644
--- a/desktop-widgets/divelogimportdialog.cpp
+++ b/desktop-widgets/divelogimportdialog.cpp
@@ -8,6 +8,7 @@
#include <QMimeData>
#include <QRegExp>
#include "core/qthelper.h"
+#include "core/import-csv.h"
static QString subsurface_mimedata = "subsurface/csvcolumns";
static QString subsurface_index = "subsurface/csvindex";