GnuCash  5.6-150-g038405b370+
gnc-import-price.cpp
1 /********************************************************************\
2  * gnc-import-price.cpp - import prices from csv files *
3  * *
4  * This program is free software; you can redistribute it and/or *
5  * modify it under the terms of the GNU General Public License as *
6  * published by the Free Software Foundation; either version 2 of *
7  * the License, or (at your option) any later version. *
8  * *
9  * This program is distributed in the hope that it will be useful, *
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12  * GNU General Public License for more details. *
13  * *
14  * You should have received a copy of the GNU General Public License*
15  * along with this program; if not, contact: *
16  * *
17  * Free Software Foundation Voice: +1-617-542-5942 *
18  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
19  * Boston, MA 02110-1301, USA gnu@gnu.org *
20  * *
21 \********************************************************************/
22 
23 #include <guid.hpp>
24 
25 #include <platform.h>
26 #if PLATFORM(WINDOWS)
27 #include <windows.h>
28 #endif
29 
30 #include <glib/gi18n.h>
31 
32 #include "gnc-ui-util.h" //get book
33 #include "gnc-commodity.h"
34 #include "gnc-pricedb.h"
35 
36 #include <algorithm>
37 #include <exception>
38 #include <iostream>
39 #include <memory>
40 #include <optional>
41 #include <string>
42 #include <tuple>
43 #include <vector>
44 
45 #include <boost/regex.hpp>
46 #include <boost/regex/icu.hpp>
47 
48 #include "gnc-import-price.hpp"
49 #include "gnc-imp-props-price.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_price = 3;
57 const gchar* currency_format_user_price[] = {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 GncImpFileFormat GncPriceImport::file_format()
132 {
133  return m_settings.m_file_format;
134 }
135 
136 void GncPriceImport::over_write (bool over)
137 {
138  m_over_write = over;
139 }
140 bool GncPriceImport::over_write () { return m_over_write; }
141 
147 void GncPriceImport::from_commodity (gnc_commodity* from_commodity)
148 {
149  m_settings.m_from_commodity = from_commodity;
150  if (m_settings.m_from_commodity)
151  {
152  auto col_type_sym = std::find (m_settings.m_column_types_price.begin(),
153  m_settings.m_column_types_price.end(), GncPricePropType::FROM_SYMBOL);
154 
155  if (col_type_sym != m_settings.m_column_types_price.end())
156  set_column_type_price (col_type_sym -m_settings.m_column_types_price.begin(),
157  GncPricePropType::NONE);
158 
159  auto col_type_name = std::find (m_settings.m_column_types_price.begin(),
160  m_settings.m_column_types_price.end(), GncPricePropType::FROM_NAMESPACE);
161 
162  if (col_type_name != m_settings.m_column_types_price.end())
163  set_column_type_price (col_type_name -m_settings.m_column_types_price.begin(),
164  GncPricePropType::NONE);
165 
166  // force a refresh of the to_currency if the from_commodity is changed
167  std::vector<GncPricePropType> commodities = { GncPricePropType::TO_CURRENCY };
168  reset_formatted_column (commodities);
169  }
170 }
171 gnc_commodity *GncPriceImport::from_commodity () { return m_settings.m_from_commodity; }
172 
178 void GncPriceImport::to_currency (gnc_commodity* to_currency)
179 {
180  m_settings.m_to_currency = to_currency;
181  if (m_settings.m_to_currency)
182  {
183  auto col_type_currency = std::find (m_settings.m_column_types_price.begin(),
184  m_settings.m_column_types_price.end(), GncPricePropType::TO_CURRENCY);
185 
186  if (col_type_currency != m_settings.m_column_types_price.end())
187  set_column_type_price (col_type_currency -m_settings.m_column_types_price.begin(),
188  GncPricePropType::NONE);
189 
190  // force a refresh of the from_commodity if the to_currency is changed
191  // either namespace or symbol will be sufice
192  std::vector<GncPricePropType> commodities = { GncPricePropType::FROM_SYMBOL };
193  reset_formatted_column (commodities);
194  }
195 }
196 gnc_commodity *GncPriceImport::to_currency () { return m_settings.m_to_currency; }
197 
198 void GncPriceImport::reset_formatted_column (std::vector<GncPricePropType>& col_types)
199 {
200  for (auto col_type: col_types)
201  {
202  auto col = std::find (m_settings.m_column_types_price.begin(),
203  m_settings.m_column_types_price.end(), col_type);
204  if (col != m_settings.m_column_types_price.end())
205  set_column_type_price (col - m_settings.m_column_types_price.begin(), col_type, true);
206  }
207 }
208 
209 void GncPriceImport::currency_format (int currency_format)
210 {
211  m_settings.m_currency_format = currency_format;
212 
213  /* Reparse all currency related columns */
214  std::vector<GncPricePropType> commodities = { GncPricePropType::AMOUNT };
215  reset_formatted_column (commodities);
216 }
217 int GncPriceImport::currency_format () { return m_settings.m_currency_format; }
218 
219 void GncPriceImport::date_format (int date_format)
220 {
221  m_settings.m_date_format = date_format;
222 
223  /* Reparse all date related columns */
224  std::vector<GncPricePropType> dates = { GncPricePropType::DATE };
225  reset_formatted_column (dates);
226 }
227 int GncPriceImport::date_format () { return m_settings.m_date_format; }
228 
234 void GncPriceImport::encoding (const std::string& encoding)
235 {
236  // TODO investigate if we can catch conversion errors and report them
237  if (m_tokenizer)
238  {
239  m_tokenizer->encoding(encoding); // May throw
240  try
241  {
242  tokenize(false);
243  }
244  catch (...)
245  { };
246  }
247 
248  m_settings.m_encoding = encoding;
249 }
250 
251 std::string GncPriceImport::encoding () { return m_settings.m_encoding; }
252 
253 void GncPriceImport::update_skipped_lines(std::optional<uint32_t> start, std::optional<uint32_t> end,
254  std::optional<bool> alt, std::optional<bool> errors)
255 {
256  if (start)
257  m_settings.m_skip_start_lines = *start;
258  if (end)
259  m_settings.m_skip_end_lines = *end;
260  if (alt)
261  m_settings.m_skip_alt_lines = *alt;
262  if (errors)
263  m_skip_errors = *errors;
264 
265  for (uint32_t i = 0; i < m_parsed_lines.size(); i++)
266  {
267  std::get<PL_SKIP>(m_parsed_lines[i]) =
268  ((i < skip_start_lines()) || // start rows to skip
269  (i >= m_parsed_lines.size() - skip_end_lines()) || // end rows to skip
270  (((i - skip_start_lines()) % 2 == 1) && // skip every second row...
271  skip_alt_lines()) || // ...if requested
272  (m_skip_errors && !std::get<PL_ERROR>(m_parsed_lines[i]).empty())); // skip lines with errors
273  }
274 }
275 
276 uint32_t GncPriceImport::skip_start_lines () { return m_settings.m_skip_start_lines; }
277 uint32_t GncPriceImport::skip_end_lines () { return m_settings.m_skip_end_lines; }
278 bool GncPriceImport::skip_alt_lines () { return m_settings.m_skip_alt_lines; }
279 bool GncPriceImport::skip_err_lines () { return m_skip_errors; }
280 
281 void GncPriceImport::separators (std::string separators)
282 {
283  if (file_format() != GncImpFileFormat::CSV)
284  return;
285 
286  m_settings.m_separators = separators;
287  auto csvtok = dynamic_cast<GncCsvTokenizer*>(m_tokenizer.get());
288  csvtok->set_separators (separators);
289 
290 }
291 std::string GncPriceImport::separators () { return m_settings.m_separators; }
292 
293 void GncPriceImport::enable_escape(bool enable)
294 {
295  if (file_format() != GncImpFileFormat::CSV)
296  return;
297  m_settings.m_enable_escape = enable;
298  auto csvtok = dynamic_cast<GncCsvTokenizer*>(m_tokenizer.get());
299  csvtok->set_enable_escape (enable);
300 }
301 bool GncPriceImport::enable_escape() { return m_settings.m_enable_escape; }
302 
303 void GncPriceImport::settings (const CsvPriceImpSettings& settings)
304 {
305  /* First apply file format as this may recreate the tokenizer */
306  file_format (settings.m_file_format);
307  /* Only then apply the other settings */
308  m_settings = settings;
309  from_commodity (m_settings.m_from_commodity);
310  to_currency (m_settings.m_to_currency);
311  encoding (m_settings.m_encoding);
312 
313  if (file_format() == GncImpFileFormat::CSV)
314  separators (m_settings.m_separators);
315  else if (file_format() == GncImpFileFormat::FIXED_WIDTH)
316  {
317  auto fwtok = dynamic_cast<GncFwTokenizer*>(m_tokenizer.get());
318  fwtok->columns (m_settings.m_column_widths);
319  }
320  try
321  {
322  tokenize(false);
323  }
324  catch (...)
325  { };
326 
327  /* Tokenizing will clear column types, reset them here
328  * based on the loaded settings.
329  */
330  std::copy_n (settings.m_column_types_price.begin(),
331  std::min (m_settings.m_column_types_price.size(), settings.m_column_types_price.size()),
332  m_settings.m_column_types_price.begin());
333 }
334 
335 bool GncPriceImport::save_settings ()
336 {
337  if (preset_is_reserved_name (m_settings.m_name))
338  return true;
339 
340  /* separators are already copied to m_settings in the separators
341  * function above. However this is not the case for the column
342  * widths in fw mode, so do this now.
343  */
344  if (file_format() == GncImpFileFormat::FIXED_WIDTH)
345  {
346  auto fwtok = dynamic_cast<GncFwTokenizer*>(m_tokenizer.get());
347  m_settings.m_column_widths = fwtok->get_columns();
348  }
349  return m_settings.save();
350 }
351 
352 void GncPriceImport::settings_name (std::string name) { m_settings.m_name = name; }
353 std::string GncPriceImport::settings_name () { return m_settings.m_name; }
354 
361 void GncPriceImport::load_file (const std::string& filename)
362 {
363  /* Get the raw data first and handle an error if one occurs. */
364  try
365  {
366  m_tokenizer->load_file (filename);
367  return;
368  }
369  catch (std::ifstream::failure& ios_err)
370  {
371  // Just log the error and pass it on the call stack for proper handling
372  PWARN ("Error: %s", ios_err.what());
373  throw;
374  }
375 }
376 
388 void GncPriceImport::tokenize (bool guessColTypes)
389 {
390  if (!m_tokenizer)
391  return;
392 
393  uint32_t max_cols = 0;
394  m_tokenizer->tokenize();
395  m_parsed_lines.clear();
396  for (auto tokenized_line : m_tokenizer->get_tokens())
397  {
398  auto length = tokenized_line.size();
399  if (length > 0)
400  m_parsed_lines.push_back (std::make_tuple (tokenized_line, std::string(),
401  std::make_shared<GncImportPrice>(date_format(), currency_format()),
402  false));
403  if (length > max_cols)
404  max_cols = length;
405  }
406 
407  /* If it failed, generate an error. */
408  if (m_parsed_lines.size() == 0)
409  {
410  throw (std::range_error ("Tokenizing failed."));
411  return;
412  }
413 
414  m_settings.m_column_types_price.resize(max_cols, GncPricePropType::NONE);
415 
416  /* Force reinterpretation of already set columns and/or base_account */
417  for (uint32_t i = 0; i < m_settings.m_column_types_price.size(); i++)
418  set_column_type_price (i, m_settings.m_column_types_price[i], true);
419 
420  if (guessColTypes)
421  {
422  /* Guess column_types based
423  * on the contents of each column. */
424  /* TODO Make it actually guess. */
425  }
426 }
427 
429 {
430 public:
431  void add_error (std::string msg);
432  std::string str();
433  bool empty() { return m_error.empty(); }
434 private:
435  std::string m_error;
436 };
437 
438 void ErrorListPrice::add_error (std::string msg)
439 {
440  m_error += "- " + msg + "\n";
441 }
442 
443 std::string ErrorListPrice::str()
444 {
445  return m_error.substr(0, m_error.size() - 1);
446 }
447 
448 /* Test for the required minimum number of columns selected and
449  * the selection is consistent.
450  * @param An ErrorListPrice object to which all found issues are added.
451  */
452 void GncPriceImport::verify_column_selections (ErrorListPrice& error_msg)
453 {
454  /* Verify if a date column is selected and it's parsable.
455  */
456  if (!check_for_column_type(GncPricePropType::DATE))
457  error_msg.add_error( _("Please select a date column."));
458 
459  /* Verify an amount column is selected.
460  */
461  if (!check_for_column_type(GncPricePropType::AMOUNT))
462  error_msg.add_error( _("Please select an amount column."));
463 
464  /* Verify a Currency to column is selected.
465  */
466  if (!check_for_column_type(GncPricePropType::TO_CURRENCY))
467  {
468  if (!m_settings.m_to_currency)
469  error_msg.add_error( _("Please select a 'Currency to' column or set a Currency in the 'Currency To' field."));
470  }
471 
472  /* Verify a From Symbol column is selected.
473  */
474  if (!check_for_column_type(GncPricePropType::FROM_SYMBOL))
475  {
476  if (!m_settings.m_from_commodity)
477  error_msg.add_error( _("Please select a 'From Symbol' column or set a Commodity in the 'Commodity From' field."));
478  }
479 
480  /* Verify a From Namespace column is selected.
481  */
482  if (!check_for_column_type(GncPricePropType::FROM_NAMESPACE))
483  {
484  if (!m_settings.m_from_commodity)
485  error_msg.add_error( _("Please select a 'From Namespace' column or set a Commodity in the 'Commodity From' field."));
486  }
487 
488  /* Verify a 'Commodity from' does not equal 'Currency to'.
489  */
490  if ((m_settings.m_to_currency) && (m_settings.m_from_commodity))
491  {
492  if (gnc_commodity_equal (m_settings.m_to_currency, m_settings.m_from_commodity))
493  error_msg.add_error( _("'Commodity From' can not be the same as 'Currency To'."));
494  }
495 }
496 
497 /* Check whether the chosen settings can successfully parse
498  * the import data. This will check:
499  * - there's at least one line selected for import
500  * - the minimum number of columns is selected
501  * - the values in the selected columns can be parsed meaningfully.
502  * @return An empty string if all checks passed or the reason
503  * verification failed otherwise.
504  */
505 std::string GncPriceImport::verify ()
506 {
507  auto newline = std::string();
508  auto error_msg = ErrorListPrice();
509 
510  /* Check if the import file did actually contain any information */
511  if (m_parsed_lines.size() == 0)
512  {
513  error_msg.add_error(_("No valid data found in the selected file. It may be empty or the selected encoding is wrong."));
514  return error_msg.str();
515  }
516 
517  /* Check if at least one line is selected for importing */
518  auto skip_alt_offset = m_settings.m_skip_alt_lines ? 1 : 0;
519  if (m_settings.m_skip_start_lines + m_settings.m_skip_end_lines + skip_alt_offset >= m_parsed_lines.size())
520  {
521  error_msg.add_error(_("No lines are selected for importing. Please reduce the number of lines to skip."));
522  return error_msg.str();
523  }
524 
525  verify_column_selections (error_msg);
526 
527  update_skipped_lines (std::nullopt, std::nullopt, std::nullopt, std::nullopt);
528 
529  auto have_line_errors = false;
530  for (auto line : m_parsed_lines)
531  {
532  if (!std::get<PL_SKIP>(line) && !std::get<PL_ERROR>(line).empty())
533  {
534  have_line_errors = true;
535  break;
536  }
537  }
538 
539  if (have_line_errors)
540  error_msg.add_error( _("Not all fields could be parsed. Please correct the issues reported for each line or adjust the lines to skip."));
541 
542  return error_msg.str();
543 }
544 
549 static void price_properties_verify_essentials (std::vector<parse_line_t>::iterator& parsed_line)
550 {
551  std::string error_message;
552  std::shared_ptr<GncImportPrice> price_props;
553  std::tie(std::ignore, error_message, price_props, std::ignore) = *parsed_line;
554 
555  auto price_error = price_props->verify_essentials();
556 
557  error_message.clear();
558  if (!price_error.empty())
559  {
560  error_message += price_error;
561  error_message += "\n";
562  }
563 
564  if (!error_message.empty())
565  throw std::invalid_argument(error_message);
566 }
567 
568 void GncPriceImport::create_price (std::vector<parse_line_t>::iterator& parsed_line)
569 {
570  StrVec line;
571  std::string error_message;
572  std::shared_ptr<GncImportPrice> price_props = nullptr;
573  bool skip_line = false;
574  std::tie(line, error_message, price_props, skip_line) = *parsed_line;
575 
576  if (skip_line)
577  return;
578 
579  error_message.clear();
580 
581  // Add a TO_CURRENCY property with the selected 'to_currency' if no 'Currency To' column was set by the user
582  auto line_to_currency = price_props->get_to_currency();
583  if (!line_to_currency)
584  {
585  if (m_settings.m_to_currency)
586  price_props->set_to_currency(m_settings.m_to_currency);
587  else
588  {
589  // Oops - the user didn't select a 'currency to' column *and* we didn't get a selected value either!
590  // Note if you get here this suggests a bug in the code!
591  error_message = _("No 'Currency to' column selected and no selected Currency specified either.\n"
592  "This should never happen. Please report this as a bug.");
593  PINFO("User warning: %s", error_message.c_str());
594  throw std::invalid_argument(error_message);
595  }
596  }
597 
598  // Add a FROM_COMMODITY property with the selected 'from_commodity' if no 'From Namespace/Symbol' columns were set by the user
599  auto line_from_commodity = price_props->get_from_commodity();
600  if (!line_from_commodity)
601  {
602  if (m_settings.m_from_commodity)
603  price_props->set_from_commodity(m_settings.m_from_commodity);
604  else
605  {
606  // Oops - the user didn't select a 'commodity from' column *and* we didn't get a selected value either!
607  // Note if you get here this suggests a bug in the code!
608  error_message = _("No 'From Namespace/From Symbol' columns selected and no selected Commodity From specified either.\n"
609  "This should never happen. Please report this as a bug.");
610  PINFO("User warning: %s", error_message.c_str());
611  throw std::invalid_argument(error_message);
612  }
613  }
614 
615  /* If column parsing was successful, convert price properties into a price. */
616  try
617  {
618  price_properties_verify_essentials (parsed_line);
619 
620  QofBook* book = gnc_get_current_book();
621  GNCPriceDB *pdb = gnc_pricedb_get_db (book);
622 
623  /* If all went well, add this price to the list. */
624  auto price_created = price_props->create_price (book, pdb, m_over_write);
625  if (price_created == ADDED)
626  m_prices_added++;
627  else if (price_created == DUPLICATED)
628  m_prices_duplicated++;
629  else if (price_created == REPLACED)
630  m_prices_replaced++;
631  }
632  catch (const std::invalid_argument& e)
633  {
634  error_message = e.what();
635  PINFO("User warning: %s", error_message.c_str());
636  }
637 }
638 
647 {
648  /* Start with verifying the current data. */
649  auto verify_result = verify();
650  if (!verify_result.empty())
651  throw std::invalid_argument (verify_result);
652 
653  m_prices_added = 0;
654  m_prices_duplicated = 0;
655  m_prices_replaced = 0;
656 
657  /* Iterate over all parsed lines */
658  for (auto parsed_lines_it = m_parsed_lines.begin();
659  parsed_lines_it != m_parsed_lines.end();
660  ++parsed_lines_it)
661  {
662  /* Skip current line if the user specified so */
663  if ((std::get<PL_SKIP>(*parsed_lines_it)))
664  continue;
665 
666  /* Should not throw anymore, otherwise verify needs revision */
667  create_price (parsed_lines_it);
668  }
669  PINFO("Number of lines is %d, added %d, duplicated %d, replaced %d",
670  (int)m_parsed_lines.size(), m_prices_added, m_prices_duplicated, m_prices_replaced);
671 }
672 
673 bool
674 GncPriceImport::check_for_column_type (GncPricePropType type)
675 {
676  return (std::find (m_settings.m_column_types_price.begin(),
677  m_settings.m_column_types_price.end(), type)
678  != m_settings.m_column_types_price.end());
679 }
680 
681 /* A helper function intended to be called only from set_column_type_price */
682 void GncPriceImport::update_price_props (uint32_t row, uint32_t col, GncPricePropType prop_type)
683 {
684  if (prop_type == GncPricePropType::NONE)
685  return; /* Only deal with price related properties. */
686 
687  auto price_props = std::make_shared<GncImportPrice> (*(std::get<PL_PREPRICE>(m_parsed_lines[row])).get());
688 
689  if (col >= std::get<PL_INPUT>(m_parsed_lines[row]).size())
690  price_props->reset (prop_type); //reset errors
691  else
692  {
693  auto value = std::get<PL_INPUT>(m_parsed_lines[row]).at(col);
694  bool enable_test_empty = true;
695  try
696  {
697  // set the from_commodity based on combo so we can test for same.
698  if (prop_type == GncPricePropType::TO_CURRENCY)
699  {
700  if (m_settings.m_from_commodity)
701  price_props->set_from_commodity (m_settings.m_from_commodity);
702 
703  if (m_settings.m_to_currency)
704  enable_test_empty = false;
705  }
706  // set the to_currency based on combo so we can test for same.
707  if (prop_type == GncPricePropType::FROM_SYMBOL)
708  {
709  if (m_settings.m_to_currency)
710  price_props->set_to_currency (m_settings.m_to_currency);
711 
712  if (m_settings.m_from_commodity)
713  enable_test_empty = false;
714  }
715  price_props->set(prop_type, value, enable_test_empty);
716  }
717  catch (const std::exception& e)
718  {
719  /* Do nothing, just prevent the exception from escalating up
720  * However log the error if it happens on a row that's not skipped
721  */
722  if (!std::get<PL_SKIP>(m_parsed_lines[row]))
723  PINFO("User warning: %s", e.what());
724  }
725  }
726  /* Store the result */
727  std::get<PL_PREPRICE>(m_parsed_lines[row]) = price_props;
728 }
729 
730 void
731 GncPriceImport::set_column_type_price (uint32_t position, GncPricePropType type, bool force)
732 {
733  if (position >= m_settings.m_column_types_price.size())
734  return;
735 
736  auto old_type = m_settings.m_column_types_price[position];
737  if ((type == old_type) && !force)
738  return; /* Nothing to do */
739 
740  // Column types should be unique, so remove any previous occurrence of the new type
741  std::replace(m_settings.m_column_types_price.begin(), m_settings.m_column_types_price.end(),
742  type, GncPricePropType::NONE);
743 
744  m_settings.m_column_types_price.at (position) = type;
745 
746  // If the user has set a 'from namespace' column, we can't have a 'commodity from' selected
747  if (type == GncPricePropType::FROM_NAMESPACE)
748  from_commodity (nullptr);
749 
750  // If the user has set a 'from symbol' column, we can't have a 'commodity from' selected
751  if (type == GncPricePropType::FROM_SYMBOL)
752  from_commodity (nullptr);
753 
754  // If the user has set a 'currency to' column, we can't have a 'currency to' selected
755  if (type == GncPricePropType::TO_CURRENCY)
756  to_currency (nullptr);
757 
758  /* Update the preparsed data */
759  for (auto parsed_lines_it = m_parsed_lines.begin();
760  parsed_lines_it != m_parsed_lines.end();
761  ++parsed_lines_it)
762  {
763  /* Reset date and currency formats for each price props object
764  * to ensure column updates use the most recent one
765  */
766  std::get<PL_PREPRICE>(*parsed_lines_it)->set_date_format (m_settings.m_date_format);
767  std::get<PL_PREPRICE>(*parsed_lines_it)->set_currency_format (m_settings.m_currency_format);
768 
769  uint32_t row = parsed_lines_it - m_parsed_lines.begin();
770 
771  /* If the column type actually changed, first reset the property
772  * represented by the old column type
773  */
774  if (old_type != type)
775  {
776  auto old_col = std::get<PL_INPUT>(*parsed_lines_it).size(); // Deliberately out of bounds to trigger a reset!
777  if ((old_type > GncPricePropType::NONE)
778  && (old_type <= GncPricePropType::PRICE_PROPS))
779  update_price_props (row, old_col, old_type);
780  }
781  /* Then set the property represented by the new column type */
782  if ((type > GncPricePropType::NONE)
783  && (type <= GncPricePropType::PRICE_PROPS))
784  update_price_props (row, position, type);
785 
786  /* Report errors if there are any */
787  auto price_errors = std::get<PL_PREPRICE>(*parsed_lines_it)->errors();
788  std::get<PL_ERROR>(*parsed_lines_it) =
789  price_errors +
790  (price_errors.empty() ? std::string() : "\n");
791  }
792 }
793 
794 std::vector<GncPricePropType> GncPriceImport::column_types_price ()
795 {
796  return m_settings.m_column_types_price;
797 }
798 
void create_prices()
This function will attempt to convert all tokenized lines into prices using the column types the user...
GncPriceImport(GncImpFileFormat format=GncImpFileFormat::UNKNOWN)
Constructor for GncPriceImport.
a simple price database for gnucash
~GncPriceImport()
Destructor for GncPriceImport.
utility functions for the GnuCash UI
#define PINFO(format, args...)
Print an informational note.
Definition: qoflog.h:256
void tokenize(bool guessColTypes)
Splits a file into cells.
Class to convert a csv file into vector of string vectors.
gboolean gnc_commodity_equal(const gnc_commodity *a, const gnc_commodity *b)
This routine returns TRUE if the two commodities are equal.
std::unique_ptr< GncTokenizer > m_tokenizer
Will handle file loading/encoding conversion/splitting into fields.
std::vector< parse_line_t > m_parsed_lines
source file parsed into a two-dimensional array of strings.
GNCPriceDB * gnc_pricedb_get_db(QofBook *book)
Return the pricedb associated with the book.
#define PWARN(format, args...)
Log a warning.
Definition: qoflog.h:250
void to_currency(gnc_commodity *to_currency)
Sets a to currency.
bool preset_is_reserved_name(const std::string &name)
Check whether name can be used as a preset name.
GncImpFileFormat
Enumeration for file formats supported by this importer.
void from_commodity(gnc_commodity *from_commodity)
Sets a from commodity.
Class to import prices from CSV or fixed width files.
Class convert a file with fixed with delimited contents into vector of string vectors.
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...
QofBook reference.
Definition: qofbook-p.hpp:46
void load_file(const std::string &filename)
Loads a file into a GncPriceImport.
void encoding(const std::string &encoding)
Converts raw file data using a new encoding.
bool save(void)
Save the gathered widget properties to a key File.
Commodity handling public routines.
CSV Import Settings.