17std::optional<double> parseAmountInternal(
const std::string& line)
21 auto replaceAll = [&](
const std::string& from,
const std::string& to) {
23 while ((pos = s.find(from, pos)) != std::string::npos) {
24 s.replace(pos, from.size(), to);
28 replaceAll(
"\xE2\x80\x93",
"-");
29 replaceAll(
"\xE2\x80\x94",
"-");
30 replaceAll(
"\xE2\x88\x92",
"-");
31 replaceAll(
"\xC2\xA0",
" ");
32 replaceAll(
"\xE2\x80\xAF",
" ");
33 replaceAll(
"\xE2\x80\x89",
" ");
34 replaceAll(
"\xE2\x80\xA2",
".");
35 replaceAll(
"\xE2\x80\xB7",
".");
39 clean.reserve(s.size());
40 for (
unsigned char c : s) {
41 if ((c >=
'0' && c <=
'9') || c ==
'.' || c ==
',' || c ==
'-' || c ==
'(' || c ==
')' || std::isspace(c)) clean.push_back(
static_cast<char>(c));
42 else clean.push_back(
' ');
47 std::string direct = s;
48 direct.erase(std::remove_if(direct.begin(), direct.end(), [](
unsigned char c) {
49 return std::isspace(c);
51 if (!direct.empty()) {
52 size_t parsedChars = 0;
53 const double value = std::stod(direct, &parsedChars);
54 if (parsedChars == direct.size() && std::isfinite(value)) {
61 static const std::regex splitDec(R
"((\d{1,3}[\.,]\d)\s+(\d{1,2}-?))");
64 while (std::regex_search(tmp, m, splitDec)) {
65 const std::string repl = m.str(1) + m.str(2);
66 tmp = m.prefix().str() + repl + m.suffix().str();
71 static const std::regex re(R
"((\(?-?(?:\d{1,3}(?:[\.,]\d{3})*|\d+),\d{2}\)?))");
73 std::optional<double> best;
75 for (std::sregex_iterator it(s.begin(), s.end(), re), end; it != end; ++it) {
77 std::string token = (*it).str(1);
80 bool negative =
false;
81 if (!token.empty() && token.front() ==
'(' && token.back() ==
')') { negative =
true; token = token.substr(1, token.size() - 2); }
82 if (!token.empty() && token.front() ==
'-') { negative =
true; token = token.substr(1); }
83 if (!token.empty() && token.back() ==
'-') { negative =
true; token.pop_back(); }
85 std::string normalized = token;
86 normalized.erase(std::remove(normalized.begin(), normalized.end(),
'.'), normalized.end());
87 std::replace(normalized.begin(), normalized.end(),
',',
'.');
90 double value = std::stod(normalized);
91 if (negative) value = -value;
92 const double absoluteValue = std::abs(value);
93 if (!best || absoluteValue > bestAbs) {
95 bestAbs = absoluteValue;
100 if (best)
return best;
103 static const std::regex re2(R
"((\(?-?\d+[\.,]\d{2}\)?))");
105 std::optional<double> best;
106 double bestAbs = 0.0;
107 for (std::sregex_iterator it(s.begin(), s.end(), re2), end; it != end; ++it) {
109 std::string token = (*it).str(1);
111 bool negative =
false;
112 if (!token.empty() && token.front() ==
'(' && token.back() ==
')') { negative =
true; token = token.substr(1, token.size() - 2); }
113 if (!token.empty() && token.front() ==
'-') { negative =
true; token = token.substr(1); }
114 token.erase(std::remove(token.begin(), token.end(),
'.'), token.end());
115 std::replace(token.begin(), token.end(),
',',
'.');
117 double value = std::stod(token);
118 if (negative) value = -value;
119 const double absoluteValue = std::abs(value);
120 if (!best || absoluteValue > bestAbs) {
122 bestAbs = absoluteValue;
127 if (best)
return best;
139 return parseAmountInternal(line);
Declares helpers for parsing imported transaction amount strings.
Declares date-detection helpers for the import parser.
bool hasShortDateToken(const std::string &text) noexcept
Checks whether the text contains a short date token.
std::optional< double > parseAmountString(const std::string &s)