2 * stf-export.c : Structured Text Format Exporter (STF-E)
3 * Engine to construct CSV files
5 * Copyright (C) Almer. S. Tigelaar.
6 * EMail: almer1@dds.nl or almer-t@bigfoot.com
8 * Based on the csv-io.c plugin by:
9 * Miguel de Icaza <miguel@gnu.org>
10 * Jody Goldberg <jody@gnome.org>
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses/>.
26 #include <gnumeric-config.h>
29 #include <stf-export.h>
31 #include <gnumeric-conf.h>
37 #include <gnm-format.h>
38 #include <gnm-datetime.h>
39 #include <gsf/gsf-output-iconv.h>
40 #include <gsf/gsf-output-memory.h>
41 #include <gsf/gsf-impl-utils.h>
42 #include <goffice/goffice.h>
47 struct _GnmStfExport
{
53 GnmStfTransliterateMode transliterate_mode
;
54 GnmStfFormatMode format
;
57 static GObjectClass
*parent_class
;
60 GsfOutputCsvClass base_class
;
67 PROP_TRANSLITERATE_MODE
,
71 /* ------------------------------------------------------------------------- */
74 cb_sheet_destroyed (GnmStfExport
*stfe
, gpointer deadsheet
)
76 g_return_if_fail (GNM_IS_STF_EXPORT (stfe
));
78 stfe
->sheet_list
= g_slist_remove (stfe
->sheet_list
, deadsheet
);
82 * gnm_stf_export_options_sheet_list_clear:
83 * @stfe: an export options struct
85 * Clears the sheet list.
88 gnm_stf_export_options_sheet_list_clear (GnmStfExport
*stfe
)
92 g_return_if_fail (GNM_IS_STF_EXPORT (stfe
));
95 for (l
= stfe
->sheet_list
; l
; l
= l
->next
) {
96 Sheet
*sheet
= l
->data
;
98 g_object_weak_unref (G_OBJECT (sheet
),
99 (GWeakNotify
) cb_sheet_destroyed
,
103 g_slist_free (stfe
->sheet_list
);
104 stfe
->sheet_list
= NULL
;
109 * gnm_stf_export_options_sheet_list_add:
110 * @stfe: an export options struct
111 * @sheet: a gnumeric sheet
113 * Appends a @sheet to the list of sheets to be exported
116 gnm_stf_export_options_sheet_list_add (GnmStfExport
*stfe
, Sheet
*sheet
)
118 g_return_if_fail (GNM_IS_STF_EXPORT (stfe
));
119 g_return_if_fail (IS_SHEET (sheet
));
121 g_object_weak_ref (G_OBJECT (sheet
),
122 (GWeakNotify
) cb_sheet_destroyed
,
124 stfe
->sheet_list
= g_slist_append (stfe
->sheet_list
, sheet
);
129 * gnm_stf_export_options_sheet_list_get:
130 * @stfe: #GnmStfExport
132 * Returns: (element-type Sheet) (transfer none): the list of #Sheet instances
133 * added to @stfe using gnm_stf_export_options_sheet_list_add().
136 gnm_stf_export_options_sheet_list_get (const GnmStfExport
*stfe
)
138 g_return_val_if_fail (GNM_IS_STF_EXPORT (stfe
), NULL
);
140 return stfe
->sheet_list
;
143 /* ------------------------------------------------------------------------- */
147 try_auto_float (GnmValue
*value
, const GOFormat
*format
,
148 GODateConventions
const *date_conv
)
153 if (!VALUE_IS_FLOAT (value
))
156 format
= gnm_format_specialize (format
, value
);
157 is_date
= go_format_is_date (format
) > 0;
158 is_time
= go_format_is_time (format
);
160 if (is_date
|| is_time
> 0)
163 return format_value (go_format_general (), value
, -1, date_conv
);
168 try_auto_date (GnmValue
*value
, const GOFormat
*format
,
169 GODateConventions
const *date_conv
)
174 gboolean needs_date
, needs_time
, needs_frac_sec
;
180 format
= gnm_format_specialize (format
, value
);
181 is_date
= go_format_is_date (format
) > 0;
182 is_time
= go_format_is_time (format
);
184 if (!is_date
&& is_time
<= 0)
187 /* We don't want to coerce strings. */
188 if (!VALUE_IS_FLOAT (value
))
191 /* Verify that the date is valid. */
192 if (!datetime_value_to_g (&date
, value
, date_conv
))
195 v
= value_get_as_float (value
);
196 vr
= gnm_fake_round (v
);
197 vs
= (24 * 60 * 60) * gnm_abs (v
- vr
);
199 needs_date
= is_time
< 2 && (is_date
|| gnm_abs (v
) >= 1);
200 needs_time
= is_time
> 0 || gnm_abs (v
- vr
) > 1e-9;
201 needs_frac_sec
= needs_time
&& gnm_abs (vs
- gnm_fake_round (vs
)) >= 0.5e-3;
203 xlfmt
= g_string_new (NULL
);
204 if (needs_date
) g_string_append (xlfmt
, "yyyy/mm/dd");
207 g_string_append_c (xlfmt
, ' ');
209 g_string_append (xlfmt
, "[h]:mm:ss");
211 g_string_append (xlfmt
, "hh:mm:ss");
213 g_string_append (xlfmt
, ".000");
215 actual
= go_format_new_from_XL (xlfmt
->str
);
216 g_string_free (xlfmt
, TRUE
);
217 res
= format_value (actual
, value
, -1, date_conv
);
218 go_format_unref (actual
);
225 * @stfe: an export options struct
226 * @cell: the cell to write to the file
228 * Return value: return TRUE on success, FALSE otherwise.
231 stf_export_cell (GnmStfExport
*stfe
, GnmCell
*cell
)
233 char const *text
= NULL
;
236 g_return_val_if_fail (stfe
!= NULL
, FALSE
);
239 switch (stfe
->format
) {
240 case GNM_STF_FORMAT_PRESERVE
:
241 text
= tmp
= gnm_cell_get_rendered_text (cell
);
244 case GNM_STF_FORMAT_AUTO
:
246 GODateConventions
const *date_conv
=
247 sheet_date_conv (cell
->base
.sheet
);
248 GOFormat
const *format
= gnm_cell_get_format (cell
);
249 text
= tmp
= try_auto_date (cell
->value
, format
, date_conv
);
251 text
= tmp
= try_auto_float (cell
->value
, format
, date_conv
);
253 text
= value_peek_string (cell
->value
);
256 case GNM_STF_FORMAT_RAW
:
258 text
= value_peek_string (cell
->value
);
263 ok
= gsf_output_csv_write_field (GSF_OUTPUT_CSV (stfe
),
273 * @stfe: an export options struct
274 * @sheet: the sheet to export
276 * Writes the @sheet to the callback function
278 * Return value: returns TRUE on success, FALSE otherwise
281 stf_export_sheet (GnmStfExport
*stfe
, Sheet
*sheet
)
287 g_return_val_if_fail (stfe
!= NULL
, FALSE
);
288 g_return_val_if_fail (IS_SHEET (sheet
), FALSE
);
290 range
= g_object_get_data (G_OBJECT (sheet
->workbook
), "ssconvert-range");
292 Sheet
*start_sheet
, *end_sheet
;
295 gnm_rangeref_normalize (range
,
296 eval_pos_init_sheet (&ep
, sheet
),
297 &start_sheet
, &end_sheet
,
300 if (start_sheet
!= sheet
)
303 r
= sheet_get_extent (sheet
, FALSE
, TRUE
);
305 for (row
= r
.start
.row
; row
<= r
.end
.row
; row
++) {
306 for (col
= r
.start
.col
; col
<= r
.end
.col
; col
++) {
307 GnmCell
*cell
= sheet_cell_get (sheet
, col
, row
);
308 if (!stf_export_cell (stfe
, cell
))
311 if (!gsf_output_csv_write_eol (GSF_OUTPUT_CSV (stfe
)))
320 * @export_options: an export options struct
322 * Exports the sheets given in @stfe
324 * Return value: TRUE on success, FALSE otherwise
327 gnm_stf_export (GnmStfExport
*stfe
)
331 gboolean result
= TRUE
;
332 char *old_locale
= NULL
;
334 g_return_val_if_fail (GNM_IS_STF_EXPORT (stfe
), FALSE
);
335 g_return_val_if_fail (stfe
->sheet_list
!= NULL
, FALSE
);
336 g_object_get (G_OBJECT (stfe
), "sink", &sink
, NULL
);
337 g_return_val_if_fail (sink
!= NULL
, FALSE
);
340 strcmp (stfe
->charset
, "UTF-8") != 0) {
342 GsfOutput
*converter
;
344 switch (stfe
->transliterate_mode
) {
346 case GNM_STF_TRANSLITERATE_MODE_ESCAPE
:
347 charset
= g_strdup (stfe
->charset
);
349 case GNM_STF_TRANSLITERATE_MODE_TRANS
:
350 charset
= g_strconcat (stfe
->charset
,
355 converter
= gsf_output_iconv_new (sink
, charset
, "UTF-8");
359 g_object_set (G_OBJECT (stfe
), "sink", converter
, NULL
);
360 g_object_unref (converter
);
362 g_warning ("Failed to create converter.");
368 old_locale
= g_strdup (go_setlocale (LC_ALL
, NULL
));
369 go_setlocale (LC_ALL
, stfe
->locale
);
372 for (ptr
= stfe
->sheet_list
; ptr
!= NULL
; ptr
= ptr
->next
) {
373 Sheet
*sheet
= ptr
->data
;
374 if (!stf_export_sheet (stfe
, sheet
)) {
380 if (old_locale
) /* go_setlocale clears the cache, */
381 /*so we don't want to call it with NULL*/
382 go_setlocale (LC_ALL
, old_locale
);
386 g_object_set (G_OBJECT (stfe
), "sink", sink
, NULL
);
387 g_object_unref (sink
);
392 /* ------------------------------------------------------------------------- */
395 * gnm_stf_export_can_transliterate:
397 * Return value: %TRUE iff //TRANSLIT is supported
400 gnm_stf_export_can_transliterate (void)
402 char const *text
= "G\xc3\xbclzow";
404 GError
*error
= NULL
;
406 encoded_text
= g_convert (text
, -1,
407 "ASCII//TRANSLIT", "UTF-8",
409 g_free (encoded_text
);
414 g_error_free (error
);
418 /* ------------------------------------------------------------------------- */
421 gnm_stf_transliterate_mode_get_type (void)
423 static GType etype
= 0;
425 static GEnumValue
const values
[] = {
426 { GNM_STF_TRANSLITERATE_MODE_TRANS
, "GNM_STF_TRANSLITERATE_MODE_TRANS", "transliterate" },
427 { GNM_STF_TRANSLITERATE_MODE_ESCAPE
, "GNM_STF_TRANSLITERATE_MODE_ESCAPE", "escape" },
430 etype
= g_enum_register_static ("GnmStfTransliterateMode", values
);
435 /* ------------------------------------------------------------------------- */
438 gnm_stf_format_mode_get_type (void)
440 static GType etype
= 0;
442 static GEnumValue
const values
[] = {
443 { GNM_STF_FORMAT_AUTO
, "GNM_STF_FORMAT_AUTO", "automatic" },
444 { GNM_STF_FORMAT_RAW
, "GNM_STF_FORMAT_RAW", "raw" },
445 { GNM_STF_FORMAT_PRESERVE
, "GNM_STF_FORMAT_PRESERVE", "preserve" },
448 etype
= g_enum_register_static ("GnmStfFormatMode", values
);
453 /* ------------------------------------------------------------------------- */
456 gnm_stf_export_init (G_GNUC_UNUSED GObject
*obj
)
460 /* ------------------------------------------------------------------------- */
463 gnm_stf_export_finalize (GObject
*obj
)
465 GnmStfExport
*stfe
= (GnmStfExport
*)obj
;
467 gnm_stf_export_options_sheet_list_clear (stfe
);
468 g_free (stfe
->charset
);
469 g_free (stfe
->locale
);
471 G_OBJECT_CLASS (parent_class
)->finalize (obj
);
474 /* ------------------------------------------------------------------------- */
477 gnm_stf_export_get_property (GObject
*object
,
482 GnmStfExport
*stfe
= (GnmStfExport
*)object
;
484 switch (property_id
) {
486 g_value_set_string (value
, stfe
->charset
);
489 g_value_set_string (value
, stfe
->locale
);
491 case PROP_TRANSLITERATE_MODE
:
492 g_value_set_enum (value
, stfe
->transliterate_mode
);
495 g_value_set_enum (value
, stfe
->format
);
498 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
503 /* ------------------------------------------------------------------------- */
506 gnm_stf_export_set_property (GObject
*object
,
511 GnmStfExport
*stfe
= (GnmStfExport
*)object
;
514 switch (property_id
) {
516 scopy
= g_value_dup_string (value
);
517 g_free (stfe
->charset
);
518 stfe
->charset
= scopy
;
521 scopy
= g_value_dup_string (value
);
522 g_free (stfe
->locale
);
523 stfe
->locale
= scopy
;
525 case PROP_TRANSLITERATE_MODE
:
526 stfe
->transliterate_mode
= g_value_get_enum (value
);
529 stfe
->format
= g_value_get_enum (value
);
532 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
537 /* ------------------------------------------------------------------------- */
540 gnm_stf_export_class_init (GObjectClass
*gobject_class
)
542 parent_class
= g_type_class_peek_parent (gobject_class
);
544 gobject_class
->finalize
= gnm_stf_export_finalize
;
545 gobject_class
->get_property
= gnm_stf_export_get_property
;
546 gobject_class
->set_property
= gnm_stf_export_set_property
;
548 g_object_class_install_property
551 g_param_spec_string ("charset",
553 P_("The character encoding of the output."),
557 g_object_class_install_property
560 g_param_spec_string ("locale",
562 P_("The locale to use for number and date formatting."),
566 g_object_class_install_property
568 PROP_TRANSLITERATE_MODE
,
569 g_param_spec_enum ("transliterate-mode",
570 P_("Transliterate mode"),
571 P_("What to do with unrepresentable characters."),
572 GNM_STF_TRANSLITERATE_MODE_TYPE
,
573 GNM_STF_TRANSLITERATE_MODE_ESCAPE
,
576 g_object_class_install_property
579 g_param_spec_enum ("format",
581 P_("How should cells be formatted?"),
582 GNM_STF_FORMAT_MODE_TYPE
,
588 /* ------------------------------------------------------------------------- */
590 GSF_CLASS (GnmStfExport
, gnm_stf_export
,
591 gnm_stf_export_class_init
, gnm_stf_export_init
, GSF_OUTPUT_CSV_TYPE
)
593 /* ------------------------------------------------------------------------- */
594 /* ------------------------------------------------------------------------- */
598 #include <dialogs/dialog-stf-export.h>
599 #include <workbook-view.h>
603 * @obj: #GObject with a #GnmStfExport attached as data.
605 * If none is found, a new one is created and attached to @obj.
606 * Returns: (transfer none): the #GnmStfExport.
609 gnm_stf_get_stfe (GObject
*obj
)
611 GnmStfExport
*stfe
= g_object_get_data (obj
, "stfe");
613 const char * sep
= gnm_conf_get_stf_export_separator ();
614 const char * string_indicator
= gnm_conf_get_stf_export_stringindicator ();
615 const char * terminator
= gnm_conf_get_stf_export_terminator ();
616 const char * locale
= gnm_conf_get_stf_export_locale ();
617 const char * encoding
= gnm_conf_get_stf_export_encoding ();
618 int quotingmode
= gnm_conf_get_stf_export_quoting ();
619 int format
= gnm_conf_get_stf_export_format ();
620 int transliteratemode
= gnm_conf_get_stf_export_transliteration () ?
621 GNM_STF_TRANSLITERATE_MODE_TRANS
: GNM_STF_TRANSLITERATE_MODE_ESCAPE
;
622 GString
*triggers
= g_string_new (NULL
);
624 if (strlen (locale
) == 0)
626 if (strlen (encoding
) == 0)
629 /* Workaround GConf bug #641807. */
630 if (terminator
== NULL
|| strlen (terminator
) == 0)
633 if (quotingmode
== GSF_OUTPUT_CSV_QUOTING_MODE_AUTO
) {
634 g_string_append (triggers
, " \t");
635 g_string_append (triggers
, terminator
);
636 g_string_append (triggers
, string_indicator
);
637 g_string_append (triggers
, sep
);
640 stfe
= g_object_new (GNM_STF_EXPORT_TYPE
,
641 "quoting-triggers", triggers
->str
,
643 "quote", string_indicator
,
647 "quoting-mode", quotingmode
,
648 "transliterate-mode", transliteratemode
,
651 g_object_set_data_full (obj
, "stfe", stfe
, g_object_unref
);
652 g_string_free (triggers
, TRUE
);
658 gnm_stf_file_saver_save (G_GNUC_UNUSED GOFileSaver
const *fs
,
659 GOIOContext
*context
,
660 GoView
const *view
, GsfOutput
*output
)
662 WorkbookView
*wbv
= GNM_WORKBOOK_VIEW (view
);
663 Workbook
*wb
= wb_view_get_workbook (wbv
);
664 GnmStfExport
*stfe
= gnm_stf_get_stfe (G_OBJECT (wb
));
665 GsfOutput
*dummy_sink
;
668 /* TODO: move this GUI dependent code out of this
669 * filesaver into gui-file.c. After this, remove includes (see above). */
670 if (GNM_IS_WBC_GTK (context
->impl
)) {
672 stf_export_dialog (WBC_GTK (context
->impl
), stfe
, wb
);
674 go_io_error_unknown (context
);
679 nosheets
= (stfe
->sheet_list
== NULL
);
681 GPtrArray
*sel
= gnm_file_saver_get_sheets (fs
, wbv
, TRUE
);
683 for (ui
= 0; ui
< sel
->len
; ui
++)
684 gnm_stf_export_options_sheet_list_add
685 (stfe
, g_ptr_array_index (sel
, ui
));
686 g_ptr_array_unref (sel
);
689 g_object_set (G_OBJECT (stfe
), "sink", output
, NULL
);
690 if (gnm_stf_export (stfe
) == FALSE
)
691 go_cmd_context_error_import (GO_CMD_CONTEXT (context
),
692 _("Error while trying to export file as text"));
694 /* We're not allowed to set a NULL sink, so use a dummy. */
695 dummy_sink
= gsf_output_memory_new ();
696 g_object_set (G_OBJECT (stfe
), "sink", dummy_sink
, NULL
);
697 g_object_unref (dummy_sink
);
700 gnm_stf_export_options_sheet_list_clear (stfe
);
703 struct cb_set_export_option
{
709 cb_set_export_option (const char *key
, const char *value
,
710 GError
**err
, gpointer user_
)
712 struct cb_set_export_option
*user
= user_
;
713 Workbook
const *wb
= user
->wb
;
714 GnmStfExport
*stfe
= gnm_stf_get_stfe (G_OBJECT (wb
));
717 if (strcmp (key
, "eol") == 0) {
719 if (g_ascii_strcasecmp ("unix", value
) == 0)
721 else if (g_ascii_strcasecmp ("mac", value
) == 0)
723 else if (g_ascii_strcasecmp ("windows", value
) == 0)
726 errtxt
= _("eol must be one of unix, mac, and windows");
730 g_object_set (G_OBJECT (stfe
), "eol", eol
, NULL
);
734 if (strcmp (key
, "charset") == 0 ||
735 strcmp (key
, "locale") == 0 ||
736 strcmp (key
, "quote") == 0 ||
737 strcmp (key
, "separator") == 0 ||
738 strcmp (key
, "format") == 0 ||
739 strcmp (key
, "transliterate-mode") == 0 ||
740 strcmp (key
, "quoting-mode") == 0 ||
741 strcmp (key
, "quoting-on-whitespace") == 0)
742 return go_object_set_property
746 (_("Invalid value for option %s: \"%s\"")));
748 return gnm_file_saver_common_export_option (user
->fs
, wb
,
753 *err
= g_error_new (go_error_invalid (), 0, "%s", errtxt
);
759 gnm_stf_fs_set_export_options (GOFileSaver
*fs
,
763 G_GNUC_UNUSED gpointer user
)
765 GnmStfExport
*stfe
= gnm_stf_get_stfe (G_OBJECT (doc
));
766 struct cb_set_export_option data
;
768 data
.wb
= WORKBOOK (doc
);
769 gnm_stf_export_options_sheet_list_clear (stfe
);
770 return go_parse_key_value (options
, err
, cb_set_export_option
, &data
);
774 * gnm_stf_file_saver_create:
777 * Returns: (transfer full): the newly allocated #GOFileSaver.
780 gnm_stf_file_saver_create (gchar
const *id
)
782 GOFileSaver
*fs
= go_file_saver_new (id
,
784 _("Text (configurable)"),
785 GO_FILE_FL_WRITE_ONLY
,
786 gnm_stf_file_saver_save
);
787 go_file_saver_set_save_scope (fs
, GO_FILE_SAVE_WORKBOOK
);
788 g_object_set (G_OBJECT (fs
), "sheet-selection", TRUE
, NULL
);
789 g_signal_connect (G_OBJECT (fs
), "set-export-options",
790 G_CALLBACK (gnm_stf_fs_set_export_options
),
792 return GO_FILE_SAVER (fs
);