GoalSeek: code cleanup.
[gnumeric.git] / src / ssconvert.c
blob319e3020d99880695702cfcc2ccce6c14aec5c7c
1 /*
2 * ssconvert.c: A wrapper application to convert spreadsheet formats
4 * Author:
5 * Jon Kåre Hellan <hellan@acm.org>
6 * Morten Welinder <terra@gnome.org>
7 * Jody Goldberg <jody@gnome.org>
9 * Copyright (C) 2002-2003 Jody Goldberg
10 * Copyright (C) 2006-2018 Morten Welinder (terra@gnome.org)
12 #include <gnumeric-config.h>
13 #include <glib/gi18n.h>
14 #include <gnumeric.h>
15 #include <position.h>
16 #include <parse-util.h>
17 #include <application.h>
18 #include <workbook.h>
19 #include <workbook-priv.h>
20 #include <workbook-control.h>
21 #include <sheet.h>
22 #include <dependent.h>
23 #include <expr-name.h>
24 #include <libgnumeric.h>
25 #include <gutils.h>
26 #include <value.h>
27 #include <commands.h>
28 #include <gnumeric-paths.h>
29 #include <gnm-plugin.h>
30 #include <command-context.h>
31 #include <command-context-stderr.h>
32 #include <workbook-view.h>
33 #include <gnumeric-conf.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 #ifdef HAVE_SYS_RESOURCE_H
40 #include <sys/resource.h>
41 #endif
43 // Sheets that an exporter should export.
44 #define SHEET_SELECTION_KEY "sheet-selection"
46 // Sheets user has specified as export options
47 #define SSCONVERT_SHEET_SET_KEY "ssconvert-sheets"
49 static gboolean ssconvert_show_version = FALSE;
50 static gboolean ssconvert_verbose = FALSE;
51 static gboolean ssconvert_list_exporters = FALSE;
52 static gboolean ssconvert_list_importers = FALSE;
53 static gboolean ssconvert_one_file_per_sheet = FALSE;
54 static gboolean ssconvert_recalc = FALSE;
55 static gboolean ssconvert_solve = FALSE;
56 static char *ssconvert_resize = NULL;
57 static char *ssconvert_range = NULL;
58 static char *ssconvert_import_encoding = NULL;
59 static char *ssconvert_import_id = NULL;
60 static char *ssconvert_export_id = NULL;
61 static char *ssconvert_export_options = NULL;
62 static char *ssconvert_merge_target = NULL;
63 static char **ssconvert_goal_seek = NULL;
64 static char **ssconvert_tool_test = NULL;
66 static const GOptionEntry ssconvert_options [] = {
68 "version", 0,
69 0, G_OPTION_ARG_NONE, &ssconvert_show_version,
70 N_("Display program version"),
71 NULL
75 "verbose", 'v',
76 0, G_OPTION_ARG_NONE, &ssconvert_verbose,
77 N_("Be somewhat more verbose during conversion"),
78 NULL
81 /* ---------------------------------------- */
84 "import-encoding", 'E',
85 0, G_OPTION_ARG_STRING, &ssconvert_import_encoding,
86 N_("Optionally specify an encoding for imported content"),
87 N_("ENCODING")
91 "import-type", 'I',
92 0, G_OPTION_ARG_STRING, &ssconvert_import_id,
93 N_("Optionally specify which importer to use"),
94 N_("ID")
98 "list-importers", 0,
99 0, G_OPTION_ARG_NONE, &ssconvert_list_importers,
100 N_("List the available importers"),
101 NULL
104 /* ---------------------------------------- */
107 "merge-to", 'M',
108 0, G_OPTION_ARG_STRING, &ssconvert_merge_target,
109 N_("Merge listed files (all same format) to make this file"),
110 N_("file")
114 "export-type", 'T',
115 0, G_OPTION_ARG_STRING, &ssconvert_export_id,
116 N_("Optionally specify which exporter to use"),
117 N_("ID")
121 "export-options", 'O',
122 0, G_OPTION_ARG_STRING, &ssconvert_export_options,
123 N_("Detailed instructions for the chosen exporter"),
124 N_("string")
128 "list-exporters", 0,
129 0, G_OPTION_ARG_NONE, &ssconvert_list_exporters,
130 N_("List the available exporters"),
131 NULL
135 "export-file-per-sheet", 'S',
136 0, G_OPTION_ARG_NONE, &ssconvert_one_file_per_sheet,
137 N_("Export a file for each sheet if the exporter only supports one sheet at a time"),
138 NULL
142 "recalc", 0,
143 0, G_OPTION_ARG_NONE, &ssconvert_recalc,
144 N_("Recalculate all cells before writing the result"),
145 NULL
149 "resize", 0,
150 G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &ssconvert_resize,
151 N_("Resize to given ROWSxCOLS"),
152 NULL
156 /* ---------------------------------------- */
158 /* For now these are for INTERNAL GNUMERIC USE ONLY. */
160 "export-range", 0,
161 G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &ssconvert_range,
162 N_("The range to export"),
163 NULL
167 "goal-seek", 0,
168 G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING_ARRAY, &ssconvert_goal_seek,
169 N_("Goal seek areas"),
170 NULL
174 "solve", 0,
175 G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &ssconvert_solve,
176 N_("Run the solver"),
177 NULL
181 "tool-test", 0,
182 G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING_ARRAY, &ssconvert_tool_test,
183 N_("Tool test specs"),
184 NULL
187 /* ---------------------------------------- */
189 { NULL }
192 static GnmRangeRef const *
193 setup_range (GObject *obj, const char *key, Workbook *wb, const char *rtxt)
195 GnmParsePos pp;
196 const char *end;
197 GnmRangeRef rr, *rrc;
199 pp.wb = wb;
200 pp.sheet = workbook_sheet_by_index (wb, 0);
201 pp.eval.col = 0;
202 pp.eval.row = 0;
204 end = rangeref_parse (&rr, rtxt, &pp, gnm_conventions_default);
205 if (!end || end == rtxt || *end != 0) {
206 g_printerr ("Invalid range specified.\n");
207 exit (1);
210 rrc = g_memdup (&rr, sizeof (rr));
211 g_object_set_data_full (obj, key, rrc, g_free);
213 return rrc;
216 struct cb_handle_export_options {
217 GOFileSaver *fs;
218 Workbook const *wb;
221 static gboolean
222 cb_handle_export_options (const char *key, const char *value,
223 GError **err, gpointer user_)
225 struct cb_handle_export_options *user = user_;
226 return gnm_file_saver_common_export_option (user->fs, user->wb,
227 key, value, err);
230 static int
231 handle_export_options (GOFileSaver *fs, Workbook *wb)
233 GError *err = NULL;
234 gboolean fail;
235 guint sig;
237 if (!ssconvert_export_options)
238 return 0;
240 sig = g_signal_lookup ("set-export-options", G_TYPE_FROM_INSTANCE (fs));
241 if (g_signal_handler_find (fs, G_SIGNAL_MATCH_ID,
242 sig, 0, NULL, NULL, NULL))
243 fail = go_file_saver_set_export_options
244 (fs, GO_DOC (wb),
245 ssconvert_export_options,
246 &err);
247 else {
248 struct cb_handle_export_options data;
249 data.fs = fs;
250 data.wb = wb;
251 fail = go_parse_key_value (ssconvert_export_options, &err,
252 cb_handle_export_options, &data);
255 if (fail) {
256 g_printerr ("ssconvert: %s\n", err
257 ? err->message
258 : _("Cannot parse export options."));
259 return 1;
262 return 0;
265 // Check that the sheet selection, if any, matches the file saver's
266 // capabilities.
267 static int
268 validate_sheet_selection (GOFileSaver *fs, Workbook *wb)
270 GOFileSaveScope fsscope = go_file_saver_get_save_scope (fs);
271 gboolean fs_sheet_selection;
273 g_object_get (G_OBJECT (fs),
274 "sheet-selection", &fs_sheet_selection, NULL);
276 if (ssconvert_one_file_per_sheet) {
277 gboolean ok;
278 switch (fsscope) {
279 case GO_FILE_SAVE_WORKBOOK:
280 ok = fs_sheet_selection;
281 break;
282 case GO_FILE_SAVE_SHEET:
283 ok = TRUE;
284 break;
285 case GO_FILE_SAVE_RANGE:
286 default:
287 ok = FALSE;
288 break;
290 if (!ok) {
291 g_printerr (_("Selected exporter (%s) does not have the ability to split a workbook into sheets.\n"),
292 go_file_saver_get_id (fs));
293 return 1;
295 } else {
296 GPtrArray *sheets = g_object_get_data (G_OBJECT (wb),
297 SSCONVERT_SHEET_SET_KEY);
298 switch (fsscope) {
299 case GO_FILE_SAVE_WORKBOOK:
300 case GO_FILE_SAVE_RANGE:
301 default:
302 if (sheets && !fs_sheet_selection) {
303 g_printerr (_("Selected exporter (%s) does not have the ability to export a subset of sheets.\n"),
304 go_file_saver_get_id (fs));
305 return 1;
307 break;
308 case GO_FILE_SAVE_SHEET:
309 if (sheets && sheets->len != 1) {
310 g_printerr (_("Selected exporter (%s) can only export one sheet at a time.\n"),
311 go_file_saver_get_id (fs));
312 return 1;
314 break;
318 return 0;
322 typedef gchar const *(*get_desc_f)(const void *);
324 static int
325 by_his_id (gconstpointer a, gconstpointer b, gpointer user)
327 get_desc_f get_his_id = user;
328 return strcmp (get_his_id (a), get_his_id (b));
331 static void
332 list_them (GList *them,
333 get_desc_f get_his_id,
334 get_desc_f get_his_description)
336 GList *them_copy = g_list_copy (them);
337 GList *ptr;
338 guint len = 0;
339 gboolean interactive;
341 them_copy = g_list_sort_with_data (them_copy, by_his_id, get_his_id);
343 for (ptr = them_copy; ptr; ptr = ptr->next) {
344 GObject *obj = ptr->data;
345 char const *id;
347 g_object_get (obj, "interactive-only", &interactive, NULL);
348 if (interactive)
349 continue;
351 id = get_his_id (obj);
352 if (!id) id = "";
353 len = MAX (len, strlen (id));
356 g_printerr ("%-*s | %s\n", len,
357 /* Translate these? */
358 _("ID"),
359 _("Description"));
360 for (ptr = them_copy; ptr ; ptr = ptr->next) {
361 GObject *obj = ptr->data;
362 char const *id;
364 g_object_get (obj, "interactive-only", &interactive, NULL);
365 if (interactive)
366 continue;
368 id = get_his_id (obj);
369 if (!id) id = "";
370 g_printerr ("%-*s | %s\n", len,
372 (*get_his_description) (ptr->data));
375 g_list_free (them_copy);
379 * Read the files we're going to merge and return a list of Workbooks.
381 static GSList *
382 read_files_to_merge (const char *inputs[], GOFileOpener *fo,
383 GOIOContext *io_context, GOCmdContext *cc)
385 GSList *wbs = NULL;
387 while (*inputs) {
388 const char *fname = *inputs;
389 char *uri = go_shell_arg_to_uri (fname);
390 WorkbookView *wbv =
391 workbook_view_new_from_uri (uri, fo, io_context,
392 ssconvert_import_encoding);
393 g_free (uri);
394 inputs++;
396 if (go_io_error_occurred (io_context)) {
397 g_slist_free_full (wbs, g_object_unref);
398 return NULL;
401 if (!wbv)
402 continue;
404 wbs = g_slist_prepend (wbs, wb_view_get_workbook (wbv));
407 return g_slist_reverse (wbs);
411 * Look at a set of workbooks, and pick a sheet size that would
412 * be good for sheets in a workbook merging them all.
414 static void
415 suggest_size (GSList *wbs, int *csuggest, int *rsuggest)
417 GSList *l;
418 int rmax = 0;
419 int cmax = 0;
421 for (l = wbs; l; l = l->next) {
422 Workbook *wb = l->data;
424 WORKBOOK_FOREACH_SHEET (wb, sheet, {
425 int r = gnm_sheet_get_max_rows (sheet);
426 int c = gnm_sheet_get_max_cols (sheet);
427 if (r > rmax) rmax = r;
428 if (c > cmax) cmax = c;
432 gnm_sheet_suggest_size (&cmax, &rmax);
433 *csuggest = cmax;
434 *rsuggest = rmax;
437 static void
438 cb_collect_names (G_GNUC_UNUSED gconstpointer key,
439 GnmNamedExpr *nexpr,
440 GSList **plist)
442 if (!expr_name_is_active (nexpr))
443 return;
444 *plist = g_slist_prepend (*plist, expr_name_ref (nexpr));
447 /* Append the sheets of workbook wb2 to workbook wb. Resize sheets
448 if necessary. Fix workbook links in sheet if necessary.
449 Merge names in workbook scope (conflicts result in an error). */
450 static gboolean
451 merge_single (Workbook *wb, Workbook *wb2,
452 int cmax, int rmax,
453 GOCmdContext *cc)
455 /* Move names with workbook scope in wb2 over to wb */
456 GSList *names = g_slist_sort (gnm_named_expr_collection_list (wb2->names),
457 (GCompareFunc)expr_name_cmp_by_name);
458 GSList *p;
460 for (p = names; p; p = p->next) {
461 GnmNamedExpr *nexpr = p->data;
462 const char *name = expr_name_name (nexpr);
463 GnmNamedExpr *nexpr2;
464 GnmParsePos pp;
465 GnmParsePos newpos = nexpr->pos;
467 if (!expr_name_is_active (nexpr))
468 continue;
470 if (nexpr->pos.wb != wb2 || nexpr->pos.sheet != NULL)
471 continue;
473 /* Check for clash with existing name */
475 parse_pos_init (&pp, wb, NULL, 0, 0);
476 nexpr2 = expr_name_lookup (&pp, name);
477 if (nexpr2 /* FIXME: && nexpr2-is-not-the-same-as-nexpr */) {
478 g_printerr (_("Name conflict during merge: '%s' appears twice at workbook scope.\n"),
479 name);
480 g_slist_free (names);
481 return TRUE;
484 /* Move name scope to workbook wb */
485 newpos.wb = wb;
486 expr_name_set_pos (nexpr, &newpos);
488 g_slist_free (names);
490 while (workbook_sheet_count (wb2) > 0) {
491 /* Remove sheet from incoming workbook */
492 Sheet *sheet = workbook_sheet_by_index (wb2, 0);
493 int loc = workbook_sheet_count (wb);
494 GOUndo *undo;
495 char *sheet_name;
496 gboolean err;
497 GSList *names = NULL;
499 g_object_ref (sheet);
500 workbook_sheet_delete (sheet);
501 sheet->workbook = wb;
503 /* Fix names that reference the old workbook */
504 gnm_sheet_foreach_name (sheet, (GHFunc)cb_collect_names, &names);
505 while (names) {
506 GnmNamedExpr *nexpr = names->data;
507 names = g_slist_delete_link (names, names);
509 if (nexpr->pos.wb) {
510 GnmParsePos newpos = nexpr->pos;
511 newpos.wb = wb;
512 expr_name_set_pos (nexpr, &newpos);
514 expr_name_unref (nexpr);
517 undo = gnm_sheet_resize (sheet, cmax, rmax, cc, &err);
518 if (undo)
519 g_object_unref (undo);
521 /* Pick a free sheet name */
522 sheet_name = workbook_sheet_get_free_name
523 (wb, sheet->name_unquoted, FALSE, TRUE);
524 g_object_set (sheet, "name", sheet_name, NULL);
525 g_free (sheet_name);
527 /* Insert and revive the sheet */
528 workbook_sheet_attach_at_pos (wb, sheet, loc);
529 dependents_revive_sheet (sheet);
530 g_object_unref (sheet);
533 return FALSE;
536 /* Merge a collection of workbooks into one. */
537 static gboolean
538 merge (Workbook *wb, char const *inputs[],
539 GOFileOpener *fo, GOIOContext *io_context, GOCmdContext *cc)
541 GSList *wbs, *l;
542 int result = 0;
543 int cmax, rmax;
545 wbs = read_files_to_merge (inputs, fo, io_context, cc);
546 if (go_io_error_occurred (io_context)) {
547 go_io_error_display (io_context);
548 return TRUE;
551 suggest_size (wbs, &cmax, &rmax);
553 for (l = wbs; l; l = l->next) {
554 Workbook *wb2 = l->data;
555 const char *uri = go_doc_get_uri (GO_DOC (wb2));
557 g_printerr (_("Adding sheets from %s\n"), uri);
559 result = merge_single (wb, wb2, cmax, rmax, cc);
560 if (result)
561 break;
564 g_slist_free_full (wbs, g_object_unref);
565 return result;
568 static char *
569 resolve_template (const char *template, Sheet *sheet, unsigned n)
571 GString *s = g_string_new (NULL);
572 while (1) {
573 switch (*template) {
574 done:
575 case 0: {
576 char *res = go_shell_arg_to_uri (s->str);
577 g_string_free (s, TRUE);
578 return res;
580 case '%':
581 template++;
582 switch (*template) {
583 case 0:
584 goto done;
585 case 'n':
586 g_string_append_printf (s, "%u", n);
587 break;
588 case 's':
589 g_string_append (s, sheet->name_unquoted);
590 break;
591 case '%':
592 g_string_append_c (s, '%');
593 break;
595 template++;
596 break;
597 default:
598 g_string_append_c (s, *template);
599 template++;
604 static void
605 run_solver (Sheet *sheet, WorkbookView *wbv)
607 GnmSolverParameters *params = sheet->solver_parameters;
608 GError *err = NULL;
609 WorkbookControl *wbc;
610 GnmSolver *sol = NULL;
612 wbc = g_object_new (GNM_WBC_TYPE, NULL);
613 wb_control_set_view (wbc, wbv, NULL);
615 /* Pick a functional algorithm. */
616 if (!gnm_solver_factory_functional (params->options.algorithm,
617 NULL)) {
618 GSList *l;
619 for (l = gnm_solver_db_get (); l; l = l->next) {
620 GnmSolverFactory *factory = l->data;
621 if (params->options.model_type != factory->type)
622 continue;
623 if (gnm_solver_factory_functional (factory, NULL)) {
624 gnm_solver_param_set_algorithm (params,
625 factory);
626 break;
631 if (!gnm_solver_param_valid (params, &err))
632 goto done;
634 sol = params->options.algorithm
635 ? gnm_solver_factory_create (params->options.algorithm, params)
636 : NULL;
637 if (!sol) {
638 g_set_error (&err, go_error_invalid (), 0,
639 _("Failed to create solver"));
640 goto done;
643 if (!gnm_solver_start (sol, wbc, &err))
644 goto done;
646 while (!gnm_solver_finished (sol)) {
647 g_main_context_iteration (NULL, TRUE);
650 switch (sol->status) {
651 case GNM_SOLVER_STATUS_DONE:
652 break;
653 case GNM_SOLVER_STATUS_CANCELLED:
654 g_printerr (_("Solver reached time or iteration limit\n"));
655 break;
656 default:
657 g_set_error (&err, go_error_invalid (), 0,
658 _("Solver ran, but failed"));
659 goto done;
662 gnm_solver_store_result (sol);
664 gnm_solver_create_report (sol, "Solver");
666 done:
667 if (sol)
668 g_object_unref (sol);
669 if (err) {
670 g_printerr (_("Solver: %s\n"), err->message);
671 g_error_free (err);
675 #define GET_ARG(conv_,name_,def_) (g_hash_table_lookup_extended(args,(name_),NULL,&arg) ? conv_((const char *)arg) : (def_))
676 #define RANGE_ARG(s_) value_new_cellrange_str(sheet,(s_))
677 #define RANGE_LIST_ARG(s_) g_slist_prepend (NULL, value_new_cellrange_str(sheet,(s_)))
678 #define SHEET_ARG(s_) workbook_sheet_by_name(wb,(s_))
680 static void
681 run_tool_test (const char *tool, char **argv, WorkbookView *wbv)
683 int i;
684 WorkbookControl *wbc;
685 gpointer specs;
686 data_analysis_output_t *dao;
687 analysis_tool_engine engine;
688 Workbook *wb;
689 Sheet *sheet;
690 GHashTable *args;
691 gpointer arg;
694 * Arguments in argv are of the form key:value.
695 * Make a hash for those.
697 args = g_hash_table_new_full (g_str_hash, g_str_equal,
698 (GDestroyNotify)g_free,
699 (GDestroyNotify)g_free);
700 for (i = 0; argv[i]; i++) {
701 const char *s = argv[i];
702 const char *colon = strchr (s, ':');
703 if (!colon) {
704 g_printerr ("Ignoring tool test argument \"%s\"\n", s);
705 continue;
707 g_hash_table_replace (args, g_strndup (s, colon - s),
708 g_strdup (colon + 1));
711 wb = wb_view_get_workbook (wbv);
712 wbc = g_object_new (GNM_WBC_TYPE, NULL);
713 wb_control_set_view (wbc, wbv, NULL);
715 sheet = GET_ARG (SHEET_ARG, "sheet", wb_view_cur_sheet (wbv));
717 if (g_str_equal (tool, "regression")) {
718 analysis_tools_data_regression_t *data =
719 g_new0 (analysis_tools_data_regression_t, 1);
721 data->base.wbc = wbc;
722 data->base.range_1 = GET_ARG (RANGE_ARG, "x", value_new_error_REF (NULL));
723 data->base.range_2 = GET_ARG (RANGE_ARG, "y", value_new_error_REF (NULL));
724 data->base.labels = GET_ARG (atoi, "labels", FALSE);
725 data->base.alpha = GET_ARG (atof, "alpha", 0.05);
726 data->group_by = GET_ARG ((group_by_t), "grouped-by", GROUPED_BY_COL);
727 data->intercept = GET_ARG (atoi, "intercept", TRUE);
728 data->multiple_regression = GET_ARG (atoi, "multiple", TRUE);
729 data->multiple_y = GET_ARG (atoi, "multiple-y", FALSE);
730 data->residual = GET_ARG (atoi, "residual", TRUE);
732 engine = analysis_tool_regression_engine;
733 specs = data;
734 } else if (g_str_equal (tool, "anova")) {
735 analysis_tools_data_anova_single_t *data =
736 g_new0 (analysis_tools_data_anova_single_t, 1);
738 data->base.input = GET_ARG (RANGE_LIST_ARG, "data", NULL);
739 data->base.labels = GET_ARG (atoi, "labels", FALSE);
740 data->base.group_by = GET_ARG ((group_by_t), "grouped-by", GROUPED_BY_COL);
741 data->alpha = GET_ARG (atof, "alpha", 0.05);
743 engine = analysis_tool_anova_single_engine;
744 specs = data;
745 } else {
746 g_printerr ("no test for tool \"%s\"\n", tool);
747 return;
750 dao = dao_init_new_sheet (NULL);
751 dao->put_formulas = TRUE;
752 cmd_analysis_tool (wbc, sheet, dao, specs, engine, TRUE);
754 g_hash_table_destroy (args);
757 #undef GET_ARG
758 #undef RANGE_ARG
759 #undef RANGE_LISTARG
760 #undef SHEET_ARG
763 static int
764 do_split_save (GOFileSaver *fs, WorkbookView *wbv,
765 const char *outarg, GOCmdContext *cc)
767 Workbook *wb = wb_view_get_workbook (wbv);
768 char *template;
769 GPtrArray *sheets;
770 unsigned ui;
771 int res = 0;
772 GPtrArray *sheet_sel =
773 g_object_get_data (G_OBJECT (wb), SHEET_SELECTION_KEY);
774 gboolean fs_sheet_selection;
776 g_object_get (G_OBJECT (fs), "sheet-selection", &fs_sheet_selection, NULL);
778 template = strchr (outarg, '%')
779 ? g_strdup (outarg)
780 : g_strconcat (outarg, ".%n", NULL);
782 sheets = g_object_get_data (G_OBJECT (wb),
783 SSCONVERT_SHEET_SET_KEY);
784 if (sheets)
785 g_ptr_array_ref (sheets);
786 else {
787 int i;
788 sheets = g_ptr_array_new ();
789 for (i = 0; i < workbook_sheet_count (wb); i++) {
790 Sheet *sheet = workbook_sheet_by_index (wb, i);
791 g_ptr_array_add (sheets, sheet);
795 for (ui = 0; ui < sheets->len; ui++) {
796 Sheet *sheet = g_ptr_array_index (sheets, ui);
797 char *tmpfile = resolve_template (template, sheet, ui);
798 int oldn = sheet->index_in_wb;
800 g_ptr_array_set_size (sheet_sel, 0);
801 g_ptr_array_add (sheet_sel, sheet);
803 if (!fs_sheet_selection) {
805 * HACK: (bug 694408).
807 * We don't have a good way of specifying the
808 * sheet. Move it to the front and select
809 * it. That will at least make cvs and txt
810 * exporters reliably find it.
812 workbook_sheet_move (sheet, -oldn);
813 wb_view_sheet_focus (wbv, sheet);
816 res = !workbook_view_save_as (wbv, fs, tmpfile, cc);
818 if (!fs_sheet_selection)
819 workbook_sheet_move (sheet, +oldn);
821 g_free (tmpfile);
822 if (res)
823 break;
826 g_free (template);
827 g_ptr_array_unref (sheets);
829 return res;
832 static int
833 convert (char const *inarg, char const *outarg, char const *mergeargs[],
834 GOCmdContext *cc)
836 int res = 0;
837 GOFileSaver *fs = NULL;
838 GOFileOpener *fo = NULL;
839 char *infile = go_shell_arg_to_uri (inarg);
840 char *outfile = outarg ? go_shell_arg_to_uri (outarg) : NULL;
841 WorkbookView *wbv;
842 GOIOContext *io_context = NULL;
843 Workbook *wb = NULL;
844 GOFileSaveScope fsscope;
845 GPtrArray *sheet_sel = NULL;
846 GnmRangeRef const *range = NULL;
848 if (ssconvert_export_id != NULL) {
849 fs = go_file_saver_for_id (ssconvert_export_id);
850 if (fs == NULL) {
851 res = 1;
852 g_printerr (_("Unknown exporter '%s'.\n"
853 "Try --list-exporters to see a list of possibilities.\n"),
854 ssconvert_export_id);
855 goto out;
856 } else if (outfile == NULL &&
857 !ssconvert_one_file_per_sheet &&
858 go_file_saver_get_extension (fs) != NULL) {
859 char const *ext = gsf_extension_pointer (infile);
860 if (*infile) {
861 GString *res = g_string_new (NULL);
862 g_string_append_len (res, infile, ext - infile);
863 g_string_append (res, go_file_saver_get_extension(fs));
864 outfile = g_string_free (res, FALSE);
867 } else {
868 if (outfile != NULL) {
869 fs = go_file_saver_for_file_name (outfile);
870 if (fs == NULL) {
871 res = 2;
872 g_printerr (_("Unable to guess exporter to use for '%s'.\n"
873 "Try --list-exporters to see a list of possibilities.\n"),
874 outfile);
875 goto out;
877 if (ssconvert_verbose)
878 g_printerr (_("Using exporter %s\n"),
879 go_file_saver_get_id (fs));
883 if (outfile == NULL) {
884 g_printerr (_("An output file name or an explicit export type is required.\n"
885 "Try --list-exporters to see a list of possibilities.\n"));
886 res = 1;
887 goto out;
890 if (ssconvert_import_id != NULL) {
891 fo = go_file_opener_for_id (ssconvert_import_id);
892 if (fo == NULL) {
893 res = 1;
894 g_printerr (_("Unknown importer '%s'.\n"
895 "Try --list-importers to see a list of possibilities.\n"),
896 ssconvert_import_id);
897 goto out;
901 if (!fs)
902 goto out;
903 fsscope = go_file_saver_get_save_scope (fs);
905 io_context = go_io_context_new (cc);
906 if (mergeargs == NULL) {
907 wbv = workbook_view_new_from_uri (infile, fo,
908 io_context,
909 ssconvert_import_encoding);
910 } else {
911 wbv = workbook_view_new (NULL);
914 if (go_io_error_occurred (io_context)) {
915 go_io_error_display (io_context);
916 res = 1;
917 goto out;
918 } else if (wbv == NULL) {
919 g_printerr (_("Loading %s failed\n"), infile);
920 res = 1;
921 goto out;
924 wb = wb_view_get_workbook (wbv);
926 res = handle_export_options (fs, wb);
927 if (res)
928 goto out;
930 res = validate_sheet_selection (fs, wb);
931 if (res)
932 goto out;
934 if (mergeargs != NULL) {
935 if (merge (wb, mergeargs, fo, io_context, cc))
936 goto out;
939 if (ssconvert_goal_seek) {
940 int i;
941 Sheet *sheet = wb_view_cur_sheet (wbv);
943 for (i = 0; ssconvert_goal_seek[i]; i++) {
944 setup_range (G_OBJECT (sheet),
945 "ssconvert-goal-seek",
947 ssconvert_goal_seek[i]);
948 dialog_goal_seek (NULL, sheet);
952 if (ssconvert_solve) {
953 Sheet *sheet = wb_view_cur_sheet (wbv);
954 run_solver (sheet, wbv);
957 if (ssconvert_tool_test && ssconvert_tool_test[0]) {
958 run_tool_test (ssconvert_tool_test[0],
959 ssconvert_tool_test + 1,
960 wbv);
963 if (ssconvert_resize) {
964 int rows, cols;
965 if (sscanf (ssconvert_resize, "%dx%d", &rows, &cols) == 2) {
966 int n;
968 if (ssconvert_verbose)
969 g_printerr (_("Resizing to %dx%d\n"),
970 rows, cols);
972 for (n = workbook_sheet_count (wb) - 1;
973 n >= 0;
974 n--) {
975 gboolean err;
976 Sheet *sheet = workbook_sheet_by_index (wb, n);
977 GOUndo *undo =
978 gnm_sheet_resize (sheet, cols, rows,
979 NULL, &err);
980 if (err)
981 g_printerr (_("Resizing of sheet %s failed\n"),
982 sheet->name_unquoted);
983 g_object_unref (undo);
988 if (ssconvert_recalc)
989 workbook_recalc_all (wb);
990 gnm_app_recalc ();
992 if (ssconvert_range)
993 range = setup_range (G_OBJECT (wb),
994 "ssconvert-range",
996 ssconvert_range);
998 if (ssconvert_one_file_per_sheet ||
999 fsscope == GO_FILE_SAVE_SHEET ||
1000 range) {
1001 Sheet *def_sheet = NULL;
1003 if (range && range->a.sheet)
1004 def_sheet = range->a.sheet;
1005 else if (fsscope == GO_FILE_SAVE_SHEET || range)
1006 def_sheet = wb_view_cur_sheet (wbv);
1008 sheet_sel = g_ptr_array_new ();
1009 if (def_sheet)
1010 g_ptr_array_add (sheet_sel, def_sheet);
1011 g_object_set_data (G_OBJECT (wb),
1012 SHEET_SELECTION_KEY, sheet_sel);
1015 if (ssconvert_one_file_per_sheet) {
1016 res = do_split_save (fs, wbv, outarg, cc);
1017 } else {
1018 res = !workbook_view_save_as (wbv, fs, outfile, cc);
1021 if (sheet_sel) {
1022 g_object_set_data (G_OBJECT (wb),
1023 SHEET_SELECTION_KEY, NULL);
1024 g_ptr_array_free (sheet_sel, TRUE);
1027 out:
1028 if (wb)
1029 g_object_unref (wb);
1030 if (io_context)
1031 g_object_unref (io_context);
1032 g_free (infile);
1033 g_free (outfile);
1035 return res;
1039 main (int argc, char const **argv)
1041 GOErrorInfo *plugin_errs;
1042 int res = 0;
1043 GOCmdContext *cc;
1044 GOptionContext *ocontext;
1045 GError *error = NULL;
1047 /* No code before here, we need to init threads */
1048 argv = gnm_pre_parse_init (argc, argv);
1050 gnm_conf_set_persistence (FALSE);
1052 ocontext = g_option_context_new (_("INFILE [OUTFILE]"));
1053 g_option_context_add_main_entries (ocontext, ssconvert_options, GETTEXT_PACKAGE);
1054 g_option_context_add_group (ocontext, gnm_get_option_group ());
1056 * The printing code uses gtk+ stuff, so we need to init gtk+. We
1057 * do that without opening any displays.
1059 g_option_context_add_group (ocontext, gtk_get_option_group (FALSE));
1060 g_option_context_parse (ocontext, &argc, (char ***)&argv, &error);
1061 g_option_context_free (ocontext);
1063 if (error) {
1064 g_printerr (_("%s\nRun '%s --help' to see a full list of available command line options.\n"),
1065 error->message, argv[0]);
1066 g_error_free (error);
1067 return 1;
1070 if (ssconvert_show_version) {
1071 g_print (_("ssconvert version '%s'\ndatadir := '%s'\nlibdir := '%s'\n"),
1072 GNM_VERSION_FULL, gnm_sys_data_dir (), gnm_sys_lib_dir ());
1073 return 0;
1076 if (ssconvert_one_file_per_sheet && ssconvert_merge_target) {
1077 g_printerr (_("--export-file-per-sheet and --merge-to are incompatible\n"));
1078 return 1;
1081 gnm_init ();
1083 cc = gnm_cmd_context_stderr_new ();
1084 gnm_plugins_init (GO_CMD_CONTEXT (cc));
1085 go_plugin_db_activate_plugin_list (
1086 go_plugins_get_available_plugins (), &plugin_errs);
1087 if (plugin_errs) {
1088 /* FIXME: What do we want to do here? */
1089 go_error_info_free (plugin_errs);
1091 go_component_set_default_command_context (cc);
1093 if (ssconvert_list_exporters)
1094 list_them (go_get_file_savers (),
1095 (get_desc_f) &go_file_saver_get_id,
1096 (get_desc_f) &go_file_saver_get_description);
1097 else if (ssconvert_list_importers)
1098 list_them (go_get_file_openers (),
1099 (get_desc_f) &go_file_opener_get_id,
1100 (get_desc_f) &go_file_opener_get_description);
1101 else if (ssconvert_merge_target!=NULL && argc>=3) {
1102 res = convert (argv[1], ssconvert_merge_target, argv+1, cc);
1103 } else if (argc == 2 || argc == 3) {
1104 res = convert (argv[1], argv[2], NULL, cc);
1105 } else {
1106 g_printerr (_("Usage: %s [OPTION...] %s\n"),
1107 g_get_prgname (),
1108 _("INFILE [OUTFILE]"));
1109 res = 1;
1112 go_component_set_default_command_context (NULL);
1113 g_object_unref (cc);
1114 gnm_shutdown ();
1115 gnm_pre_parse_shutdown ();
1117 return res;