mirror of
https://github.com/subsurface/subsurface.git
synced 2024-11-28 05:00:20 +00:00
Refactor CSV import
Move CSV import related functions into import-csv.c. Signed-off-by: Miika Turkia <miika.turkia@gmail.com>
This commit is contained in:
parent
fbbca93d53
commit
b808723f9c
5 changed files with 890 additions and 853 deletions
|
@ -55,6 +55,7 @@ set(SUBSURFACE_CORE_LIB_SRCS
|
||||||
import-shearwater.c
|
import-shearwater.c
|
||||||
import-cobalt.c
|
import-cobalt.c
|
||||||
import-divinglog.c
|
import-divinglog.c
|
||||||
|
import-csv.c
|
||||||
planner.c
|
planner.c
|
||||||
plannernotes.c
|
plannernotes.c
|
||||||
profile.c
|
profile.c
|
||||||
|
|
854
core/file.c
854
core/file.c
|
@ -14,6 +14,7 @@
|
||||||
#include "file.h"
|
#include "file.h"
|
||||||
#include "git-access.h"
|
#include "git-access.h"
|
||||||
#include "qthelperfromc.h"
|
#include "qthelperfromc.h"
|
||||||
|
#include "import-csv.h"
|
||||||
|
|
||||||
/* For SAMPLE_* */
|
/* For SAMPLE_* */
|
||||||
#include <libdivecomputer/parser.h>
|
#include <libdivecomputer/parser.h>
|
||||||
|
@ -114,55 +115,6 @@ int try_to_open_zip(const char *filename)
|
||||||
return success;
|
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)
|
int db_test_func(void *param, int columns, char **data, char **column)
|
||||||
{
|
{
|
||||||
(void) param;
|
(void) param;
|
||||||
|
@ -266,55 +218,6 @@ timestamp_t parse_date(const char *date)
|
||||||
return utc_mktime(&tm);
|
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.
|
* 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).
|
* 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)
|
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
|
// hack to be able to provide a comment for the translated string
|
||||||
|
@ -541,704 +390,3 @@ int parse_file(const char *filename)
|
||||||
return ret;
|
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;
|
|
||||||
}
|
|
||||||
|
|
854
core/import-csv.c
Normal file
854
core/import-csv.c
Normal file
|
@ -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;
|
||||||
|
}
|
33
core/import-csv.h
Normal file
33
core/import-csv.h
Normal file
|
@ -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
|
|
@ -8,6 +8,7 @@
|
||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
#include <QRegExp>
|
#include <QRegExp>
|
||||||
#include "core/qthelper.h"
|
#include "core/qthelper.h"
|
||||||
|
#include "core/import-csv.h"
|
||||||
|
|
||||||
static QString subsurface_mimedata = "subsurface/csvcolumns";
|
static QString subsurface_mimedata = "subsurface/csvcolumns";
|
||||||
static QString subsurface_index = "subsurface/csvindex";
|
static QString subsurface_index = "subsurface/csvindex";
|
||||||
|
|
Loading…
Reference in a new issue