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;
31 BankTemplate() : name_match(""), description_match(".*")
37 BankTemplate(bool cr, String nm, String dm)
38 : name_match(nm), description_match(dm)
44 BankTemplate(const BankTemplate & b)
45 : name_match(b.name_match), description_match(b.description_match)
48 text_template = b.text_template;
51 void add_to_template(String s)
53 text_template += s + "\n";
56 bool matches(String deb_cred, String name, String descr);
58 String substitute_template(String date, String name, String amount, String descr);
61 bool BankTemplate::matches(String deb_cred, String name, String descr)
63 return (deb_cred == "C") == Credit && name == name_match && descr == description_match;
66 String BankTemplate::substitute_template(String date, String name, String amount, String descr)
68 String text(text_template);
70 int sub_start, sub_len;
72 sub_start = text.index('~');
74 while (sub_start != -1)
77 while (text[sub_start + sub_len] != '~')
82 String match = text(sub_start, sub_len);
83 if (~match == 4 && match[1] == '$')
88 text(sub_start, sub_len) = amount;
92 text(sub_start, sub_len) = name;
96 text(sub_start, sub_len) = date(6,2) + String("-") + date(4,2) + String("-") + date(0,4);
100 text(sub_start, sub_len) = String("INVALID SUBSTITUTION");
108 String extraction = descr(regex(match));
109 text(sub_start, sub_len) = extraction;
112 sub_start = text.index('~');
118 std::vector<BankTemplate> read_templates(const char * filename);
119 void read_mutations(const char *filename, std::vector<BankTemplate> templates);
120 std::vector<String> parse_csv(String line, char delim);
122 // Fields in the bank record.
124 const int DEBIT_CREDIT = 3;
125 const int AMOUNT = 4;
127 const int BOOKING_DATE = 7;
128 const int DESCRIP1 = 10;
129 const int DESCRIP2 = 11;
130 const int DESCRIP3 = 12;
131 const int DESCRIP4 = 13;
132 const int DESCRIP5 = 14;
133 const int DESCRIP6 = 15;
135 static char helptext[] =
136 "Usage: bank [options] filename\n"
139 " --help : Print this helpful message\n"
141 " --version : Print the version number\n";
143 static struct option long_options[] =
145 { "help", no_argument, 0, 'h' },
146 { "version", no_argument, 0, 'V' },
150 main(int argc, char *argv[])
154 int option_index = 0;
156 while ((c = getopt_long(argc, argv, "hV", long_options, &option_index)) != -1)
161 std::cout << helptext;
166 std::cout << "bank version 1.1\n";
175 std::cerr << "Usage: bank [options] filename\n";
179 std::vector<BankTemplate> templates = read_templates("Bank.templ");
181 read_mutations(argv[optind], templates);
184 std::vector<BankTemplate> read_templates(const char * filename)
186 std::ifstream in(filename);
190 std::vector<BankTemplate> templates;
196 if (~line > 0 && (line[0] == '-' || line[0] == '+'))
198 // Start a new template
202 templates.push_back(bt);
206 int separator = line.index('~');
207 bt = BankTemplate(line[0] == '+', line(1, separator -1), line(separator + 1, ~line - separator -1));
211 // Add to the template text
212 bt.add_to_template(line);
216 templates.push_back(bt);
221 void read_mutations(const char *filename, std::vector<BankTemplate> templates)
225 std::ifstream in(filename);
228 std::vector<String> bank_record;
235 bank_record = parse_csv(line, ',');
237 description = bank_record[DESCRIP1];
238 for (i = DESCRIP2; i <= DESCRIP6; i++)
240 description += " " + bank_record[i];
243 // Remove the trailing \r and trailing blanks from the description.
246 while (description[~description - 1] == ' ')
251 // Find a template for this bank record
254 while (!templates[i].matches(bank_record[DEBIT_CREDIT], bank_record[NAME], description) && i < templates.size())
258 if (i != templates.size())
260 std::cout << templates[i].substitute_template(bank_record[BOOKING_DATE], bank_record[NAME],
261 bank_record[AMOUNT], description);
265 std::cerr << "Bank record does not match any tempplate:\n";
270 std::vector<String> parse_csv(String line, char delim)
272 std::vector<String> record;
274 bool inquotes = false;
275 int start = 0, len = 0;
277 for (i = 0; i < ~line; i++)
279 if (!inquotes && line[i] == '"')
283 else if (inquotes && line[i] == '"')
287 else if (!inquotes && line[i] == delim)
289 // Delimiter: copy the substring to the record
291 String field = line(start, len);
292 record.push_back(field);