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(6,2) + String("-") + date(4,2) + String("-") + date(0,4);
110 text(sub_start, sub_len) = String("INVALID SUBSTITUTION");
118 String extraction = descr(regex(match));
119 text(sub_start, sub_len) = extraction;
122 sub_start = text.index('~');
128 std::vector<BankTemplate> read_templates(const char * filename);
129 void read_mutations(const char *filename, std::vector<BankTemplate> templates);
130 std::vector<String> parse_csv(String line, char delim);
132 // Fields in the bank record.
134 const int DEBIT_CREDIT = 3;
135 const int AMOUNT = 4;
137 const int BOOKING_DATE = 7;
138 const int DESCRIP1 = 10;
139 const int DESCRIP2 = 11;
140 const int DESCRIP3 = 12;
141 const int DESCRIP4 = 13;
142 const int DESCRIP5 = 14;
143 const int DESCRIP6 = 15;
145 static char helptext[] =
146 "Usage: bank [options] filename\n"
149 " --help : Print this helpful message\n"
151 " --version : Print the version number\n";
153 static struct option long_options[] =
155 { "help", no_argument, 0, 'h' },
156 { "version", no_argument, 0, 'V' },
160 int main(int argc, char *argv[])
164 int option_index = 0;
166 while ((c = getopt_long(argc, argv, "hV", long_options, &option_index)) != -1)
171 std::cout << helptext;
176 std::cout << "bank version 1.1\n";
185 std::cerr << "Usage: bank [options] filename\n";
189 std::vector<BankTemplate> templates = read_templates("Bank.templ");
191 read_mutations(argv[optind], templates);
196 std::vector<BankTemplate> read_templates(const char * filename)
198 std::ifstream in(filename);
202 std::vector<BankTemplate> templates;
208 if (~line > 0 && (line[0] == '-' || line[0] == '+'))
210 // Start a new template
214 templates.push_back(bt);
218 int separator = line.index('~');
219 bt = BankTemplate(line[0] == '+', line(1, separator -1), line(separator + 1, ~line - separator -1));
223 // Add to the template text
224 bt.add_to_template(line);
228 templates.push_back(bt);
233 void read_mutations(const char *filename, std::vector<BankTemplate> templates)
237 std::ifstream in(filename);
240 std::vector<String> bank_record;
247 bank_record = parse_csv(line, ',');
249 description = bank_record[DESCRIP1];
250 for (i = DESCRIP2; i <= DESCRIP6; i++)
252 description += " " + bank_record[i];
255 // Remove the trailing \r and trailing blanks from the description.
258 while (description[~description - 1] == ' ')
263 // Find a template for this bank record
266 while (!templates[i].matches(bank_record[DEBIT_CREDIT], bank_record[NAME], description) && i < templates.size())
270 if (i != templates.size())
272 std::cout << templates[i].substitute_template(bank_record[BOOKING_DATE], bank_record[NAME],
273 bank_record[AMOUNT], description);
277 std::cerr << "Bank record does not match any tempplate:\n";
282 std::vector<String> parse_csv(String line, char delim)
284 std::vector<String> record;
286 bool inquotes = false;
287 int start = 0, len = 0;
289 for (i = 0; i < ~line; i++)
291 if (!inquotes && line[i] == '"')
295 else if (inquotes && line[i] == '"')
299 else if (!inquotes && line[i] == delim)
301 // Delimiter: copy the substring to the record
303 String field = line(start, len);
304 record.push_back(field);