30 #include <glib/gi18n.h> 45 #include <boost/regex.hpp> 46 #include <boost/regex/icu.hpp> 49 #include "gnc-imp-props-price.hpp" 54 G_GNUC_UNUSED
static QofLogModule log_module = GNC_MOD_IMPORT;
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")
70 m_skip_errors =
false;
89 if (
m_tokenizer && m_settings.m_file_format == format)
92 auto new_encoding = std::string(
"UTF-8");
93 auto new_imp_file = std::string();
100 if (
file_format() == GncImpFileFormat::FIXED_WIDTH)
103 if (!fwtok->get_columns().empty())
104 m_settings.m_column_widths = fwtok->get_columns();
108 m_settings.m_file_format = format;
109 m_tokenizer = gnc_tokenizer_factory(m_settings.m_file_format);
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())
124 fwtok->columns (m_settings.m_column_widths);
130 return m_settings.m_file_format;
133 void GncPriceImport::over_write (
bool over)
137 bool GncPriceImport::over_write () {
return m_over_write; }
146 m_settings.m_from_commodity = from_commodity;
147 if (m_settings.m_from_commodity)
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);
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);
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);
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);
164 std::vector<GncPricePropType> commodities = { GncPricePropType::TO_CURRENCY };
165 reset_formatted_column (commodities);
168 gnc_commodity *GncPriceImport::from_commodity () {
return m_settings.m_from_commodity; }
177 m_settings.m_to_currency = to_currency;
178 if (m_settings.m_to_currency)
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);
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);
189 std::vector<GncPricePropType> commodities = { GncPricePropType::FROM_SYMBOL };
190 reset_formatted_column (commodities);
193 gnc_commodity *GncPriceImport::to_currency () {
return m_settings.m_to_currency; }
195 void GncPriceImport::reset_formatted_column (std::vector<GncPricePropType>& col_types)
197 for (
auto col_type: col_types)
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);
206 void GncPriceImport::currency_format (
int currency_format)
208 m_settings.m_currency_format = currency_format;
211 std::vector<GncPricePropType> commodities = { GncPricePropType::AMOUNT };
212 reset_formatted_column (commodities);
214 int GncPriceImport::currency_format () {
return m_settings.m_currency_format; }
216 void GncPriceImport::date_format (
int date_format)
218 m_settings.m_date_format = date_format;
221 std::vector<GncPricePropType> dates = { GncPricePropType::DATE };
222 reset_formatted_column (dates);
224 int GncPriceImport::date_format () {
return m_settings.m_date_format; }
245 m_settings.m_encoding = encoding;
248 std::string GncPriceImport::encoding () {
return m_settings.m_encoding; }
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)
254 m_settings.m_skip_start_lines = *start;
256 m_settings.m_skip_end_lines = *end;
258 m_settings.m_skip_alt_lines = *alt;
260 m_skip_errors = *errors;
265 ((i < skip_start_lines()) ||
267 (((i - skip_start_lines()) % 2 == 1) &&
269 (m_skip_errors && !std::get<PL_ERROR>(
m_parsed_lines[i]).empty()));
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; }
278 void GncPriceImport::separators (std::string separators)
283 m_settings.m_separators = separators;
285 csvtok->set_separators (separators);
288 std::string GncPriceImport::separators () {
return m_settings.m_separators; }
295 m_settings = settings;
301 separators (m_settings.m_separators);
302 else if (
file_format() == GncImpFileFormat::FIXED_WIDTH)
305 fwtok->columns (m_settings.m_column_widths);
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());
322 bool GncPriceImport::save_settings ()
331 if (
file_format() == GncImpFileFormat::FIXED_WIDTH)
334 m_settings.m_column_widths = fwtok->get_columns();
336 return m_settings.
save();
339 void GncPriceImport::settings_name (std::string name) { m_settings.m_name = name; }
340 std::string GncPriceImport::settings_name () {
return m_settings.m_name; }
356 catch (std::ifstream::failure& ios_err)
359 PWARN (
"Error: %s", ios_err.what());
380 uint32_t max_cols = 0;
383 for (
auto tokenized_line :
m_tokenizer->get_tokens())
385 auto length = tokenized_line.size();
387 m_parsed_lines.push_back (std::make_tuple (tokenized_line, std::string(),
388 std::make_shared<GncImportPrice>(date_format(), currency_format()),
390 if (length > max_cols)
397 throw (std::range_error (
"Tokenizing failed."));
401 m_settings.m_column_types_price.resize(max_cols, GncPricePropType::NONE);
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);
418 void add_error (std::string msg);
420 bool empty() {
return m_error.empty(); }
425 void ErrorListPrice::add_error (std::string msg)
427 m_error +=
"- " + msg +
"\n";
430 std::string ErrorListPrice::str()
432 return m_error.substr(0, m_error.size() - 1);
439 void GncPriceImport::verify_column_selections (
ErrorListPrice& error_msg)
443 if (!check_for_column_type(GncPricePropType::DATE))
444 error_msg.add_error( _(
"Please select a date column."));
448 if (!check_for_column_type(GncPricePropType::AMOUNT))
449 error_msg.add_error( _(
"Please select an amount column."));
453 if (!check_for_column_type(GncPricePropType::TO_CURRENCY))
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."));
461 if (!check_for_column_type(GncPricePropType::FROM_SYMBOL))
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."));
469 if (!check_for_column_type(GncPricePropType::FROM_NAMESPACE))
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."));
477 if ((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'."));
492 std::string GncPriceImport::verify ()
494 auto newline = std::string();
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();
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())
508 error_msg.add_error(_(
"No lines are selected for importing. Please reduce the number of lines to skip."));
509 return error_msg.str();
512 verify_column_selections (error_msg);
514 update_skipped_lines (std::nullopt, std::nullopt, std::nullopt, std::nullopt);
516 auto have_line_errors =
false;
519 if (!std::get<PL_SKIP>(line) && !std::get<PL_ERROR>(line).empty())
521 have_line_errors =
true;
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."));
529 return error_msg.str();
536 static void price_properties_verify_essentials (std::vector<parse_line_t>::iterator& parsed_line)
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;
542 auto price_error = price_props->verify_essentials();
544 error_message.clear();
545 if (!price_error.empty())
547 error_message += price_error;
548 error_message +=
"\n";
551 if (!error_message.empty())
552 throw std::invalid_argument(error_message);
555 void GncPriceImport::create_price (std::vector<parse_line_t>::iterator& parsed_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;
566 error_message.clear();
569 auto line_to_currency = price_props->get_to_currency();
570 if (!line_to_currency)
572 if (m_settings.m_to_currency)
573 price_props->set_to_currency(m_settings.m_to_currency);
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);
586 auto line_from_commodity = price_props->get_from_commodity();
587 if (!line_from_commodity)
589 if (m_settings.m_from_commodity)
590 price_props->set_from_commodity(m_settings.m_from_commodity);
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);
605 price_properties_verify_essentials (parsed_line);
607 QofBook* book = gnc_get_current_book();
611 auto price_created = price_props->create_price (book, pdb, m_over_write);
612 if (price_created == ADDED)
614 else if (price_created == DUPLICATED)
615 m_prices_duplicated++;
616 else if (price_created == REPLACED)
619 catch (
const std::invalid_argument& e)
621 error_message = e.what();
622 PINFO(
"User warning: %s", error_message.c_str());
636 auto verify_result = verify();
637 if (!verify_result.empty())
638 throw std::invalid_argument (verify_result);
641 m_prices_duplicated = 0;
642 m_prices_replaced = 0;
650 if ((std::get<PL_SKIP>(*parsed_lines_it)))
654 create_price (parsed_lines_it);
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);
661 GncPriceImport::check_for_column_type (GncPricePropType type)
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());
669 void GncPriceImport::update_price_props (uint32_t row, uint32_t col, GncPricePropType prop_type)
671 if (prop_type == GncPricePropType::NONE)
674 auto price_props = std::make_shared<GncImportPrice> (*(std::get<PL_PREPRICE>(
m_parsed_lines[row])).get());
677 price_props->reset (prop_type);
681 bool enable_test_empty =
true;
685 if (prop_type == GncPricePropType::TO_CURRENCY)
687 if (m_settings.m_from_commodity)
688 price_props->set_from_commodity (m_settings.m_from_commodity);
690 if (m_settings.m_to_currency)
691 enable_test_empty =
false;
694 if (prop_type == GncPricePropType::FROM_SYMBOL)
696 if (m_settings.m_to_currency)
697 price_props->set_to_currency (m_settings.m_to_currency);
699 if (m_settings.m_from_commodity)
700 enable_test_empty =
false;
702 price_props->set(prop_type, value, enable_test_empty);
704 catch (
const std::exception& e)
710 PINFO(
"User warning: %s", e.what());
718 GncPriceImport::set_column_type_price (uint32_t position, GncPricePropType type,
bool force)
720 if (position >= m_settings.m_column_types_price.size())
723 auto old_type = m_settings.m_column_types_price[position];
724 if ((type == old_type) && !force)
728 std::replace(m_settings.m_column_types_price.begin(), m_settings.m_column_types_price.end(),
729 type, GncPricePropType::NONE);
731 m_settings.m_column_types_price.at (position) = type;
734 if (type == GncPricePropType::FROM_NAMESPACE)
738 if (type == GncPricePropType::FROM_SYMBOL)
742 if (type == GncPricePropType::TO_CURRENCY)
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);
761 if (old_type != type)
763 auto old_col = std::get<PL_INPUT>(*parsed_lines_it).size();
764 if ((old_type > GncPricePropType::NONE)
765 && (old_type <= GncPricePropType::PRICE_PROPS))
766 update_price_props (row, old_col, old_type);
769 if ((type > GncPricePropType::NONE)
770 && (type <= GncPricePropType::PRICE_PROPS))
771 update_price_props (row, position, type);
774 auto price_errors = std::get<PL_PREPRICE>(*parsed_lines_it)->errors();
775 std::get<PL_ERROR>(*parsed_lines_it) =
777 (price_errors.empty() ? std::string() :
"\n");
781 std::vector<GncPricePropType> GncPriceImport::column_types_price ()
783 return m_settings.m_column_types_price;
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.
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.
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.