1 /*
2 * MoneyWise: Finance Application
3 * Copyright 2012-2026. Tony Washer
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6 * use this file except in compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 * License for the specific language governing permissions and limitations under
15 * the License.
16 */
17 package io.github.tonywasher.joceanus.moneywise.quicken.file;
18
19 import io.github.tonywasher.joceanus.moneywise.quicken.definitions.MoneyWiseQIFType;
20 import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
21 import io.github.tonywasher.joceanus.tethys.api.factory.TethysUIFactory;
22
23 import java.io.BufferedReader;
24 import java.io.IOException;
25 import java.util.ArrayList;
26 import java.util.List;
27
28 /**
29 * Class to parse a QIFFile.
30 */
31 public class MoneyWiseQIFParser {
32 /**
33 * The QIFFile being built.
34 */
35 private final MoneyWiseQIFFile theFile;
36
37 /**
38 * Data formatter.
39 */
40 private final OceanusDataFormatter theFormatter;
41
42 /**
43 * Record mode.
44 */
45 private MoneyWiseQIFSection theSection;
46
47 /**
48 * Active account.
49 */
50 private MoneyWiseQIFAccountEvents theActive;
51
52 /**
53 * is active account portfolio?
54 */
55 private boolean isPortfolio;
56
57 /**
58 * Constructor.
59 *
60 * @param pFactory the gui factory
61 * @param pFileType the QIF file type.
62 */
63 public MoneyWiseQIFParser(final TethysUIFactory<?> pFactory,
64 final MoneyWiseQIFType pFileType) {
65 /* Create new file */
66 theFile = new MoneyWiseQIFFile(pFileType);
67
68 /* Allocate the formatter and set date format */
69 theFormatter = pFactory.newDataFormatter();
70 theFormatter.setFormat(MoneyWiseQIFWriter.QIF_DATEFORMAT);
71 }
72
73 /**
74 * Obtain file.
75 *
76 * @return the QIF file.
77 */
78 public MoneyWiseQIFFile getFile() {
79 return theFile;
80 }
81
82 /**
83 * Load from Stream.
84 *
85 * @param pStream the input stream
86 * @return continue true/false
87 * @throws IOException on error
88 */
89 public boolean loadFile(final BufferedReader pStream) throws IOException {
90 /* List of lines */
91 final List<String> myLines = new ArrayList<>();
92
93 /* Loop through the file */
94 while (true) {
95 /* Read the next line and break on EOF */
96 final String myLine = pStream.readLine();
97 if (myLine == null) {
98 break;
99 }
100
101 /* If this is a command */
102 if (myLine.startsWith(MoneyWiseQIFRecord.QIF_CMD)) {
103 /* Process the mode */
104 processMode(myLine);
105 /* If this is an EOI */
106 } else if (myLine.equals(MoneyWiseQIFRecord.QIF_EOI)) {
107 /* Process the records */
108 processRecord(myLines);
109
110 /* else normal line */
111 } else {
112 /* Ignore blank lines */
113 if (myLine.length() > 0) {
114 myLines.add(myLine);
115 }
116 }
117 }
118
119 /* Sort the file lists */
120 theFile.sortLists();
121
122 /* Return to the caller */
123 return true;
124 }
125
126 /**
127 * Process the mode.
128 *
129 * @param pLine the line to process
130 */
131 private void processMode(final String pLine) {
132 /* If the line is a type record */
133 if (pLine.startsWith(MoneyWiseQIFRecord.QIF_ITEMTYPE)) {
134 /* Access the type */
135 final String myType = pLine.substring(MoneyWiseQIFRecord.QIF_ITEMTYPE.length());
136
137 /* Determine which section this describes */
138 final MoneyWiseQIFSection mySection = MoneyWiseQIFSection.determineType(myType);
139
140 /* If we found a section */
141 if (mySection != null) {
142 /* Switch on the section */
143 switch (mySection) {
144 case CLASS:
145 case CATEGORY:
146 case SECURITY:
147 case PRICE:
148 theSection = mySection;
149 theActive = null;
150 break;
151 default:
152 theSection = null;
153 break;
154 }
155
156 /* else if we have an active account */
157 } else if (theActive != null) {
158 /* Make sure that the type matches */
159 final String myActiveType = theActive.getAccount().getType();
160 if (myActiveType.equals(myType)) {
161 /* Set events */
162 theSection = MoneyWiseQIFSection.EVENT;
163
164 /* Note portfolio */
165 isPortfolio = myType.equals(MoneyWiseQIFAccount.QIFACT_INVST);
166
167 /* Not recognised */
168 } else {
169 theSection = null;
170 }
171
172 /* Not recognised */
173 } else {
174 theSection = null;
175 }
176
177 /* Handle Account call */
178 } else if (pLine.startsWith(MoneyWiseQIFAccount.QIF_HDR)) {
179 /* Look for account record */
180 theSection = MoneyWiseQIFSection.ACCOUNT;
181 }
182 }
183
184 /**
185 * Process the record.
186 *
187 * @param pLines the lines to process
188 */
189 private void processRecord(final List<String> pLines) {
190 /* Switch on the section */
191 switch (theSection) {
192 case CLASS:
193 processClassRecord(pLines);
194 break;
195 case CATEGORY:
196 processCategoryRecord(pLines);
197 break;
198 case ACCOUNT:
199 processAccountRecord(pLines);
200 break;
201 case SECURITY:
202 processSecurityRecord(pLines);
203 break;
204 case EVENT:
205 processEventRecord(pLines);
206 break;
207 case PRICE:
208 processPriceRecord(pLines);
209 break;
210 default:
211 break;
212 }
213
214 /* Clear line list */
215 pLines.clear();
216 }
217
218 /**
219 * Process the class record.
220 *
221 * @param pLines the lines to process
222 */
223 private void processClassRecord(final List<String> pLines) {
224 /* register the class */
225 final MoneyWiseQIFClass myClass = new MoneyWiseQIFClass(theFile, pLines);
226 theFile.registerClass(myClass);
227 }
228
229 /**
230 * Process the category record.
231 *
232 * @param pLines the lines to process
233 */
234 private void processCategoryRecord(final List<String> pLines) {
235 /* Register the category */
236 final MoneyWiseQIFEventCategory myCategory = new MoneyWiseQIFEventCategory(theFile, pLines);
237 theFile.registerCategory(myCategory);
238 }
239
240 /**
241 * Process the account record.
242 *
243 * @param pLines the lines to process
244 */
245 private void processAccountRecord(final List<String> pLines) {
246 /* Register the account */
247 final MoneyWiseQIFAccount myAccount = new MoneyWiseQIFAccount(theFile, theFormatter, pLines);
248 theActive = theFile.registerAccount(myAccount);
249 }
250
251 /**
252 * Process the security record.
253 *
254 * @param pLines the lines to process
255 */
256 private void processSecurityRecord(final List<String> pLines) {
257 /* Register the security */
258 final MoneyWiseQIFSecurity mySecurity = new MoneyWiseQIFSecurity(theFile, pLines);
259 theFile.registerSecurity(mySecurity);
260 }
261
262 /**
263 * Process the event record.
264 *
265 * @param pLines the lines to process
266 */
267 private void processEventRecord(final List<String> pLines) {
268 /* Switch on portfolio */
269 if (isPortfolio) {
270 /* Register the event */
271 final MoneyWiseQIFPortfolioEvent myEvent = new MoneyWiseQIFPortfolioEvent(theFile, theFormatter, pLines);
272 theActive.addEvent(myEvent);
273 } else {
274 /* Register the event */
275 final MoneyWiseQIFEvent myEvent = new MoneyWiseQIFEvent(theFile, theFormatter, pLines);
276 theActive.addEvent(myEvent);
277 }
278 }
279
280 /**
281 * Process the price record.
282 *
283 * @param pLines the lines to process
284 */
285 private void processPriceRecord(final List<String> pLines) {
286 /* Register the price */
287 final MoneyWiseQIFPrice myPrice = new MoneyWiseQIFPrice(theFile, theFormatter, pLines);
288 theFile.registerPrice(myPrice);
289 }
290
291 /**
292 * QIF File section.
293 */
294 private enum MoneyWiseQIFSection {
295 /**
296 * Classes.
297 */
298 CLASS("Class"),
299
300 /**
301 * Category.
302 */
303 CATEGORY("Cat"),
304
305 /**
306 * Account.
307 */
308 ACCOUNT("Account"),
309
310 /**
311 * Security.
312 */
313 SECURITY("Security"),
314
315 /**
316 * EVENT.
317 */
318 EVENT(null),
319
320 /**
321 * Price.
322 */
323 PRICE("Prices");
324
325 /**
326 * Type.
327 */
328 private final String theType;
329
330 /**
331 * Constructor.
332 *
333 * @param pType the type
334 */
335 MoneyWiseQIFSection(final String pType) {
336 theType = pType;
337 }
338
339 /**
340 * Determine section.
341 *
342 * @param pLine the line to check
343 * @return the section
344 */
345 private static MoneyWiseQIFSection determineType(final String pLine) {
346 /* Loop through the values */
347 for (MoneyWiseQIFSection mySection : MoneyWiseQIFSection.values()) {
348 /* If we have a match */
349 if (pLine.equals(mySection.theType)) {
350 return mySection;
351 }
352 }
353
354 /* Not found */
355 return null;
356 }
357 }
358 }