1 /*
2  * dif.c: read/write sheets using a DIF encoding.
3  *
4  * Authors:
5  *   Kevin Handy <kth@srv.net>
6  *   Zbigniew Chyla <cyba@gnome.pl>
7  *
8  *	Based on ff-csv code.
9  */
10 #include <gnumeric-config.h>
11 #include <glib/gi18n-lib.h>
12 #include <gnumeric.h>
13 #include <libgnumeric.h>
14 
15 #include <cell.h>
16 #include <sheet.h>
17 #include <value.h>
18 #include <numbers.h>
19 #include <gutils.h>
20 #include <goffice/goffice.h>
21 #include <workbook-view.h>
22 #include <workbook.h>
23 #include <gnm-plugin.h>
24 
25 #include <gsf/gsf-input-textline.h>
26 #include <gsf/gsf-output.h>
27 #include <gsf/gsf-utils.h>
28 #include <string.h>
29 #include <stdlib.h>
30 
31 GNM_PLUGIN_MODULE_HEADER;
32 
33 #define N_INPUT_LINES_BETWEEN_UPDATES   50
34 
35 void dif_file_open (GOFileOpener const *fo, GOIOContext *io_context,
36                     WorkbookView *wbv, GsfInput *input);
37 void dif_file_save (GOFileSaver const *fs, GOIOContext *io_context,
38                     WorkbookView const *wbv, GsfOutput *out);
39 
40 typedef struct {
41 	GOIOContext *io_context;
42 
43 	GsfInputTextline *input;
44 	gint   line_no;
45 	gsize  line_len;
46 	gchar *line;
47 
48 	Sheet *sheet;
49 
50 	GIConv converter;
51 } DifInputContext;
52 
53 
54 static DifInputContext *
dif_input_context_new(GOIOContext * io_context,Workbook * wb,GsfInput * input)55 dif_input_context_new (GOIOContext *io_context, Workbook *wb, GsfInput *input)
56 {
57 	DifInputContext *ctxt = NULL;
58 
59 	ctxt = g_new (DifInputContext, 1);
60 	ctxt->io_context     = io_context;
61 
62 	ctxt->input	     = (GsfInputTextline *) gsf_input_textline_new (input);
63 
64 	ctxt->line_no        = 1;
65 	ctxt->line           = NULL;
66 	ctxt->sheet          = workbook_sheet_add (wb, -1, GNM_DEFAULT_COLS, GNM_DEFAULT_ROWS);
67 	ctxt->converter      = g_iconv_open ("UTF-8", "ISO-8859-1");
68 
69 	go_io_progress_message (io_context, _("Reading file..."));
70 
71 	return ctxt;
72 }
73 
74 static void
dif_input_context_destroy(DifInputContext * ctxt)75 dif_input_context_destroy (DifInputContext *ctxt)
76 {
77 	go_io_progress_unset (ctxt->io_context);
78 	g_object_unref (ctxt->input); ctxt->input = NULL;
79 	gsf_iconv_close (ctxt->converter);
80 	g_free (ctxt->line);
81 	g_free (ctxt);
82 }
83 
84 static gboolean
dif_get_line(DifInputContext * ctxt)85 dif_get_line (DifInputContext *ctxt)
86 {
87 	char *raw;
88 
89 	if (NULL == (raw = gsf_input_textline_ascii_gets (ctxt->input)))
90 		return FALSE;
91 
92 	g_free (ctxt->line);
93 	ctxt->line = g_convert_with_iconv (raw, -1, ctxt->converter,
94 					   NULL, &ctxt->line_len, NULL);
95 
96 	ctxt->line_no++;
97 	return ctxt->line != NULL;
98 }
99 
100 /*
101  * Raturns FALSE on EOF.
102  */
103 static gboolean
dif_parse_header(DifInputContext * ctxt)104 dif_parse_header (DifInputContext *ctxt)
105 {
106 	while (1) {
107 		gchar *topic = NULL, *num_line = NULL, *str_line = NULL;
108 		size_t str_line_len;
109 		int res = -1;
110 
111 		if (!dif_get_line (ctxt)) {
112 			res = FALSE;
113 			goto out;
114 		}
115 		topic = g_strdup (ctxt->line);
116 
117 		if (!dif_get_line (ctxt)) {
118 			res = FALSE;
119 			goto out;
120 		}
121 		num_line = g_strdup (ctxt->line);
122 
123 		if (!dif_get_line (ctxt)) {
124 			res = FALSE;
125 			goto out;
126 		}
127 		str_line = g_strdup (ctxt->line);
128 		str_line_len = strlen (str_line);
129 
130 		if (strcmp (topic, "TABLE") == 0) {
131 			gchar *name;
132 
133 			if (str_line_len > 2 && str_line[0] == '"' && str_line[str_line_len - 1] == '"') {
134 				str_line[str_line_len - 1] = '\0';
135 				name = str_line + 1;
136 			}  else {
137 				name = str_line;
138 			}
139 			if (name[0] != '\0') {
140 				/* FIXME - rename the sheet */
141 			}
142 		} else if (strcmp (topic, "DATA") == 0) {
143 			res = TRUE;
144 		}
145 
146 		/*
147 		 * Other "standard" header entry items are:
148 		 * SIZE, LABEL, UNITS, TUPLES, VECTORS, COMMENT, MINORSTART,
149 		 * TRUELENGTH, PERIODICITY, DISPLAYUNITS
150 		 */
151 
152 	out:
153 		g_free (topic);
154 		g_free (num_line);
155 		g_free (str_line);
156 		if (res >= 0)
157 			return res;
158 	}
159 }
160 
161 /*
162  * Raturns FALSE on EOF.
163  */
164 static gboolean
dif_parse_data(DifInputContext * ctxt)165 dif_parse_data (DifInputContext *ctxt)
166 {
167 	gboolean too_many_rows = FALSE, too_many_columns = FALSE;
168 	gint row = -1, col = 0;
169 	gint val_type;
170 	GnmCell *cell;
171 	gchar *msg;
172 
173 	while (1) {
174 		if (!dif_get_line (ctxt))
175 			return FALSE;
176 
177 		val_type = atoi (ctxt->line);
178 		if (val_type == 0) {
179 			gchar const *comma = strchr (ctxt->line, ',');
180 			if (comma == NULL)
181 				go_io_warning (ctxt->io_context,
182 						_("Syntax error at line %d. Ignoring."),
183 						ctxt->line_no);
184 			else if (col > gnm_sheet_get_max_cols (ctxt->sheet)) {
185 				too_many_columns = TRUE;
186 				break;
187 			} else {
188 				gnm_float num = gnm_strto (comma+1, NULL);
189 				GnmValue *v = NULL;
190 
191 				if (!dif_get_line (ctxt))
192 					return FALSE;
193 
194 				if (0 == strcmp (ctxt->line, "V")) {		/* V       value */
195 					v = value_new_float (num);
196 				} else if (0 == strcmp (ctxt->line, "NA")) {	/* NA      not available res must be O */
197 					v = value_new_error_NA (NULL);
198 				} else if (0 == strcmp (ctxt->line, "TRUE")) {	/* TRUE    bool T	 res must be 1 */
199 					v = value_new_bool (TRUE);
200 				} else if (0 == strcmp (ctxt->line, "FALSE")) {	/* FALSE   bool F	 res must be O */
201 					v = value_new_bool (TRUE);
202 				} else if (0 == strcmp (ctxt->line, "ERROR")) {	/* ERROR   err		 res must be O */
203 					go_io_warning (ctxt->io_context,
204 							_("Unknown value type '%s' at line %d. Ignoring."),
205 							ctxt->line, ctxt->line_no);
206 				}
207 
208 				if (NULL != v) {
209 					cell = sheet_cell_fetch (ctxt->sheet, col, row);
210 					gnm_cell_set_value (cell, v);
211 				}
212 				col++;
213 			}
214 		} else if (val_type == 1) {
215 			if (!dif_get_line (ctxt))
216 				return FALSE;
217 			if (col > gnm_sheet_get_max_cols (ctxt->sheet)) {
218 				too_many_columns = TRUE;
219 				continue;
220 			}
221 			cell = sheet_cell_fetch (ctxt->sheet, col, row);
222 			if (ctxt->line_len >= 2 &&
223 			    ctxt->line[0] == '"' && ctxt->line[ctxt->line_len - 1] == '"') {
224 				ctxt->line[ctxt->line_len - 1] = '\0';
225 				gnm_cell_set_text (cell, ctxt->line + 1);
226 			} else
227 				gnm_cell_set_text (cell, ctxt->line);
228 			col++;
229 		} else if (val_type == -1) {
230 			if (!dif_get_line (ctxt))
231 				return FALSE;
232 			if (strcmp (ctxt->line, "BOT") == 0) {
233 				col = 0;
234 				row++;
235 				if (row > gnm_sheet_get_max_rows (ctxt->sheet)) {
236 					too_many_rows = TRUE;
237 					break;
238 				}
239 			} else if (strcmp (ctxt->line, "EOD") == 0) {
240 				break;
241 			} else {
242 				msg = g_strdup_printf (
243 				      _("Unknown data value \"%s\" at line %d. Ignoring."),
244 				      ctxt->line, ctxt->line_no);
245 				g_warning ("%s", msg);
246 				g_free (msg);
247 			}
248 		} else {
249 			msg = g_strdup_printf (
250 			      _("Unknown value type %d at line %d. Ignoring."),
251 			      val_type, ctxt->line_no);
252 			g_warning ("%s", msg);
253 			g_free (msg);
254 			(void) dif_get_line (ctxt);
255 		}
256 	}
257 
258 	if (too_many_rows) {
259 		g_warning (_("DIF file has more than the maximum number of rows %d. "
260 		             "Ignoring remaining rows."), gnm_sheet_get_max_rows (ctxt->sheet));
261 	}
262 	if (too_many_columns) {
263 		g_warning (_("DIF file has more than the maximum number of columns %d. "
264 		             "Ignoring remaining columns."), gnm_sheet_get_max_cols (ctxt->sheet));
265 	}
266 
267 	return TRUE;
268 }
269 
270 static void
dif_parse_sheet(DifInputContext * ctxt)271 dif_parse_sheet (DifInputContext *ctxt)
272 {
273 	GnmLocale *locale = gnm_push_C_locale ();
274 
275 	if (!dif_parse_header (ctxt)) {
276 		go_io_error_info_set (ctxt->io_context, go_error_info_new_printf (
277 		_("Unexpected end of file at line %d while reading header."),
278 		ctxt->line_no));
279 	} else if (!dif_parse_data(ctxt)) {
280 		go_io_error_info_set (ctxt->io_context, go_error_info_new_printf (
281 		_("Unexpected end of file at line %d while reading data."),
282 		ctxt->line_no));
283 	}
284 
285 	gnm_pop_C_locale (locale);
286 }
287 
288 void
dif_file_open(GOFileOpener const * fo,GOIOContext * io_context,WorkbookView * wbv,GsfInput * input)289 dif_file_open (GOFileOpener const *fo, GOIOContext *io_context,
290                WorkbookView *wbv, GsfInput *input)
291 {
292 	Workbook *wb = wb_view_get_workbook (wbv);
293 	DifInputContext *ctxt = dif_input_context_new (io_context, wb, input);
294 
295 	workbook_set_saveinfo (wb, GO_FILE_FL_MANUAL_REMEMBER,
296 		go_file_saver_for_id ("Gnumeric_dif:dif"));
297 	if (ctxt != NULL) {
298 		dif_parse_sheet (ctxt);
299 		if (go_io_error_occurred (io_context))
300 			go_io_error_push (io_context,
301 				go_error_info_new_str (_("Error while reading DIF file.")));
302 		dif_input_context_destroy (ctxt);
303 	} else if (!go_io_error_occurred (io_context))
304 		go_io_error_unknown (io_context);
305 }
306 
307 /*
308  * Write _current_ sheet of the workbook to a DIF format file
309  */
310 void
dif_file_save(GOFileSaver const * fs,GOIOContext * io_context,WorkbookView const * wbv,GsfOutput * out)311 dif_file_save (GOFileSaver const *fs, GOIOContext *io_context,
312                WorkbookView const *wbv, GsfOutput *out)
313 {
314 	GnmLocale *locale;
315 	Sheet *sheet;
316 	GnmRange r;
317 	gint row, col;
318 	gboolean ok = TRUE;
319 
320 	sheet = wb_view_cur_sheet (wbv);
321 	if (sheet == NULL) {
322 		go_io_error_string (io_context, _("Cannot get default sheet."));
323 		return;
324 	}
325 
326 	r = sheet_get_extent (sheet, FALSE, TRUE);
327 
328 	/* Write out the standard headers */
329 	gsf_output_puts   (out, "TABLE\n"   "0,1\n" "\"GNUMERIC\"\n");
330 	gsf_output_printf (out, "VECTORS\n" "0,%d\n" "\"\"\n", r.end.col+1);
331 	gsf_output_printf (out, "TUPLES\n"  "0,%d\n" "\"\"\n", r.end.row+1);
332 	gsf_output_puts   (out, "DATA\n"    "0,0\n"  "\"\"\n");
333 
334 	locale = gnm_push_C_locale ();
335 
336 	/* Process all cells */
337 	for (row = r.start.row; ok && row <= r.end.row; row++) {
338 		gsf_output_puts (out, "-1,0\n" "BOT\n");
339 		for (col = r.start.col; col <= r.end.col; col++) {
340 			GnmCell *cell = sheet_cell_get (sheet, col, row);
341 			if (gnm_cell_is_empty (cell)) {
342 				gsf_output_puts(out, "1,0\n" "\"\"\n");
343 			} else if (VALUE_IS_BOOLEAN (cell->value)) {
344 				if (value_get_as_checked_bool (cell->value))
345 					gsf_output_puts(out, "0,1\n" "TRUE\n");
346 				else
347 					gsf_output_puts(out, "0,0\n" "FALSE\n");
348 			} else if (VALUE_IS_ERROR (cell->value)) {
349 				if (value_error_classify (cell->value) == GNM_ERROR_NA)
350 					gsf_output_puts(out, "0,0\n" "NA\n");
351 				else
352 					gsf_output_puts(out, "0,0\n" "ERROR\n");
353 			} else if (VALUE_IS_FLOAT (cell->value))
354 				gsf_output_printf (out, "0,%" GNM_FORMAT_g "\n" "V\n",
355 					value_get_as_float (cell->value));
356 			else {
357 				gchar *str = gnm_cell_get_rendered_text (cell);
358 				ok = gsf_output_printf (out,
359 							 "1,0\n" "\"%s\"\n",
360 							 str);
361 				g_free (str);
362 			}
363 		}
364 	}
365 
366 	gsf_output_puts (out, "-1,0\n" "EOD\n");
367 
368 	gnm_pop_C_locale (locale);
369 
370 	if (!ok)
371 		go_io_error_string (io_context, _("Error while saving DIF file."));
372 }
373