2 * Read bank transactions and generate account bookings.
5 * Lines starting with '+' or '-': Match for name and description. Two regular
6 * expressions, separated by '~'. The '+' matches CREDIT transactions (adds to
7 * the bank account); the '-' matches DEBIT transactions.
9 * Other lines are content of the template. Parts enclosed in '~' characters are substituted by fields
10 * from the input. Possibilities are: "$D" -> booking date, "$N" -> name, "$A" -> amount,
11 * anything else is taken as a regular expression that is extracted from the description.
17 #include <AXE/String.h>
25 regex description_match;
36 BankTemplate() : name_match(""), description_match(".*")
42 BankTemplate(bool cr, String nm, String dm)
43 : name_match(nm), description_match(dm)
51 BankTemplate(const BankTemplate & b)
52 : name_match(b.name_match), description_match(b.description_match)
55 text_template = b.text_template;
60 void add_to_template(String s)
62 text_template += s + "\n";
65 bool matches(String deb_cred, String name, String descr);
67 String substitute_template(String date, String name, String amount, String descr);
70 bool BankTemplate::matches(String deb_cred, String name, String descr)
72 //std::cout << name << " == " << _nm << "\n" << descr << " == " << _dm << "\n";
73 return (deb_cred == "C") == Credit && name == name_match && descr == description_match;
76 String BankTemplate::substitute_template(String date, String name, String amount, String descr)
78 String text(text_template);
80 int sub_start, sub_len;
82 sub_start = text.index('~');
84 while (sub_start != -1)
87 while (text[sub_start + sub_len] != '~')
92 String match = text(sub_start, sub_len);
93 if (~match == 4 && match[1] == '$')
98 text(sub_start, sub_len) = amount;
102 text(sub_start, sub_len) = name;
106 //text(sub_start, sub_len) = date;
107 text(sub_start, sub_len) = date(8,2) + String("-") + date(5,2) + String("-") + date(0,4);
111 text(sub_start, sub_len) = String("INVALID SUBSTITUTION");
119 String extraction = descr(regex(match));
120 text(sub_start, sub_len) = extraction;
123 sub_start = text.index('~');
129 std::vector<BankTemplate> read_templates(const char * filename);
130 void read_mutations(const char *filename, std::vector<BankTemplate> templates);
131 std::vector<String> parse_csv(String line, char delim);
133 // Fields in the bank record.
135 const int DEBIT_CREDIT = 2;
136 const int AMOUNT = 6;
138 const int BOOKING_DATE = 4;
139 const int DESCRIP1 = 19;
140 const int DESCRIP2 = 20;
141 const int DESCRIP3 = 21;
142 const int DESCRIP4 = 13;
143 const int DESCRIP5 = 14;
144 const int DESCRIP6 = 15;
146 static char helptext[] =
147 "Usage: bank [options] filename\n"
150 " --help : Print this helpful message\n"
152 " --version : Print the version number\n";
154 static struct option long_options[] =
156 { "help", no_argument, 0, 'h' },
157 { "version", no_argument, 0, 'V' },
161 int main(int argc, char *argv[])
165 int option_index = 0;
167 while ((c = getopt_long(argc, argv, "hV", long_options, &option_index)) != -1)
172 std::cout << helptext;
177 std::cout << "bank version 1.3\n";
186 std::cerr << "Usage: bank [options] filename\n";
190 std::vector<BankTemplate> templates = read_templates("Bank.templ");
192 read_mutations(argv[optind], templates);
197 std::vector<BankTemplate> read_templates(const char * filename)
199 std::ifstream in(filename);
203 std::vector<BankTemplate> templates;
209 if (~line > 0 && (line[0] == '-' || line[0] == '+'))
211 // Start a new template
215 templates.push_back(bt);
219 int separator = line.index('~');
220 bt = BankTemplate(line[0] == '+', line(1, separator -1), line(separator + 1, ~line - separator -1));
224 // Add to the template text
225 bt.add_to_template(line);
229 templates.push_back(bt);
234 void read_mutations(const char *filename, std::vector<BankTemplate> templates)
238 std::ifstream in(filename);
241 std::vector<String> bank_record;
248 bank_record = parse_csv(line, ',');
250 // A + amount is Credit, a - amount is Debit
252 bank_record[DEBIT_CREDIT] = "C";
253 if (bank_record[AMOUNT][0] == '-')
255 bank_record[DEBIT_CREDIT] = "D";
257 bank_record[AMOUNT] <<= 1;
259 description = bank_record[DESCRIP1];
260 for (i = DESCRIP2; i <= DESCRIP3; i++)
262 description += " " + bank_record[i];
265 // Remove the trailing \r and trailing blanks from the description.
268 while (description[~description - 1] == ' ')
273 // Find a template for this bank record
276 while (!templates[i].matches(bank_record[DEBIT_CREDIT], bank_record[NAME], description) && i < templates.size())
280 if (i != templates.size())
282 std::cout << templates[i].substitute_template(bank_record[BOOKING_DATE], bank_record[NAME],
283 bank_record[AMOUNT], description);
287 std::cerr << "Bank record does not match any tempplate:\n";
292 std::vector<String> parse_csv(String line, char delim)
294 std::vector<String> record;
296 bool inquotes = false;
297 int start = 0, len = 0;
299 for (i = 0; i < ~line; i++)
301 if (!inquotes && line[i] == '"')
305 else if (inquotes && line[i] == '"')
309 else if (!inquotes && line[i] == delim)
311 // Delimiter: copy the substring to the record
313 String field = line(start, len);
314 record.push_back(field);