Branch data Line data Source code
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 : 5222 : price_parse_xml_sub_node (GNCPrice* p, xmlNodePtr sub_node, QofBook* book)
88 : : {
89 : 5222 : if (!p || !sub_node) return FALSE;
90 : :
91 : 5222 : gnc_price_begin_edit (p);
92 : 5222 : if (g_strcmp0 ("price:id", (char*)sub_node->name) == 0)
93 : : {
94 : 746 : GncGUID* c = dom_tree_to_guid (sub_node);
95 : 746 : if (!c) return FALSE;
96 : 746 : gnc_price_set_guid (p, c);
97 : 746 : guid_free (c);
98 : : }
99 : 4476 : else if (g_strcmp0 ("price:commodity", (char*)sub_node->name) == 0)
100 : : {
101 : 746 : gnc_commodity* c = dom_tree_to_commodity_ref (sub_node, book);
102 : 746 : if (!c) return FALSE;
103 : 746 : gnc_price_set_commodity (p, c);
104 : : }
105 : 3730 : else if (g_strcmp0 ("price:currency", (char*)sub_node->name) == 0)
106 : : {
107 : 746 : gnc_commodity* c = dom_tree_to_commodity_ref (sub_node, book);
108 : 746 : if (!c) return FALSE;
109 : 746 : gnc_price_set_currency (p, c);
110 : : }
111 : 2984 : else if (g_strcmp0 ("price:time", (char*)sub_node->name) == 0)
112 : : {
113 : 746 : time64 time = dom_tree_to_time64 (sub_node);
114 : 746 : if (!dom_tree_valid_time64 (time, sub_node->name)) time = 0;
115 : 746 : gnc_price_set_time64 (p, time);
116 : : }
117 : 2238 : else if (g_strcmp0 ("price:source", (char*)sub_node->name) == 0)
118 : : {
119 : 746 : char* text = dom_tree_to_text (sub_node);
120 : 746 : if (!text) return FALSE;
121 : 746 : gnc_price_set_source_string (p, text);
122 : 746 : g_free (text);
123 : : }
124 : 1492 : else if (g_strcmp0 ("price:type", (char*)sub_node->name) == 0)
125 : : {
126 : 746 : char* text = dom_tree_to_text (sub_node);
127 : 746 : if (!text) return FALSE;
128 : 746 : gnc_price_set_typestr (p, text);
129 : 746 : g_free (text);
130 : : }
131 : 746 : else if (g_strcmp0 ("price:value", (char*)sub_node->name) == 0)
132 : : {
133 : 746 : gnc_price_set_value (p, dom_tree_to_gnc_numeric (sub_node));
134 : : }
135 : 5222 : gnc_price_commit_edit (p);
136 : 5222 : return TRUE;
137 : : }
138 : :
139 : : static gboolean
140 : 9698 : 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 : 9698 : gboolean ok = TRUE;
149 : 9698 : xmlNodePtr price_xml = (xmlNodePtr) data_for_children;
150 : : xmlNodePtr child;
151 : 9698 : GNCPrice* p = NULL;
152 : 9698 : gxpf_data* gdata = static_cast<decltype (gdata)> (global_data);
153 : 9698 : QofBook* book = static_cast<decltype (book)> (gdata->bookdata);
154 : :
155 : : /* we haven't been handed the *top* level node yet... */
156 : 9698 : if (parent_data) return TRUE;
157 : :
158 : 746 : *result = NULL;
159 : :
160 : 746 : if (!price_xml) return FALSE;
161 : 746 : if (price_xml->next)
162 : : {
163 : 0 : ok = FALSE;
164 : 0 : goto cleanup_and_exit;
165 : : }
166 : 746 : if (price_xml->prev)
167 : : {
168 : 0 : ok = FALSE;
169 : 0 : goto cleanup_and_exit;
170 : : }
171 : 746 : if (!price_xml->xmlChildrenNode)
172 : : {
173 : 0 : ok = FALSE;
174 : 0 : goto cleanup_and_exit;
175 : : }
176 : :
177 : 746 : p = gnc_price_create (book);
178 : 746 : if (!p)
179 : : {
180 : 0 : ok = FALSE;
181 : 0 : goto cleanup_and_exit;
182 : : }
183 : :
184 : 11936 : for (child = price_xml->xmlChildrenNode; child; child = child->next)
185 : : {
186 : 11190 : switch (child->type)
187 : : {
188 : 5968 : case XML_COMMENT_NODE:
189 : : case XML_TEXT_NODE:
190 : 5968 : break;
191 : 5222 : case XML_ELEMENT_NODE:
192 : 5222 : if (!price_parse_xml_sub_node (p, child, book))
193 : : {
194 : 0 : ok = FALSE;
195 : 0 : goto cleanup_and_exit;
196 : : }
197 : 5222 : break;
198 : 0 : default:
199 : 0 : PERR ("Unknown node type (%d) while parsing gnc-price xml.", child->type);
200 : 0 : child = NULL;
201 : 0 : ok = FALSE;
202 : 0 : goto cleanup_and_exit;
203 : : break;
204 : : }
205 : : }
206 : :
207 : 746 : cleanup_and_exit:
208 : 746 : if (ok)
209 : : {
210 : 746 : *result = p;
211 : : }
212 : : else
213 : : {
214 : 0 : *result = NULL;
215 : 0 : gnc_price_unref (p);
216 : : }
217 : 746 : xmlFreeNode (price_xml);
218 : 746 : return ok;
219 : : }
220 : :
221 : : static void
222 : 746 : cleanup_gnc_price (sixtp_child_result* result)
223 : : {
224 : 746 : if (result->data) gnc_price_unref ((GNCPrice*) result->data);
225 : 746 : }
226 : :
227 : : static sixtp*
228 : 62 : gnc_price_parser_new (void)
229 : : {
230 : 62 : return sixtp_dom_parser_new (price_parse_xml_end_handler,
231 : : cleanup_gnc_price,
232 : 62 : 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 : 21 : 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 : 21 : gxpf_data* gdata = static_cast<decltype (gdata)> (global_data);
262 : 21 : QofBook* book = static_cast<decltype (book)> (gdata->bookdata);
263 : 21 : GNCPriceDB* db = gnc_pricedb_get_db (book);
264 : 21 : g_return_val_if_fail (db, FALSE);
265 : 21 : gnc_pricedb_set_bulk_update (db, TRUE);
266 : 21 : *result = db;
267 : 21 : return (TRUE);
268 : : }
269 : :
270 : : static gboolean
271 : 746 : 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 : 746 : gxpf_data* gdata = static_cast<decltype (gdata)> (global_data);
282 : 746 : sixtp_gdv2* gd = static_cast<decltype (gd)> (gdata->parsedata);
283 : 746 : GNCPriceDB* db = (GNCPriceDB*) * result;
284 : :
285 : 746 : g_return_val_if_fail (db, FALSE);
286 : :
287 : : /* right now children have to produce results :> */
288 : 746 : if (!child_result) return (FALSE);
289 : 746 : if (child_result->type != SIXTP_CHILD_RESULT_NODE) return (FALSE);
290 : :
291 : 746 : if (strcmp (child_result->tag, "price") == 0)
292 : : {
293 : 746 : GNCPrice* p = (GNCPrice*) child_result->data;
294 : :
295 : 746 : g_return_val_if_fail (p, FALSE);
296 : 746 : gnc_pricedb_add_price (db, p);
297 : 746 : gd->counter.prices_loaded++;
298 : 746 : sixtp_run_callback (gd, "prices");
299 : 746 : return TRUE;
300 : : }
301 : : else
302 : : {
303 : 0 : PERR ("unexpected tag %s\n", child_result->tag);
304 : 0 : return FALSE;
305 : : }
306 : : return FALSE;
307 : : }
308 : :
309 : : static void
310 : 0 : pricedb_cleanup_result_handler (sixtp_child_result* result)
311 : : {
312 : 0 : if (result->data)
313 : : {
314 : 0 : GNCPriceDB* db = (GNCPriceDB*) result->data;
315 : 0 : if (db) gnc_pricedb_destroy (db);
316 : 0 : result->data = NULL;
317 : : }
318 : 0 : }
319 : :
320 : : static gboolean
321 : 21 : 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 : 21 : GNCPriceDB* db = static_cast<decltype (db)> (*result);
327 : 21 : gxpf_data* gdata = (gxpf_data*)global_data;
328 : :
329 : 21 : if (parent_data)
330 : : {
331 : 0 : return TRUE;
332 : : }
333 : :
334 : 21 : if (!tag)
335 : : {
336 : 0 : return TRUE;
337 : : }
338 : :
339 : 21 : gdata->cb (tag, gdata->parsedata, db);
340 : 21 : *result = NULL;
341 : :
342 : 21 : gnc_pricedb_set_bulk_update (db, FALSE);
343 : :
344 : 21 : return TRUE;
345 : : }
346 : :
347 : : static sixtp*
348 : 62 : gnc_pricedb_parser_new (void)
349 : : {
350 : : sixtp* top_level;
351 : : sixtp* price_parser;
352 : :
353 : : top_level =
354 : 62 : 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 : 62 : if (!top_level) return NULL;
364 : :
365 : 62 : price_parser = gnc_price_parser_new ();
366 : :
367 : 62 : if (!price_parser)
368 : : {
369 : 0 : sixtp_destroy (top_level);
370 : 0 : return NULL;
371 : : }
372 : :
373 : 62 : sixtp_add_sub_parser (top_level, "price", price_parser);
374 : :
375 : 62 : return top_level;
376 : : }
377 : :
378 : : sixtp*
379 : 62 : gnc_pricedb_sixtp_parser_create (void)
380 : : {
381 : : sixtp* ret;
382 : 62 : ret = gnc_pricedb_parser_new ();
383 : 62 : sixtp_set_end (ret, pricedb_v2_end_handler);
384 : 62 : return ret;
385 : : }
386 : :
387 : :
388 : : /***********************************************************************/
389 : : /* WRITING */
390 : : /***********************************************************************/
391 : :
392 : : static gboolean
393 : 2926 : add_child_or_kill_parent (xmlNodePtr parent, xmlNodePtr child)
394 : : {
395 : 2926 : if (!child)
396 : : {
397 : 0 : xmlFreeNode (parent);
398 : 0 : return FALSE;
399 : : }
400 : 2926 : xmlAddChild (parent, child);
401 : 2926 : return TRUE;
402 : : }
403 : :
404 : : static xmlNodePtr
405 : 418 : 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 : 418 : if (! (tag && price)) return NULL;
416 : :
417 : 418 : price_xml = xmlNewNode (NULL, tag);
418 : 418 : if (!price_xml) return NULL;
419 : :
420 : 418 : commodity = gnc_price_get_commodity (price);
421 : 418 : currency = gnc_price_get_currency (price);
422 : :
423 : 418 : if (! (commodity && currency)) return NULL;
424 : :
425 : 418 : tmpnode = guid_to_dom_tree ("price:id", gnc_price_get_guid (price));
426 : 418 : if (!add_child_or_kill_parent (price_xml, tmpnode)) return NULL;
427 : :
428 : 418 : tmpnode = commodity_ref_to_dom_tree ("price:commodity", commodity);
429 : 418 : if (!add_child_or_kill_parent (price_xml, tmpnode)) return NULL;
430 : :
431 : 418 : tmpnode = commodity_ref_to_dom_tree ("price:currency", currency);
432 : 418 : if (!add_child_or_kill_parent (price_xml, tmpnode)) return NULL;
433 : :
434 : 418 : time = gnc_price_get_time64 (price);
435 : 418 : tmpnode = time64_to_dom_tree ("price:time", time);
436 : 418 : if (!add_child_or_kill_parent (price_xml, tmpnode)) return NULL;
437 : :
438 : 418 : sourcestr = gnc_price_get_source_string (price);
439 : 418 : if (sourcestr && *sourcestr)
440 : : {
441 : 418 : tmpnode = text_to_dom_tree ("price:source", sourcestr);
442 : 418 : if (!add_child_or_kill_parent (price_xml, tmpnode)) return NULL;
443 : : }
444 : :
445 : 418 : typestr = gnc_price_get_typestr (price);
446 : 418 : if (typestr && *typestr)
447 : : {
448 : 418 : tmpnode = text_to_dom_tree ("price:type", typestr);
449 : 418 : if (!add_child_or_kill_parent (price_xml, tmpnode)) return NULL;
450 : : }
451 : :
452 : 418 : value = gnc_price_get_value (price);
453 : 418 : tmpnode = gnc_numeric_to_dom_tree ("price:value", &value);
454 : 418 : if (!add_child_or_kill_parent (price_xml, tmpnode)) return NULL;
455 : :
456 : 418 : return price_xml;
457 : : }
458 : :
459 : : static gboolean
460 : 418 : xml_add_gnc_price_adapter (GNCPrice* p, gpointer data)
461 : : {
462 : 418 : xmlNodePtr xml_node = (xmlNodePtr) data;
463 : :
464 : 418 : if (p)
465 : : {
466 : 418 : xmlNodePtr price_xml = gnc_price_to_dom_tree (BAD_CAST "price", p);
467 : 418 : if (!price_xml) return FALSE;
468 : 418 : xmlAddChild (xml_node, price_xml);
469 : 418 : return TRUE;
470 : : }
471 : : else
472 : : {
473 : 0 : return TRUE;
474 : : }
475 : : }
476 : :
477 : : static xmlNodePtr
478 : 24 : gnc_pricedb_to_dom_tree (const xmlChar* tag, GNCPriceDB* db)
479 : : {
480 : 24 : xmlNodePtr db_xml = NULL;
481 : :
482 : 24 : if (!tag) return NULL;
483 : :
484 : 24 : db_xml = xmlNewNode (NULL, tag);
485 : 24 : if (!db_xml) return NULL;
486 : :
487 : 24 : xmlSetProp (db_xml, BAD_CAST "version", BAD_CAST "1");
488 : :
489 : 24 : if (!gnc_pricedb_foreach_price (db, xml_add_gnc_price_adapter, db_xml, TRUE))
490 : : {
491 : 0 : xmlFreeNode (db_xml);
492 : 0 : return NULL;
493 : : }
494 : :
495 : : /* if no children have been added just return NULL */
496 : 24 : if (!db_xml->xmlChildrenNode)
497 : : {
498 : 4 : xmlFreeNode (db_xml);
499 : 4 : return NULL;
500 : : }
501 : :
502 : 20 : return db_xml;
503 : : }
504 : :
505 : : xmlNodePtr
506 : 24 : gnc_pricedb_dom_tree_create (GNCPriceDB* db)
507 : : {
508 : 24 : return gnc_pricedb_to_dom_tree (BAD_CAST "gnc:pricedb", db);
509 : : }
|