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 //read_templates("Bank.templ");
180 std::vector<BankTemplate> templates = read_templates("Bank.templ");
182 read_mutations(argv[optind], templates);
185 std::vector<BankTemplate> read_templates(const char * filename)
187 std::ifstream in(filename);
191 std::vector<BankTemplate> templates;
197 if (~line > 0 && (line[0] == '-' || line[0] == '+'))
199 // Start a new template
203 templates.push_back(bt);
208 int separator = line.index('~');
209 bt = BankTemplate(line[0] == '+', line(1, separator -1), line(separator + 1, ~line - separator -1));
213 // Add to the template text
214 bt.add_to_template(line);
218 templates.push_back(bt);
223 void read_mutations(const char *filename, std::vector<BankTemplate> templates)
227 std::ifstream in(filename);
230 std::vector<String> bank_record;
236 //std::cout << line << "\n";
238 bank_record = parse_csv(line, ',');
240 //std::cout << " " << bank_record[BOOKING_DATE] << " " <<bank_record[DEBIT_CREDIT] << " " <<bank_record[NAME] << ": " << bank_record[AMOUNT];
241 description = bank_record[DESCRIP1];
242 for (i = DESCRIP2; i <= DESCRIP6; i++)
244 description += " " + bank_record[i];
246 //std::cout << " " << description << "\n";
248 // Find a template for this bank record
251 while (!templates[i].matches(bank_record[DEBIT_CREDIT], bank_record[NAME], description) && i < templates.size())
255 if (i != templates.size())
257 std::cout << templates[i].substitute_template(bank_record[BOOKING_DATE], bank_record[NAME],
258 bank_record[AMOUNT], description);
262 std::cerr << "Bank record does not match any tempplate:\n";
267 std::vector<String> parse_csv(String line, char delim)
269 std::vector<String> record;
271 bool inquotes = false;
272 int start = 0, len = 0;
274 for (i = 0; i < ~line; i++)
276 if (!inquotes && line[i] == '"')
280 else if (inquotes && line[i] == '"')
284 else if (!inquotes && line[i] == delim)
286 // Delimiter: copy the substring to the record
288 String field = line(start, len);
289 record.push_back(field);