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 : auto c = dom_tree_to_guid (sub_node);
95 : 746 : if (!c) return FALSE;
96 : 746 : gnc_price_set_guid (p, &*c);
97 : : }
98 : 4476 : else if (g_strcmp0 ("price:commodity", (char*)sub_node->name) == 0)
99 : : {
100 : 746 : gnc_commodity* c = dom_tree_to_commodity_ref (sub_node, book);
101 : 746 : if (!c) return FALSE;
102 : 746 : gnc_price_set_commodity (p, c);
103 : : }
104 : 3730 : else if (g_strcmp0 ("price:currency", (char*)sub_node->name) == 0)
105 : : {
106 : 746 : gnc_commodity* c = dom_tree_to_commodity_ref (sub_node, book);
107 : 746 : if (!c) return FALSE;
108 : 746 : gnc_price_set_currency (p, c);
109 : : }
110 : 2984 : else if (g_strcmp0 ("price:time", (char*)sub_node->name) == 0)
111 : : {
112 : 746 : time64 time = dom_tree_to_time64 (sub_node);
113 : 746 : if (!dom_tree_valid_time64 (time, sub_node->name)) time = 0;
114 : 746 : gnc_price_set_time64 (p, time);
115 : : }
116 : 2238 : else if (g_strcmp0 ("price:source", (char*)sub_node->name) == 0)
117 : : {
118 : 746 : if (!apply_xmlnode_text (gnc_price_set_source_string, p, sub_node))
119 : 0 : return FALSE;
120 : : }
121 : 1492 : else if (g_strcmp0 ("price:type", (char*)sub_node->name) == 0)
122 : : {
123 : 746 : if (!apply_xmlnode_text (gnc_price_set_typestr, p, sub_node))
124 : 0 : return FALSE;
125 : : }
126 : 746 : else if (g_strcmp0 ("price:value", (char*)sub_node->name) == 0)
127 : : {
128 : 746 : gnc_price_set_value (p, dom_tree_to_gnc_numeric (sub_node));
129 : : }
130 : 5222 : gnc_price_commit_edit (p);
131 : 5222 : return TRUE;
132 : : }
133 : :
134 : : static gboolean
135 : 9698 : price_parse_xml_end_handler (gpointer data_for_children,
136 : : GSList* data_from_children,
137 : : GSList* sibling_data,
138 : : gpointer parent_data,
139 : : gpointer global_data,
140 : : gpointer* result,
141 : : const gchar* tag)
142 : : {
143 : 9698 : gboolean ok = TRUE;
144 : 9698 : xmlNodePtr price_xml = (xmlNodePtr) data_for_children;
145 : : xmlNodePtr child;
146 : 9698 : GNCPrice* p = NULL;
147 : 9698 : gxpf_data* gdata = static_cast<decltype (gdata)> (global_data);
148 : 9698 : QofBook* book = static_cast<decltype (book)> (gdata->bookdata);
149 : :
150 : : /* we haven't been handed the *top* level node yet... */
151 : 9698 : if (parent_data) return TRUE;
152 : :
153 : 746 : *result = NULL;
154 : :
155 : 746 : if (!price_xml) return FALSE;
156 : 746 : if (price_xml->next)
157 : : {
158 : 0 : ok = FALSE;
159 : 0 : goto cleanup_and_exit;
160 : : }
161 : 746 : if (price_xml->prev)
162 : : {
163 : 0 : ok = FALSE;
164 : 0 : goto cleanup_and_exit;
165 : : }
166 : 746 : if (!price_xml->xmlChildrenNode)
167 : : {
168 : 0 : ok = FALSE;
169 : 0 : goto cleanup_and_exit;
170 : : }
171 : :
172 : 746 : p = gnc_price_create (book);
173 : 746 : if (!p)
174 : : {
175 : 0 : ok = FALSE;
176 : 0 : goto cleanup_and_exit;
177 : : }
178 : :
179 : 11936 : for (child = price_xml->xmlChildrenNode; child; child = child->next)
180 : : {
181 : 11190 : switch (child->type)
182 : : {
183 : 5968 : case XML_COMMENT_NODE:
184 : : case XML_TEXT_NODE:
185 : 5968 : break;
186 : 5222 : case XML_ELEMENT_NODE:
187 : 5222 : if (!price_parse_xml_sub_node (p, child, book))
188 : : {
189 : 0 : ok = FALSE;
190 : 0 : goto cleanup_and_exit;
191 : : }
192 : 5222 : break;
193 : 0 : default:
194 : 0 : PERR ("Unknown node type (%d) while parsing gnc-price xml.", child->type);
195 : 0 : child = NULL;
196 : 0 : ok = FALSE;
197 : 0 : goto cleanup_and_exit;
198 : : break;
199 : : }
200 : : }
201 : :
202 : 746 : cleanup_and_exit:
203 : 746 : if (ok)
204 : : {
205 : 746 : *result = p;
206 : : }
207 : : else
208 : : {
209 : 0 : *result = NULL;
210 : 0 : gnc_price_unref (p);
211 : : }
212 : 746 : xmlFreeNode (price_xml);
213 : 746 : return ok;
214 : : }
215 : :
216 : : static void
217 : 746 : cleanup_gnc_price (sixtp_child_result* result)
218 : : {
219 : 746 : if (result->data) gnc_price_unref ((GNCPrice*) result->data);
220 : 746 : }
221 : :
222 : : static sixtp*
223 : 64 : gnc_price_parser_new (void)
224 : : {
225 : 64 : return sixtp_dom_parser_new (price_parse_xml_end_handler,
226 : : cleanup_gnc_price,
227 : 64 : cleanup_gnc_price);
228 : : }
229 : :
230 : :
231 : : /****************************************************************************/
232 : : /* <pricedb> (lineage <ledger-data>)
233 : :
234 : : restores a pricedb. We allocate the new db in the start block, the
235 : : children add to it, and it gets returned in result. Note that the
236 : : cleanup handler will destroy the pricedb, so the parent needs to
237 : : stop that if desired.
238 : :
239 : : result: GNCPriceDB*
240 : :
241 : : start: create new GNCPriceDB*, and leave in *data_for_children.
242 : : cleanup-result: destroy GNCPriceDB*
243 : : result-fail: destroy GNCPriceDB*
244 : :
245 : : */
246 : :
247 : : static gboolean
248 : 21 : pricedb_start_handler (GSList* sibling_data,
249 : : gpointer parent_data,
250 : : gpointer global_data,
251 : : gpointer* data_for_children,
252 : : gpointer* result,
253 : : const gchar* tag,
254 : : gchar** attrs)
255 : : {
256 : 21 : gxpf_data* gdata = static_cast<decltype (gdata)> (global_data);
257 : 21 : QofBook* book = static_cast<decltype (book)> (gdata->bookdata);
258 : 21 : GNCPriceDB* db = gnc_pricedb_get_db (book);
259 : 21 : g_return_val_if_fail (db, FALSE);
260 : 21 : gnc_pricedb_set_bulk_update (db, TRUE);
261 : 21 : *result = db;
262 : 21 : return (TRUE);
263 : : }
264 : :
265 : : static gboolean
266 : 746 : pricedb_after_child_handler (gpointer data_for_children,
267 : : GSList* data_from_children,
268 : : GSList* sibling_data,
269 : : gpointer parent_data,
270 : : gpointer global_data,
271 : : gpointer* result,
272 : : const gchar* tag,
273 : : const gchar* child_tag,
274 : : sixtp_child_result* child_result)
275 : : {
276 : 746 : gxpf_data* gdata = static_cast<decltype (gdata)> (global_data);
277 : 746 : sixtp_gdv2* gd = static_cast<decltype (gd)> (gdata->parsedata);
278 : 746 : GNCPriceDB* db = (GNCPriceDB*) * result;
279 : :
280 : 746 : g_return_val_if_fail (db, FALSE);
281 : :
282 : : /* right now children have to produce results :> */
283 : 746 : if (!child_result) return (FALSE);
284 : 746 : if (child_result->type != SIXTP_CHILD_RESULT_NODE) return (FALSE);
285 : :
286 : 746 : if (strcmp (child_result->tag, "price") == 0)
287 : : {
288 : 746 : GNCPrice* p = (GNCPrice*) child_result->data;
289 : :
290 : 746 : g_return_val_if_fail (p, FALSE);
291 : 746 : gnc_pricedb_add_price (db, p);
292 : 746 : gd->counter.prices_loaded++;
293 : 746 : sixtp_run_callback (gd, "prices");
294 : 746 : return TRUE;
295 : : }
296 : : else
297 : : {
298 : 0 : PERR ("unexpected tag %s\n", child_result->tag);
299 : 0 : return FALSE;
300 : : }
301 : : return FALSE;
302 : : }
303 : :
304 : : static void
305 : 0 : pricedb_cleanup_result_handler (sixtp_child_result* result)
306 : : {
307 : 0 : if (result->data)
308 : : {
309 : 0 : GNCPriceDB* db = (GNCPriceDB*) result->data;
310 : 0 : if (db) gnc_pricedb_destroy (db);
311 : 0 : result->data = NULL;
312 : : }
313 : 0 : }
314 : :
315 : : static gboolean
316 : 21 : pricedb_v2_end_handler (
317 : : gpointer data_for_children, GSList* data_from_children,
318 : : GSList* sibling_data, gpointer parent_data, gpointer global_data,
319 : : gpointer* result, const gchar* tag)
320 : : {
321 : 21 : GNCPriceDB* db = static_cast<decltype (db)> (*result);
322 : 21 : gxpf_data* gdata = (gxpf_data*)global_data;
323 : :
324 : 21 : if (parent_data)
325 : : {
326 : 0 : return TRUE;
327 : : }
328 : :
329 : 21 : if (!tag)
330 : : {
331 : 0 : return TRUE;
332 : : }
333 : :
334 : 21 : gdata->cb (tag, gdata->parsedata, db);
335 : 21 : *result = NULL;
336 : :
337 : 21 : gnc_pricedb_set_bulk_update (db, FALSE);
338 : :
339 : 21 : return TRUE;
340 : : }
341 : :
342 : : static sixtp*
343 : 64 : gnc_pricedb_parser_new (void)
344 : : {
345 : : sixtp* top_level;
346 : : sixtp* price_parser;
347 : :
348 : : top_level =
349 : 64 : sixtp_set_any (sixtp_new (), TRUE,
350 : : SIXTP_START_HANDLER_ID, pricedb_start_handler,
351 : : SIXTP_AFTER_CHILD_HANDLER_ID, pricedb_after_child_handler,
352 : : SIXTP_CHARACTERS_HANDLER_ID,
353 : : allow_and_ignore_only_whitespace,
354 : : SIXTP_RESULT_FAIL_ID, pricedb_cleanup_result_handler,
355 : : SIXTP_CLEANUP_RESULT_ID, pricedb_cleanup_result_handler,
356 : : SIXTP_NO_MORE_HANDLERS);
357 : :
358 : 64 : if (!top_level) return NULL;
359 : :
360 : 64 : price_parser = gnc_price_parser_new ();
361 : :
362 : 64 : if (!price_parser)
363 : : {
364 : 0 : sixtp_destroy (top_level);
365 : 0 : return NULL;
366 : : }
367 : :
368 : 64 : sixtp_add_sub_parser (top_level, "price", price_parser);
369 : :
370 : 64 : return top_level;
371 : : }
372 : :
373 : : sixtp*
374 : 64 : gnc_pricedb_sixtp_parser_create (void)
375 : : {
376 : : sixtp* ret;
377 : 64 : ret = gnc_pricedb_parser_new ();
378 : 64 : sixtp_set_end (ret, pricedb_v2_end_handler);
379 : 64 : return ret;
380 : : }
381 : :
382 : :
383 : : /***********************************************************************/
384 : : /* WRITING */
385 : : /***********************************************************************/
386 : :
387 : : static gboolean
388 : 2926 : add_child_or_kill_parent (xmlNodePtr parent, xmlNodePtr child)
389 : : {
390 : 2926 : if (!child)
391 : : {
392 : 0 : xmlFreeNode (parent);
393 : 0 : return FALSE;
394 : : }
395 : 2926 : xmlAddChild (parent, child);
396 : 2926 : return TRUE;
397 : : }
398 : :
399 : : static xmlNodePtr
400 : 418 : gnc_price_to_dom_tree (const xmlChar* tag, GNCPrice* price)
401 : : {
402 : : xmlNodePtr price_xml;
403 : : const gchar* typestr, *sourcestr;
404 : : xmlNodePtr tmpnode;
405 : : gnc_commodity* commodity;
406 : : gnc_commodity* currency;
407 : : time64 time;
408 : : gnc_numeric value;
409 : :
410 : 418 : if (! (tag && price)) return NULL;
411 : :
412 : 418 : price_xml = xmlNewNode (NULL, tag);
413 : 418 : if (!price_xml) return NULL;
414 : :
415 : 418 : commodity = gnc_price_get_commodity (price);
416 : 418 : currency = gnc_price_get_currency (price);
417 : :
418 : 418 : if (! (commodity && currency)) return NULL;
419 : :
420 : 418 : tmpnode = guid_to_dom_tree ("price:id", gnc_price_get_guid (price));
421 : 418 : if (!add_child_or_kill_parent (price_xml, tmpnode)) return NULL;
422 : :
423 : 418 : tmpnode = commodity_ref_to_dom_tree ("price:commodity", commodity);
424 : 418 : if (!add_child_or_kill_parent (price_xml, tmpnode)) return NULL;
425 : :
426 : 418 : tmpnode = commodity_ref_to_dom_tree ("price:currency", currency);
427 : 418 : if (!add_child_or_kill_parent (price_xml, tmpnode)) return NULL;
428 : :
429 : 418 : time = gnc_price_get_time64 (price);
430 : 418 : tmpnode = time64_to_dom_tree ("price:time", time);
431 : 418 : if (!add_child_or_kill_parent (price_xml, tmpnode)) return NULL;
432 : :
433 : 418 : sourcestr = gnc_price_get_source_string (price);
434 : 418 : if (sourcestr && *sourcestr)
435 : : {
436 : 418 : tmpnode = text_to_dom_tree ("price:source", sourcestr);
437 : 418 : if (!add_child_or_kill_parent (price_xml, tmpnode)) return NULL;
438 : : }
439 : :
440 : 418 : typestr = gnc_price_get_typestr (price);
441 : 418 : if (typestr && *typestr)
442 : : {
443 : 418 : tmpnode = text_to_dom_tree ("price:type", typestr);
444 : 418 : if (!add_child_or_kill_parent (price_xml, tmpnode)) return NULL;
445 : : }
446 : :
447 : 418 : value = gnc_price_get_value (price);
448 : 418 : tmpnode = gnc_numeric_to_dom_tree ("price:value", &value);
449 : 418 : if (!add_child_or_kill_parent (price_xml, tmpnode)) return NULL;
450 : :
451 : 418 : return price_xml;
452 : : }
453 : :
454 : : static gboolean
455 : 418 : xml_add_gnc_price_adapter (GNCPrice* p, gpointer data)
456 : : {
457 : 418 : xmlNodePtr xml_node = (xmlNodePtr) data;
458 : :
459 : 418 : if (p)
460 : : {
461 : 418 : xmlNodePtr price_xml = gnc_price_to_dom_tree (BAD_CAST "price", p);
462 : 418 : if (!price_xml) return FALSE;
463 : 418 : xmlAddChild (xml_node, price_xml);
464 : 418 : return TRUE;
465 : : }
466 : : else
467 : : {
468 : 0 : return TRUE;
469 : : }
470 : : }
471 : :
472 : : static xmlNodePtr
473 : 24 : gnc_pricedb_to_dom_tree (const xmlChar* tag, GNCPriceDB* db)
474 : : {
475 : 24 : xmlNodePtr db_xml = NULL;
476 : :
477 : 24 : if (!tag) return NULL;
478 : :
479 : 24 : db_xml = xmlNewNode (NULL, tag);
480 : 24 : if (!db_xml) return NULL;
481 : :
482 : 24 : xmlSetProp (db_xml, BAD_CAST "version", BAD_CAST "1");
483 : :
484 : 24 : if (!gnc_pricedb_foreach_price (db, xml_add_gnc_price_adapter, db_xml, TRUE))
485 : : {
486 : 0 : xmlFreeNode (db_xml);
487 : 0 : return NULL;
488 : : }
489 : :
490 : : /* if no children have been added just return NULL */
491 : 24 : if (!db_xml->xmlChildrenNode)
492 : : {
493 : 4 : xmlFreeNode (db_xml);
494 : 4 : return NULL;
495 : : }
496 : :
497 : 20 : return db_xml;
498 : : }
499 : :
500 : : xmlNodePtr
501 : 24 : gnc_pricedb_dom_tree_create (GNCPriceDB* db)
502 : : {
503 : 24 : return gnc_pricedb_to_dom_tree (BAD_CAST "gnc:pricedb", db);
504 : : }
|