[PATCH] Initial code to import CSV log files

Miika Turkia miika.turkia at gmail.com
Sun Sep 29 05:44:38 UTC 2013


This patch implements basic functionality to import CSV formatted log
profiles to Subsurface. The import includes time, depth and temperature
from AP Logviewer based on one sample log file I have received. It is
assumed that dive time is the first parameter and depth second.

Temperature is given as a parameter from C source (hard coded currently
to field 15) but we should have a GUI implemented for selecting the
wanted fields.

The two different sample logs of CSV dive log export I have received use
tabulator as field separator. I assume the possible GUI should have
option for the FS as well to be given as parameter to the XSLT.

Signed-off-by: Miika Turkia <miika.turkia at gmail.com>
---
 file.c            |  33 ++++++++++++++++
 parse-xml.c       |  23 +++++++++++-
 xslt/csv2xml.xslt | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 165 insertions(+), 1 deletion(-)
 create mode 100644 xslt/csv2xml.xslt

diff --git a/file.c b/file.c
index 70eb79a..4bc8bcf 100644
--- a/file.c
+++ b/file.c
@@ -97,6 +97,35 @@ static int try_to_open_zip(const char *filename, struct memblock *mem, char **er
 	return success;
 }
 
+static int try_to_xslt_open_csv(const char *filename, struct memblock *mem, char **error)
+{
+	char *buf;
+
+	if (readfile(filename, mem) < 0) {
+		if (error) {
+			*error = malloc(1024);
+			snprintf(*error, 1024, _("Failed to read '%s'"), filename);
+		}
+
+		return 1;
+	}
+
+	/* Surround the CSV file content with XML tags to enable XSLT
+	 * parsing
+	 */
+	buf = realloc(mem->buffer, mem->size + strlen("<csv></csv>"));
+	if (buf != NULL) {
+		memmove(buf + 5, mem->buffer, mem->size);
+		memcpy(buf, "<csv>", 5);
+		memcpy(mem->buffer + mem->size + 5, "</csv>", 7);
+		mem->buffer = buf;
+		mem->size += strlen("<csv></csv>");
+	} else
+		return 1;
+
+	return 0;
+}
+
 static int try_to_open_db(const char *filename, struct memblock *mem, char **error)
 {
 	return parse_dm4_buffer(filename, mem->buffer, mem->size, &dive_table, error);
@@ -229,6 +258,10 @@ static int open_by_filename(const char *filename, const char *fmt, struct memblo
 	if (!strcasecmp(fmt, "SDE") || !strcasecmp(fmt, "ZIP") || !strcasecmp(fmt, "DLD"))
 		return try_to_open_zip(filename, mem, error);
 
+	/* CSV files */
+	if (!strcasecmp(fmt, "CSV"))
+		return try_to_xslt_open_csv(filename, mem, error);
+
 #if ONCE_COCHRAN_IS_SUPPORTED
 	/* Truly nasty intentionally obfuscated Cochran Anal software */
 	if (!strcasecmp(fmt, "CAN"))
diff --git a/parse-xml.c b/parse-xml.c
index 370388f..195eafe 100644
--- a/parse-xml.c
+++ b/parse-xml.c
@@ -1915,6 +1915,7 @@ static struct xslt_files {
 	{ "UDDF", "uddf.xslt" },
 	{ "profile", "udcf.xslt" },
 	{ "Divinglog", "DivingLog.xslt" },
+	{ "csv", "csv2xml.xslt" },
 	{ NULL, }
 };
 
@@ -1925,6 +1926,7 @@ static xmlDoc *test_xslt_transforms(xmlDoc *doc, char **error)
 	xsltStylesheetPtr xslt = NULL;
 	xmlNode *root_element = xmlDocGetRootElement(doc);
 	char *attribute;
+	char *params[3];
 
 	while ((info->root) && (strcasecmp(root_element->name, info->root) != 0)) {
 		info++;
@@ -1946,9 +1948,28 @@ static xmlDoc *test_xslt_transforms(xmlDoc *doc, char **error)
 			parser_error(error, _("Can't open stylesheet (%s)/%s"), xslt_path, info->file);
 			return doc;
 		}
-		transformed = xsltApplyStylesheet(xslt, doc, NULL);
+
+		/*
+		 * params is only used for CSV import, but it does not
+		 * hurt if we supply unused parameters for other
+		 * transforms as well.
+		 *
+		 * We should have a GUI set the parameters but currently
+		 * we just have PoC how parameters would be handled.
+		 *
+		 * (Field 9 is temperature for XP5 import, field 15
+		 * is temperature for AP Logviewer.
+		 */
+
+		params[0] = strdup("tempField");
+		params[1] = strdup("15");
+		params[2] = NULL;
+
+		transformed = xsltApplyStylesheet(xslt, doc, (const char **)params);
 		xmlFreeDoc(doc);
 		xsltFreeStylesheet(xslt);
+		free(params[0]);
+		free(params[1]);
 		return transformed;
 	}
 	return doc;
diff --git a/xslt/csv2xml.xslt b/xslt/csv2xml.xslt
new file mode 100644
index 0000000..faa9e96
--- /dev/null
+++ b/xslt/csv2xml.xslt
@@ -0,0 +1,110 @@
+<?xml version="1.0"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+  <xsl:import href="commonTemplates.xsl"/>
+  <xsl:strip-space elements="*"/>
+  <xsl:param name="tempField" select="tempField"/>
+  <xsl:output method="xml" indent="yes"/>
+
+  <xsl:variable name="lf"><xsl:text>
+</xsl:text></xsl:variable>
+  <xsl:variable name="fs"><xsl:text>	</xsl:text></xsl:variable>
+
+  <xsl:template match="/">
+    <divelog program="subsurface-import" version="2">
+      <dives>
+        <dive>
+          <divecomputerid deviceid="ffffffff" model="stone" />
+          <xsl:call-template name="printLine">
+            <xsl:with-param name="line" select="substring-before(//csv, $lf)"/>
+            <xsl:with-param name="remaining" select="substring-after(//csv, $lf)"/>
+          </xsl:call-template>
+        </dive>
+      </dives>
+    </divelog>
+  </xsl:template>
+
+  <xsl:template name="printLine">
+    <xsl:param name="line"/>
+    <xsl:param name="remaining"/>
+    <xsl:call-template name="printFields">
+      <xsl:with-param name="line" select="$line"/>
+    </xsl:call-template>
+    <xsl:if test="$remaining != ''">
+      <xsl:call-template name="printLine">
+        <xsl:with-param name="line" select="substring-before($remaining, $lf)"/>
+        <xsl:with-param name="remaining" select="substring-after($remaining, $lf)"/>
+      </xsl:call-template>
+    </xsl:if>
+  </xsl:template>
+
+  <xsl:template name="printFields">
+    <xsl:param name="line"/>
+
+    <xsl:variable name="value">
+      <xsl:call-template name="getFieldByIndex">
+        <xsl:with-param name="index" select="0"/>
+        <xsl:with-param name="line" select="$line"/>
+      </xsl:call-template>
+    </xsl:variable>
+
+    <!-- First field should be dive time. If the value is not numeric,
+         we'll skip it. (We do also allow time in h:m:s notation.) -->
+
+    <xsl:if test="number($value) = $value or number(substring-before($value, ':')) = substring-before($value, ':')">
+      <sample>
+        <xsl:attribute name="time">
+          <xsl:choose>
+            <xsl:when test="number($value) = $value">
+              <!-- We assume time in seconds -->
+
+              <xsl:call-template name="sec2time">
+                <xsl:with-param name="timeSec">
+                  <xsl:value-of select="$value"/>
+                </xsl:with-param>
+              </xsl:call-template>
+            </xsl:when>
+            <xsl:otherwise>
+              <!-- We assume time format h:m:s -->
+
+              <xsl:value-of select="concat(
+                substring-before($value, ':') * 60 + substring-before(substring-after($value, ':'), ':'),
+                ':',
+                substring-after(substring-after($value, ':'), ':')
+                )" />
+            </xsl:otherwise>
+          </xsl:choose>
+        </xsl:attribute>
+
+        <xsl:attribute name="depth">
+          <xsl:call-template name="getFieldByIndex">
+            <xsl:with-param name="index" select="1"/>
+            <xsl:with-param name="line" select="$line"/>
+          </xsl:call-template>
+        </xsl:attribute>
+
+        <xsl:attribute name="temp">
+          <xsl:call-template name="getFieldByIndex">
+            <xsl:with-param name="index" select="$tempField"/>
+            <xsl:with-param name="line" select="$line"/>
+          </xsl:call-template>
+        </xsl:attribute>
+      </sample>
+    </xsl:if>
+  </xsl:template>
+
+  <xsl:template name="getFieldByIndex">
+    <xsl:param name="index"/>
+    <xsl:param name="line"/>
+    <xsl:choose>
+      <xsl:when test="$index > 0">
+        <xsl:call-template name="getFieldByIndex">
+          <xsl:with-param name="index" select="$index -1"/>
+          <xsl:with-param name="line" select="substring-after($line, $fs)"/>
+        </xsl:call-template>
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:value-of select="substring-before($line,$fs)"/>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:template>
+</xsl:stylesheet>
-- 
1.8.1.2



More information about the subsurface mailing list