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