mirror of
https://github.com/Gnucash/gnucash.git
synced 2026-10-24 23:27:39 -05:00
Merge Thomas Teixeira's 'bug799774' into stable.
This commit is contained in:
commit
9db7b4439c
13 files changed
+189
-42
No files matched your search
@@ -276,7 +276,7 @@ There are two reserved names which can't be used to save custom settings:
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<!-- n-columns=3 n-rows=3 -->
|
||||
<!-- n-columns=3 n-rows=4 -->
|
||||
<object class="GtkGrid" id="separator_table">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
@@ -410,6 +410,25 @@ There are two reserved names which can't be used to save custom settings:
|
||||
<property name="top-attach">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="escape_cbutton">
|
||||
<property name="label" translatable="yes">Enable escape character (\)</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="tooltip-text" translatable="yes">If enabled, the escape character, backslash (\) is processed specially: backslash-quote (\")becomes a single quote ("); backslash-backslash (\\) becomes a single backslash (\); backslash-n (\n) becomes a newline character; a backslash followed by any other character is treated literally: both the backslash and the following character become part of the data field.</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw-indicator">True</property>
|
||||
<signal name="toggled" handler="csv_price_imp_preview_sep_button_cb" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">3</property>
|
||||
<property name="width">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
|
||||
@@ -265,7 +265,7 @@ There are two reserved names which can't be used to save custom settings:
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<!-- n-columns=3 n-rows=3 -->
|
||||
<!-- n-columns=3 n-rows=4 -->
|
||||
<object class="GtkGrid" id="separator_table">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
@@ -400,6 +400,25 @@ There are two reserved names which can't be used to save custom settings:
|
||||
<property name="width">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="escape_cbutton">
|
||||
<property name="label" translatable="yes">Enable escape character (\)</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="tooltip-text" translatable="yes">If enabled, the escape character, backslash (\) is processed specially: backslash-quote (\")becomes a single quote ("); backslash-backslash (\\) becomes a single backslash (\); backslash-n (\n) becomes a newline character; a backslash followed by any other character is treated literally: both the backslash and the following character become part of the data field.</property>
|
||||
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="active">True</property>
|
||||
<property name="draw-indicator">True</property>
|
||||
<signal name="toggled" handler="csv_tximp_preview_sep_button_cb" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left-attach">0</property>
|
||||
<property name="top-attach">3</property>
|
||||
<property name="width">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
|
||||
@@ -167,6 +167,7 @@ private:
|
||||
GOCharmapSel *encselector; /**< The widget for selecting the encoding */
|
||||
GtkWidget *separator_table; /**< Container for the separator checkboxes */
|
||||
GtkCheckButton *sep_button[SEP_NUM_OF_TYPES]; /**< Checkbuttons for common separators */
|
||||
GtkCheckButton *escape_cbutton; /**< The checkbutton for escape processing */
|
||||
GtkWidget *fw_instructions_hbox; /**< Container for fixed-width instructions */
|
||||
GtkCheckButton *custom_cbutton; /**< The checkbutton for a custom separator */
|
||||
GtkEntry *custom_entry; /**< The entry for custom separators */
|
||||
@@ -613,16 +614,21 @@ CsvImpPriceAssist::CsvImpPriceAssist ()
|
||||
};
|
||||
for (int i = 0; i < SEP_NUM_OF_TYPES; i++)
|
||||
sep_button[i]
|
||||
= (GtkCheckButton*)GTK_WIDGET(gtk_builder_get_object (builder, sep_button_names[i]));
|
||||
= GTK_CHECK_BUTTON(gtk_builder_get_object (builder, sep_button_names[i]));
|
||||
|
||||
/* Load and connect the custom separator checkbutton in the same way
|
||||
* as the other separator buttons. */
|
||||
custom_cbutton
|
||||
= (GtkCheckButton*)GTK_WIDGET(gtk_builder_get_object (builder, "custom_cbutton"));
|
||||
= GTK_CHECK_BUTTON(gtk_builder_get_object (builder, "custom_cbutton"));
|
||||
|
||||
/* Load the entry for the custom separator entry. Connect it to the
|
||||
* sep_button_clicked event handler as well. */
|
||||
custom_entry = (GtkEntry*)GTK_WIDGET(gtk_builder_get_object (builder, "custom_entry"));
|
||||
custom_entry = GTK_ENTRY(gtk_builder_get_object (builder, "custom_entry"));
|
||||
|
||||
/* Load and connect the escape checkbutton in the same way
|
||||
* as the other separator buttons. */
|
||||
escape_cbutton
|
||||
= GTK_CHECK_BUTTON(gtk_builder_get_object (builder, "escape_cbutton"));
|
||||
|
||||
/* Create the encoding selector widget and add it to the assistant */
|
||||
encselector = GO_CHARMAP_SEL(go_charmap_sel_new(GO_CHARMAP_SEL_TO_UTF8));
|
||||
@@ -689,7 +695,7 @@ CsvImpPriceAssist::CsvImpPriceAssist ()
|
||||
fixed_button = GTK_WIDGET(gtk_builder_get_object (builder, "fixed_button"));
|
||||
|
||||
/* Load the data treeview and connect it to its resizing event handler. */
|
||||
treeview = (GtkTreeView*)GTK_WIDGET(gtk_builder_get_object (builder, "treeview"));
|
||||
treeview = GTK_TREE_VIEW(gtk_builder_get_object (builder, "treeview"));
|
||||
gtk_tree_view_set_headers_clickable (treeview, true);
|
||||
|
||||
/* This is true only after encoding_selected is called, so we must
|
||||
@@ -1008,8 +1014,8 @@ void CsvImpPriceAssist::preview_over_write (bool over)
|
||||
|
||||
/** Event handler for separator changes. This function is called
|
||||
* whenever one of the widgets for configuring the separators (the
|
||||
* separator checkbuttons or the custom separator entry) is
|
||||
* changed.
|
||||
* separator checkbuttons, the escape checkbutton or the custom
|
||||
* separator entry) is changed.
|
||||
* @param widget The widget that was changed
|
||||
* @param info The data that is being configured
|
||||
*/
|
||||
@@ -1040,7 +1046,8 @@ void CsvImpPriceAssist::preview_update_separators (GtkWidget* widget)
|
||||
|
||||
/* Set the parse options using the checked_separators list. */
|
||||
price_imp->separators (checked_separators);
|
||||
|
||||
price_imp->enable_escape (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(escape_cbutton)));
|
||||
|
||||
/* if there are no separators, there will only be one column
|
||||
* so make sure column header is NONE */
|
||||
if (checked_separators.empty())
|
||||
@@ -1780,6 +1787,7 @@ CsvImpPriceAssist::preview_refresh ()
|
||||
if (price_imp->file_format() == GncImpFileFormat::CSV)
|
||||
{
|
||||
auto separators = price_imp->separators();
|
||||
auto escape = price_imp->enable_escape();
|
||||
const auto stock_sep_chars = std::string (" \t,:;-");
|
||||
|
||||
for (int i = 0; i < SEP_NUM_OF_TYPES; i++)
|
||||
@@ -1798,6 +1806,11 @@ CsvImpPriceAssist::preview_refresh ()
|
||||
separators.erase(pos);
|
||||
pos = separators.find_first_of (stock_sep_chars);
|
||||
}
|
||||
g_signal_handlers_block_by_func (escape_cbutton, (gpointer) csv_price_imp_preview_sep_button_cb, this);
|
||||
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(escape_cbutton),
|
||||
escape);
|
||||
g_signal_handlers_unblock_by_func (escape_cbutton, (gpointer) csv_price_imp_preview_sep_button_cb, this);
|
||||
|
||||
g_signal_handlers_block_by_func (custom_cbutton, (gpointer) csv_price_imp_preview_sep_button_cb, this);
|
||||
g_signal_handlers_block_by_func (custom_entry, (gpointer) csv_price_imp_preview_sep_button_cb, this);
|
||||
|
||||
@@ -1894,7 +1907,8 @@ CsvImpPriceAssist::assist_preview_page_prepare ()
|
||||
{
|
||||
/* Parsing failed ... */
|
||||
gnc_error_dialog (GTK_WINDOW(csv_imp_asst), "%s", _(e.what()));
|
||||
go_back = true;
|
||||
/* Stay in this step so user can override */
|
||||
go_back = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -242,6 +242,7 @@ private:
|
||||
GOCharmapSel *encselector; /**< The widget for selecting the encoding */
|
||||
GtkWidget *separator_table; /**< Container for the separator checkboxes */
|
||||
GtkCheckButton *sep_button[SEP_NUM_OF_TYPES]; /**< Checkbuttons for common separators */
|
||||
GtkCheckButton *escape_cbutton; /**< The checkbutton for escape processing */
|
||||
GtkWidget *fw_instructions_hbox; /**< Container for fixed-width instructions */
|
||||
GtkCheckButton *custom_cbutton; /**< The checkbutton for a custom separator */
|
||||
GtkEntry *custom_entry; /**< The entry for custom separators */
|
||||
@@ -565,16 +566,21 @@ CsvImpTransAssist::CsvImpTransAssist ()
|
||||
};
|
||||
for (int i = 0; i < SEP_NUM_OF_TYPES; i++)
|
||||
sep_button[i]
|
||||
= (GtkCheckButton*)GTK_WIDGET(gtk_builder_get_object (builder, sep_button_names[i]));
|
||||
= GTK_CHECK_BUTTON(gtk_builder_get_object (builder, sep_button_names[i]));
|
||||
|
||||
/* Load and connect the custom separator checkbutton in the same way
|
||||
* as the other separator buttons. */
|
||||
custom_cbutton
|
||||
= (GtkCheckButton*)GTK_WIDGET(gtk_builder_get_object (builder, "custom_cbutton"));
|
||||
= GTK_CHECK_BUTTON(gtk_builder_get_object (builder, "custom_cbutton"));
|
||||
|
||||
/* Load the entry for the custom separator entry. Connect it to the
|
||||
* sep_button_clicked event handler as well. */
|
||||
custom_entry = (GtkEntry*)GTK_WIDGET(gtk_builder_get_object (builder, "custom_entry"));
|
||||
custom_entry = GTK_ENTRY(gtk_builder_get_object (builder, "custom_entry"));
|
||||
|
||||
/* Load and connect the escape checkbutton in the same way
|
||||
* as the other separator buttons. */
|
||||
escape_cbutton
|
||||
= GTK_CHECK_BUTTON(gtk_builder_get_object (builder, "escape_cbutton"));
|
||||
|
||||
/* Add account selection widget */
|
||||
acct_selector = gnc_account_sel_new();
|
||||
@@ -637,7 +643,7 @@ CsvImpTransAssist::CsvImpTransAssist ()
|
||||
fixed_button = GTK_WIDGET(gtk_builder_get_object (builder, "fixed_button"));
|
||||
|
||||
/* Load the data treeview and connect it to its resizing event handler. */
|
||||
treeview = (GtkTreeView*)GTK_WIDGET(gtk_builder_get_object (builder, "treeview"));
|
||||
treeview = GTK_TREE_VIEW(gtk_builder_get_object (builder, "treeview"));
|
||||
gtk_tree_view_set_headers_clickable (treeview, true);
|
||||
|
||||
/* This is true only after encoding_selected is called, so we must
|
||||
@@ -986,8 +992,8 @@ void CsvImpTransAssist::preview_multi_split (bool multi)
|
||||
|
||||
/** Event handler for separator changes. This function is called
|
||||
* whenever one of the widgets for configuring the separators (the
|
||||
* separator checkbuttons or the custom separator entry) is
|
||||
* changed.
|
||||
* separator checkbuttons, the escape checkbutton or the custom
|
||||
* separator entry) is changed.
|
||||
* @param widget The widget that was changed
|
||||
*/
|
||||
void CsvImpTransAssist::preview_update_separators (GtkWidget* widget)
|
||||
@@ -1018,6 +1024,7 @@ void CsvImpTransAssist::preview_update_separators (GtkWidget* widget)
|
||||
|
||||
/* Set the parse options using the checked_separators list. */
|
||||
tx_imp->separators (checked_separators);
|
||||
tx_imp->enable_escape (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON(escape_cbutton)));
|
||||
|
||||
/* Parse the data using the new options. We don't want to reguess
|
||||
* the column types because we want to leave the user's
|
||||
@@ -1704,6 +1711,7 @@ CsvImpTransAssist::preview_refresh ()
|
||||
if (tx_imp->file_format() == GncImpFileFormat::CSV)
|
||||
{
|
||||
auto separators = tx_imp->separators();
|
||||
auto enable_escape = tx_imp->enable_escape();
|
||||
const auto stock_sep_chars = std::string (" \t,:;-");
|
||||
for (int i = 0; i < SEP_NUM_OF_TYPES; i++)
|
||||
{
|
||||
@@ -1726,8 +1734,15 @@ CsvImpTransAssist::preview_refresh ()
|
||||
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(custom_cbutton),
|
||||
!separators.empty());
|
||||
gtk_entry_set_text (GTK_ENTRY(custom_entry), separators.c_str());
|
||||
|
||||
g_signal_handlers_block_by_func (escape_cbutton, (gpointer) csv_tximp_preview_sep_button_cb, this);
|
||||
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(escape_cbutton),
|
||||
enable_escape);
|
||||
g_signal_handlers_unblock_by_func (escape_cbutton, (gpointer) csv_tximp_preview_sep_button_cb, this);
|
||||
|
||||
g_signal_handlers_unblock_by_func (custom_cbutton, (gpointer) csv_tximp_preview_sep_button_cb, this);
|
||||
g_signal_handlers_unblock_by_func (custom_entry, (gpointer) csv_tximp_preview_sep_button_cb, this);
|
||||
g_signal_handlers_unblock_by_func (custom_cbutton, (gpointer) csv_tximp_preview_sep_button_cb, this);
|
||||
try
|
||||
{
|
||||
tx_imp->tokenize (false);
|
||||
@@ -2036,7 +2051,8 @@ CsvImpTransAssist::assist_preview_page_prepare ()
|
||||
{
|
||||
/* Parsing failed ... */
|
||||
gnc_error_dialog (GTK_WINDOW (csv_imp_asst), "%s", _(e.what()));
|
||||
go_back = true;
|
||||
/* Stay in this step so user can override */
|
||||
go_back = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ const std::string gnc_exp_4{N_("GnuCash Export Format (4.x and older)")};
|
||||
#define CSV_SKIP_END "SkipEndLines"
|
||||
|
||||
#define CSV_SEP "Separators"
|
||||
#define CSV_ESC "EnableEscape"
|
||||
|
||||
#define CSV_CUSTOM "Custom"
|
||||
#define CSV_CUSTOM_ENTRY "CustomEntry"
|
||||
@@ -150,6 +151,14 @@ CsvImportSettings::load (void)
|
||||
if (key_char)
|
||||
g_free (key_char);
|
||||
|
||||
m_enable_escape = g_key_file_get_boolean (keyfile, group.c_str(), CSV_ESC, &key_error);
|
||||
// Older import settings will not have a value for escape processing
|
||||
// so set it to true if no value is found in the file format settings
|
||||
if (key_error && key_error->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND)
|
||||
m_enable_escape = true;
|
||||
else
|
||||
m_load_error |= handle_load_error (&key_error, group);
|
||||
|
||||
m_date_format = g_key_file_get_integer (keyfile, group.c_str(), CSV_DATE, &key_error);
|
||||
m_load_error |= handle_load_error (&key_error, group);
|
||||
|
||||
@@ -203,6 +212,7 @@ CsvImportSettings::save (void)
|
||||
(m_file_format == GncImpFileFormat::CSV) ? true : false);
|
||||
|
||||
g_key_file_set_string (keyfile, group.c_str(), CSV_SEP, m_separators.c_str());
|
||||
g_key_file_set_boolean (keyfile, group.c_str(), CSV_ESC, m_enable_escape);
|
||||
g_key_file_set_integer (keyfile, group.c_str(), CSV_DATE, m_date_format);
|
||||
std::ostringstream cmt_ss;
|
||||
cmt_ss << "Supported date formats: ";
|
||||
|
||||
@@ -53,7 +53,7 @@ struct CsvImportSettings
|
||||
CsvImportSettings() : m_file_format (GncImpFileFormat::CSV), m_encoding {"UTF-8"},
|
||||
m_date_format {0}, m_currency_format {0},
|
||||
m_skip_start_lines{0}, m_skip_end_lines{0}, m_skip_alt_lines (false),
|
||||
m_separators {","}, m_load_error {false} { }
|
||||
m_separators {","}, m_enable_escape {true}, m_load_error {false} { }
|
||||
virtual ~CsvImportSettings() = default;
|
||||
|
||||
/** Save the gathered widget properties to a key File.
|
||||
@@ -82,6 +82,7 @@ uint32_t m_skip_start_lines; // Number of header rows to skip
|
||||
uint32_t m_skip_end_lines; // Number of footer rows to skip
|
||||
bool m_skip_alt_lines; // Skip alternate rows
|
||||
std::string m_separators; // Separators for csv format
|
||||
bool m_enable_escape; // Process escapes in quoted csv entries
|
||||
bool m_load_error; // Was there an error while parsing the state file ?
|
||||
std::vector<uint32_t> m_column_widths; // The Column widths
|
||||
|
||||
|
||||
@@ -114,9 +114,12 @@ void GncPriceImport::file_format(GncImpFileFormat format)
|
||||
load_file(new_imp_file);
|
||||
|
||||
// Restore potentially previously set separators or column_widths
|
||||
if ((file_format() == GncImpFileFormat::CSV)
|
||||
&& !m_settings.m_separators.empty())
|
||||
separators (m_settings.m_separators);
|
||||
if (file_format() == GncImpFileFormat::CSV)
|
||||
{
|
||||
if (!m_settings.m_separators.empty())
|
||||
separators (m_settings.m_separators);
|
||||
enable_escape (m_settings.m_enable_escape);
|
||||
}
|
||||
else if ((file_format() == GncImpFileFormat::FIXED_WIDTH)
|
||||
&& !m_settings.m_column_widths.empty())
|
||||
{
|
||||
@@ -287,6 +290,16 @@ void GncPriceImport::separators (std::string separators)
|
||||
}
|
||||
std::string GncPriceImport::separators () { return m_settings.m_separators; }
|
||||
|
||||
void GncPriceImport::enable_escape(bool enable)
|
||||
{
|
||||
if (file_format() != GncImpFileFormat::CSV)
|
||||
return;
|
||||
m_settings.m_enable_escape = enable;
|
||||
auto csvtok = dynamic_cast<GncCsvTokenizer*>(m_tokenizer.get());
|
||||
csvtok->set_enable_escape (enable);
|
||||
}
|
||||
bool GncPriceImport::enable_escape() { return m_settings.m_enable_escape; }
|
||||
|
||||
void GncPriceImport::settings (const CsvPriceImpSettings& settings)
|
||||
{
|
||||
/* First apply file format as this may recreate the tokenizer */
|
||||
|
||||
@@ -117,6 +117,9 @@ public:
|
||||
void separators (std::string separators);
|
||||
std::string separators ();
|
||||
|
||||
void enable_escape (bool enable);
|
||||
bool enable_escape ();
|
||||
|
||||
void settings (const CsvPriceImpSettings& settings);
|
||||
bool save_settings ();
|
||||
|
||||
|
||||
@@ -114,9 +114,12 @@ void GncTxImport::file_format(GncImpFileFormat format)
|
||||
load_file(new_imp_file);
|
||||
|
||||
// Restore potentially previously set separators or column_widths
|
||||
if ((file_format() == GncImpFileFormat::CSV)
|
||||
&& !m_settings.m_separators.empty())
|
||||
separators (m_settings.m_separators);
|
||||
if (file_format() == GncImpFileFormat::CSV)
|
||||
{
|
||||
if (!m_settings.m_separators.empty())
|
||||
separators (m_settings.m_separators);
|
||||
enable_escape (m_settings.m_enable_escape);
|
||||
}
|
||||
else if ((file_format() == GncImpFileFormat::FIXED_WIDTH)
|
||||
&& !m_settings.m_column_widths.empty())
|
||||
{
|
||||
@@ -313,6 +316,16 @@ void GncTxImport::separators (std::string separators)
|
||||
}
|
||||
std::string GncTxImport::separators () { return m_settings.m_separators; }
|
||||
|
||||
void GncTxImport::enable_escape(bool enable)
|
||||
{
|
||||
if (file_format() != GncImpFileFormat::CSV)
|
||||
return;
|
||||
m_settings.m_enable_escape = enable;
|
||||
auto csvtok = dynamic_cast<GncCsvTokenizer*>(m_tokenizer.get());
|
||||
csvtok->set_enable_escape (enable);
|
||||
}
|
||||
bool GncTxImport::enable_escape() { return m_settings.m_enable_escape; }
|
||||
|
||||
void GncTxImport::settings (const CsvTransImpSettings& settings)
|
||||
{
|
||||
/* First apply file format as this may recreate the tokenizer */
|
||||
@@ -324,7 +337,10 @@ void GncTxImport::settings (const CsvTransImpSettings& settings)
|
||||
encoding (m_settings.m_encoding);
|
||||
|
||||
if (file_format() == GncImpFileFormat::CSV)
|
||||
{
|
||||
separators (m_settings.m_separators);
|
||||
enable_escape (m_settings.m_enable_escape);
|
||||
}
|
||||
else if (file_format() == GncImpFileFormat::FIXED_WIDTH)
|
||||
{
|
||||
auto fwtok = dynamic_cast<GncFwTokenizer*>(m_tokenizer.get());
|
||||
|
||||
@@ -136,6 +136,9 @@ public:
|
||||
void separators (std::string separators);
|
||||
std::string separators ();
|
||||
|
||||
void enable_escape (bool enable);
|
||||
bool enable_escape ();
|
||||
|
||||
void settings (const CsvTransImpSettings& settings);
|
||||
bool save_settings ();
|
||||
|
||||
|
||||
@@ -43,6 +43,12 @@ GncCsvTokenizer::set_separators(const std::string& separators)
|
||||
m_sep_str = separators;
|
||||
}
|
||||
|
||||
void
|
||||
GncCsvTokenizer::set_enable_escape(const bool enable)
|
||||
{
|
||||
m_enable_escape = enable;
|
||||
}
|
||||
|
||||
|
||||
int GncCsvTokenizer::tokenize()
|
||||
{
|
||||
@@ -71,7 +77,7 @@ int GncCsvTokenizer::tokenize()
|
||||
{
|
||||
if (last_quote == 0) // Test separately because last_quote - 1 would be out of range
|
||||
inside_quotes = !inside_quotes;
|
||||
else if (buffer[ last_quote - 1 ] != '\\')
|
||||
else if (!m_enable_escape || buffer[ last_quote - 1 ] != '\\')
|
||||
inside_quotes = !inside_quotes;
|
||||
|
||||
last_quote = buffer.find_first_of('"',last_quote+1);
|
||||
@@ -91,10 +97,21 @@ int GncCsvTokenizer::tokenize()
|
||||
auto bs_pos = line.find ('\\');
|
||||
while (bs_pos != std::string::npos)
|
||||
{
|
||||
if ((bs_pos == line.size()) || // got trailing single backslash
|
||||
(line.find_first_of ("\"\\n", bs_pos + 1) != bs_pos + 1)) // backslash is not part of known escapes \\, \" or \n
|
||||
line = line.substr(0, bs_pos) + "\\\\" + line.substr(bs_pos + 1);
|
||||
bs_pos += 2;
|
||||
if (! m_enable_escape)
|
||||
{
|
||||
// Put an escape before this escape and look for the next
|
||||
line.insert (bs_pos, 1, '\\');
|
||||
bs_pos += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Process as before
|
||||
if ((bs_pos == line.size()) || // got trailing single backslash
|
||||
(line.find_first_of ("\"\\n", bs_pos + 1) != bs_pos + 1)) // backslash is not part of known escapes \\, \" or \n
|
||||
line = line.substr(0, bs_pos) + "\\\\" + line.substr(bs_pos + 1);
|
||||
bs_pos += 2;
|
||||
}
|
||||
|
||||
bs_pos = line.find ('\\', bs_pos);
|
||||
}
|
||||
|
||||
|
||||
@@ -53,10 +53,13 @@ public:
|
||||
~GncCsvTokenizer() = default; // destructor
|
||||
|
||||
void set_separators(const std::string& separators);
|
||||
void set_enable_escape(const bool enable);
|
||||
int tokenize() override;
|
||||
|
||||
private:
|
||||
std::string m_sep_str = ",";
|
||||
|
||||
bool m_enable_escape = true;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -42,6 +42,7 @@
|
||||
|
||||
struct tokenize_csv_test_data
|
||||
{
|
||||
bool enable_escape;
|
||||
const char *csv_line;
|
||||
uint num_fields;
|
||||
const char *fields [8];
|
||||
@@ -160,7 +161,9 @@ GncTokenizerTest::test_gnc_tokenize_helper (const std::string& separators,
|
||||
{
|
||||
|
||||
tokenize_csv_test_data cur_line = test_data[i];
|
||||
|
||||
set_utf8_contents (csv_tok, std::string(cur_line.csv_line));
|
||||
csvtok->set_enable_escape (cur_line.enable_escape);
|
||||
csv_tok->tokenize();
|
||||
|
||||
// The tests only come with one line, so get the first row only
|
||||
@@ -176,15 +179,25 @@ GncTokenizerTest::test_gnc_tokenize_helper (const std::string& separators,
|
||||
}
|
||||
|
||||
static tokenize_csv_test_data comma_separated [] = {
|
||||
{ "Date,Num,Description,Notes,Account,Deposit,Withdrawal,Balance", 8, { "Date","Num","Description","Notes","Account","Deposit","Withdrawal","Balance" } },
|
||||
{ "05/01/15,45,Typical csv import line - including quoted empty field,,Miscellaneous,\"\",\"1,100.00\",", 8, { "05/01/15","45","Typical csv import line - including quoted empty field","","Miscellaneous","","1,100.00","" } },
|
||||
{ "05/01/15,45,Acme Inc.,,Miscellaneous,", 6, { "05/01/15","45","Acme Inc.","","Miscellaneous","",NULL,NULL } },
|
||||
{ "Test\\ with backslash,nextfield", 2, { "Test\\ with backslash","nextfield",NULL,NULL,NULL,NULL,NULL,NULL } },
|
||||
{ "Test with \\\" escaped quote,nextfield", 2, { "Test with \" escaped quote","nextfield",NULL,NULL,NULL,NULL,NULL,NULL } },
|
||||
{ "Test with \"\" escaped quote,nextfield", 2, { "Test with \" escaped quote","nextfield",NULL,NULL,NULL,NULL,NULL,NULL } },
|
||||
{ "\"Test (quoted) with \"\" escaped quote\",\"nextfield\"", 2, { "Test (quoted) with \" escaped quote","nextfield",NULL,NULL,NULL,NULL,NULL,NULL } },
|
||||
{ "\"Unescaped quote test\",nextfield", 2, { "Unescaped quote test","nextfield",NULL,NULL,NULL,NULL,NULL,NULL } },
|
||||
{ NULL, 0, { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL } },
|
||||
{ true, "Date,Num,Description,Notes,Account,Deposit,Withdrawal,Balance", 8, { "Date","Num","Description","Notes","Account","Deposit","Withdrawal","Balance" } },
|
||||
{ false, "Date,Num,Description,Notes,Account,Deposit,Withdrawal,Balance", 8, { "Date","Num","Description","Notes","Account","Deposit","Withdrawal","Balance" } },
|
||||
{ true, "05/01/15,45,Typical csv import line - including quoted empty field,,Miscellaneous,\"\",\"1,100.00\",", 8, { "05/01/15","45","Typical csv import line - including quoted empty field","","Miscellaneous","","1,100.00","" } },
|
||||
{ false, "05/01/15,45,Typical csv import line - including quoted empty field,,Miscellaneous,\"\",\"1,100.00\",", 8, { "05/01/15","45","Typical csv import line - including quoted empty field","","Miscellaneous","","1,100.00","" } },
|
||||
{ true, "05/01/15,45,Acme Inc.,,Miscellaneous,", 6, { "05/01/15","45","Acme Inc.","","Miscellaneous","",NULL,NULL } },
|
||||
{ false, "05/01/15,45,Acme Inc.,,Miscellaneous,", 6, { "05/01/15","45","Acme Inc.","","Miscellaneous","",NULL,NULL } },
|
||||
{ true, "Test\\ with backslash,nextfield", 2, { "Test\\ with backslash","nextfield",NULL,NULL,NULL,NULL,NULL,NULL } },
|
||||
{ false, "Test\\ with backslash,nextfield", 2, { "Test\\ with backslash","nextfield",NULL,NULL,NULL,NULL,NULL,NULL } },
|
||||
// This test string is invalid without escape characters because there is only one quote
|
||||
{ true, "Test with \\\" escaped quote,nextfield", 2, { "Test with \" escaped quote","nextfield",NULL,NULL,NULL,NULL,NULL,NULL } },
|
||||
{ true, "Test with \"\" escaped quote,nextfield", 2, { "Test with \" escaped quote","nextfield",NULL,NULL,NULL,NULL,NULL,NULL } },
|
||||
{ false, "Test with \"\" escaped quote,nextfield", 2, { "Test with \" escaped quote","nextfield",NULL,NULL,NULL,NULL,NULL,NULL } },
|
||||
{ true,"\"Test (quoted) with \"\" escaped quote\",\"nextfield\"", 2, { "Test (quoted) with \" escaped quote","nextfield",NULL,NULL,NULL,NULL,NULL,NULL } },
|
||||
{ true, "\"Unescaped quote test\",nextfield", 2, { "Unescaped quote test","nextfield",NULL,NULL,NULL,NULL,NULL,NULL } },
|
||||
{ false, "\"Unescaped quote test\",nextfield", 2, { "Unescaped quote test","nextfield",NULL,NULL,NULL,NULL,NULL,NULL } },
|
||||
// The following has a parse error with escape processing true
|
||||
{ false, "\"Unescaped quote test with trailing backslash and escape off\\\",nextfield", 2, { "Unescaped quote test with trailing backslash and escape off\\","nextfield",NULL,NULL,NULL,NULL,NULL,NULL } },
|
||||
|
||||
{true, NULL, 0, { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL } },
|
||||
};
|
||||
|
||||
TEST_F (GncTokenizerTest, tokenize_comma_sep)
|
||||
@@ -193,10 +206,10 @@ TEST_F (GncTokenizerTest, tokenize_comma_sep)
|
||||
}
|
||||
|
||||
static tokenize_csv_test_data semicolon_separated [] = {
|
||||
{ "Date;Num;Description;Notes;Account;Deposit;Withdrawal;Balance", 8, { "Date","Num","Description","Notes","Account","Deposit","Withdrawal","Balance" } },
|
||||
{ "05/01/15;45;Typical csv import line - including quoted empty field;;Miscellaneous;\"\";\"1,100.00\";", 8, { "05/01/15","45","Typical csv import line - including quoted empty field","","Miscellaneous","","1,100.00","" } },
|
||||
{ "05/01/15;45;Acme Inc.;;Miscellaneous;", 6, { "05/01/15","45","Acme Inc.","","Miscellaneous","",NULL,NULL } },
|
||||
{ NULL, 0, { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL } },
|
||||
{ true, "Date;Num;Description;Notes;Account;Deposit;Withdrawal;Balance", 8, { "Date","Num","Description","Notes","Account","Deposit","Withdrawal","Balance" } },
|
||||
{ true, "05/01/15;45;Typical csv import line - including quoted empty field;;Miscellaneous;\"\";\"1,100.00\";", 8, { "05/01/15","45","Typical csv import line - including quoted empty field","","Miscellaneous","","1,100.00","" } },
|
||||
{ true, "05/01/15;45;Acme Inc.;;Miscellaneous;", 6, { "05/01/15","45","Acme Inc.","","Miscellaneous","",NULL,NULL } },
|
||||
{ true, NULL, 0, { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL } },
|
||||
};
|
||||
TEST_F (GncTokenizerTest, tokenize_semicolon_sep)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user