Merge Thomas Teixeira's 'bug799774' into stable.

This commit is contained in:
John Ralls committed 2026-07-09 16:16:06 -07:00
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 (&#92;) is processed specially: backslash-quote (&#92;&quot;)becomes a single quote (&quot;); backslash-backslash (&#92;&#92;) becomes a single backslash (&#92;); backslash-n (&#92;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 (&#92;) is processed specially: backslash-quote (&#92;&quot;)becomes a single quote (&quot;); backslash-backslash (&#92;&#92;) becomes a single backslash (&#92;); backslash-n (&#92;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)
{