GnuCash  5.6-150-g038405b370+
gnc-import-tx.cpp
1 /********************************************************************\
2  * gnc-import-tx.cpp - import transactions from csv or fixed-width *
3  * files *
4  * *
5  * This program is free software; you can redistribute it and/or *
6  * modify it under the terms of the GNU General Public License as *
7  * published by the Free Software Foundation; either version 2 of *
8  * the License, or (at your option) any later version. *
9  * *
10  * This program is distributed in the hope that it will be useful, *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13  * GNU General Public License for more details. *
14  * *
15  * You should have received a copy of the GNU General Public License*
16  * along with this program; if not, contact: *
17  * *
18  * Free Software Foundation Voice: +1-617-542-5942 *
19  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
20  * Boston, MA 02110-1301, USA gnu@gnu.org *
21  * *
22 \********************************************************************/
23 
24 #include <guid.hpp>
25 
26 #include <platform.h>
27 #if PLATFORM(WINDOWS)
28 #include <windows.h>
29 #endif
30 
31 #include <glib/gi18n.h>
32 
33 #include <algorithm>
34 #include <exception>
35 #include <iostream>
36 #include <map>
37 #include <memory>
38 #include <numeric>
39 #include <optional>
40 #include <string>
41 #include <tuple>
42 #include <utility>
43 #include <vector>
44 
45 #include <boost/regex.hpp>
46 #include <boost/regex/icu.hpp>
47 
48 #include "gnc-import-tx.hpp"
49 #include "gnc-imp-props-tx.hpp"
50 #include "gnc-tokenizer-csv.hpp"
51 #include "gnc-tokenizer-fw.hpp"
53 
54 G_GNUC_UNUSED static QofLogModule log_module = GNC_MOD_IMPORT;
55 
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")
60  };
61 
62 
66 {
67  /* All of the data pointers are initially NULL. This is so that, if
68  * gnc_csv_parse_data_free is called before all of the data is
69  * initialized, only the data that needs to be freed is freed. */
70  m_skip_errors = false;
71  file_format(m_settings.m_file_format = format);
72 }
73 
77 {
78 }
79 
88 {
89  if (m_tokenizer && m_settings.m_file_format == format)
90  return;
91 
92  auto new_encoding = std::string("UTF-8");
93  auto new_imp_file = std::string();
94 
95  // Recover common settings from old tokenizer
96  if (m_tokenizer)
97  {
98  new_encoding = m_tokenizer->encoding();
99  new_imp_file = m_tokenizer->current_file();
100  if (file_format() == GncImpFileFormat::FIXED_WIDTH)
101  {
102  auto fwtok = dynamic_cast<GncFwTokenizer*>(m_tokenizer.get());
103  if (!fwtok->get_columns().empty())
104  m_settings.m_column_widths = fwtok->get_columns();
105  }
106  }
107 
108  m_settings.m_file_format = format;
109  m_tokenizer = gnc_tokenizer_factory(m_settings.m_file_format);
110 
111  // Set up new tokenizer with common settings
112  // recovered from old tokenizer
113  m_tokenizer->encoding(new_encoding);
114  load_file(new_imp_file);
115 
116  // Restore potentially previously set separators or column_widths
117  if (file_format() == GncImpFileFormat::CSV)
118  {
119  if (!m_settings.m_separators.empty())
120  separators (m_settings.m_separators);
121  enable_escape (m_settings.m_enable_escape);
122  }
123  else if ((file_format() == GncImpFileFormat::FIXED_WIDTH)
124  && !m_settings.m_column_widths.empty())
125  {
126  auto fwtok = dynamic_cast<GncFwTokenizer*>(m_tokenizer.get());
127  fwtok->columns (m_settings.m_column_widths);
128  }
129 
130 }
131 
132 GncImpFileFormat GncTxImport::file_format()
133 {
134  return m_settings.m_file_format;
135 }
136 
148 void GncTxImport::multi_split (bool multi_split)
149 {
150  auto trans_prop_seen = false;
151  m_settings.m_multi_split = multi_split;
152  for (uint32_t i = 0; i < m_settings.m_column_types.size(); i++)
153  {
154  auto old_prop = m_settings.m_column_types[i];
155  auto is_trans_prop = ((old_prop > GncTransPropType::NONE)
156  && (old_prop <= GncTransPropType::TRANS_PROPS));
157  auto san_prop = sanitize_trans_prop (old_prop, m_settings.m_multi_split);
158  if (san_prop != old_prop)
159  set_column_type (i, san_prop);
160  else if (is_trans_prop && !trans_prop_seen)
161  set_column_type (i, old_prop, true);
162  trans_prop_seen |= is_trans_prop;
163 
164  }
165  if (m_settings.m_multi_split)
166  m_settings.m_base_account = nullptr;
167 }
168 
169 bool GncTxImport::multi_split () { return m_settings.m_multi_split; }
170 
180 void GncTxImport::base_account (Account* base_account)
181 {
182  if (m_settings.m_multi_split)
183  {
184  m_settings.m_base_account = nullptr;
185  return;
186  }
187 
188  auto base_account_is_new = m_settings.m_base_account == nullptr;
189  m_settings.m_base_account = base_account;
190 
191  if (m_settings.m_base_account)
192  {
193  auto col_type_it = std::find (m_settings.m_column_types.begin(),
194  m_settings.m_column_types.end(), GncTransPropType::ACCOUNT);
195  if (col_type_it != m_settings.m_column_types.end())
196  set_column_type(col_type_it - m_settings.m_column_types.begin(),
197  GncTransPropType::NONE);
198 
199  if (base_account_is_new)
200  {
201  /* Set default account for each line's split properties */
202  for (auto line : m_parsed_lines)
203  std::get<PL_PRESPLIT>(line)->set_account (m_settings.m_base_account);
204  }
205 
206  /* Reparse all of the lines with the new base account's commodity. */
207  tokenize(false);
208  }
209 }
210 
211 Account *GncTxImport::base_account () { return m_settings.m_base_account; }
212 
213 void GncTxImport::reset_formatted_column (std::vector<GncTransPropType>& col_types)
214 {
215  for (auto col_type: col_types)
216  {
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);
221  }
222 
223  /* Reparse all lines */
224  tokenize(false);
225 }
226 
227 void GncTxImport::currency_format (int currency_format)
228 {
229  m_settings.m_currency_format = currency_format;
230 
231  /* Reparse all currency related columns */
232  std::vector<GncTransPropType> commodities = {
233  GncTransPropType::AMOUNT,
234  GncTransPropType::AMOUNT_NEG,
235  GncTransPropType::TAMOUNT,
236  GncTransPropType::TAMOUNT_NEG,
237  GncTransPropType::PRICE};
238  reset_formatted_column (commodities);
239 }
240 int GncTxImport::currency_format () { return m_settings.m_currency_format; }
241 
242 void GncTxImport::date_format (int date_format)
243 {
244  m_settings.m_date_format = date_format;
245 
246  /* Reparse all date related columns */
247  std::vector<GncTransPropType> dates = { GncTransPropType::DATE,
248  GncTransPropType::REC_DATE,
249  GncTransPropType::TREC_DATE};
250  reset_formatted_column (dates);
251 }
252 int GncTxImport::date_format () { return m_settings.m_date_format; }
253 
259 void GncTxImport::encoding (const std::string& encoding)
260 {
261 
262  // TODO investigate if we can catch conversion errors and report them
263  if (m_tokenizer)
264  {
265  m_tokenizer->encoding(encoding); // May throw
266  try
267  {
268  tokenize(false);
269  }
270  catch (...)
271  { };
272  }
273 
274  m_settings.m_encoding = encoding;
275 }
276 
277 std::string GncTxImport::encoding () { return m_settings.m_encoding; }
278 
279 void GncTxImport::update_skipped_lines(std::optional<uint32_t> start, std::optional<uint32_t> end,
280  std::optional<bool> alt, std::optional<bool> errors)
281 {
282  if (start)
283  m_settings.m_skip_start_lines = *start;
284  if (end)
285  m_settings.m_skip_end_lines = *end;
286  if (alt)
287  m_settings.m_skip_alt_lines = *alt;
288  if (errors)
289  m_skip_errors = *errors;
290 
291  for (uint32_t i = 0; i < m_parsed_lines.size(); i++)
292  {
293  std::get<PL_SKIP>(m_parsed_lines[i]) =
294  ((i < skip_start_lines()) || // start rows to skip
295  (i >= m_parsed_lines.size() - skip_end_lines()) || // end rows to skip
296  (((i - skip_start_lines()) % 2 == 1) && // skip every second row...
297  skip_alt_lines()) || // ...if requested
298  (m_skip_errors && !std::get<PL_ERROR>(m_parsed_lines[i]).empty())); // skip lines with errors
299  }
300 }
301 
302 uint32_t GncTxImport::skip_start_lines () { return m_settings.m_skip_start_lines; }
303 uint32_t GncTxImport::skip_end_lines () { return m_settings.m_skip_end_lines; }
304 bool GncTxImport::skip_alt_lines () { return m_settings.m_skip_alt_lines; }
305 bool GncTxImport::skip_err_lines () { return m_skip_errors; }
306 
307 void GncTxImport::separators (std::string separators)
308 {
309  if (file_format() != GncImpFileFormat::CSV)
310  return;
311 
312  m_settings.m_separators = separators;
313  auto csvtok = dynamic_cast<GncCsvTokenizer*>(m_tokenizer.get());
314  csvtok->set_separators (separators);
315 
316 }
317 std::string GncTxImport::separators () { return m_settings.m_separators; }
318 
319 void GncTxImport::enable_escape(bool enable)
320 {
321  if (file_format() != GncImpFileFormat::CSV)
322  return;
323  m_settings.m_enable_escape = enable;
324  auto csvtok = dynamic_cast<GncCsvTokenizer*>(m_tokenizer.get());
325  csvtok->set_enable_escape (enable);
326 }
327 bool GncTxImport::enable_escape() { return m_settings.m_enable_escape; }
328 
329 void GncTxImport::settings (const CsvTransImpSettings& settings)
330 {
331  /* First apply file format as this may recreate the tokenizer */
332  file_format (settings.m_file_format);
333  /* Only then apply the other settings */
334  m_settings = settings;
335  multi_split (m_settings.m_multi_split);
336  base_account (m_settings.m_base_account);
337  encoding (m_settings.m_encoding);
338 
339  if (file_format() == GncImpFileFormat::CSV)
340  {
341  separators (m_settings.m_separators);
342  enable_escape (m_settings.m_enable_escape);
343  }
344  else if (file_format() == GncImpFileFormat::FIXED_WIDTH)
345  {
346  auto fwtok = dynamic_cast<GncFwTokenizer*>(m_tokenizer.get());
347  fwtok->columns (m_settings.m_column_widths);
348  }
349  try
350  {
351  tokenize(false);
352  }
353  catch (...)
354  { };
355 
356  /* Tokenizing will clear column types, reset them here
357  * based on the loaded settings.
358  */
359  std::copy_n (settings.m_column_types.begin(),
360  std::min (m_settings.m_column_types.size(), settings.m_column_types.size()),
361  m_settings.m_column_types.begin());
362 
363 }
364 
365 bool GncTxImport::save_settings ()
366 {
367 
368  if (preset_is_reserved_name (m_settings.m_name))
369  return true;
370 
371  /* separators are already copied to m_settings in the separators
372  * function above. However this is not the case for the column
373  * widths in fw mode, so do this now.
374  */
375  if (file_format() == GncImpFileFormat::FIXED_WIDTH)
376  {
377  auto fwtok = dynamic_cast<GncFwTokenizer*>(m_tokenizer.get());
378  m_settings.m_column_widths = fwtok->get_columns();
379  }
380 
381  return m_settings.save();
382 }
383 
384 void GncTxImport::settings_name (std::string name) { m_settings.m_name = name; }
385 std::string GncTxImport::settings_name () { return m_settings.m_name; }
386 
393 void GncTxImport::load_file (const std::string& filename)
394 {
395 
396  /* Get the raw data first and handle an error if one occurs. */
397  try
398  {
399  m_tokenizer->load_file (filename);
400  return;
401  }
402  catch (std::ifstream::failure& ios_err)
403  {
404  // Just log the error and pass it on the call stack for proper handling
405  PWARN ("Error: %s", ios_err.what());
406  throw;
407  }
408 }
409 
421 void GncTxImport::tokenize (bool guessColTypes)
422 {
423  if (!m_tokenizer)
424  return;
425 
426  uint32_t max_cols = 0;
427  m_tokenizer->tokenize();
428  m_parsed_lines.clear();
429  for (auto tokenized_line : m_tokenizer->get_tokens())
430  {
431  auto length = tokenized_line.size();
432  if (length > 0)
433  {
434  auto pretrans = std::make_shared<GncPreTrans>(date_format(), m_settings.m_multi_split);
435  auto presplit = std::make_shared<GncPreSplit>(date_format(), currency_format());
436  presplit->set_pre_trans (std::move (pretrans));
437  m_parsed_lines.push_back (std::make_tuple (tokenized_line, ErrMap(),
438  presplit->get_pre_trans(), std::move (presplit), false));
439  }
440  if (length > max_cols)
441  max_cols = length;
442  }
443 
444  /* If it failed, generate an error. */
445  if (m_parsed_lines.size() == 0)
446  {
447  throw (std::range_error (N_("There was an error parsing the file.")));
448  return;
449  }
450 
451  m_settings.m_column_types.resize(max_cols, GncTransPropType::NONE);
452 
453  /* Force reinterpretation of already set columns and/or base_account */
454  for (uint32_t i = 0; i < m_settings.m_column_types.size(); i++)
455  set_column_type (i, m_settings.m_column_types[i], true);
456  if (m_settings.m_base_account)
457  {
458  for (auto line : m_parsed_lines)
459  std::get<PL_PRESPLIT>(line)->set_account (m_settings.m_base_account);
460  }
461 
462  if (guessColTypes)
463  {
464  /* Guess column_types based
465  * on the contents of each column. */
466  /* TODO Make it actually guess. */
467  }
468 }
469 
470 
471 struct ErrorList
472 {
473 public:
474  void add_error (std::string msg);
475  std::string str();
476 private:
477  StrVec m_error;
478 };
479 
480 void ErrorList::add_error (std::string msg)
481 {
482  m_error.emplace_back (msg);
483 }
484 
485 std::string ErrorList::str()
486 {
487  auto err_msg = std::string();
488  if (!m_error.empty())
489  {
490  auto add_bullet_item = [](std::string& a, std::string& b)->std::string { return std::move(a) + "\n• " + b; };
491  err_msg = std::accumulate (m_error.begin(), m_error.end(), std::move (err_msg), add_bullet_item);
492  err_msg.erase (0, 1);
493  }
494 
495  return err_msg;
496 }
497 
498 
499 /* Test for the required minimum number of columns selected and
500  * the selection is consistent.
501  * @param An ErrorList object to which all found issues are added.
502  */
503 void GncTxImport::verify_column_selections (ErrorList& error_msg)
504 {
505 
506  /* Verify if a date column is selected and it's parsable.
507  */
508  if (!check_for_column_type(GncTransPropType::DATE))
509  error_msg.add_error( _("Please select a date column."));
510 
511  /* Verify if an account is selected either in the base account selector
512  * or via a column in the import data.
513  */
514  if (!check_for_column_type(GncTransPropType::ACCOUNT))
515  {
516  if (m_settings.m_multi_split)
517  error_msg.add_error( _("Please select an account column."));
518  else if (!m_settings.m_base_account)
519  error_msg.add_error( _("Please select an account column or set a base account in the Account field."));
520  }
521 
522  /* Verify a description column is selected.
523  */
524  if (!check_for_column_type(GncTransPropType::DESCRIPTION))
525  error_msg.add_error( _("Please select a description column."));
526 
527  /* Verify at least one amount column (amount or amount_neg) column is selected.
528  */
529  if (!check_for_column_type(GncTransPropType::AMOUNT) &&
530  !check_for_column_type(GncTransPropType::AMOUNT_NEG))
531  error_msg.add_error( _("Please select a (negated) amount column."));
532 
533  /* In multisplit mode and where current account selections imply multi-
534  * currency transactions, we require extra columns to ensure each split is
535  * fully defined.
536  * Note this check only involves splits created by the csv importer
537  * code. The generic import matcher may add a balancing split
538  * optionally using Transfer <something> properties. The generic
539  * import matcher has its own tools to balance that split so
540  * we won't concern ourselves with that one here.
541  */
542  if (m_multi_currency)
543  {
544  if (m_settings.m_multi_split &&
545  !check_for_column_type(GncTransPropType::PRICE) &&
546  !check_for_column_type(GncTransPropType::VALUE) &&
547  !check_for_column_type(GncTransPropType::VALUE_NEG))
548  error_msg.add_error( _("The current account selections will generate multi-currency transactions. Please select one of the following columns: price, (negated) value."));
549  else if (!m_settings.m_multi_split &&
550  !check_for_column_type(GncTransPropType::PRICE) &&
551  !check_for_column_type(GncTransPropType::TAMOUNT) &&
552  !check_for_column_type(GncTransPropType::TAMOUNT_NEG) &&
553  !check_for_column_type(GncTransPropType::VALUE) &&
554  !check_for_column_type(GncTransPropType::VALUE_NEG))
555  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."));
556  }
557 }
558 
559 
560 /* Check whether the chosen settings can successfully parse
561  * the import data. This will check:
562  * - there's at least one line selected for import
563  * - the minimum number of columns is selected
564  * - the values in the selected columns can be parsed meaningfully.
565  * @return An empty string if all checks passed or the reason
566  * verification failed otherwise.
567  */
568 std::string GncTxImport::verify (bool with_acct_errors)
569 {
570  auto newline = std::string();
571  auto error_msg = ErrorList();
572 
573  /* Check if the import file did actually contain any information */
574  if (m_parsed_lines.size() == 0)
575  {
576  error_msg.add_error(_("No valid data found in the selected file. It may be empty or the selected encoding is wrong."));
577  return error_msg.str();
578  }
579 
580  /* Check if at least one line is selected for importing */
581  auto skip_alt_offset = m_settings.m_skip_alt_lines ? 1 : 0;
582  if (m_settings.m_skip_start_lines + m_settings.m_skip_end_lines + skip_alt_offset >= m_parsed_lines.size())
583  {
584  error_msg.add_error(_("No lines are selected for importing. Please reduce the number of lines to skip."));
585  return error_msg.str();
586  }
587 
588  verify_column_selections (error_msg);
589 
590  update_skipped_lines (std::nullopt, std::nullopt, std::nullopt, std::nullopt);
591 
592  auto have_line_errors = false;
593  for (auto line : m_parsed_lines)
594  {
595  auto errors = std::get<PL_ERROR>(line);
596  if (std::get<PL_SKIP>(line))
597  continue;
598  if (with_acct_errors && !errors.empty())
599  {
600  have_line_errors = true;
601  break;
602  }
603  auto non_acct_error = [](ErrPair curr_err)
604  {
605  return !((curr_err.first == GncTransPropType::ACCOUNT) ||
606  (curr_err.first == GncTransPropType::TACCOUNT));
607  };
608  if (!with_acct_errors &&
609  std::any_of(errors.cbegin(), errors.cend(), non_acct_error))
610  {
611  have_line_errors = true;
612  break;
613  }
614  }
615 
616  if (have_line_errors)
617  error_msg.add_error( _("Not all fields could be parsed. Please correct the issues reported for each line or adjust the lines to skip."));
618 
619  return error_msg.str();
620 }
621 
622 
629 std::shared_ptr<DraftTransaction> GncTxImport::trans_properties_to_trans (std::vector<parse_line_t>::iterator& parsed_line)
630 {
631  auto created_trans = false;
632  std::shared_ptr<GncPreSplit> split_props;
633  std::tie(std::ignore, std::ignore, std::ignore, split_props, std::ignore) = *parsed_line;
634  auto trans_props = split_props->get_pre_trans();
635  auto account = split_props->get_account();
636 
637  QofBook* book = gnc_account_get_book (account);
638  gnc_commodity* currency = xaccAccountGetCommodity (account);
639  if (!gnc_commodity_is_currency(currency))
640  currency = gnc_account_get_currency_or_parent (account);
641 
642  auto draft_trans = trans_props->create_trans (book, currency);
643 
644  if (draft_trans)
645  {
646  /* We're about to continue with a new transaction
647  * Time to do some closing actions on the previous one
648  */
649  if (m_current_draft && m_current_draft->void_reason)
650  {
651  /* The import data specifies this transaction was voided.
652  * So void the created transaction as well.
653  * Attention: this assumes the imported transaction was balanced.
654  * If not, this will cause an imbalance split to be added automatically!
655  */
656  xaccTransCommitEdit (m_current_draft->trans);
657  xaccTransVoid (m_current_draft->trans, m_current_draft->void_reason->c_str());
658  }
659  m_current_draft = draft_trans;
660  m_current_draft->void_reason = trans_props->get_void_reason();
661  created_trans = true;
662  }
663  else if (m_settings.m_multi_split) // in multi_split mode create_trans will return a nullptr for all but the first split
664  draft_trans = m_current_draft;
665  else // in non-multi-split mode each line should be a transaction, so not having one here is an error
666  throw std::invalid_argument ("Failed to create transaction from selected columns.");
667 
668  if (!draft_trans)
669  return nullptr;
670 
671  split_props->create_split (draft_trans);
672 
673  /* Only return the draft transaction if we really created a new one
674  * The return value will be added to a list for further processing,
675  * we want each transaction to appear only once in that list.
676  */
677  return created_trans ? m_current_draft : nullptr;
678 }
679 
680 void GncTxImport::create_transaction (std::vector<parse_line_t>::iterator& parsed_line)
681 {
682  ErrMap errors;
683  std::shared_ptr<GncPreSplit> split_props = nullptr;
684  bool skip_line = false;
685  std::tie(std::ignore, errors, std::ignore, split_props, skip_line) = *parsed_line;
686  auto trans_props = split_props->get_pre_trans();
687 
688  if (skip_line)
689  return;
690 
691  // We still have errors for this line. That shouldn't happen!
692  if(!errors.empty())
693  {
694  auto error_message = _("Current line still has parse errors.\n"
695  "This should never happen. Please report this as a bug.");
696  throw GncCsvImpParseError(error_message, errors);
697  }
698 
699  // Add an ACCOUNT property with the default account if no account column was set by the user
700  auto line_acct = split_props->get_account();
701  if (!line_acct)
702  {
703  // Oops - the user didn't select an Account column *and* we didn't get a default value either!
704  // Note if you get here this suggests a bug in the code!
705  auto error_message = _("No account column selected and no base account specified either.\n"
706  "This should never happen. Please report this as a bug.");
707  PINFO("User warning: %s", error_message);
708  auto errs = ErrMap { ErrPair { GncTransPropType::NONE, error_message},};
709  throw GncCsvImpParseError(_("Parse Error"), errs);
710  }
711 
712  /* If column parsing was successful, convert trans properties into a draft transaction. */
713  try
714  {
715  /* If all went well, add this transaction to the list. */
716  auto draft_trans = trans_properties_to_trans (parsed_line);
717  if (draft_trans)
718  {
719  auto trans_date = xaccTransGetDate (draft_trans->trans);
720  m_transactions.insert (std::pair<time64, std::shared_ptr<DraftTransaction>>(trans_date,std::move(draft_trans)));
721  }
722  }
723  catch (const std::invalid_argument& e)
724  {
725  auto err_str = _("Problem creating preliminary transaction");
726  PINFO("%s: %s", err_str, e.what());
727  auto errs = ErrMap { ErrPair { GncTransPropType::NONE, err_str},};
728  throw (GncCsvImpParseError(err_str, errs));
729  }
730 }
731 
732 
741 {
742  /* Start with verifying the current data. */
743  auto verify_result = verify (true);
744  if (!verify_result.empty())
745  throw std::invalid_argument (verify_result);
746 
747  /* Drop all existing draft transactions */
748  m_transactions.clear();
749 
750  m_parent = nullptr;
751 
752  /* Iterate over all parsed lines */
753  for (auto parsed_lines_it = m_parsed_lines.begin();
754  parsed_lines_it != m_parsed_lines.end();
755  ++parsed_lines_it)
756  {
757  /* Skip current line if the user specified so */
758  if ((std::get<PL_SKIP>(*parsed_lines_it)))
759  continue;
760 
761  /* Should not throw anymore, otherwise verify needs revision */
762  create_transaction (parsed_lines_it);
763  }
764 }
765 
766 
767 bool
768 GncTxImport::check_for_column_type (GncTransPropType type)
769 {
770  return (std::find (m_settings.m_column_types.begin(),
771  m_settings.m_column_types.end(), type)
772  != m_settings.m_column_types.end());
773 }
774 
775 
776 void GncTxImport::update_pre_split_multi_col_prop (parse_line_t& parsed_line, GncTransPropType col_type)
777 {
778  if (!is_multi_col_prop(col_type))
779  return;
780 
781  auto input_vec = std::get<PL_INPUT>(parsed_line);
782  auto split_props = std::get<PL_PRESPLIT> (parsed_line);
783 
784  /* All amount columns may appear more than once. The net amount
785  * needs to be recalculated rather than just reset if one column
786  * is unset. */
787  for (auto col_it = m_settings.m_column_types.cbegin();
788  col_it < m_settings.m_column_types.cend();
789  col_it++)
790  if (*col_it == col_type)
791  {
792  auto value = std::string();
793  auto col_num = static_cast<uint32_t>(col_it - m_settings.m_column_types.cbegin());
794 
795  if (col_num < input_vec.size())
796  value = input_vec.at(col_num);
797  split_props->add (col_type, value);
798  }
799 }
800 
801 void GncTxImport::update_pre_trans_props (parse_line_t& parsed_line, uint32_t col, GncTransPropType old_type, GncTransPropType new_type)
802 {
803  auto input_vec = std::get<PL_INPUT>(parsed_line);
804  auto trans_props = std::get<PL_PRETRANS> (parsed_line);
805 
806  /* Reset date format for each trans props object
807  * to ensure column updates use the most recent one */
808  trans_props->set_date_format (m_settings.m_date_format);
809  trans_props->set_multi_split (m_settings.m_multi_split);
810 
811  if ((old_type > GncTransPropType::NONE) && (old_type <= GncTransPropType::TRANS_PROPS))
812  trans_props->reset (old_type);
813  if ((new_type > GncTransPropType::NONE) && (new_type <= GncTransPropType::TRANS_PROPS))
814  {
815  auto value = std::string();
816 
817  if (col < input_vec.size())
818  value = input_vec.at(col);
819 
820  trans_props->set(new_type, value);
821  }
822 
823  /* In the trans_props we also keep track of currencies/commodities for further
824  * multi-currency checks. These come from a PreSplit's account property.
825  * If that's the property that we are about to modify, the current
826  * counters should be reset. */
827  if ((old_type == GncTransPropType::ACCOUNT) || (new_type == GncTransPropType::ACCOUNT))
828  trans_props->reset_cross_split_counters();
829 }
830 
831 void GncTxImport::update_pre_split_props (parse_line_t& parsed_line, uint32_t col, GncTransPropType old_type, GncTransPropType new_type)
832 {
833  /* With multi-split input data this line may be part of a transaction
834  * that has already been started by a previous parsed line.
835  * If so
836  * - set the GncPreTrans from that previous line (which we track
837  * in m_parent) as this GncPreSplit's pre_trans.
838  * In all other cases
839  * - set the GncPreTrans that's unique to this line
840  * as this GncPreSplit's pre_trans
841  * - mark it as the new potential m_parent for subsequent lines.
842  */
843  auto split_props = std::get<PL_PRESPLIT> (parsed_line);
844  auto trans_props = std::get<PL_PRETRANS> (parsed_line);
845  /* Reset date format for each split props object
846  * to ensure column updates use the most recent one */
847  split_props->set_date_format (m_settings.m_date_format);
848  if (m_settings.m_multi_split && trans_props->is_part_of( m_parent))
849  split_props->set_pre_trans (m_parent);
850  else
851  {
852  split_props->set_pre_trans (trans_props);
853  m_parent = trans_props;
854  }
855 
856  if ((old_type > GncTransPropType::TRANS_PROPS) && (old_type <= GncTransPropType::SPLIT_PROPS))
857  {
858  split_props->reset (old_type);
859  if (is_multi_col_prop(old_type))
860  update_pre_split_multi_col_prop (parsed_line, old_type);
861  }
862 
863  if ((new_type > GncTransPropType::TRANS_PROPS) && (new_type <= GncTransPropType::SPLIT_PROPS))
864  {
865  if (is_multi_col_prop(new_type))
866  {
867  split_props->reset(new_type);
868  update_pre_split_multi_col_prop (parsed_line, new_type);
869  }
870  else
871  {
872  auto input_vec = std::get<PL_INPUT>(parsed_line);
873  auto value = std::string();
874  if (col < input_vec.size())
875  value = input_vec.at(col);
876  split_props->set(new_type, value);
877  }
878  }
879  m_multi_currency |= split_props->get_pre_trans()->is_multi_currency();
880 
881  /* Collect errors from this line's GncPreSplit and its embedded GncPreTrans */
882  auto all_errors = split_props->get_pre_trans()->errors();
883  all_errors.merge (split_props->errors());
884  std::get<PL_ERROR>(parsed_line) = std::move(all_errors);
885 }
886 
887 
888 void
889 GncTxImport::set_column_type (uint32_t position, GncTransPropType type, bool force)
890 {
891  if (position >= m_settings.m_column_types.size())
892  return;
893 
894  auto old_type = m_settings.m_column_types[position];
895  if ((type == old_type) && !force)
896  return; /* Nothing to do */
897 
898  // Column types except amount and negated amount should be unique,
899  // so remove any previous occurrence of the new type
900  if (!is_multi_col_prop(type))
901  std::replace(m_settings.m_column_types.begin(), m_settings.m_column_types.end(),
902  type, GncTransPropType::NONE);
903 
904  m_settings.m_column_types.at (position) = type;
905 
906  // If the user has set an Account column, we can't have a base account set
907  if (type == GncTransPropType::ACCOUNT)
908  base_account (nullptr);
909 
910  /* Update the preparsed data */
911  m_parent = nullptr;
912  m_multi_currency = false;
913  for (auto& parsed_lines_it: m_parsed_lines)
914  {
915  update_pre_trans_props (parsed_lines_it, position, old_type, type);
916  update_pre_split_props (parsed_lines_it, position, old_type, type);
917  }
918 }
919 
920 std::vector<GncTransPropType> GncTxImport::column_types ()
921 {
922  return m_settings.m_column_types;
923 }
924 
925 std::set<std::string>
926 GncTxImport::accounts ()
927 {
928  auto accts = std::set<std::string>();
929  auto acct_col_it = std::find (m_settings.m_column_types.begin(),
930  m_settings.m_column_types.end(), GncTransPropType::ACCOUNT);
931  uint32_t acct_col = acct_col_it - m_settings.m_column_types.begin();
932  auto tacct_col_it = std::find (m_settings.m_column_types.begin(),
933  m_settings.m_column_types.end(), GncTransPropType::TACCOUNT);
934  uint32_t tacct_col = tacct_col_it - m_settings.m_column_types.begin();
935 
936  /* Iterate over all parsed lines */
937  for (auto parsed_line : m_parsed_lines)
938  {
939  /* Skip current line if the user specified so */
940  if ((std::get<PL_SKIP>(parsed_line)))
941  continue;
942 
943  auto col_strs = std::get<PL_INPUT>(parsed_line);
944  if ((acct_col_it != m_settings.m_column_types.end()) &&
945  (acct_col < col_strs.size()) &&
946  !col_strs[acct_col].empty())
947  accts.insert(col_strs[acct_col]);
948  if ((tacct_col_it != m_settings.m_column_types.end()) &&
949  (tacct_col < col_strs.size()) &&
950  !col_strs[tacct_col].empty())
951  accts.insert(col_strs[tacct_col]);
952  }
953 
954  return accts;
955 }
CSV Import Settings.
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.
Definition: qoflog.h:256
std::tuple< StrVec, std::string, std::shared_ptr< GncImportPrice >, bool > parse_line_t
Tuple to hold.
STRUCTS.
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.
Definition: qoflog.h:250
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&#39;s currency.
Definition: Account.cpp:3415
Class convert a file with fixed with delimited contents into vector of string vectors.
QofBook reference.
Definition: qofbook-p.hpp:46
gnc_commodity * xaccAccountGetCommodity(const Account *acc)
Get the account&#39;s commodity.
Definition: Account.cpp:3408
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition: gnc-date.h:87
std::vector< parse_line_t > m_parsed_lines
source file parsed into a two-dimensional array of strings.