/******************************************************************** * io-gncxml-r.c -- read XML-format gnucash data file * * Copyright (C) 2000 Gnumatic, Inc. * * * * Initial code by Rob L. Browning 4Q 2000 * * Tuneups by James LewisMoss Dec 2000 * * Excessive hacking Linas Vepstas January 2001 * * * * 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 #include #include #include #include #include #include #include #include #include #include } #include "io-gncxml.h" #include "sixtp.h" #include "sixtp-dom-parsers.h" #include "sixtp-stack.h" #include "sixtp-parsers.h" #include "sixtp-utils.h" #include /* from Transaction-xml-parser-v1.c */ static sixtp* gnc_transaction_parser_new (void); /* from Account-xml-parser-v1.c */ static sixtp* gnc_account_parser_new (void); /* from Ledger-xml-parser-v1.c */ static sixtp* ledger_data_parser_new (void); /* from Commodity-xml-parser-v1.c */ static sixtp* commodity_restore_parser_new (void); /* from Commodity-xml-parser-v1.c */ static sixtp* generic_gnc_commodity_lookup_parser_new (void); /* from Query-xml-parser-v1.c */ //static sixtp* query_server_parser_new (void); /* from sixtp-kvp-parser.c */ static sixtp* kvp_frame_parser_new (void); /* from gnc-pricedb-xml-v1.c */ static sixtp* gnc_pricedb_parser_new (void); typedef enum { GNC_PARSE_ERR_NONE, GNC_PARSE_ERR_BAD_VERSION, } GNCParseErr; typedef struct { /* have we gotten the file version yet? */ gboolean seen_version; gint64 version; /* top level parser - we need this so we can set it up after we see the file version. */ sixtp* gnc_parser; /* The book */ QofBook* book; /* The root account */ Account* root_account; /* The pricedb */ GNCPriceDB* pricedb; /* The query */ // Query *query; GNCParseErr error; } GNCParseStatus; /* result for a characters handler is shared across all character handlers for a given node. result for start/end pair is shared as well. Cleaning up the results returned by children and characters handlers is the user's responsibility. results have to have cleanup pointers for exceptional failures stack frames also have to have cleanup funcs for exceptional failures after the start tag handler has been called, but before the end tag handler has been called. If the end tag handler is called, but returns FALSE, it is expected that the end tag handler has taken care of any cleanup itself. result cleanup functions are called for a node's children just after the end handler unless should_cleanup has been set to FALSE, or unless there's a failure. If there's a failure, then the cleanup is left to the failure handler. */ static QofLogModule log_module = GNC_MOD_IO; /* ================================================================= */ /* (lineage ) Fancy and strange - look for an integer version number. If we get one, then modify the parent parser to handle the input. this is a simple_chars_only_parser with an end handler that grabs the version number and tweaks the parser, if possible. */ static gboolean gnc_parser_configure_for_input_version (GNCParseStatus* status, gint64 version) { status->version = version; status->seen_version = TRUE; /* Check for a legal version here. */ if (version != 1) { status->error = GNC_PARSE_ERR_BAD_VERSION; return (FALSE); } /* Now set up the parser based on the version. */ /* add */ { sixtp* ledger_data_pr = ledger_data_parser_new (); g_return_val_if_fail (ledger_data_pr, FALSE); sixtp_add_sub_parser (status->gnc_parser, "ledger-data", ledger_data_pr); } return (TRUE); } static gboolean gnc_version_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { GNCParseStatus* pstatus = (GNCParseStatus*) global_data; gint64 version; gboolean ok; gchar* txt; g_return_val_if_fail (pstatus, FALSE); if (pstatus->seen_version) return (FALSE); txt = concatenate_child_result_chars (data_from_children); g_return_val_if_fail (txt, FALSE); ok = string_to_gint64 (txt, &version); g_free (txt); g_return_val_if_fail (ok, FALSE); if (!gnc_parser_configure_for_input_version (pstatus, version)) return (FALSE); return (TRUE); } static sixtp* gnc_version_parser_new (void) { return (simple_chars_only_parser_new (gnc_version_end_handler)); } /****************************************************************************/ /* (lineage #f) Takes the results from various children and puts them in the global_data result structure. from parent: NA for children: NA result: NA ----------- start: NA before-child: make sure we don't get two ledger-data's (not allowed ATM). after-child: if a ledger-data child, parse_data->root_account = *result. characters: allow_and_ignore_only_whitespace Similarly, only one query is allowed ... end: NA cleanup-result: NA cleanup-chars: NA fail: NA result-fail: NA chars-fail: NA */ static gboolean gnc_parser_before_child_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag, const gchar* child_tag) { GNCParseStatus* pstatus = (GNCParseStatus*) global_data; g_return_val_if_fail (pstatus, FALSE); if (strcmp (child_tag, "ledger-data") == 0) { if (pstatus->root_account) { return (FALSE); } } return (TRUE); } static gboolean gnc_parser_after_child_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag, const gchar* child_tag, sixtp_child_result* child_result) { GNCParseStatus* pstatus = (GNCParseStatus*) global_data; g_return_val_if_fail (pstatus, FALSE); if (strcmp (child_tag, "ledger-data") == 0) { g_return_val_if_fail (child_result, FALSE); g_return_val_if_fail (child_result->data, FALSE); pstatus->root_account = (Account*) child_result->data; child_result->should_cleanup = FALSE; } return (TRUE); } static sixtp* gnc_parser_new (void) { return sixtp_set_any ( sixtp_new (), FALSE, SIXTP_CHARACTERS_HANDLER_ID, allow_and_ignore_only_whitespace, SIXTP_BEFORE_CHILD_HANDLER_ID, gnc_parser_before_child_handler, SIXTP_AFTER_CHILD_HANDLER_ID, gnc_parser_after_child_handler, SIXTP_NO_MORE_HANDLERS); } /* ================================================================== */ static sixtp* gncxml_setup_for_read (GNCParseStatus* global_parse_status) { sixtp* top_level_pr; sixtp* gnc_pr; sixtp* gnc_version_pr; /* top-level: This is just a dummy node. It doesn't do anything. For now, the result is communicated through the global_data parser. */ top_level_pr = sixtp_new (); g_return_val_if_fail (top_level_pr, FALSE); sixtp_set_chars (top_level_pr, allow_and_ignore_only_whitespace); /* */ gnc_pr = gnc_parser_new (); if (!gnc_pr) { sixtp_destroy (top_level_pr); return (NULL); } sixtp_add_sub_parser (top_level_pr, "gnc", gnc_pr); /* */ gnc_version_pr = gnc_version_parser_new (); if (!gnc_version_pr) { sixtp_destroy (top_level_pr); return (NULL); } sixtp_add_sub_parser (gnc_pr, "version", gnc_version_pr); global_parse_status->seen_version = FALSE; global_parse_status->gnc_parser = gnc_pr; global_parse_status->root_account = NULL; global_parse_status->pricedb = NULL; // global_parse_status->query = NULL; global_parse_status->error = GNC_PARSE_ERR_NONE; return top_level_pr; } /* ================================================================== */ gboolean qof_session_load_from_xml_file (QofBook* book, const char* filename) { gboolean parse_ok; gpointer parse_result = NULL; sixtp* top_level_pr; GNCParseStatus global_parse_status; Account* root; global_parse_status.book = book; g_return_val_if_fail (book, FALSE); g_return_val_if_fail (filename, FALSE); xaccDisableDataScrubbing (); top_level_pr = gncxml_setup_for_read (&global_parse_status); g_return_val_if_fail (top_level_pr, FALSE); parse_ok = sixtp_parse_file (top_level_pr, filename, NULL, &global_parse_status, &parse_result); sixtp_destroy (top_level_pr); xaccEnableDataScrubbing (); if (parse_ok) { if (!global_parse_status.root_account) return FALSE; root = global_parse_status.root_account; gnc_book_set_root_account (book, root); /* Fix account and transaction commodities */ xaccAccountTreeScrubCommodities (root); /* Fix split amount/value */ xaccAccountTreeScrubSplits (root); return (TRUE); } else { return (FALSE); } } /* ================================================================== */ gboolean gnc_is_xml_data_file (const gchar* filename) { if ((gnc_is_our_xml_file (filename, NULL)) == GNC_BOOK_XML1_FILE) return TRUE; return FALSE; } /* ================================================================== */ #include "qof.h" /****************************************************************************/ /* A collection of node functions intended to parse a sub-node set that looks like this: notes foo temp 97 and return a KvpFrame*. The start handler for the top allocates the KvpFrame* and passes it to the children. The blocks add slots according to their (key) and value blocks. FIXME: right now this is totally inappropriate for cases where we want to read in a set of new values that should "merge" with the existing values. This is only appropriate for wholesale replacement of the slots. */ /* kvp-frame [value] handlers Handle the possible values. Each value handler is expected to parse it's subtree and return an appropriate kvp_value* in its result. The handler will then cram it where it belongs. */ static void kvp_value_result_cleanup (sixtp_child_result* cr) { KvpValue* v = static_cast (cr->data); if (v) delete v; } static sixtp* simple_kvp_value_parser_new (sixtp_end_handler end_handler) { return sixtp_set_any (sixtp_new (), FALSE, SIXTP_CHARACTERS_HANDLER_ID, generic_accumulate_chars, SIXTP_END_HANDLER_ID, end_handler, SIXTP_CLEANUP_RESULT_ID, kvp_value_result_cleanup, SIXTP_CLEANUP_CHARS_ID, sixtp_child_free_data, SIXTP_RESULT_FAIL_ID, kvp_value_result_cleanup, SIXTP_CHARS_FAIL_ID, sixtp_child_free_data, SIXTP_NO_MORE_HANDLERS); } /* - gint64 kvp_value parser. input: NA returns: gint64 kvp_value start: NA chars: generic_accumulate_chars. end: convert chars to gint64 kvp_value* if possible and return. cleanup-result: kvp_value_delete. cleanup-chars: g_free (for chars) fail: NA result-fail: kvp_value_delete chars-fail: g_free (for chars) */ /* ------------------------------------------------------------ */ /* generic type copnversion for kvp types */ #define KVP_CVT_VALUE(TYPE) \ { \ gchar *txt = NULL; \ TYPE val; \ KvpValue *kvpv; \ gboolean ok; \ \ txt = concatenate_child_result_chars(data_from_children); \ g_return_val_if_fail(txt, FALSE); \ \ ok = (gboolean) string_to_##TYPE(txt, &val); \ g_free(txt); \ g_return_val_if_fail(ok, FALSE); \ \ kvpv = new KvpValue{val}; \ g_return_val_if_fail(kvpv, FALSE); \ \ *result = kvpv; \ return(TRUE); \ } /* ------------------------------------------------------------ */ static gboolean gint64_kvp_value_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { KVP_CVT_VALUE (gint64); } static sixtp* gint64_kvp_value_parser_new (void) { return (simple_kvp_value_parser_new (gint64_kvp_value_end_handler)); } static gboolean double_kvp_value_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { KVP_CVT_VALUE (double); } static sixtp* double_kvp_value_parser_new (void) { return (simple_kvp_value_parser_new (double_kvp_value_end_handler)); } static gboolean gnc_numeric_kvp_value_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { KVP_CVT_VALUE (gnc_numeric); } static sixtp* gnc_numeric_kvp_value_parser_new (void) { return (simple_kvp_value_parser_new (gnc_numeric_kvp_value_end_handler)); } static gboolean string_kvp_value_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; KvpValue* kvpv; txt = concatenate_child_result_chars (data_from_children); g_return_val_if_fail (txt, FALSE); kvpv = new KvpValue {g_strdup (txt)}; g_free (txt); g_return_val_if_fail (kvpv, FALSE); *result = kvpv; return (TRUE); } static sixtp* string_kvp_value_parser_new (void) { return (simple_kvp_value_parser_new (string_kvp_value_end_handler)); } /* the guid handler is almost the same as above, but has * inconsistent type handling */ static gboolean guid_kvp_value_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 val; KvpValue* kvpv; gboolean ok; txt = concatenate_child_result_chars (data_from_children); g_return_val_if_fail (txt, FALSE); ok = string_to_guid (txt, &val); g_free (txt); g_return_val_if_fail (ok, FALSE); kvpv = new KvpValue {guid_copy (&val)}; g_return_val_if_fail (kvpv, FALSE); *result = kvpv; return (TRUE); } static sixtp* guid_kvp_value_parser_new (void) { return (simple_kvp_value_parser_new (guid_kvp_value_end_handler)); } /*********************************/ /* glist kvp-value handler */ /* (lineage ) input: NA returns: glist kvp_value start: NA chars: allow_and_ignore_only_whitespace end: convert the child list pointer to a glist kvp_value and return. cleanup-result: kvp_value_delete cleanup-chars: NA fail: NA result-fail: kvp_value_delete chars-fail: NA */ static gboolean glist_kvp_value_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { GSList* lp; GList* result_glist; KvpValue* kvp_result; result_glist = NULL; for (lp = data_from_children; lp; lp = lp->next) { sixtp_child_result* cr = (sixtp_child_result*) lp->data; KvpValue* kvp = (KvpValue*) cr->data; /* children are in reverse chron order, so this fixes it. */ result_glist = g_list_prepend (result_glist, kvp); cr->should_cleanup = FALSE; } kvp_result = new KvpValue {result_glist}; if (!kvp_result) g_list_free_full (result_glist, [] (void * data) { delete static_cast (data);}); *result = kvp_result; return (TRUE); } /* ---------------------------------------------- */ #define KVP_TOKEN(NAME,TOK) \ child_pr = NAME##_kvp_value_parser_new(); \ g_return_val_if_fail(child_pr, FALSE); \ sixtp_add_sub_parser(p, TOK, child_pr); /* ---------------------------------------------- */ static gboolean add_all_kvp_value_parsers_as_sub_nodes (sixtp* p, sixtp* kvp_frame_parser, sixtp* glist_parser) { sixtp* child_pr; g_return_val_if_fail (p, FALSE); g_return_val_if_fail (kvp_frame_parser, FALSE); KVP_TOKEN (gint64, "gint64"); KVP_TOKEN (double, "double"); KVP_TOKEN (gnc_numeric, "numeric"); KVP_TOKEN (string, "string"); KVP_TOKEN (guid, "guid"); sixtp_add_sub_parser (p, "glist", glist_parser); sixtp_add_sub_parser (p, "frame", kvp_frame_parser); return (TRUE); } static sixtp* glist_kvp_value_parser_new (sixtp* kvp_frame_parser) { sixtp* top_level = sixtp_set_any ( sixtp_new (), FALSE, SIXTP_CHARACTERS_HANDLER_ID, allow_and_ignore_only_whitespace, SIXTP_END_HANDLER_ID, glist_kvp_value_end_handler, SIXTP_CLEANUP_RESULT_ID, kvp_value_result_cleanup, SIXTP_RESULT_FAIL_ID, kvp_value_result_cleanup, SIXTP_NO_MORE_HANDLERS); if (!top_level) { return NULL; } if (!add_all_kvp_value_parsers_as_sub_nodes (top_level, kvp_frame_parser, top_level)) { sixtp_destroy (top_level); return (NULL); } return (top_level); } /*********************************/ /* kvp-frame slot handlers handlers for the some key<[value]>data sub-structure. */ /* (lineage ) kvp-frame slot key handler - just a generic-string-parser */ /* (lineage ) kvp-frame slot handler. input: KvpFrame* returns: NA start: NA characters: allow_and_ignore_only_whitespace end: check for two children - one must be a - if OK, set slot. cleanup-result: NA cleanup-chars: NA fail: NA result-fail: NA chars-fail: NA */ static gboolean kvp_frame_slot_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { KvpFrame* f = (KvpFrame*) parent_data; GSList* lp; gboolean first = TRUE; gchar* key = NULL; KvpValue* value = NULL; gboolean delete_value = FALSE; sixtp_child_result *cr1 = NULL, *cr2 = NULL, *cr = NULL; g_return_val_if_fail (f, FALSE); if (g_slist_length (data_from_children) != 2) return (FALSE); cr1 = (sixtp_child_result*)data_from_children->data; cr2 = (sixtp_child_result*)data_from_children->next->data; if (is_child_result_from_node_named(cr1, "k")) { key = (char*)cr1->data; cr = cr2; } else if (is_child_result_from_node_named(cr2, "k")) { key = (char*)cr2->data; cr = cr1; } else return FALSE; if (is_child_result_from_node_named (cr, "frame")) { KvpFrame* frame = static_cast (cr->data); value = new KvpValue {frame}; delete_value = TRUE; } else { value = static_cast (cr->data); delete_value = FALSE; } f->set ({key}, value); if (delete_value) delete value; return (TRUE); } static sixtp* kvp_frame_slot_parser_new (sixtp* kvp_frame_parser) { sixtp* top_level; sixtp* child_pr; sixtp* glist_pr; g_return_val_if_fail (kvp_frame_parser, NULL); if (! (top_level = sixtp_set_any ( sixtp_new (), FALSE, SIXTP_CHARACTERS_HANDLER_ID, allow_and_ignore_only_whitespace, SIXTP_END_HANDLER_ID, kvp_frame_slot_end_handler, SIXTP_NO_MORE_HANDLERS))) { return NULL; } child_pr = simple_chars_only_parser_new (NULL); if (!child_pr) { sixtp_destroy (top_level); return (NULL); } sixtp_add_sub_parser (top_level, "k", child_pr); glist_pr = glist_kvp_value_parser_new (kvp_frame_parser); if (!glist_pr) { sixtp_destroy (top_level); return (NULL); } if (!add_all_kvp_value_parsers_as_sub_nodes (top_level, kvp_frame_parser, glist_pr)) { sixtp_destroy (top_level); return (NULL); } return (top_level); } /* - can be used anywhere. input: NA returns: KvpFrame* start: Allocates KvpFrame* and places in data_for_children. characters: none (whitespace only). end: put KvpFrame* into result if everything's OK. cleanup-result: delete KvpFrame* cleanup-chars: NA fail: delete KvpFrame* result-fail: delete KvpFrame* chars-fail: NA */ static gboolean kvp_frame_start_handler (GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* data_for_children, gpointer* result, const gchar* tag, gchar** attrs) { auto f = new KvpFrame; *data_for_children = f; return (TRUE); } static gboolean kvp_frame_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { g_return_val_if_fail (data_for_children != NULL, FALSE); *result = data_for_children; return (TRUE); } static void kvp_frame_fail_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { auto f = static_cast (data_for_children); if (f) delete f; } static void kvp_frame_result_cleanup (sixtp_child_result* cr) { auto f = static_cast (cr->data); if (f) delete f; } static sixtp* kvp_frame_parser_new (void) { sixtp* top_level; if (! (top_level = sixtp_set_any ( sixtp_new (), FALSE, SIXTP_START_HANDLER_ID, kvp_frame_start_handler, SIXTP_CHARACTERS_HANDLER_ID, allow_and_ignore_only_whitespace, SIXTP_END_HANDLER_ID, kvp_frame_end_handler, SIXTP_CLEANUP_RESULT_ID, kvp_frame_result_cleanup, SIXTP_RESULT_FAIL_ID, kvp_frame_result_cleanup, SIXTP_FAIL_HANDLER_ID, kvp_frame_fail_handler, SIXTP_NO_MORE_HANDLERS))) { return NULL; } if (! (sixtp_add_some_sub_parsers ( top_level, TRUE, "s", kvp_frame_slot_parser_new (top_level), NULL, NULL))) { return NULL; } return (top_level); } /****************************************************************************/ /****************************************************************************/ /****************************************************************************/ /* (parent ) On failure or on normal cleanup, the root account will be killed, so if you want it, you better set should_cleanup to false input: NA to-children-via-*result: new root Account* returns: an Account* start: creates the root account and puts it into *result characters: NA end: finishes up the root account and leaves it in result. cleanup-result: deletes the root account (use should_cleanup to avoid). cleanup-chars: NA fail: deletes the root account in *result. result-fail: same as cleanup-result. chars-fail: NA */ static gboolean ledger_data_start_handler (GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* data_for_children, gpointer* result, const gchar* tag, gchar** attrs) { GNCParseStatus* pstatus = (GNCParseStatus*) global_data; Account* ra; /* disable logging during load; otherwise its just a mess */ xaccLogDisable (); ra = xaccMallocAccount (pstatus->book); g_return_val_if_fail (ra, FALSE); *data_for_children = ra; return (ra != NULL); } static gboolean ledger_data_after_child_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag, const gchar* child_tag, sixtp_child_result* child_result) { if (!child_result) return (TRUE); /* if we see the pricedb, deal with it */ if (child_result->type != SIXTP_CHILD_RESULT_NODE) return (TRUE); if (strcmp (child_result->tag, "pricedb") == 0) { GNCPriceDB* pdb = (GNCPriceDB*) child_result->data; GNCParseStatus* status = (GNCParseStatus*) global_data; g_return_val_if_fail (pdb, FALSE); g_return_val_if_fail (status, FALSE); if (status->pricedb) { PERR ("hit pricedb twice in data file."); return FALSE; } status->pricedb = pdb; child_result->should_cleanup = FALSE; } return (TRUE); } static gboolean ledger_data_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { Account* ra = (Account*) data_for_children; GList* descendants; g_return_val_if_fail (ra, FALSE); /* commit all accounts, this completes the BeginEdit started when the * account_end_handler finished reading the account. */ descendants = gnc_account_get_descendants (ra); g_list_foreach (descendants, (GFunc)xaccAccountCommitEdit, NULL); g_list_free (descendants); xaccLogEnable (); *result = ra; return (TRUE); } static void ledger_data_fail_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { Account* account = (Account*) data_for_children; if (account) { xaccAccountBeginEdit (account); xaccAccountDestroy (account); } } static void ledger_data_result_cleanup (sixtp_child_result* cr) { Account* account = (Account*) cr->data; if (account) { xaccAccountBeginEdit (account); xaccAccountDestroy (account); } } static sixtp* ledger_data_parser_new (void) { sixtp* top_level; /* */ if (! (top_level = sixtp_set_any ( sixtp_new (), FALSE, SIXTP_START_HANDLER_ID, ledger_data_start_handler, SIXTP_CHARACTERS_HANDLER_ID, allow_and_ignore_only_whitespace, SIXTP_AFTER_CHILD_HANDLER_ID, ledger_data_after_child_handler, SIXTP_END_HANDLER_ID, ledger_data_end_handler, SIXTP_CLEANUP_RESULT_ID, ledger_data_result_cleanup, SIXTP_FAIL_HANDLER_ID, ledger_data_fail_handler, SIXTP_RESULT_FAIL_ID, ledger_data_result_cleanup, SIXTP_NO_MORE_HANDLERS))) { return NULL; } if (!sixtp_add_some_sub_parsers ( top_level, TRUE, "commodity", commodity_restore_parser_new (), "pricedb", gnc_pricedb_parser_new (), "account", gnc_account_parser_new (), "transaction", gnc_transaction_parser_new (), NULL, NULL)) { return NULL; } return (top_level); } /***********************************************************************/ /****************************************************************************/ /* (parent ) This block does nothing but pass the ledger-data account group down to its children. It generates no data of its own, so it doesn't need any cleanup. input: Account* to-children-via-*result: Account* returns: NA start: pass input to children. characters: NA end: NA cleanup-result: NA cleanup-chars: NA fail: NA result-fail: NA chars-fail: NA */ static gboolean account_start_handler (GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* data_for_children, gpointer* result, const gchar* tag, gchar** attrs) { /* pass the parent data down to the children */ *data_for_children = parent_data; return (TRUE); } /****************************************************************************/ /* (lineage ) restores a given account. We allocate the new account in the start block, the children modify it, and in the end block, we see if the resultant account is OK, and if so, we add it to the ledger-data's account group. input: Account* to-children-via-*result: new Account* returns: NA start: create new Account*, and leave in for children. characters: NA end: clear *result cleanup-result: NA cleanup-chars: NA fail: delete Account* result-fail: NA chars-fail: NA */ static gboolean account_restore_start_handler (GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* data_for_children, gpointer* result, const gchar* tag, gchar** attrs) { GNCParseStatus* pstatus = (GNCParseStatus*) global_data; Account* acc = xaccMallocAccount (pstatus->book); g_return_val_if_fail (acc, FALSE); xaccAccountBeginEdit (acc); *data_for_children = acc; *result = acc; return (TRUE); } static gboolean account_restore_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { Account* parent = (Account*) parent_data; Account* acc = (Account*) * result; g_return_val_if_fail ((parent && acc), FALSE); /* CHECKME: do we need to xaccAccountRecomputeBalance(acc) here? */ xaccAccountCommitEdit (acc); /* If the account doesn't have a parent yet, just cram it into the top level */ if (!gnc_account_get_parent (acc)) gnc_account_append_child (parent, acc); *result = NULL; /* Now return the account to the "edit" state. At the end of reading * all the transactions, we will Commit. This replaces #splits * rebalances with #accounts rebalances at the end. A BIG win! */ xaccAccountBeginEdit (acc); return (TRUE); } static gboolean account_restore_after_child_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag, const gchar* child_tag, sixtp_child_result* child_result) { Account* a = (Account*) data_for_children; /* GNCParseStatus *pstatus = (GNCParseStatus *) global_data; */ g_return_val_if_fail (a, FALSE); if (!child_result) return (TRUE); if (child_result->type != SIXTP_CHILD_RESULT_NODE) return (TRUE); if (strcmp (child_result->tag, "slots") == 0) { auto f = static_cast (child_result->data); g_return_val_if_fail (f, FALSE); if (a->inst.kvp_data) delete a->inst.kvp_data; a->inst.kvp_data = f; child_result->should_cleanup = FALSE; } else if (strcmp (child_result->tag, "currency") == 0) { gnc_commodity* com = (gnc_commodity*) child_result->data; g_return_val_if_fail (com, FALSE); if (DxaccAccountGetCurrency (a)) return FALSE; DxaccAccountSetCurrency (a, com); /* let the normal child_result handler clean up com */ } else if (strcmp (child_result->tag, "security") == 0) { gnc_commodity* com = (gnc_commodity*) child_result->data; g_return_val_if_fail (com, FALSE); if (xaccAccountGetCommodity (a)) return FALSE; xaccAccountSetCommodity (a, com); /* let the normal child_result handler clean up com */ } return (TRUE); } static void account_restore_fail_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { Account* acc = (Account*) * result; if (acc) { xaccAccountBeginEdit (acc); xaccAccountDestroy (acc); } } /****************************************************************************/ /* (lineage ) restores a given account's name. input: Account* returns: NA start: NA characters: return string copy for accumulation in end handler. end: concatenate all chars and set as account name. cleanup-result: NA cleanup-chars: g_free the result string. fail: NA result-fail: NA chars-fail: g_free the result string. */ static gboolean acc_restore_name_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { Account* acc = (Account*) parent_data; gchar* name = NULL; g_return_val_if_fail (acc, FALSE); name = concatenate_child_result_chars (data_from_children); g_return_val_if_fail (name, FALSE); xaccAccountSetName (acc, name); g_free (name); return (TRUE); } /****************************************************************************/ /* (lineage ) restores a given account's guid. input: Account* returns: NA start: NA characters: return string copy for accumulation in end handler. end: concatenate all chars and set as account GncGUID if not duplicate. cleanup-result: NA cleanup-chars: g_free the result string. fail: NA result-fail: NA chars-fail: g_free the result string. */ static gboolean acc_restore_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) { GNCParseStatus* pstatus = (GNCParseStatus*) global_data; Account* acc = (Account*) parent_data; gchar* txt = NULL; GncGUID gid; gboolean ok; g_return_val_if_fail (acc, FALSE); txt = concatenate_child_result_chars (data_from_children); g_return_val_if_fail (txt, FALSE); ok = string_to_guid (txt, &gid); g_free (txt); g_return_val_if_fail (ok, FALSE); if (xaccAccountLookup (&gid, pstatus->book)) { return (FALSE); } xaccAccountSetGUID (acc, &gid); return (TRUE); } /****************************************************************************/ /* (lineage ) restores a given account's type. input: Account* returns: NA start: NA characters: return string copy for accumulation in end handler. end: concatenate all chars and set as account type. cleanup-result: NA cleanup-chars: g_free the result string. fail: NA result-fail: NA chars-fail: g_free the result string. */ static gboolean acc_restore_type_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { Account* acc = (Account*) parent_data; gchar* txt = NULL; GNCAccountType type; gboolean ok; g_return_val_if_fail (acc, FALSE); txt = concatenate_child_result_chars (data_from_children); g_return_val_if_fail (txt, FALSE); ok = xaccAccountStringToType (txt, &type); g_free (txt); g_return_val_if_fail (ok, FALSE); xaccAccountSetType (acc, type); return (TRUE); } /****************************************************************************/ /* (lineage ) restores a given account's code. input: Account* returns: NA start: NA characters: return string copy for accumulation in end handler. end: concatenate all chars and set as account type. cleanup-result: NA cleanup-chars: g_free the result string. fail: NA result-fail: NA chars-fail: g_free the result string. */ static gboolean acc_restore_code_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { Account* acc = (Account*) parent_data; gchar* txt = NULL; g_return_val_if_fail (acc, FALSE); txt = concatenate_child_result_chars (data_from_children); g_return_val_if_fail (txt, FALSE); xaccAccountSetCode (acc, txt); g_free (txt); return (TRUE); } /****************************************************************************/ /* (lineage ) restores a given account's description. input: Account* returns: NA start: NA characters: return string copy for accumulation in end handler. end: concatenate all chars and set as account description. cleanup-result: NA cleanup-chars: g_free the result string. fail: NA result-fail: NA chars-fail: g_free the result string. restores a given account's description. */ static gboolean acc_restore_description_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { Account* acc = (Account*) parent_data; gchar* txt = NULL; g_return_val_if_fail (acc, FALSE); txt = concatenate_child_result_chars (data_from_children); g_return_val_if_fail (txt, FALSE); xaccAccountSetDescription (acc, txt); g_free (txt); return (TRUE); } /****************************************************************************/ /* (lineage ) restores a given account's notes. input: Account* returns: NA start: NA characters: return string copy for accumulation in end handler. end: concatenate all chars and set as account notes. cleanup-result: NA cleanup-chars: g_free the result string. fail: NA result-fail: NA chars-fail: g_free the result string. */ static gboolean acc_restore_notes_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { Account* acc = (Account*) parent_data; gchar* txt = NULL; g_return_val_if_fail (acc, FALSE); txt = concatenate_child_result_chars (data_from_children); g_return_val_if_fail (txt, FALSE); xaccAccountSetNotes (acc, txt); g_free (txt); return (TRUE); } /****************************************************************************/ /* (lineage ) restores a given account's parent. input: Account* returns: NA start: NA characters: allow and ignore only whitespace. end: check for single child and if found, use result to set account guid. cleanup-result: NA cleanup-chars: NA fail: NA result-fail: NA chars-fail: NA */ static gboolean acc_restore_parent_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { GNCParseStatus* pstatus = (GNCParseStatus*) global_data; Account* acc = (Account*) parent_data; Account* parent; sixtp_child_result* child_result; GncGUID gid; g_return_val_if_fail (acc, FALSE); if (g_slist_length (data_from_children) != 1) return (FALSE); child_result = (sixtp_child_result*) data_from_children->data; if (!is_child_result_from_node_named (child_result, "guid")) return (FALSE); /* otherwise this must be a good result - use it */ gid = * ((GncGUID*) child_result->data); parent = xaccAccountLookup (&gid, pstatus->book); g_return_val_if_fail (parent, FALSE); gnc_account_append_child (parent, acc); return (TRUE); } static sixtp* parent_lookup_parser_new (void) { return sixtp_set_any (sixtp_new (), TRUE, SIXTP_CHARACTERS_HANDLER_ID, allow_and_ignore_only_whitespace, SIXTP_END_HANDLER_ID, acc_restore_parent_end_handler, SIXTP_NO_MORE_HANDLERS); } static sixtp* gnc_account_parser_new (void) { sixtp* restore_pr; sixtp* ret; /* */ if (! (ret = sixtp_set_any ( sixtp_new (), FALSE, SIXTP_START_HANDLER_ID, account_start_handler, SIXTP_CHARACTERS_HANDLER_ID, allow_and_ignore_only_whitespace, SIXTP_NO_MORE_HANDLERS))) { return NULL; } /* */ if (! (restore_pr = sixtp_set_any (sixtp_new (), FALSE, SIXTP_START_HANDLER_ID, account_restore_start_handler, SIXTP_END_HANDLER_ID, account_restore_end_handler, SIXTP_FAIL_HANDLER_ID, account_restore_fail_handler, SIXTP_AFTER_CHILD_HANDLER_ID, account_restore_after_child_handler, SIXTP_NO_MORE_HANDLERS))) { sixtp_destroy (ret); return NULL; } /* ( | | | | | )*/ if (!sixtp_add_some_sub_parsers ( restore_pr, TRUE, "name", restore_char_generator (acc_restore_name_end_handler), "guid", restore_char_generator (acc_restore_guid_end_handler), "type", restore_char_generator (acc_restore_type_end_handler), "code", restore_char_generator (acc_restore_code_end_handler), "description", restore_char_generator (acc_restore_description_end_handler), "notes", restore_char_generator (acc_restore_notes_end_handler), /* */ "currency", generic_gnc_commodity_lookup_parser_new (), /* */ "security", generic_gnc_commodity_lookup_parser_new (), /* */ "parent", sixtp_add_some_sub_parsers ( parent_lookup_parser_new (), TRUE, "guid", generic_guid_parser_new (), NULL, NULL), "slots", kvp_frame_parser_new (), NULL, NULL)) { sixtp_destroy (ret); return NULL; } sixtp_add_sub_parser (ret, "restore", restore_pr); return ret; } /***********************************************************************/ /****************************************************************************/ /* Commodity restorer. Right now we just check to see that fields aren't duplicated. If fields don't show up, then we just use "". We also check to see that we get a . If not, it's an error. Example: NASDAQ XYZZY Grue Enterprises XXX 100 */ /* ==================================================================== */ /*********************************/ /* (lineage ) Start handler allocates a gnc_commodity. The end_handler, if everything's OK, crams the commodity into the engine, otherwise it deletes it. input: NA returns: NA start: allocate CommodityParseInfo* and put it into data_for_children. characters: allow and ignore only whitespace. after-child: handle strings from simple chars children. end: if OK create gnc_commodity and add to engine. delete CommodityParseInfo. cleanup-result: NA cleanup-chars: NA fail: delete CommodityParseInfo*. result-fail: NA chars-fail: NA */ typedef struct { gchar* space; gchar* id; gchar* name; gchar* xcode; gboolean seen_fraction; int fraction; } CommodityParseInfo; static gboolean commodity_restore_start_handler (GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* data_for_children, gpointer* result, const gchar* tag, gchar** attrs) { CommodityParseInfo* cpi = (CommodityParseInfo*) g_new0 (CommodityParseInfo, 1); g_return_val_if_fail (cpi, FALSE); *data_for_children = cpi; return (TRUE); } /* ----------------------------------------------------*/ #define COMMOD_TOKEN(NAME) \ if(strcmp(child_result->tag, #NAME) == 0) { \ if(cpi->NAME) return(FALSE); \ cpi->NAME = (gchar *) child_result->data; \ child_result->should_cleanup = FALSE; \ } \ else /* ----------------------------------------------------*/ static gboolean commodity_restore_after_child_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag, const gchar* child_tag, sixtp_child_result* child_result) { CommodityParseInfo* cpi = (CommodityParseInfo*) data_for_children; g_return_val_if_fail (cpi, FALSE); g_return_val_if_fail (child_result, FALSE); COMMOD_TOKEN (space) COMMOD_TOKEN (id) COMMOD_TOKEN (name) COMMOD_TOKEN (xcode) if (strcmp (child_result->tag, "fraction") == 0) { gint64 frac; if (cpi->seen_fraction) return (FALSE); string_to_gint64 ((gchar*) child_result->data, &frac); cpi->fraction = frac; cpi->seen_fraction = TRUE; child_result->should_cleanup = TRUE; } else { /* redundant because the parser won't allow any other children */ return (FALSE); } return (TRUE); } static gboolean commodity_restore_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { CommodityParseInfo* cpi = (CommodityParseInfo*) data_for_children; GNCParseStatus* pstatus = (GNCParseStatus*) global_data; gboolean ok = FALSE; gnc_commodity* comm = NULL; g_return_val_if_fail (cpi, FALSE); if (cpi->seen_fraction) { gnc_commodity* comm; if (!cpi->space) cpi->space = g_strdup (""); if (!cpi->id) cpi->id = g_strdup (""); if (!cpi->name) cpi->name = g_strdup (""); if (!cpi->xcode) cpi->xcode = g_strdup (""); comm = gnc_commodity_new (pstatus->book, cpi->name, cpi->space, cpi->id, cpi->xcode, cpi->fraction); if (comm) { gnc_commodity_table* ctab; ctab = gnc_commodity_table_get_table (pstatus->book); if (ctab) { gnc_commodity_table_insert (ctab, comm); ok = TRUE; } } } g_free (cpi->space); g_free (cpi->id); g_free (cpi->name); g_free (cpi->xcode); g_free (cpi); if (!ok) gnc_commodity_destroy (comm); return (ok); } static sixtp* commodity_restore_parser_new (void) { sixtp* top_level; sixtp* restore_pr; top_level = sixtp_new (); g_return_val_if_fail (top_level, NULL); if (! (restore_pr = sixtp_set_any ( sixtp_new (), FALSE, SIXTP_START_HANDLER_ID, commodity_restore_start_handler, SIXTP_END_HANDLER_ID, commodity_restore_end_handler, SIXTP_FAIL_HANDLER_ID, generic_free_data_for_children, SIXTP_AFTER_CHILD_HANDLER_ID, commodity_restore_after_child_handler, SIXTP_NO_MORE_HANDLERS))) { sixtp_destroy (top_level); return (NULL); } sixtp_add_sub_parser (top_level, "restore", restore_pr); if (!sixtp_add_some_sub_parsers ( restore_pr, TRUE, "space", simple_chars_only_parser_new (NULL), "id", simple_chars_only_parser_new (NULL), "name", simple_chars_only_parser_new (NULL), "xcode", simple_chars_only_parser_new (NULL), "fraction", simple_chars_only_parser_new (NULL), NULL, NULL)) { return NULL; } return (top_level); } /****************************************************************************/ /* generic gnc_commodity lookup handler. A collection of node functions intended to parse a sub-node set that looks like this: NASDAQ ZXDDQ and produce a gnc_commodity* by looking up the unique combination of namespace and ID (mnemonic). The start handler for the top allocates a CommodityParseInfo* and passes it to the children. The block sets the namespace and the block sets the ID. The end handler performs the lookup. If all goes well, returns the gnc_commodity* as the result. */ /* Top level gnc_commodity lookup node: input: NA returns: gnc_commodity* start: Allocates CommodityParseInfo* for data_for_children. characters: none (whitespace only). end: lookup commodity and place into *result, free data_for_children. fail: g_free data_for_children (CommodityParseInfo and contents). cleanup-chars: NA chars-fail: NA cleanup-result: NA (we didn't create the gnc_commodity we're returning) result-fail: NA */ typedef struct { gchar* name_space; gchar* id; } CommodityLookupParseInfo; static gboolean generic_gnc_commodity_lookup_start_handler ( GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* data_for_children, gpointer* result, const gchar* tag, gchar** attrs) { CommodityLookupParseInfo* cpi = g_new0 (CommodityLookupParseInfo, 1); g_return_val_if_fail (cpi, FALSE); *data_for_children = cpi; return (TRUE); } static gboolean generic_gnc_commodity_lookup_after_child_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag, const gchar* child_tag, sixtp_child_result* child_result) { CommodityLookupParseInfo* cpi = (CommodityLookupParseInfo*) data_for_children; g_return_val_if_fail (cpi, FALSE); g_return_val_if_fail (child_result, FALSE); if (child_result->type != SIXTP_CHILD_RESULT_NODE) return (FALSE); if (strcmp (child_result->tag, "space") == 0) { if (cpi->name_space) return (FALSE); cpi->name_space = (gchar*) child_result->data; child_result->should_cleanup = FALSE; } else if (strcmp (child_result->tag, "id") == 0) { if (cpi->id) return (FALSE); cpi->id = (gchar*) child_result->data; child_result->should_cleanup = FALSE; } else { /* redundant because the parser won't allow any other children */ return (FALSE); } return (TRUE); } static gboolean generic_gnc_commodity_lookup_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { CommodityLookupParseInfo* cpi = (CommodityLookupParseInfo*) data_for_children; GNCParseStatus* pstatus = (GNCParseStatus*) global_data; gboolean ok = FALSE; g_return_val_if_fail (cpi, FALSE); if (cpi->name_space && cpi->id) { gnc_commodity_table* table; gnc_commodity* com; table = gnc_commodity_table_get_table (pstatus->book); com = gnc_commodity_table_lookup (table, cpi->name_space, cpi->id); if (com) { *result = com; ok = TRUE; } } g_free (cpi->name_space); g_free (cpi->id); g_free (cpi); return (ok); } static sixtp* generic_gnc_commodity_lookup_parser_new (void) { sixtp* top_level; if (! (top_level = sixtp_set_any ( sixtp_new (), FALSE, SIXTP_START_HANDLER_ID, generic_gnc_commodity_lookup_start_handler, SIXTP_CHARACTERS_HANDLER_ID, allow_and_ignore_only_whitespace, SIXTP_END_HANDLER_ID, generic_gnc_commodity_lookup_end_handler, SIXTP_FAIL_HANDLER_ID, generic_free_data_for_children, SIXTP_AFTER_CHILD_HANDLER_ID, generic_gnc_commodity_lookup_after_child_handler, SIXTP_NO_MORE_HANDLERS))) { return NULL; } if (!sixtp_add_some_sub_parsers ( top_level, TRUE, "space", simple_chars_only_parser_new (NULL), "id", simple_chars_only_parser_new (NULL), NULL, NULL)) { return NULL; } return (top_level); } /****************************************************************************/ /* (parent ) This block does nothing but pass the ledger-data account group down to its children. It generates no data of its own, so it doesn't need any cleanup. input: Account* to-children-via-*result: Account* returns: NA start: pass input to children. characters: ignore whitespace only end: NA cleanup-result: NA cleanup-chars: NA fail: NA result-fail: NA chars-fail: NA */ static gboolean transaction_start_handler (GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* data_for_children, gpointer* result, const gchar* tag, gchar** attrs) { /* pass the parent data down to the children */ *data_for_children = parent_data; return (TRUE); } /****************************************************************************/ /* (lineage ) restores a given transaction. We allocate the new transaction in the start block, the children modify it, and in the end block, we see if the resultant account is OK, and if so, we add it to the ledger-data's account group. from parent: Account* for children: new Transaction* result: NA ----------- start: create new Transaction*, and store in data_for_children. chars: allow and ignore only whitespace. end: commit transaction to group if appropriate. cleanup-result: NA cleanup-chars: NA fail: delete Transaction* in data_for_children result-fail: NA chars-fail: NA */ static gboolean txn_restore_start_handler (GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* data_for_children, gpointer* result, const gchar* tag, gchar** attrs) { GNCParseStatus* pstatus = (GNCParseStatus*) global_data; Transaction* trans = xaccMallocTransaction (pstatus->book); g_return_val_if_fail (trans, FALSE); xaccTransBeginEdit (trans); *data_for_children = trans; return (TRUE); } static gboolean txn_restore_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { Account* parent = (Account*) parent_data; Transaction* trans = (Transaction*) data_for_children; g_return_val_if_fail (trans, FALSE); if (!parent) { xaccTransDestroy (trans); xaccTransCommitEdit (trans); return (FALSE); } if (!xaccTransGetGUID (trans)) { /* must at least have a GncGUID for a restore */ xaccTransDestroy (trans); xaccTransCommitEdit (trans); return (FALSE); } /* FIXME: what if the trans has no splits? */ xaccTransCommitEdit (trans); return (TRUE); } static gboolean txn_restore_after_child_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag, const gchar* child_tag, sixtp_child_result* child_result) { Transaction* trans = (Transaction*) data_for_children; g_return_val_if_fail (trans, FALSE); if (!child_result) return (TRUE); if (child_result->type != SIXTP_CHILD_RESULT_NODE) return (TRUE); if (strcmp (child_result->tag, "slots") == 0) { KvpFrame* f = (KvpFrame*) child_result->data; g_return_val_if_fail (f, FALSE); qof_instance_set_slots (QOF_INSTANCE (trans), f); child_result->should_cleanup = FALSE; } return (TRUE); } static void txn_restore_fail_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { Transaction* trans = (Transaction*) data_for_children; if (trans) { xaccTransDestroy (trans); xaccTransCommitEdit (trans); } } /****************************************************************************/ /* (lineage ) restores a given account's guid. from parent: Transaction* for children: NA result: NA ----------- start: NA characters: return string copy for accumulation in end handler. end: concatenate all chars and set as transaction GncGUID if not duplicate. cleanup-result: NA cleanup-chars: g_free the result string. fail: NA result-fail: NA chars-fail: g_free the result string. */ static gboolean txn_restore_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) { GNCParseStatus* pstatus = (GNCParseStatus*) global_data; Transaction* t = (Transaction*) parent_data; gchar* txt = NULL; GncGUID gid; gboolean ok; g_return_val_if_fail (t, FALSE); txt = concatenate_child_result_chars (data_from_children); g_return_val_if_fail (txt, FALSE); ok = string_to_guid (txt, &gid); g_free (txt); g_return_val_if_fail (ok, FALSE); if (xaccTransLookup (&gid, pstatus->book)) { return (FALSE); } xaccTransSetGUID (t, &gid); return (TRUE); } /****************************************************************************/ /* (lineage ) restores a given transaction's num. from parent: Transaction* for children: NA result: NA ----------- start: NA characters: return string copy for accumulation in end handler. end: concatenate all chars and set as transaction num. cleanup-result: NA cleanup-chars: g_free the result string. fail: NA result-fail: NA chars-fail: g_free the result string. */ static gboolean txn_restore_num_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { Transaction* t = (Transaction*) parent_data; gchar* txt = NULL; g_return_val_if_fail (t, FALSE); txt = concatenate_child_result_chars (data_from_children); g_return_val_if_fail (txt, FALSE); xaccTransSetNum (t, txt); g_free (txt); return (TRUE); } /****************************************************************************/ /* (lineage ) restores a given transaction's description. from parent: Transaction* for children: NA result: NA ----------- start: NA characters: return string copy for accumulation in end handler. end: concatenate all chars and set as transaction description. cleanup-result: NA cleanup-chars: g_free the result string. fail: NA result-fail: NA chars-fail: g_free the result string. */ static gboolean txn_restore_description_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { Transaction* t = (Transaction*) parent_data; gchar* txt = NULL; g_return_val_if_fail (t, FALSE); txt = concatenate_child_result_chars (data_from_children); g_return_val_if_fail (txt, FALSE); xaccTransSetDescription (t, txt); g_free (txt); return (TRUE); } /****************************************************************************/ /* (lineage ) restores a given transaction's posted date. Just uses a generic_timespec parser, but with our own end handler. end: set date posted. */ static gboolean txn_rest_date_posted_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { Transaction* t = (Transaction*) parent_data; Time64ParseInfo* info = (Time64ParseInfo*) data_for_children; g_return_val_if_fail (info, FALSE); if (!t || !timespec_parse_ok (info)) { g_free (info); return (FALSE); } xaccTransSetDatePostedSecs (t, info->time); g_free (info); return (TRUE); } /****************************************************************************/ /* (lineage ) restores a given transaction's entered date. Just uses a generic_timespec parser, but with our own end handler. end: set date entered. */ static gboolean txn_rest_date_entered_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { Transaction* t = (Transaction*) parent_data; Time64ParseInfo* info = (Time64ParseInfo*) data_for_children; g_return_val_if_fail (info, FALSE); if (!t || !timespec_parse_ok (info)) { g_free (info); return (FALSE); } xaccTransSetDateEnteredSecs (t, info->time); g_free (info); return (TRUE); } /****************************************************************************/ /* (lineage ) Restores a given split. We allocate the new split in the start block, the children modify it, and in the end block, we see if the resultant split is OK, and if so, we add it to the input Transaction* account group. from parent: Transaction* for children: new Split* result: NA ----------- start: create new Split*, and store in data_for_children. chars: allow and ignore only whitespace. end: commit split to transaction if appropriate. cleanup-result: NA cleanup-chars: NA fail: delete Transaction* in data_for_children result-fail: NA chars-fail: NA */ static gboolean txn_restore_split_start_handler (GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* data_for_children, gpointer* result, const gchar* tag, gchar** attrs) { GNCParseStatus* pstatus = (GNCParseStatus*) global_data; Split* s = xaccMallocSplit (pstatus->book); g_return_val_if_fail (s, FALSE); *data_for_children = s; return (TRUE); } static gboolean txn_restore_split_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { Transaction* t = (Transaction*) parent_data; Split* s = (Split*) data_for_children; g_return_val_if_fail (s, FALSE); if (!t) { xaccSplitDestroy (s); return (FALSE); } if (!xaccSplitGetGUID (s)) { /* must at least have a GncGUID for a restore */ xaccSplitDestroy (s); return (FALSE); } xaccTransAppendSplit (t, s); return (TRUE); } static gboolean txn_restore_split_after_child_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag, const gchar* child_tag, sixtp_child_result* child_result) { Split* s = (Split*) data_for_children; g_return_val_if_fail (s, FALSE); if (!child_result) return (TRUE); if (child_result->type != SIXTP_CHILD_RESULT_NODE) return (TRUE); if (strcmp (child_result->tag, "slots") == 0) { KvpFrame* f = static_cast (child_result->data); g_return_val_if_fail (f, FALSE); if (s->inst.kvp_data) delete s->inst.kvp_data; s->inst.kvp_data = f; child_result->should_cleanup = FALSE; } else if (strcmp (child_result->tag, "quantity") == 0) { gnc_numeric* n = (gnc_numeric*) child_result->data; g_return_val_if_fail (n, FALSE); xaccSplitSetAmount (s, *n); /* let the normal child_result handler clean up n */ } else if (strcmp (child_result->tag, "value") == 0) { gnc_numeric* n = (gnc_numeric*) child_result->data; g_return_val_if_fail (n, FALSE); xaccSplitSetValue (s, *n); /* let the normal child_result handler clean up n */ } return (TRUE); } static void txn_restore_split_fail_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { Split* s = (Split*) data_for_children; if (s) xaccSplitDestroy (s); } /****************************************************************************/ /* (lineage ) restores a given split's guid. from parent: Split* for children: NA result: NA ----------- start: NA characters: return string copy for accumulation in end handler. end: concatenate all chars and set as split GncGUID if not duplicate. cleanup-result: NA cleanup-chars: g_free the result string. fail: NA result-fail: NA chars-fail: g_free the result string. */ static gboolean txn_restore_split_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) { GNCParseStatus* pstatus = (GNCParseStatus*) global_data; Split* s = (Split*) parent_data; gchar* txt = NULL; GncGUID gid; gboolean ok; g_return_val_if_fail (s, FALSE); txt = concatenate_child_result_chars (data_from_children); g_return_val_if_fail (txt, FALSE); ok = string_to_guid (txt, &gid); g_free (txt); g_return_val_if_fail (ok, FALSE); if (xaccSplitLookup (&gid, pstatus->book)) { return (FALSE); } xaccSplitSetGUID (s, &gid); return (TRUE); } /****************************************************************************/ /* (lineage ) restores a given split's memo. from parent: Split* for children: NA result: NA ----------- start: NA characters: return string copy for accumulation in end handler. end: concatenate all chars and set as split description. cleanup-result: NA cleanup-chars: g_free the result string. fail: NA result-fail: NA chars-fail: g_free the result string. */ static gboolean txn_restore_split_memo_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { Split* s = (Split*) parent_data; gchar* txt = NULL; g_return_val_if_fail (s, FALSE); txt = concatenate_child_result_chars (data_from_children); g_return_val_if_fail (txt, FALSE); xaccSplitSetMemo (s, txt); g_free (txt); return (TRUE); } /****************************************************************************/ /* (lineage ) restores a given split's action. from parent: Split* for children: NA result: NA ----------- start: NA characters: return string copy for accumulation in end handler. end: concatenate all chars and set as split action. cleanup-result: NA cleanup-chars: g_free the result string. fail: NA result-fail: NA chars-fail: g_free the result string. */ static gboolean txn_restore_split_action_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { Split* s = (Split*) parent_data; gchar* txt = NULL; g_return_val_if_fail (s, FALSE); txt = concatenate_child_result_chars (data_from_children); g_return_val_if_fail (txt, FALSE); xaccSplitSetAction (s, txt); g_free (txt); return (TRUE); } /****************************************************************************/ /* (lineage ) restores a given split's reconcile-state. from parent: Split* for children: NA result: NA ----------- start: NA characters: return string copy for accumulation in end handler. end: concatenate all chars and set as split reconcile-state. cleanup-result: NA cleanup-chars: g_free the result string. fail: NA result-fail: NA chars-fail: g_free the result string. */ static gboolean txn_restore_split_reconcile_state_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { Split* s = (Split*) parent_data; gchar* txt = NULL; g_return_val_if_fail (s, FALSE); txt = concatenate_child_result_chars (data_from_children); g_return_val_if_fail (txt, FALSE); if (strlen (txt) != 1) { g_free (txt); return (FALSE); } xaccSplitSetReconcile (s, txt[0]); g_free (txt); return (TRUE); } /****************************************************************************/ /* (lineage ) restores a given split's reconcile-date. Just uses a generic_timespec parser, but with our own end handler. end: set reconcile-date. */ static gboolean txn_restore_split_reconcile_date_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { Split* s = (Split*) parent_data; Time64ParseInfo* info = (Time64ParseInfo*) data_for_children; g_return_val_if_fail (info, FALSE); if (!s || !timespec_parse_ok (info)) { g_free (info); return (FALSE); } xaccSplitSetDateReconciledSecs (s, info->time); g_free (info); return (TRUE); } /****************************************************************************/ /* (lineage ) restores a given split's account. from parent: Split* for children: NA result: NA ----------- start: NA characters: return string copy for accumulation in end handler. end: concatenate all chars and set as split account if GncGUID OK. cleanup-result: NA cleanup-chars: g_free the result string. fail: NA result-fail: NA chars-fail: g_free the result string. */ static gboolean txn_restore_split_account_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { GNCParseStatus* pstatus = (GNCParseStatus*) global_data; Split* s = (Split*) parent_data; Account* acct; gchar* txt = NULL; GncGUID gid; gboolean ok; g_return_val_if_fail (s, FALSE); txt = concatenate_child_result_chars (data_from_children); g_return_val_if_fail (txt, FALSE); ok = string_to_guid (txt, &gid); g_free (txt); g_return_val_if_fail (ok, FALSE); acct = xaccAccountLookup (&gid, pstatus->book); g_return_val_if_fail (acct, FALSE); xaccAccountInsertSplit (acct, s); return (TRUE); } /****************************************************************************/ /****************************************************************************/ static sixtp* gnc_txn_restore_split_parser_new (void) { sixtp* top_level; if (! (top_level = sixtp_set_any (sixtp_new (), FALSE, SIXTP_START_HANDLER_ID, txn_restore_split_start_handler, SIXTP_CHARACTERS_HANDLER_ID, allow_and_ignore_only_whitespace, SIXTP_END_HANDLER_ID, txn_restore_split_end_handler, SIXTP_FAIL_HANDLER_ID, txn_restore_split_fail_handler, SIXTP_AFTER_CHILD_HANDLER_ID, txn_restore_split_after_child_handler, SIXTP_NO_MORE_HANDLERS))) { return NULL; } if (!sixtp_add_some_sub_parsers ( top_level, TRUE, "guid", restore_char_generator (txn_restore_split_guid_end_handler), "memo", restore_char_generator (txn_restore_split_memo_end_handler), "action", restore_char_generator (txn_restore_split_action_end_handler), "account", restore_char_generator (txn_restore_split_account_end_handler), "reconcile-state", restore_char_generator (txn_restore_split_reconcile_state_end_handler), "reconcile-date", generic_timespec_parser_new ( txn_restore_split_reconcile_date_end_handler), "quantity", generic_gnc_numeric_parser_new (), "value", generic_gnc_numeric_parser_new (), "slots", kvp_frame_parser_new (), NULL, NULL)) { return NULL; } return (top_level); } /***************************************************************************/ static sixtp* gnc_transaction_parser_new (void) { sixtp* top_level; sixtp* restore_pr; if (! (top_level = sixtp_set_any (sixtp_new (), FALSE, SIXTP_START_HANDLER_ID, transaction_start_handler, SIXTP_CHARACTERS_HANDLER_ID, allow_and_ignore_only_whitespace, SIXTP_AFTER_CHILD_HANDLER_ID, txn_restore_after_child_handler, SIXTP_NO_MORE_HANDLERS))) { return NULL; } /* */ if (! (restore_pr = sixtp_set_any (sixtp_new (), FALSE, SIXTP_START_HANDLER_ID, txn_restore_start_handler, SIXTP_END_HANDLER_ID, txn_restore_end_handler, SIXTP_FAIL_HANDLER_ID, txn_restore_fail_handler, SIXTP_AFTER_CHILD_HANDLER_ID, txn_restore_after_child_handler, SIXTP_NO_MORE_HANDLERS))) { sixtp_destroy (top_level); return (NULL); } sixtp_add_sub_parser (top_level, "restore", restore_pr); if (! (sixtp_add_some_sub_parsers ( restore_pr, TRUE, "guid", restore_char_generator (txn_restore_guid_end_handler), "num", restore_char_generator (txn_restore_num_end_handler), "description", restore_char_generator (txn_restore_description_end_handler), "date-posted", generic_timespec_parser_new (txn_rest_date_posted_end_handler), "date-entered", generic_timespec_parser_new (txn_rest_date_entered_end_handler), "slots", kvp_frame_parser_new (), "split", gnc_txn_restore_split_parser_new (), NULL, NULL))) { sixtp_destroy (top_level); return NULL; } return (top_level); } /****************************************************************************/ /****************************************************************************/ /* Read and Write the pricedb as XML -- something like this: price-1 price-2 ... where each price should look roughly like this: 00000000111111112222222233333333 NASDAQ RHAT ISO? USD Mon ...12 Finance::Quote bid 11011/100 */ /***********************************************************************/ /* READING */ /***********************************************************************/ /****************************************************************************/ /* restores a price. Does so via a walk of the XML tree in memory. Returns a GNCPrice * in result. Right now, a price is legitimate even if all of it's fields are not set. We may need to change that later, but at the moment. */ static gboolean price_parse_xml_sub_node (GNCPrice* p, xmlNodePtr sub_node, QofBook* book) { if (!p || !sub_node) return FALSE; gnc_price_begin_edit (p); if (g_strcmp0 ("price:id", (char*)sub_node->name) == 0) { GncGUID* c = dom_tree_to_guid (sub_node); if (!c) return FALSE; gnc_price_set_guid (p, c); g_free (c); } else if (g_strcmp0 ("price:commodity", (char*)sub_node->name) == 0) { gnc_commodity* c = dom_tree_to_commodity_ref (sub_node, book); if (!c) return FALSE; gnc_price_set_commodity (p, c); } else if (g_strcmp0 ("price:currency", (char*)sub_node->name) == 0) { gnc_commodity* c = dom_tree_to_commodity_ref (sub_node, book); if (!c) return FALSE; gnc_price_set_currency (p, c); } else if (g_strcmp0 ("price:time", (char*)sub_node->name) == 0) { time64 time = dom_tree_to_time64 (sub_node); if (!dom_tree_valid_time64 (time, sub_node->name)) time = 0; gnc_price_set_time64 (p, time); } else if (g_strcmp0 ("price:source", (char*)sub_node->name) == 0) { char* text = dom_tree_to_text (sub_node); if (!text) return FALSE; gnc_price_set_source_string (p, text); g_free (text); } else if (g_strcmp0 ("price:type", (char*)sub_node->name) == 0) { char* text = dom_tree_to_text (sub_node); if (!text) return FALSE; gnc_price_set_typestr (p, text); g_free (text); } else if (g_strcmp0 ("price:value", (char*)sub_node->name) == 0) { gnc_numeric* value = dom_tree_to_gnc_numeric (sub_node); if (!value) return FALSE; gnc_price_set_value (p, *value); g_free (value); } gnc_price_commit_edit (p); return TRUE; } static gboolean price_parse_xml_end_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag) { gboolean ok = TRUE; xmlNodePtr price_xml = (xmlNodePtr) data_for_children; xmlNodePtr child; GNCPrice* p = NULL; GNCParseStatus* pstatus = (GNCParseStatus*) global_data; /* we haven't been handed the *top* level node yet... */ if (parent_data) return TRUE; *result = NULL; if (!price_xml) return FALSE; if (price_xml->next) { ok = FALSE; goto cleanup_and_exit; } if (price_xml->prev) { ok = FALSE; goto cleanup_and_exit; } if (!price_xml->xmlChildrenNode) { ok = FALSE; goto cleanup_and_exit; } p = gnc_price_create (pstatus->book); if (!p) { ok = FALSE; goto cleanup_and_exit; } for (child = price_xml->xmlChildrenNode; child; child = child->next) { switch (child->type) { case XML_COMMENT_NODE: case XML_TEXT_NODE: break; case XML_ELEMENT_NODE: if (!price_parse_xml_sub_node (p, child, pstatus->book)) { ok = FALSE; goto cleanup_and_exit; } break; default: PERR ("Unknown node type (%d) while parsing gnc-price xml.", child->type); child = NULL; ok = FALSE; goto cleanup_and_exit; break; } } cleanup_and_exit: if (ok) { *result = p; } else { *result = NULL; gnc_price_unref (p); } xmlFreeNode (price_xml); return ok; } static void cleanup_gnc_price (sixtp_child_result* result) { if (result->data) gnc_price_unref ((GNCPrice*) result->data); } static sixtp* gnc_price_parser_new (void) { return sixtp_dom_parser_new (price_parse_xml_end_handler, cleanup_gnc_price, cleanup_gnc_price); } /****************************************************************************/ /* (lineage ) restores a pricedb. We allocate the new db in the start block, the children add to it, and it gets returned in result. Note that the cleanup handler will destroy the pricedb, so the parent needs to stop that if desired. result: GNCPriceDB* start: create new GNCPriceDB*, and leave in *data_for_children. cleanup-result: destroy GNCPriceDB* result-fail: destroy GNCPriceDB* */ static gboolean pricedb_start_handler (GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* data_for_children, gpointer* result, const gchar* tag, gchar** attrs) { GNCParseStatus* pstatus = (GNCParseStatus*) global_data; GNCPriceDB* db = gnc_pricedb_get_db (pstatus->book); g_return_val_if_fail (db, FALSE); *result = db; return (TRUE); } static gboolean pricedb_after_child_handler (gpointer data_for_children, GSList* data_from_children, GSList* sibling_data, gpointer parent_data, gpointer global_data, gpointer* result, const gchar* tag, const gchar* child_tag, sixtp_child_result* child_result) { GNCPriceDB* db = (GNCPriceDB*) * result; g_return_val_if_fail (db, FALSE); /* right now children have to produce results :> */ if (!child_result) return (FALSE); if (child_result->type != SIXTP_CHILD_RESULT_NODE) return (FALSE); if (strcmp (child_result->tag, "price") == 0) { GNCPrice* p = (GNCPrice*) child_result->data; g_return_val_if_fail (p, FALSE); gnc_pricedb_add_price (db, p); return TRUE; } else { return FALSE; } return FALSE; } static void pricedb_cleanup_result_handler (sixtp_child_result* result) { if (result->data) { GNCPriceDB* db = (GNCPriceDB*) result->data; if (db) gnc_pricedb_destroy (db); result->data = NULL; } } static sixtp* gnc_pricedb_parser_new (void) { sixtp* top_level; sixtp* price_parser; top_level = sixtp_set_any (sixtp_new (), TRUE, SIXTP_START_HANDLER_ID, pricedb_start_handler, SIXTP_AFTER_CHILD_HANDLER_ID, pricedb_after_child_handler, SIXTP_CHARACTERS_HANDLER_ID, allow_and_ignore_only_whitespace, SIXTP_RESULT_FAIL_ID, pricedb_cleanup_result_handler, SIXTP_CLEANUP_RESULT_ID, pricedb_cleanup_result_handler, SIXTP_NO_MORE_HANDLERS); if (!top_level) return NULL; price_parser = gnc_price_parser_new (); if (!price_parser) { sixtp_destroy (top_level); return NULL; } sixtp_add_sub_parser (top_level, "price", price_parser); return top_level; } /* ======================= END OF FILE ============================== */