GnuCash c935c2f+
Loading...
Searching...
No Matches
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
54G_GNUC_UNUSED static QofLogModule log_module = GNC_MOD_IMPORT;
55
56const int num_currency_formats_price = 3;
57const 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
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
131GncImpFileFormat GncPriceImport::file_format()
132{
133 return m_settings.m_file_format;
134}
135
136void GncPriceImport::over_write (bool over)
137{
138 m_over_write = over;
139}
140bool GncPriceImport::over_write () { return m_over_write; }
141
147void 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}
171gnc_commodity *GncPriceImport::from_commodity () { return m_settings.m_from_commodity; }
172
178void 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}
196gnc_commodity *GncPriceImport::to_currency () { return m_settings.m_to_currency; }
197
198void 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
209void 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}
217int GncPriceImport::currency_format () { return m_settings.m_currency_format; }
218
219void 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}
227int GncPriceImport::date_format () { return m_settings.m_date_format; }
228
234void 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
251std::string GncPriceImport::encoding () { return m_settings.m_encoding; }
252
253void 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
276uint32_t GncPriceImport::skip_start_lines () { return m_settings.m_skip_start_lines; }
277uint32_t GncPriceImport::skip_end_lines () { return m_settings.m_skip_end_lines; }
278bool GncPriceImport::skip_alt_lines () { return m_settings.m_skip_alt_lines; }
279bool GncPriceImport::skip_err_lines () { return m_skip_errors; }
280
281void 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}
291std::string GncPriceImport::separators () { return m_settings.m_separators; }
292
293void 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}
301bool GncPriceImport::enable_escape() { return m_settings.m_enable_escape; }
302
303void 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
335bool 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
352void GncPriceImport::settings_name (std::string name) { m_settings.m_name = name; }
353std::string GncPriceImport::settings_name () { return m_settings.m_name; }
354
361void 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
388void 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{
430public:
431 void add_error (std::string msg);
432 std::string str();
433 bool empty() { return m_error.empty(); }
434private:
435 std::string m_error;
436};
437
438void ErrorListPrice::add_error (std::string msg)
439{
440 m_error += "- " + msg + "\n";
441}
442
443std::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 */
452void 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 */
505std::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
549static 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
568void 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
673bool
674GncPriceImport::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 */
682void 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
730void
731GncPriceImport::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
794std::vector<GncPricePropType> GncPriceImport::column_types_price ()
795{
796 return m_settings.m_column_types_price;
797}
798
void tokenize(bool guessColTypes)
Splits a file into cells.
void to_currency(gnc_commodity *to_currency)
Sets a to currency.
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.
void from_commodity(gnc_commodity *from_commodity)
Sets a from commodity.
std::unique_ptr< GncTokenizer > m_tokenizer
Will handle file loading/encoding conversion/splitting into fields.
void create_prices()
This function will attempt to convert all tokenized lines into prices using the column types the user...
~GncPriceImport()
Destructor for GncPriceImport.
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...
GncPriceImport(GncImpFileFormat format=GncImpFileFormat::UNKNOWN)
Constructor for GncPriceImport.
std::vector< parse_line_t > m_parsed_lines
source file parsed into a two-dimensional array of strings.
Commodity handling public routines.
CSV Import Settings.
bool preset_is_reserved_name(const std::string &name)
Check whether name can be used as a preset name.
Class to import prices from CSV or fixed width files.
a simple price database for gnucash
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.
utility functions for the GnuCash UI
gboolean gnc_commodity_equal(const gnc_commodity *a, const gnc_commodity *b)
This routine returns TRUE if the two commodities are equal.
#define PINFO(format, args...)
Print an informational note.
Definition qoflog.h:256
#define PWARN(format, args...)
Log a warning.
Definition qoflog.h:250
GNCPriceDB * gnc_pricedb_get_db(QofBook *book)
Return the pricedb associated with the book.
bool save(void)
Save the gathered widget properties to a key File.
QofBook reference.
Definition qofbook-p.hpp:47