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