ssconvert.c 23.1 KB
Newer Older
1 2 3 4 5 6
/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * ssconvert.c: A wrapper application to convert spreadsheet formats
 *
 * Author:
 *   Jon Kre Hellan <hellan@acm.org>
Morten Welinder's avatar
Morten Welinder committed
7
 *   Morten Welinder <terra@gnome.org>
8 9 10
 *   Jody Goldberg <jody@gnome.org>
 *
 * Copyright (C) 2002-2003 Jody Goldberg
Morten Welinder's avatar
Morten Welinder committed
11
 * Copyright (C) 2006-2018 Morten Welinder (terra@gnome.org)
12 13
 */
#include <gnumeric-config.h>
14
#include <glib/gi18n.h>
15
#include "gnumeric.h"
16 17
#include "position.h"
#include "parse-util.h"
18
#include "application.h"
19
#include "workbook.h"
20
#include "workbook-priv.h"
Morten Welinder's avatar
Morten Welinder committed
21
#include "workbook-control.h"
22 23 24
#include "sheet.h"
#include "dependent.h"
#include "expr-name.h"
25
#include "libgnumeric.h"
26
#include "gutils.h"
27 28
#include "value.h"
#include "commands.h"
29
#include "gnumeric-paths.h"
30 31
#include "gnm-plugin.h"
#include "command-context.h"
32
#include "command-context-stderr.h"
33
#include "workbook-view.h"
34
#include "tools/analysis-tools.h"
35
#include <dialogs/dialogs.h>
36
#include <goffice/goffice.h>
37
#include <gsf/gsf-utils.h>
38
#include <string.h>
39 40 41
#ifdef HAVE_SYS_RESOURCE_H
#include <sys/resource.h>
#endif
42 43

static gboolean ssconvert_show_version = FALSE;
44
static gboolean ssconvert_verbose = FALSE;
45
static gboolean ssconvert_list_exporters = FALSE;
46
static gboolean ssconvert_list_importers = FALSE;
47
static gboolean ssconvert_one_file_per_sheet = FALSE;
48
static gboolean ssconvert_recalc = FALSE;
Morten Welinder's avatar
Morten Welinder committed
49
static gboolean ssconvert_solve = FALSE;
50
static char *ssconvert_resize = NULL;
51
static char *ssconvert_range = NULL;
Morten Welinder's avatar
Morten Welinder committed
52 53 54
static char *ssconvert_import_encoding = NULL;
static char *ssconvert_import_id = NULL;
static char *ssconvert_export_id = NULL;
55
static char *ssconvert_export_options = NULL;
56
static char *ssconvert_merge_target = NULL;
57
static char **ssconvert_goal_seek = NULL;
58
static char **ssconvert_tool_test = NULL;
Morten Welinder's avatar
Morten Welinder committed
59

60
static const GOptionEntry ssconvert_options [] = {
Morten Welinder's avatar
Morten Welinder committed
61
	{
62
		"version", 0,
Morten Welinder's avatar
Morten Welinder committed
63 64 65 66 67
		0, G_OPTION_ARG_NONE, &ssconvert_show_version,
		N_("Display program version"),
		NULL
	},

68 69 70 71 72 73 74
	{
		"verbose", 'v',
		0, G_OPTION_ARG_NONE, &ssconvert_verbose,
		N_("Be somewhat more verbose during conversion"),
		NULL
	},

Morten Welinder's avatar
Morten Welinder committed
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
	/* ---------------------------------------- */

	{
		"import-encoding", 'E',
		0, G_OPTION_ARG_STRING, &ssconvert_import_encoding,
		N_("Optionally specify an encoding for imported content"),
		N_("ENCODING")
	},

	{
		"import-type", 'I',
		0, G_OPTION_ARG_STRING, &ssconvert_import_id,
		N_("Optionally specify which importer to use"),
		N_("ID")
	},

	{
		"list-importers", 0,
		0, G_OPTION_ARG_NONE, &ssconvert_list_importers,
		N_("List the available importers"),
		NULL
	},

	/* ---------------------------------------- */

100 101 102 103 104 105 106
	{
		"merge-to", 'M',
		0, G_OPTION_ARG_STRING, &ssconvert_merge_target,
		N_("Merge listed files (all same format) to make this file"),
		N_("file")
	},

Morten Welinder's avatar
Morten Welinder committed
107 108 109 110 111 112 113
	{
		"export-type", 'T',
		0, G_OPTION_ARG_STRING, &ssconvert_export_id,
		N_("Optionally specify which exporter to use"),
		N_("ID")
	},

114 115 116 117 118 119 120
	{
		"export-options", 'O',
		0, G_OPTION_ARG_STRING, &ssconvert_export_options,
		N_("Detailed instructions for the chosen exporter"),
		N_("string")
	},

Morten Welinder's avatar
Morten Welinder committed
121 122 123 124 125 126 127 128 129 130
	{
		"list-exporters", 0,
		0, G_OPTION_ARG_NONE, &ssconvert_list_exporters,
		N_("List the available exporters"),
		NULL
	},

	{
		"export-file-per-sheet", 'S',
		0, G_OPTION_ARG_NONE, &ssconvert_one_file_per_sheet,
Morten Welinder's avatar
Morten Welinder committed
131
		N_("Export a file for each sheet if the exporter only supports one sheet at a time"),
Morten Welinder's avatar
Morten Welinder committed
132 133 134
		NULL
	},

135 136 137
	{
		"recalc", 0,
		0, G_OPTION_ARG_NONE, &ssconvert_recalc,
Morten Welinder's avatar
Morten Welinder committed
138
		N_("Recalculate all cells before writing the result"),
139 140 141
		NULL
	},

142 143 144 145 146 147 148
	{
		"resize", 0,
		G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &ssconvert_resize,
		N_("Resize to given ROWSxCOLS"),
		NULL
	},

149 150 151

	/* ---------------------------------------- */

Morten Welinder's avatar
Morten Welinder committed
152
	/* For now these are for INTERNAL GNUMERIC USE ONLY.  */
153 154 155 156 157 158 159
	{
		"export-range", 0,
		G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &ssconvert_range,
		N_("The range to export"),
		NULL
	},

160 161 162 163 164 165 166
	{
		"goal-seek", 0,
		G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING_ARRAY, &ssconvert_goal_seek,
		N_("Goal seek areas"),
		NULL
	},

Morten Welinder's avatar
Morten Welinder committed
167 168
	{
		"solve", 0,
169
		G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &ssconvert_solve,
Morten Welinder's avatar
Morten Welinder committed
170 171 172 173
		N_("Run the solver"),
		NULL
	},

174 175 176 177 178 179 180
	{
		"tool-test", 0,
		G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING_ARRAY, &ssconvert_tool_test,
		N_("Tool test specs"),
		NULL
	},

Morten Welinder's avatar
Morten Welinder committed
181 182
	/* ---------------------------------------- */

183
	{ NULL }
184 185
};

186
static void
187
setup_range (GObject *obj, const char *key, Workbook *wb, const char *rtxt)
188 189 190 191 192 193
{
	GnmParsePos pp;
	const char *end;
	GnmRangeRef rr;

	pp.wb = wb;
194
	pp.sheet = workbook_sheet_by_index (wb, 0);
195 196 197
	pp.eval.col = 0;
	pp.eval.row = 0;

198
	end = rangeref_parse (&rr, rtxt, &pp, gnm_conventions_default);
199 200 201 202 203
	if (!end || end == rtxt || *end != 0) {
		g_printerr ("Invalid range specified.\n");
		exit (1);
	}

204
	g_object_set_data_full (obj, key,
205 206 207 208
				g_memdup (&rr, sizeof (rr)),
				g_free);
}

209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
static int
handle_export_options (GOFileSaver *fs, GODoc *doc)
{
	guint sig = g_signal_lookup ("set-export-options",
				     G_TYPE_FROM_INSTANCE (fs));

	if (!ssconvert_export_options)
		return 0;

	if (g_signal_handler_find (fs, G_SIGNAL_MATCH_ID,
				   sig, 0, NULL, NULL, NULL)) {
		GError *err = NULL;
		gboolean fail =
			go_file_saver_set_export_options
			(fs, doc,
			 ssconvert_export_options,
			 &err);

		if (fail) {
			g_printerr ("ssconvert: %s\n", err
				    ? err->message
				    : _("Cannot parse export options."));
			return 1;
		}

		return 0;
	} else {
236
		g_printerr (_("The file saver does not take options\n"));
237 238 239 240
		return 1;
	}
}

241

242 243 244
typedef gchar const *(*get_desc_f)(void *);

static void
245
list_them (GList *them,
246 247 248 249
	   get_desc_f get_his_id,
	   get_desc_f get_his_description)
{
	GList *ptr;
250 251
	guint len = 0;
	gboolean interactive;
252

253 254 255 256
	for (ptr = them; ptr ; ptr = ptr->next) {
		GObject *obj = ptr->data;
		char const *id;

257
		g_object_get (obj, "interactive-only", &interactive, NULL);
258 259 260 261 262 263
		if (interactive)
			continue;

		id = get_his_id (obj);
		if (!id) id = "";
		len = MAX (len, strlen (id));
264 265
	}

Morten Welinder's avatar
Morten Welinder committed
266 267 268 269
	g_printerr ("%-*s | %s\n", len,
		    /* Translate these? */
		    "ID",
		    "Description");
270 271
	for (ptr = them; ptr ; ptr = ptr->next) {
		GObject *obj = ptr->data;
Morten Welinder's avatar
Morten Welinder committed
272
		char const *id;
273

274
		g_object_get (obj, "interactive-only", &interactive, NULL);
275 276 277
		if (interactive)
			continue;

Morten Welinder's avatar
Morten Welinder committed
278
		id = get_his_id (obj);
279
		if (!id) id = "";
Morten Welinder's avatar
Morten Welinder committed
280 281 282
		g_printerr ("%-*s | %s\n", len,
			    id,
			    (*get_his_description) (ptr->data));
283 284 285
	}
}

286 287 288 289
/*
 * Read the files we're going to merge and return a list of Workbooks.
 */
static GSList *
Morten Welinder's avatar
Morten Welinder committed
290
read_files_to_merge (const char *inputs[], GOFileOpener *fo,
291
		     GOIOContext *io_context, GOCmdContext *cc)
292
{
293 294 295
	GSList *wbs = NULL;

	while (*inputs) {
296 297
		const char *fname = *inputs;
		char *uri = go_shell_arg_to_uri (fname);
Morten Welinder's avatar
Morten Welinder committed
298
		WorkbookView *wbv =
299
			workbook_view_new_from_uri (uri, fo, io_context,
300
					      ssconvert_import_encoding);
301
		g_free (uri);
302
		inputs++;
303

304
		if (go_io_error_occurred (io_context)) {
305
			g_slist_free_full (wbs, g_object_unref);
306
			return NULL;
307
		}
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337

		if (!wbv)
			continue;

		wbs = g_slist_prepend (wbs, wb_view_get_workbook (wbv));
	}

	return g_slist_reverse (wbs);
}

/*
 * Look at a set of workbooks, and pick a sheet size that would
 * be good for sheets in a workbook merging them all.
 */
static void
suggest_size (GSList *wbs, int *csuggest, int *rsuggest)
{
	GSList *l;
	int rmax = 0;
	int cmax = 0;

	for (l = wbs; l; l = l->next) {
		Workbook *wb = l->data;

		WORKBOOK_FOREACH_SHEET (wb, sheet, {
			int r = gnm_sheet_get_max_rows (sheet);
			int c = gnm_sheet_get_max_cols (sheet);
			if (r > rmax) rmax = r;
			if (c > cmax) cmax = c;
		});
338
	}
339

340
	gnm_sheet_suggest_size (&cmax, &rmax);
341 342 343 344 345
	*csuggest = cmax;
	*rsuggest = rmax;
}

static void
346
cb_collect_names (G_GNUC_UNUSED gconstpointer key,
347
		  GnmNamedExpr *nexpr,
348
		  GSList **plist)
349
{
Morten Welinder's avatar
Morten Welinder committed
350 351
	if (!expr_name_is_active (nexpr))
		return;
352
	*plist = g_slist_prepend (*plist, expr_name_ref (nexpr));
353 354 355 356 357
}

/* Append the sheets of workbook wb2 to workbook wb.  Resize sheets
   if necessary.  Fix workbook links in sheet if necessary.
   Merge names in workbook scope (conflicts result in an error). */
358 359 360 361 362
static gboolean
merge_single (Workbook *wb, Workbook *wb2,
	      int cmax, int rmax,
	      GOCmdContext *cc)
{
363 364 365 366
	/* Move names with workbook scope in wb2 over to wb */
	GSList *names = g_slist_sort (gnm_named_expr_collection_list (wb2->names),
				      (GCompareFunc)expr_name_cmp_by_name);
	GSList *p;
367

368 369
	for (p = names; p; p = p->next) {
		GnmNamedExpr *nexpr = p->data;
370 371
		const char *name = expr_name_name (nexpr);
		GnmNamedExpr *nexpr2;
Morten Welinder's avatar
Morten Welinder committed
372 373
		GnmParsePos pp;
		GnmParsePos newpos = nexpr->pos;
374

Morten Welinder's avatar
Morten Welinder committed
375
		if (!expr_name_is_active (nexpr))
376 377
			continue;

Morten Welinder's avatar
Morten Welinder committed
378
		if (nexpr->pos.wb != wb2 || nexpr->pos.sheet != NULL)
379 380 381 382
			continue;

		/* Check for clash with existing name */

Morten Welinder's avatar
Morten Welinder committed
383
		parse_pos_init (&pp, wb, NULL, 0, 0);
384
		nexpr2 = expr_name_lookup (&pp, name);
Morten Welinder's avatar
Morten Welinder committed
385
		if (nexpr2 /* FIXME: && nexpr2-is-not-the-same-as-nexpr */) {
386 387 388 389
			g_printerr (_("Name conflict during merge: '%s' appears twice at workbook scope.\n"),
				    name);
			g_slist_free (names);
			return TRUE;
390
		}
391 392

		/* Move name scope to workbook wb */
Morten Welinder's avatar
Morten Welinder committed
393 394
		newpos.wb = wb;
		expr_name_set_pos (nexpr, &newpos);
395
	}
396
	g_slist_free (names);
397 398 399

	while (workbook_sheet_count (wb2) > 0) {
		/* Remove sheet from incoming workbook */
400
		Sheet *sheet = workbook_sheet_by_index (wb2, 0);
401 402
		int loc = workbook_sheet_count (wb);
		GOUndo *undo;
403
		char *sheet_name;
404
		gboolean err;
405
		GSList *names = NULL;
406

407 408 409
		g_object_ref (sheet);
		workbook_sheet_delete (sheet);
		sheet->workbook = wb;
410

411
		/* Fix names that reference the old workbook */
412 413 414 415 416 417 418 419 420 421 422 423
		gnm_sheet_foreach_name (sheet, (GHFunc)cb_collect_names, &names);
		while (names) {
			GnmNamedExpr *nexpr = names->data;
			names = g_slist_delete_link (names, names);

			if (nexpr->pos.wb) {
				GnmParsePos newpos = nexpr->pos;
				newpos.wb = wb;
				expr_name_set_pos (nexpr, &newpos);
			}
			expr_name_unref (nexpr);
		}
424

425
		undo = gnm_sheet_resize (sheet, cmax, rmax, cc, &err);
426 427
		if (undo)
			g_object_unref (undo);
428 429

		/* Pick a free sheet name */
430 431 432 433
		sheet_name = workbook_sheet_get_free_name
			(wb, sheet->name_unquoted, FALSE, TRUE);
		g_object_set (sheet, "name", sheet_name, NULL);
		g_free (sheet_name);
434 435

		/* Insert and revive the sheet */
436
		workbook_sheet_attach_at_pos (wb, sheet, loc);
437 438 439
		dependents_revive_sheet (sheet);
		g_object_unref (sheet);
	}
440 441

	return FALSE;
442 443 444
}

/* Merge a collection of workbooks into one. */
445
static gboolean
Morten Welinder's avatar
Morten Welinder committed
446
merge (Workbook *wb, char const *inputs[],
447
       GOFileOpener *fo, GOIOContext *io_context, GOCmdContext *cc)
448
{
449
	GSList *wbs, *l;
450 451 452
	int result = 0;
	int cmax, rmax;

453
	wbs = read_files_to_merge (inputs, fo, io_context, cc);
454 455
	if (go_io_error_occurred (io_context)) {
		go_io_error_display (io_context);
456 457 458 459 460 461 462 463 464 465 466 467 468 469
		return TRUE;
	}

	suggest_size (wbs, &cmax, &rmax);

	for (l = wbs; l; l = l->next) {
		Workbook *wb2 = l->data;
		const char *uri = go_doc_get_uri (GO_DOC (wb2));

		g_printerr ("Adding sheets from %s\n", uri);

		result = merge_single (wb, wb2, cmax, rmax, cc);
		if (result)
			break;
470
	}
471

472
	g_slist_free_full (wbs, g_object_unref);
473 474 475
	return result;
}

476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511
static char *
resolve_template (const char *template, Sheet *sheet)
{
	GString *s = g_string_new (NULL);
	while (1) {
		switch (*template) {
		done:
		case 0: {
			char *res = go_shell_arg_to_uri (s->str);
			g_string_free (s, TRUE);
			return res;
		}
		case '%':
			template++;
			switch (*template) {
			case 0:
				goto done;
			case 'n':
				g_string_append_printf (s, "%d", sheet->index_in_wb);
				break;
			case 's':
				g_string_append (s, sheet->name_unquoted);
				break;
			case '%':
				g_string_append_c (s, '%');
				break;
			}
			template++;
			break;
		default:
			g_string_append_c (s, *template);
			template++;
		}
	}
}

Morten Welinder's avatar
Morten Welinder committed
512 513 514 515 516 517 518 519
static void
run_solver (Sheet *sheet, WorkbookView *wbv)
{
	GnmSolverParameters *params = sheet->solver_parameters;
	GError *err = NULL;
	WorkbookControl *wbc;
	GnmSolver *sol = NULL;

Morten Welinder's avatar
Morten Welinder committed
520
	wbc = g_object_new (GNM_WBC_TYPE, NULL);
Morten Welinder's avatar
Morten Welinder committed
521 522 523
	wb_control_set_view (wbc, wbv, NULL);

	/* Pick a functional algorithm.  */
524 525
	if (!gnm_solver_factory_functional (params->options.algorithm,
					    NULL)) {
Morten Welinder's avatar
Morten Welinder committed
526 527 528 529 530
		GSList *l;
		for (l = gnm_solver_db_get (); l; l = l->next) {
			GnmSolverFactory *factory = l->data;
			if (params->options.model_type != factory->type)
				continue;
531
			if (gnm_solver_factory_functional (factory, NULL)) {
Morten Welinder's avatar
Morten Welinder committed
532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557
				gnm_solver_param_set_algorithm (params,
								factory);
				break;
			}
		}
	}

	if (!gnm_solver_param_valid (params, &err))
		goto done;

	sol = params->options.algorithm
		? gnm_solver_factory_create (params->options.algorithm, params)
		: NULL;
	if (!sol) {
		g_set_error (&err, go_error_invalid (), 0,
			     _("Failed to create solver"));
		goto done;
	}

	if (!gnm_solver_start (sol, wbc, &err))
		goto done;

	while (!gnm_solver_finished (sol)) {
		g_main_context_iteration (NULL, TRUE);
	}

558 559 560 561 562 563 564
	switch (sol->status) {
	case GNM_SOLVER_STATUS_DONE:
		break;
	case GNM_SOLVER_STATUS_CANCELLED:
		g_printerr ("Solver reached time or iteration limit\n");
		break;
	default:
Morten Welinder's avatar
Morten Welinder committed
565 566 567 568 569 570 571
		g_set_error (&err, go_error_invalid (), 0,
			     _("Solver ran, but failed"));
		goto done;
	}

	gnm_solver_store_result (sol);

572 573
	gnm_solver_create_report (sol, "Solver");

Morten Welinder's avatar
Morten Welinder committed
574 575 576 577 578 579 580 581 582
 done:
	if (sol)
		g_object_unref (sol);
	if (err) {
		g_printerr (_("Solver: %s\n"), err->message);
		g_error_free (err);
	}
}

583 584
#define GET_ARG(conv_,name_,def_) (g_hash_table_lookup_extended(args,(name_),NULL,&arg) ? conv_((const char *)arg) : (def_))
#define RANGE_ARG(s_) value_new_cellrange_str(sheet,(s_))
Morten Welinder's avatar
Morten Welinder committed
585
#define RANGE_LIST_ARG(s_) g_slist_prepend (NULL, value_new_cellrange_str(sheet,(s_)))
586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619
#define SHEET_ARG(s_) workbook_sheet_by_name(wb,(s_))

static void
run_tool_test (const char *tool, char **argv, WorkbookView *wbv)
{
	int i;
	WorkbookControl *wbc;
	gpointer specs;
	data_analysis_output_t *dao;
	analysis_tool_engine engine;
	Workbook *wb;
	Sheet *sheet;
	GHashTable *args;
	gpointer arg;

	/*
	 * Arguments in argv are of the form key:value.
	 * Make a hash for those.
	 */
	args = g_hash_table_new_full (g_str_hash, g_str_equal,
				      (GDestroyNotify)g_free,
				      (GDestroyNotify)g_free);
	for (i = 0; argv[i]; i++) {
		const char *s = argv[i];
		const char *colon = strchr (s, ':');
		if (!colon) {
			g_printerr ("Ignoring tool test argument \"%s\"\n", s);
			continue;
		}
		g_hash_table_replace (args, g_strndup (s, colon - s),
				      g_strdup (colon + 1));
	}

	wb = wb_view_get_workbook (wbv);
Morten Welinder's avatar
Morten Welinder committed
620
	wbc = g_object_new (GNM_WBC_TYPE, NULL);
621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641
	wb_control_set_view (wbc, wbv, NULL);

	sheet = GET_ARG (SHEET_ARG, "sheet", wb_view_cur_sheet (wbv));

	if (g_str_equal (tool, "regression")) {
		analysis_tools_data_regression_t *data =
			g_new0 (analysis_tools_data_regression_t, 1);

		data->base.wbc = wbc;
		data->base.range_1 = GET_ARG (RANGE_ARG, "x", value_new_error_REF (NULL));
		data->base.range_2 = GET_ARG (RANGE_ARG, "y", value_new_error_REF (NULL));
		data->base.labels = GET_ARG (atoi, "labels", FALSE);
		data->base.alpha = GET_ARG (atof, "alpha", 0.05);
		data->group_by = GET_ARG ((group_by_t), "grouped-by", GROUPED_BY_COL);
		data->intercept = GET_ARG (atoi, "intercept", TRUE);
		data->multiple_regression = GET_ARG (atoi, "multiple", TRUE);
		data->multiple_y = GET_ARG (atoi, "multiple-y", FALSE);
		data->residual = GET_ARG (atoi, "residual", TRUE);

		engine = analysis_tool_regression_engine;
		specs = data;
Morten Welinder's avatar
Morten Welinder committed
642 643 644 645 646 647 648 649 650 651 652
	} else if (g_str_equal (tool, "anova")) {
		analysis_tools_data_anova_single_t *data =
			g_new0 (analysis_tools_data_anova_single_t, 1);

		data->base.input = GET_ARG (RANGE_LIST_ARG, "data", NULL);
		data->base.labels = GET_ARG (atoi, "labels", FALSE);
		data->base.group_by = GET_ARG ((group_by_t), "grouped-by", GROUPED_BY_COL);
		data->alpha = GET_ARG (atof, "alpha", 0.05);

		engine = analysis_tool_anova_single_engine;
		specs = data;
653 654 655 656 657 658 659 660 661 662 663 664 665 666
	} else {
		g_printerr ("no test for tool \"%s\"\n", tool);
		return;
	}

	dao = dao_init_new_sheet (NULL);
	dao->put_formulas = TRUE;
	cmd_analysis_tool (wbc, sheet, dao, specs, engine, TRUE);

	g_hash_table_destroy (args);
}

#undef GET_ARG
#undef RANGE_ARG
Morten Welinder's avatar
Morten Welinder committed
667
#undef RANGE_LISTARG
668
#undef SHEET_ARG
Morten Welinder's avatar
Morten Welinder committed
669

670
static int
671
convert (char const *inarg, char const *outarg, char const *mergeargs[],
Morten Welinder's avatar
Morten Welinder committed
672
	 GOCmdContext *cc)
673 674
{
	int res = 0;
Jody Goldberg's avatar
Jody Goldberg committed
675 676
	GOFileSaver *fs = NULL;
	GOFileOpener *fo = NULL;
Morten Welinder's avatar
Morten Welinder committed
677 678
	char *infile = go_shell_arg_to_uri (inarg);
	char *outfile = outarg ? go_shell_arg_to_uri (outarg) : NULL;
679 680 681
	WorkbookView *wbv;
	GOIOContext *io_context = NULL;
	Workbook *wb = NULL;
682 683

	if (ssconvert_export_id != NULL) {
Jody Goldberg's avatar
Jody Goldberg committed
684
		fs = go_file_saver_for_id (ssconvert_export_id);
685 686
		if (fs == NULL) {
			res = 1;
Morten Welinder's avatar
Morten Welinder committed
687 688 689
			g_printerr (_("Unknown exporter '%s'.\n"
				      "Try --list-exporters to see a list of possibilities.\n"),
				    ssconvert_export_id);
Morten Welinder's avatar
Morten Welinder committed
690
			goto out;
691
		} else if (outfile == NULL &&
692
			   !ssconvert_one_file_per_sheet &&
Morten Welinder's avatar
Morten Welinder committed
693
			   go_file_saver_get_extension (fs) != NULL) {
694
			char const *ext = gsf_extension_pointer (infile);
Morten Welinder's avatar
Morten Welinder committed
695 696 697 698 699
			if (*infile) {
				GString *res = g_string_new (NULL);
				g_string_append_len (res, infile, ext - infile);
				g_string_append (res, go_file_saver_get_extension(fs));
				outfile = g_string_free (res, FALSE);
700 701 702 703
			}
		}
	} else {
		if (outfile != NULL) {
Jody Goldberg's avatar
Jody Goldberg committed
704
			fs = go_file_saver_for_file_name (outfile);
705 706
			if (fs == NULL) {
				res = 2;
Morten Welinder's avatar
Morten Welinder committed
707 708 709
				g_printerr (_("Unable to guess exporter to use for '%s'.\n"
					      "Try --list-exporters to see a list of possibilities.\n"),
					    outfile);
Morten Welinder's avatar
Morten Welinder committed
710
				goto out;
711
			}
712 713 714
			if (ssconvert_verbose)
				g_printerr ("Using exporter %s\n",
					    go_file_saver_get_id (fs));
715 716
		}
	}
Morten Welinder's avatar
Morten Welinder committed
717 718

	if (outfile == NULL) {
Morten Welinder's avatar
Morten Welinder committed
719 720
		g_printerr (_("An output file name or an explicit export type is required.\n"
			      "Try --list-exporters to see a list of possibilities.\n"));
Morten Welinder's avatar
Morten Welinder committed
721 722 723 724
		res = 1;
		goto out;
	}

725
	if (ssconvert_import_id != NULL) {
Jody Goldberg's avatar
Jody Goldberg committed
726
		fo = go_file_opener_for_id (ssconvert_import_id);
727 728
		if (fo == NULL) {
			res = 1;
Morten Welinder's avatar
Morten Welinder committed
729 730 731
			g_printerr (_("Unknown importer '%s'.\n"
				      "Try --list-importers to see a list of possibilities.\n"),
				    ssconvert_import_id);
Morten Welinder's avatar
Morten Welinder committed
732
			goto out;
733
		}
734
	}
735

736 737
	if (!fs)
		goto out;
Morten Welinder's avatar
Morten Welinder committed
738

739 740
	io_context = go_io_context_new (cc);
	if (mergeargs == NULL) {
741
		wbv = workbook_view_new_from_uri (infile, fo,
742 743 744 745 746
					    io_context,
					    ssconvert_import_encoding);
	} else {
		wbv = workbook_view_new (NULL);
	}
747

748
	if (go_io_error_occurred (io_context)) {
749 750 751
		go_io_error_display (io_context);
		res = 1;
		goto out;
752 753 754 755
	} else if (wbv == NULL) {
		g_printerr (_("Loading %s failed\n"), infile);
		res = 1;
		goto out;
756
	}
757

758 759 760
	wb = wb_view_get_workbook (wbv);

	res = handle_export_options (fs, GO_DOC (wb));
761
	if (res)
762
		goto out;
763

764 765 766 767
	if (mergeargs != NULL) {
		if (merge (wb, mergeargs, fo, io_context, cc))
			goto out;
	}
768

769 770 771
	if (ssconvert_goal_seek) {
		int i;
		Sheet *sheet = wb_view_cur_sheet (wbv);
Morten Welinder's avatar
Morten Welinder committed
772

773 774 775 776 777 778
		for (i = 0; ssconvert_goal_seek[i]; i++) {
			setup_range (G_OBJECT (sheet),
				     "ssconvert-goal-seek",
				     wb,
				     ssconvert_goal_seek[i]);
			dialog_goal_seek (NULL, sheet);
779 780
		}
	}
Morten Welinder's avatar
Morten Welinder committed
781

782 783 784 785 786
	if (ssconvert_solve) {
		Sheet *sheet = wb_view_cur_sheet (wbv);
		run_solver (sheet, wbv);
	}

787 788 789 790 791 792
	if (ssconvert_tool_test && ssconvert_tool_test[0]) {
		run_tool_test (ssconvert_tool_test[0],
			       ssconvert_tool_test + 1,
			       wbv);
	}

793 794 795 796 797
	if (ssconvert_resize) {
		int rows, cols;
		if (sscanf (ssconvert_resize, "%dx%d", &rows, &cols) == 2) {
			int n;

798 799 800
			if (ssconvert_verbose)
				g_printerr ("Resizing to %dx%d\n", rows, cols);

801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816
			for (n = workbook_sheet_count (wb) - 1;
			     n >= 0;
			     n--) {
				gboolean err;
				Sheet *sheet = workbook_sheet_by_index (wb, n);
				GOUndo *undo =
					gnm_sheet_resize (sheet, cols, rows,
							  NULL, &err);
				if (err)
					g_printerr ("Resizing of sheet %s failed\n",
						    sheet->name_unquoted);
				g_object_unref (undo);
			}
		}
	}

817 818
	if (ssconvert_recalc)
		workbook_recalc_all (wb);
819
	gnm_app_recalc ();
820 821 822 823 824 825 826 827 828 829 830 831

	if (ssconvert_range)
		setup_range (G_OBJECT (wb),
			     "ssconvert-range",
			     wb,
			     ssconvert_range);
	else if (ssconvert_one_file_per_sheet ||
		 (workbook_sheet_count (wb) > 1 &&
		  go_file_saver_get_save_scope (fs) != GO_FILE_SAVE_WORKBOOK)) {
		if (ssconvert_one_file_per_sheet) {
			GSList *ptr, *sheets;
			char *template;
832

833 834 835 836 837 838 839 840 841 842
			res = 0;

			template = strchr (outarg, '%')
				? g_strdup (outarg)
				: g_strconcat (outarg, ".%n", NULL);

			sheets = workbook_sheets (wb);
			for (ptr = sheets; ptr; ptr = ptr->next) {
				Sheet *sheet = ptr->data;
				char *tmpfile =	resolve_template (template, sheet);
843 844 845 846 847 848 849 850 851 852 853
				int oldn = sheet->index_in_wb;

				/*
				 * HACK: (bug 694408).
				 *
				 * We don't have a good way of specifying the
				 * sheet.  Move it to the front and select
				 * it.  That will at least make cvs and txt
				 * exporters reliable find it.
				 */
				workbook_sheet_move (sheet, -oldn);
854
				wb_view_sheet_focus (wbv, sheet);
855

856
				res = !workbook_view_save_as (wbv, fs, tmpfile, cc);
857
				workbook_sheet_move (sheet, +oldn);
858 859 860 861 862 863 864 865 866 867 868 869 870
				g_free (tmpfile);
				if (res)
					break;
			}

			g_free (template);
			g_slist_free (sheets);
			goto out;
		} else
			g_printerr (_("Selected exporter (%s) does not support saving multiple sheets in one file.\n"
				      "Only the current sheet will be saved.  To get around this limitation, use -S.\n"),
				    go_file_saver_get_id (fs));
	}
871
	res = !workbook_view_save_as (wbv, fs, outfile, cc);
872

Morten Welinder's avatar
Morten Welinder committed
873
 out:
874 875 876 877
	if (wb)
		g_object_unref (wb);
	if (io_context)
		g_object_unref (io_context);
Morten Welinder's avatar
Morten Welinder committed
878 879 880
	g_free (infile);
	g_free (outfile);

881 882 883
	return res;
}

884
int
Jody Goldberg's avatar
Jody Goldberg committed
885
main (int argc, char const **argv)
886
{
887
	GOErrorInfo	*plugin_errs;
J.H.M. Dassen (Ray)'s avatar
J.H.M. Dassen (Ray) committed
888
	int		 res = 0;
889
	GOCmdContext	*cc;
Morten Welinder's avatar
Morten Welinder committed
890
	GOptionContext *ocontext;
891
	GError *error = NULL;
892

893 894
	/* No code before here, we need to init threads */
	argv = gnm_pre_parse_init (argc, argv);
895

Morten Welinder's avatar
Morten Welinder committed
896
	ocontext = g_option_context_new (_("INFILE [OUTFILE]"));
897
	g_option_context_add_main_entries (ocontext, ssconvert_options, GETTEXT_PACKAGE);
898 899 900 901 902 903
	g_option_context_add_group (ocontext, gnm_get_option_group ());
	/*
	 * The printing code uses gtk+ stuff, so we need to init gtk+.  We
	 * do that without opening any displays.
	 */
	g_option_context_add_group (ocontext, gtk_get_option_group (FALSE));
904
	g_option_context_parse (ocontext, &argc, (char ***)&argv, &error);
Morten Welinder's avatar
Morten Welinder committed
905
	g_option_context_free (ocontext);
906

Morten Welinder's avatar
Morten Welinder committed
907 908 909 910 911 912
	if (error) {
		g_printerr (_("%s\nRun '%s --help' to see a full list of available command line options.\n"),
			    error->message, argv[0]);
		g_error_free (error);
		return 1;
	}
913 914

	if (ssconvert_show_version) {
Morten Welinder's avatar
Morten Welinder committed
915
		g_print (_("ssconvert version '%s'\ndatadir := '%s'\nlibdir := '%s'\n"),
916
			 GNM_VERSION_FULL, gnm_sys_data_dir (), gnm_sys_lib_dir ());
917 918 919
		return 0;
	}

920
	gnm_init ();
921

Morten Welinder's avatar
Morten Welinder committed
922
	cc = gnm_cmd_context_stderr_new ();
923
	gnm_plugins_init (GO_CMD_CONTEXT (cc));
Jon Kåre Hellan's avatar
Jon Kåre Hellan committed
924 925
	go_plugin_db_activate_plugin_list (
		go_plugins_get_available_plugins (), &plugin_errs);
Morten Welinder's avatar
Morten Welinder committed
926 927
	if (plugin_errs) {
		/* FIXME: What do we want to do here? */
928
		go_error_info_free (plugin_errs);
Morten Welinder's avatar
Morten Welinder committed
929
	}
Jean Bréfort's avatar
Jean Bréfort committed
930
	go_component_set_default_command_context (cc);
931 932

	if (ssconvert_list_exporters)
933
		list_them (go_get_file_savers (),
Morten Welinder's avatar
Morten Welinder committed
934 935
			   (get_desc_f) &go_file_saver_get_id,
			   (get_desc_f) &go_file_saver_get_description);
936
	else if (ssconvert_list_importers)
937
		list_them (go_get_file_openers (),
Morten Welinder's avatar
Morten Welinder committed
938 939
			   (get_desc_f) &go_file_opener_get_id,
			   (get_desc_f) &go_file_opener_get_description);
940 941 942 943
	else if (ssconvert_merge_target!=NULL && argc>=3) {
		res = convert (argv[1], ssconvert_merge_target, argv+1, cc);
	} else if (argc == 2 || argc == 3) {
		res = convert (argv[1], argv[2], NULL, cc);
Morten Welinder's avatar
Morten Welinder committed
944 945 946 947 948
	} else {
		g_printerr (_("Usage: %s [OPTION...] %s\n"),
			    g_get_prgname (),
			    _("INFILE [OUTFILE]"));
		res = 1;
949
	}
950

Jean Bréfort's avatar
Jean Bréfort committed
951
	go_component_set_default_command_context (NULL);
952 953
	g_object_unref (cc);
	gnm_shutdown ();
954
	gnm_pre_parse_shutdown ();
955

956 957
	return res;
}