FOSSredder 0.5.0
Loading...
Searching...
No Matches
AmountParser.cpp
Go to the documentation of this file.
1
5
7
9
10#include <algorithm>
11#include <cctype>
12#include <cmath>
13#include <regex>
14
15namespace {
16
17std::optional<double> parseAmountInternal(const std::string& line)
18{
19 std::string s = line;
20 try {
21 auto replaceAll = [&](const std::string& from, const std::string& to) {
22 size_t pos = 0;
23 while ((pos = s.find(from, pos)) != std::string::npos) {
24 s.replace(pos, from.size(), to);
25 pos += to.size();
26 }
27 };
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", ".");
36 } catch (...) {}
37
38 std::string clean;
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(' ');
43 }
44 s = std::move(clean);
45
46 try {
47 std::string direct = s;
48 direct.erase(std::remove_if(direct.begin(), direct.end(), [](unsigned char c) {
49 return std::isspace(c);
50 }), direct.end());
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)) {
55 return value;
56 }
57 }
58 } catch (...) {}
59
60 try {
61 static const std::regex splitDec(R"((\d{1,3}[\.,]\d)\s+(\d{1,2}-?))");
62 std::smatch m;
63 std::string tmp = s;
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();
67 }
68 s = std::move(tmp);
69 } catch (...) {}
70
71 static const std::regex re(R"((\‍(?-?(?:\d{1,3}(?:[\.,]\d{3})*|\d+),\d{2}\)?))");
72 try {
73 std::optional<double> best;
74 double bestAbs = 0.0;
75 for (std::sregex_iterator it(s.begin(), s.end(), re), end; it != end; ++it) {
76 try {
77 std::string token = (*it).str(1);
79
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(); }
84
85 std::string normalized = token;
86 normalized.erase(std::remove(normalized.begin(), normalized.end(), '.'), normalized.end());
87 std::replace(normalized.begin(), normalized.end(), ',', '.');
88
89 try {
90 double value = std::stod(normalized);
91 if (negative) value = -value;
92 const double absoluteValue = std::abs(value);
93 if (!best || absoluteValue > bestAbs) {
94 best = value;
95 bestAbs = absoluteValue;
96 }
97 } catch (...) {}
98 } catch (...) {}
99 }
100 if (best) return best;
101 } catch (...) {}
102
103 static const std::regex re2(R"((\‍(?-?\d+[\.,]\d{2}\)?))");
104 try {
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) {
108 try {
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(), ',', '.');
116 try {
117 double value = std::stod(token);
118 if (negative) value = -value;
119 const double absoluteValue = std::abs(value);
120 if (!best || absoluteValue > bestAbs) {
121 best = value;
122 bestAbs = absoluteValue;
123 }
124 } catch (...) {}
125 } catch (...) {}
126 }
127 if (best) return best;
128 } catch (...) {}
129
130 return std::nullopt;
131}
132
133}
134
136
137std::optional<double> parseAmountString(const std::string& line)
138{
139 return parseAmountInternal(line);
140}
141
142} // namespace core::application::importing::transaction
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)