GnuCash c935c2f+
Loading...
Searching...
No Matches
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
54G_GNUC_UNUSED static QofLogModule log_module = GNC_MOD_IMPORT;
55
56const int num_currency_formats = 3;
57const 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
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
132GncImpFileFormat GncTxImport::file_format()
133{
134 return m_settings.m_file_format;
135}
136
148void 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
169bool GncTxImport::multi_split () { return m_settings.m_multi_split; }
170
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
211Account *GncTxImport::base_account () { return m_settings.m_base_account; }
212
213void 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
227void 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}
240int GncTxImport::currency_format () { return m_settings.m_currency_format; }
241
242void 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}
252int GncTxImport::date_format () { return m_settings.m_date_format; }
253
259void 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
277std::string GncTxImport::encoding () { return m_settings.m_encoding; }
278
279void 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
302uint32_t GncTxImport::skip_start_lines () { return m_settings.m_skip_start_lines; }
303uint32_t GncTxImport::skip_end_lines () { return m_settings.m_skip_end_lines; }
304bool GncTxImport::skip_alt_lines () { return m_settings.m_skip_alt_lines; }
305bool GncTxImport::skip_err_lines () { return m_skip_errors; }
306
307void 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}
317std::string GncTxImport::separators () { return m_settings.m_separators; }
318
319void 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}
327bool GncTxImport::enable_escape() { return m_settings.m_enable_escape; }
328
329void 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
365bool 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
384void GncTxImport::settings_name (std::string name) { m_settings.m_name = name; }
385std::string GncTxImport::settings_name () { return m_settings.m_name; }
386
393void 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
421void 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
472{
473public:
474 void add_error (std::string msg);
475 std::string str();
476private:
477 StrVec m_error;
478};
479
480void ErrorList::add_error (std::string msg)
481{
482 m_error.emplace_back (msg);
483}
484
485std::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 */
503void 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 */
568std::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
629std::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
680void 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
767bool
768GncTxImport::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
776void 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
801void 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
831void 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
888void
889GncTxImport::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
920std::vector<GncTransPropType> GncTxImport::column_types ()
921{
922 return m_settings.m_column_types;
923}
924
925std::set<std::string>
926GncTxImport::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}
~GncTxImport()
Destructor for GncTxImport.
void tokenize(bool guessColTypes)
Splits a file into cells.
void create_transactions()
This function will attempt to convert all tokenized lines into transactions using the column types th...
void encoding(const std::string &encoding)
Converts raw file data using a new encoding.
std::unique_ptr< GncTokenizer > m_tokenizer
Will handle file loading/encoding conversion/splitting into fields.
void multi_split(bool multi_split)
Toggles the multi-split state of the importer and will subsequently sanitize the column_types list.
std::vector< parse_line_t > m_parsed_lines
source file parsed into a two-dimensional array of strings.
void load_file(const std::string &filename)
Loads a file into a GncTxImport.
GncTxImport(GncImpFileFormat format=GncImpFileFormat::UNKNOWN)
Constructor for GncTxImport.
std::multimap< time64, std::shared_ptr< DraftTransaction > > m_transactions
map of transaction objects created from parsed_lines and column_types, ordered by date
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 base_account(Account *base_account)
Sets a base account.
CSV Import Settings.
bool preset_is_reserved_name(const std::string &name)
Check whether name can be used as a preset name.
std::tuple< StrVec, std::string, std::shared_ptr< GncImportPrice >, bool > parse_line_t
Tuple to hold.
Class to import transactions from CSV or fixed width files.
Class to convert a csv file into vector of string vectors.
Class convert a file with fixed with delimited contents into vector of string vectors.
GncImpFileFormat
Enumeration for file formats supported by this importer.
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.
Definition Account.cpp:3415
gnc_commodity * xaccAccountGetCommodity(const Account *acc)
Get the account's commodity
Definition Account.cpp:3408
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.
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition gnc-date.h:87
void xaccTransCommitEdit(Transaction *trans)
The xaccTransCommitEdit() method indicates that the changes to the transaction and its splits are com...
void xaccTransVoid(Transaction *trans, const char *reason)
xaccTransVoid voids a transaction.
time64 xaccTransGetDate(const Transaction *trans)
Retrieve the posted date of the transaction.
#define PINFO(format, args...)
Print an informational note.
Definition qoflog.h:256
#define PWARN(format, args...)
Log a warning.
Definition qoflog.h:250
STRUCTS.
bool save(void)
Save the gathered widget properties to a key File.
Exception that will be thrown whenever a parsing error is encountered.
QofBook reference.
Definition qofbook-p.hpp:47