Files
gnucash/gnucash/gnome-utils/assistant-xml-encoding.c
John Ralls 6a1cb5eecd Replace the three bool parameters to qof_session_begin to an enum.
For clarity. In so doing found the backend behavior a bit inconsistent
so it's modified to do what the enum values indicate.

In the course of changing the various calls I found some implementation
errors in the back end and corrected them.
2020-06-04 14:07:28 -07:00

1430 lines
47 KiB
C

/**********************************************************************
* assistant-xml-encoding.c -- Conversion of old XML file
* Copyright (C) 2006 Andreas Koehler <andi5.py@gmx.net>
* Copyright (C) 2011 Robert Fewell
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, contact:
*
* Free Software Foundation Voice: +1-617-542-5942
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652
* Boston, MA 02110-1301, USA gnu@gnu.org
*
**********************************************************************/
#include <config.h>
#include <glib/gi18n.h>
#include <gmodule.h>
#include "TransLog.h"
#include "assistant-xml-encoding.h"
#include "dialog-utils.h"
#include "gnc-backend-xml.h"
#include "gnc-component-manager.h"
#include "gnc-uri-utils.h"
#include "gnc-ui.h"
/* The following are copied from src/backend/xml/io-gncxml2-v2.h as a temporary
* measure to enable this to compile in the face of making changing struct
* FileBackend into C++ class XmlBackend, which can't be exposed to this C
* file. A future commit will separate the session code from the UI code in this
* file.
*/
typedef struct
{
GQuark encoding;
gchar* utf8_string;
} conv_type;
extern gint gnc_xml2_find_ambiguous (const gchar* filename,
GList* encodings,
GHashTable** unique,
GHashTable** ambiguous,
GList** impossible);
extern gboolean gnc_xml2_parse_with_subst (QofBackend* xml_be, QofBook* book,
GHashTable* subst);
/* NOTE: This file uses the term "encoding" even in places where it is not
* accurate. Please ignore that. Encodings occur in different forms:
* - as descriptive string, as in the list of system encodings
* - as string used for g_iconv_open
* - as GQuark, representing above string
* - as pointer, containing above gquark, used in lists
*/
typedef struct
{
GtkWidget *assistant; /* assistant */
gboolean canceled; /* we are canceled */
GtkWidget *default_encoding_combo; /* top combo on conversion page */
GtkWidget *default_encoding_hbox; /* Encoding Hbox */
GtkWidget *summary_label; /* label on conversion page */
GtkWidget *impossible_label; /* impossible label on conversion page */
GtkWidget *string_box; /* vbox of combos on conversion page */
GtkWidget *string_box_container; /* container on conversion page */
GtkWidget *encodings_dialog; /* dialog for selection of encodings */
GtkWidget *custom_enc_entry; /* custom entry */
GtkTreeView *available_encs_view; /* list view of standard encodings */
GtkTreeView *selected_encs_view; /* list view of selected encodings */
GList *encodings; /* list of GQuarks for encodings */
GQuark default_encoding; /* default GQuark, may be zero */
/* hash table that maps byte sequences to conversions, i.e. in the current
encodings setting, there is only one possible conversion */
GHashTable *unique;
/* hash table that maps byte sequences to a list of conversions, i.e. in the
current encodings setting, there exactly these conversions are possible */
GHashTable *ambiguous_ht;
/* sorted list of ambiguous words, used for the construction of the combos */
GList *ambiguous_list;
/* hash table that maps byte sequences to conversions. these reflect the
choices the user made, accumulated and updated in the whole conversion.
Note: this may contain conversions that are not available in the current
encodings setting, just imagine, user accidentally removed an important
encoding from the list */
GHashTable *choices;
/* number of byte sequences that have multiple possible conversions, but not in
the default encoding. and the user has not decided yet, of course. */
gint n_unassigned;
/* number of byte sequences without any reasonable interpretation */
gint n_impossible;
/* hash table that maps byte sequences to other byte sequences to be replaced
by them. */
GHashTable *subst;
gchar *filename;
QofSession *session;
} GncXmlImportData;
/* used for the string combos, see ambiguous_free */
typedef struct
{
gchar *byte_sequence;
GList *conv_list;
} ambiguous_type;
enum
{
FILE_COL_NAME = 0,
FILE_COL_INFO,
FILE_NUM_COLS
};
enum
{
WORD_COL_STRING = 0,
WORD_COL_ENCODING,
WORD_NUM_COLS
};
enum
{
ENC_COL_STRING = 0,
ENC_COL_QUARK,
ENC_NUM_COLS
};
void gxi_prepare_cb (GtkAssistant *assistant, GtkWidget *page, GncXmlImportData *data);
void gxi_cancel_cb (GtkAssistant *gtkassistant, GncXmlImportData *data);
void gxi_finish_cb (GtkAssistant *gtkassistant, GncXmlImportData *data);
void gxi_conversion_prepare (GtkAssistant *assistant, gpointer data );
void gxi_conversion_next (GtkAssistant *assistant, gpointer data);
static void gxi_data_destroy (GncXmlImportData *data);
static void gxi_ambiguous_info_destroy (GncXmlImportData *data);
static void gxi_session_destroy (GncXmlImportData *data);
static void gxi_check_file (GncXmlImportData *data);
static void gxi_sort_ambiguous_list (GncXmlImportData *data);
static gboolean gxi_parse_file (GncXmlImportData *data);
static gboolean gxi_save_file (GncXmlImportData *data);
static void gxi_update_progress_bar (const gchar *message, double percentage);
static void gxi_update_default_enc_combo (GncXmlImportData *data);
static void gxi_update_summary_label (GncXmlImportData *data);
static void gxi_update_string_box (GncXmlImportData *data);
static void gxi_update_conversion_forward (GncXmlImportData *data);
static void gxi_default_enc_combo_changed_cb (GtkComboBox *combo, GncXmlImportData *data);
static void gxi_string_combo_changed_cb (GtkComboBox *combo, GncXmlImportData *data);
void gxi_edit_encodings_clicked_cb (GtkButton *button, GncXmlImportData *data);
void gxi_available_enc_activated_cb (GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *column, GncXmlImportData *data);
void gxi_add_enc_clicked_cb (GtkButton *button, GncXmlImportData *data);
void gxi_custom_enc_activate_cb (GtkEntry *entry, GncXmlImportData *data);
void gxi_add_custom_enc_clicked_cb (GtkButton *button, GncXmlImportData *data);
void gxi_selected_enc_activated_cb (GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *column, GncXmlImportData *data);
void gxi_remove_enc_clicked_cb (GtkButton *button, GncXmlImportData *data);
/* Translators: Run the assistant in your language to see GTK's translation of the button labels. */
static const gchar *encodings_doc_string = N_(
"\nThe file you are trying to load is from an older version of "
"GnuCash. The file format in the older versions was missing the "
"detailed specification of the character encoding being used. This "
"means the text in your data file could be read in multiple ambiguous "
"ways. This ambiguity cannot be resolved automatically, but the new "
"GnuCash 2.0.0 file format will include all necessary specifications so "
"that you do not have to go through this step again."
"\n\n"
"GnuCash will try to guess the correct character encoding for your data "
"file. On the next page GnuCash will show the resulting texts when "
"using this guess. You have to check whether the words look as "
"expected. Either everything looks fine and you can simply press "
"\"Next\". Or the words contain unexpected characters, in which "
"case you should select different character encodings to see "
"different results. You may have to edit the list of character "
"encodings by clicking on the respective button."
"\n\n"
"Press \"Next\" now to select the correct character encoding for "
"your data file.\n");
static const gchar *encodings_doc_page_title = N_("Ambiguous character encoding");
static const gchar *finish_convert_string = N_(
"The file has been loaded successfully. If you click \"Apply\" it will be saved "
"and reloaded into the main application. That way you will have a working "
"file as backup in the same directory.\n\n"
"You can also go back and verify your selections by clicking on \"Back\".");
/* The debugging module that this .o belongs to. */
static QofLogModule log_module = GNC_MOD_ASSISTANT;
/* window containing a progress bar */
static GtkWidget *progress_window = NULL;
static GtkProgressBar *progress_bar = NULL;
/* this is used for a static tree of system encodings. encoding may be NULL.
parent declares how often to go up in the path of the previous element and use
that as parent, e.g. 0 -> child of previous, 1 -> same level as previous */
typedef struct
{
gchar *text;
gchar *encoding;
gint parent;
} system_encoding_type;
static system_encoding_type system_encodings [] =
{
{ N_("Unicode"), NULL, 2 },
{ "UTF-8", "UTF-8", 0 },
{ N_("European"), NULL, 2 },
{ N_("ISO-8859-1 (West European)"), "ISO-8859-1", 0 },
{ N_("ISO-8859-2 (East European)"), "ISO-8859-2", 1 },
{ N_("ISO-8859-3 (South European)"), "ISO-8859-3", 1 },
{ N_("ISO-8859-4 (North European)"), "ISO-8859-4", 1 },
{ N_("ISO-8859-5 (Cyrillic)"), "ISO-8859-5", 1 },
{ N_("ISO-8859-6 (Arabic)"), "ISO-8859-6", 1 },
{ N_("ISO-8859-7 (Greek)"), "ISO-8859-7", 1 },
{ N_("ISO-8859-8 (Hebrew)"), "ISO-8859-8", 1 },
{ N_("ISO-8859-9 (Turkish)"), "ISO-8859-9", 1 },
{ N_("ISO-8859-10 (Nordic)"), "ISO-8859-10", 1 },
{ N_("ISO-8859-11 (Thai)"), "ISO-8859-11", 1 },
{ N_("ISO-8859-13 (Baltic)"), "ISO-8859-13", 1 },
{ N_("ISO-8859-14 (Celtic)"), "ISO-8859-14", 1 },
{ N_("ISO-8859-15 (West European, Euro sign)"), "ISO-8859-15", 1 },
{ N_("ISO-8859-16 (South-East European)"), "ISO-8859-16", 1 },
{ N_("Cyrillic"), NULL, 2 },
{ N_("KOI8-R (Russian)"), "KOI8-R", 0 },
{ N_("KOI8-U (Ukrainian)"), "KOI8-U", 1 },
};
static guint n_system_encodings = G_N_ELEMENTS (system_encodings);
void gxi_prepare_cb (GtkAssistant *assistant, GtkWidget *page,
GncXmlImportData *data)
{
switch (gtk_assistant_get_current_page(assistant))
{
case 1:
/* Current page is the Conversion page */
gxi_conversion_prepare (assistant, data);
break;
case 2:
/* Current page is final page */
gxi_conversion_next (assistant, data);
break;
}
}
void
gxi_finish_cb (GtkAssistant *assistant, GncXmlImportData *data)
{
gtk_main_quit();
}
static void
gxi_update_conversion_forward (GncXmlImportData *data)
{
GtkAssistant *assistant = GTK_ASSISTANT(data->assistant);
gint num = gtk_assistant_get_current_page (assistant);
GtkWidget *page = gtk_assistant_get_nth_page (assistant, num);
if (data->n_unassigned || data->n_impossible)
gtk_assistant_set_page_complete (assistant, page, FALSE);
else
gtk_assistant_set_page_complete (assistant, page, TRUE);
}
void
gxi_cancel_cb (GtkAssistant *gtkassistant, GncXmlImportData *data)
{
gnc_suspend_gui_refresh ();
data->canceled = TRUE;
gnc_resume_gui_refresh ();
gtk_main_quit();
}
/***************************************************/
gboolean
gnc_xml_convert_single_file (const gchar *filename)
{
GncXmlImportData *data;
GtkWidget *widget;
GtkBuilder *builder;
gboolean success;
data = g_new0 (GncXmlImportData, 1);
data->filename = gnc_uri_get_path (filename);
data->canceled = FALSE;
/* gather ambiguous info */
gxi_check_file (data);
if (data->n_impossible == -1)
return FALSE;
if (!g_hash_table_size (data->ambiguous_ht))
{
/* no ambiguous strings */
success = gxi_parse_file (data) &&
gxi_save_file (data);
gxi_data_destroy (data);
}
else
{
/* common assistant initialization */
builder = gtk_builder_new();
gnc_builder_add_from_file (builder , "assistant-xml-encoding.glade", "assistant_xml_encoding");
data->assistant = GTK_WIDGET(gtk_builder_get_object (builder, "assistant_xml_encoding"));
/* Enable buttons on all pages. */
gtk_assistant_set_page_complete (GTK_ASSISTANT (data->assistant),
GTK_WIDGET(gtk_builder_get_object(builder, "start_page")),
TRUE);
gtk_assistant_set_page_complete (GTK_ASSISTANT (data->assistant),
GTK_WIDGET(gtk_builder_get_object(builder, "conversion_page")),
TRUE);
gtk_assistant_set_page_complete (GTK_ASSISTANT (data->assistant),
GTK_WIDGET(gtk_builder_get_object(builder, "end_page")),
TRUE);
/* start page, explanations */
gtk_assistant_set_page_title (GTK_ASSISTANT(data->assistant),
gtk_assistant_get_nth_page (GTK_ASSISTANT(data->assistant), 0),
gettext(encodings_doc_page_title));
widget = GTK_WIDGET(gtk_builder_get_object (builder, "start_page"));
gtk_label_set_text (GTK_LABEL(widget), gettext (encodings_doc_string));
/* conversion page */
data->default_encoding_hbox = GTK_WIDGET(gtk_builder_get_object (builder, "default_enc_box"));
data->string_box_container = GTK_WIDGET(gtk_builder_get_object (builder, "string_box_container"));
data->impossible_label = GTK_WIDGET(gtk_builder_get_object (builder, "impossible_label"));
/* finish page */
widget = GTK_WIDGET(gtk_builder_get_object(builder, "end_page"));
gtk_label_set_text (GTK_LABEL(widget), gettext (finish_convert_string));
gtk_builder_connect_signals(builder, data);
gtk_widget_show_all (data->assistant);
gxi_update_default_enc_combo (data);
gxi_update_string_box (data);
g_object_unref(G_OBJECT(builder));
/* This won't return until the assistant is finished */
gtk_main();
if (data->canceled)
success = FALSE;
else
success = gxi_save_file (data);
}
/* destroy all the data variables */
gxi_data_destroy (data);
g_free (data);
return success;
}
static void
gxi_data_destroy (GncXmlImportData *data)
{
if (!data)
return;
if (data->filename)
{
g_free (data->filename);
data->filename = NULL;
}
gxi_session_destroy (data);
gxi_ambiguous_info_destroy (data);
if (data->choices)
{
g_hash_table_destroy (data->choices);
data->choices = NULL;
}
if (data->string_box)
{
gtk_widget_destroy (data->string_box);
data->string_box = NULL;
}
if (data->assistant)
{
gtk_widget_destroy (data->assistant);
data->assistant = NULL;
}
}
static void
conv_free (conv_type *conv)
{
if (conv)
{
g_free(conv->utf8_string);
g_free(conv);
}
}
static conv_type *
conv_copy (const conv_type *conv)
{
conv_type *new_type = NULL;
if (conv)
{
new_type = g_new(conv_type, 1);
new_type->encoding = conv->encoding;
new_type->utf8_string = g_strdup (conv->utf8_string);
}
return new_type;
}
static gint
conv_enc_cmp (const conv_type *conv, const GQuark *enc)
{
return conv->encoding - *enc;
}
static const gchar *
get_decoded_string (const ambiguous_type *amb, const GQuark enc)
{
GList *found = g_list_find_custom (amb->conv_list, &enc,
(GCompareFunc) conv_enc_cmp);
if (found)
{
return ((conv_type*) found->data)->utf8_string;
}
else
{
return NULL;
}
}
static gint
ambiguous_cmp (const ambiguous_type *a, const ambiguous_type *b,
GncXmlImportData *data)
{
const gchar *string_a = get_decoded_string (a, data->default_encoding);
const gchar *string_b = get_decoded_string (b, data->default_encoding);
if (string_a)
{
if (string_b)
{
/* both look good, usual compare */
return strcmp (string_a, string_b);
}
else
{
/* a look good, b not. put b to the top */
return 1;
}
}
else
{
if (string_b)
{
/* b looks good, a not. put a to the top */
return -1;
}
else
{
/* both look suboptimal, see whether one has a decision attached to it */
conv_type *conv_a = g_hash_table_lookup (data->choices, a->byte_sequence);
conv_type *conv_b = g_hash_table_lookup (data->choices, b->byte_sequence);
if (conv_a && !conv_b) return 1;
if (conv_b && !conv_a) return -1;
return strcmp (a->byte_sequence, b->byte_sequence);
}
}
}
static void
ambiguous_list_insert (gchar *byte_sequence, GList *conv_list,
GncXmlImportData *data)
{
GList *iter;
ambiguous_type *amb = g_new (ambiguous_type, 1);
amb->byte_sequence = g_strdup (byte_sequence);
amb->conv_list = NULL;
for (iter = g_list_last (conv_list); iter; iter = iter->prev)
amb->conv_list = g_list_prepend (amb->conv_list, conv_copy (iter->data));
data->ambiguous_list = g_list_prepend (data->ambiguous_list, amb);
}
static void
ambiguous_free (ambiguous_type *amb)
{
if (amb)
{
g_free (amb->byte_sequence);
g_list_foreach (amb->conv_list, (GFunc) conv_free, NULL);
g_list_free (amb->conv_list);
g_free (amb);
}
}
static void
gxi_ambiguous_info_destroy (GncXmlImportData *data)
{
if (data->unique)
{
g_hash_table_destroy (data->unique);
data->unique = NULL;
}
if (data->ambiguous_ht)
{
g_hash_table_destroy (data->ambiguous_ht);
data->ambiguous_ht = NULL;
}
if (data->ambiguous_list)
{
g_list_foreach (data->ambiguous_list, (GFunc) ambiguous_free, NULL);
g_list_free (data->ambiguous_list);
data->ambiguous_list = NULL;
}
}
static void
gxi_session_destroy (GncXmlImportData *data)
{
if (data->session)
{
xaccLogDisable ();
qof_session_destroy (data->session);
xaccLogEnable ();
data->session = NULL;
}
}
static void
gxi_sort_ambiguous_list (GncXmlImportData *data)
{
data->ambiguous_list = g_list_sort_with_data (
data->ambiguous_list, (GCompareDataFunc) ambiguous_cmp, data);
}
static void
subst_insert_amb (gchar *byte_sequence, GList *conv_list, GncXmlImportData *data)
{
conv_type *choice;
GList *default_conv;
gchar *default_utf8;
if (!data->subst)
return;
choice = g_hash_table_lookup (data->choices, byte_sequence);
if (choice)
{
/* user choice */
g_hash_table_insert (data->subst, g_strdup (byte_sequence),
g_strdup (choice->utf8_string));
}
else
{
default_conv = g_list_find_custom (conv_list, &data->default_encoding,
(GCompareFunc) conv_enc_cmp);
if (default_conv)
{
/* default conversion */
default_utf8 = ((conv_type*) default_conv->data)->utf8_string;
g_hash_table_insert (data->subst, g_strdup (byte_sequence),
g_strdup (default_utf8));
}
else
{
/* no conversion available, stop filling of subst */
g_hash_table_destroy (data->subst);
data->subst = NULL;
}
}
}
static void
subst_insert_unique (gchar *byte_sequence, conv_type *conv,
GncXmlImportData *data)
{
if (!data->subst)
return;
g_hash_table_insert (data->subst, g_strdup (byte_sequence),
g_strdup (conv->utf8_string));
}
static void
gxi_update_progress_bar (const gchar *message, double percentage)
{
if (!progress_window)
{
progress_window = gtk_window_new (GTK_WINDOW_POPUP);
progress_bar = GTK_PROGRESS_BAR (gtk_progress_bar_new ());
gtk_container_set_border_width (GTK_CONTAINER (progress_window), 12);
gtk_container_add (GTK_CONTAINER (progress_window),
GTK_WIDGET (progress_bar));
gtk_widget_show (GTK_WIDGET (progress_bar));
}
if (percentage < 0)
{
gtk_progress_bar_set_text (progress_bar, NULL);
gtk_progress_bar_set_fraction (progress_bar, 0.0);
gtk_widget_hide (progress_window);
}
else
{
gtk_progress_bar_set_text (progress_bar, message);
if (percentage <= 100)
gtk_progress_bar_set_fraction (progress_bar, percentage / 100);
else
gtk_progress_bar_pulse (progress_bar);
gtk_widget_show (progress_window);
}
}
static void
gxi_update_default_enc_combo (GncXmlImportData *data)
{
GtkComboBoxText *combo;
GList *enc_iter;
/* add encodings list */
if (data->default_encoding_combo)
gtk_widget_destroy (data->default_encoding_combo);
data->default_encoding_combo = gtk_combo_box_text_new();
combo = GTK_COMBO_BOX_TEXT (data->default_encoding_combo);
for (enc_iter = data->encodings; enc_iter; enc_iter = enc_iter->next)
{
gtk_combo_box_text_append_text (
combo, g_quark_to_string (GPOINTER_TO_UINT (enc_iter->data)));
}
gtk_combo_box_set_active (GTK_COMBO_BOX(combo),
g_list_index (data->encodings, GUINT_TO_POINTER (data->default_encoding)));
/* show encodings */
g_signal_connect (G_OBJECT (combo), "changed",
G_CALLBACK (gxi_default_enc_combo_changed_cb), data);
gtk_container_add (GTK_CONTAINER (data->default_encoding_hbox), GTK_WIDGET (combo));
gtk_widget_show (GTK_WIDGET (combo));
}
static void
gxi_update_summary_label (GncXmlImportData *data)
{
gchar *string = NULL;
gboolean show = FALSE;
if (data->n_unassigned)
{
if (data->n_impossible)
{
string = g_strdup_printf (
_("There are %d unassigned and %d undecodable words. "
"Please add encodings."),
data->n_unassigned, data->n_impossible);
show = TRUE;
}
else
{
string = g_strdup_printf (
_("There are %d unassigned words. "
"Please decide on them or add encodings."),
data->n_unassigned);
show = TRUE;
}
}
else
{
if (data->n_impossible)
{
string = g_strdup_printf (
_("There are %d undecodable words. "
"Please add encodings."),
data->n_impossible);
show = TRUE;
}
else
{
show = FALSE;
}
}
if (show)
{
gtk_label_set_text (GTK_LABEL (data->summary_label), string);
g_free (string);
gtk_widget_show (data->summary_label);
}
else
{
gtk_widget_hide (data->summary_label);
}
}
static void
gxi_update_string_box (GncXmlImportData *data)
{
gchar *string;
const gchar *utf8;
GtkBox *vbox;
GtkComboBox *combo;
GtkListStore *store;
GList *word_iter, *conv_iter;
GtkCellRenderer *renderer;
GtkTreeIter iter;
GQuark chosen_encoding;
GtkTreeIter *chosen_iter, *default_iter;
ambiguous_type *amb;
conv_type *conv;
if (data->string_box)
gtk_widget_destroy (data->string_box);
data->string_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
gtk_box_set_homogeneous (GTK_BOX (data->string_box), FALSE);
vbox = GTK_BOX (data->string_box);
data->n_unassigned = 0;
/* loop through words */
for (word_iter = data->ambiguous_list; word_iter; word_iter = word_iter->next)
{
store = gtk_list_store_new (WORD_NUM_COLS, G_TYPE_STRING, G_TYPE_POINTER);
combo = GTK_COMBO_BOX (gtk_combo_box_new_with_model (
GTK_TREE_MODEL (store)));
g_object_unref (store);
renderer = gtk_cell_renderer_text_new ();
gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, TRUE);
gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), renderer,
"text", WORD_COL_STRING, NULL);
/* add default string, if possible */
amb = (ambiguous_type*) word_iter->data;
utf8 = get_decoded_string (amb, data->default_encoding);
default_iter = NULL;
if (utf8)
{
string = g_strdup_printf ("%s (default)", utf8);
gtk_list_store_append (store, &iter);
gtk_list_store_set (store, &iter, WORD_COL_STRING, string,
WORD_COL_ENCODING,
GUINT_TO_POINTER (data->default_encoding), -1);
g_free (string);
default_iter = gtk_tree_iter_copy (&iter);
}
/* user has selected this previously */
conv = (conv_type*) g_hash_table_lookup (data->choices, amb->byte_sequence);
chosen_encoding = (conv) ? conv->encoding : 0;
chosen_iter = NULL;
/* loop through conversions */
for (conv_iter = amb->conv_list; conv_iter; conv_iter = conv_iter->next)
{
conv = (conv_type*) conv_iter->data;
string = g_strdup_printf ("%s (%s)", conv->utf8_string,
g_quark_to_string (conv->encoding));
gtk_list_store_append (store, &iter);
gtk_list_store_set (store, &iter, WORD_COL_STRING, string,
WORD_COL_ENCODING,
GUINT_TO_POINTER (conv->encoding), -1);
g_free (string);
if (chosen_encoding && conv->encoding == chosen_encoding)
{
chosen_iter = gtk_tree_iter_copy (&iter);
}
} /* next conversion */
if (chosen_iter)
{
/* select previous selection again, are not we cute */
gtk_combo_box_set_active_iter (combo, chosen_iter);
gtk_tree_iter_free (chosen_iter);
}
else
{
if (default_iter)
{
/* select default entry */
gtk_combo_box_set_active_iter (combo, default_iter);
}
else
{
/* count it */
data->n_unassigned++;
}
}
/* wire up combo */
g_object_set_data (G_OBJECT (combo), "ambiguous", amb);
g_signal_connect (G_OBJECT (combo), "changed",
G_CALLBACK (gxi_string_combo_changed_cb), data);
gtk_box_pack_start (vbox, GTK_WIDGET (combo), FALSE, FALSE, 0);
gtk_widget_show (GTK_WIDGET (combo));
} /* next word */
/* wire up whole string vbox */
gtk_container_add (GTK_CONTAINER (data->string_box_container), GTK_WIDGET (vbox));
gtk_widget_show (GTK_WIDGET (vbox));
/* update label now, n_unassigned is calculated */
if (!data->summary_label)
data->summary_label = data->impossible_label;
gxi_update_summary_label (data);
}
void
gxi_conversion_prepare (GtkAssistant *assistant, gpointer user_data )
{
GncXmlImportData *data = user_data;
gxi_update_string_box (data);
gxi_update_conversion_forward (data);
}
static void
gxi_default_enc_combo_changed_cb (GtkComboBox *combo, GncXmlImportData *data)
{
GtkTreeIter iter;
gchar *enc_string;
GQuark curr_enc;
if (!gtk_combo_box_get_active_iter (combo, &iter))
return;
gtk_tree_model_get (gtk_combo_box_get_model (combo), &iter,
0, &enc_string, -1);
curr_enc = g_quark_from_string (enc_string);
g_free (enc_string);
if (data->default_encoding == curr_enc)
return;
if (!g_list_find (data->encodings, GUINT_TO_POINTER (curr_enc)))
{
/* should not happen */
PERR("invalid encoding selection");
return;
}
data->default_encoding = curr_enc;
gxi_sort_ambiguous_list (data);
gxi_update_string_box (data);
gxi_update_conversion_forward (data);
}
static void
gxi_string_combo_changed_cb (GtkComboBox *combo, GncXmlImportData *data)
{
GtkTreeIter iter;
GList *found, *default_conv;
gboolean is_active;
ambiguous_type *amb;
conv_type *prev_conv, *curr_conv = NULL;
gpointer ptr;
GQuark prev_enc, curr_enc;
amb = (ambiguous_type*) g_object_get_data (G_OBJECT (combo), "ambiguous");
prev_conv = (conv_type*) g_hash_table_lookup (data->choices,
amb->byte_sequence);
if (prev_conv)
prev_enc = prev_conv->encoding;
default_conv = g_list_find_custom (amb->conv_list, &data->default_encoding,
(GCompareFunc) conv_enc_cmp);
is_active = gtk_combo_box_get_active_iter (combo, &iter);
if (is_active)
{
gtk_tree_model_get (gtk_combo_box_get_model (combo), &iter,
WORD_COL_ENCODING, &ptr, -1);
curr_enc = GPOINTER_TO_UINT (ptr);
found = g_list_find_custom (amb->conv_list, &curr_enc,
(GCompareFunc) conv_enc_cmp);
if (found)
{
curr_conv = (conv_type*) found->data;
}
else
{
/* should not happen */
PERR("invalid string selection");
is_active = FALSE;
}
}
if (is_active)
{
if (prev_conv)
{
if (curr_enc == prev_enc)
return;
/* remember new choice */
g_hash_table_replace (data->choices, g_strdup (amb->byte_sequence),
conv_copy (curr_conv));
found = g_list_find_custom (amb->conv_list, &prev_enc,
(GCompareFunc) conv_enc_cmp);
if (!found && !default_conv)
{
/* user selected encoding for a byte sequence undecodable in the default
encoding, for the first time. previous selection is invalid now */
data->n_unassigned--;
gxi_update_summary_label (data);
gxi_update_conversion_forward (data);
}
}
else
{
/* first choice ever */
g_hash_table_insert (data->choices, g_strdup (amb->byte_sequence),
conv_copy (curr_conv));
if (!default_conv)
{
/* user selected encoding for a byte sequence undecodable in the default
encoding, for the first time. no previous selection */
data->n_unassigned--;
gxi_update_summary_label (data);
gxi_update_conversion_forward (data);
}
}
}
else
{
if (prev_conv)
{
/* user decided not to decide... however he did that */
g_hash_table_remove (data->choices, amb->byte_sequence);
if (!default_conv)
{
/* user deselected encoding for a byte sequence undecodable in the
default encoding */
data->n_unassigned++;
gxi_update_summary_label (data);
gxi_update_conversion_forward (data);
}
}
/* the missing else clause means pure ignorance of this dialog ;-) */
}
}
void
gxi_conversion_next (GtkAssistant *assistant, gpointer user_data)
{
GncXmlImportData *data = user_data;
gxi_parse_file (data);
}
static void
gxi_check_file (GncXmlImportData *data)
{
if (!data->encodings)
{
gboolean is_utf8;
const gchar *locale_enc;
gchar *enc_string, **enc_array, **enc_cursor;
gpointer enc_ptr;
GIConv iconv;
/* first locale encoding */
is_utf8 = g_get_charset (&locale_enc);
enc_string = g_ascii_strup (locale_enc, -1);
enc_ptr = GUINT_TO_POINTER (g_quark_from_string (enc_string));
g_free (enc_string);
data->encodings = g_list_append (NULL, enc_ptr);
/* add utf-8 */
if (!is_utf8)
{
enc_ptr = GUINT_TO_POINTER (g_quark_from_string ("UTF-8"));
data->encodings = g_list_append (data->encodings, enc_ptr);
}
/* Translators: Please insert encodings here that are typically used in your
* locale, separated by spaces. No need for ASCII or UTF-8, check `locale -m`
* for assistance with spelling. */
enc_array = g_strsplit (_("ISO-8859-1 KOI8-U"), " ", 0);
/* loop through typical encodings */
for (enc_cursor = enc_array; *enc_cursor; enc_cursor++)
{
if (!**enc_cursor) continue;
enc_string = g_ascii_strup (*enc_cursor, -1);
enc_ptr = GUINT_TO_POINTER (g_quark_from_string (enc_string));
if (!g_list_find (data->encodings, enc_ptr))
{
/* test whether we like this encoding */
iconv = g_iconv_open ("UTF-8", enc_string);
if (iconv != (GIConv) - 1)
/* we like it */
data->encodings = g_list_append (data->encodings, enc_ptr);
g_iconv_close (iconv);
}
g_free (enc_string);
}
g_strfreev (enc_array);
}
if (!data->default_encoding)
{
/* choose top one */
data->default_encoding = GPOINTER_TO_UINT (data->encodings->data);
}
if (!data->choices)
{
data->choices = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, (GDestroyNotify) conv_free);
}
gxi_ambiguous_info_destroy (data);
/* analyze file */
data->n_impossible = gnc_xml2_find_ambiguous (
data->filename, data->encodings, &data->unique, &data->ambiguous_ht, NULL);
if (data->n_impossible != -1)
{
/* sort ambiguous words */
g_hash_table_foreach (data->ambiguous_ht, (GHFunc)ambiguous_list_insert,
data);
gxi_sort_ambiguous_list (data);
}
}
static gboolean
gxi_parse_file (GncXmlImportData *data)
{
QofSession *session = NULL;
QofBook *book;
QofBackend *backend;
QofBackendError io_err = ERR_BACKEND_NO_ERR;
gchar *message = NULL;
gboolean success = FALSE;
if (data->n_unassigned || data->n_impossible)
goto cleanup_parse_file;
/* fill subst hash table with byte sequence substitutions */
data->subst = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
g_hash_table_foreach (data->ambiguous_ht, (GHFunc) subst_insert_amb, data);
g_hash_table_foreach (data->unique, (GHFunc) subst_insert_unique, data);
if (!data->subst)
goto cleanup_parse_file;
/* create a temporary QofSession */
gxi_session_destroy (data);
session = qof_session_new (NULL);
data->session = session;
qof_session_begin (session, data->filename, SESSION_READ_ONLY);
io_err = qof_session_get_error (session);
if (io_err != ERR_BACKEND_NO_ERR)
{
message = _("The file could not be reopened.");
goto cleanup_parse_file;
}
xaccLogDisable ();
gxi_update_progress_bar (_("Reading file..."), 0.0);
qof_session_load (session, gxi_update_progress_bar);
gxi_update_progress_bar (NULL, -1.0);
xaccLogEnable ();
io_err = qof_session_get_error (session);
if (io_err == ERR_BACKEND_NO_ERR)
{
/* loaded successfully now. strange, but ok */
success = TRUE;
goto cleanup_parse_file;
}
else if (io_err != ERR_FILEIO_NO_ENCODING)
{
/* another error, cannot handle this here */
message = _("The file could not be reopened.");
goto cleanup_parse_file;
}
qof_session_pop_error (session);
book = qof_session_get_book (session);
backend = qof_book_get_backend (book);
gxi_update_progress_bar (_("Parsing file..."), 0.0);
success = gnc_xml2_parse_with_subst (backend, book, data->subst);
gxi_update_progress_bar (NULL, -1.0);
if (success)
data->session = session;
else
message = _("There was an error parsing the file.");
cleanup_parse_file:
if (data->subst)
{
g_hash_table_destroy (data->subst);
data->subst = NULL;
}
if (message)
{
gnc_error_dialog (GTK_WINDOW (data->assistant), "%s", message);
}
if (!success)
gxi_session_destroy (data);
return success;
}
static gboolean
gxi_save_file (GncXmlImportData *data)
{
QofBackendError io_err;
g_return_val_if_fail (data && data->session, FALSE);
gxi_update_progress_bar (_("Writing file..."), 0.0);
qof_session_save (data->session, gxi_update_progress_bar);
gxi_update_progress_bar (NULL, -1.0);
io_err = qof_session_get_error (data->session);
if (io_err == ERR_BACKEND_NO_ERR)
{
return TRUE;
}
else
{
gxi_session_destroy (data);
return FALSE;
}
}
/***************************
* *
* Encodings dialog window *
* *
**************************/
void
gxi_edit_encodings_clicked_cb (GtkButton *button, GncXmlImportData *data)
{
GtkBuilder *builder;
GtkWidget *dialog;
GtkListStore *list_store;
GtkTreeStore *tree_store;
GtkTreeIter iter, parent, *parent_ptr;
GList *encodings_bak, *enc_iter;
const gchar *encoding;
system_encoding_type *system_enc;
gpointer enc_ptr;
gint i, j;
builder = gtk_builder_new();
gnc_builder_add_from_file (builder, "assistant-xml-encoding.glade", "encodings_dialog");
dialog = GTK_WIDGET(gtk_builder_get_object (builder, "encodings_dialog"));
data->encodings_dialog = dialog;
// Set the name for this assistant so it can be easily manipulated with css
gtk_widget_set_name (GTK_WIDGET(dialog), "gnc-id-assistant-xml-encoding");
gtk_builder_connect_signals_full (builder, gnc_builder_connect_full_func, data);
gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (data->assistant));
data->available_encs_view = GTK_TREE_VIEW (gtk_builder_get_object (builder, "available_encs_view"));
data->custom_enc_entry = GTK_WIDGET(gtk_builder_get_object (builder, "custom_enc_entry"));
/* set up selected encodings list */
data->selected_encs_view = GTK_TREE_VIEW (gtk_builder_get_object (builder, "selected_encs_view"));
list_store = gtk_list_store_new (ENC_NUM_COLS, G_TYPE_STRING, G_TYPE_POINTER);
for (enc_iter = data->encodings; enc_iter; enc_iter = enc_iter->next)
{
encoding = g_quark_to_string (GPOINTER_TO_UINT (enc_iter->data));
gtk_list_store_append (list_store, &iter);
gtk_list_store_set (list_store, &iter, ENC_COL_STRING, encoding,
ENC_COL_QUARK, enc_iter->data, -1);
}
gtk_tree_view_insert_column_with_attributes (
data->selected_encs_view, -1, NULL,
gtk_cell_renderer_text_new (), "text", ENC_COL_STRING, NULL);
gtk_tree_view_set_model (data->selected_encs_view,
GTK_TREE_MODEL (list_store));
g_object_unref (list_store);
/* set up system encodings list */
data->available_encs_view = GTK_TREE_VIEW (gtk_builder_get_object (builder, "available_encs_view"));
tree_store = gtk_tree_store_new (ENC_NUM_COLS, G_TYPE_STRING, G_TYPE_POINTER);
for (i = 0, system_enc = system_encodings;
i < n_system_encodings;
i++, system_enc++)
{
if (i == 0)
{
/* first system encoding */
parent_ptr = NULL;
}
else
{
parent_ptr = &iter;
for (j = 0; j < system_enc->parent; j++)
if (gtk_tree_model_iter_parent (GTK_TREE_MODEL (tree_store),
&parent, &iter))
{
/* go up one level */
iter = parent;
}
else
{
/* no parent to toplevel element */
parent_ptr = NULL;
}
}
if (system_enc->encoding)
enc_ptr = GUINT_TO_POINTER (g_quark_from_string (system_enc->encoding));
else
enc_ptr = NULL;
gtk_tree_store_append (tree_store, &iter, parent_ptr);
gtk_tree_store_set (tree_store, &iter, ENC_COL_STRING,
gettext (system_enc->text), ENC_COL_QUARK, enc_ptr, -1);
}
gtk_tree_view_insert_column_with_attributes (
data->available_encs_view, -1, NULL,
gtk_cell_renderer_text_new (), "text", ENC_COL_STRING, NULL);
gtk_tree_view_set_model (data->available_encs_view,
GTK_TREE_MODEL (tree_store));
g_object_unref (tree_store);
/* run the dialog */
encodings_bak = g_list_copy (data->encodings);
if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK)
{
g_list_free (encodings_bak);
if (data->encodings && !g_list_find (data->encodings,
GUINT_TO_POINTER (data->default_encoding)))
{
/* choose top level encoding then */
data->default_encoding = GPOINTER_TO_UINT (data->encodings->data);
}
/* update whole page */
gxi_check_file (data);
gxi_update_default_enc_combo (data);
gxi_update_string_box (data);
gxi_update_conversion_forward (data);
}
else
{
g_list_free (data->encodings);
data->encodings = encodings_bak;
}
g_object_unref(G_OBJECT(builder));
gtk_widget_destroy (dialog);
data->encodings_dialog = NULL;
}
static void
gxi_add_encoding (GncXmlImportData *data, gpointer encoding_ptr)
{
GIConv iconv;
const gchar *message;
gchar *enc_string;
GtkListStore *store;
GtkTreeIter iter;
enc_string = g_ascii_strup (
g_quark_to_string (GPOINTER_TO_UINT (encoding_ptr)), -1);
encoding_ptr = GUINT_TO_POINTER (g_quark_from_string (enc_string));
if (g_list_find (data->encodings, encoding_ptr))
{
message = _("This encoding has been added to the list already.");
gnc_error_dialog (GTK_WINDOW (data->encodings_dialog), "%s", message);
return;
}
/* test whether we like this encoding */
iconv = g_iconv_open ("UTF-8", enc_string);
if (iconv == (GIConv) - 1)
{
g_iconv_close (iconv);
g_free (enc_string);
message = _("This is an invalid encoding.");
gnc_error_dialog (GTK_WINDOW (data->encodings_dialog), "%s", message);
return;
}
g_iconv_close (iconv);
/* add to the list */
data->encodings = g_list_append (data->encodings, encoding_ptr);
store = GTK_LIST_STORE (gtk_tree_view_get_model (data->selected_encs_view));
gtk_list_store_append (store, &iter);
gtk_list_store_set (store, &iter, ENC_COL_STRING, enc_string,
ENC_COL_QUARK, encoding_ptr, -1);
g_free (enc_string);
if (!data->encodings->next)
gtk_dialog_set_response_sensitive (GTK_DIALOG (data->encodings_dialog),
GTK_RESPONSE_OK, TRUE);
}
void
gxi_add_enc_clicked_cb (GtkButton *button, GncXmlImportData *data)
{
GtkTreeSelection *selection;
GtkTreeModel *model;
GtkTreeIter iter;
gpointer enc_ptr;
selection = gtk_tree_view_get_selection (data->available_encs_view);
if (!gtk_tree_selection_get_selected (selection, &model, &iter))
return;
gtk_tree_model_get (model, &iter, ENC_COL_QUARK, &enc_ptr, -1);
if (!enc_ptr)
return;
gxi_add_encoding (data, enc_ptr);
}
static void
gxi_remove_encoding (GncXmlImportData *data, GtkTreeModel *model,
GtkTreeIter *iter)
{
gpointer enc_ptr;
gtk_tree_model_get (model, iter, ENC_COL_QUARK, &enc_ptr, -1);
data->encodings = g_list_remove (data->encodings, enc_ptr);
gtk_list_store_remove (GTK_LIST_STORE (model), iter);
if (!data->encodings)
gtk_dialog_set_response_sensitive (GTK_DIALOG (data->encodings_dialog),
GTK_RESPONSE_OK, FALSE);
}
void
gxi_remove_enc_clicked_cb (GtkButton *button, GncXmlImportData *data)
{
GtkTreeSelection *selection;
GtkTreeModel *model;
GtkTreeIter iter;
selection = gtk_tree_view_get_selection (data->selected_encs_view);
if (!gtk_tree_selection_get_selected (selection, &model, &iter))
return;
gxi_remove_encoding (data, model, &iter);
}
void
gxi_available_enc_activated_cb (GtkTreeView *view, GtkTreePath *path,
GtkTreeViewColumn *column,
GncXmlImportData *data)
{
GtkTreeModel *model;
GtkTreeIter iter;
gpointer enc_ptr;
model = gtk_tree_view_get_model (data->available_encs_view);
if (!gtk_tree_model_get_iter (model, &iter, path))
return;
gtk_tree_model_get (model, &iter, ENC_COL_QUARK, &enc_ptr, -1);
if (!enc_ptr)
return;
gxi_add_encoding (data, enc_ptr);
}
void
gxi_custom_enc_activate_cb (GtkEntry *entry, GncXmlImportData *data)
{
const gchar *enc_string;
enc_string = gtk_entry_get_text (entry);
if (!enc_string)
return;
gxi_add_encoding (data, GUINT_TO_POINTER (g_quark_from_string (enc_string)));
}
void
gxi_add_custom_enc_clicked_cb (GtkButton *button, GncXmlImportData *data)
{
GtkWidget *entry = data->custom_enc_entry;
gxi_custom_enc_activate_cb (GTK_ENTRY (entry), data);
}
void
gxi_selected_enc_activated_cb (GtkTreeView *view, GtkTreePath *path,
GtkTreeViewColumn *column, GncXmlImportData *data)
{
GtkTreeModel *model;
GtkTreeIter iter;
model = gtk_tree_view_get_model (data->selected_encs_view);
if (!gtk_tree_model_get_iter (model, &iter, path))
return;
gxi_remove_encoding (data, model, &iter);
}