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 using namespace std;
37 
38 OfxMainContainer * MainContainer = NULL;
39 extern SGMLApplication::OpenEntityPtr entity_ptr;
40 extern SGMLApplication::Position position;
41 
42 
45 class OFXApplication : public SGMLApplication
46 {
47 private:
48  OfxGenericContainer *curr_container_element;
49  OfxGenericContainer *tmp_container_element;
50  bool is_data_element;
51  string incoming_data;
52  LibofxContext * libofx_context;
53 
54 public:
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")
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->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  curr_container_element = new OfxBankTransactionContainer (libofx_context, curr_container_element, identifier);
142  }
143  else if (identifier == "BUYDEBT" ||
144  identifier == "BUYMF" ||
145  identifier == "BUYOPT" ||
146  identifier == "BUYOTHER" ||
147  identifier == "BUYSTOCK" ||
148  identifier == "CLOSUREOPT" ||
149  identifier == "INCOME" ||
150  identifier == "INVEXPENSE" ||
151  identifier == "JRNLFUND" ||
152  identifier == "JRNLSEC" ||
153  identifier == "MARGININTEREST" ||
154  identifier == "REINVEST" ||
155  identifier == "RETOFCAP" ||
156  identifier == "SELLDEBT" ||
157  identifier == "SELLMF" ||
158  identifier == "SELLOPT" ||
159  identifier == "SELLOTHER" ||
160  identifier == "SELLSTOCK" ||
161  identifier == "SPLIT" ||
162  identifier == "TRANSFER" )
163  {
164  message_out (PARSER, "Element " + identifier + " found");
165  curr_container_element = new OfxInvestmentTransactionContainer (libofx_context, curr_container_element, identifier);
166  }
167  /*The following is a list of OFX elements whose attributes will be processed by the parent container*/
168  else if (identifier == "INVBUY" ||
169  identifier == "INVSELL" ||
170  identifier == "INVTRAN" ||
171  identifier == "SECID")
172  {
173  message_out (PARSER, "Element " + identifier + " found");
174  curr_container_element = new OfxPushUpContainer (libofx_context, curr_container_element, identifier);
175  }
176 
177  /* The different types of accounts */
178  else if (identifier == "BANKACCTFROM" || identifier == "CCACCTFROM" || identifier == "INVACCTFROM")
179  {
180  message_out (PARSER, "Element " + identifier + " found");
181  curr_container_element = new OfxAccountContainer (libofx_context, curr_container_element, identifier);
182  }
183  else if (identifier == "SECINFO")
184  {
185  message_out (PARSER, "Element " + identifier + " found");
186  curr_container_element = new OfxSecurityContainer (libofx_context, curr_container_element, identifier);
187  }
188  /* The different types of balances */
189  else if (identifier == "LEDGERBAL" || identifier == "AVAILBAL")
190  {
191  message_out (PARSER, "Element " + identifier + " found");
192  curr_container_element = new OfxBalanceContainer (libofx_context, curr_container_element, identifier);
193  }
194  else
195  {
196  /* We dont know this OFX element, so we create a dummy container */
197  curr_container_element = new OfxDummyContainer(libofx_context, curr_container_element, identifier);
198  }
199  }
200  else
201  {
202  /* The element was a data element. OpenSP will call one or several data() callback with the data */
203  message_out (PARSER, "Data element " + identifier + " found");
204  /* 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 skiped. So we empty it, so at least the last element has a chance of having valid data */
205  if (incoming_data != "")
206  {
207  message_out (ERROR, "startElement: incoming_data should be empty! You are probably using OpenSP <= 1.3.4. The following data was lost: " + incoming_data );
208  incoming_data.assign ("");
209  }
210  }
211  }
212 
217  void endElement (const EndElementEvent & event)
218  {
219  string identifier;
220  bool end_element_for_data_element;
221 
222  CharStringtostring (event.gi, identifier);
223  end_element_for_data_element = is_data_element;
224  message_out(PARSER, "endElement event received from OpenSP for element " + identifier);
225 
226  position = event.pos;
227  if (curr_container_element == NULL)
228  {
229  message_out (ERROR, "Tried to close a " + identifier + " without a open element (NULL pointer)");
230  incoming_data.assign ("");
231  }
232  else //curr_container_element != NULL
233  {
234  if (end_element_for_data_element == true)
235  {
236  incoming_data = strip_whitespace(incoming_data);
237 
238  curr_container_element->add_attribute (identifier, incoming_data);
239  message_out (PARSER, "endElement: Added data '" + incoming_data + "' from " + identifier + " to " + curr_container_element->type + " container_element");
240  incoming_data.assign ("");
241  is_data_element = false;
242  }
243  else
244  {
245  if (identifier == curr_container_element->tag_identifier)
246  {
247  if (incoming_data != "")
248  {
249  message_out(ERROR, "End tag for non data element " + identifier + ", incoming data should be empty but contains: " + incoming_data + " DATA HAS BEEN LOST SOMEWHERE!");
250  }
251 
252  if (identifier == "OFX")
253  {
254  /* The main container is a special case */
255  tmp_container_element = curr_container_element;
256  curr_container_element = curr_container_element->getparent ();
257  if (curr_container_element == NULL)
258  {
259  //Defensive coding, this isn't supposed to happen
260  curr_container_element = tmp_container_element;
261  }
262  if (MainContainer != NULL)
263  {
264  MainContainer->gen_event();
265  delete MainContainer;
266  MainContainer = NULL;
267  curr_container_element = NULL;
268  message_out (DEBUG, "Element " + identifier + " closed, MainContainer destroyed");
269  }
270  else
271  {
272  message_out (DEBUG, "Element " + identifier + " closed, but there was no MainContainer to destroy (probably a malformed file)!");
273  }
274  }
275  else
276  {
277  tmp_container_element = curr_container_element;
278  curr_container_element = curr_container_element->getparent ();
279  if (MainContainer != NULL)
280  {
281  tmp_container_element->add_to_main_tree();
282  message_out (PARSER, "Element " + identifier + " closed, object added to MainContainer");
283  }
284  else
285  {
286  message_out (ERROR, "MainContainer is NULL trying to add element " + identifier);
287  }
288  }
289  }
290  else
291  {
292  message_out (ERROR, "Tried to close a " + identifier + " but a " + curr_container_element->type + " is currently open.");
293  }
294  }
295  }
296  }
297 
302  void data (const DataEvent & event)
303  {
304  string tmp;
305  position = event.pos;
306  AppendCharStringtostring (event.data, incoming_data);
307  message_out(PARSER, "data event received from OpenSP, incoming_data is now: " + incoming_data);
308  }
309 
314  void error (const ErrorEvent & event)
315  {
316  string message;
317  string string_buf;
318  OfxMsgType error_type = ERROR;
319 
320  position = event.pos;
321  message = message + "OpenSP parser: ";
322  switch (event.type)
323  {
324  case SGMLApplication::ErrorEvent::quantity:
325  message = message + "quantity (Exceeding a quantity limit):";
326  error_type = ERROR;
327  break;
328  case SGMLApplication::ErrorEvent::idref:
329  message = message + "idref (An IDREF to a non-existent ID):";
330  error_type = ERROR;
331  break;
332  case SGMLApplication::ErrorEvent::capacity:
333  message = message + "capacity (Exceeding a capacity limit):";
334  error_type = ERROR;
335  break;
336  case SGMLApplication::ErrorEvent::otherError:
337  message = message + "otherError (misc parse error):";
338  error_type = ERROR;
339  break;
340  case SGMLApplication::ErrorEvent::warning:
341  message = message + "warning (Not actually an error.):";
342  error_type = WARNING;
343  break;
344  case SGMLApplication::ErrorEvent::info:
345  message = message + "info (An informationnal message. Not actually an error):";
346  error_type = INFO;
347  break;
348  default:
349  message = message + "OpenSP sent an unknown error to LibOFX (You probably have a newer version of OpenSP):";
350  }
351  message = message + "\n" + CharStringtostring (event.message, string_buf);
352  message_out (error_type, message);
353  }
354 
359  void openEntityChange (const OpenEntityPtr & para_entity_ptr)
360  {
361  message_out(DEBUG, "openEntityChange()\n");
362  entity_ptr = para_entity_ptr;
363 
364  };
365 
366 private:
367 };
368 
372 int ofx_proc_sgml(LibofxContext * libofx_context, int argc, char * const* argv)
373 {
374  message_out(DEBUG, "Begin ofx_proc_sgml()");
375  assert(argc >= 3);
376  message_out(DEBUG, argv[0]);
377  message_out(DEBUG, argv[1]);
378  message_out(DEBUG, argv[2]);
379 
380  ParserEventGeneratorKit parserKit;
381  parserKit.setOption (ParserEventGeneratorKit::showOpenEntities);
382  EventGenerator *egp = parserKit.makeEventGenerator (argc, argv);
383  egp->inhibitMessages (true); /* Error output is handled by libofx not OpenSP */
384  OFXApplication *app = new OFXApplication(libofx_context);
385  unsigned nErrors = egp->run (*app); /* Begin parsing */
386  delete egp; //Note that this is where bug is triggered
387  return nErrors > 0;
388 }
Represents a security, such as a stock or bond.
A generic container for an OFX SGML element. Every container inherits from OfxGenericContainer.
Definition: messages.hh:32
void openEntityChange(const OpenEntityPtr &para_entity_ptr)
Callback: Receive internal OpenSP state.
Definition: ofx_sgml.cpp:359
SGMLApplication::Position position
Definition: messages.cpp:28
string AppendCharStringtostring(const SGMLApplication::CharString source, string &dest)
Append an OpenSP CharString to an existing C++ STL string.
void error(const ErrorEvent &event)
Callback: SGML parse error.
Definition: ofx_sgml.cpp:314
void data(const DataEvent &event)
Callback: Data from an OFX element.
Definition: ofx_sgml.cpp:302
int message_out(OfxMsgType error_type, const string message)
Message output function.
Definition: messages.cpp:60
OFX/SGML parsing functionnality.
OfxMsgType
Definition: messages.hh:23
string CharStringtostring(const SGMLApplication::CharString source, string &dest)
Convert OpenSP CharString to a C++ STL string.
Various simple functions for type conversion & al.
int ofx_proc_sgml(LibofxContext *libofx_context, int argc, char *const *argv)
Parses a DTD and OFX file(s)
Definition: ofx_sgml.cpp:372
Represents a statement for either a bank account or a credit card account.
Represents a bank or credid card transaction.
A container to hold a OFX SGML element for which you want the parent to process it's data elements...
void startElement(const StartElementEvent &event)
Callback: Start of an OFX element.
Definition: ofx_sgml.cpp:72
SGMLApplication::OpenEntityPtr entity_ptr
Definition: messages.cpp:27
string strip_whitespace(const string para_string)
Sanitize a string coming from OpenSP.
LibOFX internal object code.
Message IO functionality.
Represents a bank or credid card transaction.
Represents a bank account or a credit card account.
The root container. Created by the <OFX> OFX element or by the export functions.
int gen_event()
Generate libofx.h events.
This object is driven by OpenSP as it parses the SGML from the ofx file(s)
Definition: ofx_sgml.cpp:45
void endElement(const EndElementEvent &event)
Callback: End of an OFX element.
Definition: ofx_sgml.cpp:217
A container to holds OFX SGML elements that LibOFX knows nothing about.
Represents the <BALANCE> OFX SGML entity.
Represents the <STATUS> OFX SGML entity.