/******************************************************************** * sixtp-utils.c * * Copyright (c) 2001 Gnumatic, Inc. * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License* * along with this program; if not, contact: * * * * Free Software Foundation Voice: +1-617-542-5942 * * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 * * Boston, MA 02110-1301, USA gnu@gnu.org * * * ********************************************************************/ #define __EXTENSIONS__ #include extern "C" { #include #include #include #include #include #include #include #include #ifdef GNUCASH_MAJOR_VERSION #ifndef HAVE_STRPTIME #include "strptime.h" #endif #include #endif } #include "sixtp.h" #include "sixtp-utils.h" static QofLogModule log_module = GNC_MOD_IO; gboolean isspace_str (const gchar* str, int nomorethan) { const gchar* cursor = str; while (*cursor && (nomorethan != 0)) { if (!isspace (*cursor)) { return (FALSE); } cursor++; nomorethan--; } return (TRUE); } gboolean allow_and_ignore_only_whitespace (GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const char* text, int length) { return (isspace_str (text, length)); } gboolean generic_accumulate_chars (GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const char* text, int length) { gchar* copytxt = g_strndup (text, length); g_return_val_if_fail (result, FALSE); *result = copytxt; return (TRUE); } void generic_free_data_for_children (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { if (data_for_children) g_free (data_for_children); } gchar* concatenate_child_result_chars (GSList* data_from_children) { GSList* lp; gchar* name = g_strdup (""); g_return_val_if_fail (name, NULL); /* child data lists are in reverse chron order */ data_from_children = g_slist_reverse (g_slist_copy (data_from_children)); for (lp = data_from_children; lp; lp = lp->next) { sixtp_child_result* cr = (sixtp_child_result*) lp->data; if (cr->type != SIXTP_CHILD_RESULT_CHARS) { PERR ("result type is not chars"); g_slist_free (data_from_children); g_free (name); return (NULL); } else { char* temp; temp = g_strconcat (name, (gchar*) cr->data, NULL); g_free (name); name = temp; } } g_slist_free (data_from_children); return (name); } /****************************************************************************/ /* string to data converters... */ /*********/ /* double */ gboolean string_to_double (const char* str, double* result) { char* endptr = 0x0; g_return_val_if_fail (str, FALSE); g_return_val_if_fail (result, FALSE); *result = strtod (str, &endptr); if (endptr == str) return (FALSE); return (TRUE); } /*********/ /* gint64 */ /* Maybe there should be a comment here explaining why this function doesn't call g_ascii_strtoull, because it's not so obvious. -CAS */ gboolean string_to_gint64 (const gchar* str, gint64* v) { /* convert a string to a gint64. only whitespace allowed before and after. */ long long int v_in; int num_read; g_return_val_if_fail (str, FALSE); /* must use "<" here because %n's effects aren't well defined */ if (sscanf (str, " " QOF_SCANF_LLD "%n", &v_in, &num_read) < 1) { return (FALSE); } /* * Mac OS X version 10.1 and under has a silly bug where scanf * returns bad values in num_read if there is a space before %n. It * is fixed in the next release 10.2 afaik */ while ((* ((gchar*)str + num_read) != '\0') && isspace (* ((unsigned char*)str + num_read))) num_read++; if (v) *v = v_in; if (!isspace_str (str + num_read, -1)) return (FALSE); return (TRUE); } /*********/ /* gint32 */ gboolean string_to_gint32 (const gchar* str, gint32* v) { /* convert a string to a gint32. only whitespace allowed before and after. */ int num_read; int v_in; /* must use "<" here because %n's effects aren't well defined */ if (sscanf (str, " %d%n", &v_in, &num_read) < 1) { return (FALSE); } while ((* ((gchar*)str + num_read) != '\0') && isspace (* ((unsigned char*)str + num_read))) num_read++; if (v) *v = v_in; if (!isspace_str (str + num_read, -1)) return (FALSE); return (TRUE); } /************/ /* hex string */ gboolean hex_string_to_binary (const gchar* str, void** v, guint64* data_len) { /* Convert a hex string to binary. No whitespace allowed. */ const gchar* cursor = str; guint64 str_len; gboolean error = FALSE; g_return_val_if_fail (str, FALSE); g_return_val_if_fail (v, FALSE); g_return_val_if_fail (data_len, FALSE); str_len = strlen (str); /* Since no whitespace is allowed and hex encoding is 2 text chars per binary char, the result must be half the input size and the input size must be even. */ if ((str_len % 2) != 0) return (FALSE); *data_len = 0; *v = g_new0 (char, str_len / 2); g_return_val_if_fail (*v, FALSE); while (*cursor && * (cursor + 1)) { gchar tmpstr[2]; int tmpint; if (isspace (*cursor) || isspace (* (cursor + 1))) { error = TRUE; } else { int num_read; tmpstr[0] = *cursor; tmpstr[0] = * (cursor + 1); if ((sscanf (tmpstr, "%x%n", &tmpint, &num_read) < 1) || (num_read != 2)) { error = TRUE; } else { * ((gchar*) (v + *data_len)) = tmpint; *data_len += 1; cursor += 2; } } } if (error || (*data_len != (str_len / 2))) { g_free (*v); *v = NULL; *data_len = 0; return (FALSE); } return (TRUE); } /***************************************************************************/ /* simple chars only parser - just grabs all it's contained chars and does what you specify in the end handler - if you pass NULL as the end handler to simple_chars_only_parser_new, the characters are just passed to the parent as a new string. input: NA returns: gchar array allocated via g_new, etc. start: NA chars: generic_accumulate_chars. end: varies - default is to concatenate all accumulated chars and return. cleanup-result: g_free (for chars) cleanup-chars: g_free (for chars) fail: NA result-fail: g_free (for chars) chars-fail: g_free (for chars) */ gboolean generic_return_chars_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { gchar* txt = NULL; txt = concatenate_child_result_chars (data_from_children); g_return_val_if_fail (txt, FALSE); *result = txt; return (TRUE); } sixtp* simple_chars_only_parser_new (sixtp_end_handler end_handler) { return sixtp_set_any ( sixtp_new (), FALSE, SIXTP_END_HANDLER_ID, (end_handler ? end_handler : generic_return_chars_end_handler), SIXTP_CHARACTERS_HANDLER_ID, generic_accumulate_chars, SIXTP_CLEANUP_RESULT_ID, sixtp_child_free_data, SIXTP_CLEANUP_CHARS_ID, sixtp_child_free_data, SIXTP_RESULT_FAIL_ID, sixtp_child_free_data, SIXTP_CHARS_FAIL_ID, sixtp_child_free_data, SIXTP_NO_MORE_HANDLERS); } /****************************************************************************/ /* generic timespec handler. A collection of node functions intended to parse a sub-node set that looks like this: Mon, 05 Jun 2000 23:16:19 -0500 658864000 and produce a Timespec*. The start handler for the top allocates the Timespec * and passes it to the children. The block sets the seconds and the block (if any) sets the nanoseconds. If all goes well, returns the Timespec* as the result. */ gboolean string_to_timespec_secs (const gchar* str, Timespec* ts) { *ts = gnc_iso8601_to_timespec_gmt (str); return (TRUE); } gboolean string_to_timespec_nsecs (const gchar* str, Timespec* ts) { /* We don't do nanoseconds anymore. */ return (TRUE); } /* Top level timespec node: input: user end handler * returns: Timespec* start: Allocates TimespecParseInfo* for data_for_children. characters: none (whitespace only). end: g_free TimespecParseInfo + any other actions cleanup-result: NA cleanup-chars: NA fail: g_free data_for_children. result-fail: g_free data_for_children. chars-fail: NA */ gboolean generic_timespec_start_handler (GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* data_for_children, gpointer* result, const gchar* tag, gchar** attrs) { TimespecParseInfo* tsp = g_new0 (TimespecParseInfo, 1); g_return_val_if_fail (tsp, FALSE); *data_for_children = tsp; return (TRUE); } /* You can't use this function directly. You have to call it from your own end handler. If it returns TRUE, *result will contain the new timespec. Otherwise, you can presume that everything's been cleaned up properly and return FALSE. */ gboolean timespec_parse_ok (TimespecParseInfo* info) { if ((info->s_block_count > 1) || (info->ns_block_count > 1) || ((info->s_block_count == 0) && (info->ns_block_count == 0))) { return (FALSE); } else { return (TRUE); } } /* generic_timespec_end_handler - must be customized and provided by the user. */ /* (parent timespec-node) input: TimespecParseInfo * returns: NA start: NA characters: accumulate. end: convert characters to secs part of input Timespec and inc s_block_count. cleanup-result: NA cleanup-chars: g_free data. fail: NA result-fail: NA chars-fail: g_free data. */ gboolean generic_timespec_secs_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { gchar* txt = NULL; TimespecParseInfo* info = (TimespecParseInfo*) parent_data; gboolean ok; g_return_val_if_fail (parent_data, FALSE); txt = concatenate_child_result_chars (data_from_children); g_return_val_if_fail (txt, FALSE); ok = string_to_timespec_secs (txt, & (info->ts)); g_free (txt); g_return_val_if_fail (ok, FALSE); info->s_block_count++; return (TRUE); } /* (parent timespec-node) input: TimespecParseInfo * returns: NA start: NA characters: accumulate. end: convert characters to secs part of input Timespec and inc s_block_count. cleanup-result: NA cleanup-chars: g_free data. fail: NA result-fail: NA chars-fail: g_free data. */ gboolean generic_timespec_nsecs_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { gchar* txt = NULL; TimespecParseInfo* info = (TimespecParseInfo*) parent_data; gboolean ok; g_return_val_if_fail (parent_data, FALSE); txt = concatenate_child_result_chars (data_from_children); g_return_val_if_fail (txt, FALSE); ok = string_to_timespec_nsecs (txt, & (info->ts)); g_free (txt); g_return_val_if_fail (ok, FALSE); info->ns_block_count++; return (TRUE); } static sixtp* timespec_sixtp_new (sixtp_end_handler ender) { return sixtp_set_any ( sixtp_new (), FALSE, SIXTP_CHARACTERS_HANDLER_ID, generic_accumulate_chars, SIXTP_END_HANDLER_ID, ender, SIXTP_CLEANUP_CHARS_ID, sixtp_child_free_data, SIXTP_CHARS_FAIL_ID, sixtp_child_free_data, SIXTP_NO_MORE_HANDLERS); } sixtp* generic_timespec_parser_new (sixtp_end_handler end_handler) { sixtp* top_level = sixtp_set_any (sixtp_new (), FALSE, SIXTP_START_HANDLER_ID, generic_timespec_start_handler, SIXTP_CHARACTERS_HANDLER_ID, allow_and_ignore_only_whitespace, SIXTP_END_HANDLER_ID, end_handler, SIXTP_CLEANUP_RESULT_ID, sixtp_child_free_data, SIXTP_FAIL_HANDLER_ID, generic_free_data_for_children, SIXTP_RESULT_FAIL_ID, sixtp_child_free_data, SIXTP_NO_MORE_HANDLERS); g_return_val_if_fail (top_level, NULL); if (!sixtp_add_some_sub_parsers ( top_level, TRUE, "s", timespec_sixtp_new (generic_timespec_secs_end_handler), "ns", timespec_sixtp_new (generic_timespec_nsecs_end_handler), NULL, NULL)) { return NULL; } return (top_level); } /****************************************************************************/ /* generic guid handler... Attempts to parse the current accumulated characters data as a guid and return it. input: NA returns: GncGUID* start: NA characters: return string copy for accumulation in end handler. end: concatenate all chars and create and return GncGUID*, if possible. cleanup-result: g_free the GncGUID* cleanup-chars: g_free the result string. fail: NA result-fail: g_free the GncGUID* chars-fail: g_free the result string. */ gboolean generic_guid_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { gchar* txt = NULL; GncGUID* gid; gboolean ok; txt = concatenate_child_result_chars (data_from_children); g_return_val_if_fail (txt, FALSE); gid = g_new (GncGUID, 1); if (!gid) { g_free (txt); return (FALSE); } ok = string_to_guid (txt, gid); g_free (txt); if (!ok) { PERR ("couldn't parse GncGUID"); g_free (gid); return (FALSE); } *result = gid; return (TRUE); } sixtp* generic_guid_parser_new (void) { return sixtp_set_any ( sixtp_new (), FALSE, SIXTP_CHARACTERS_HANDLER_ID, generic_accumulate_chars, SIXTP_CLEANUP_CHARS_ID, sixtp_child_free_data, SIXTP_CHARS_FAIL_ID, sixtp_child_free_data, SIXTP_END_HANDLER_ID, generic_guid_end_handler, SIXTP_RESULT_FAIL_ID, sixtp_child_free_data, SIXTP_CLEANUP_RESULT_ID, sixtp_child_free_data, SIXTP_NO_MORE_HANDLERS); } /****************************************************************************/ /* generic gnc_numeric handler... Attempts to parse the current accumulated characters data as a gnc_numeric and return it. input: NA returns: gnc_numeric* start: NA characters: return string copy for accumulation in end handler. end: concatenate all chars and create and return gnc_numeric*, if possible. cleanup-result: g_free the gnc_numeric* cleanup-chars: g_free the result string. fail: NA result-fail: g_free the gnc_numeric* chars-fail: g_free the result string. */ gboolean generic_gnc_numeric_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { gnc_numeric* num = NULL; gchar* txt = NULL; gboolean ok = FALSE; txt = concatenate_child_result_chars (data_from_children); if (txt) { num = g_new (gnc_numeric, 1); if (num) { if (string_to_gnc_numeric (txt, num)) { ok = TRUE; *result = num; } } } g_free (txt); if (!ok) { PERR ("couldn't parse numeric quantity"); g_free (num); } return (ok); } sixtp* generic_gnc_numeric_parser_new (void) { return sixtp_set_any ( sixtp_new (), FALSE, SIXTP_CHARACTERS_HANDLER_ID, generic_accumulate_chars, SIXTP_CLEANUP_CHARS_ID, sixtp_child_free_data, SIXTP_CHARS_FAIL_ID, sixtp_child_free_data, SIXTP_END_HANDLER_ID, generic_gnc_numeric_end_handler, SIXTP_RESULT_FAIL_ID, sixtp_child_free_data, SIXTP_CLEANUP_RESULT_ID, sixtp_child_free_data, SIXTP_NO_MORE_HANDLERS); } /***************************************************************************/ sixtp* restore_char_generator (sixtp_end_handler ender) { return sixtp_set_any ( sixtp_new (), FALSE, SIXTP_CHARACTERS_HANDLER_ID, generic_accumulate_chars, SIXTP_END_HANDLER_ID, ender, SIXTP_CLEANUP_CHARS_ID, sixtp_child_free_data, SIXTP_CHARS_FAIL_ID, sixtp_child_free_data, SIXTP_NO_MORE_HANDLERS); } /***************************** END OF FILE *********************************/