8e464449abef31a68cb37f8b614b84d64d7c9495
[account.git] / bank.cpp
1 /*
2  *  Read bank transactions and generate account bookings.
3  *
4  *  Template syntax:
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.
8  *
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.
12  */
13
14
15 #include <fstream>
16 #include <vector>
17 #include <AXE/String.h>
18 #include <AXE/date.h>
19
20 class BankTemplate
21 {
22    bool Credit;
23    regex name_match;
24    regex description_match;
25
26    String text_template;
27
28 public:
29
30    BankTemplate() : name_match(""), description_match(".*")
31    {
32       Credit = false;
33       text_template = "";
34    }
35
36    BankTemplate(bool cr, String nm, String dm)
37       : name_match(nm), description_match(dm)
38    {
39       Credit = cr;
40       text_template = "";
41    }
42
43    BankTemplate(const BankTemplate  & b)
44       : name_match(b.name_match), description_match(b.description_match)
45    {
46       Credit            = b.Credit;
47       text_template     = b.text_template;
48    }
49
50    void add_to_template(String s)
51    {
52       text_template += s + "\n";
53    }
54
55    bool matches(String deb_cred, String name, String descr);
56
57    String substitute_template(String date, String name, String amount, String descr);
58 };
59
60 bool BankTemplate::matches(String deb_cred, String name, String descr)
61 {
62    return (deb_cred == "C") == Credit && name == name_match && descr == description_match;
63 }
64
65 String BankTemplate::substitute_template(String date, String name, String amount, String descr)
66 {
67    String text(text_template);
68
69    int sub_start, sub_len;
70
71    sub_start = text.index('~');
72
73    while (sub_start != -1)
74    {
75       sub_len = 1;
76       while (text[sub_start + sub_len] != '~')
77       {
78          sub_len++;
79       }
80       sub_len++;
81       String match = text(sub_start, sub_len);
82       if (~match == 4 && match[1] == '$')
83       {
84          switch (match[2])
85          {
86          case 'A':
87             text(sub_start, sub_len) = amount;
88             break;
89
90          case 'N':
91             text(sub_start, sub_len) = name;
92             break;
93
94          case 'D':
95             text(sub_start, sub_len) = date(6,2) + String("-") + date(4,2) + String("-") + date(0,4);
96             break;
97
98          default:
99             text(sub_start, sub_len) = String("INVALID SUBSTITUTION");
100             break;
101          }
102       }
103       else
104       {
105          match <<= 1;
106          match >>= 1;
107          String extraction = descr(regex(match));
108          text(sub_start, sub_len) = extraction;
109       }
110
111       sub_start = text.index('~');
112    }
113
114    return text;
115 }
116
117 std::vector<BankTemplate> read_templates(const char * filename);
118 void read_mutations(const char *filename, std::vector<BankTemplate> templates);
119 std::vector<String> parse_csv(String line, char delim);
120
121 // Fields in the bank record.
122
123 const int DEBIT_CREDIT = 3;
124 const int AMOUNT       = 4;
125 const int NAME         = 6;
126 const int BOOKING_DATE = 7;
127 const int DESCRIP1     = 10;
128 const int DESCRIP2     = 11;
129 const int DESCRIP3     = 12;
130 const int DESCRIP4     = 13;
131 const int DESCRIP5     = 14;
132 const int DESCRIP6     = 15;
133
134 main()
135 {
136    //read_templates("Bank.templ");
137    std::vector<BankTemplate> templates = read_templates("Bank.templ");
138
139    read_mutations("mut.txt", templates);
140 }
141
142 std::vector<BankTemplate> read_templates(const char * filename)
143 {
144    std::ifstream  in(filename);
145
146    String line;
147    BankTemplate   bt;
148    std::vector<BankTemplate>  templates;
149
150    bool           first = true;
151
152    while (in >> line)
153    {
154       if (~line > 0 && (line[0] == '-' || line[0] == '+'))
155       {
156          //  Start a new template
157
158          if (!first)
159          {
160             templates.push_back(bt);
161          }
162          first = false;
163
164          //line <<= 1;
165          int separator = line.index('~');
166          bt = BankTemplate(line[0] == '+', line(1, separator -1), line(separator + 1, ~line - separator -1));
167       }
168       else
169       {
170          // Add to the template text
171          bt.add_to_template(line);
172       }
173    }
174
175    templates.push_back(bt);
176
177    return templates;
178 }
179
180 void read_mutations(const char *filename, std::vector<BankTemplate> templates)
181 {
182    int            i;
183
184    std::ifstream  in(filename);
185
186    String line;
187    std::vector<String> bank_record;
188
189    while (in >> line)
190    {
191       String description;
192
193       //std::cout << line << "\n";
194       line += ",\"\"";
195       bank_record = parse_csv(line, ',');
196
197       //std::cout << "  " << bank_record[BOOKING_DATE] << " " <<bank_record[DEBIT_CREDIT] << " " <<bank_record[NAME] << ": " << bank_record[AMOUNT];
198       description = bank_record[DESCRIP1];
199       for (i = DESCRIP2; i <= DESCRIP6; i++)
200       {
201          description += " " + bank_record[i];
202       }
203       //std::cout << " " << description << "\n";
204
205       //  Find a template for this bank record
206
207       i = 0;
208       while (!templates[i].matches(bank_record[DEBIT_CREDIT], bank_record[NAME], description) && i < templates.size())
209       {
210          i++;
211       }
212       if (i != templates.size())
213       {
214          std::cout << templates[i].substitute_template(bank_record[BOOKING_DATE], bank_record[NAME],
215                                                        bank_record[AMOUNT], description);
216       }
217       else
218       {
219          std::cerr << "Bank record does not match any tempplate:\n";
220       }
221    }
222 }
223
224 std::vector<String> parse_csv(String line, char delim)
225 {
226    std::vector<String> record;
227    int i;
228    bool inquotes = false;
229    int  start = 0, len = 0;
230
231    for (i = 0; i < ~line; i++)
232    {
233       if (!inquotes && line[i] == '"')
234       {
235          inquotes = true;
236       }
237       else if (inquotes && line[i] == '"')
238       {
239          inquotes = false;
240       }
241       else if (!inquotes && line[i] == delim)
242       {
243          //  Delimiter: copy the substring to the record
244
245          String field = line(start, len);
246          record.push_back(field);
247          len = 0;
248       }
249       else
250       {
251          if (len == 0)
252          {
253             start = i;
254          }
255          len++;
256       }
257    }
258
259    return record;
260 }