1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * stf-export.c : Structured Text Format Exporter (STF-E)
4 * Engine to construct CSV files
6 * Copyright (C) Almer. S. Tigelaar.
7 * EMail: almer1@dds.nl or almer-t@bigfoot.com
9 * Based on the csv-io.c plugin by:
10 * Miguel de Icaza <miguel@gnu.org>
11 * Jody Goldberg <jody@gnome.org>
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, see <https://www.gnu.org/licenses/>.
27 #include <gnumeric-config.h>
30 #include "stf-export.h"
32 #include "gnumeric-conf.h"
38 #include "gnm-format.h"
39 #include "gnm-datetime.h"
40 #include <gsf/gsf-output-iconv.h>
41 #include <gsf/gsf-output-memory.h>
42 #include <gsf/gsf-impl-utils.h>
43 #include <goffice/goffice.h>
48 struct _GnmStfExport
{
54 GnmStfTransliterateMode transliterate_mode
;
55 GnmStfFormatMode format
;
58 static GObjectClass
*parent_class
;
61 GsfOutputCsvClass base_class
;
68 PROP_TRANSLITERATE_MODE
,
72 /* ------------------------------------------------------------------------- */
75 cb_sheet_destroyed (GnmStfExport
*stfe
, gpointer deadsheet
)
77 g_return_if_fail (GNM_IS_STF_EXPORT (stfe
));
79 stfe
->sheet_list
= g_slist_remove (stfe
->sheet_list
, deadsheet
);
83 * gnm_stf_export_options_sheet_list_clear:
84 * @stfe: an export options struct
86 * Clears the sheet list.
89 gnm_stf_export_options_sheet_list_clear (GnmStfExport
*stfe
)
93 g_return_if_fail (GNM_IS_STF_EXPORT (stfe
));
96 for (l
= stfe
->sheet_list
; l
; l
= l
->next
) {
97 Sheet
*sheet
= l
->data
;
99 g_object_weak_unref (G_OBJECT (sheet
),
100 (GWeakNotify
) cb_sheet_destroyed
,
104 g_slist_free (stfe
->sheet_list
);
105 stfe
->sheet_list
= NULL
;
110 * gnm_stf_export_options_sheet_list_add:
111 * @stfe: an export options struct
112 * @sheet: a gnumeric sheet
114 * Appends a @sheet to the list of sheets to be exported
117 gnm_stf_export_options_sheet_list_add (GnmStfExport
*stfe
, Sheet
*sheet
)
119 g_return_if_fail (GNM_IS_STF_EXPORT (stfe
));
120 g_return_if_fail (IS_SHEET (sheet
));
122 g_object_weak_ref (G_OBJECT (sheet
),
123 (GWeakNotify
) cb_sheet_destroyed
,
125 stfe
->sheet_list
= g_slist_append (stfe
->sheet_list
, sheet
);
130 * gnm_stf_export_options_sheet_list_get:
131 * @stfe: #GnmStfExport
133 * Returns: (element-type Sheet) (transfer none): the list of #Sheet instances
134 * added to @stfe using gnm_stf_export_options_sheet_list_add().
137 gnm_stf_export_options_sheet_list_get (const GnmStfExport
*stfe
)
139 g_return_val_if_fail (GNM_IS_STF_EXPORT (stfe
), NULL
);
141 return stfe
->sheet_list
;
144 /* ------------------------------------------------------------------------- */
148 try_auto_float (GnmValue
*value
, const GOFormat
*format
,
149 GODateConventions
const *date_conv
)
154 if (!VALUE_IS_FLOAT (value
))
157 format
= gnm_format_specialize (format
, value
);
158 is_date
= go_format_is_date (format
) > 0;
159 is_time
= go_format_is_time (format
);
161 if (is_date
|| is_time
> 0)
164 return format_value (go_format_general (), value
, -1, date_conv
);
169 try_auto_date (GnmValue
*value
, const GOFormat
*format
,
170 GODateConventions
const *date_conv
)
175 gboolean needs_date
, needs_time
, needs_frac_sec
;
181 format
= gnm_format_specialize (format
, value
);
182 is_date
= go_format_is_date (format
) > 0;
183 is_time
= go_format_is_time (format
);
185 if (!is_date
&& is_time
<= 0)
188 /* We don't want to coerce strings. */
189 if (!VALUE_IS_FLOAT (value
))
192 /* Verify that the date is valid. */
193 if (!datetime_value_to_g (&date
, value
, date_conv
))
196 v
= value_get_as_float (value
);
197 vr
= gnm_fake_round (v
);
198 vs
= (24 * 60 * 60) * gnm_abs (v
- vr
);
200 needs_date
= is_time
< 2 && (is_date
|| gnm_abs (v
) >= 1);
201 needs_time
= is_time
> 0 || gnm_abs (v
- vr
) > 1e-9;
202 needs_frac_sec
= needs_time
&& gnm_abs (vs
- gnm_fake_round (vs
)) >= 0.5e-3;
204 xlfmt
= g_string_new (NULL
);
205 if (needs_date
) g_string_append (xlfmt
, "yyyy/mm/dd");
208 g_string_append_c (xlfmt
, ' ');
210 g_string_append (xlfmt
, "[h]:mm:ss");
212 g_string_append (xlfmt
, "hh:mm:ss");
214 g_string_append (xlfmt
, ".000");
216 actual
= go_format_new_from_XL (xlfmt
->str
);
217 g_string_free (xlfmt
, TRUE
);
218 res
= format_value (actual
, value
, -1, date_conv
);
219 go_format_unref (actual
);
226 * @stfe: an export options struct
227 * @cell: the cell to write to the file
229 * Return value: return TRUE on success, FALSE otherwise.
232 stf_export_cell (GnmStfExport
*stfe
, GnmCell
*cell
)
234 char const *text
= NULL
;
237 g_return_val_if_fail (stfe
!= NULL
, FALSE
);
240 switch (stfe
->format
) {
241 case GNM_STF_FORMAT_PRESERVE
:
242 text
= tmp
= gnm_cell_get_rendered_text (cell
);
245 case GNM_STF_FORMAT_AUTO
:
247 GODateConventions
const *date_conv
=
248 sheet_date_conv (cell
->base
.sheet
);
249 GOFormat
const *format
= gnm_cell_get_format (cell
);
250 text
= tmp
= try_auto_date (cell
->value
, format
, date_conv
);
252 text
= tmp
= try_auto_float (cell
->value
, format
, date_conv
);
254 text
= value_peek_string (cell
->value
);
257 case GNM_STF_FORMAT_RAW
:
259 text
= value_peek_string (cell
->value
);
264 ok
= gsf_output_csv_write_field (GSF_OUTPUT_CSV (stfe
),
274 * @stfe: an export options struct
275 * @sheet: the sheet to export
277 * Writes the @sheet to the callback function
279 * Return value: returns TRUE on success, FALSE otherwise
282 stf_export_sheet (GnmStfExport
*stfe
, Sheet
*sheet
)
288 g_return_val_if_fail (stfe
!= NULL
, FALSE
);
289 g_return_val_if_fail (IS_SHEET (sheet
), FALSE
);
291 range
= g_object_get_data (G_OBJECT (sheet
->workbook
), "ssconvert-range");
293 Sheet
*start_sheet
, *end_sheet
;
296 gnm_rangeref_normalize (range
,
297 eval_pos_init_sheet (&ep
, sheet
),
298 &start_sheet
, &end_sheet
,
301 if (start_sheet
!= sheet
)
304 r
= sheet_get_extent (sheet
, FALSE
, TRUE
);
306 for (row
= r
.start
.row
; row
<= r
.end
.row
; row
++) {
307 for (col
= r
.start
.col
; col
<= r
.end
.col
; col
++) {
308 GnmCell
*cell
= sheet_cell_get (sheet
, col
, row
);
309 if (!stf_export_cell (stfe
, cell
))
312 if (!gsf_output_csv_write_eol (GSF_OUTPUT_CSV (stfe
)))
321 * @export_options: an export options struct
323 * Exports the sheets given in @stfe
325 * Return value: TRUE on success, FALSE otherwise
328 gnm_stf_export (GnmStfExport
*stfe
)
332 gboolean result
= TRUE
;
333 char *old_locale
= NULL
;
335 g_return_val_if_fail (GNM_IS_STF_EXPORT (stfe
), FALSE
);
336 g_return_val_if_fail (stfe
->sheet_list
!= NULL
, FALSE
);
337 g_object_get (G_OBJECT (stfe
), "sink", &sink
, NULL
);
338 g_return_val_if_fail (sink
!= NULL
, FALSE
);
341 strcmp (stfe
->charset
, "UTF-8") != 0) {
343 GsfOutput
*converter
;
345 switch (stfe
->transliterate_mode
) {
347 case GNM_STF_TRANSLITERATE_MODE_ESCAPE
:
348 charset
= g_strdup (stfe
->charset
);
350 case GNM_STF_TRANSLITERATE_MODE_TRANS
:
351 charset
= g_strconcat (stfe
->charset
,
356 converter
= gsf_output_iconv_new (sink
, charset
, "UTF-8");
360 g_object_set (G_OBJECT (stfe
), "sink", converter
, NULL
);
361 g_object_unref (converter
);
363 g_warning ("Failed to create converter.");
369 old_locale
= g_strdup (go_setlocale (LC_ALL
, NULL
));
370 go_setlocale (LC_ALL
, stfe
->locale
);
373 for (ptr
= stfe
->sheet_list
; ptr
!= NULL
; ptr
= ptr
->next
) {
374 Sheet
*sheet
= ptr
->data
;
375 if (!stf_export_sheet (stfe
, sheet
)) {
381 if (old_locale
) /* go_setlocale clears the cache, */
382 /*so we don't want to call it with NULL*/
383 go_setlocale (LC_ALL
, old_locale
);
387 g_object_set (G_OBJECT (stfe
), "sink", sink
, NULL
);
388 g_object_unref (sink
);
393 /* ------------------------------------------------------------------------- */
396 * gnm_stf_export_can_transliterate:
398 * Return value: TRUE iff //TRANSLIT is supported
402 gnm_stf_export_can_transliterate (void)
404 char const *text
= "G\xc3\xbclzow";
406 GError
*error
= NULL
;
408 encoded_text
= g_convert (text
, -1,
409 "ASCII//TRANSLIT", "UTF-8",
411 g_free (encoded_text
);
416 g_error_free (error
);
420 /* ------------------------------------------------------------------------- */
423 gnm_stf_transliterate_mode_get_type (void)
425 static GType etype
= 0;
427 static GEnumValue
const values
[] = {
428 { GNM_STF_TRANSLITERATE_MODE_TRANS
, "GNM_STF_TRANSLITERATE_MODE_TRANS", "transliterate" },
429 { GNM_STF_TRANSLITERATE_MODE_ESCAPE
, "GNM_STF_TRANSLITERATE_MODE_ESCAPE", "escape" },
432 etype
= g_enum_register_static ("GnmStfTransliterateMode", values
);
437 /* ------------------------------------------------------------------------- */
440 gnm_stf_format_mode_get_type (void)
442 static GType etype
= 0;
444 static GEnumValue
const values
[] = {
445 { GNM_STF_FORMAT_AUTO
, "GNM_STF_FORMAT_AUTO", "automatic" },
446 { GNM_STF_FORMAT_RAW
, "GNM_STF_FORMAT_RAW", "raw" },
447 { GNM_STF_FORMAT_PRESERVE
, "GNM_STF_FORMAT_PRESERVE", "preserve" },
450 etype
= g_enum_register_static ("GnmStfFormatMode", values
);
455 /* ------------------------------------------------------------------------- */
458 gnm_stf_export_init (G_GNUC_UNUSED GObject
*obj
)
462 /* ------------------------------------------------------------------------- */
465 gnm_stf_export_finalize (GObject
*obj
)
467 GnmStfExport
*stfe
= (GnmStfExport
*)obj
;
469 gnm_stf_export_options_sheet_list_clear (stfe
);
470 g_free (stfe
->charset
);
471 g_free (stfe
->locale
);
473 G_OBJECT_CLASS (parent_class
)->finalize (obj
);
476 /* ------------------------------------------------------------------------- */
479 gnm_stf_export_get_property (GObject
*object
,
484 GnmStfExport
*stfe
= (GnmStfExport
*)object
;
486 switch (property_id
) {
488 g_value_set_string (value
, stfe
->charset
);
491 g_value_set_string (value
, stfe
->locale
);
493 case PROP_TRANSLITERATE_MODE
:
494 g_value_set_enum (value
, stfe
->transliterate_mode
);
497 g_value_set_enum (value
, stfe
->format
);
500 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
505 /* ------------------------------------------------------------------------- */
508 gnm_stf_export_set_property (GObject
*object
,
513 GnmStfExport
*stfe
= (GnmStfExport
*)object
;
516 switch (property_id
) {
518 scopy
= g_value_dup_string (value
);
519 g_free (stfe
->charset
);
520 stfe
->charset
= scopy
;
523 scopy
= g_value_dup_string (value
);
524 g_free (stfe
->locale
);
525 stfe
->locale
= scopy
;
527 case PROP_TRANSLITERATE_MODE
:
528 stfe
->transliterate_mode
= g_value_get_enum (value
);
531 stfe
->format
= g_value_get_enum (value
);
534 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
539 /* ------------------------------------------------------------------------- */
542 gnm_stf_export_class_init (GObjectClass
*gobject_class
)
544 parent_class
= g_type_class_peek_parent (gobject_class
);
546 gobject_class
->finalize
= gnm_stf_export_finalize
;
547 gobject_class
->get_property
= gnm_stf_export_get_property
;
548 gobject_class
->set_property
= gnm_stf_export_set_property
;
550 g_object_class_install_property
553 g_param_spec_string ("charset",
555 P_("The character encoding of the output."),
559 g_object_class_install_property
562 g_param_spec_string ("locale",
564 P_("The locale to use for number and date formatting."),
568 g_object_class_install_property
570 PROP_TRANSLITERATE_MODE
,
571 g_param_spec_enum ("transliterate-mode",
572 P_("Transliterate mode"),
573 P_("What to do with unrepresentable characters."),
574 GNM_STF_TRANSLITERATE_MODE_TYPE
,
575 GNM_STF_TRANSLITERATE_MODE_ESCAPE
,
578 g_object_class_install_property
581 g_param_spec_enum ("format",
583 P_("How should cells be formatted?"),
584 GNM_STF_FORMAT_MODE_TYPE
,
590 /* ------------------------------------------------------------------------- */
592 GSF_CLASS (GnmStfExport
, gnm_stf_export
,
593 gnm_stf_export_class_init
, gnm_stf_export_init
, GSF_OUTPUT_CSV_TYPE
)
595 /* ------------------------------------------------------------------------- */
596 /* ------------------------------------------------------------------------- */
600 #include "dialogs/dialog-stf-export.h"
601 #include "workbook-view.h"
605 * @obj: #GObject with a #GnmStfExport attached as data.
607 * If none is found, a new one is created and attached to @obj.
608 * Returns: (transfer none): the #GnmStfExport.
611 gnm_stf_get_stfe (GObject
*obj
)
613 GnmStfExport
*stfe
= g_object_get_data (obj
, "stfe");
615 const char * sep
= gnm_conf_get_stf_export_separator ();
616 const char * string_indicator
= gnm_conf_get_stf_export_stringindicator ();
617 const char * terminator
= gnm_conf_get_stf_export_terminator ();
618 const char * locale
= gnm_conf_get_stf_export_locale ();
619 const char * encoding
= gnm_conf_get_stf_export_encoding ();
620 int quotingmode
= gnm_conf_get_stf_export_quoting ();
621 int format
= gnm_conf_get_stf_export_format ();
622 int transliteratemode
= gnm_conf_get_stf_export_transliteration () ?
623 GNM_STF_TRANSLITERATE_MODE_TRANS
: GNM_STF_TRANSLITERATE_MODE_ESCAPE
;
624 GString
*triggers
= g_string_new (NULL
);
626 if (strlen (locale
) == 0)
628 if (strlen (encoding
) == 0)
631 /* Workaround GConf bug #641807. */
632 if (terminator
== NULL
|| strlen (terminator
) == 0)
635 if (quotingmode
== GSF_OUTPUT_CSV_QUOTING_MODE_AUTO
) {
636 g_string_append (triggers
, " \t");
637 g_string_append (triggers
, terminator
);
638 g_string_append (triggers
, string_indicator
);
639 g_string_append (triggers
, sep
);
642 stfe
= g_object_new (GNM_STF_EXPORT_TYPE
,
643 "quoting-triggers", triggers
->str
,
645 "quote", string_indicator
,
649 "quoting-mode", quotingmode
,
650 "transliterate-mode", transliteratemode
,
653 g_object_set_data_full (obj
, "stfe", stfe
, g_object_unref
);
654 g_string_free (triggers
, TRUE
);
660 gnm_stf_file_saver_save (G_GNUC_UNUSED GOFileSaver
const *fs
,
661 GOIOContext
*context
,
662 GoView
const *view
, GsfOutput
*output
)
664 WorkbookView
*wbv
= GNM_WORKBOOK_VIEW (view
);
665 Workbook
*wb
= wb_view_get_workbook (wbv
);
666 GnmStfExport
*stfe
= gnm_stf_get_stfe (G_OBJECT (wb
));
667 GsfOutput
*dummy_sink
;
670 /* TODO: move this GUI dependent code out of this
671 * filesaver into gui-file.c. After this, remove includes (see above). */
672 if (GNM_IS_WBC_GTK (context
->impl
)) {
674 stf_export_dialog (WBC_GTK (context
->impl
), stfe
, wb
);
676 go_io_error_unknown (context
);
681 nosheets
= (stfe
->sheet_list
== NULL
);
683 GPtrArray
*sel
= gnm_file_saver_get_sheets (fs
, wbv
, TRUE
);
685 for (ui
= 0; ui
< sel
->len
; ui
++)
686 gnm_stf_export_options_sheet_list_add
687 (stfe
, g_ptr_array_index (sel
, ui
));
688 g_ptr_array_unref (sel
);
691 g_object_set (G_OBJECT (stfe
), "sink", output
, NULL
);
692 if (gnm_stf_export (stfe
) == FALSE
)
693 go_cmd_context_error_import (GO_CMD_CONTEXT (context
),
694 _("Error while trying to export file as text"));
696 /* We're not allowed to set a NULL sink, so use a dummy. */
697 dummy_sink
= gsf_output_memory_new ();
698 g_object_set (G_OBJECT (stfe
), "sink", dummy_sink
, NULL
);
699 g_object_unref (dummy_sink
);
702 gnm_stf_export_options_sheet_list_clear (stfe
);
705 struct cb_set_export_option
{
711 cb_set_export_option (const char *key
, const char *value
,
712 GError
**err
, gpointer user_
)
714 struct cb_set_export_option
*user
= user_
;
715 Workbook
const *wb
= user
->wb
;
716 GnmStfExport
*stfe
= gnm_stf_get_stfe (G_OBJECT (wb
));
719 if (strcmp (key
, "eol") == 0) {
721 if (g_ascii_strcasecmp ("unix", value
) == 0)
723 else if (g_ascii_strcasecmp ("mac", value
) == 0)
725 else if (g_ascii_strcasecmp ("windows", value
) == 0)
728 errtxt
= _("eol must be one of unix, mac, and windows");
732 g_object_set (G_OBJECT (stfe
), "eol", eol
, NULL
);
736 if (strcmp (key
, "charset") == 0 ||
737 strcmp (key
, "locale") == 0 ||
738 strcmp (key
, "quote") == 0 ||
739 strcmp (key
, "separator") == 0 ||
740 strcmp (key
, "format") == 0 ||
741 strcmp (key
, "transliterate-mode") == 0 ||
742 strcmp (key
, "quoting-mode") == 0 ||
743 strcmp (key
, "quoting-on-whitespace") == 0)
744 return go_object_set_property
748 (_("Invalid value for option %s: \"%s\"")));
750 return gnm_file_saver_common_export_option (user
->fs
, wb
,
755 *err
= g_error_new (go_error_invalid (), 0, "%s", errtxt
);
761 gnm_stf_fs_set_export_options (GOFileSaver
*fs
,
765 G_GNUC_UNUSED gpointer user
)
767 GnmStfExport
*stfe
= gnm_stf_get_stfe (G_OBJECT (doc
));
768 struct cb_set_export_option data
;
770 data
.wb
= WORKBOOK (doc
);
771 gnm_stf_export_options_sheet_list_clear (stfe
);
772 return go_parse_key_value (options
, err
, cb_set_export_option
, &data
);
776 * gnm_stf_file_saver_create:
779 * Returns: (transfer full): the newly allocated #GOFileSaver.
782 gnm_stf_file_saver_create (gchar
const *id
)
784 GOFileSaver
*fs
= go_file_saver_new (id
,
786 _("Text (configurable)"),
787 GO_FILE_FL_WRITE_ONLY
,
788 gnm_stf_file_saver_save
);
789 go_file_saver_set_save_scope (fs
, GO_FILE_SAVE_WORKBOOK
);
790 g_object_set (G_OBJECT (fs
), "sheet-selection", TRUE
, NULL
);
791 g_signal_connect (G_OBJECT (fs
), "set-export-options",
792 G_CALLBACK (gnm_stf_fs_set_export_options
),
794 return GO_FILE_SAVER (fs
);