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