GnuCash  5.6-150-g038405b370+
gnc-pricedb-xml-v2.cpp
1 /********************************************************************
2  * gnc-pricedb-xml-v2.c -- xml routines for price db *
3  * Copyright (C) 2001 Gnumatic, Inc. *
4  * *
5  * This program is free software; you can redistribute it and/or *
6  * modify it under the terms of the GNU General Public License as *
7  * published by the Free Software Foundation; either version 2 of *
8  * the License, or (at your option) any later version. *
9  * *
10  * This program is distributed in the hope that it will be useful, *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13  * GNU General Public License for more details. *
14  * *
15  * You should have received a copy of the GNU General Public License*
16  * along with this program; if not, contact: *
17  * *
18  * Free Software Foundation Voice: +1-617-542-5942 *
19  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
20  * Boston, MA 02110-1301, USA gnu@gnu.org *
21  * *
22  *******************************************************************/
23 #include <config.h>
24 
25 #include <string.h>
26 #include "gnc-pricedb.h"
27 #include "gnc-pricedb-p.h"
28 
29 #include "gnc-xml.h"
30 #include "sixtp.h"
31 #include "sixtp-utils.h"
32 #include "sixtp-parsers.h"
33 #include "sixtp-dom-parsers.h"
34 #include "sixtp-dom-generators.h"
35 #include "io-gncxml-gen.h"
36 #include "io-gncxml-v2.h"
37 
38 /* This static indicates the debugging module that this .o belongs to. */
39 static QofLogModule log_module = GNC_MOD_IO;
40 
41 /* Read and Write the pricedb as XML -- something like this:
42 
43  <pricedb>
44  price-1
45  price-2
46  ...
47  </pricedb>
48 
49  where each price should look roughly like this:
50 
51  <price>
52  <price:id>
53  00000000111111112222222233333333
54  </price:id>
55  <price:commodity>
56  <cmdty:space>NASDAQ</cmdty:space>
57  <cmdty:id>RHAT</cmdty:id>
58  </price:commodity>
59  <price:currency>
60  <cmdty:space>ISO?</cmdty:space>
61  <cmdty:id>USD</cmdty:id>
62  </price:currency>
63  <price:time><ts:date>Mon ...</ts:date><ts:ns>12</ts:ns></price:time>
64  <price:source>Finance::Quote</price:source>
65  <price:type>bid</price:type>
66  <price:value>11011/100</price:value>
67  </price>
68 
69 */
70 
71 /***********************************************************************/
72 /* READING */
73 /***********************************************************************/
74 
75 /****************************************************************************/
76 /* <price>
77 
78  restores a price. Does so via a walk of the XML tree in memory.
79  Returns a GNCPrice * in result.
80 
81  Right now, a price is legitimate even if all of it's fields are not
82  set. We may need to change that later, but at the moment.
83 
84 */
85 
86 static gboolean
87 price_parse_xml_sub_node (GNCPrice* p, xmlNodePtr sub_node, QofBook* book)
88 {
89  if (!p || !sub_node) return FALSE;
90 
91  gnc_price_begin_edit (p);
92  if (g_strcmp0 ("price:id", (char*)sub_node->name) == 0)
93  {
94  GncGUID* c = dom_tree_to_guid (sub_node);
95  if (!c) return FALSE;
96  gnc_price_set_guid (p, c);
97  guid_free (c);
98  }
99  else if (g_strcmp0 ("price:commodity", (char*)sub_node->name) == 0)
100  {
101  gnc_commodity* c = dom_tree_to_commodity_ref (sub_node, book);
102  if (!c) return FALSE;
103  gnc_price_set_commodity (p, c);
104  }
105  else if (g_strcmp0 ("price:currency", (char*)sub_node->name) == 0)
106  {
107  gnc_commodity* c = dom_tree_to_commodity_ref (sub_node, book);
108  if (!c) return FALSE;
109  gnc_price_set_currency (p, c);
110  }
111  else if (g_strcmp0 ("price:time", (char*)sub_node->name) == 0)
112  {
113  time64 time = dom_tree_to_time64 (sub_node);
114  if (!dom_tree_valid_time64 (time, sub_node->name)) time = 0;
115  gnc_price_set_time64 (p, time);
116  }
117  else if (g_strcmp0 ("price:source", (char*)sub_node->name) == 0)
118  {
119  char* text = dom_tree_to_text (sub_node);
120  if (!text) return FALSE;
121  gnc_price_set_source_string (p, text);
122  g_free (text);
123  }
124  else if (g_strcmp0 ("price:type", (char*)sub_node->name) == 0)
125  {
126  char* text = dom_tree_to_text (sub_node);
127  if (!text) return FALSE;
128  gnc_price_set_typestr (p, text);
129  g_free (text);
130  }
131  else if (g_strcmp0 ("price:value", (char*)sub_node->name) == 0)
132  {
133  gnc_price_set_value (p, dom_tree_to_gnc_numeric (sub_node));
134  }
135  gnc_price_commit_edit (p);
136  return TRUE;
137 }
138 
139 static gboolean
140 price_parse_xml_end_handler (gpointer data_for_children,
141  GSList* data_from_children,
142  GSList* sibling_data,
143  gpointer parent_data,
144  gpointer global_data,
145  gpointer* result,
146  const gchar* tag)
147 {
148  gboolean ok = TRUE;
149  xmlNodePtr price_xml = (xmlNodePtr) data_for_children;
150  xmlNodePtr child;
151  GNCPrice* p = NULL;
152  gxpf_data* gdata = static_cast<decltype (gdata)> (global_data);
153  QofBook* book = static_cast<decltype (book)> (gdata->bookdata);
154 
155  /* we haven't been handed the *top* level node yet... */
156  if (parent_data) return TRUE;
157 
158  *result = NULL;
159 
160  if (!price_xml) return FALSE;
161  if (price_xml->next)
162  {
163  ok = FALSE;
164  goto cleanup_and_exit;
165  }
166  if (price_xml->prev)
167  {
168  ok = FALSE;
169  goto cleanup_and_exit;
170  }
171  if (!price_xml->xmlChildrenNode)
172  {
173  ok = FALSE;
174  goto cleanup_and_exit;
175  }
176 
177  p = gnc_price_create (book);
178  if (!p)
179  {
180  ok = FALSE;
181  goto cleanup_and_exit;
182  }
183 
184  for (child = price_xml->xmlChildrenNode; child; child = child->next)
185  {
186  switch (child->type)
187  {
188  case XML_COMMENT_NODE:
189  case XML_TEXT_NODE:
190  break;
191  case XML_ELEMENT_NODE:
192  if (!price_parse_xml_sub_node (p, child, book))
193  {
194  ok = FALSE;
195  goto cleanup_and_exit;
196  }
197  break;
198  default:
199  PERR ("Unknown node type (%d) while parsing gnc-price xml.", child->type);
200  child = NULL;
201  ok = FALSE;
202  goto cleanup_and_exit;
203  break;
204  }
205  }
206 
207 cleanup_and_exit:
208  if (ok)
209  {
210  *result = p;
211  }
212  else
213  {
214  *result = NULL;
215  gnc_price_unref (p);
216  }
217  xmlFreeNode (price_xml);
218  return ok;
219 }
220 
221 static void
222 cleanup_gnc_price (sixtp_child_result* result)
223 {
224  if (result->data) gnc_price_unref ((GNCPrice*) result->data);
225 }
226 
227 static sixtp*
228 gnc_price_parser_new (void)
229 {
230  return sixtp_dom_parser_new (price_parse_xml_end_handler,
231  cleanup_gnc_price,
232  cleanup_gnc_price);
233 }
234 
235 
236 /****************************************************************************/
237 /* <pricedb> (lineage <ledger-data>)
238 
239  restores a pricedb. We allocate the new db in the start block, the
240  children add to it, and it gets returned in result. Note that the
241  cleanup handler will destroy the pricedb, so the parent needs to
242  stop that if desired.
243 
244  result: GNCPriceDB*
245 
246  start: create new GNCPriceDB*, and leave in *data_for_children.
247  cleanup-result: destroy GNCPriceDB*
248  result-fail: destroy GNCPriceDB*
249 
250 */
251 
252 static gboolean
253 pricedb_start_handler (GSList* sibling_data,
254  gpointer parent_data,
255  gpointer global_data,
256  gpointer* data_for_children,
257  gpointer* result,
258  const gchar* tag,
259  gchar** attrs)
260 {
261  gxpf_data* gdata = static_cast<decltype (gdata)> (global_data);
262  QofBook* book = static_cast<decltype (book)> (gdata->bookdata);
263  GNCPriceDB* db = gnc_pricedb_get_db (book);
264  g_return_val_if_fail (db, FALSE);
265  gnc_pricedb_set_bulk_update (db, TRUE);
266  *result = db;
267  return (TRUE);
268 }
269 
270 static gboolean
271 pricedb_after_child_handler (gpointer data_for_children,
272  GSList* data_from_children,
273  GSList* sibling_data,
274  gpointer parent_data,
275  gpointer global_data,
276  gpointer* result,
277  const gchar* tag,
278  const gchar* child_tag,
279  sixtp_child_result* child_result)
280 {
281  gxpf_data* gdata = static_cast<decltype (gdata)> (global_data);
282  sixtp_gdv2* gd = static_cast<decltype (gd)> (gdata->parsedata);
283  GNCPriceDB* db = (GNCPriceDB*) * result;
284 
285  g_return_val_if_fail (db, FALSE);
286 
287  /* right now children have to produce results :> */
288  if (!child_result) return (FALSE);
289  if (child_result->type != SIXTP_CHILD_RESULT_NODE) return (FALSE);
290 
291  if (strcmp (child_result->tag, "price") == 0)
292  {
293  GNCPrice* p = (GNCPrice*) child_result->data;
294 
295  g_return_val_if_fail (p, FALSE);
296  gnc_pricedb_add_price (db, p);
297  gd->counter.prices_loaded++;
298  sixtp_run_callback (gd, "prices");
299  return TRUE;
300  }
301  else
302  {
303  PERR ("unexpected tag %s\n", child_result->tag);
304  return FALSE;
305  }
306  return FALSE;
307 }
308 
309 static void
310 pricedb_cleanup_result_handler (sixtp_child_result* result)
311 {
312  if (result->data)
313  {
314  GNCPriceDB* db = (GNCPriceDB*) result->data;
315  if (db) gnc_pricedb_destroy (db);
316  result->data = NULL;
317  }
318 }
319 
320 static gboolean
321 pricedb_v2_end_handler (
322  gpointer data_for_children, GSList* data_from_children,
323  GSList* sibling_data, gpointer parent_data, gpointer global_data,
324  gpointer* result, const gchar* tag)
325 {
326  GNCPriceDB* db = static_cast<decltype (db)> (*result);
327  gxpf_data* gdata = (gxpf_data*)global_data;
328 
329  if (parent_data)
330  {
331  return TRUE;
332  }
333 
334  if (!tag)
335  {
336  return TRUE;
337  }
338 
339  gdata->cb (tag, gdata->parsedata, db);
340  *result = NULL;
341 
342  gnc_pricedb_set_bulk_update (db, FALSE);
343 
344  return TRUE;
345 }
346 
347 static sixtp*
348 gnc_pricedb_parser_new (void)
349 {
350  sixtp* top_level;
351  sixtp* price_parser;
352 
353  top_level =
354  sixtp_set_any (sixtp_new (), TRUE,
355  SIXTP_START_HANDLER_ID, pricedb_start_handler,
356  SIXTP_AFTER_CHILD_HANDLER_ID, pricedb_after_child_handler,
357  SIXTP_CHARACTERS_HANDLER_ID,
358  allow_and_ignore_only_whitespace,
359  SIXTP_RESULT_FAIL_ID, pricedb_cleanup_result_handler,
360  SIXTP_CLEANUP_RESULT_ID, pricedb_cleanup_result_handler,
361  SIXTP_NO_MORE_HANDLERS);
362 
363  if (!top_level) return NULL;
364 
365  price_parser = gnc_price_parser_new ();
366 
367  if (!price_parser)
368  {
369  sixtp_destroy (top_level);
370  return NULL;
371  }
372 
373  sixtp_add_sub_parser (top_level, "price", price_parser);
374 
375  return top_level;
376 }
377 
378 sixtp*
379 gnc_pricedb_sixtp_parser_create (void)
380 {
381  sixtp* ret;
382  ret = gnc_pricedb_parser_new ();
383  sixtp_set_end (ret, pricedb_v2_end_handler);
384  return ret;
385 }
386 
387 
388 /***********************************************************************/
389 /* WRITING */
390 /***********************************************************************/
391 
392 static gboolean
393 add_child_or_kill_parent (xmlNodePtr parent, xmlNodePtr child)
394 {
395  if (!child)
396  {
397  xmlFreeNode (parent);
398  return FALSE;
399  }
400  xmlAddChild (parent, child);
401  return TRUE;
402 }
403 
404 static xmlNodePtr
405 gnc_price_to_dom_tree (const xmlChar* tag, GNCPrice* price)
406 {
407  xmlNodePtr price_xml;
408  const gchar* typestr, *sourcestr;
409  xmlNodePtr tmpnode;
410  gnc_commodity* commodity;
411  gnc_commodity* currency;
412  time64 time;
413  gnc_numeric value;
414 
415  if (! (tag && price)) return NULL;
416 
417  price_xml = xmlNewNode (NULL, tag);
418  if (!price_xml) return NULL;
419 
420  commodity = gnc_price_get_commodity (price);
421  currency = gnc_price_get_currency (price);
422 
423  if (! (commodity && currency)) return NULL;
424 
425  tmpnode = guid_to_dom_tree ("price:id", gnc_price_get_guid (price));
426  if (!add_child_or_kill_parent (price_xml, tmpnode)) return NULL;
427 
428  tmpnode = commodity_ref_to_dom_tree ("price:commodity", commodity);
429  if (!add_child_or_kill_parent (price_xml, tmpnode)) return NULL;
430 
431  tmpnode = commodity_ref_to_dom_tree ("price:currency", currency);
432  if (!add_child_or_kill_parent (price_xml, tmpnode)) return NULL;
433 
434  time = gnc_price_get_time64 (price);
435  tmpnode = time64_to_dom_tree ("price:time", time);
436  if (!add_child_or_kill_parent (price_xml, tmpnode)) return NULL;
437 
438  sourcestr = gnc_price_get_source_string (price);
439  if (sourcestr && *sourcestr)
440  {
441  tmpnode = text_to_dom_tree ("price:source", sourcestr);
442  if (!add_child_or_kill_parent (price_xml, tmpnode)) return NULL;
443  }
444 
445  typestr = gnc_price_get_typestr (price);
446  if (typestr && *typestr)
447  {
448  tmpnode = text_to_dom_tree ("price:type", typestr);
449  if (!add_child_or_kill_parent (price_xml, tmpnode)) return NULL;
450  }
451 
452  value = gnc_price_get_value (price);
453  tmpnode = gnc_numeric_to_dom_tree ("price:value", &value);
454  if (!add_child_or_kill_parent (price_xml, tmpnode)) return NULL;
455 
456  return price_xml;
457 }
458 
459 static gboolean
460 xml_add_gnc_price_adapter (GNCPrice* p, gpointer data)
461 {
462  xmlNodePtr xml_node = (xmlNodePtr) data;
463 
464  if (p)
465  {
466  xmlNodePtr price_xml = gnc_price_to_dom_tree (BAD_CAST "price", p);
467  if (!price_xml) return FALSE;
468  xmlAddChild (xml_node, price_xml);
469  return TRUE;
470  }
471  else
472  {
473  return TRUE;
474  }
475 }
476 
477 static xmlNodePtr
478 gnc_pricedb_to_dom_tree (const xmlChar* tag, GNCPriceDB* db)
479 {
480  xmlNodePtr db_xml = NULL;
481 
482  if (!tag) return NULL;
483 
484  db_xml = xmlNewNode (NULL, tag);
485  if (!db_xml) return NULL;
486 
487  xmlSetProp (db_xml, BAD_CAST "version", BAD_CAST "1");
488 
489  if (!gnc_pricedb_foreach_price (db, xml_add_gnc_price_adapter, db_xml, TRUE))
490  {
491  xmlFreeNode (db_xml);
492  return NULL;
493  }
494 
495  /* if no children have been added just return NULL */
496  if (!db_xml->xmlChildrenNode)
497  {
498  xmlFreeNode (db_xml);
499  return NULL;
500  }
501 
502  return db_xml;
503 }
504 
505 xmlNodePtr
506 gnc_pricedb_dom_tree_create (GNCPriceDB* db)
507 {
508  return gnc_pricedb_to_dom_tree (BAD_CAST "gnc:pricedb", db);
509 }
GNCPrice * gnc_price_create(QofBook *book)
gnc_price_create - returns a newly allocated and initialized price with a reference count of 1...
Definition: sixtp.h:129
a simple price database for gnucash
void gnc_price_unref(GNCPrice *p)
gnc_price_unref - indicate you&#39;re finished with a price (i.e.
gboolean gnc_pricedb_add_price(GNCPriceDB *db, GNCPrice *p)
Add a price to the pricedb.
#define PERR(format, args...)
Log a serious error.
Definition: qoflog.h:244
GNCPriceDB * gnc_pricedb_get_db(QofBook *book)
Return the pricedb associated with the book.
api for GnuCash version 2 XML-based file format
void gnc_pricedb_destroy(GNCPriceDB *db)
Destroy the given pricedb and unref all of the prices it contains.
void gnc_pricedb_set_bulk_update(GNCPriceDB *db, gboolean bulk_update)
Set flag to indicate whether duplication checks should be performed.
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition: gnc-date.h:87
gboolean gnc_pricedb_foreach_price(GNCPriceDB *db, GncPriceForeachFunc f, gpointer user_data, gboolean stable_order)
Call a GncPriceForeachFunction once for each price in db, until the function returns FALSE...
The type used to store guids in C.
Definition: guid.h:75