LibOFX
ofx_sgml.cpp
Go to the documentation of this file.
1/***************************************************************************
2 ofx_sgml.cpp
3 -------------------
4 copyright : (C) 2002 by Benoit Grégoire
5 email : benoitg@coeus.ca
6***************************************************************************/
12/***************************************************************************
13 * *
14 * This program is free software; you can redistribute it and/or modify *
15 * it under the terms of the GNU General Public License as published by *
16 * the Free Software Foundation; either version 2 of the License, or *
17 * (at your option) any later version. *
18 * *
19 ***************************************************************************/
20
21#ifdef HAVE_CONFIG_H
22#include <config.h>
23#endif
24
25#include <iostream>
26#include <stdlib.h>
27#include <string>
28#include <cassert>
29#include "ParserEventGeneratorKit.h"
30#include "libofx.h"
31#include "ofx_utilities.hh"
32#include "messages.hh"
33#include "ofx_containers.hh"
34#include "ofx_sgml.hh"
35
36using namespace std;
37
38OfxMainContainer * MainContainer = NULL;
39extern SGMLApplication::OpenEntityPtr entity_ptr;
40extern SGMLApplication::Position position;
41
42
45class OFXApplication : public SGMLApplication
46{
47private:
48 OfxGenericContainer *curr_container_element;
49 OfxGenericContainer *tmp_container_element;
50 bool is_data_element;
51 string incoming_data;
52 LibofxContext * libofx_context;
53
54public:
55
56 OFXApplication (LibofxContext * p_libofx_context)
57 {
58 MainContainer = NULL;
59 curr_container_element = NULL;
60 is_data_element = false;
61 libofx_context = p_libofx_context;
62 }
64 {
65 message_out(DEBUG, "Entering the OFXApplication's destructor");
66 }
67
72 void startElement (const StartElementEvent & event)
73 {
74 string identifier;
75 CharStringtostring (event.gi, identifier);
76 message_out(PARSER, "startElement event received from OpenSP for element " + identifier);
77
78 position = event.pos;
79
80 switch (event.contentType)
81 {
82 case StartElementEvent::empty:
83 message_out(ERROR, "StartElementEvent::empty\n");
84 break;
85 case StartElementEvent::cdata:
86 message_out(ERROR, "StartElementEvent::cdata\n");
87 break;
88 case StartElementEvent::rcdata:
89 message_out(ERROR, "StartElementEvent::rcdata\n");
90 break;
91 case StartElementEvent::mixed:
92 message_out(PARSER, "StartElementEvent::mixed");
93 is_data_element = true;
94 break;
95 case StartElementEvent::element:
96 message_out(PARSER, "StartElementEvent::element");
97 is_data_element = false;
98 break;
99 default:
100 message_out(ERROR, "Unknown SGML content type?!?!?!? OpenSP interface changed?");
101 }
102
103 if (is_data_element == false)
104 {
105 /*------- The following are OFX entities ---------------*/
106
107 if (identifier == "OFX")
108 {
109 message_out (PARSER, "Element " + identifier + " found");
110 MainContainer = new OfxMainContainer (libofx_context, curr_container_element, identifier);
111 curr_container_element = MainContainer;
112 }
113 else if (identifier == "STATUS")
114 {
115 message_out (PARSER, "Element " + identifier + " found");
116 curr_container_element = new OfxStatusContainer (libofx_context, curr_container_element, identifier);
117 }
118 else if (identifier == "STMTRS" ||
119 identifier == "CCSTMTRS" ||
120 identifier == "INVSTMTRS")
121 {
122 message_out (PARSER, "Element " + identifier + " found");
123 curr_container_element = new OfxStatementContainer (libofx_context, curr_container_element, identifier);
124 }
125 else if (identifier == "BANKTRANLIST" || identifier == "INVTRANLIST")
126 {
127 message_out (PARSER, "Element " + identifier + " found");
128 //BANKTRANLIST ignored, we will process it's attributes directly inside the STATEMENT,
129 if (curr_container_element && curr_container_element->type != "STATEMENT")
130 {
131 message_out(ERROR, "Element " + identifier + " found while not inside a STATEMENT container");
132 }
133 else
134 {
135 curr_container_element = new OfxPushUpContainer (libofx_context, curr_container_element, identifier);
136 }
137 }
138 else if (identifier == "STMTTRN")
139 {
140 message_out (PARSER, "Element " + identifier + " found");
141 if (curr_container_element->type == "INVESTMENT")
142 {
143 //push up to the INVBANKTRAN OfxInvestmentTransactionContainer
144 curr_container_element = new OfxPushUpContainer (libofx_context, curr_container_element, identifier);
145 }
146 else
147 {
148 curr_container_element = new OfxBankTransactionContainer (libofx_context, curr_container_element, identifier);
149 }
150 }
151 else if (identifier == "BUYDEBT" ||
152 identifier == "BUYMF" ||
153 identifier == "BUYOPT" ||
154 identifier == "BUYOTHER" ||
155 identifier == "BUYSTOCK" ||
156 identifier == "CLOSUREOPT" ||
157 identifier == "INCOME" ||
158 identifier == "INVEXPENSE" ||
159 identifier == "JRNLFUND" ||
160 identifier == "JRNLSEC" ||
161 identifier == "MARGININTEREST" ||
162 identifier == "REINVEST" ||
163 identifier == "RETOFCAP" ||
164 identifier == "SELLDEBT" ||
165 identifier == "SELLMF" ||
166 identifier == "SELLOPT" ||
167 identifier == "SELLOTHER" ||
168 identifier == "SELLSTOCK" ||
169 identifier == "SPLIT" ||
170 identifier == "TRANSFER" ||
171 identifier == "INVBANKTRAN" )
172 {
173 message_out (PARSER, "Element " + identifier + " found");
174 curr_container_element = new OfxInvestmentTransactionContainer (libofx_context, curr_container_element, identifier);
175 }
176 /*The following is a list of OFX elements whose attributes will be processed by the parent container*/
177 else if (identifier == "INVBUY" ||
178 identifier == "INVSELL" ||
179 identifier == "INVTRAN" ||
180 identifier == "SECINFO" ||
181 identifier == "SECID" ||
182 identifier == "CURRENCY" ||
183 identifier == "ORIGCURRENCY")
184 {
185 message_out (PARSER, "Element " + identifier + " found");
186 curr_container_element = new OfxPushUpContainer (libofx_context, curr_container_element, identifier);
187 }
188
189 /* provide a parent for the account list response so its ACCTFROM can be recognized */
190 else if (identifier == "BANKACCTINFO" || identifier == "CCACCTINFO" || identifier == "INVACCTINFO")
191 {
192 message_out (PARSER, "Element " + identifier + " found");
193 curr_container_element = new OfxPushUpContainer (libofx_context, curr_container_element, identifier);
194 }
195
196 /* The different types of accounts */
197 else if (identifier == "BANKACCTFROM" || identifier == "CCACCTFROM" || identifier == "INVACCTFROM")
198 {
199 message_out (PARSER, "Element " + identifier + " found");
200 /* check the container to avoid creating multiple statements for TRANSFERs */
201 if (curr_container_element->type == "STATEMENT"
202 || curr_container_element->tag_identifier == "BANKACCTINFO"
203 || curr_container_element->tag_identifier == "CCACCTINFO"
204 || curr_container_element->tag_identifier == "INVACCTINFO")
205 curr_container_element = new OfxAccountContainer (libofx_context, curr_container_element, identifier);
206 else
207 // no new account or statement for a <TRANSFER>
208 curr_container_element = new OfxDummyContainer (libofx_context, curr_container_element, identifier);
209 }
210 else if (identifier == "STOCKINFO" || identifier == "OPTINFO" ||
211 identifier == "DEBTINFO" || identifier == "MFINFO" || identifier == "OTHERINFO")
212 {
213 message_out (PARSER, "Element " + identifier + " found");
214 curr_container_element = new OfxSecurityContainer (libofx_context, curr_container_element, identifier);
215 }
216 /* The different types of balances */
217 else if (identifier == "LEDGERBAL" ||
218 identifier == "AVAILBAL" ||
219 identifier == "INVBAL")
220 {
221 message_out (PARSER, "Element " + identifier + " found");
222 curr_container_element = new OfxBalanceContainer (libofx_context, curr_container_element, identifier);
223 }
224 else if (identifier == "INVPOS")
225 {
226 message_out (PARSER, "Element " + identifier + " found");
227 curr_container_element = new OfxPositionContainer (libofx_context, curr_container_element, identifier);
228 }
229 else
230 {
231 /* We dont know this OFX element, so we create a dummy container */
232 curr_container_element = new OfxDummyContainer(libofx_context, curr_container_element, identifier);
233 }
234 }
235 else
236 {
239 if (identifier == "INV401K")
240 {
241 /* Minimal handler for this section to discard <DTASOF>, <DTSTART> and <DTEND> that need to be ignored */
242 message_out (PARSER, "Element " + identifier + " found");
243 curr_container_element = new OfxInv401kContainer (libofx_context, curr_container_element, identifier);
244 }
245 if (identifier == "INV401KBAL")
246 {
247 message_out (PARSER, "Element " + identifier + " found");
248 curr_container_element = new OfxBalanceContainer (libofx_context, curr_container_element, identifier);
249 }
250 else
251 {
252 /* The element was a data element. OpenSP will call one or several data() callback with the data */
253 message_out (PARSER, "Data element " + identifier + " found");
254 /* There is a bug in OpenSP 1.3.4, which won't send endElement Event for some elements, and will instead send an error like "document type does not allow element "MESSAGE" here". Incoming_data should be empty in such a case, but it will not be if the endElement event was skipped. So we empty it, so at least the last element has a chance of having valid data */
255 if (incoming_data != "")
256 {
257 message_out (ERROR, "startElement: incoming_data should be empty! You are probably using OpenSP <= 1.3.4. The following data was lost: " + incoming_data );
258 incoming_data.assign ("");
259 }
260 }
261 }
262 }
263
268 void endElement (const EndElementEvent & event)
269 {
270 string identifier;
271 bool end_element_for_data_element;
272
273 CharStringtostring (event.gi, identifier);
274 end_element_for_data_element = is_data_element;
275 message_out(PARSER, "endElement event received from OpenSP for element " + identifier);
276
277 position = event.pos;
278 if (curr_container_element == NULL)
279 {
280 message_out (ERROR, "Tried to close a " + identifier + " without a open element (NULL pointer)");
281 incoming_data.assign ("");
282 }
283 else //curr_container_element != NULL
284 {
285 if (end_element_for_data_element == true)
286 {
287 incoming_data = strip_whitespace(incoming_data);
288
289 curr_container_element->add_attribute (identifier, incoming_data);
290 message_out (PARSER, "endElement: Added data '" + incoming_data + "' from " + identifier + " to " + curr_container_element->type + " container_element");
291 incoming_data.assign ("");
292 is_data_element = false;
293 }
294 else
295 {
296 if (identifier == curr_container_element->tag_identifier)
297 {
298 if (incoming_data != "")
299 {
300 message_out(ERROR, "End tag for non data element " + identifier + ", incoming data should be empty but contains: " + incoming_data + " DATA HAS BEEN LOST SOMEWHERE!");
301 }
302
303 if (identifier == "OFX")
304 {
305 /* The main container is a special case */
306 tmp_container_element = curr_container_element;
307 curr_container_element = curr_container_element->getparent ();
308 if (curr_container_element == NULL)
309 {
310 //Defensive coding, this isn't supposed to happen
311 curr_container_element = tmp_container_element;
312 }
313 if (MainContainer != NULL)
314 {
315 MainContainer->gen_event();
316 delete MainContainer;
317 MainContainer = NULL;
318 curr_container_element = NULL;
319 message_out (DEBUG, "Element " + identifier + " closed, MainContainer destroyed");
320 }
321 else
322 {
323 message_out (DEBUG, "Element " + identifier + " closed, but there was no MainContainer to destroy (probably a malformed file)!");
324 }
325 }
326 else
327 {
328 tmp_container_element = curr_container_element;
329 curr_container_element = curr_container_element->getparent ();
330 if (MainContainer != NULL)
331 {
335 if (identifier == "CURRENCY" || identifier == "ORIGCURRENCY")
336 {
337 tmp_container_element->add_attribute (identifier, incoming_data);
338 message_out (DEBUG, "Element " + identifier + " closed, container " + tmp_container_element->type + " updated");
339 }
340 else
341 {
342 tmp_container_element->add_to_main_tree();
343 message_out (PARSER, "Element " + identifier + " closed, object added to MainContainer");
344 }
345 }
346 else
347 {
348 message_out (ERROR, "MainContainer is NULL trying to add element " + identifier);
349 }
350 }
351 }
352 else
353 {
354 message_out (ERROR, "Tried to close a " + identifier + " but a " + curr_container_element->type + " is currently open.");
355 }
356 }
357 }
358 }
359
364 void data (const DataEvent & event)
365 {
366 string tmp;
367 position = event.pos;
368 AppendCharStringtostring (event.data, incoming_data);
369 message_out(PARSER, "data event received from OpenSP, incoming_data is now: " + incoming_data);
370 }
371
376 void error (const ErrorEvent & event)
377 {
378 string message;
379 string string_buf;
380 OfxMsgType error_type = ERROR;
381
382 position = event.pos;
383 message = message + "OpenSP parser: ";
384 switch (event.type)
385 {
386 case SGMLApplication::ErrorEvent::quantity:
387 message = message + "quantity (Exceeding a quantity limit):";
388 error_type = ERROR;
389 break;
390 case SGMLApplication::ErrorEvent::idref:
391 message = message + "idref (An IDREF to a non-existent ID):";
392 error_type = ERROR;
393 break;
394 case SGMLApplication::ErrorEvent::capacity:
395 message = message + "capacity (Exceeding a capacity limit):";
396 error_type = ERROR;
397 break;
398 case SGMLApplication::ErrorEvent::otherError:
399 message = message + "otherError (misc parse error):";
400 error_type = ERROR;
401 break;
402 case SGMLApplication::ErrorEvent::warning:
403 message = message + "warning (Not actually an error.):";
404 error_type = WARNING;
405 break;
406 case SGMLApplication::ErrorEvent::info:
407 message = message + "info (An informationnal message. Not actually an error):";
408 error_type = INFO;
409 break;
410 default:
411 message = message + "OpenSP sent an unknown error to LibOFX (You probably have a newer version of OpenSP):";
412 }
413 message = message + "\n" + CharStringtostring (event.message, string_buf);
414 message_out (error_type, message);
415 }
416
421 void openEntityChange (const OpenEntityPtr & para_entity_ptr)
422 {
423 message_out(DEBUG, "openEntityChange()\n");
424 entity_ptr = para_entity_ptr;
425
426 };
427
428private:
429};
430
434int ofx_proc_sgml(LibofxContext * libofx_context, int argc, char * const* argv)
435{
436 message_out(DEBUG, "Begin ofx_proc_sgml()");
437 assert(argc >= 3);
438 message_out(DEBUG, argv[0]);
439 message_out(DEBUG, argv[1]);
440 message_out(DEBUG, argv[2]);
441
442 ParserEventGeneratorKit parserKit;
443 parserKit.setOption (ParserEventGeneratorKit::showOpenEntities);
444 EventGenerator *egp = parserKit.makeEventGenerator (argc, argv);
445 egp->inhibitMessages (true); /* Error output is handled by libofx not OpenSP */
446 OFXApplication *app = new OFXApplication(libofx_context);
447 unsigned nErrors = egp->run (*app); /* Begin parsing */
448 delete egp; //Note that this is where bug is triggered
449 return nErrors > 0;
450}
This object is driven by OpenSP as it parses the SGML from the ofx file(s)
Definition ofx_sgml.cpp:46
void data(const DataEvent &event)
Callback: Data from an OFX element.
Definition ofx_sgml.cpp:364
void error(const ErrorEvent &event)
Callback: SGML parse error.
Definition ofx_sgml.cpp:376
void openEntityChange(const OpenEntityPtr &para_entity_ptr)
Callback: Receive internal OpenSP state.
Definition ofx_sgml.cpp:421
void startElement(const StartElementEvent &event)
Callback: Start of an OFX element.
Definition ofx_sgml.cpp:72
void endElement(const EndElementEvent &event)
Callback: End of an OFX element.
Definition ofx_sgml.cpp:268
Represents a bank account or a credit card account.
Represents the <BALANCE>, <INVBAL> or <INV401KBAL> OFX SGML entity.
Represents a bank or credid card transaction.
A container to hold OFX SGML elements that LibOFX knows nothing about.
A generic container for an OFX SGML element. Every container inherits from OfxGenericContainer.
OfxGenericContainer * getparent()
Returns the parent container object (the one representing the containing OFX SGML element)
virtual void add_attribute(const string identifier, const string value)
Add data to a container object.
virtual int add_to_main_tree()
Add this container to the main tree.
A container to hold OFX SGML elements for <INV401K>
Represents a bank or credid card transaction.
The root container. Created by the <OFX> OFX element or by the export functions.
int gen_event()
Generate libofx.h events.
Represents an investment position, such as a stock or bond.
A container to hold a OFX SGML element for which you want the parent to process it's data elements.
Represents a security, such as a stock or bond.
Represents a statement for either a bank account or a credit card account.
Represents the <STATUS> OFX SGML entity.
Main header file containing the LibOfx API.
int message_out(OfxMsgType error_type, const string message)
Message output function.
Definition messages.cpp:61
Message IO functionality.
OfxMsgType
Definition messages.hh:24
@ DEBUG
Definition messages.hh:25
@ PARSER
Definition messages.hh:35
@ ERROR
Definition messages.hh:34
@ INFO
Definition messages.hh:32
@ WARNING
Definition messages.hh:33
LibOFX internal object code.
int ofx_proc_sgml(LibofxContext *libofx_context, int argc, char *const *argv)
Parses a DTD and OFX file(s)
Definition ofx_sgml.cpp:434
SGMLApplication::Position position
Definition messages.cpp:28
SGMLApplication::OpenEntityPtr entity_ptr
Definition messages.cpp:27
OFX/SGML parsing functionality.
string strip_whitespace(const string para_string)
Sanitize a string coming from OpenSP.
string AppendCharStringtostring(const SGMLApplication::CharString source, string &dest)
Append an OpenSP CharString to an existing C++ STL string.
string CharStringtostring(const SGMLApplication::CharString source, string &dest)
Convert OpenSP CharString to a C++ STL string.
Various simple functions for type conversion & al.