GnuCash  5.6-150-g038405b370+
sixtp-dom-parsers.cpp
1 /********************************************************************
2  * sixtp-dom-parsers.c *
3  * Copyright 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 <glib.h>
24 
25 #include <config.h>
26 
27 #include <string.h>
28 
29 #include <gnc-engine.h>
30 
31 #include "gnc-xml-helper.h"
32 #include "sixtp-utils.h"
33 #include "sixtp-dom-parsers.h"
34 #include <kvp-frame.hpp>
35 
36 static QofLogModule log_module = GNC_MOD_IO;
37 
38 GncGUID*
39 dom_tree_to_guid (xmlNodePtr node)
40 {
41  if (!node->properties)
42  {
43  return NULL;
44  }
45 
46  if (strcmp ((char*) node->properties->name, "type") != 0)
47  {
48  PERR ("Unknown attribute for id tag: %s",
49  node->properties->name ?
50  (char*) node->properties->name : "(null)");
51  return NULL;
52  }
53 
54  {
55  char* type;
56 
57  type = (char*)xmlNodeGetContent (node->properties->xmlAttrPropertyValue);
58 
59  /* handle new and guid the same for the moment */
60  if ((g_strcmp0 ("guid", type) == 0) || (g_strcmp0 ("new", type) == 0))
61  {
62  auto gid = guid_new ();
63  char* guid_str;
64 
65  guid_str = (char*)xmlNodeGetContent (node->xmlChildrenNode);
66  string_to_guid (guid_str, gid);
67  xmlFree (guid_str);
68  xmlFree (type);
69  return gid;
70  }
71  else
72  {
73  PERR ("Unknown type %s for attribute type for tag %s",
74  type ? type : "(null)",
75  node->properties->name ?
76  (char*) node->properties->name : "(null)");
77  xmlFree (type);
78  return NULL;
79  }
80  }
81 }
82 
83 static KvpValue*
84 dom_tree_to_integer_kvp_value (xmlNodePtr node)
85 {
86  gchar* text;
87  gint64 daint;
88  KvpValue* ret = NULL;
89 
90  text = dom_tree_to_text (node);
91 
92  if (string_to_gint64 (text, &daint))
93  {
94  ret = new KvpValue {daint};
95  }
96  g_free (text);
97 
98  return ret;
99 }
100 
101 template <typename T>
102 static bool
103 dom_tree_to_num (xmlNodePtr node, std::function<bool(const char*, T*)>string_to_num, T* num_ptr)
104 {
105  auto text = dom_tree_to_text (node);
106  auto ret = string_to_num (text, num_ptr);
107  g_free (text);
108  return ret;
109 }
110 
111 gboolean
112 dom_tree_to_integer (xmlNodePtr node, gint64* daint)
113 {
114  return dom_tree_to_num<gint64>(node, string_to_gint64, daint);
115 }
116 
117 gboolean
118 dom_tree_to_guint16 (xmlNodePtr node, guint16* i)
119 {
120  return dom_tree_to_num<guint16>(node, string_to_guint16, i);
121 }
122 
123 gboolean
124 dom_tree_to_guint (xmlNodePtr node, guint* i)
125 {
126  return dom_tree_to_num<guint>(node, string_to_guint, i);
127 }
128 
129 gboolean
130 dom_tree_to_boolean (xmlNodePtr node, gboolean* b)
131 {
132  gchar* text;
133 
134  text = dom_tree_to_text (node);
135  if (g_ascii_strncasecmp (text, "true", 4) == 0)
136  {
137  *b = TRUE;
138  g_free (text);
139  return TRUE;
140  }
141  else if (g_ascii_strncasecmp (text, "false", 5) == 0)
142  {
143  *b = FALSE;
144  g_free (text);
145  return TRUE;
146  }
147  else
148  {
149  *b = FALSE;
150  g_free (text);
151  return FALSE;
152  }
153 }
154 
155 static KvpValue*
156 dom_tree_to_double_kvp_value (xmlNodePtr node)
157 {
158  gchar* text;
159  double dadoub;
160  KvpValue* ret = NULL;
161 
162  text = dom_tree_to_text (node);
163 
164  if (string_to_double (text, &dadoub))
165  {
166  ret = new KvpValue {dadoub};
167  }
168 
169  g_free (text);
170 
171  return ret;
172 }
173 
174 static KvpValue*
175 dom_tree_to_numeric_kvp_value (xmlNodePtr node)
176 {
177  return new KvpValue {dom_tree_to_gnc_numeric (node)};
178 }
179 
180 static KvpValue*
181 dom_tree_to_string_kvp_value (xmlNodePtr node)
182 {
183  const gchar* datext;
184  KvpValue* ret = NULL;
185 
186  datext = dom_tree_to_text (node);
187  if (datext)
188  {
189  ret = new KvpValue {datext};
190  }
191 
192  return ret;
193 }
194 
195 static KvpValue*
196 dom_tree_to_guid_kvp_value (xmlNodePtr node)
197 {
198  GncGUID* daguid;
199  KvpValue* ret = NULL;
200 
201  daguid = dom_tree_to_guid (node);
202  if (daguid)
203  {
204  ret = new KvpValue {daguid};
205  }
206 
207  return ret;
208 }
209 
210 static KvpValue*
211 dom_tree_to_time64_kvp_value (xmlNodePtr node)
212 {
213  Time64 t{dom_tree_to_time64 (node)};
214  return new KvpValue {t};
215 }
216 
217 static KvpValue*
218 dom_tree_to_gdate_kvp_value (xmlNodePtr node)
219 {
220  GDate* date;
221  KvpValue* ret = NULL;
222 
223  date = dom_tree_to_gdate (node);
224 
225  if (date)
226  {
227  ret = new KvpValue {*date};
228  }
229 
230  g_free (date);
231 
232  return ret;
233 }
234 
235 gboolean
236 string_to_binary (const gchar* str, void** v, guint64* data_len)
237 {
238  guint64 str_len;
239  guchar* data;
240  unsigned int i, j;
241 
242  g_return_val_if_fail (v != NULL, FALSE);
243  g_return_val_if_fail (data_len != NULL, FALSE);
244 
245  str_len = strlen (str);
246 
247  /* Since no whitespace is allowed and hex encoding is 2 text chars
248  per binary char, the result must be half the input size and the
249  input size must be even. */
250  if ((str_len % 2) != 0)
251  return (FALSE);
252  *data_len = str_len / 2;
253  data = g_new0 (guchar, *data_len);
254 
255  for (j = 0, i = 0; i < str_len; i += 2, j++)
256  {
257  gchar tmpstr[3];
258  long int converted;
259 
260  tmpstr[0] = str[i];
261  tmpstr[1] = str[i + 1];
262  tmpstr[2] = '\0';
263 
264  converted = strtol (tmpstr, NULL, 16);
265 
266  data[j] = (unsigned char)converted;
267  }
268 
269  *v = data;
270 
271  return (TRUE);
272 }
273 
274 static KvpValue* dom_tree_to_kvp_value (xmlNodePtr node);
275 //needed for test access as well as internal use.
276 KvpFrame* dom_tree_to_kvp_frame (xmlNodePtr node);
277 
278 static KvpValue*
279 dom_tree_to_list_kvp_value (xmlNodePtr node)
280 {
281  GList* list = NULL;
282  xmlNodePtr mark;
283  KvpValue* ret = NULL;
284 
285  for (mark = node->xmlChildrenNode; mark; mark = mark->next)
286  {
287  KvpValue* new_val;
288 
289  if (g_strcmp0 ((char*)mark->name, "text") == 0)
290  continue;
291 
292  new_val = dom_tree_to_kvp_value (mark);
293  if (new_val)
294  {
295  list = g_list_prepend (list, (gpointer)new_val);
296  }
297  }
298 
299  list = g_list_reverse (list);
300 
301  ret = new KvpValue {list};
302 
303  return ret;
304 }
305 
306 static KvpValue*
307 dom_tree_to_frame_kvp_value (xmlNodePtr node)
308 {
309  KvpFrame* frame;
310  KvpValue* ret = NULL;
311 
312  frame = dom_tree_to_kvp_frame (node);
313 
314  if (frame)
315  {
316  ret = new KvpValue {frame};
317  }
318 
319  return ret;
320 }
321 
322 
324 {
325  const gchar* tag;
326  KvpValue* (*converter) (xmlNodePtr node);
327 };
328 /* Note: The type attribute must remain 'timespec' to maintain compatibility.
329  */
330 
331 struct kvp_val_converter val_converters[] =
332 {
333  { "integer", dom_tree_to_integer_kvp_value },
334  { "double", dom_tree_to_double_kvp_value },
335  { "numeric", dom_tree_to_numeric_kvp_value },
336  { "string", dom_tree_to_string_kvp_value },
337  { "guid", dom_tree_to_guid_kvp_value },
338  { "timespec", dom_tree_to_time64_kvp_value },
339  { "gdate", dom_tree_to_gdate_kvp_value },
340  { "list", dom_tree_to_list_kvp_value },
341  { "frame", dom_tree_to_frame_kvp_value },
342  { 0, 0 },
343 };
344 
345 static KvpValue*
346 dom_tree_to_kvp_value (xmlNodePtr node)
347 {
348  xmlChar* xml_type;
349  gchar* type;
350  struct kvp_val_converter* mark;
351  KvpValue* ret = NULL;
352 
353  xml_type = xmlGetProp (node, BAD_CAST "type");
354  if (xml_type)
355  {
356  type = g_strdup ((char*) xml_type);
357  xmlFree (xml_type);
358  }
359  else
360  type = NULL;
361 
362  for (mark = val_converters; mark->tag; mark++)
363  {
364  if (g_strcmp0 (type, mark->tag) == 0)
365  {
366  ret = (mark->converter) (node);
367  }
368  }
369 
370  if (!mark->tag)
371  {
372  /* FIXME: deal with unknown type tag here */
373  }
374 
375  g_free (type);
376 
377  return ret;
378 }
379 
380 static gboolean
381 dom_tree_to_kvp_frame_given (xmlNodePtr node, KvpFrame* frame)
382 {
383  xmlNodePtr mark;
384 
385  g_return_val_if_fail (node, FALSE);
386  g_return_val_if_fail (frame, FALSE);
387 
388  for (mark = node->xmlChildrenNode; mark; mark = mark->next)
389  {
390  if (g_strcmp0 ((char*)mark->name, "slot") == 0)
391  {
392  xmlNodePtr mark2;
393  gchar* key = NULL;
394  KvpValue* val = NULL;
395 
396  for (mark2 = mark->xmlChildrenNode; mark2; mark2 = mark2->next)
397  {
398  if (g_strcmp0 ((char*)mark2->name, "slot:key") == 0)
399  {
400  key = dom_tree_to_text (mark2);
401  }
402  else if (g_strcmp0 ((char*)mark2->name, "slot:value") == 0)
403  {
404  val = dom_tree_to_kvp_value (mark2);
405  }
406  else
407  {
408  /* FIXME: should put some error here.
409  * But ignore text type! */
410  }
411  }
412 
413  if (key)
414  {
415  if (val)
416  {
417  //We're deleting the old KvpValue returned by replace_nc().
418  delete frame->set ({key}, val);
419  }
420  else
421  {
422  /* FIXME: should put some error here */
423  }
424  g_free (key);
425  }
426  }
427  }
428 
429  return TRUE;
430 }
431 
432 
433 KvpFrame*
434 dom_tree_to_kvp_frame (xmlNodePtr node)
435 {
436  g_return_val_if_fail (node, NULL);
437 
438  auto ret = new KvpFrame;
439 
440  if (dom_tree_to_kvp_frame_given (node, ret))
441  return ret;
442 
443  delete ret;
444  return NULL;
445 }
446 
447 gboolean
448 dom_tree_create_instance_slots (xmlNodePtr node, QofInstance* inst)
449 {
450  KvpFrame* frame = qof_instance_get_slots (inst);
451  return dom_tree_to_kvp_frame_given (node, frame);
452 }
453 
454 gchar*
455 dom_tree_to_text (xmlNodePtr tree)
456 {
457  /* Expect *only* text and comment sibling nodes in the given tree --
458  which actually may only be a "list". i.e. if you're trying to
459  extract bar from <foo>bar</foo>, pass in <foo>->xmlChildrenNode
460  to this function. This expectation is different from the rest of
461  the dom_tree_to_* converters...
462 
463  Ignores comment nodes and collapse text nodes into one string.
464  Returns NULL if expectations are unsatisfied.
465  */
466  gchar* result;
467  gchar* temp;
468 
469  g_return_val_if_fail (tree, NULL);
470 
471  /* no nodes means it's an empty string text */
472  if (!tree->xmlChildrenNode)
473  {
474  DEBUG ("No children");
475  return g_strdup ("");
476  }
477 
478  temp = (char*)xmlNodeListGetString (NULL, tree->xmlChildrenNode, TRUE);
479  if (!temp)
480  {
481  DEBUG ("Null string");
482  return NULL;
483  }
484 
485  DEBUG ("node string [%s]", (temp == NULL ? "(null)" : temp));
486  result = g_strdup (temp);
487  xmlFree (temp);
488  return result;
489 }
490 
491 gnc_numeric
492 dom_tree_to_gnc_numeric (xmlNodePtr node)
493 {
494  gchar* content = dom_tree_to_text (node);
495  if (!content)
496  return gnc_numeric_zero ();
497 
498  gnc_numeric num = gnc_numeric_from_string (content);
499  if (gnc_numeric_check (num))
500  num = gnc_numeric_zero ();
501 
502  g_free (content);
503  return num;
504 }
505 
506 
507 time64
508 dom_tree_to_time64 (xmlNodePtr node)
509 {
510  /* Turn something like this
511 
512  <date-posted>
513  <ts:date>Mon, 05 Jun 2000 23:16:19 -0500</ts:date>
514  </date-posted>
515 
516  into a time64, returning INT64_MAX that we're using to flag an erroneous
517  date if there's a problem. Only one ts:date element is permitted for any
518  date attribute.
519  */
520 
521  time64 ret {INT64_MAX};
522  gboolean seen = FALSE;
523  xmlNodePtr n;
524 
525  for (n = node->xmlChildrenNode; n; n = n->next)
526  {
527  switch (n->type)
528  {
529  case XML_COMMENT_NODE:
530  case XML_TEXT_NODE:
531  break;
532  case XML_ELEMENT_NODE:
533  if (g_strcmp0 ("ts:date", (char*)n->name) == 0)
534  {
535  if (seen)
536  {
537  return INT64_MAX;
538  }
539  else
540  {
541  gchar* content = dom_tree_to_text (n);
542  if (!content)
543  {
544  return INT64_MAX;
545  }
546 
547  ret = gnc_iso8601_to_time64_gmt (content);
548  g_free (content);
549  seen = TRUE;
550  }
551  }
552  break;
553  default:
554  PERR ("unexpected sub-node.");
555  return INT64_MAX;
556  break;
557  }
558  }
559 
560  if (!seen)
561  {
562  PERR ("no ts:date node found.");
563  return INT64_MAX;
564  }
565 
566  return ret;
567 }
568 
569 GDate*
570 dom_tree_to_gdate (xmlNodePtr node)
571 {
572  /* Turn something like this
573 
574  <sx:startdate>
575  <gdate>2001-04-03</gdate>
576  </sx:startdate>
577 
578  into a GDate. If the xml is invalid, returns NULL. */
579 
580  GDate* ret;
581  gboolean seen_date = FALSE;
582  xmlNodePtr n;
583 
584  /* creates an invalid date */
585  ret = g_date_new ();
586 
587  for (n = node->xmlChildrenNode; n; n = n->next)
588  {
589  switch (n->type)
590  {
591  case XML_COMMENT_NODE:
592  case XML_TEXT_NODE:
593  break;
594  case XML_ELEMENT_NODE:
595  if (g_strcmp0 ("gdate", (char*)n->name) == 0)
596  {
597  if (seen_date)
598  {
599  goto failure;
600  }
601  else
602  {
603  gchar* content = dom_tree_to_text (n);
604  gint year, month, day;
605  if (!content)
606  {
607  goto failure;
608  }
609 
610  if (sscanf (content, "%d-%d-%d", &year, &month, &day) != 3)
611  {
612  g_free (content);
613  goto failure;
614  }
615  g_free (content);
616  seen_date = TRUE;
617  g_date_set_dmy (ret, day, static_cast<GDateMonth> (month),
618  year);
619  if (!g_date_valid (ret))
620  {
621  PWARN ("invalid date");
622  goto failure;
623  }
624  }
625  }
626  break;
627  default:
628  PERR ("unexpected sub-node.");
629  goto failure;
630  }
631  }
632 
633  if (!seen_date)
634  {
635  PWARN ("no gdate node found.");
636  goto failure;
637  }
638 
639  return ret;
640 failure:
641  g_date_free (ret);
642  return NULL;
643 }
644 
645 
646 gnc_commodity*
647 dom_tree_to_commodity_ref_no_engine (xmlNodePtr node, QofBook* book)
648 {
649  /* Turn something like this
650 
651  <currency>
652  <cmdty:space>NASDAQ</cmdty:space>
653  <cmdty:id>LNUX</cmdty:space>
654  </currency>
655 
656  into a gnc_commodity*, returning NULL on failure. Both sub-nodes
657  are required, though for now, order is irrelevant. */
658 
659  gnc_commodity* c = NULL;
660  gchar* space_str = NULL;
661  gchar* id_str = NULL;
662  xmlNodePtr n;
663 
664  if (!node) return NULL;
665  if (!node->xmlChildrenNode) return NULL;
666 
667  for (n = node->xmlChildrenNode; n; n = n->next)
668  {
669  switch (n->type)
670  {
671  case XML_COMMENT_NODE:
672  case XML_TEXT_NODE:
673  break;
674  case XML_ELEMENT_NODE:
675  if (g_strcmp0 ("cmdty:space", (char*)n->name) == 0)
676  {
677  if (space_str)
678  {
679  return NULL;
680  }
681  else
682  {
683  gchar* content = dom_tree_to_text (n);
684  if (!content) return NULL;
685  space_str = content;
686  }
687  }
688  else if (g_strcmp0 ("cmdty:id", (char*)n->name) == 0)
689  {
690  if (id_str)
691  {
692  return NULL;
693  }
694  else
695  {
696  gchar* content = dom_tree_to_text (n);
697  if (!content) return NULL;
698  id_str = content;
699  }
700  }
701  break;
702  default:
703  PERR ("unexpected sub-node.");
704  return NULL;
705  break;
706  }
707  }
708  if (! (space_str && id_str))
709  {
710  c = NULL;
711  }
712  else
713  {
714  g_strstrip (space_str);
715  g_strstrip (id_str);
716  c = gnc_commodity_new (book, NULL, space_str, id_str, NULL, 0);
717  }
718 
719  g_free (space_str);
720  g_free (id_str);
721 
722  return c;
723 }
724 
725 gnc_commodity*
726 dom_tree_to_commodity_ref (xmlNodePtr node, QofBook* book)
727 {
728  gnc_commodity* daref;
729  gnc_commodity* ret;
730  gnc_commodity_table* table;
731 
732  daref = dom_tree_to_commodity_ref_no_engine (node, book);
733 
735 
736  g_return_val_if_fail (table != NULL, NULL);
737 
738  ret = gnc_commodity_table_lookup (table,
741 
742  gnc_commodity_destroy (daref);
743 
744  g_return_val_if_fail (ret != NULL, NULL);
745 
746  return ret;
747 }
748 
749 /***********************************************************************/
750 /* generic parser */
751 
752 static inline void
753 dom_tree_handlers_reset (struct dom_tree_handler* handlers)
754 {
755  for (; handlers->tag != NULL; handlers++)
756  {
757  handlers->gotten = 0;
758  }
759 }
760 
761 static inline gboolean
762 dom_tree_handlers_all_gotten_p (struct dom_tree_handler* handlers)
763 {
764  gboolean ret = TRUE;
765  for (; handlers->tag != NULL; handlers++)
766  {
767  if (handlers->required && ! handlers->gotten)
768  {
769  PERR ("Not defined and it should be: %s",
770  handlers->tag ? handlers->tag : "(null)");
771  ret = FALSE;
772  }
773  }
774  return ret;
775 }
776 
777 
778 static inline gboolean
779 gnc_xml_set_data (const gchar* tag, xmlNodePtr node, gpointer item,
780  struct dom_tree_handler* handlers)
781 {
782  for (; handlers->tag != NULL; handlers++)
783  {
784  if (g_strcmp0 (tag, handlers->tag) == 0)
785  {
786  (handlers->handler) (node, item);
787  handlers->gotten = TRUE;
788  break;
789  }
790  }
791 
792  if (!handlers->tag)
793  {
794  PERR ("Unhandled tag: %s",
795  tag ? tag : "(null)");
796  return FALSE;
797  }
798 
799  return TRUE;
800 }
801 
802 gboolean
803 dom_tree_generic_parse (xmlNodePtr node, struct dom_tree_handler* handlers,
804  gpointer data)
805 {
806  xmlNodePtr achild;
807  gboolean successful = TRUE;
808 
809  dom_tree_handlers_reset (handlers);
810 
811  for (achild = node->xmlChildrenNode; achild; achild = achild->next)
812  {
813  /* ignore stray text nodes */
814  if (g_strcmp0 ((char*)achild->name, "text") == 0)
815  continue;
816 
817  if (!gnc_xml_set_data ((char*)achild->name, achild, data, handlers))
818  {
819  PERR ("gnc_xml_set_data failed");
820  successful = FALSE;
821  continue;
822  }
823  }
824 
825  if (!dom_tree_handlers_all_gotten_p (handlers))
826  {
827  PERR ("didn't find all of the expected tags in the input");
828  successful = FALSE;
829  }
830 
831  return successful;
832 }
833 
834 gboolean
835 dom_tree_valid_time64 (time64 val, const xmlChar * name)
836 {
837  if (val != INT64_MAX)
838  return TRUE;
839  g_warning ("Invalid timestamp in data file. Look for a '%s' entry "
840  "with a year outside of the valid range: 1400..10000", name);
841  return FALSE;
842 }
time64 gnc_iso8601_to_time64_gmt(const gchar *)
The gnc_iso8601_to_time64_gmt() routine converts an ISO-8601 style date/time string to time64...
gnc_commodity_table * gnc_commodity_table_get_table(QofBook *book)
Returns the commodity table associated with a book.
const char * gnc_commodity_get_mnemonic(const gnc_commodity *cm)
Retrieve the mnemonic for the specified commodity.
#define DEBUG(format, args...)
Print a debugging message.
Definition: qoflog.h:264
gboolean string_to_guid(const gchar *string, GncGUID *guid)
Given a string, replace the given guid with the parsed one unless the given value is null...
GncGUID * guid_new(void)
Allocate and construct a new GUID.
Definition: guid.cpp:151
const char * gnc_commodity_get_namespace(const gnc_commodity *cm)
Retrieve the namespace for the specified commodity.
#define PERR(format, args...)
Log a serious error.
Definition: qoflog.h:244
#define PWARN(format, args...)
Log a warning.
Definition: qoflog.h:250
gnc_commodity * gnc_commodity_new(QofBook *book, const char *fullname, const char *name_space, const char *mnemonic, const char *cusip, int fraction)
Create a new commodity.
All type declarations for the whole Gnucash engine.
gnc_numeric gnc_numeric_from_string(const gchar *str)
Read a gnc_numeric from str, skipping any leading whitespace.
GNCNumericErrorCode gnc_numeric_check(gnc_numeric a)
Check for error signal in value.
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition: gnc-date.h:87
The type used to store guids in C.
Definition: guid.h:75
void gnc_commodity_destroy(gnc_commodity *cm)
Destroy a commodity.