mirror of
				https://github.com/subsurface/subsurface.git
				synced 2025-02-19 22:16:15 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1237 lines
		
	
	
	
		
			32 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1237 lines
		
	
	
	
		
			32 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| #include <unistd.h>
 | |
| #include <fcntl.h>
 | |
| #include <sys/stat.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| #include <errno.h>
 | |
| #include "gettext.h"
 | |
| #include <zip.h>
 | |
| #include <time.h>
 | |
| 
 | |
| #include "dive.h"
 | |
| #include "divelist.h"
 | |
| #include "file.h"
 | |
| #include "git-access.h"
 | |
| #include "qthelperfromc.h"
 | |
| 
 | |
| /* For SAMPLE_* */
 | |
| #include <libdivecomputer/parser.h>
 | |
| 
 | |
| /* to check XSLT version number */
 | |
| #include <libxslt/xsltconfig.h>
 | |
| 
 | |
| /* Crazy windows sh*t */
 | |
| #ifndef O_BINARY
 | |
| #define O_BINARY 0
 | |
| #endif
 | |
| 
 | |
| int readfile(const char *filename, struct memblock *mem)
 | |
| {
 | |
| 	int ret, fd;
 | |
| 	struct stat st;
 | |
| 	char *buf;
 | |
| 
 | |
| 	mem->buffer = NULL;
 | |
| 	mem->size = 0;
 | |
| 
 | |
| 	fd = subsurface_open(filename, O_RDONLY | O_BINARY, 0);
 | |
| 	if (fd < 0)
 | |
| 		return fd;
 | |
| 	ret = fstat(fd, &st);
 | |
| 	if (ret < 0)
 | |
| 		goto out;
 | |
| 	ret = -EINVAL;
 | |
| 	if (!S_ISREG(st.st_mode))
 | |
| 		goto out;
 | |
| 	ret = 0;
 | |
| 	if (!st.st_size)
 | |
| 		goto out;
 | |
| 	buf = malloc(st.st_size + 1);
 | |
| 	ret = -1;
 | |
| 	errno = ENOMEM;
 | |
| 	if (!buf)
 | |
| 		goto out;
 | |
| 	mem->buffer = buf;
 | |
| 	mem->size = st.st_size;
 | |
| 	ret = read(fd, buf, mem->size);
 | |
| 	if (ret < 0)
 | |
| 		goto free;
 | |
| 	buf[ret] = 0;
 | |
| 	if (ret == (int)mem->size) // converting to int loses a bit but size will never be that big
 | |
| 		goto out;
 | |
| 	errno = EIO;
 | |
| 	ret = -1;
 | |
| free:
 | |
| 	free(mem->buffer);
 | |
| 	mem->buffer = NULL;
 | |
| 	mem->size = 0;
 | |
| out:
 | |
| 	close(fd);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| 
 | |
| static void zip_read(struct zip_file *file, const char *filename)
 | |
| {
 | |
| 	int size = 1024, n, read = 0;
 | |
| 	char *mem = malloc(size);
 | |
| 
 | |
| 	while ((n = zip_fread(file, mem + read, size - read)) > 0) {
 | |
| 		read += n;
 | |
| 		size = read * 3 / 2;
 | |
| 		mem = realloc(mem, size);
 | |
| 	}
 | |
| 	mem[read] = 0;
 | |
| 	(void) parse_xml_buffer(filename, mem, read, &dive_table, NULL);
 | |
| 	free(mem);
 | |
| }
 | |
| 
 | |
| int try_to_open_zip(const char *filename)
 | |
| {
 | |
| 	int success = 0;
 | |
| 	/* Grr. libzip needs to re-open the file, it can't take a buffer */
 | |
| 	struct zip *zip = subsurface_zip_open_readonly(filename, ZIP_CHECKCONS, NULL);
 | |
| 
 | |
| 	if (zip) {
 | |
| 		int index;
 | |
| 		for (index = 0;; index++) {
 | |
| 			struct zip_file *file = zip_fopen_index(zip, index, 0);
 | |
| 			if (!file)
 | |
| 				break;
 | |
| 			/* skip parsing the divelogs.de pictures */
 | |
| 			if (strstr(zip_get_name(zip, index, 0), "pictures/"))
 | |
| 				continue;
 | |
| 			zip_read(file, filename);
 | |
| 			zip_fclose(file);
 | |
| 			success++;
 | |
| 		}
 | |
| 		subsurface_zip_close(zip);
 | |
| 
 | |
| 		if (!success)
 | |
| 			return report_error(translate("gettextFromC", "No dives in the input file '%s'"), 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;
 | |
| 	(void) columns;
 | |
| 	(void) column;
 | |
| 	return *data[0] == '0';
 | |
| }
 | |
| 
 | |
| 
 | |
| static int try_to_open_db(const char *filename, struct memblock *mem)
 | |
| {
 | |
| 	sqlite3 *handle;
 | |
| 	char dm4_test[] = "select count(*) from sqlite_master where type='table' and name='Dive' and sql like '%ProfileBlob%'";
 | |
| 	char dm5_test[] = "select count(*) from sqlite_master where type='table' and name='Dive' and sql like '%SampleBlob%'";
 | |
| 	char shearwater_test[] = "select count(*) from sqlite_master where type='table' and name='system' and sql like '%dbVersion%'";
 | |
| 	char cobalt_test[] = "select count(*) from sqlite_master where type='table' and name='TrackPoints' and sql like '%DepthPressure%'";
 | |
| 	char divinglog_test[] = "select count(*) from sqlite_master where type='table' and name='DBInfo' and sql like '%PrgName%'";
 | |
| 	int retval;
 | |
| 
 | |
| 	retval = sqlite3_open(filename, &handle);
 | |
| 
 | |
| 	if (retval) {
 | |
| 		fprintf(stderr, "Database connection failed '%s'.\n", filename);
 | |
| 		return 1;
 | |
| 	}
 | |
| 
 | |
| 	/* Testing if DB schema resembles Suunto DM5 database format */
 | |
| 	retval = sqlite3_exec(handle, dm5_test, &db_test_func, 0, NULL);
 | |
| 	if (!retval) {
 | |
| 		retval = parse_dm5_buffer(handle, filename, mem->buffer, mem->size, &dive_table);
 | |
| 		sqlite3_close(handle);
 | |
| 		return retval;
 | |
| 	}
 | |
| 
 | |
| 	/* Testing if DB schema resembles Suunto DM4 database format */
 | |
| 	retval = sqlite3_exec(handle, dm4_test, &db_test_func, 0, NULL);
 | |
| 	if (!retval) {
 | |
| 		retval = parse_dm4_buffer(handle, filename, mem->buffer, mem->size, &dive_table);
 | |
| 		sqlite3_close(handle);
 | |
| 		return retval;
 | |
| 	}
 | |
| 
 | |
| 	/* Testing if DB schema resembles Shearwater database format */
 | |
| 	retval = sqlite3_exec(handle, shearwater_test, &db_test_func, 0, NULL);
 | |
| 	if (!retval) {
 | |
| 		retval = parse_shearwater_buffer(handle, filename, mem->buffer, mem->size, &dive_table);
 | |
| 		sqlite3_close(handle);
 | |
| 		return retval;
 | |
| 	}
 | |
| 
 | |
| 	/* Testing if DB schema resembles Atomic Cobalt database format */
 | |
| 	retval = sqlite3_exec(handle, cobalt_test, &db_test_func, 0, NULL);
 | |
| 	if (!retval) {
 | |
| 		retval = parse_cobalt_buffer(handle, filename, mem->buffer, mem->size, &dive_table);
 | |
| 		sqlite3_close(handle);
 | |
| 		return retval;
 | |
| 	}
 | |
| 
 | |
| 	/* Testing if DB schema resembles Divinglog database format */
 | |
| 	retval = sqlite3_exec(handle, divinglog_test, &db_test_func, 0, NULL);
 | |
| 	if (!retval) {
 | |
| 		retval = parse_divinglog_buffer(handle, filename, mem->buffer, mem->size, &dive_table);
 | |
| 		sqlite3_close(handle);
 | |
| 		return retval;
 | |
| 	}
 | |
| 
 | |
| 	sqlite3_close(handle);
 | |
| 
 | |
| 	return retval;
 | |
| }
 | |
| 
 | |
| timestamp_t parse_date(const char *date)
 | |
| {
 | |
| 	int hour, min, sec;
 | |
| 	struct tm tm;
 | |
| 	char *p;
 | |
| 
 | |
| 	memset(&tm, 0, sizeof(tm));
 | |
| 	tm.tm_mday = strtol(date, &p, 10);
 | |
| 	if (tm.tm_mday < 1 || tm.tm_mday > 31)
 | |
| 		return 0;
 | |
| 	for (tm.tm_mon = 0; tm.tm_mon < 12; tm.tm_mon++) {
 | |
| 		if (!memcmp(p, monthname(tm.tm_mon), 3))
 | |
| 			break;
 | |
| 	}
 | |
| 	if (tm.tm_mon > 11)
 | |
| 		return 0;
 | |
| 	date = p + 3;
 | |
| 	tm.tm_year = strtol(date, &p, 10);
 | |
| 	if (date == p)
 | |
| 		return 0;
 | |
| 	if (tm.tm_year < 70)
 | |
| 		tm.tm_year += 2000;
 | |
| 	if (tm.tm_year < 100)
 | |
| 		tm.tm_year += 1900;
 | |
| 	if (sscanf(p, "%d:%d:%d", &hour, &min, &sec) != 3)
 | |
| 		return 0;
 | |
| 	tm.tm_hour = hour;
 | |
| 	tm.tm_min = min;
 | |
| 	tm.tm_sec = sec;
 | |
| 	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.
 | |
|  *
 | |
|  * They start with eight comma-separated fields like:
 | |
|  *
 | |
|  *   filename: {C:\Analyst4\can\T036785.can},{C:\Analyst4\can\K031892.can}
 | |
|  *   divenr: %d
 | |
|  *   datetime: {03Sep11 16:37:22},{15Dec11 18:27:02}
 | |
|  *   ??: 1
 | |
|  *   serialnr??: {CCI134},{CCI207}
 | |
|  *   computer??: {GeminiII},{CommanderIII}
 | |
|  *   computer??: {GeminiII},{CommanderIII}
 | |
|  *   ??: 1
 | |
|  *
 | |
|  * 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
 | |
| 	static char *csv_warning = QT_TRANSLATE_NOOP3("gettextFromC",
 | |
| 						      "Cannot open CSV file %s; please use Import log file dialog",
 | |
| 						      "'Import log file' should be the same text as corresponding label in Import menu");
 | |
| 
 | |
| 	/* Suunto Dive Manager files: SDE, ZIP; divelogs.de files: DLD */
 | |
| 	if (!strcasecmp(fmt, "SDE") || !strcasecmp(fmt, "ZIP") || !strcasecmp(fmt, "DLD"))
 | |
| 		return try_to_open_zip(filename);
 | |
| 
 | |
| 	/* CSV files */
 | |
| 	if (!strcasecmp(fmt, "CSV"))
 | |
| 		return report_error(translate("gettextFromC", csv_warning), filename);
 | |
| 	/* Truly nasty intentionally obfuscated Cochran Anal software */
 | |
| 	if (!strcasecmp(fmt, "CAN"))
 | |
| 		return try_to_open_cochran(filename, mem);
 | |
| 	/* Cochran export comma-separated-value files */
 | |
| 	if (!strcasecmp(fmt, "DPT"))
 | |
| 		return try_to_open_csv(mem, CSV_DEPTH);
 | |
| 	if (!strcasecmp(fmt, "LVD"))
 | |
| 		return try_to_open_liquivision(filename, mem);
 | |
| 	if (!strcasecmp(fmt, "TMP"))
 | |
| 		return try_to_open_csv(mem, CSV_TEMP);
 | |
| 	if (!strcasecmp(fmt, "HP1"))
 | |
| 		return try_to_open_csv(mem, CSV_PRESSURE);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int parse_file_buffer(const char *filename, struct memblock *mem)
 | |
| {
 | |
| 	int ret;
 | |
| 	char *fmt = strrchr(filename, '.');
 | |
| 	if (fmt && (ret = open_by_filename(filename, fmt + 1, mem)) != 0)
 | |
| 		return ret;
 | |
| 
 | |
| 	if (!mem->size || !mem->buffer)
 | |
| 		return report_error("Out of memory parsing file %s\n", filename);
 | |
| 
 | |
| 	return parse_xml_buffer(filename, mem->buffer, mem->size, &dive_table, NULL);
 | |
| }
 | |
| 
 | |
| int check_git_sha(const char *filename, struct git_repository **git_p, const char **branch_p)
 | |
| {
 | |
| 	struct git_repository *git;
 | |
| 	const char *branch = NULL;
 | |
| 
 | |
| 	char *current_sha = strdup(saved_git_id);
 | |
| 	git = is_git_repository(filename, &branch, NULL, false);
 | |
| 	if (git_p)
 | |
| 		*git_p = git;
 | |
| 	if (branch_p)
 | |
| 		*branch_p = branch;
 | |
| 	if (prefs.cloud_git_url &&
 | |
| 	    strstr(filename, prefs.cloud_git_url)
 | |
| 	    && git == dummy_git_repository) {
 | |
| 		/* opening the cloud storage repository failed for some reason,
 | |
| 		 * so we don't know if there is additional data in the remote */
 | |
| 		free(current_sha);
 | |
| 		return 1;
 | |
| 	}
 | |
| 	/* if this is a git repository, do we already have this exact state loaded ?
 | |
| 	 * get the SHA and compare with what we currently have */
 | |
| 	if (git && git != dummy_git_repository) {
 | |
| 		const char *sha = get_sha(git, branch);
 | |
| 		if (!same_string(sha, "") &&
 | |
| 		    same_string(sha, current_sha)) {
 | |
| 			fprintf(stderr, "already have loaded SHA %s - don't load again\n", sha);
 | |
| 			free(current_sha);
 | |
| 			return 0;
 | |
| 		}
 | |
| 	}
 | |
| 	free(current_sha);
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| int parse_file(const char *filename)
 | |
| {
 | |
| 	struct git_repository *git;
 | |
| 	const char *branch = NULL;
 | |
| 	char *current_sha = copy_string(saved_git_id);
 | |
| 	struct memblock mem;
 | |
| 	char *fmt;
 | |
| 	int ret;
 | |
| 
 | |
| 	git = is_git_repository(filename, &branch, NULL, false);
 | |
| 	if (prefs.cloud_git_url &&
 | |
| 	    strstr(filename, prefs.cloud_git_url)
 | |
| 	    && git == dummy_git_repository) {
 | |
| 		/* opening the cloud storage repository failed for some reason
 | |
| 		 * give up here and don't send errors about git repositories */
 | |
| 		free(current_sha);
 | |
| 		return -1;
 | |
| 	}
 | |
| 	/* if this is a git repository, do we already have this exact state loaded ?
 | |
| 	 * get the SHA and compare with what we currently have */
 | |
| 	if (git && git != dummy_git_repository) {
 | |
| 		const char *sha = get_sha(git, branch);
 | |
| 		if (!same_string(sha, "") &&
 | |
| 		    same_string(sha, current_sha) &&
 | |
| 		    !unsaved_changes()) {
 | |
| 			fprintf(stderr, "already have loaded SHA %s - don't load again\n", sha);
 | |
| 			free(current_sha);
 | |
| 			return 0;
 | |
| 		}
 | |
| 	}
 | |
| 	free(current_sha);
 | |
| 	if (git)
 | |
| 		return git_load_dives(git, branch);
 | |
| 
 | |
| 	if ((ret = readfile(filename, &mem)) < 0) {
 | |
| 		/* we don't want to display an error if this was the default file or the cloud storage */
 | |
| 		if ((prefs.default_filename && !strcmp(filename, prefs.default_filename)) ||
 | |
| 		    isCloudUrl(filename))
 | |
| 			return 0;
 | |
| 
 | |
| 		return report_error(translate("gettextFromC", "Failed to read '%s'"), filename);
 | |
| 	} else if (ret == 0) {
 | |
| 		return report_error(translate("gettextFromC", "Empty file '%s'"), filename);
 | |
| 	}
 | |
| 
 | |
| 	fmt = strrchr(filename, '.');
 | |
| 	if (fmt && (!strcasecmp(fmt + 1, "DB") || !strcasecmp(fmt + 1, "BAK") || !strcasecmp(fmt + 1, "SQL"))) {
 | |
| 		if (!try_to_open_db(filename, &mem)) {
 | |
| 			free(mem.buffer);
 | |
| 			return 0;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* Divesoft Freedom */
 | |
| 	if (fmt && (!strcasecmp(fmt + 1, "DLF"))) {
 | |
| 		if (!parse_dlf_buffer(mem.buffer, mem.size)) {
 | |
| 			free(mem.buffer);
 | |
| 			return 0;
 | |
| 		}
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	/* DataTrak/Wlog */
 | |
| 	if (fmt && !strcasecmp(fmt + 1, "LOG")) {
 | |
| 		ret = datatrak_import(&mem, &dive_table);
 | |
| 		free(mem.buffer);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	/* OSTCtools */
 | |
| 	if (fmt && (!strcasecmp(fmt + 1, "DIVE"))) {
 | |
| 		ostctools_import(filename, &dive_table);
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	ret = parse_file_buffer(filename, &mem);
 | |
| 	free(mem.buffer);
 | |
| 	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;
 | |
| 		}
 | |
| 
 | |
| 		/* 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);
 | |
| 
 | |
| 		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;
 | |
| }
 |