1 /*
2  * sc.c - file import of SC/xspread files
3  * Copyright 1999 Jeff Garzik <jgarzik@mandrakesoft.com>
4  * Copyright (C) 2010 Andreas J. Guelzow <aguelzow@pyrshep.ca> All Rights Reserved
5  *
6  * With some code from sylk.c
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License as
9  * published by the Free Software Foundation; either version 2 of the
10  * License, or (at your option) version 3.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
20  * USA
21  */
22 
23 #include <gnumeric-config.h>
24 #include <glib/gi18n-lib.h>
25 #include <gnumeric.h>
26 #include <string.h>
27 #include <goffice/goffice.h>
28 #include <gnm-plugin.h>
29 #include <sheet-style.h>
30 #include <workbook-view.h>
31 #include <workbook.h>
32 #include <parse-util.h>
33 #include <value.h>
34 #include <cell.h>
35 #include <ranges.h>
36 #include <style.h>
37 #include <sheet.h>
38 #include <expr.h>
39 #include <expr-name.h>
40 #include <func.h>
41 #include <sheet-view.h>
42 #include <selection.h>
43 #include <rendered-value.h>
44 #include <style-font.h>
45 
46 #include <gsf/gsf-input.h>
47 #include <gsf/gsf-input-textline.h>
48 #include <string.h>
49 #include <math.h>
50 #include <stdlib.h>
51 
52 GNM_PLUGIN_MODULE_HEADER;
53 
54 gboolean sc_file_probe (GOFileOpener const *fo, GsfInput *input,
55 			GOFileProbeLevel pl);
56 void sc_file_open (GOFileOpener const *fo, GOIOContext *io_context,
57                    WorkbookView *wb_view, GsfInput *input);
58 
59 typedef struct {
60 	GsfInputTextline *textline;
61 	Sheet            *sheet;
62 	GIConv            converter;
63 	GnmConventions	 *convs;
64 	GOIOContext	 *context;	/* The IOcontext managing things */
65 	char             *last_error;
66 	GArray           *precision;
67 	GPtrArray        *formats;
68 } ScParseState;
69 
70 typedef enum {
71 	LABEL,
72 	LEFTSTRING,
73 	RIGHTSTRING
74 } sc_string_cmd_t;
75 
76 
77 static GOErrorInfo *sc_go_error_info_new_vprintf (GOSeverity severity,
78 					  char const *msg_format, ...)
79 	G_GNUC_PRINTF (2, 3);
80 
81 static GOErrorInfo *
sc_go_error_info_new_vprintf(GOSeverity severity,char const * msg_format,...)82 sc_go_error_info_new_vprintf (GOSeverity severity,
83 			      char const *msg_format, ...)
84 {
85 	va_list args;
86 	GOErrorInfo *ei;
87 
88 	va_start (args, msg_format);
89 	ei = go_error_info_new_vprintf (severity, msg_format, args);
90 	va_end (args);
91 
92 	return ei;
93 }
94 
95 static gboolean sc_warning (ScParseState *state, char const *fmt, ...)
96 	G_GNUC_PRINTF (2, 3);
97 
98 static gboolean
sc_warning(ScParseState * state,char const * fmt,...)99 sc_warning (ScParseState *state, char const *fmt, ...)
100 {
101 	char *msg;
102 	char *detail;
103 	va_list args;
104 
105 	va_start (args, fmt);
106 	detail = g_strdup_vprintf (fmt, args);
107 	va_end (args);
108 
109 	if (IS_SHEET (state->sheet))
110 		msg = g_strdup_printf (_("On worksheet %s:"),state->sheet->name_quoted);
111 	else
112 		msg = g_strdup (_("General SC import error"));
113 
114 	if (0 != go_str_compare (msg, state->last_error)) {
115 		GOErrorInfo *ei = sc_go_error_info_new_vprintf
116 			(GO_WARNING, "%s", msg);
117 
118 		go_io_error_info_set (state->context, ei);
119 		g_free (state->last_error);
120 		state->last_error = msg;
121 	} else
122 		g_free (msg);
123 
124 	go_error_info_add_details
125 		(state->context->info->data,
126 		 sc_go_error_info_new_vprintf (GO_WARNING, "%s", detail));
127 
128 	g_free (detail);
129 
130 	return FALSE; /* convenience */
131 }
132 
133 static gboolean
enlarge(ScParseState * state,int col,int row)134 enlarge (ScParseState *state, int col, int row)
135 {
136 	GnmSheetSize const *size = gnm_sheet_get_size (state->sheet);
137 	gboolean err = FALSE;
138 
139 	if (col >= size->max_cols
140 	    || row >= size->max_rows) {
141 		GOUndo   * goundo;
142 		int cols_needed = (col >= size->max_cols) ? col + 1
143 			: size->max_cols;
144 		int rows_needed = (row >= size->max_rows) ? row + 1
145 			: size->max_rows;
146 		gnm_sheet_suggest_size (&cols_needed, &rows_needed);
147 
148 		goundo = gnm_sheet_resize
149 			(state->sheet, cols_needed, rows_needed, NULL, &err);
150 		if (goundo) g_object_unref (goundo);
151 	}
152 
153 	return err;
154 }
155 
156 
157 static GnmCell  *
sc_sheet_cell_fetch(ScParseState * state,int col,int row)158 sc_sheet_cell_fetch (ScParseState *state, int col, int row)
159 {
160 	gboolean err = enlarge (state, col, row);
161 
162 	if (err) {
163 		sc_warning (state, _("The cell in row %i and column %i is beyond "
164 				     "Gnumeric's maximum sheet size."),
165 			   row, col);
166 
167 		return NULL;
168 	} else
169 		return sheet_cell_fetch (state->sheet, col, row);
170 }
171 
172 static gint
sc_colname_to_coords(char const * colname,gint * m)173 sc_colname_to_coords (char const *colname, gint *m)
174 {
175 	int mult;
176 	int digits = 1;
177 
178 	g_return_val_if_fail (colname, 0);
179 
180 	if (!colname || !*colname || !g_ascii_isalpha (*colname))
181 		return 0;
182 
183 	mult = g_ascii_toupper (*colname) - 'A';
184 	if (mult < 0 || mult > 25)
185 		return 0;
186 
187 	colname++;
188 
189 	if (g_ascii_isalpha (*colname)) {
190 		int ofs = g_ascii_toupper (*colname) - 'A';
191 		if (ofs < 0 || ofs > 25)
192 			return 0;
193 		mult = ((mult + 1) * 26) + ofs;
194 		digits++;
195 	}
196 
197 	*m = mult;
198 
199 	return digits;
200 }
201 
202 
203 /* we can't use cellpos_parse b/c it doesn't support 0 bases (A0, B0, ...) */
204 static gboolean
sc_cellname_to_coords(char const * cellname,GnmCellPos * pos)205 sc_cellname_to_coords (char const *cellname, GnmCellPos *pos)
206 {
207 	int mult, digits;
208 
209 	g_return_val_if_fail (cellname, FALSE);
210 
211 	digits = sc_colname_to_coords (cellname, &mult);
212 	if (digits == 0)
213 		goto err_out;
214 
215 	pos->col = mult;
216 	cellname += digits;
217 
218 	/* XXX need to replace this block with strtol+error checking */
219 	if (1) {
220 		if (!g_ascii_isdigit (*cellname))
221 			goto err_out;
222 
223 		pos->row = atoi (cellname);
224 	}
225 
226 	g_return_val_if_fail (pos->col > -1, FALSE);
227 	g_return_val_if_fail (pos->row > -1, FALSE);
228 	return TRUE;
229 
230 err_out:
231 	pos->col = pos->row = -1;
232 	return FALSE;
233 }
234 
235 
236 
237 static gboolean
sc_parse_coord_real(ScParseState * state,char const * strdata,GnmCellPos * pos,size_t tmplen)238 sc_parse_coord_real (ScParseState *state, char const *strdata, GnmCellPos *pos,
239 		     size_t tmplen)
240 {
241 	char *tmpstr;
242 	GnmNamedExpr *nexpr;
243 	GnmParsePos pp;
244 	GnmValue *v;
245 
246 	g_return_val_if_fail (strdata, FALSE);
247 
248 	tmpstr = g_strndup (strdata, tmplen);
249 
250 	/* It ought to be a cellref.  */
251 	if (sc_cellname_to_coords (tmpstr, pos)) {
252 		g_return_val_if_fail (pos->col >= 0, FALSE);
253 		g_return_val_if_fail (pos->row >= 0, FALSE);
254 		g_free (tmpstr);
255 		return TRUE;
256 	}
257 
258 	/* But it could be a named expression of the same kind.  */
259 
260 	parse_pos_init (&pp, NULL, state->sheet, 0, 0);
261 	nexpr = expr_name_lookup (&pp, tmpstr);
262 	if (nexpr && (v = gnm_expr_top_get_range (nexpr->texpr))) {
263 		if (VALUE_IS_CELLRANGE (v)) {
264 			GnmEvalPos ep;
265 			const GnmCellRef *cr = &v->v_range.cell.a;
266 
267 			eval_pos_init_sheet (&ep, state->sheet);
268 			pos->col = gnm_cellref_get_col (cr, &ep);
269 			pos->row = gnm_cellref_get_row (cr, &ep);
270 			value_release (v);
271 			g_free (tmpstr);
272 			return TRUE;
273 		}
274 
275 		value_release (v);
276 	}
277 	g_free (tmpstr);
278 	return FALSE;
279 }
280 
281 static gboolean
sc_parse_coord(ScParseState * state,char const ** strdata,GnmCellPos * pos)282 sc_parse_coord (ScParseState *state, char const **strdata, GnmCellPos *pos)
283 {
284 	char const *s, *eq;
285 	gboolean res;
286 
287 	g_return_val_if_fail (strdata, FALSE);
288 	g_return_val_if_fail (*strdata, FALSE);
289 
290 	s = *strdata;
291 
292 	eq = strstr (s, " = ");
293 	if (!eq)
294 		return FALSE;
295 
296 	res = sc_parse_coord_real (state, s, pos, eq - s);
297 
298 	if (res) {
299 		if ((eq - s + 1 + 3) > (int) strlen (s))
300 			res = FALSE;
301 		else
302 			*strdata = eq + 3;
303 	}
304 	return res;
305 }
306 
307 static void
set_h_align(Sheet * sheet,GnmCellPos const * pos,GnmHAlign ha)308 set_h_align (Sheet *sheet, GnmCellPos const *pos, GnmHAlign ha)
309 {
310 	GnmRange r;
311 	GnmStyle *style = gnm_style_new ();
312 	gnm_style_set_align_h (style, ha);
313 	r.start = r.end = *pos;
314 	sheet_style_apply_range	(sheet, &r, style);
315 }
316 
317 static void
sc_parse_set_handle_option(ScParseState * state,char const * option)318 sc_parse_set_handle_option (ScParseState *state, char const *option)
319 {
320 	if (g_str_has_prefix (option, "iterations=")) {
321 		int it = atoi (option + 11);
322 		if (it > 0) {
323 			workbook_iteration_enabled (state->sheet->workbook, TRUE);
324 			workbook_iteration_max_number (state->sheet->workbook, it);
325 		}
326 	} else if (g_str_has_prefix (option, "autocalc"))
327 		workbook_set_recalcmode	(state->sheet->workbook, TRUE);
328 	else if (g_str_has_prefix (option, "!autocalc"))
329 		workbook_set_recalcmode (state->sheet->workbook, FALSE);
330 }
331 
332 static gboolean
sc_parse_set(ScParseState * state,char const * cmd,char const * str,GnmCellPos const * cpos)333 sc_parse_set (ScParseState *state, char const *cmd, char const *str,
334 	      GnmCellPos const *cpos)
335 {
336 	gchar** options = g_strsplit (str, " ", -1), **tmp;
337 
338 	if (options != NULL)
339 		for (tmp = options; *tmp != NULL; tmp++)
340 			sc_parse_set_handle_option (state, *tmp);
341 	g_strfreev(options);
342 
343 	/* Most of these settings are not applicable to Gnumeric */
344 	return TRUE;
345 }
346 
347 static gboolean
sc_parse_goto(ScParseState * state,char const * cmd,char const * str,GnmCellPos const * cpos)348 sc_parse_goto (ScParseState *state, char const *cmd, char const *str,
349 		GnmCellPos const *cpos)
350 {
351 	GnmCellPos pos = { -1, -1 };
352 	gboolean res;
353 
354 	res = sc_parse_coord_real (state, str, &pos, strlen (str));
355 	if (!res)
356 		return FALSE;
357 
358 	SHEET_FOREACH_VIEW(state->sheet, sv,
359 			   sv_selection_set
360 			   (sv, &pos, pos.col, pos.row, pos.col, pos.row););
361 
362 	return TRUE;
363 }
364 
365 static gboolean
sc_parse_format_definition(ScParseState * state,char const * cmd,char const * str)366 sc_parse_format_definition (ScParseState *state, char const *cmd, char const *str)
367 {
368 	sc_warning (state, "Ignoring column format definition: %s", str);
369 	return TRUE;
370 }
371 
372 static void
sc_parse_format_set_width(ScParseState * state,int len,int col_from,int col_to)373 sc_parse_format_set_width (ScParseState *state, int len, int col_from, int col_to)
374 {
375 	GnmFont *style_font;
376 	int width;
377 	int col;
378 	GnmStyle *mstyle;
379 	gboolean err;
380 
381 	if (len < 1)
382 		return;
383 
384 	err = enlarge (state, col_to, 0);
385 	if (err) {
386 		sc_warning (state, _("The sheet is wider than "
387 				     "Gnumeric can handle."));
388 		return;
389 	}
390 
391 	mstyle = gnm_style_new_default ();
392 	style_font = gnm_style_get_font
393 		(mstyle, state->sheet->rendered_values->context);
394 	width = PANGO_PIXELS (len * style_font->go.metrics->avg_digit_width) + 4;
395 	gnm_style_unref (mstyle);
396 
397 	for (col = col_from; col <= col_to; col++)
398 		sheet_col_set_size_pixels (state->sheet, col, width, TRUE);
399 }
400 
401 static void
sc_parse_format_free_precision(ScParseState * state)402 sc_parse_format_free_precision (ScParseState *state)
403 {
404 	if (state->precision != NULL)
405 		g_array_free (state->precision, TRUE);
406 }
407 
408 static int
sc_parse_format_get_precision(ScParseState * state,int col)409 sc_parse_format_get_precision (ScParseState *state, int col)
410 {
411 	if (state->precision != NULL &&
412 	    col < (int)state->precision->len) {
413 		return (g_array_index(state->precision, int, col) - 1 );
414 	} else return -1;
415 }
416 
417 static void
sc_parse_format_save_precision(ScParseState * state,int precision,int col_from,int col_to)418 sc_parse_format_save_precision (ScParseState *state, int precision,
419 				int col_from, int col_to)
420 {
421 	int col;
422 
423 	if (state->precision == NULL)
424 		state->precision = g_array_new (FALSE, TRUE, sizeof (int));
425 
426 	if (!(col_to < (int)state->precision->len))
427 		state->precision = g_array_set_size (state->precision, col_to + 1);
428 
429 	for (col = col_from; col <= col_to; col++)
430 		g_array_index(state->precision, int, col) = precision + 1;
431 }
432 
433 static char *
sc_parse_format_apply_precision(ScParseState * state,char * format,int col)434 sc_parse_format_apply_precision (ScParseState *state, char *format, int col)
435 {
436 	if (strchr (format, '&')) {
437 		GString* str = g_string_new (format);
438 		char *amp = str->str;
439 		int off = 0;
440 
441 		g_free (format);
442 		while (NULL != (amp = strchr (str->str + off, '&'))) {
443 			off = amp - str->str + 1;
444 			if (amp == str->str || *(amp - 1) != '\\') {
445 				int p = sc_parse_format_get_precision (state, col);
446 				int i;
447 				if (p == -1) {
448 					p = 0;
449 					sc_warning (state, _("Encountered precision dependent format without set precision."));
450 				}
451 				off--;
452 				g_string_erase (str, off, 1);
453 				for (i = 0; i < p; i++)
454 					g_string_insert_c (str, off, '0');
455 			}
456 
457 		}
458 		format = g_string_free (str, FALSE);
459 	}
460 	return format;
461 }
462 
463 static void
sc_parse_format_set_type(ScParseState * state,int type,int col_from,int col_to)464 sc_parse_format_set_type (ScParseState *state, int type, int col_from, int col_to)
465 {
466 	char const *o_format = type >= 0 && (size_t)type < state->formats->len
467 		? g_ptr_array_index(state->formats, type)
468 		: NULL;
469 	int col;
470 
471 	if (o_format == NULL) {
472 		sc_warning (state, _("Column format %i is undefined."), type);
473 		return;
474 	}
475 	for (col = col_from; col <= col_to; col++) {
476 		char *fmt = g_strdup (o_format);
477 		GOFormat *gfmt;
478 		GnmStyle *style;
479 		GnmRange range;
480 		range_init_cols (&range, state->sheet, col, col);
481 		fmt = sc_parse_format_apply_precision (state, fmt, col);
482 		gfmt = go_format_new_from_XL (fmt);
483 		style = gnm_style_new_default ();
484 		gnm_style_set_format (style, gfmt);
485 		sheet_style_apply_range (state->sheet, &range, style);
486 		/* gnm_style_unref (style); reference has been absorbed */
487 		go_format_unref (gfmt);
488 		g_free (fmt);
489 	}
490 
491 }
492 
493 static gboolean
sc_parse_format(ScParseState * state,char const * cmd,char const * str,GnmCellPos const * cpos)494 sc_parse_format (ScParseState *state, char const *cmd, char const *str,
495 		 GnmCellPos const *cpos)
496 {
497 	char const *s = str;
498 	int col_from = -1, col_to = -1, d;
499 	int len = 0, precision = 0, format_type = 0;
500 
501 	if (g_ascii_isdigit ((gchar) *str))
502 		return sc_parse_format_definition (state, cmd, str);
503 
504 	d = sc_colname_to_coords (s, &col_from);
505 
506 	if (d == 0)
507 		goto cannotparse;
508 
509 	s += d;
510 	if (*s == ':') {
511 		s++;
512 		d = sc_colname_to_coords (s, &col_to);
513 		if (d == 0)
514 			goto cannotparse;
515 		s += d;
516 	} else
517 		col_to= col_from;
518 	while (*s == ' ')
519 		s++;
520 
521 	d = sscanf(s, "%i %i %i", &len, &precision, &format_type);
522 
523 	if (d != 3)
524 		goto cannotparse;
525 
526 	if (len > 0)
527 		sc_parse_format_set_width (state, len, col_from, col_to);
528 	sc_parse_format_save_precision (state, precision, col_from, col_to);
529 	sc_parse_format_set_type (state, format_type, col_from, col_to);
530 
531 	return TRUE;
532  cannotparse:
533 		sc_warning (state, "Unable to parse: %s %s", cmd, str);
534 	return FALSE;
535 }
536 
537 static gboolean
sc_parse_fmt(ScParseState * state,char const * cmd,char const * str,GnmCellPos const * cpos)538 sc_parse_fmt (ScParseState *state, char const *cmd, char const *str,
539 		GnmCellPos const *cpos)
540 {
541 	char const *s = str, *space;
542 	char *fmt;
543 	gboolean res;
544 	GOFormat *gfmt;
545 	GnmStyle *style;
546 	GnmCellPos pos = { -1, -1 };
547 
548 	space = strstr (s, "\"");
549 	space--;
550 	if (!space)
551 		return FALSE;
552 
553 	res = sc_parse_coord_real (state, s, &pos, space - s);
554 	if (!res)
555 		return FALSE;
556 	s = space + 2;
557 	space = strstr (s, "\"");
558 	if (!space)
559 		return FALSE;
560 	fmt = g_strndup (s, space - s);
561 	fmt = sc_parse_format_apply_precision (state, fmt, pos.col);
562 	gfmt = go_format_new_from_XL (fmt);
563 	style = gnm_style_new_default ();
564 	gnm_style_set_format (style, gfmt);
565 
566 	sheet_style_apply_pos (state->sheet, pos.col, pos.row, style);
567 	/* gnm_style_unref (style); reference has been absorbed */
568 	go_format_unref (gfmt);
569 	g_free (fmt);
570 
571 	return TRUE;
572 }
573 
574 static gboolean
sc_parse_label(ScParseState * state,char const * cmd,char const * str,GnmCellPos const * pos)575 sc_parse_label (ScParseState *state, char const *cmd, char const *str,
576 		GnmCellPos const *pos)
577 {
578 	GnmCell *cell;
579 	char *s = NULL, *tmpout;
580 	char const *tmpstr;
581 	gboolean result = FALSE;
582 
583 	g_return_val_if_fail (str, FALSE);
584 
585 	if (*str != '"' || str[1] == 0)
586 		goto err_out;
587 
588 	s = tmpout = g_strdup (str);
589 	if (!s)
590 		goto err_out;
591 
592 	tmpstr = str + 1; /* skip leading " */
593 	while (*tmpstr) {
594 		if (*tmpstr != '\\') {
595 			*tmpout = *tmpstr;
596 			tmpout++;
597 		}
598 		tmpstr++;
599 	}
600 	if (*(tmpstr - 1) != '"')
601 		goto err_out;
602 	tmpout--;
603 	*tmpout = 0;
604 
605 	cell = sc_sheet_cell_fetch (state, pos->col, pos->row);
606 	if (!cell)
607 		goto err_out;
608 
609 	gnm_cell_set_text (cell, s);
610 
611 	if (strcmp (cmd, "leftstring") == 0)
612 		set_h_align (state->sheet, pos, GNM_HALIGN_LEFT);
613 	else if (strcmp (cmd, "rightstring") == 0)
614 		set_h_align (state->sheet, pos, GNM_HALIGN_RIGHT);
615 #if 0
616 	else
617 		cmdtype = LABEL;
618 #endif
619 
620 	result = TRUE;
621 	/* fall through */
622 
623  err_out:
624 	g_free (s);
625 	return result;
626 }
627 
628 
629 #if 0
630 static GSList *
631 sc_parse_cell_name_list (ScParseState *state, char const *cell_name_str,
632 		         int *error_flag)
633 {
634         char     *buf;
635 	GSList   *cells = NULL;
636 	GnmCell     *cell;
637 	GnmCellPos   pos;
638 	int      i, n;
639 
640 	g_return_val_if_fail (state->sheet != NULL, NULL);
641 	g_return_val_if_fail (IS_SHEET (state->sheet), NULL);
642 	g_return_val_if_fail (cell_name_str != NULL, NULL);
643 	g_return_val_if_fail (error_flag != NULL, NULL);
644 
645 	buf = g_malloc (strlen (cell_name_str) + 1);
646 	for (i = n = 0; cell_name_str[i]; i++) {
647 
648 	        if ((cell_name_str [i] == ',') ||
649 		    (!cell_name_str [i])){
650 		        buf [n] = '\0';
651 
652 			if (!cellpos_parse (buf, &pos)){
653 			        *error_flag = 1;
654 				g_free (buf);
655 				g_slist_free (cells);
656 				return NULL;
657 			}
658 
659 			cell = sc_sheet_cell_fetch (state, pos.col, pos.row);
660 			if (cell != NULL)
661 				cells = g_slist_append (cells, (gpointer) cell);
662 			n = 0;
663 		} else
664 		        buf [n++] = cell_name_str [i];
665 	}
666 
667 	*error_flag = 0;
668 	g_free (buf);
669 	return cells;
670 }
671 #endif
672 
673 
674 static char const *
sc_row_parse(char const * str,Sheet * sheet,int * res,unsigned char * relative)675 sc_row_parse (char const *str, Sheet *sheet, int *res, unsigned char *relative)
676 {
677 	char const *end, *ptr = str;
678 	long int row;
679 
680 	if (!(*relative = (*ptr != '$')))
681 		ptr++;
682 
683 	if (*ptr < '0' || *ptr > '9')
684 		return NULL;
685 
686 	/*
687 	 * Do not allow letters after the row number.  If we did, then
688 	 * the name "K3P" would lex as the reference K3 followed by the
689 	 * name "P".
690 	 */
691 	row = strtol (ptr, (char **)&end, 10);
692 	if (ptr != end &&
693 	    !g_unichar_isalnum (g_utf8_get_char (end)) && *end != '_' &&
694 	    0 <= row && row < gnm_sheet_get_max_rows (sheet)) {
695 		*res = row;
696 		return end;
697 	} else
698 		return NULL;
699 }
700 
701 
702 static char const *
sc_rangeref_parse(GnmRangeRef * res,char const * start,GnmParsePos const * pp,G_GNUC_UNUSED GnmConventions const * convs)703 sc_rangeref_parse (GnmRangeRef *res, char const *start, GnmParsePos const *pp,
704 		   G_GNUC_UNUSED GnmConventions const *convs)
705 {
706 	char const *ptr = start, *tmp1, *tmp2;
707 	GnmSheetSize const *ss;
708 
709 	g_return_val_if_fail (start != NULL, start);
710 	g_return_val_if_fail (pp != NULL, start);
711 
712 	ss = gnm_sheet_get_size (pp->sheet);
713 
714 	res->a.sheet = NULL;
715 	tmp1 = col_parse (ptr, ss, &res->a.col, &res->a.col_relative);
716 	if (!tmp1)
717 		return start;
718 	tmp2 = sc_row_parse (tmp1, pp->sheet, &res->a.row, &res->a.row_relative);
719 	if (!tmp2)
720 		return start;
721 	if (res->a.col_relative)
722 		res->a.col -= pp->eval.col;
723 	if (res->a.row_relative)
724 		res->a.row -= pp->eval.row;
725 
726 	/* prepare as if it's a singleton, in case we want to fall back */
727 	res->b = res->a;
728 	if (*tmp2 != ':')
729 		return tmp2;
730 
731 	start = tmp2;
732 	tmp1 = col_parse (start+1, ss, &res->b.col, &res->b.col_relative);
733 	if (!tmp1)
734 		return start;
735 	tmp2 = sc_row_parse (tmp1, pp->sheet, &res->b.row, &res->b.row_relative);
736 	if (!tmp2)
737 		return start;
738 	if (res->b.col_relative)
739 		res->b.col -= pp->eval.col;
740 	if (res->b.row_relative)
741 		res->b.row -= pp->eval.row;
742 	return tmp2;
743 }
744 
745 static GnmExprTop const *
sc_parse_expr(ScParseState * state,const char * str,GnmParsePos * pp)746 sc_parse_expr (ScParseState *state, const char *str, GnmParsePos *pp)
747 {
748 	GnmExprTop const *texpr;
749 	const char *p1;
750 	gboolean infunc = FALSE;
751 	GString *exprstr;
752 
753 	exprstr = g_string_sized_new (500);
754 	for (p1 = str; *p1; p1++) {
755 		char c = *p1;
756 		if (infunc) {
757 			infunc = g_ascii_isalpha (c);
758 			if (!infunc && *p1 != '(')
759 				g_string_append_len (exprstr, "()", 2);
760 			g_string_append_c (exprstr, c);
761 		} else if (*p1 == '@')
762 			infunc = TRUE;
763 		else
764 			g_string_append_c (exprstr, c);
765 	}
766 	if (infunc)
767 		g_string_append_len (exprstr, "()", 2);
768 
769 	texpr = gnm_expr_parse_str (exprstr->str, pp,
770 				    GNM_EXPR_PARSE_DEFAULT,
771 				    state->convs, NULL);
772 	g_string_free (exprstr, TRUE);
773 
774 	return texpr;
775 }
776 
777 
778 static gboolean
sc_parse_let(ScParseState * state,char const * cmd,char const * str,GnmCellPos const * pos)779 sc_parse_let (ScParseState *state, char const *cmd, char const *str,
780 	      GnmCellPos const *pos)
781 {
782 	GnmExprTop const *texpr;
783 	GnmCell *cell;
784 	GnmParsePos pp;
785 	GnmValue const *v;
786 
787 	g_return_val_if_fail (cmd, FALSE);
788 	g_return_val_if_fail (str, FALSE);
789 
790 	cell = sc_sheet_cell_fetch (state, pos->col, pos->row);
791 	if (!cell)
792 		return FALSE;
793 
794 	texpr = sc_parse_expr (state, str,
795 			       parse_pos_init_cell (&pp, cell));
796 
797 	if (!texpr) {
798 		sc_warning (state, _("Unable to parse cmd='%s', str='%s', col=%d, row=%d."),
799 			   cmd, str, pos->col, pos->row);
800 		return TRUE;
801 	}
802 
803 	v = gnm_expr_top_get_constant (texpr);
804 	if (v && VALUE_IS_NUMBER (v)) {
805 		gnm_cell_set_value (cell, value_dup (v));
806 	} else {
807 		gnm_cell_set_expr (cell, texpr);
808 		cell_queue_recalc (cell);
809 	}
810 
811 	if (texpr) gnm_expr_top_unref (texpr);
812 	return TRUE;
813 }
814 
815 static gboolean
sc_parse_define(ScParseState * state,char const * cmd,char const * str,GnmCellPos const * dummy_pos)816 sc_parse_define (ScParseState *state, char const *cmd, char const *str,
817 		 GnmCellPos const *dummy_pos)
818 {
819 	GnmParsePos pp;
820 	GString *name = g_string_new (NULL);
821 	char *errstr = NULL;
822 	GnmNamedExpr *nexpr;
823 	gboolean res = FALSE;
824 	GnmExprTop const *texpr;
825 
826 	str = go_strunescape (name, str);
827 	if (!str)
828 		goto out;
829 	while (g_ascii_isspace (*str))
830 		str++;
831 	texpr = sc_parse_expr (state, str,
832 			       parse_pos_init (&pp, NULL, state->sheet, 0, 0));
833 	if (!texpr) {
834 		sc_warning (state, "Unable to parse cmd='%s', str='%s'.", cmd, str);
835 		goto out;
836 	}
837 
838 	nexpr = expr_name_add (&pp, name->str, texpr, &errstr, TRUE, NULL);
839 	if (!nexpr)
840 		goto out;
841 
842 	res = TRUE;
843 
844 out:
845 	g_string_free (name, TRUE);
846 	g_free (errstr);
847 	return res;
848 }
849 
850 typedef struct {
851 	char const *name;
852 	int namelen;
853 	gboolean (*handler) (ScParseState *state, char const *name,
854 			     char const *str, GnmCellPos const *pos);
855 	gboolean have_coord;
856 } sc_cmd_t;
857 
858 static sc_cmd_t const sc_cmd_list[] = {
859 	{ "leftstring", 10,	sc_parse_label,	 TRUE },
860 	{ "rightstring", 11,	sc_parse_label,	 TRUE },
861 	{ "label", 5,		sc_parse_label,	 TRUE },
862 	{ "let", 3,		sc_parse_let,	 TRUE },
863 	{ "define", 6,          sc_parse_define, FALSE },
864 	{ "fmt", 3,             sc_parse_fmt,    FALSE },
865 	{ "format", 6,          sc_parse_format, FALSE },
866 	{ "set", 3,             sc_parse_set,    FALSE },
867 	{ "goto", 4,            sc_parse_goto,   FALSE },
868 	{ NULL, 0, NULL, 0 },
869 };
870 
871 
872 static gboolean
sc_parse_line(ScParseState * state,char * buf)873 sc_parse_line (ScParseState *state, char *buf)
874 {
875 	char const *space;
876 	int i, cmdlen;
877 	sc_cmd_t const *cmd;
878 
879 	g_return_val_if_fail (state, FALSE);
880 	g_return_val_if_fail (state->sheet, FALSE);
881 	g_return_val_if_fail (buf, FALSE);
882 
883 	for (space = buf; g_ascii_isalnum (*space) || *space == '_'; space++)
884 		; /* Nothing */
885 	if (*space == 0)
886 		return TRUE;
887 	cmdlen = space - buf;
888 	while (*space == ' ')
889 		space++;
890 
891 	for (i = 0 ; sc_cmd_list[i].name != NULL ; ++i) {
892 		cmd = &sc_cmd_list [i];
893 		if (cmd->namelen == cmdlen &&
894 		    strncmp (cmd->name, buf, cmdlen) == 0) {
895 			GnmCellPos pos = { -1, -1 };
896 			char const *strdata = space;
897 
898 			if (cmd->have_coord) {
899 				if (!sc_parse_coord (state, &strdata, &pos)) {
900 					sc_warning (state, "Cannot parse %s\n",
901 						    buf);
902 					return FALSE;
903 				}
904 			}
905 
906 			cmd->handler (state, cmd->name, strdata, &pos);
907 			return TRUE;
908 		}
909 	}
910 
911 	sc_warning (state, "Unhandled directive: '%-.*s'",
912 		   cmdlen, buf);
913 	return TRUE;
914 }
915 
916 
917 static GOErrorInfo *
sc_parse_sheet(ScParseState * state)918 sc_parse_sheet (ScParseState *state)
919 {
920 	unsigned char *data;
921 	GOErrorInfo *res = NULL;
922 
923 	while ((data = gsf_input_textline_ascii_gets (state->textline)) != NULL) {
924 		char *utf8data;
925 
926 		g_strchomp (data);
927 		utf8data = g_convert_with_iconv (data, -1, state->converter,
928 						 NULL, NULL, NULL);
929 
930 		if (g_ascii_isalpha (*data) && !sc_parse_line (state, utf8data)) {
931 			if (!res)
932 				res = go_error_info_new_str
933 					(_("Error parsing line"));
934 		}
935 
936 		g_free (utf8data);
937 	}
938 
939 	return res;
940 }
941 
942 static GnmExpr const *
sc_func_map_in(GnmConventions const * conv,Workbook * scope,char const * name,GnmExprList * args)943 sc_func_map_in (GnmConventions const *conv, Workbook *scope,
944 		char const *name, GnmExprList *args)
945 {
946 	static struct {
947 		char const *sc_name;
948 		char const *gnm_name;
949 	} const sc_func_renames[] = {
950 		{ "AVG",    "AVERAGE" },
951 		{ "DTR",    "RADIANS" },
952 		{ "FABS",   "ABS" },
953 		{ "COLS",   "COLUMNS" },
954 		{ "AVG",    "AVERAGE" },
955 		{ "POW",    "POWER" },
956 		{ "PROD",   "PRODUCT" },
957 		{ "RND",    "ROUND" },
958 		{ "RTD",    "DEGREES" },
959 		{ "STDDEV", "STDEV" },
960 		{ "STON",   "INT" },
961 		{ "SUBSTR", "MID" },
962 		{ NULL, NULL }
963 	};
964 	static GHashTable *namemap = NULL;
965 
966 	GnmFunc  *f;
967 	char const *new_name;
968 	int i;
969 
970 	if (NULL == namemap) {
971 		namemap = g_hash_table_new (go_ascii_strcase_hash,
972 					    go_ascii_strcase_equal);
973 		for (i = 0; sc_func_renames[i].sc_name; i++)
974 			g_hash_table_insert (namemap,
975 				(gchar *) sc_func_renames[i].sc_name,
976 				(gchar *) sc_func_renames[i].gnm_name);
977 	}
978 
979 	if (NULL != namemap &&
980 	    NULL != (new_name = g_hash_table_lookup (namemap, name)))
981 		name = new_name;
982 	if (NULL == (f = gnm_func_lookup (name, scope)))
983 		f = gnm_func_add_placeholder (scope, name, "");
984 	return gnm_expr_new_funcall (f, args);
985 }
986 
987 static GnmConventions *
sc_conventions(void)988 sc_conventions (void)
989 {
990 	GnmConventions *conv = gnm_conventions_new ();
991 
992 	conv->decimal_sep_dot		= TRUE;
993 	conv->range_sep_colon		= TRUE;
994 	conv->input.range_ref		= sc_rangeref_parse;
995 	conv->input.func		= sc_func_map_in;
996 
997 	return conv;
998 }
999 
1000 static void
sc_format_free(gpointer data,gpointer user_data)1001 sc_format_free (gpointer data,  gpointer user_data)
1002 {
1003 	g_free (data);
1004 }
1005 
1006 void
sc_file_open(GOFileOpener const * fo,GOIOContext * io_context,WorkbookView * wb_view,GsfInput * input)1007 sc_file_open (GOFileOpener const *fo, GOIOContext *io_context,
1008               WorkbookView *wb_view, GsfInput *input)
1009 {
1010 	Workbook  *wb;
1011 	char      *name;
1012 	GOErrorInfo *error;
1013 	ScParseState state;
1014 
1015 	wb = wb_view_get_workbook (wb_view);
1016 	name = workbook_sheet_get_free_name (wb, "SC", FALSE, TRUE);
1017 	state.sheet = sheet_new (wb, name, 256, 65536);
1018 	g_free (name);
1019 	workbook_sheet_attach (wb, state.sheet);
1020 
1021 	/* This should probably come from import dialog.  */
1022 	state.converter = g_iconv_open ("UTF-8", "ISO-8859-1");
1023 
1024 	state.convs = sc_conventions ();
1025 	state.context = io_context;
1026 	state.last_error = NULL;
1027 	state.precision = NULL;
1028 	state.formats = g_ptr_array_sized_new (10);
1029 	g_ptr_array_add (state.formats, g_strdup ("#.&")); /* 0 */
1030 	g_ptr_array_add (state.formats, g_strdup ("0.&E+00")); /* 1 */
1031 	g_ptr_array_add (state.formats, g_strdup ("##0.&E+00")); /* 2 */
1032 	g_ptr_array_add (state.formats, g_strdup ("[$-f8f2]m/d/yy")); /* 3 */
1033 	g_ptr_array_add (state.formats, g_strdup ("[$-f800]dddd, mmmm dd, yyyy")); /* 4 */
1034 	g_ptr_array_set_size (state.formats, 10);
1035 
1036 	state.textline = (GsfInputTextline *) gsf_input_textline_new (input);
1037 	error = sc_parse_sheet (&state);
1038 	if (error != NULL) {
1039 		workbook_sheet_delete (state.sheet);
1040 		go_io_error_info_set (io_context, error);
1041 	}
1042 	g_object_unref (state.textline);
1043 	g_iconv_close (state.converter);
1044 	gnm_conventions_unref (state.convs);
1045 	g_free (state.last_error);
1046 	sc_parse_format_free_precision (&state);
1047 
1048 	/*In glib 2.22 or later we could use g_ptr_array_set_free_func */
1049 	g_ptr_array_foreach (state.formats, (GFunc) sc_format_free, NULL);
1050 	g_ptr_array_unref (state.formats);
1051 }
1052 
1053 
1054 static guint8 const signature[] =
1055 "# This data file was generated by the Spreadsheet Calculator.";
1056 
1057 gboolean
sc_file_probe(GOFileOpener const * fo,GsfInput * input,GOFileProbeLevel pl)1058 sc_file_probe (GOFileOpener const *fo, GsfInput *input,
1059 	       GOFileProbeLevel pl)
1060 {
1061 	char const *header = NULL;
1062 
1063 	if (!gsf_input_seek (input, 0, G_SEEK_SET))
1064 		header = gsf_input_read (input, sizeof (signature)-1, NULL);
1065 	return header != NULL &&
1066 	    memcmp (header, signature, sizeof (signature)-1) == 0;
1067 }
1068 
1069 
1070 /*
1071  * http://www.thule.no/haynie/cpumods/a2620/docs/commrc.sc.txt:
1072  * format B 20 2
1073  *
1074  * http://www.mcs.kent.edu/system/documentation/xspread/demo_func
1075  * format A 15 2 0
1076  * goto C7
1077  *
1078  */
1079