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