GnuCash  5.6-150-g038405b370+
sixtp-utils.cpp
1 /********************************************************************
2  * sixtp-utils.c *
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 
24 #define __EXTENSIONS__
25 #include <guid.hpp>
26 #include <config.h>
27 
28 #include <ctype.h>
29 #include <glib.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 
34 #include <time.h>
35 #include <errno.h>
36 
37 #ifndef HAVE_STRPTIME
38 #include "strptime.h"
39 #endif
40 #include <gnc-date.h>
41 
42 #include "sixtp.h"
43 #include "sixtp-utils.h"
44 
45 #include <charconv>
46 #include <cctype>
47 
48 static QofLogModule log_module = GNC_MOD_IO;
49 
50 gboolean
51 isspace_str (const gchar* str, int nomorethan)
52 {
53  const gchar* cursor = str;
54  while (*cursor && (nomorethan != 0))
55  {
56  if (!isspace (*cursor))
57  {
58  return (FALSE);
59  }
60  cursor++;
61  nomorethan--;
62  }
63  return (TRUE);
64 }
65 
66 gboolean
67 allow_and_ignore_only_whitespace (GSList* sibling_data,
68  gpointer parent_data,
69  gpointer global_data,
70  gpointer* result,
71  const char* text,
72  int length)
73 {
74  return (isspace_str (text, length));
75 }
76 
77 gboolean
78 generic_accumulate_chars (GSList* sibling_data,
79  gpointer parent_data,
80  gpointer global_data,
81  gpointer* result,
82 
83  const char* text,
84  int length)
85 {
86  gchar* copytxt = g_strndup (text, length);
87  g_return_val_if_fail (result, FALSE);
88 
89  *result = copytxt;
90  return (TRUE);
91 }
92 
93 
94 void
95 generic_free_data_for_children (gpointer data_for_children,
96  GSList* data_from_children,
97  GSList* sibling_data,
98  gpointer parent_data,
99  gpointer global_data,
100  gpointer* result,
101  const gchar* tag)
102 {
103  if (data_for_children) g_free (data_for_children);
104 }
105 
106 gchar*
107 concatenate_child_result_chars (GSList* data_from_children)
108 {
109  GSList* lp;
110  gchar* name = g_strdup ("");
111 
112  g_return_val_if_fail (name, NULL);
113 
114  /* child data lists are in reverse chron order */
115  data_from_children = g_slist_reverse (g_slist_copy (data_from_children));
116 
117  for (lp = data_from_children; lp; lp = lp->next)
118  {
119  sixtp_child_result* cr = (sixtp_child_result*) lp->data;
120  if (cr->type != SIXTP_CHILD_RESULT_CHARS)
121  {
122  PERR ("result type is not chars");
123  g_slist_free (data_from_children);
124  g_free (name);
125  return (NULL);
126  }
127  else
128  {
129  char* temp;
130  temp = g_strconcat (name, (gchar*) cr->data, nullptr);
131  g_free (name);
132  name = temp;
133  }
134  }
135  g_slist_free (data_from_children);
136  return (name);
137 }
138 
139 /****************************************************************************/
140 /* string to data converters...
141  */
142 
143 
144 template <typename T>
145 static bool parse_chars_into_num (const char* str, T* num_ptr)
146 {
147  if (!str || !num_ptr)
148  return false;
149 
150  while (std::isspace (*str))
151  ++str;
152 
153  const char* end_ptr = str + std::strlen (str);
154 
155  auto res = std::from_chars (str, end_ptr, *num_ptr);
156  if (res.ec != std::errc{})
157  return false;
158 
159  while (std::isspace (*res.ptr))
160  ++res.ptr;
161 
162  return (res.ptr == end_ptr);
163 }
164 
165 /*********/
166 /* double
167  */
168 
169 gboolean
170 string_to_double (const char* str, double* result)
171 {
172 #if __cpp_lib_to_chars >= 201611L
173  return parse_chars_into_num<double>(str, result);
174 #else
175  // because from_chars in cpp < 201611L cannot parse floats
176  char* endptr = nullptr;
177  g_return_val_if_fail (str && result, false);
178  *result = std::strtod (str, &endptr);
179  return (endptr != str);
180 #endif
181 }
182 
183 /*********/
184 /* gint64
185  */
186 gboolean
187 string_to_gint64 (const gchar* str, gint64* v)
188 {
189  return parse_chars_into_num<gint64>(str, v);
190 }
191 
192 /*********/
193 /* guint16
194  */
195 gboolean
196 string_to_guint16 (const gchar* str, guint16* v)
197 {
198  return parse_chars_into_num<guint16>(str, v);
199 }
200 
201 /*********/
202 /* guint
203  */
204 gboolean
205 string_to_guint (const gchar* str, guint* v)
206 {
207  return parse_chars_into_num<guint>(str, v);
208 }
209 
210 /************/
211 /* hex string
212  */
213 
214 gboolean
215 hex_string_to_binary (const gchar* str, void** v, guint64* data_len)
216 {
217  /* Convert a hex string to binary. No whitespace allowed. */
218  const gchar* cursor = str;
219  guint64 str_len;
220  gboolean error = FALSE;
221 
222  g_return_val_if_fail (str, FALSE);
223  g_return_val_if_fail (v, FALSE);
224  g_return_val_if_fail (data_len, FALSE);
225 
226  str_len = strlen (str);
227  /* Since no whitespace is allowed and hex encoding is 2 text chars
228  per binary char, the result must be half the input size and the
229  input size must be even. */
230  if ((str_len % 2) != 0) return (FALSE);
231  *data_len = 0;
232  *v = g_new0 (char, str_len / 2);
233 
234  g_return_val_if_fail (*v, FALSE);
235 
236  while (*cursor && * (cursor + 1))
237  {
238  gchar tmpstr[2];
239  int tmpint;
240 
241  if (isspace (*cursor) || isspace (* (cursor + 1)))
242  {
243  error = TRUE;
244  }
245  else
246  {
247  int num_read;
248  tmpstr[0] = *cursor;
249  tmpstr[0] = * (cursor + 1);
250 
251  if ((sscanf (tmpstr, "%x%n", &tmpint, &num_read) < 1)
252  || (num_read != 2))
253  {
254  error = TRUE;
255  }
256  else
257  {
258  * ((gchar*) (v + *data_len)) = tmpint;
259  *data_len += 1;
260  cursor += 2;
261  }
262  }
263  }
264 
265  if (error || (*data_len != (str_len / 2)))
266  {
267  g_free (*v);
268  *v = NULL;
269  *data_len = 0;
270  return (FALSE);
271  }
272 
273  return (TRUE);
274 }
275 
276 /***************************************************************************/
277 /* simple chars only parser - just grabs all it's contained chars and
278  does what you specify in the end handler - if you pass NULL as the
279  end handler to simple_chars_only_parser_new, the characters are just
280  passed to the parent as a new string.
281 
282  input: NA
283  returns: gchar array allocated via g_new, etc.
284 
285  start: NA
286  chars: generic_accumulate_chars.
287  end: varies - default is to concatenate all accumulated chars and return.
288 
289  cleanup-result: g_free (for chars)
290  cleanup-chars: g_free (for chars)
291  fail: NA
292  result-fail: g_free (for chars)
293  chars-fail: g_free (for chars)
294 
295  */
296 
297 gboolean
298 generic_return_chars_end_handler (gpointer data_for_children,
299  GSList* data_from_children,
300  GSList* sibling_data,
301  gpointer parent_data,
302  gpointer global_data,
303  gpointer* result,
304  const gchar* tag)
305 {
306  gchar* txt = NULL;
307 
308  txt = concatenate_child_result_chars (data_from_children);
309  g_return_val_if_fail (txt, FALSE);
310  *result = txt;
311  return (TRUE);
312 }
313 
314 
315 sixtp*
316 simple_chars_only_parser_new (sixtp_end_handler end_handler)
317 {
318  return sixtp_set_any (
319  sixtp_new (), FALSE,
320  SIXTP_END_HANDLER_ID, (end_handler
321  ? end_handler
322  : generic_return_chars_end_handler),
323  SIXTP_CHARACTERS_HANDLER_ID, generic_accumulate_chars,
324  SIXTP_CLEANUP_RESULT_ID, sixtp_child_free_data,
325  SIXTP_CLEANUP_CHARS_ID, sixtp_child_free_data,
326  SIXTP_RESULT_FAIL_ID, sixtp_child_free_data,
327  SIXTP_CHARS_FAIL_ID, sixtp_child_free_data,
328  SIXTP_NO_MORE_HANDLERS);
329 }
330 
331 /****************************************************************************/
332 /* generic timespec handler for XML Version 1 files.
333 
334  A collection of node functions intended to parse a sub-node set
335  that looks like this:
336 
337  <date-posted>
338  <s>Mon, 05 Jun 2000 23:16:19 -0500</s>
339  <ns>658864000</ns>
340  </date-posted>
341 
342  and produce a time64. The start handler for the top allocates
343  the time64 and passes it to the children. The <s> block sets
344  the seconds and the <ns> block is ignored. If
345  all goes well, returns the time64 as the result.
346 */
347 
348 /* Top level timespec node:
349 
350  input: user end handler *
351  returns: time64
352 
353  start: Allocates Time64ParseInfo* for data_for_children.
354  characters: none (whitespace only).
355  end: g_free Time64ParseInfo + any other actions
356 
357  cleanup-result: NA
358  cleanup-chars: NA
359  fail: g_free data_for_children.
360  result-fail: g_free data_for_children.
361  chars-fail: NA
362 
363  */
364 
365 gboolean
366 generic_timespec_start_handler (GSList* sibling_data, gpointer parent_data,
367  gpointer global_data,
368  gpointer* data_for_children, gpointer* result,
369  const gchar* tag, gchar** attrs)
370 {
371  Time64ParseInfo* tsp = g_new0 (Time64ParseInfo, 1);
372  g_return_val_if_fail (tsp, FALSE);
373  *data_for_children = tsp;
374  return (TRUE);
375 }
376 
377 /* You can't use this function directly. You have to call it from
378  your own end handler. If it returns TRUE, *result will contain the
379  new timespec. Otherwise, you can presume that everything's been
380  cleaned up properly and return FALSE. */
381 gboolean
382 timespec_parse_ok (Time64ParseInfo* info)
383 {
384  return info->s_block_count != 1;
385 }
386 
387 /* generic_timespec_end_handler - must be customized and provided by
388  the user. */
389 
390 /* <s> (parent timespec-node)
391 
392  input: Time64ParseInfo *
393  returns: NA
394 
395  start: NA
396  characters: accumulate.
397  end: convert characters to secs part of input time64 and inc s_block_count.
398 
399  cleanup-result: NA
400  cleanup-chars: g_free data.
401  fail: NA
402  result-fail: NA
403  chars-fail: g_free data.
404 
405  */
406 
407 gboolean
408 generic_timespec_secs_end_handler (gpointer data_for_children,
409  GSList* data_from_children, GSList* sibling_data,
410  gpointer parent_data, gpointer global_data,
411  gpointer* result, const gchar* tag)
412 {
413  gchar* txt = NULL;
414  Time64ParseInfo* info = (Time64ParseInfo*) parent_data;
415 
416  g_return_val_if_fail (parent_data, FALSE);
417 
418  txt = concatenate_child_result_chars (data_from_children);
419  g_return_val_if_fail (txt, FALSE);
420 
421  info->time = gnc_iso8601_to_time64_gmt (txt);
422  g_free (txt);
423 
424 // gnc_iso8601_to_time64_gmt returns INT64_MAX on failure.
425  g_return_val_if_fail (info->time < INT64_MAX, FALSE);
426 
427  info->s_block_count++;
428  return (TRUE);
429 }
430 
431 /* <s> (parent timespec-node)
432 
433  input: Time64ParseInfo *
434  returns: NA
435 
436  start: NA
437  characters: accumulate.
438  end: NA
439 
440  cleanup-result: NA
441  cleanup-chars: NA.
442  fail: NA
443  result-fail: NA
444  chars-fail: g_free data.
445 
446  */
447 
448 gboolean
449 generic_timespec_nsecs_end_handler (gpointer data_for_children,
450  GSList* data_from_children, GSList* sibling_data,
451  gpointer parent_data, gpointer global_data,
452  gpointer* result, const gchar* tag)
453 {
454  return TRUE;
455 }
456 
457 static sixtp*
458 timespec_sixtp_new (sixtp_end_handler ender)
459 {
460  return sixtp_set_any (
461  sixtp_new (), FALSE,
462  SIXTP_CHARACTERS_HANDLER_ID, generic_accumulate_chars,
463  SIXTP_END_HANDLER_ID, ender,
464  SIXTP_CLEANUP_CHARS_ID, sixtp_child_free_data,
465  SIXTP_CHARS_FAIL_ID, sixtp_child_free_data,
466  SIXTP_NO_MORE_HANDLERS);
467 }
468 
469 sixtp*
470 generic_timespec_parser_new (sixtp_end_handler end_handler)
471 {
472  sixtp* top_level =
473  sixtp_set_any (sixtp_new (), FALSE,
474  SIXTP_START_HANDLER_ID, generic_timespec_start_handler,
475  SIXTP_CHARACTERS_HANDLER_ID, allow_and_ignore_only_whitespace,
476  SIXTP_END_HANDLER_ID, end_handler,
477  SIXTP_CLEANUP_RESULT_ID, sixtp_child_free_data,
478  SIXTP_FAIL_HANDLER_ID, generic_free_data_for_children,
479  SIXTP_RESULT_FAIL_ID, sixtp_child_free_data,
480  SIXTP_NO_MORE_HANDLERS);
481  g_return_val_if_fail (top_level, NULL);
482 
483  if (!sixtp_add_some_sub_parsers (
484  top_level, TRUE,
485  "s", timespec_sixtp_new (generic_timespec_secs_end_handler),
486  "ns", timespec_sixtp_new (generic_timespec_nsecs_end_handler),
487  NULL, NULL))
488  {
489  return NULL;
490  }
491 
492  return (top_level);
493 }
494 
495 /****************************************************************************/
496 /* <?> generic guid handler...
497 
498  Attempts to parse the current accumulated characters data as a guid
499  and return it.
500 
501  input: NA
502  returns: GncGUID*
503 
504  start: NA
505  characters: return string copy for accumulation in end handler.
506  end: concatenate all chars and create and return GncGUID*, if possible.
507 
508  cleanup-result: g_free the GncGUID*
509  cleanup-chars: g_free the result string.
510  fail: NA
511  result-fail: g_free the GncGUID*
512  chars-fail: g_free the result string.
513 
514  */
515 
516 gboolean
517 generic_guid_end_handler (gpointer data_for_children,
518  GSList* data_from_children, GSList* sibling_data,
519  gpointer parent_data, gpointer global_data,
520  gpointer* result, const gchar* tag)
521 {
522  gchar* txt = NULL;
523  GncGUID* gid;
524  gboolean ok;
525 
526  txt = concatenate_child_result_chars (data_from_children);
527  g_return_val_if_fail (txt, FALSE);
528 
529  gid = g_new (GncGUID, 1);
530  if (!gid)
531  {
532  g_free (txt);
533  return (FALSE);
534  }
535 
536  ok = string_to_guid (txt, gid);
537  g_free (txt);
538 
539  if (!ok)
540  {
541  PERR ("couldn't parse GncGUID");
542  g_free (gid);
543  return (FALSE);
544  }
545 
546  *result = gid;
547  return (TRUE);
548 }
549 
550 sixtp*
551 generic_guid_parser_new (void)
552 {
553  return sixtp_set_any (
554  sixtp_new (), FALSE,
555  SIXTP_CHARACTERS_HANDLER_ID, generic_accumulate_chars,
556  SIXTP_CLEANUP_CHARS_ID, sixtp_child_free_data,
557  SIXTP_CHARS_FAIL_ID, sixtp_child_free_data,
558  SIXTP_END_HANDLER_ID, generic_guid_end_handler,
559  SIXTP_RESULT_FAIL_ID, sixtp_child_free_data,
560  SIXTP_CLEANUP_RESULT_ID, sixtp_child_free_data,
561  SIXTP_NO_MORE_HANDLERS);
562 }
563 
564 /****************************************************************************/
565 /* <?> generic gnc_numeric handler...
566 
567  Attempts to parse the current accumulated characters data as a
568  gnc_numeric and return it.
569 
570  input: NA
571  returns: gnc_numeric*
572 
573  start: NA
574  characters: return string copy for accumulation in end handler.
575  end: concatenate all chars and create and return gnc_numeric*, if possible.
576 
577  cleanup-result: g_free the gnc_numeric*
578  cleanup-chars: g_free the result string.
579  fail: NA
580  result-fail: g_free the gnc_numeric*
581  chars-fail: g_free the result string.
582 
583  */
584 
585 gboolean
586 generic_gnc_numeric_end_handler (gpointer data_for_children,
587  GSList* data_from_children, GSList* sibling_data,
588  gpointer parent_data, gpointer global_data,
589  gpointer* result, const gchar* tag)
590 {
591  gnc_numeric* num = NULL;
592  gchar* txt = NULL;
593  gboolean ok = FALSE;
594 
595  txt = concatenate_child_result_chars (data_from_children);
596 
597  if (txt)
598  {
599  num = g_new (gnc_numeric, 1);
600  if (num)
601  {
602  *num = gnc_numeric_from_string (txt);
603  if (!gnc_numeric_check (*num))
604  {
605  ok = TRUE;
606  *result = num;
607  }
608  }
609  }
610 
611  g_free (txt);
612  if (!ok)
613  {
614  PERR ("couldn't parse numeric quantity");
615  g_free (num);
616  }
617 
618  return (ok);
619 }
620 
621 sixtp*
622 generic_gnc_numeric_parser_new (void)
623 {
624  return sixtp_set_any (
625  sixtp_new (), FALSE,
626  SIXTP_CHARACTERS_HANDLER_ID, generic_accumulate_chars,
627  SIXTP_CLEANUP_CHARS_ID, sixtp_child_free_data,
628  SIXTP_CHARS_FAIL_ID, sixtp_child_free_data,
629  SIXTP_END_HANDLER_ID, generic_gnc_numeric_end_handler,
630  SIXTP_RESULT_FAIL_ID, sixtp_child_free_data,
631  SIXTP_CLEANUP_RESULT_ID, sixtp_child_free_data,
632  SIXTP_NO_MORE_HANDLERS);
633 }
634 
635 /***************************************************************************/
636 
637 sixtp*
638 restore_char_generator (sixtp_end_handler ender)
639 {
640  return sixtp_set_any (
641  sixtp_new (), FALSE,
642  SIXTP_CHARACTERS_HANDLER_ID, generic_accumulate_chars,
643  SIXTP_END_HANDLER_ID, ender,
644  SIXTP_CLEANUP_CHARS_ID, sixtp_child_free_data,
645  SIXTP_CHARS_FAIL_ID, sixtp_child_free_data,
646  SIXTP_NO_MORE_HANDLERS);
647 }
648 
649 /***************************** END OF FILE *********************************/
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...
Definition: sixtp.h:129
Date and Time handling routines.
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...
#define PERR(format, args...)
Log a serious error.
Definition: qoflog.h:244
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.
The type used to store guids in C.
Definition: guid.h:75