31 #include <glib/gi18n.h> 45 #include <boost/regex.hpp> 46 #include <boost/regex/icu.hpp> 49 #include "gnc-imp-props-tx.hpp" 54 G_GNUC_UNUSED
static QofLogModule log_module = GNC_MOD_IMPORT;
56 const int num_currency_formats = 3;
57 const gchar* currency_format_user[] = {N_(
"Locale"),
58 N_(
"Period: 123,456.78"),
59 N_(
"Comma: 123.456,78")
70 m_skip_errors =
false;
89 if (
m_tokenizer && m_settings.m_file_format == format)
92 auto new_encoding = std::string(
"UTF-8");
93 auto new_imp_file = std::string();
100 if (
file_format() == GncImpFileFormat::FIXED_WIDTH)
103 if (!fwtok->get_columns().empty())
104 m_settings.m_column_widths = fwtok->get_columns();
108 m_settings.m_file_format = format;
109 m_tokenizer = gnc_tokenizer_factory(m_settings.m_file_format);
118 && !m_settings.m_separators.empty())
119 separators (m_settings.m_separators);
120 else if ((
file_format() == GncImpFileFormat::FIXED_WIDTH)
121 && !m_settings.m_column_widths.empty())
124 fwtok->columns (m_settings.m_column_widths);
131 return m_settings.m_file_format;
147 auto trans_prop_seen =
false;
148 m_settings.m_multi_split = multi_split;
149 for (uint32_t i = 0; i < m_settings.m_column_types.size(); i++)
151 auto old_prop = m_settings.m_column_types[i];
152 auto is_trans_prop = ((old_prop > GncTransPropType::NONE)
153 && (old_prop <= GncTransPropType::TRANS_PROPS));
154 auto san_prop = sanitize_trans_prop (old_prop, m_settings.m_multi_split);
155 if (san_prop != old_prop)
156 set_column_type (i, san_prop);
157 else if (is_trans_prop && !trans_prop_seen)
158 set_column_type (i, old_prop,
true);
159 trans_prop_seen |= is_trans_prop;
162 if (m_settings.m_multi_split)
163 m_settings.m_base_account =
nullptr;
166 bool GncTxImport::multi_split () {
return m_settings.m_multi_split; }
179 if (m_settings.m_multi_split)
181 m_settings.m_base_account =
nullptr;
185 auto base_account_is_new = m_settings.m_base_account ==
nullptr;
186 m_settings.m_base_account = base_account;
188 if (m_settings.m_base_account)
190 auto col_type_it = std::find (m_settings.m_column_types.begin(),
191 m_settings.m_column_types.end(), GncTransPropType::ACCOUNT);
192 if (col_type_it != m_settings.m_column_types.end())
193 set_column_type(col_type_it - m_settings.m_column_types.begin(),
194 GncTransPropType::NONE);
196 if (base_account_is_new)
200 std::get<PL_PRESPLIT>(line)->set_account (m_settings.m_base_account);
211 Account *GncTxImport::base_account () {
return m_settings.m_base_account; }
213 void GncTxImport::reset_formatted_column (std::vector<GncTransPropType>& col_types)
215 for (
auto col_type: col_types)
217 auto col = std::find (m_settings.m_column_types.begin(),
218 m_settings.m_column_types.end(), col_type);
219 if (col != m_settings.m_column_types.end())
220 set_column_type (col - m_settings.m_column_types.begin(), col_type,
true);
224 void GncTxImport::currency_format (
int currency_format)
226 m_settings.m_currency_format = currency_format;
229 std::vector<GncTransPropType> commodities = {
230 GncTransPropType::AMOUNT,
231 GncTransPropType::AMOUNT_NEG,
232 GncTransPropType::TAMOUNT,
233 GncTransPropType::TAMOUNT_NEG,
234 GncTransPropType::PRICE};
235 reset_formatted_column (commodities);
237 int GncTxImport::currency_format () {
return m_settings.m_currency_format; }
239 void GncTxImport::date_format (
int date_format)
241 m_settings.m_date_format = date_format;
244 std::vector<GncTransPropType> dates = { GncTransPropType::DATE,
245 GncTransPropType::REC_DATE,
246 GncTransPropType::TREC_DATE};
247 reset_formatted_column (dates);
249 int GncTxImport::date_format () {
return m_settings.m_date_format; }
271 m_settings.m_encoding = encoding;
274 std::string GncTxImport::encoding () {
return m_settings.m_encoding; }
276 void GncTxImport::update_skipped_lines(std::optional<uint32_t> start, std::optional<uint32_t> end,
277 std::optional<bool> alt, std::optional<bool> errors)
280 m_settings.m_skip_start_lines = *start;
282 m_settings.m_skip_end_lines = *end;
284 m_settings.m_skip_alt_lines = *alt;
286 m_skip_errors = *errors;
291 ((i < skip_start_lines()) ||
293 (((i - skip_start_lines()) % 2 == 1) &&
295 (m_skip_errors && !std::get<PL_ERROR>(
m_parsed_lines[i]).empty()));
299 uint32_t GncTxImport::skip_start_lines () {
return m_settings.m_skip_start_lines; }
300 uint32_t GncTxImport::skip_end_lines () {
return m_settings.m_skip_end_lines; }
301 bool GncTxImport::skip_alt_lines () {
return m_settings.m_skip_alt_lines; }
302 bool GncTxImport::skip_err_lines () {
return m_skip_errors; }
304 void GncTxImport::separators (std::string separators)
309 m_settings.m_separators = separators;
311 csvtok->set_separators (separators);
314 std::string GncTxImport::separators () {
return m_settings.m_separators; }
321 m_settings = settings;
327 separators (m_settings.m_separators);
328 else if (
file_format() == GncImpFileFormat::FIXED_WIDTH)
331 fwtok->columns (m_settings.m_column_widths);
343 std::copy_n (settings.m_column_types.begin(),
344 std::min (m_settings.m_column_types.size(), settings.m_column_types.size()),
345 m_settings.m_column_types.begin());
349 bool GncTxImport::save_settings ()
359 if (
file_format() == GncImpFileFormat::FIXED_WIDTH)
362 m_settings.m_column_widths = fwtok->get_columns();
365 return m_settings.
save();
368 void GncTxImport::settings_name (std::string name) { m_settings.m_name = name; }
369 std::string GncTxImport::settings_name () {
return m_settings.m_name; }
386 catch (std::ifstream::failure& ios_err)
389 PWARN (
"Error: %s", ios_err.what());
410 uint32_t max_cols = 0;
413 for (
auto tokenized_line :
m_tokenizer->get_tokens())
415 auto length = tokenized_line.size();
418 auto pretrans = std::make_shared<GncPreTrans>(date_format(), m_settings.m_multi_split);
419 auto presplit = std::make_shared<GncPreSplit>(date_format(), currency_format());
420 presplit->set_pre_trans (std::move (pretrans));
421 m_parsed_lines.push_back (std::make_tuple (tokenized_line, ErrMap(),
422 presplit->get_pre_trans(), std::move (presplit),
false));
424 if (length > max_cols)
431 throw (std::range_error (N_(
"There was an error parsing the file.")));
435 m_settings.m_column_types.resize(max_cols, GncTransPropType::NONE);
438 for (uint32_t i = 0; i < m_settings.m_column_types.size(); i++)
439 set_column_type (i, m_settings.m_column_types[i],
true);
440 if (m_settings.m_base_account)
443 std::get<PL_PRESPLIT>(line)->set_account (m_settings.m_base_account);
458 void add_error (std::string msg);
464 void ErrorList::add_error (std::string msg)
466 m_error.emplace_back (msg);
469 std::string ErrorList::str()
471 auto err_msg = std::string();
472 if (!m_error.empty())
474 auto add_bullet_item = [](std::string& a, std::string& b)->std::string {
return std::move(a) +
"\n• " + b; };
475 err_msg = std::accumulate (m_error.begin(), m_error.end(), std::move (err_msg), add_bullet_item);
476 err_msg.erase (0, 1);
487 void GncTxImport::verify_column_selections (
ErrorList& error_msg)
492 if (!check_for_column_type(GncTransPropType::DATE))
493 error_msg.add_error( _(
"Please select a date column."));
498 if (!check_for_column_type(GncTransPropType::ACCOUNT))
500 if (m_settings.m_multi_split)
501 error_msg.add_error( _(
"Please select an account column."));
502 else if (!m_settings.m_base_account)
503 error_msg.add_error( _(
"Please select an account column or set a base account in the Account field."));
508 if (!check_for_column_type(GncTransPropType::DESCRIPTION))
509 error_msg.add_error( _(
"Please select a description column."));
513 if (!check_for_column_type(GncTransPropType::AMOUNT) &&
514 !check_for_column_type(GncTransPropType::AMOUNT_NEG))
515 error_msg.add_error( _(
"Please select a (negated) amount column."));
526 if (m_multi_currency)
528 if (m_settings.m_multi_split &&
529 !check_for_column_type(GncTransPropType::PRICE) &&
530 !check_for_column_type(GncTransPropType::VALUE) &&
531 !check_for_column_type(GncTransPropType::VALUE_NEG))
532 error_msg.add_error( _(
"The current account selections will generate multi-currency transactions. Please select one of the following columns: price, (negated) value."));
533 else if (!m_settings.m_multi_split &&
534 !check_for_column_type(GncTransPropType::PRICE) &&
535 !check_for_column_type(GncTransPropType::TAMOUNT) &&
536 !check_for_column_type(GncTransPropType::TAMOUNT_NEG) &&
537 !check_for_column_type(GncTransPropType::VALUE) &&
538 !check_for_column_type(GncTransPropType::VALUE_NEG))
539 error_msg.add_error( _(
"The current account selections will generate multi-currency transactions. Please select one of the following columns: price, (negated) value, (negated) transfer amount."));
552 std::string GncTxImport::verify (
bool with_acct_errors)
554 auto newline = std::string();
560 error_msg.add_error(_(
"No valid data found in the selected file. It may be empty or the selected encoding is wrong."));
561 return error_msg.str();
565 auto skip_alt_offset = m_settings.m_skip_alt_lines ? 1 : 0;
566 if (m_settings.m_skip_start_lines + m_settings.m_skip_end_lines + skip_alt_offset >=
m_parsed_lines.size())
568 error_msg.add_error(_(
"No lines are selected for importing. Please reduce the number of lines to skip."));
569 return error_msg.str();
572 verify_column_selections (error_msg);
574 update_skipped_lines (std::nullopt, std::nullopt, std::nullopt, std::nullopt);
576 auto have_line_errors =
false;
579 auto errors = std::get<PL_ERROR>(line);
580 if (std::get<PL_SKIP>(line))
582 if (with_acct_errors && !errors.empty())
584 have_line_errors =
true;
587 auto non_acct_error = [](ErrPair curr_err)
589 return !((curr_err.first == GncTransPropType::ACCOUNT) ||
590 (curr_err.first == GncTransPropType::TACCOUNT));
592 if (!with_acct_errors &&
593 std::any_of(errors.cbegin(), errors.cend(), non_acct_error))
595 have_line_errors =
true;
600 if (have_line_errors)
601 error_msg.add_error( _(
"Not all fields could be parsed. Please correct the issues reported for each line or adjust the lines to skip."));
603 return error_msg.str();
613 std::shared_ptr<DraftTransaction> GncTxImport::trans_properties_to_trans (std::vector<parse_line_t>::iterator& parsed_line)
615 auto created_trans =
false;
616 std::shared_ptr<GncPreSplit> split_props;
617 std::tie(std::ignore, std::ignore, std::ignore, split_props, std::ignore) = *parsed_line;
618 auto trans_props = split_props->get_pre_trans();
619 auto account = split_props->get_account();
621 QofBook* book = gnc_account_get_book (account);
626 auto draft_trans = trans_props->create_trans (book, currency);
633 if (m_current_draft && m_current_draft->void_reason)
641 xaccTransVoid (m_current_draft->trans, m_current_draft->void_reason->c_str());
643 m_current_draft = draft_trans;
644 m_current_draft->void_reason = trans_props->get_void_reason();
645 created_trans =
true;
647 else if (m_settings.m_multi_split)
648 draft_trans = m_current_draft;
650 throw std::invalid_argument (
"Failed to create transaction from selected columns.");
655 split_props->create_split (draft_trans);
661 return created_trans ? m_current_draft :
nullptr;
664 void GncTxImport::create_transaction (std::vector<parse_line_t>::iterator& parsed_line)
667 std::shared_ptr<GncPreSplit> split_props =
nullptr;
668 bool skip_line =
false;
669 std::tie(std::ignore, errors, std::ignore, split_props, skip_line) = *parsed_line;
670 auto trans_props = split_props->get_pre_trans();
678 auto error_message = _(
"Current line still has parse errors.\n" 679 "This should never happen. Please report this as a bug.");
684 auto line_acct = split_props->get_account();
689 auto error_message = _(
"No account column selected and no base account specified either.\n" 690 "This should never happen. Please report this as a bug.");
691 PINFO(
"User warning: %s", error_message);
692 auto errs = ErrMap { ErrPair { GncTransPropType::NONE, error_message},};
700 auto draft_trans = trans_properties_to_trans (parsed_line);
704 m_transactions.insert (std::pair<
time64, std::shared_ptr<DraftTransaction>>(trans_date,std::move(draft_trans)));
707 catch (
const std::invalid_argument& e)
709 auto err_str = _(
"Problem creating preliminary transaction");
710 PINFO(
"%s: %s", err_str, e.what());
711 auto errs = ErrMap { ErrPair { GncTransPropType::NONE, err_str},};
727 auto verify_result = verify (
true);
728 if (!verify_result.empty())
729 throw std::invalid_argument (verify_result);
742 if ((std::get<PL_SKIP>(*parsed_lines_it)))
746 create_transaction (parsed_lines_it);
752 GncTxImport::check_for_column_type (GncTransPropType type)
754 return (std::find (m_settings.m_column_types.begin(),
755 m_settings.m_column_types.end(), type)
756 != m_settings.m_column_types.end());
760 void GncTxImport::update_pre_split_multi_col_prop (
parse_line_t& parsed_line, GncTransPropType col_type)
762 if (!is_multi_col_prop(col_type))
765 auto input_vec = std::get<PL_INPUT>(parsed_line);
766 auto split_props = std::get<PL_PRESPLIT> (parsed_line);
771 for (
auto col_it = m_settings.m_column_types.cbegin();
772 col_it < m_settings.m_column_types.cend();
774 if (*col_it == col_type)
776 auto value = std::string();
777 auto col_num =
static_cast<uint32_t
>(col_it - m_settings.m_column_types.cbegin());
779 if (col_num < input_vec.size())
780 value = input_vec.at(col_num);
781 split_props->add (col_type, value);
785 void GncTxImport::update_pre_trans_props (
parse_line_t& parsed_line, uint32_t col, GncTransPropType old_type, GncTransPropType new_type)
787 auto input_vec = std::get<PL_INPUT>(parsed_line);
788 auto trans_props = std::get<PL_PRETRANS> (parsed_line);
792 trans_props->set_date_format (m_settings.m_date_format);
793 trans_props->set_multi_split (m_settings.m_multi_split);
795 if ((old_type > GncTransPropType::NONE) && (old_type <= GncTransPropType::TRANS_PROPS))
796 trans_props->reset (old_type);
797 if ((new_type > GncTransPropType::NONE) && (new_type <= GncTransPropType::TRANS_PROPS))
799 auto value = std::string();
801 if (col < input_vec.size())
802 value = input_vec.at(col);
804 trans_props->set(new_type, value);
811 if ((old_type == GncTransPropType::ACCOUNT) || (new_type == GncTransPropType::ACCOUNT))
812 trans_props->reset_cross_split_counters();
815 void GncTxImport::update_pre_split_props (
parse_line_t& parsed_line, uint32_t col, GncTransPropType old_type, GncTransPropType new_type)
827 auto split_props = std::get<PL_PRESPLIT> (parsed_line);
828 auto trans_props = std::get<PL_PRETRANS> (parsed_line);
831 split_props->set_date_format (m_settings.m_date_format);
832 if (m_settings.m_multi_split && trans_props->is_part_of( m_parent))
833 split_props->set_pre_trans (m_parent);
836 split_props->set_pre_trans (trans_props);
837 m_parent = trans_props;
840 if ((old_type > GncTransPropType::TRANS_PROPS) && (old_type <= GncTransPropType::SPLIT_PROPS))
842 split_props->reset (old_type);
843 if (is_multi_col_prop(old_type))
844 update_pre_split_multi_col_prop (parsed_line, old_type);
847 if ((new_type > GncTransPropType::TRANS_PROPS) && (new_type <= GncTransPropType::SPLIT_PROPS))
849 if (is_multi_col_prop(new_type))
851 split_props->reset(new_type);
852 update_pre_split_multi_col_prop (parsed_line, new_type);
856 auto input_vec = std::get<PL_INPUT>(parsed_line);
857 auto value = std::string();
858 if (col < input_vec.size())
859 value = input_vec.at(col);
860 split_props->set(new_type, value);
863 m_multi_currency |= split_props->get_pre_trans()->is_multi_currency();
866 auto all_errors = split_props->get_pre_trans()->errors();
867 all_errors.merge (split_props->errors());
868 std::get<PL_ERROR>(parsed_line) = std::move(all_errors);
873 GncTxImport::set_column_type (uint32_t position, GncTransPropType type,
bool force)
875 if (position >= m_settings.m_column_types.size())
878 auto old_type = m_settings.m_column_types[position];
879 if ((type == old_type) && !force)
884 if (!is_multi_col_prop(type))
885 std::replace(m_settings.m_column_types.begin(), m_settings.m_column_types.end(),
886 type, GncTransPropType::NONE);
888 m_settings.m_column_types.at (position) = type;
891 if (type == GncTransPropType::ACCOUNT)
896 m_multi_currency =
false;
899 update_pre_trans_props (parsed_lines_it, position, old_type, type);
900 update_pre_split_props (parsed_lines_it, position, old_type, type);
904 std::vector<GncTransPropType> GncTxImport::column_types ()
906 return m_settings.m_column_types;
909 std::set<std::string>
910 GncTxImport::accounts ()
912 auto accts = std::set<std::string>();
913 auto acct_col_it = std::find (m_settings.m_column_types.begin(),
914 m_settings.m_column_types.end(), GncTransPropType::ACCOUNT);
915 uint32_t acct_col = acct_col_it - m_settings.m_column_types.begin();
916 auto tacct_col_it = std::find (m_settings.m_column_types.begin(),
917 m_settings.m_column_types.end(), GncTransPropType::TACCOUNT);
918 uint32_t tacct_col = tacct_col_it - m_settings.m_column_types.begin();
924 if ((std::get<PL_SKIP>(parsed_line)))
927 auto col_strs = std::get<PL_INPUT>(parsed_line);
928 if ((acct_col_it != m_settings.m_column_types.end()) &&
929 (acct_col < col_strs.size()) &&
930 !col_strs[acct_col].empty())
931 accts.insert(col_strs[acct_col]);
932 if ((tacct_col_it != m_settings.m_column_types.end()) &&
933 (tacct_col < col_strs.size()) &&
934 !col_strs[tacct_col].empty())
935 accts.insert(col_strs[tacct_col]);
gboolean gnc_commodity_is_currency(const gnc_commodity *cm)
Checks to see if the specified commodity is an ISO 4217 recognized currency or a legacy currency...
time64 xaccTransGetDate(const Transaction *trans)
Retrieve the posted date of the transaction.
~GncTxImport()
Destructor for GncTxImport.
bool save(void)
Save the gathered widget properties to a key File.
#define PINFO(format, args...)
Print an informational note.
std::tuple< StrVec, std::string, std::shared_ptr< GncImportPrice >, bool > parse_line_t
Tuple to hold.
void base_account(Account *base_account)
Sets a base account.
Class to convert a csv file into vector of string vectors.
Exception that will be thrown whenever a parsing error is encountered.
Class to import transactions from CSV or fixed width files.
void load_file(const std::string &filename)
Loads a file into a GncTxImport.
GncTxImport(GncImpFileFormat format=GncImpFileFormat::UNKNOWN)
Constructor for GncTxImport.
#define PWARN(format, args...)
Log a warning.
void multi_split(bool multi_split)
Toggles the multi-split state of the importer and will subsequently sanitize the column_types list...
bool preset_is_reserved_name(const std::string &name)
Check whether name can be used as a preset name.
void xaccTransVoid(Transaction *trans, const char *reason)
xaccTransVoid voids a transaction.
std::multimap< time64, std::shared_ptr< DraftTransaction > > m_transactions
map of transaction objects created from parsed_lines and column_types, ordered by date ...
void encoding(const std::string &encoding)
Converts raw file data using a new encoding.
GncImpFileFormat
Enumeration for file formats supported by this importer.
std::unique_ptr< GncTokenizer > m_tokenizer
Will handle file loading/encoding conversion/splitting into fields.
void file_format(GncImpFileFormat format)
Sets the file format for the file to import, which may cause the file to be reloaded as well if the p...
void create_transactions()
This function will attempt to convert all tokenized lines into transactions using the column types th...
void tokenize(bool guessColTypes)
Splits a file into cells.
void xaccTransCommitEdit(Transaction *trans)
The xaccTransCommitEdit() method indicates that the changes to the transaction and its splits are com...
gnc_commodity * gnc_account_get_currency_or_parent(const Account *account)
Returns a gnc_commodity that is a currency, suitable for being a Transaction's currency.
Class convert a file with fixed with delimited contents into vector of string vectors.
gnc_commodity * xaccAccountGetCommodity(const Account *acc)
Get the account's commodity.
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
std::vector< parse_line_t > m_parsed_lines
source file parsed into a two-dimensional array of strings.