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