/******************************************************************** * sixtp-dom-parsers.c * * Copyright 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 * * * ********************************************************************/ #include extern "C" { #include #include #include } #include "gnc-xml-helper.h" #include "sixtp-utils.h" #include "sixtp-dom-parsers.h" #include static QofLogModule log_module = GNC_MOD_IO; GncGUID* dom_tree_to_guid (xmlNodePtr node) { if (!node->properties) { return NULL; } if (strcmp ((char*) node->properties->name, "type") != 0) { PERR ("Unknown attribute for id tag: %s", node->properties->name ? (char*) node->properties->name : "(null)"); return NULL; } { char* type; type = (char*)xmlNodeGetContent (node->properties->xmlAttrPropertyValue); /* handle new and guid the same for the moment */ if ((g_strcmp0 ("guid", type) == 0) || (g_strcmp0 ("new", type) == 0)) { auto gid = guid_new (); char* guid_str; guid_str = (char*)xmlNodeGetContent (node->xmlChildrenNode); string_to_guid (guid_str, gid); xmlFree (guid_str); xmlFree (type); return gid; } else { PERR ("Unknown type %s for attribute type for tag %s", type ? type : "(null)", node->properties->name ? (char*) node->properties->name : "(null)"); xmlFree (type); return NULL; } } } static KvpValue* dom_tree_to_integer_kvp_value (xmlNodePtr node) { gchar* text; gint64 daint; KvpValue* ret = NULL; text = dom_tree_to_text (node); if (string_to_gint64 (text, &daint)) { ret = new KvpValue {daint}; } g_free (text); return ret; } gboolean dom_tree_to_integer (xmlNodePtr node, gint64* daint) { gchar* text; gboolean ret; text = dom_tree_to_text (node); ret = string_to_gint64 (text, daint); g_free (text); return ret; } gboolean dom_tree_to_guint16 (xmlNodePtr node, guint16* i) { gboolean ret; guint j = 0; ret = dom_tree_to_guint (node, &j); *i = (guint16) j; return ret; } gboolean dom_tree_to_guint (xmlNodePtr node, guint* i) { gchar* text, *endptr; gboolean ret; text = dom_tree_to_text (node); /* In spite of the strange string_to_gint64 function, I'm just going to use strtoul here until someone shows me the error of my ways. -CAS */ *i = (guint) strtoul (text, &endptr, 0); ret = (endptr != text); g_free (text); return ret; } gboolean dom_tree_to_boolean (xmlNodePtr node, gboolean* b) { gchar* text; text = dom_tree_to_text (node); if (g_ascii_strncasecmp (text, "true", 4) == 0) { *b = TRUE; g_free (text); return TRUE; } else if (g_ascii_strncasecmp (text, "false", 5) == 0) { *b = FALSE; g_free (text); return TRUE; } else { *b = FALSE; g_free (text); return FALSE; } } static KvpValue* dom_tree_to_double_kvp_value (xmlNodePtr node) { gchar* text; double dadoub; KvpValue* ret = NULL; text = dom_tree_to_text (node); if (string_to_double (text, &dadoub)) { ret = new KvpValue {dadoub}; } g_free (text); return ret; } static KvpValue* dom_tree_to_numeric_kvp_value (xmlNodePtr node) { gnc_numeric* danum; KvpValue* ret = NULL; danum = dom_tree_to_gnc_numeric (node); if (danum) { ret = new KvpValue {*danum}; } g_free (danum); return ret; } static KvpValue* dom_tree_to_string_kvp_value (xmlNodePtr node) { const gchar* datext; KvpValue* ret = NULL; datext = dom_tree_to_text (node); if (datext) { ret = new KvpValue {datext}; } return ret; } static KvpValue* dom_tree_to_guid_kvp_value (xmlNodePtr node) { GncGUID* daguid; KvpValue* ret = NULL; daguid = dom_tree_to_guid (node); if (daguid) { ret = new KvpValue {daguid}; } return ret; } static KvpValue* dom_tree_to_time64_kvp_value (xmlNodePtr node) { Time64 t{dom_tree_to_time64 (node)}; return new KvpValue {t}; } static KvpValue* dom_tree_to_gdate_kvp_value (xmlNodePtr node) { GDate* date; KvpValue* ret = NULL; date = dom_tree_to_gdate (node); if (date) { ret = new KvpValue {*date}; } g_free (date); return ret; } gboolean string_to_binary (const gchar* str, void** v, guint64* data_len) { guint64 str_len; guchar* data; unsigned int i, j; g_return_val_if_fail (v != NULL, FALSE); g_return_val_if_fail (data_len != NULL, 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 = str_len / 2; data = g_new0 (guchar, *data_len); for (j = 0, i = 0; i < str_len; i += 2, j++) { gchar tmpstr[3]; long int converted; tmpstr[0] = str[i]; tmpstr[1] = str[i + 1]; tmpstr[2] = '\0'; converted = strtol (tmpstr, NULL, 16); data[j] = (unsigned char)converted; } *v = data; return (TRUE); } static KvpValue* dom_tree_to_kvp_value (xmlNodePtr node); //needed for test access as well as internal use. KvpFrame* dom_tree_to_kvp_frame (xmlNodePtr node); static KvpValue* dom_tree_to_list_kvp_value (xmlNodePtr node) { GList* list = NULL; xmlNodePtr mark; KvpValue* ret = NULL; for (mark = node->xmlChildrenNode; mark; mark = mark->next) { KvpValue* new_val; if (g_strcmp0 ((char*)mark->name, "text") == 0) continue; new_val = dom_tree_to_kvp_value (mark); if (new_val) { list = g_list_prepend (list, (gpointer)new_val); } } list = g_list_reverse (list); ret = new KvpValue {list}; return ret; } static KvpValue* dom_tree_to_frame_kvp_value (xmlNodePtr node) { KvpFrame* frame; KvpValue* ret = NULL; frame = dom_tree_to_kvp_frame (node); if (frame) { ret = new KvpValue {frame}; } return ret; } struct kvp_val_converter { const gchar* tag; KvpValue* (*converter) (xmlNodePtr node); }; /* Note: The type attribute must remain 'timespec' to maintain compatibility. */ struct kvp_val_converter val_converters[] = { { "integer", dom_tree_to_integer_kvp_value }, { "double", dom_tree_to_double_kvp_value }, { "numeric", dom_tree_to_numeric_kvp_value }, { "string", dom_tree_to_string_kvp_value }, { "guid", dom_tree_to_guid_kvp_value }, { "timespec", dom_tree_to_time64_kvp_value }, { "gdate", dom_tree_to_gdate_kvp_value }, { "list", dom_tree_to_list_kvp_value }, { "frame", dom_tree_to_frame_kvp_value }, { 0, 0 }, }; static KvpValue* dom_tree_to_kvp_value (xmlNodePtr node) { xmlChar* xml_type; gchar* type; struct kvp_val_converter* mark; KvpValue* ret = NULL; xml_type = xmlGetProp (node, BAD_CAST "type"); if (xml_type) { type = g_strdup ((char*) xml_type); xmlFree (xml_type); } else type = NULL; for (mark = val_converters; mark->tag; mark++) { if (g_strcmp0 (type, mark->tag) == 0) { ret = (mark->converter) (node); } } if (!mark->tag) { /* FIXME: deal with unknown type tag here */ } g_free (type); return ret; } static gboolean dom_tree_to_kvp_frame_given (xmlNodePtr node, KvpFrame* frame) { xmlNodePtr mark; g_return_val_if_fail (node, FALSE); g_return_val_if_fail (frame, FALSE); for (mark = node->xmlChildrenNode; mark; mark = mark->next) { if (g_strcmp0 ((char*)mark->name, "slot") == 0) { xmlNodePtr mark2; gchar* key = NULL; KvpValue* val = NULL; for (mark2 = mark->xmlChildrenNode; mark2; mark2 = mark2->next) { if (g_strcmp0 ((char*)mark2->name, "slot:key") == 0) { key = dom_tree_to_text (mark2); } else if (g_strcmp0 ((char*)mark2->name, "slot:value") == 0) { val = dom_tree_to_kvp_value (mark2); } else { /* FIXME: should put some error here. * But ignore text type! */ } } if (key) { if (val) { //We're deleting the old KvpValue returned by replace_nc(). delete frame->set ({key}, val); } else { /* FIXME: should put some error here */ } g_free (key); } } } return TRUE; } KvpFrame* dom_tree_to_kvp_frame (xmlNodePtr node) { g_return_val_if_fail (node, NULL); auto ret = new KvpFrame; if (dom_tree_to_kvp_frame_given (node, ret)) return ret; delete ret; return NULL; } gboolean dom_tree_create_instance_slots (xmlNodePtr node, QofInstance* inst) { KvpFrame* frame = qof_instance_get_slots (inst); return dom_tree_to_kvp_frame_given (node, frame); } gchar* dom_tree_to_text (xmlNodePtr tree) { /* Expect *only* text and comment sibling nodes in the given tree -- which actually may only be a "list". i.e. if you're trying to extract bar from bar, pass in ->xmlChildrenNode to this function. This expectation is different from the rest of the dom_tree_to_* converters... Ignores comment nodes and collapse text nodes into one string. Returns NULL if expectations are unsatisfied. */ gchar* result; gchar* temp; g_return_val_if_fail (tree, NULL); /* no nodes means it's an empty string text */ if (!tree->xmlChildrenNode) { DEBUG ("No children"); return g_strdup (""); } temp = (char*)xmlNodeListGetString (NULL, tree->xmlChildrenNode, TRUE); if (!temp) { DEBUG ("Null string"); return NULL; } DEBUG ("node string [%s]", (temp == NULL ? "(null)" : temp)); result = g_strdup (temp); xmlFree (temp); return result; } gnc_numeric* dom_tree_to_gnc_numeric (xmlNodePtr node) { gchar* content = dom_tree_to_text (node); if (!content) return NULL; gnc_numeric *ret = g_new (gnc_numeric, 1); if (!string_to_gnc_numeric (content, ret)) *ret = gnc_numeric_zero (); g_free (content); return ret; } time64 dom_tree_to_time64 (xmlNodePtr node) { /* Turn something like this Mon, 05 Jun 2000 23:16:19 -0500 into a time64, returning INT64_MAX that we're using to flag an erroneous date if there's a problem. Only one ts:date element is permitted for any date attribute. */ time64 ret {INT64_MAX}; gboolean seen = FALSE; xmlNodePtr n; for (n = node->xmlChildrenNode; n; n = n->next) { switch (n->type) { case XML_COMMENT_NODE: case XML_TEXT_NODE: break; case XML_ELEMENT_NODE: if (g_strcmp0 ("ts:date", (char*)n->name) == 0) { if (seen) { return INT64_MAX; } else { gchar* content = dom_tree_to_text (n); if (!content) { return INT64_MAX; } ret = gnc_iso8601_to_time64_gmt (content); g_free (content); seen = TRUE; } } break; default: PERR ("unexpected sub-node."); return INT64_MAX; break; } } if (!seen) { PERR ("no ts:date node found."); return INT64_MAX; } return ret; } GDate* dom_tree_to_gdate (xmlNodePtr node) { /* Turn something like this 2001-04-03 into a GDate. If the xml is invalid, returns NULL. */ GDate* ret; gboolean seen_date = FALSE; xmlNodePtr n; /* creates an invalid date */ ret = g_date_new (); for (n = node->xmlChildrenNode; n; n = n->next) { switch (n->type) { case XML_COMMENT_NODE: case XML_TEXT_NODE: break; case XML_ELEMENT_NODE: if (g_strcmp0 ("gdate", (char*)n->name) == 0) { if (seen_date) { goto failure; } else { gchar* content = dom_tree_to_text (n); gint year, month, day; if (!content) { goto failure; } if (sscanf (content, "%d-%d-%d", &year, &month, &day) != 3) { g_free (content); goto failure; } g_free (content); seen_date = TRUE; g_date_set_dmy (ret, day, static_cast (month), year); if (!g_date_valid (ret)) { PWARN ("invalid date"); goto failure; } } } break; default: PERR ("unexpected sub-node."); goto failure; } } if (!seen_date) { PWARN ("no gdate node found."); goto failure; } return ret; failure: g_date_free (ret); return NULL; } gnc_commodity* dom_tree_to_commodity_ref_no_engine (xmlNodePtr node, QofBook* book) { /* Turn something like this NASDAQ LNUX into a gnc_commodity*, returning NULL on failure. Both sub-nodes are required, though for now, order is irrelevant. */ gnc_commodity* c = NULL; gchar* space_str = NULL; gchar* id_str = NULL; xmlNodePtr n; if (!node) return NULL; if (!node->xmlChildrenNode) return NULL; for (n = node->xmlChildrenNode; n; n = n->next) { switch (n->type) { case XML_COMMENT_NODE: case XML_TEXT_NODE: break; case XML_ELEMENT_NODE: if (g_strcmp0 ("cmdty:space", (char*)n->name) == 0) { if (space_str) { return NULL; } else { gchar* content = dom_tree_to_text (n); if (!content) return NULL; space_str = content; } } else if (g_strcmp0 ("cmdty:id", (char*)n->name) == 0) { if (id_str) { return NULL; } else { gchar* content = dom_tree_to_text (n); if (!content) return NULL; id_str = content; } } break; default: PERR ("unexpected sub-node."); return NULL; break; } } if (! (space_str && id_str)) { c = NULL; } else { g_strstrip (space_str); g_strstrip (id_str); c = gnc_commodity_new (book, NULL, space_str, id_str, NULL, 0); } g_free (space_str); g_free (id_str); return c; } gnc_commodity* dom_tree_to_commodity_ref (xmlNodePtr node, QofBook* book) { gnc_commodity* daref; gnc_commodity* ret; gnc_commodity_table* table; daref = dom_tree_to_commodity_ref_no_engine (node, book); table = gnc_commodity_table_get_table (book); g_return_val_if_fail (table != NULL, NULL); ret = gnc_commodity_table_lookup (table, gnc_commodity_get_namespace (daref), gnc_commodity_get_mnemonic (daref)); gnc_commodity_destroy (daref); g_return_val_if_fail (ret != NULL, NULL); return ret; } /***********************************************************************/ /* generic parser */ static inline void dom_tree_handlers_reset (struct dom_tree_handler* handlers) { for (; handlers->tag != NULL; handlers++) { handlers->gotten = 0; } } static inline gboolean dom_tree_handlers_all_gotten_p (struct dom_tree_handler* handlers) { gboolean ret = TRUE; for (; handlers->tag != NULL; handlers++) { if (handlers->required && ! handlers->gotten) { PERR ("Not defined and it should be: %s", handlers->tag ? handlers->tag : "(null)"); ret = FALSE; } } return ret; } static inline gboolean gnc_xml_set_data (const gchar* tag, xmlNodePtr node, gpointer item, struct dom_tree_handler* handlers) { for (; handlers->tag != NULL; handlers++) { if (g_strcmp0 (tag, handlers->tag) == 0) { (handlers->handler) (node, item); handlers->gotten = TRUE; break; } } if (!handlers->tag) { PERR ("Unhandled tag: %s", tag ? tag : "(null)"); return FALSE; } return TRUE; } gboolean dom_tree_generic_parse (xmlNodePtr node, struct dom_tree_handler* handlers, gpointer data) { xmlNodePtr achild; gboolean successful = TRUE; dom_tree_handlers_reset (handlers); for (achild = node->xmlChildrenNode; achild; achild = achild->next) { /* ignore stray text nodes */ if (g_strcmp0 ((char*)achild->name, "text") == 0) continue; if (!gnc_xml_set_data ((char*)achild->name, achild, data, handlers)) { PERR ("gnc_xml_set_data failed"); successful = FALSE; continue; } } if (!dom_tree_handlers_all_gotten_p (handlers)) { PERR ("didn't find all of the expected tags in the input"); successful = FALSE; } return successful; } gboolean dom_tree_valid_time64 (time64 val, const xmlChar * name) { if (val != INT64_MAX) return TRUE; g_warning ("Invalid timestamp in data file. Look for a '%s' entry " "with a year outside of the valid range: 1400..10000", name); return FALSE; }