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
401 gnm_stf_export_can_transliterate (void)
403 char const *text
= "G\xc3\xbclzow";
405 GError
*error
= NULL
;
407 encoded_text
= g_convert (text
, -1,
408 "ASCII//TRANSLIT", "UTF-8",
410 g_free (encoded_text
);
415 g_error_free (error
);
419 /* ------------------------------------------------------------------------- */
422 gnm_stf_transliterate_mode_get_type (void)
424 static GType etype
= 0;
426 static GEnumValue
const values
[] = {
427 { GNM_STF_TRANSLITERATE_MODE_TRANS
, "GNM_STF_TRANSLITERATE_MODE_TRANS", "transliterate" },
428 { GNM_STF_TRANSLITERATE_MODE_ESCAPE
, "GNM_STF_TRANSLITERATE_MODE_ESCAPE", "escape" },
431 etype
= g_enum_register_static ("GnmStfTransliterateMode", values
);
436 /* ------------------------------------------------------------------------- */
439 gnm_stf_format_mode_get_type (void)
441 static GType etype
= 0;
443 static GEnumValue
const values
[] = {
444 { GNM_STF_FORMAT_AUTO
, "GNM_STF_FORMAT_AUTO", "automatic" },
445 { GNM_STF_FORMAT_RAW
, "GNM_STF_FORMAT_RAW", "raw" },
446 { GNM_STF_FORMAT_PRESERVE
, "GNM_STF_FORMAT_PRESERVE", "preserve" },
449 etype
= g_enum_register_static ("GnmStfFormatMode", values
);
454 /* ------------------------------------------------------------------------- */
457 gnm_stf_export_init (G_GNUC_UNUSED GObject
*obj
)
461 /* ------------------------------------------------------------------------- */
464 gnm_stf_export_finalize (GObject
*obj
)
466 GnmStfExport
*stfe
= (GnmStfExport
*)obj
;
468 gnm_stf_export_options_sheet_list_clear (stfe
);
469 g_free (stfe
->charset
);
470 g_free (stfe
->locale
);
472 G_OBJECT_CLASS (parent_class
)->finalize (obj
);
475 /* ------------------------------------------------------------------------- */
478 gnm_stf_export_get_property (GObject
*object
,
483 GnmStfExport
*stfe
= (GnmStfExport
*)object
;
485 switch (property_id
) {
487 g_value_set_string (value
, stfe
->charset
);
490 g_value_set_string (value
, stfe
->locale
);
492 case PROP_TRANSLITERATE_MODE
:
493 g_value_set_enum (value
, stfe
->transliterate_mode
);
496 g_value_set_enum (value
, stfe
->format
);
499 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
504 /* ------------------------------------------------------------------------- */
507 gnm_stf_export_set_property (GObject
*object
,
512 GnmStfExport
*stfe
= (GnmStfExport
*)object
;
515 switch (property_id
) {
517 scopy
= g_value_dup_string (value
);
518 g_free (stfe
->charset
);
519 stfe
->charset
= scopy
;
522 scopy
= g_value_dup_string (value
);
523 g_free (stfe
->locale
);
524 stfe
->locale
= scopy
;
526 case PROP_TRANSLITERATE_MODE
:
527 stfe
->transliterate_mode
= g_value_get_enum (value
);
530 stfe
->format
= g_value_get_enum (value
);
533 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
538 /* ------------------------------------------------------------------------- */
541 gnm_stf_export_class_init (GObjectClass
*gobject_class
)
543 parent_class
= g_type_class_peek_parent (gobject_class
);
545 gobject_class
->finalize
= gnm_stf_export_finalize
;
546 gobject_class
->get_property
= gnm_stf_export_get_property
;
547 gobject_class
->set_property
= gnm_stf_export_set_property
;
549 g_object_class_install_property
552 g_param_spec_string ("charset",
554 P_("The character encoding of the output."),
558 g_object_class_install_property
561 g_param_spec_string ("locale",
563 P_("The locale to use for number and date formatting."),
567 g_object_class_install_property
569 PROP_TRANSLITERATE_MODE
,
570 g_param_spec_enum ("transliterate-mode",
571 P_("Transliterate mode"),
572 P_("What to do with unrepresentable characters."),
573 GNM_STF_TRANSLITERATE_MODE_TYPE
,
574 GNM_STF_TRANSLITERATE_MODE_ESCAPE
,
577 g_object_class_install_property
580 g_param_spec_enum ("format",
582 P_("How should cells be formatted?"),
583 GNM_STF_FORMAT_MODE_TYPE
,
589 /* ------------------------------------------------------------------------- */
591 GSF_CLASS (GnmStfExport
, gnm_stf_export
,
592 gnm_stf_export_class_init
, gnm_stf_export_init
, GSF_OUTPUT_CSV_TYPE
)
594 /* ------------------------------------------------------------------------- */
595 /* ------------------------------------------------------------------------- */
599 #include <dialogs/dialog-stf-export.h>
600 #include <workbook-view.h>
604 * @obj: #GObject with a #GnmStfExport attached as data.
606 * If none is found, a new one is created and attached to @obj.
607 * Returns: (transfer none): the #GnmStfExport.
610 gnm_stf_get_stfe (GObject
*obj
)
612 GnmStfExport
*stfe
= g_object_get_data (obj
, "stfe");
614 const char * sep
= gnm_conf_get_stf_export_separator ();
615 const char * string_indicator
= gnm_conf_get_stf_export_stringindicator ();
616 const char * terminator
= gnm_conf_get_stf_export_terminator ();
617 const char * locale
= gnm_conf_get_stf_export_locale ();
618 const char * encoding
= gnm_conf_get_stf_export_encoding ();
619 int quotingmode
= gnm_conf_get_stf_export_quoting ();
620 int format
= gnm_conf_get_stf_export_format ();
621 int transliteratemode
= gnm_conf_get_stf_export_transliteration () ?
622 GNM_STF_TRANSLITERATE_MODE_TRANS
: GNM_STF_TRANSLITERATE_MODE_ESCAPE
;
623 GString
*triggers
= g_string_new (NULL
);
625 if (strlen (locale
) == 0)
627 if (strlen (encoding
) == 0)
630 /* Workaround GConf bug #641807. */
631 if (terminator
== NULL
|| strlen (terminator
) == 0)
634 if (quotingmode
== GSF_OUTPUT_CSV_QUOTING_MODE_AUTO
) {
635 g_string_append (triggers
, " \t");
636 g_string_append (triggers
, terminator
);
637 g_string_append (triggers
, string_indicator
);
638 g_string_append (triggers
, sep
);
641 stfe
= g_object_new (GNM_STF_EXPORT_TYPE
,
642 "quoting-triggers", triggers
->str
,
644 "quote", string_indicator
,
648 "quoting-mode", quotingmode
,
649 "transliterate-mode", transliteratemode
,
652 g_object_set_data_full (obj
, "stfe", stfe
, g_object_unref
);
653 g_string_free (triggers
, TRUE
);
659 gnm_stf_file_saver_save (G_GNUC_UNUSED GOFileSaver
const *fs
,
660 GOIOContext
*context
,
661 GoView
const *view
, GsfOutput
*output
)
663 WorkbookView
*wbv
= GNM_WORKBOOK_VIEW (view
);
664 Workbook
*wb
= wb_view_get_workbook (wbv
);
665 GnmStfExport
*stfe
= gnm_stf_get_stfe (G_OBJECT (wb
));
666 GsfOutput
*dummy_sink
;
669 /* TODO: move this GUI dependent code out of this
670 * filesaver into gui-file.c. After this, remove includes (see above). */
671 if (GNM_IS_WBC_GTK (context
->impl
)) {
673 stf_export_dialog (WBC_GTK (context
->impl
), stfe
, wb
);
675 go_io_error_unknown (context
);
680 nosheets
= (stfe
->sheet_list
== NULL
);
682 GPtrArray
*sel
= gnm_file_saver_get_sheets (fs
, wbv
, TRUE
);
684 for (ui
= 0; ui
< sel
->len
; ui
++)
685 gnm_stf_export_options_sheet_list_add
686 (stfe
, g_ptr_array_index (sel
, ui
));
687 g_ptr_array_unref (sel
);
690 g_object_set (G_OBJECT (stfe
), "sink", output
, NULL
);
691 if (gnm_stf_export (stfe
) == FALSE
)
692 go_cmd_context_error_import (GO_CMD_CONTEXT (context
),
693 _("Error while trying to export file as text"));
695 /* We're not allowed to set a NULL sink, so use a dummy. */
696 dummy_sink
= gsf_output_memory_new ();
697 g_object_set (G_OBJECT (stfe
), "sink", dummy_sink
, NULL
);
698 g_object_unref (dummy_sink
);
701 gnm_stf_export_options_sheet_list_clear (stfe
);
704 struct cb_set_export_option
{
710 cb_set_export_option (const char *key
, const char *value
,
711 GError
**err
, gpointer user_
)
713 struct cb_set_export_option
*user
= user_
;
714 Workbook
const *wb
= user
->wb
;
715 GnmStfExport
*stfe
= gnm_stf_get_stfe (G_OBJECT (wb
));
718 if (strcmp (key
, "eol") == 0) {
720 if (g_ascii_strcasecmp ("unix", value
) == 0)
722 else if (g_ascii_strcasecmp ("mac", value
) == 0)
724 else if (g_ascii_strcasecmp ("windows", value
) == 0)
727 errtxt
= _("eol must be one of unix, mac, and windows");
731 g_object_set (G_OBJECT (stfe
), "eol", eol
, NULL
);
735 if (strcmp (key
, "charset") == 0 ||
736 strcmp (key
, "locale") == 0 ||
737 strcmp (key
, "quote") == 0 ||
738 strcmp (key
, "separator") == 0 ||
739 strcmp (key
, "format") == 0 ||
740 strcmp (key
, "transliterate-mode") == 0 ||
741 strcmp (key
, "quoting-mode") == 0 ||
742 strcmp (key
, "quoting-on-whitespace") == 0)
743 return go_object_set_property
747 (_("Invalid value for option %s: \"%s\"")));
749 return gnm_file_saver_common_export_option (user
->fs
, wb
,
754 *err
= g_error_new (go_error_invalid (), 0, "%s", errtxt
);
760 gnm_stf_fs_set_export_options (GOFileSaver
*fs
,
764 G_GNUC_UNUSED gpointer user
)
766 GnmStfExport
*stfe
= gnm_stf_get_stfe (G_OBJECT (doc
));
767 struct cb_set_export_option data
;
769 data
.wb
= WORKBOOK (doc
);
770 gnm_stf_export_options_sheet_list_clear (stfe
);
771 return go_parse_key_value (options
, err
, cb_set_export_option
, &data
);
775 * gnm_stf_file_saver_create:
778 * Returns: (transfer full): the newly allocated #GOFileSaver.
781 gnm_stf_file_saver_create (gchar
const *id
)
783 GOFileSaver
*fs
= go_file_saver_new (id
,
785 _("Text (configurable)"),
786 GO_FILE_FL_WRITE_ONLY
,
787 gnm_stf_file_saver_save
);
788 go_file_saver_set_save_scope (fs
, GO_FILE_SAVE_WORKBOOK
);
789 g_object_set (G_OBJECT (fs
), "sheet-selection", TRUE
, NULL
);
790 g_signal_connect (G_OBJECT (fs
), "set-export-options",
791 G_CALLBACK (gnm_stf_fs_set_export_options
),
793 return GO_FILE_SAVER (fs
);