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  && !m_settings.m_separators.empty())
119  separators (m_settings.m_separators);
120  else if ((file_format() == GncImpFileFormat::FIXED_WIDTH)
121  && !m_settings.m_column_widths.empty())
122  {
123  auto fwtok = dynamic_cast<GncFwTokenizer*>(m_tokenizer.get());
124  fwtok->columns (m_settings.m_column_widths);
125  }
126 }
127 
128 GncImpFileFormat GncPriceImport::file_format()
129 {
130  return m_settings.m_file_format;
131 }
132 
133 void GncPriceImport::over_write (bool over)
134 {
135  m_over_write = over;
136 }
137 bool GncPriceImport::over_write () { return m_over_write; }
138 
144 void GncPriceImport::from_commodity (gnc_commodity* from_commodity)
145 {
146  m_settings.m_from_commodity = from_commodity;
147  if (m_settings.m_from_commodity)
148  {
149  auto col_type_sym = std::find (m_settings.m_column_types_price.begin(),
150  m_settings.m_column_types_price.end(), GncPricePropType::FROM_SYMBOL);
151 
152  if (col_type_sym != m_settings.m_column_types_price.end())
153  set_column_type_price (col_type_sym -m_settings.m_column_types_price.begin(),
154  GncPricePropType::NONE);
155 
156  auto col_type_name = std::find (m_settings.m_column_types_price.begin(),
157  m_settings.m_column_types_price.end(), GncPricePropType::FROM_NAMESPACE);
158 
159  if (col_type_name != m_settings.m_column_types_price.end())
160  set_column_type_price (col_type_name -m_settings.m_column_types_price.begin(),
161  GncPricePropType::NONE);
162 
163  // force a refresh of the to_currency if the from_commodity is changed
164  std::vector<GncPricePropType> commodities = { GncPricePropType::TO_CURRENCY };
165  reset_formatted_column (commodities);
166  }
167 }
168 gnc_commodity *GncPriceImport::from_commodity () { return m_settings.m_from_commodity; }
169 
175 void GncPriceImport::to_currency (gnc_commodity* to_currency)
176 {
177  m_settings.m_to_currency = to_currency;
178  if (m_settings.m_to_currency)
179  {
180  auto col_type_currency = std::find (m_settings.m_column_types_price.begin(),
181  m_settings.m_column_types_price.end(), GncPricePropType::TO_CURRENCY);
182 
183  if (col_type_currency != m_settings.m_column_types_price.end())
184  set_column_type_price (col_type_currency -m_settings.m_column_types_price.begin(),
185  GncPricePropType::NONE);
186 
187  // force a refresh of the from_commodity if the to_currency is changed
188  // either namespace or symbol will be sufice
189  std::vector<GncPricePropType> commodities = { GncPricePropType::FROM_SYMBOL };
190  reset_formatted_column (commodities);
191  }
192 }
193 gnc_commodity *GncPriceImport::to_currency () { return m_settings.m_to_currency; }
194 
195 void GncPriceImport::reset_formatted_column (std::vector<GncPricePropType>& col_types)
196 {
197  for (auto col_type: col_types)
198  {
199  auto col = std::find (m_settings.m_column_types_price.begin(),
200  m_settings.m_column_types_price.end(), col_type);
201  if (col != m_settings.m_column_types_price.end())
202  set_column_type_price (col - m_settings.m_column_types_price.begin(), col_type, true);
203  }
204 }
205 
206 void GncPriceImport::currency_format (int currency_format)
207 {
208  m_settings.m_currency_format = currency_format;
209 
210  /* Reparse all currency related columns */
211  std::vector<GncPricePropType> commodities = { GncPricePropType::AMOUNT };
212  reset_formatted_column (commodities);
213 }
214 int GncPriceImport::currency_format () { return m_settings.m_currency_format; }
215 
216 void GncPriceImport::date_format (int date_format)
217 {
218  m_settings.m_date_format = date_format;
219 
220  /* Reparse all date related columns */
221  std::vector<GncPricePropType> dates = { GncPricePropType::DATE };
222  reset_formatted_column (dates);
223 }
224 int GncPriceImport::date_format () { return m_settings.m_date_format; }
225 
231 void GncPriceImport::encoding (const std::string& encoding)
232 {
233  // TODO investigate if we can catch conversion errors and report them
234  if (m_tokenizer)
235  {
236  m_tokenizer->encoding(encoding); // May throw
237  try
238  {
239  tokenize(false);
240  }
241  catch (...)
242  { };
243  }
244 
245  m_settings.m_encoding = encoding;
246 }
247 
248 std::string GncPriceImport::encoding () { return m_settings.m_encoding; }
249 
250 void GncPriceImport::update_skipped_lines(std::optional<uint32_t> start, std::optional<uint32_t> end,
251  std::optional<bool> alt, std::optional<bool> errors)
252 {
253  if (start)
254  m_settings.m_skip_start_lines = *start;
255  if (end)
256  m_settings.m_skip_end_lines = *end;
257  if (alt)
258  m_settings.m_skip_alt_lines = *alt;
259  if (errors)
260  m_skip_errors = *errors;
261 
262  for (uint32_t i = 0; i < m_parsed_lines.size(); i++)
263  {
264  std::get<PL_SKIP>(m_parsed_lines[i]) =
265  ((i < skip_start_lines()) || // start rows to skip
266  (i >= m_parsed_lines.size() - skip_end_lines()) || // end rows to skip
267  (((i - skip_start_lines()) % 2 == 1) && // skip every second row...
268  skip_alt_lines()) || // ...if requested
269  (m_skip_errors && !std::get<PL_ERROR>(m_parsed_lines[i]).empty())); // skip lines with errors
270  }
271 }
272 
273 uint32_t GncPriceImport::skip_start_lines () { return m_settings.m_skip_start_lines; }
274 uint32_t GncPriceImport::skip_end_lines () { return m_settings.m_skip_end_lines; }
275 bool GncPriceImport::skip_alt_lines () { return m_settings.m_skip_alt_lines; }
276 bool GncPriceImport::skip_err_lines () { return m_skip_errors; }
277 
278 void GncPriceImport::separators (std::string separators)
279 {
280  if (file_format() != GncImpFileFormat::CSV)
281  return;
282 
283  m_settings.m_separators = separators;
284  auto csvtok = dynamic_cast<GncCsvTokenizer*>(m_tokenizer.get());
285  csvtok->set_separators (separators);
286 
287 }
288 std::string GncPriceImport::separators () { return m_settings.m_separators; }
289 
290 void GncPriceImport::settings (const CsvPriceImpSettings& settings)
291 {
292  /* First apply file format as this may recreate the tokenizer */
293  file_format (settings.m_file_format);
294  /* Only then apply the other settings */
295  m_settings = settings;
296  from_commodity (m_settings.m_from_commodity);
297  to_currency (m_settings.m_to_currency);
298  encoding (m_settings.m_encoding);
299 
300  if (file_format() == GncImpFileFormat::CSV)
301  separators (m_settings.m_separators);
302  else if (file_format() == GncImpFileFormat::FIXED_WIDTH)
303  {
304  auto fwtok = dynamic_cast<GncFwTokenizer*>(m_tokenizer.get());
305  fwtok->columns (m_settings.m_column_widths);
306  }
307  try
308  {
309  tokenize(false);
310  }
311  catch (...)
312  { };
313 
314  /* Tokenizing will clear column types, reset them here
315  * based on the loaded settings.
316  */
317  std::copy_n (settings.m_column_types_price.begin(),
318  std::min (m_settings.m_column_types_price.size(), settings.m_column_types_price.size()),
319  m_settings.m_column_types_price.begin());
320 }
321 
322 bool GncPriceImport::save_settings ()
323 {
324  if (preset_is_reserved_name (m_settings.m_name))
325  return true;
326 
327  /* separators are already copied to m_settings in the separators
328  * function above. However this is not the case for the column
329  * widths in fw mode, so do this now.
330  */
331  if (file_format() == GncImpFileFormat::FIXED_WIDTH)
332  {
333  auto fwtok = dynamic_cast<GncFwTokenizer*>(m_tokenizer.get());
334  m_settings.m_column_widths = fwtok->get_columns();
335  }
336  return m_settings.save();
337 }
338 
339 void GncPriceImport::settings_name (std::string name) { m_settings.m_name = name; }
340 std::string GncPriceImport::settings_name () { return m_settings.m_name; }
341 
348 void GncPriceImport::load_file (const std::string& filename)
349 {
350  /* Get the raw data first and handle an error if one occurs. */
351  try
352  {
353  m_tokenizer->load_file (filename);
354  return;
355  }
356  catch (std::ifstream::failure& ios_err)
357  {
358  // Just log the error and pass it on the call stack for proper handling
359  PWARN ("Error: %s", ios_err.what());
360  throw;
361  }
362 }
363 
375 void GncPriceImport::tokenize (bool guessColTypes)
376 {
377  if (!m_tokenizer)
378  return;
379 
380  uint32_t max_cols = 0;
381  m_tokenizer->tokenize();
382  m_parsed_lines.clear();
383  for (auto tokenized_line : m_tokenizer->get_tokens())
384  {
385  auto length = tokenized_line.size();
386  if (length > 0)
387  m_parsed_lines.push_back (std::make_tuple (tokenized_line, std::string(),
388  std::make_shared<GncImportPrice>(date_format(), currency_format()),
389  false));
390  if (length > max_cols)
391  max_cols = length;
392  }
393 
394  /* If it failed, generate an error. */
395  if (m_parsed_lines.size() == 0)
396  {
397  throw (std::range_error ("Tokenizing failed."));
398  return;
399  }
400 
401  m_settings.m_column_types_price.resize(max_cols, GncPricePropType::NONE);
402 
403  /* Force reinterpretation of already set columns and/or base_account */
404  for (uint32_t i = 0; i < m_settings.m_column_types_price.size(); i++)
405  set_column_type_price (i, m_settings.m_column_types_price[i], true);
406 
407  if (guessColTypes)
408  {
409  /* Guess column_types based
410  * on the contents of each column. */
411  /* TODO Make it actually guess. */
412  }
413 }
414 
416 {
417 public:
418  void add_error (std::string msg);
419  std::string str();
420  bool empty() { return m_error.empty(); }
421 private:
422  std::string m_error;
423 };
424 
425 void ErrorListPrice::add_error (std::string msg)
426 {
427  m_error += "- " + msg + "\n";
428 }
429 
430 std::string ErrorListPrice::str()
431 {
432  return m_error.substr(0, m_error.size() - 1);
433 }
434 
435 /* Test for the required minimum number of columns selected and
436  * the selection is consistent.
437  * @param An ErrorListPrice object to which all found issues are added.
438  */
439 void GncPriceImport::verify_column_selections (ErrorListPrice& error_msg)
440 {
441  /* Verify if a date column is selected and it's parsable.
442  */
443  if (!check_for_column_type(GncPricePropType::DATE))
444  error_msg.add_error( _("Please select a date column."));
445 
446  /* Verify an amount column is selected.
447  */
448  if (!check_for_column_type(GncPricePropType::AMOUNT))
449  error_msg.add_error( _("Please select an amount column."));
450 
451  /* Verify a Currency to column is selected.
452  */
453  if (!check_for_column_type(GncPricePropType::TO_CURRENCY))
454  {
455  if (!m_settings.m_to_currency)
456  error_msg.add_error( _("Please select a 'Currency to' column or set a Currency in the 'Currency To' field."));
457  }
458 
459  /* Verify a From Symbol column is selected.
460  */
461  if (!check_for_column_type(GncPricePropType::FROM_SYMBOL))
462  {
463  if (!m_settings.m_from_commodity)
464  error_msg.add_error( _("Please select a 'From Symbol' column or set a Commodity in the 'Commodity From' field."));
465  }
466 
467  /* Verify a From Namespace column is selected.
468  */
469  if (!check_for_column_type(GncPricePropType::FROM_NAMESPACE))
470  {
471  if (!m_settings.m_from_commodity)
472  error_msg.add_error( _("Please select a 'From Namespace' column or set a Commodity in the 'Commodity From' field."));
473  }
474 
475  /* Verify a 'Commodity from' does not equal 'Currency to'.
476  */
477  if ((m_settings.m_to_currency) && (m_settings.m_from_commodity))
478  {
479  if (gnc_commodity_equal (m_settings.m_to_currency, m_settings.m_from_commodity))
480  error_msg.add_error( _("'Commodity From' can not be the same as 'Currency To'."));
481  }
482 }
483 
484 /* Check whether the chosen settings can successfully parse
485  * the import data. This will check:
486  * - there's at least one line selected for import
487  * - the minimum number of columns is selected
488  * - the values in the selected columns can be parsed meaningfully.
489  * @return An empty string if all checks passed or the reason
490  * verification failed otherwise.
491  */
492 std::string GncPriceImport::verify ()
493 {
494  auto newline = std::string();
495  auto error_msg = ErrorListPrice();
496 
497  /* Check if the import file did actually contain any information */
498  if (m_parsed_lines.size() == 0)
499  {
500  error_msg.add_error(_("No valid data found in the selected file. It may be empty or the selected encoding is wrong."));
501  return error_msg.str();
502  }
503 
504  /* Check if at least one line is selected for importing */
505  auto skip_alt_offset = m_settings.m_skip_alt_lines ? 1 : 0;
506  if (m_settings.m_skip_start_lines + m_settings.m_skip_end_lines + skip_alt_offset >= m_parsed_lines.size())
507  {
508  error_msg.add_error(_("No lines are selected for importing. Please reduce the number of lines to skip."));
509  return error_msg.str();
510  }
511 
512  verify_column_selections (error_msg);
513 
514  update_skipped_lines (std::nullopt, std::nullopt, std::nullopt, std::nullopt);
515 
516  auto have_line_errors = false;
517  for (auto line : m_parsed_lines)
518  {
519  if (!std::get<PL_SKIP>(line) && !std::get<PL_ERROR>(line).empty())
520  {
521  have_line_errors = true;
522  break;
523  }
524  }
525 
526  if (have_line_errors)
527  error_msg.add_error( _("Not all fields could be parsed. Please correct the issues reported for each line or adjust the lines to skip."));
528 
529  return error_msg.str();
530 }
531 
536 static void price_properties_verify_essentials (std::vector<parse_line_t>::iterator& parsed_line)
537 {
538  std::string error_message;
539  std::shared_ptr<GncImportPrice> price_props;
540  std::tie(std::ignore, error_message, price_props, std::ignore) = *parsed_line;
541 
542  auto price_error = price_props->verify_essentials();
543 
544  error_message.clear();
545  if (!price_error.empty())
546  {
547  error_message += price_error;
548  error_message += "\n";
549  }
550 
551  if (!error_message.empty())
552  throw std::invalid_argument(error_message);
553 }
554 
555 void GncPriceImport::create_price (std::vector<parse_line_t>::iterator& parsed_line)
556 {
557  StrVec line;
558  std::string error_message;
559  std::shared_ptr<GncImportPrice> price_props = nullptr;
560  bool skip_line = false;
561  std::tie(line, error_message, price_props, skip_line) = *parsed_line;
562 
563  if (skip_line)
564  return;
565 
566  error_message.clear();
567 
568  // Add a TO_CURRENCY property with the selected 'to_currency' if no 'Currency To' column was set by the user
569  auto line_to_currency = price_props->get_to_currency();
570  if (!line_to_currency)
571  {
572  if (m_settings.m_to_currency)
573  price_props->set_to_currency(m_settings.m_to_currency);
574  else
575  {
576  // Oops - the user didn't select a 'currency to' column *and* we didn't get a selected value either!
577  // Note if you get here this suggests a bug in the code!
578  error_message = _("No 'Currency to' column selected and no selected Currency specified either.\n"
579  "This should never happen. Please report this as a bug.");
580  PINFO("User warning: %s", error_message.c_str());
581  throw std::invalid_argument(error_message);
582  }
583  }
584 
585  // Add a FROM_COMMODITY property with the selected 'from_commodity' if no 'From Namespace/Symbol' columns were set by the user
586  auto line_from_commodity = price_props->get_from_commodity();
587  if (!line_from_commodity)
588  {
589  if (m_settings.m_from_commodity)
590  price_props->set_from_commodity(m_settings.m_from_commodity);
591  else
592  {
593  // Oops - the user didn't select a 'commodity from' column *and* we didn't get a selected value either!
594  // Note if you get here this suggests a bug in the code!
595  error_message = _("No 'From Namespace/From Symbol' columns selected and no selected Commodity From specified either.\n"
596  "This should never happen. Please report this as a bug.");
597  PINFO("User warning: %s", error_message.c_str());
598  throw std::invalid_argument(error_message);
599  }
600  }
601 
602  /* If column parsing was successful, convert price properties into a price. */
603  try
604  {
605  price_properties_verify_essentials (parsed_line);
606 
607  QofBook* book = gnc_get_current_book();
608  GNCPriceDB *pdb = gnc_pricedb_get_db (book);
609 
610  /* If all went well, add this price to the list. */
611  auto price_created = price_props->create_price (book, pdb, m_over_write);
612  if (price_created == ADDED)
613  m_prices_added++;
614  else if (price_created == DUPLICATED)
615  m_prices_duplicated++;
616  else if (price_created == REPLACED)
617  m_prices_replaced++;
618  }
619  catch (const std::invalid_argument& e)
620  {
621  error_message = e.what();
622  PINFO("User warning: %s", error_message.c_str());
623  }
624 }
625 
634 {
635  /* Start with verifying the current data. */
636  auto verify_result = verify();
637  if (!verify_result.empty())
638  throw std::invalid_argument (verify_result);
639 
640  m_prices_added = 0;
641  m_prices_duplicated = 0;
642  m_prices_replaced = 0;
643 
644  /* Iterate over all parsed lines */
645  for (auto parsed_lines_it = m_parsed_lines.begin();
646  parsed_lines_it != m_parsed_lines.end();
647  ++parsed_lines_it)
648  {
649  /* Skip current line if the user specified so */
650  if ((std::get<PL_SKIP>(*parsed_lines_it)))
651  continue;
652 
653  /* Should not throw anymore, otherwise verify needs revision */
654  create_price (parsed_lines_it);
655  }
656  PINFO("Number of lines is %d, added %d, duplicated %d, replaced %d",
657  (int)m_parsed_lines.size(), m_prices_added, m_prices_duplicated, m_prices_replaced);
658 }
659 
660 bool
661 GncPriceImport::check_for_column_type (GncPricePropType type)
662 {
663  return (std::find (m_settings.m_column_types_price.begin(),
664  m_settings.m_column_types_price.end(), type)
665  != m_settings.m_column_types_price.end());
666 }
667 
668 /* A helper function intended to be called only from set_column_type_price */
669 void GncPriceImport::update_price_props (uint32_t row, uint32_t col, GncPricePropType prop_type)
670 {
671  if (prop_type == GncPricePropType::NONE)
672  return; /* Only deal with price related properties. */
673 
674  auto price_props = std::make_shared<GncImportPrice> (*(std::get<PL_PREPRICE>(m_parsed_lines[row])).get());
675 
676  if (col >= std::get<PL_INPUT>(m_parsed_lines[row]).size())
677  price_props->reset (prop_type); //reset errors
678  else
679  {
680  auto value = std::get<PL_INPUT>(m_parsed_lines[row]).at(col);
681  bool enable_test_empty = true;
682  try
683  {
684  // set the from_commodity based on combo so we can test for same.
685  if (prop_type == GncPricePropType::TO_CURRENCY)
686  {
687  if (m_settings.m_from_commodity)
688  price_props->set_from_commodity (m_settings.m_from_commodity);
689 
690  if (m_settings.m_to_currency)
691  enable_test_empty = false;
692  }
693  // set the to_currency based on combo so we can test for same.
694  if (prop_type == GncPricePropType::FROM_SYMBOL)
695  {
696  if (m_settings.m_to_currency)
697  price_props->set_to_currency (m_settings.m_to_currency);
698 
699  if (m_settings.m_from_commodity)
700  enable_test_empty = false;
701  }
702  price_props->set(prop_type, value, enable_test_empty);
703  }
704  catch (const std::exception& e)
705  {
706  /* Do nothing, just prevent the exception from escalating up
707  * However log the error if it happens on a row that's not skipped
708  */
709  if (!std::get<PL_SKIP>(m_parsed_lines[row]))
710  PINFO("User warning: %s", e.what());
711  }
712  }
713  /* Store the result */
714  std::get<PL_PREPRICE>(m_parsed_lines[row]) = price_props;
715 }
716 
717 void
718 GncPriceImport::set_column_type_price (uint32_t position, GncPricePropType type, bool force)
719 {
720  if (position >= m_settings.m_column_types_price.size())
721  return;
722 
723  auto old_type = m_settings.m_column_types_price[position];
724  if ((type == old_type) && !force)
725  return; /* Nothing to do */
726 
727  // Column types should be unique, so remove any previous occurrence of the new type
728  std::replace(m_settings.m_column_types_price.begin(), m_settings.m_column_types_price.end(),
729  type, GncPricePropType::NONE);
730 
731  m_settings.m_column_types_price.at (position) = type;
732 
733  // If the user has set a 'from namespace' column, we can't have a 'commodity from' selected
734  if (type == GncPricePropType::FROM_NAMESPACE)
735  from_commodity (nullptr);
736 
737  // If the user has set a 'from symbol' column, we can't have a 'commodity from' selected
738  if (type == GncPricePropType::FROM_SYMBOL)
739  from_commodity (nullptr);
740 
741  // If the user has set a 'currency to' column, we can't have a 'currency to' selected
742  if (type == GncPricePropType::TO_CURRENCY)
743  to_currency (nullptr);
744 
745  /* Update the preparsed data */
746  for (auto parsed_lines_it = m_parsed_lines.begin();
747  parsed_lines_it != m_parsed_lines.end();
748  ++parsed_lines_it)
749  {
750  /* Reset date and currency formats for each price props object
751  * to ensure column updates use the most recent one
752  */
753  std::get<PL_PREPRICE>(*parsed_lines_it)->set_date_format (m_settings.m_date_format);
754  std::get<PL_PREPRICE>(*parsed_lines_it)->set_currency_format (m_settings.m_currency_format);
755 
756  uint32_t row = parsed_lines_it - m_parsed_lines.begin();
757 
758  /* If the column type actually changed, first reset the property
759  * represented by the old column type
760  */
761  if (old_type != type)
762  {
763  auto old_col = std::get<PL_INPUT>(*parsed_lines_it).size(); // Deliberately out of bounds to trigger a reset!
764  if ((old_type > GncPricePropType::NONE)
765  && (old_type <= GncPricePropType::PRICE_PROPS))
766  update_price_props (row, old_col, old_type);
767  }
768  /* Then set the property represented by the new column type */
769  if ((type > GncPricePropType::NONE)
770  && (type <= GncPricePropType::PRICE_PROPS))
771  update_price_props (row, position, type);
772 
773  /* Report errors if there are any */
774  auto price_errors = std::get<PL_PREPRICE>(*parsed_lines_it)->errors();
775  std::get<PL_ERROR>(*parsed_lines_it) =
776  price_errors +
777  (price_errors.empty() ? std::string() : "\n");
778  }
779 }
780 
781 std::vector<GncPricePropType> GncPriceImport::column_types_price ()
782 {
783  return m_settings.m_column_types_price;
784 }
785 
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...
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.