|
GnuCash 2.4.99
|
00001 /********************************************************************\ 00002 * Copyright (C) 2000,2001 Gnumatic Inc. * 00003 * * 00004 * This program is free software; you can redistribute it and/or * 00005 * modify it under the terms of the GNU General Public License as * 00006 * published by the Free Software Foundation; either version 2 of * 00007 * the License, or (at your option) any later version. * 00008 * * 00009 * This program is distributed in the hope that it will be useful, * 00010 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 00011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 00012 * GNU General Public License for more details. * 00013 * * 00014 * You should have received a copy of the GNU General Public License* 00015 * along with this program; if not, contact: * 00016 * * 00017 * Free Software Foundation Voice: +1-617-542-5942 * 00018 * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 * 00019 * Boston, MA 02110-1301, USA gnu@gnu.org * 00020 \********************************************************************/ 00021 00022 #include "config.h" 00023 00024 #include <glib.h> 00025 #include <glib/gstdio.h> 00026 #include <fcntl.h> 00027 #include <string.h> 00028 #ifdef HAVE_UNISTD_H 00029 # include <unistd.h> 00030 #endif 00031 #include <zlib.h> 00032 #include <errno.h> 00033 00034 #include "gnc-engine.h" 00035 #include "gnc-pricedb-p.h" 00036 #include "Scrub.h" 00037 #include "SX-book.h" 00038 #include "SX-book-p.h" 00039 #include "Transaction.h" 00040 #include "TransactionP.h" 00041 #include "TransLog.h" 00042 #include "sixtp-dom-parsers.h" 00043 #include "io-gncxml-v2.h" 00044 #include "io-gncxml-gen.h" 00045 00046 #include "sixtp.h" 00047 #include "sixtp-parsers.h" 00048 #include "sixtp-utils.h" 00049 #include "gnc-xml.h" 00050 #include "io-utils.h" 00051 #ifdef G_OS_WIN32 00052 # include <io.h> 00053 # define close _close 00054 # define fdopen _fdopen 00055 # define read _read 00056 #endif 00057 #include "platform.h" 00058 #if COMPILER(MSVC) 00059 # define g_fopen fopen 00060 # define g_open _open 00061 #endif 00062 00063 /* Do not treat -Wstrict-aliasing warnings as errors because of problems of the 00064 * G_LOCK* macros as declared by glib. See 00065 * http://bugzilla.gnome.org/show_bug.cgi?id=316221 for additional information. 00066 */ 00067 #if (__GNUC__ >= 4 && __GNUC_MINOR__ >= 2) 00068 # pragma GCC diagnostic warning "-Wstrict-aliasing" 00069 #endif 00070 00071 static QofLogModule log_module = GNC_MOD_IO; 00072 00073 /* map pointers, e.g. of type FILE*, to GThreads */ 00074 static GHashTable *threads = NULL; 00075 G_LOCK_DEFINE_STATIC(threads); 00076 00077 typedef struct 00078 { 00079 gint fd; 00080 gchar *filename; 00081 gchar *perms; 00082 gboolean compress; 00083 } gz_thread_params_t; 00084 00085 /* Callback structure */ 00086 struct file_backend 00087 { 00088 gboolean ok; 00089 gpointer data; 00090 sixtp_gdv2 * gd; 00091 const char * tag; 00092 sixtp * parser; 00093 FILE * out; 00094 QofBook * book; 00095 }; 00096 00097 #define GNC_V2_STRING "gnc-v2" 00098 /* non-static because they are used in sixtp.c */ 00099 const gchar *gnc_v2_xml_version_string = GNC_V2_STRING; 00100 extern const gchar *gnc_v2_book_version_string; /* see gnc-book-xml-v2 */ 00101 00102 void 00103 run_callback(sixtp_gdv2 *data, const char *type) 00104 { 00105 if (data->countCallback) 00106 { 00107 data->countCallback(data, type); 00108 } 00109 } 00110 00111 static void 00112 clear_up_account_commodity( 00113 gnc_commodity_table *tbl, Account *act, 00114 gnc_commodity * (*getter) (const Account *account), 00115 void (*setter) (Account *account, gnc_commodity *comm), 00116 int (*scu_getter) (const Account *account), 00117 void (*scu_setter) (Account *account, int scu)) 00118 { 00119 gnc_commodity *gcom; 00120 gnc_commodity *com = getter(act); 00121 int old_scu; 00122 00123 if (scu_getter) 00124 old_scu = scu_getter(act); 00125 else 00126 old_scu = 0; 00127 00128 if (!com) 00129 { 00130 return; 00131 } 00132 00133 gcom = gnc_commodity_table_lookup(tbl, gnc_commodity_get_namespace(com), 00134 gnc_commodity_get_mnemonic(com)); 00135 00136 if (gcom == com) 00137 { 00138 return; 00139 } 00140 else if (!gcom) 00141 { 00142 PWARN("unable to find global commodity for %s adding new", 00143 gnc_commodity_get_unique_name(com)); 00144 gnc_commodity_table_insert(tbl, com); 00145 } 00146 else 00147 { 00148 setter(act, gcom); 00149 if (old_scu != 0 && scu_setter) 00150 scu_setter(act, old_scu); 00151 gnc_commodity_destroy(com); 00152 } 00153 } 00154 00155 static void 00156 clear_up_transaction_commodity( 00157 gnc_commodity_table *tbl, Transaction *trans, 00158 gnc_commodity * (*getter) (const Transaction *trans), 00159 void (*setter) (Transaction *trans, gnc_commodity *comm)) 00160 { 00161 gnc_commodity *gcom; 00162 gnc_commodity *com = getter(trans); 00163 00164 if (!com) 00165 { 00166 return; 00167 } 00168 00169 gcom = gnc_commodity_table_lookup(tbl, gnc_commodity_get_namespace(com), 00170 gnc_commodity_get_mnemonic(com)); 00171 00172 if (gcom == com) 00173 { 00174 return; 00175 } 00176 else if (!gcom) 00177 { 00178 PWARN("unable to find global commodity for %s adding new", 00179 gnc_commodity_get_unique_name(com)); 00180 gnc_commodity_table_insert(tbl, com); 00181 } 00182 else 00183 { 00184 xaccTransBeginEdit(trans); 00185 setter(trans, gcom); 00186 xaccTransCommitEdit(trans); 00187 gnc_commodity_destroy(com); 00188 } 00189 } 00190 00191 static gboolean 00192 add_account_local(sixtp_gdv2 *data, Account *act) 00193 { 00194 gnc_commodity_table *table; 00195 Account *parent, *root; 00196 int type; 00197 00198 table = gnc_commodity_table_get_table (data->book); 00199 00200 clear_up_account_commodity(table, act, 00201 DxaccAccountGetCurrency, 00202 DxaccAccountSetCurrency, 00203 NULL, NULL); 00204 00205 clear_up_account_commodity(table, act, 00206 xaccAccountGetCommodity, 00207 xaccAccountSetCommodity, 00208 xaccAccountGetCommoditySCUi, 00209 xaccAccountSetCommoditySCU); 00210 00211 xaccAccountScrubCommodity (act); 00212 xaccAccountScrubKvp (act); 00213 00214 /* Backwards compatability. If there's no parent, see if this 00215 * account is of type ROOT. If not, find or create a ROOT 00216 * account and make that the parent. */ 00217 type = xaccAccountGetType(act); 00218 if (type == ACCT_TYPE_ROOT) 00219 { 00220 gnc_book_set_root_account(data->book, act); 00221 } 00222 else 00223 { 00224 parent = gnc_account_get_parent(act); 00225 if (parent == NULL) 00226 { 00227 root = gnc_book_get_root_account(data->book); 00228 gnc_account_append_child(root, act); 00229 } 00230 } 00231 00232 data->counter.accounts_loaded++; 00233 run_callback(data, "account"); 00234 00235 return FALSE; 00236 } 00237 00238 static gboolean 00239 add_book_local(sixtp_gdv2 *data, QofBook *book) 00240 { 00241 data->counter.books_loaded++; 00242 run_callback(data, "book"); 00243 00244 return FALSE; 00245 } 00246 00247 static gboolean 00248 add_commodity_local(sixtp_gdv2 *data, gnc_commodity *com) 00249 { 00250 gnc_commodity_table *table; 00251 00252 table = gnc_commodity_table_get_table (data->book); 00253 00254 gnc_commodity_table_insert(table, com); 00255 00256 data->counter.commodities_loaded++; 00257 run_callback(data, "commodities"); 00258 00259 return TRUE; 00260 } 00261 00262 static gboolean 00263 add_transaction_local(sixtp_gdv2 *data, Transaction *trn) 00264 { 00265 gnc_commodity_table *table; 00266 00267 table = gnc_commodity_table_get_table (data->book); 00268 00269 xaccTransBeginEdit (trn); 00270 clear_up_transaction_commodity(table, trn, 00271 xaccTransGetCurrency, 00272 xaccTransSetCurrency); 00273 00274 xaccTransScrubCurrency (trn); 00275 xaccTransCommitEdit (trn); 00276 00277 data->counter.transactions_loaded++; 00278 run_callback(data, "transaction"); 00279 return TRUE; 00280 } 00281 00282 static gboolean 00283 add_schedXaction_local(sixtp_gdv2 *data, SchedXaction *sx) 00284 { 00285 SchedXactions *sxes; 00286 sxes = gnc_book_get_schedxactions(data->book); 00287 gnc_sxes_add_sx(sxes, sx); 00288 data->counter.schedXactions_loaded++; 00289 run_callback(data, "schedXactions"); 00290 return TRUE; 00291 } 00292 00293 static gboolean 00294 add_template_transaction_local( sixtp_gdv2 *data, 00295 gnc_template_xaction_data *txd ) 00296 { 00297 GList *n; 00298 Account *acctRoot = NULL; 00299 QofBook *book; 00300 00301 book = data->book; 00302 00303 /* expect a struct of: */ 00304 /* . template accounts. */ 00305 /* . transactions in those accounts. */ 00306 for ( n = txd->accts; n; n = n->next ) 00307 { 00308 if ( gnc_account_get_parent( (Account*)n->data ) == NULL ) 00309 { 00310 if ( xaccAccountGetType( (Account*)n->data ) == ACCT_TYPE_ROOT ) 00311 { 00312 /* replace the gnc_book_init-created root account */ 00313 gnc_book_set_template_root(book, (Account *)n->data); 00314 } 00315 else 00316 { 00317 /* This is an old data file that doesn't have a template root 00318 account and this is a top level account. Make it a child 00319 of the template root account. */ 00320 acctRoot = gnc_book_get_template_root(book); 00321 gnc_account_append_child( acctRoot, (Account*)n->data ); 00322 } 00323 } 00324 00325 } 00326 00327 for ( n = txd->transactions; n; n = n->next ) 00328 { 00329 /* insert transactions into accounts */ 00330 add_transaction_local( data, (Transaction*)n->data ); 00331 } 00332 00333 return TRUE; 00334 } 00335 00336 static gboolean 00337 add_pricedb_local(sixtp_gdv2 *data, GNCPriceDB *db) 00338 { 00339 /* gnc_pricedb_print_contents(db, stdout); */ 00340 return TRUE; 00341 } 00342 00343 static void 00344 do_counter_cb (const char *type, gpointer data_p, gpointer be_data_p) 00345 { 00346 GncXmlDataType_t *data = data_p; 00347 struct file_backend *be_data = be_data_p; 00348 00349 g_return_if_fail (type && data && be_data); 00350 g_return_if_fail (data->version == GNC_FILE_BACKEND_VERS); 00351 00352 if (be_data->ok == TRUE) 00353 return; 00354 00355 if (!safe_strcmp (be_data->tag, data->type_name)) 00356 be_data->ok = TRUE; 00357 00358 /* XXX: should we do anything with this counter? */ 00359 } 00360 00361 static gboolean 00362 gnc_counter_end_handler(gpointer data_for_children, 00363 GSList* data_from_children, GSList* sibling_data, 00364 gpointer parent_data, gpointer global_data, 00365 gpointer *result, const gchar *tag) 00366 { 00367 char *strval; 00368 gint64 val; 00369 char *type; 00370 xmlNodePtr tree = (xmlNodePtr)data_for_children; 00371 gxpf_data *gdata = (gxpf_data*)global_data; 00372 sixtp_gdv2 *sixdata = (sixtp_gdv2*)gdata->parsedata; 00373 gboolean ret = TRUE; 00374 00375 if (parent_data) 00376 return TRUE; 00377 00378 /* OK. For some messed up reason this is getting called again with a 00379 NULL tag. So we ignore those cases */ 00380 if (!tag) 00381 return TRUE; 00382 00383 g_return_val_if_fail(tree, FALSE); 00384 00385 /* Note: BADXML. 00386 * 00387 * This is invalid xml because the namespace isn't declared in the 00388 * tag itself. This should be changed to 'type' at some point. */ 00389 type = (char*)xmlGetProp(tree, BAD_CAST "cd:type"); 00390 strval = dom_tree_to_text(tree); 00391 if (!string_to_gint64(strval, &val)) 00392 { 00393 PERR ("string_to_gint64 failed with input: %s", 00394 strval ? strval : "(null)"); 00395 ret = FALSE; 00396 } 00397 else if (safe_strcmp(type, "transaction") == 0) 00398 { 00399 sixdata->counter.transactions_total = val; 00400 } 00401 else if (safe_strcmp(type, "account") == 0) 00402 { 00403 sixdata->counter.accounts_total = val; 00404 } 00405 else if (safe_strcmp(type, "book") == 0) 00406 { 00407 sixdata->counter.books_total = val; 00408 } 00409 else if (safe_strcmp(type, "commodity") == 0) 00410 { 00411 sixdata->counter.commodities_total = val; 00412 } 00413 else if (safe_strcmp(type, "schedxaction") == 0) 00414 { 00415 sixdata->counter.schedXactions_total = val; 00416 } 00417 else if (safe_strcmp(type, "budget") == 0) 00418 { 00419 sixdata->counter.budgets_total = val; 00420 } 00421 else 00422 { 00423 struct file_backend be_data; 00424 00425 be_data.ok = FALSE; 00426 be_data.tag = type; 00427 00428 qof_object_foreach_backend (GNC_FILE_BACKEND, do_counter_cb, &be_data); 00429 00430 if (be_data.ok == FALSE) 00431 { 00432 PERR("Unknown type: %s", type ? type : "(null)"); 00433 /* Do *NOT* flag this as an error. Gnucash 1.8 writes invalid 00434 * xml by writing the 'cd:type' attribute without providing 00435 * the namespace in the gnc:count-data tag. The parser is 00436 * entirely within its rights to refuse to read this bad 00437 * attribute. Gnucash will function correctly without the data 00438 * in this tag, so just let the error pass. */ 00439 ret = TRUE; 00440 } 00441 } 00442 00443 g_free (strval); 00444 xmlFree (type); 00445 xmlFreeNode(tree); 00446 return ret; 00447 } 00448 00449 static sixtp* 00450 gnc_counter_sixtp_parser_create(void) 00451 { 00452 return sixtp_dom_parser_new(gnc_counter_end_handler, NULL, NULL); 00453 } 00454 00455 static void 00456 debug_print_counter_data(load_counter *data) 00457 { 00458 DEBUG("Transactions: Total: %d, Loaded: %d", 00459 data->transactions_total, data->transactions_loaded); 00460 DEBUG("Accounts: Total: %d, Loaded: %d", 00461 data->accounts_total, data->accounts_loaded); 00462 DEBUG("Books: Total: %d, Loaded: %d", 00463 data->books_total, data->books_loaded); 00464 DEBUG("Commodities: Total: %d, Loaded: %d", 00465 data->commodities_total, data->commodities_loaded); 00466 DEBUG("Scheduled Transactions: Total: %d, Loaded: %d", 00467 data->schedXactions_total, data->schedXactions_loaded); 00468 DEBUG("Budgets: Total: %d, Loaded: %d", 00469 data->budgets_total, data->budgets_loaded); 00470 } 00471 00472 static void 00473 file_rw_feedback (sixtp_gdv2 *gd, const char *type) 00474 { 00475 load_counter *counter; 00476 int loaded, total, percentage; 00477 00478 g_assert(gd != NULL); 00479 if (!gd->gui_display_fn) 00480 return; 00481 00482 counter = &gd->counter; 00483 loaded = counter->transactions_loaded + counter->accounts_loaded + 00484 counter->books_loaded + counter->commodities_loaded + 00485 counter->schedXactions_loaded + counter->budgets_loaded; 00486 total = counter->transactions_total + counter->accounts_total + 00487 counter->books_total + counter->commodities_total + 00488 counter->schedXactions_total + counter->budgets_total; 00489 if (total == 0) 00490 total = 1; 00491 00492 percentage = (loaded * 100) / total; 00493 if (percentage > 100) 00494 { 00495 /* FIXME: Perhaps the below should be replaced by: 00496 print_counter_data(counter); */ 00497 // printf("Transactions: Total: %d, Loaded: %d\n", 00498 // counter->transactions_total, counter->transactions_loaded); 00499 // printf("Accounts: Total: %d, Loaded: %d\n", 00500 // counter->accounts_total, counter->accounts_loaded); 00501 // printf("Books: Total: %d, Loaded: %d\n", 00502 // counter->books_total, counter->books_loaded); 00503 // printf("Commodities: Total: %d, Loaded: %d\n", 00504 // counter->commodities_total, counter->commodities_loaded); 00505 // printf("Scheduled Transactions: Total: %d, Loaded: %d\n", 00506 // counter->schedXactions_total, counter->schedXactions_loaded); 00507 // printf("Budgets: Total: %d, Loaded: %d\n", 00508 // counter->budgets_total, counter->budgets_loaded); 00509 } 00510 gd->gui_display_fn(NULL, percentage); 00511 } 00512 00513 static const char *BOOK_TAG = "gnc:book"; 00514 static const char *BOOK_ID_TAG = "book:id"; 00515 static const char *BOOK_SLOTS_TAG = "book:slots"; 00516 static const char *ACCOUNT_TAG = "gnc:account"; 00517 static const char *PRICEDB_TAG = "gnc:pricedb"; 00518 static const char *COMMODITY_TAG = "gnc:commodity"; 00519 static const char *COUNT_DATA_TAG = "gnc:count-data"; 00520 static const char *TRANSACTION_TAG = "gnc:transaction"; 00521 static const char *SCHEDXACTION_TAG = "gnc:schedxaction"; 00522 static const char *TEMPLATE_TRANSACTION_TAG = "gnc:template-transactions"; 00523 static const char *BUDGET_TAG = "gnc:budget"; 00524 00525 static void 00526 add_item_cb (const char *type, gpointer data_p, gpointer be_data_p) 00527 { 00528 GncXmlDataType_t *data = data_p; 00529 struct file_backend *be_data = be_data_p; 00530 00531 g_return_if_fail (type && data && be_data); 00532 g_return_if_fail (data->version == GNC_FILE_BACKEND_VERS); 00533 00534 if (be_data->ok) 00535 return; 00536 00537 if (!safe_strcmp (be_data->tag, data->type_name)) 00538 { 00539 if (data->add_item) 00540 (data->add_item)(be_data->gd, be_data->data); 00541 00542 be_data->ok = TRUE; 00543 } 00544 } 00545 00546 static gboolean 00547 book_callback(const char *tag, gpointer globaldata, gpointer data) 00548 { 00549 sixtp_gdv2 *gd = (sixtp_gdv2*)globaldata; 00550 00551 if (safe_strcmp(tag, ACCOUNT_TAG) == 0) 00552 { 00553 add_account_local(gd, (Account*)data); 00554 } 00555 else if (safe_strcmp(tag, PRICEDB_TAG) == 0) 00556 { 00557 add_pricedb_local(gd, (GNCPriceDB*)data); 00558 } 00559 else if (safe_strcmp(tag, COMMODITY_TAG) == 0) 00560 { 00561 add_commodity_local(gd, (gnc_commodity*)data); 00562 } 00563 else if (safe_strcmp(tag, TRANSACTION_TAG) == 0) 00564 { 00565 add_transaction_local(gd, (Transaction*)data); 00566 } 00567 else if (safe_strcmp(tag, SCHEDXACTION_TAG) == 0) 00568 { 00569 add_schedXaction_local(gd, (SchedXaction*)data); 00570 } 00571 else if (safe_strcmp(tag, TEMPLATE_TRANSACTION_TAG) == 0) 00572 { 00573 add_template_transaction_local( gd, (gnc_template_xaction_data*)data ); 00574 } 00575 else if (safe_strcmp(tag, BUDGET_TAG) == 0) 00576 { 00577 // Nothing needed here. 00578 } 00579 else 00580 { 00581 struct file_backend be_data; 00582 00583 be_data.ok = FALSE; 00584 be_data.tag = tag; 00585 be_data.gd = gd; 00586 be_data.data = data; 00587 00588 qof_object_foreach_backend (GNC_FILE_BACKEND, add_item_cb, &be_data); 00589 00590 if (be_data.ok == FALSE) 00591 { 00592 PWARN ("unexpected tag %s", tag); 00593 } 00594 } 00595 return TRUE; 00596 } 00597 00598 static gboolean 00599 generic_callback(const char *tag, gpointer globaldata, gpointer data) 00600 { 00601 sixtp_gdv2 *gd = (sixtp_gdv2*)globaldata; 00602 00603 if (safe_strcmp(tag, BOOK_TAG) == 0) 00604 { 00605 add_book_local(gd, (QofBook*)data); 00606 book_callback(tag, globaldata, data); 00607 } 00608 else 00609 { 00610 // PWARN ("importing pre-book-style XML data file"); 00611 book_callback(tag, globaldata, data); 00612 } 00613 return TRUE; 00614 } 00615 00616 static void 00617 add_parser_cb (const char *type, gpointer data_p, gpointer be_data_p) 00618 { 00619 GncXmlDataType_t *data = data_p; 00620 struct file_backend *be_data = be_data_p; 00621 00622 g_return_if_fail (type && data && be_data); 00623 g_return_if_fail (data->version == GNC_FILE_BACKEND_VERS); 00624 00625 if (be_data->ok == FALSE) 00626 return; 00627 00628 if (data->create_parser) 00629 if (!sixtp_add_some_sub_parsers( 00630 be_data->parser, TRUE, 00631 data->type_name, (data->create_parser)(), 00632 NULL, NULL)) 00633 be_data->ok = FALSE; 00634 } 00635 00636 static void 00637 scrub_cb (const char *type, gpointer data_p, gpointer be_data_p) 00638 { 00639 GncXmlDataType_t *data = data_p; 00640 struct file_backend *be_data = be_data_p; 00641 00642 g_return_if_fail (type && data && be_data); 00643 g_return_if_fail (data->version == GNC_FILE_BACKEND_VERS); 00644 00645 if (data->scrub) 00646 (data->scrub)(be_data->book); 00647 } 00648 00649 static sixtp_gdv2 * 00650 gnc_sixtp_gdv2_new ( 00651 QofBook *book, 00652 gboolean exporting, 00653 countCallbackFn countcallback, 00654 QofBePercentageFunc gui_display_fn) 00655 { 00656 sixtp_gdv2 *gd = g_new0(sixtp_gdv2, 1); 00657 00658 if (gd == NULL) return NULL; 00659 00660 gd->book = book; 00661 gd->counter.accounts_loaded = 0; 00662 gd->counter.accounts_total = 0; 00663 gd->counter.books_loaded = 0; 00664 gd->counter.books_total = 0; 00665 gd->counter.commodities_loaded = 0; 00666 gd->counter.commodities_total = 0; 00667 gd->counter.transactions_loaded = 0; 00668 gd->counter.transactions_total = 0; 00669 gd->counter.prices_loaded = 0; 00670 gd->counter.prices_total = 0; 00671 gd->counter.schedXactions_loaded = 0; 00672 gd->counter.schedXactions_total = 0; 00673 gd->counter.budgets_loaded = 0; 00674 gd->counter.budgets_total = 0; 00675 gd->exporting = exporting; 00676 gd->countCallback = countcallback; 00677 gd->gui_display_fn = gui_display_fn; 00678 return gd; 00679 } 00680 00681 static gboolean 00682 qof_session_load_from_xml_file_v2_full( 00683 FileBackend *fbe, QofBook *book, 00684 sixtp_push_handler push_handler, gpointer push_user_data, 00685 QofBookFileType type) 00686 { 00687 Account *root; 00688 QofBackend *be = &fbe->be; 00689 sixtp_gdv2 *gd; 00690 sixtp *top_parser; 00691 sixtp *main_parser; 00692 sixtp *book_parser; 00693 struct file_backend be_data; 00694 gboolean retval; 00695 char *v2type = NULL; 00696 00697 gd = gnc_sixtp_gdv2_new(book, FALSE, file_rw_feedback, be->percentage); 00698 00699 top_parser = sixtp_new(); 00700 main_parser = sixtp_new(); 00701 book_parser = sixtp_new(); 00702 00703 if (type == GNC_BOOK_XML2_FILE) 00704 v2type = g_strdup(GNC_V2_STRING); 00705 00706 if (!sixtp_add_some_sub_parsers( 00707 top_parser, TRUE, 00708 v2type, main_parser, 00709 NULL, NULL)) 00710 { 00711 g_free(v2type); 00712 goto bail; 00713 } 00714 00715 g_free(v2type); 00716 00717 if (!sixtp_add_some_sub_parsers( 00718 main_parser, TRUE, 00719 COUNT_DATA_TAG, gnc_counter_sixtp_parser_create(), 00720 BOOK_TAG, book_parser, 00721 00722 /* the following are present here only to support 00723 * the older, pre-book format. Basically, the top-level 00724 * book is implicit. */ 00725 PRICEDB_TAG, gnc_pricedb_sixtp_parser_create(), 00726 COMMODITY_TAG, gnc_commodity_sixtp_parser_create(), 00727 ACCOUNT_TAG, gnc_account_sixtp_parser_create(), 00728 TRANSACTION_TAG, gnc_transaction_sixtp_parser_create(), 00729 SCHEDXACTION_TAG, gnc_schedXaction_sixtp_parser_create(), 00730 TEMPLATE_TRANSACTION_TAG, gnc_template_transaction_sixtp_parser_create(), 00731 NULL, NULL)) 00732 { 00733 goto bail; 00734 } 00735 00736 if (!sixtp_add_some_sub_parsers( 00737 book_parser, TRUE, 00738 BOOK_ID_TAG, gnc_book_id_sixtp_parser_create(), 00739 BOOK_SLOTS_TAG, gnc_book_slots_sixtp_parser_create(), 00740 COUNT_DATA_TAG, gnc_counter_sixtp_parser_create(), 00741 PRICEDB_TAG, gnc_pricedb_sixtp_parser_create(), 00742 COMMODITY_TAG, gnc_commodity_sixtp_parser_create(), 00743 ACCOUNT_TAG, gnc_account_sixtp_parser_create(), 00744 BUDGET_TAG, gnc_budget_sixtp_parser_create(), 00745 TRANSACTION_TAG, gnc_transaction_sixtp_parser_create(), 00746 SCHEDXACTION_TAG, gnc_schedXaction_sixtp_parser_create(), 00747 TEMPLATE_TRANSACTION_TAG, gnc_template_transaction_sixtp_parser_create(), 00748 NULL, NULL)) 00749 { 00750 goto bail; 00751 } 00752 00753 be_data.ok = TRUE; 00754 be_data.parser = book_parser; 00755 qof_object_foreach_backend (GNC_FILE_BACKEND, add_parser_cb, &be_data); 00756 if (be_data.ok == FALSE) 00757 goto bail; 00758 00759 /* stop logging while we load */ 00760 xaccLogDisable (); 00761 xaccDisableDataScrubbing(); 00762 00763 if (push_handler) 00764 { 00765 gpointer parse_result = NULL; 00766 gxpf_data gpdata; 00767 00768 gpdata.cb = generic_callback; 00769 gpdata.parsedata = gd; 00770 gpdata.bookdata = book; 00771 00772 retval = sixtp_parse_push(top_parser, push_handler, push_user_data, 00773 NULL, &gpdata, &parse_result); 00774 } 00775 else 00776 { 00777 retval = gnc_xml_parse_file(top_parser, fbe->fullpath, 00778 generic_callback, gd, book); 00779 } 00780 00781 if (!retval) 00782 { 00783 sixtp_destroy(top_parser); 00784 xaccLogEnable (); 00785 xaccEnableDataScrubbing(); 00786 goto bail; 00787 } 00788 debug_print_counter_data(&gd->counter); 00789 00790 /* destroy the parser */ 00791 sixtp_destroy (top_parser); 00792 g_free(gd); 00793 00794 xaccEnableDataScrubbing(); 00795 00796 /* Mark the session as saved */ 00797 qof_book_mark_session_saved (book); 00798 00799 /* Call individual scrub functions */ 00800 memset(&be_data, 0, sizeof(be_data)); 00801 be_data.book = book; 00802 qof_object_foreach_backend (GNC_FILE_BACKEND, scrub_cb, &be_data); 00803 00804 /* fix price quote sources */ 00805 root = gnc_book_get_root_account(book); 00806 xaccAccountTreeScrubQuoteSources (root, gnc_commodity_table_get_table(book)); 00807 00808 /* Fix account and transaction commodities */ 00809 xaccAccountTreeScrubCommodities (root); 00810 00811 /* Fix split amount/value */ 00812 xaccAccountTreeScrubSplits (root); 00813 00814 /* commit all groups, this completes the BeginEdit started when the 00815 * account_end_handler finished reading the account. 00816 */ 00817 gnc_account_foreach_descendant(root, 00818 (AccountCb) xaccAccountCommitEdit, 00819 NULL); 00820 00821 /* start logging again */ 00822 xaccLogEnable (); 00823 00824 return TRUE; 00825 00826 bail: 00827 g_free(gd); 00828 return FALSE; 00829 } 00830 00831 gboolean 00832 qof_session_load_from_xml_file_v2(FileBackend *fbe, QofBook *book, 00833 QofBookFileType type) 00834 { 00835 return qof_session_load_from_xml_file_v2_full(fbe, book, NULL, NULL, type); 00836 } 00837 00838 /***********************************************************************/ 00839 00840 static gboolean 00841 write_counts(FILE* out, ...) 00842 { 00843 va_list ap; 00844 char *type; 00845 gboolean success = TRUE; 00846 00847 va_start(ap, out); 00848 type = va_arg(ap, char *); 00849 00850 while (success && type) 00851 { 00852 int amount = va_arg(ap, int); 00853 00854 if (amount != 0) 00855 { 00856 #if GNUCASH_REALLY_BUILD_AN_XML_TREE_ON_OUTPUT 00857 char *val; 00858 xmlNodePtr node; 00859 00860 val = g_strdup_printf("%d", amount); 00861 00862 node = xmlNewNode(NULL, BAD_CAST COUNT_DATA_TAG); 00863 /* Note: BADXML. 00864 * 00865 * This is invalid xml because the namespace isn't 00866 * declared in the tag itself. This should be changed to 00867 * 'type' at some point. */ 00868 xmlSetProp(node, BAD_CAST "cd:type", BAD_CAST type); 00869 xmlNodeAddContent(node, BAD_CAST val); 00870 g_free(val); 00871 00872 xmlElemDump(out, NULL, node); 00873 xmlFreeNode(node); 00874 00875 if (ferror(out) || fprintf(out, "\n") < 0) 00876 { 00877 success = FALSE; 00878 break; 00879 } 00880 #else 00881 if (fprintf(out, "<%s %s=\"%s\">%d</%s>\n", 00882 COUNT_DATA_TAG, "cd:type", type, amount, COUNT_DATA_TAG) < 0) 00883 { 00884 success = FALSE; 00885 break; 00886 } 00887 #endif 00888 00889 } 00890 00891 type = va_arg(ap, char *); 00892 } 00893 00894 va_end(ap); 00895 return success; 00896 } 00897 00898 static gint 00899 compare_namespaces(gconstpointer a, gconstpointer b) 00900 { 00901 const gchar *sa = (const gchar *) a; 00902 const gchar *sb = (const gchar *) b; 00903 return(safe_strcmp(sa, sb)); 00904 } 00905 00906 static gint 00907 compare_commodity_ids(gconstpointer a, gconstpointer b) 00908 { 00909 const gnc_commodity *ca = (const gnc_commodity *) a; 00910 const gnc_commodity *cb = (const gnc_commodity *) b; 00911 return(safe_strcmp(gnc_commodity_get_mnemonic(ca), 00912 gnc_commodity_get_mnemonic(cb))); 00913 } 00914 00915 static gboolean write_pricedb (FILE *out, QofBook *book, sixtp_gdv2 *gd); 00916 static gboolean write_transactions (FILE *out, QofBook *book, sixtp_gdv2 *gd); 00917 static gboolean write_template_transaction_data (FILE *out, QofBook *book, sixtp_gdv2 *gd); 00918 static gboolean write_schedXactions(FILE *out, QofBook *book, sixtp_gdv2 *gd); 00919 static void write_budget (QofInstance *ent, gpointer data); 00920 00921 static void 00922 write_counts_cb (const char *type, gpointer data_p, gpointer be_data_p) 00923 { 00924 GncXmlDataType_t *data = data_p; 00925 struct file_backend *be_data = be_data_p; 00926 00927 g_return_if_fail (type && data && be_data); 00928 g_return_if_fail (data->version == GNC_FILE_BACKEND_VERS); 00929 00930 if (data->get_count) 00931 write_counts (be_data->out, data->type_name, 00932 (data->get_count) (be_data->book), 00933 NULL); 00934 } 00935 00936 static void 00937 write_data_cb (const char *type, gpointer data_p, gpointer be_data_p) 00938 { 00939 GncXmlDataType_t *data = data_p; 00940 struct file_backend *be_data = be_data_p; 00941 00942 g_return_if_fail (type && data && be_data); 00943 g_return_if_fail (data->version == GNC_FILE_BACKEND_VERS); 00944 00945 if (data->write && !ferror(be_data->out)) 00946 (data->write)(be_data->out, be_data->book); 00947 } 00948 00949 static gboolean 00950 write_book(FILE *out, QofBook *book, sixtp_gdv2 *gd) 00951 { 00952 struct file_backend be_data; 00953 00954 #ifdef IMPLEMENT_BOOK_DOM_TREES_LATER 00955 /* We can't just blast out the dom tree, because the dom tree 00956 * doesn't have the books, transactions, etc underneath it. 00957 * But that is just as well, since I think the performance 00958 * will be much better if we write out as we go along 00959 */ 00960 xmlNodePtr node; 00961 00962 node = gnc_book_dom_tree_create(book); 00963 00964 if (!node) 00965 { 00966 return FALSE; 00967 } 00968 00969 xmlElemDump(out, NULL, node); 00970 xmlFreeNode(node); 00971 00972 if (ferror(out) || fprintf(out, "\n") < 0) 00973 { 00974 return FALSE; 00975 } 00976 00977 #endif 00978 00979 be_data.out = out; 00980 be_data.book = book; 00981 be_data.gd = gd; 00982 if (fprintf( out, "<%s version=\"%s\">\n", BOOK_TAG, gnc_v2_book_version_string) < 0) 00983 return FALSE; 00984 if (!write_book_parts (out, book)) 00985 return FALSE; 00986 00987 /* gd->counter.{foo}_total fields should have all these totals 00988 already collected. I don't know why we're re-calling all these 00989 functions. */ 00990 if (!write_counts(out, 00991 "commodity", 00992 gnc_commodity_table_get_size( 00993 gnc_commodity_table_get_table(book)), 00994 "account", 00995 1 + gnc_account_n_descendants(gnc_book_get_root_account(book)), 00996 "transaction", 00997 gnc_book_count_transactions(book), 00998 "schedxaction", 00999 g_list_length(gnc_book_get_schedxactions(book)->sx_list), 01000 "budget", qof_collection_count( 01001 qof_book_get_collection(book, GNC_ID_BUDGET)), 01002 NULL)) 01003 return FALSE; 01004 01005 qof_object_foreach_backend (GNC_FILE_BACKEND, write_counts_cb, &be_data); 01006 01007 if (ferror(out) 01008 || !write_commodities(out, book, gd) 01009 || !write_pricedb(out, book, gd) 01010 || !write_accounts(out, book, gd) 01011 || !write_transactions(out, book, gd) 01012 || !write_template_transaction_data(out, book, gd) 01013 || !write_schedXactions(out, book, gd)) 01014 01015 return FALSE; 01016 01017 qof_collection_foreach(qof_book_get_collection(book, GNC_ID_BUDGET), 01018 write_budget, &be_data); 01019 if (ferror(out)) 01020 return FALSE; 01021 01022 qof_object_foreach_backend (GNC_FILE_BACKEND, write_data_cb, &be_data); 01023 if (ferror(out)) 01024 return FALSE; 01025 01026 if (fprintf( out, "</%s>\n", BOOK_TAG ) < 0) 01027 return FALSE; 01028 01029 return TRUE; 01030 } 01031 01032 gboolean 01033 write_commodities(FILE *out, QofBook *book, sixtp_gdv2 *gd) 01034 { 01035 gnc_commodity_table *tbl; 01036 GList *namespaces; 01037 GList *lp; 01038 gboolean success = TRUE; 01039 01040 tbl = gnc_commodity_table_get_table(book); 01041 01042 namespaces = gnc_commodity_table_get_namespaces(tbl); 01043 if (namespaces) 01044 { 01045 namespaces = g_list_sort(namespaces, compare_namespaces); 01046 } 01047 01048 for (lp = namespaces; success && lp; lp = lp->next) 01049 { 01050 GList *comms, *lp2; 01051 xmlNodePtr comnode; 01052 01053 comms = gnc_commodity_table_get_commodities(tbl, lp->data); 01054 comms = g_list_sort(comms, compare_commodity_ids); 01055 01056 for (lp2 = comms; lp2; lp2 = lp2->next) 01057 { 01058 comnode = gnc_commodity_dom_tree_create(lp2->data); 01059 if (comnode == NULL) 01060 continue; 01061 01062 xmlElemDump(out, NULL, comnode); 01063 if (ferror(out) || fprintf(out, "\n") < 0) 01064 { 01065 success = FALSE; 01066 break; 01067 } 01068 01069 xmlFreeNode(comnode); 01070 gd->counter.commodities_loaded++; 01071 run_callback(gd, "commodities"); 01072 } 01073 01074 g_list_free (comms); 01075 } 01076 01077 if (namespaces) g_list_free (namespaces); 01078 01079 return success; 01080 } 01081 01082 static gboolean 01083 write_pricedb(FILE *out, QofBook *book, sixtp_gdv2 *gd) 01084 { 01085 xmlNodePtr node; 01086 01087 node = gnc_pricedb_dom_tree_create(gnc_pricedb_get_db(book)); 01088 01089 if (!node) 01090 { 01091 return TRUE; 01092 } 01093 01094 xmlElemDump(out, NULL, node); 01095 xmlFreeNode(node); 01096 01097 if (ferror(out) || fprintf(out, "\n") < 0) 01098 return FALSE; 01099 01100 return TRUE; 01101 } 01102 01103 static int 01104 xml_add_trn_data(Transaction *t, gpointer data) 01105 { 01106 struct file_backend *be_data = data; 01107 xmlNodePtr node; 01108 01109 node = gnc_transaction_dom_tree_create(t); 01110 01111 xmlElemDump(be_data->out, NULL, node); 01112 xmlFreeNode(node); 01113 01114 if (ferror(be_data->out) || fprintf(be_data->out, "\n") < 0) 01115 return -1; 01116 01117 be_data->gd->counter.transactions_loaded++; 01118 run_callback(be_data->gd, "transaction"); 01119 return 0; 01120 } 01121 01122 static gboolean 01123 write_transactions(FILE *out, QofBook *book, sixtp_gdv2 *gd) 01124 { 01125 struct file_backend be_data; 01126 01127 be_data.out = out; 01128 be_data.gd = gd; 01129 return 0 == 01130 xaccAccountTreeForEachTransaction(gnc_book_get_root_account(book), 01131 xml_add_trn_data, 01132 (gpointer) &be_data); 01133 } 01134 01135 static gboolean 01136 write_template_transaction_data( FILE *out, QofBook *book, sixtp_gdv2 *gd ) 01137 { 01138 Account *ra; 01139 struct file_backend be_data; 01140 01141 be_data.out = out; 01142 be_data.gd = gd; 01143 01144 ra = gnc_book_get_template_root(book); 01145 if ( gnc_account_n_descendants(ra) > 0 ) 01146 { 01147 if (fprintf(out, "<%s>\n", TEMPLATE_TRANSACTION_TAG) < 0 01148 || !write_account_tree(out, ra, gd) 01149 || xaccAccountTreeForEachTransaction(ra, xml_add_trn_data, (gpointer)&be_data) 01150 || fprintf(out, "</%s>\n", TEMPLATE_TRANSACTION_TAG) < 0) 01151 01152 return FALSE; 01153 } 01154 01155 return TRUE; 01156 } 01157 01158 static gboolean 01159 write_schedXactions( FILE *out, QofBook *book, sixtp_gdv2 *gd) 01160 { 01161 GList *schedXactions; 01162 SchedXaction *tmpSX; 01163 xmlNodePtr node; 01164 01165 schedXactions = gnc_book_get_schedxactions(book)->sx_list; 01166 01167 if (schedXactions == NULL) 01168 return TRUE; 01169 01170 do 01171 { 01172 tmpSX = schedXactions->data; 01173 node = gnc_schedXaction_dom_tree_create( tmpSX ); 01174 xmlElemDump( out, NULL, node ); 01175 xmlFreeNode(node); 01176 if (ferror(out) || fprintf(out, "\n") < 0) 01177 return FALSE; 01178 gd->counter.schedXactions_loaded++; 01179 run_callback(gd, "schedXactions"); 01180 } 01181 while ( (schedXactions = schedXactions->next) ); 01182 01183 return TRUE; 01184 } 01185 01186 static void 01187 write_budget (QofInstance *ent, gpointer data) 01188 { 01189 xmlNodePtr node; 01190 struct file_backend* be = data; 01191 01192 GncBudget *bgt = GNC_BUDGET(ent); 01193 01194 if (ferror(be->out)) 01195 return; 01196 01197 node = gnc_budget_dom_tree_create(bgt); 01198 xmlElemDump( be->out, NULL, node ); 01199 xmlFreeNode(node); 01200 if (ferror(be->out) || fprintf(be->out, "\n") < 0) 01201 return; 01202 01203 be->gd->counter.budgets_loaded++; 01204 run_callback(be->gd, "budgets"); 01205 } 01206 01207 gboolean 01208 gnc_xml2_write_namespace_decl (FILE *out, const char *namespace) 01209 { 01210 g_return_val_if_fail(namespace, FALSE); 01211 return fprintf(out, "\n xmlns:%s=\"http://www.gnucash.org/XML/%s\"", 01212 namespace, namespace) >= 0; 01213 } 01214 01215 static void 01216 do_write_namespace_cb (const char *type, gpointer data_p, gpointer file_p) 01217 { 01218 GncXmlDataType_t *data = data_p; 01219 FILE *out = file_p; 01220 01221 g_return_if_fail (type && data && out); 01222 g_return_if_fail (data->version == GNC_FILE_BACKEND_VERS); 01223 01224 if (data->ns && !ferror(out)) 01225 (data->ns)(out); 01226 } 01227 01228 static gboolean 01229 write_v2_header (FILE *out) 01230 { 01231 if (fprintf(out, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n") < 0 01232 || fprintf(out, "<" GNC_V2_STRING) < 0 01233 01234 || !gnc_xml2_write_namespace_decl (out, "gnc") 01235 || !gnc_xml2_write_namespace_decl (out, "act") 01236 || !gnc_xml2_write_namespace_decl (out, "book") 01237 || !gnc_xml2_write_namespace_decl (out, "cd") 01238 || !gnc_xml2_write_namespace_decl (out, "cmdty") 01239 || !gnc_xml2_write_namespace_decl (out, "price") 01240 || !gnc_xml2_write_namespace_decl (out, "slot") 01241 || !gnc_xml2_write_namespace_decl (out, "split") 01242 || !gnc_xml2_write_namespace_decl (out, "sx") 01243 || !gnc_xml2_write_namespace_decl (out, "trn") 01244 || !gnc_xml2_write_namespace_decl (out, "ts") 01245 || !gnc_xml2_write_namespace_decl (out, "fs") 01246 || !gnc_xml2_write_namespace_decl (out, "bgt") 01247 || !gnc_xml2_write_namespace_decl (out, "recurrence") 01248 || !gnc_xml2_write_namespace_decl (out, "lot")) 01249 01250 return FALSE; 01251 01252 /* now cope with the plugins */ 01253 qof_object_foreach_backend (GNC_FILE_BACKEND, do_write_namespace_cb, out); 01254 01255 if (ferror(out) || fprintf(out, ">\n") < 0) 01256 return FALSE; 01257 01258 return TRUE; 01259 } 01260 01261 gboolean 01262 gnc_book_write_to_xml_filehandle_v2(QofBook *book, FILE *out) 01263 { 01264 QofBackend *be; 01265 sixtp_gdv2 *gd; 01266 gboolean success = TRUE; 01267 01268 if (!out) return FALSE; 01269 01270 if (!write_v2_header(out) 01271 || !write_counts(out, "book", 1, NULL)) 01272 return FALSE; 01273 01274 be = qof_book_get_backend(book); 01275 gd = gnc_sixtp_gdv2_new(book, FALSE, file_rw_feedback, be->percentage); 01276 gd->counter.commodities_total = 01277 gnc_commodity_table_get_size(gnc_commodity_table_get_table(book)); 01278 gd->counter.accounts_total = 1 + 01279 gnc_account_n_descendants(gnc_book_get_root_account(book)); 01280 gd->counter.transactions_total = gnc_book_count_transactions(book); 01281 gd->counter.schedXactions_total = 01282 g_list_length(gnc_book_get_schedxactions(book)->sx_list); 01283 gd->counter.budgets_total = qof_collection_count( 01284 qof_book_get_collection(book, GNC_ID_BUDGET)); 01285 01286 if (!write_book(out, book, gd) 01287 || fprintf(out, "</" GNC_V2_STRING ">\n\n") < 0) 01288 success = FALSE; 01289 01290 g_free(gd); 01291 return success; 01292 } 01293 01294 /* 01295 * This function is called by the "export" code. 01296 */ 01297 gboolean 01298 gnc_book_write_accounts_to_xml_filehandle_v2(QofBackend *be, QofBook *book, FILE *out) 01299 { 01300 gnc_commodity_table *table; 01301 Account *root; 01302 int ncom, nacc; 01303 sixtp_gdv2 *gd; 01304 gboolean success = TRUE; 01305 01306 if (!out) return FALSE; 01307 01308 root = gnc_book_get_root_account(book); 01309 nacc = 1 + gnc_account_n_descendants(root); 01310 01311 table = gnc_commodity_table_get_table(book); 01312 ncom = gnc_commodity_table_get_size(table); 01313 01314 if (!write_v2_header(out) 01315 || !write_counts(out, "commodity", ncom, "account", nacc, NULL)) 01316 return FALSE; 01317 01318 gd = gnc_sixtp_gdv2_new(book, TRUE, file_rw_feedback, be->percentage); 01319 gd->counter.commodities_total = ncom; 01320 gd->counter.accounts_total = nacc; 01321 01322 if (!write_commodities(out, book, gd) 01323 || !write_accounts(out, book, gd) 01324 || fprintf(out, "</" GNC_V2_STRING ">\n\n") < 0) 01325 success = FALSE; 01326 01327 g_free(gd); 01328 return success; 01329 } 01330 01331 #define BUFLEN 4096 01332 01333 /* Compress or decompress function that is to be run in a separate thread. 01334 * Returns 1 on success or 0 otherwise, stuffed into a pointer type. */ 01335 static gpointer 01336 gz_thread_func(gz_thread_params_t *params) 01337 { 01338 gchar buffer[BUFLEN]; 01339 gssize bytes; 01340 gint gzval; 01341 gzFile file; 01342 gint success = 1; 01343 01344 #ifdef G_OS_WIN32 01345 { 01346 gchar *conv_name = g_win32_locale_filename_from_utf8(params->filename); 01347 gchar *perms; 01348 01349 if (!conv_name) 01350 { 01351 g_warning("Could not convert '%s' to system codepage", 01352 params->filename); 01353 success = 0; 01354 goto cleanup_gz_thread_func; 01355 } 01356 01357 if (strchr(params->perms, 'b')) 01358 perms = g_strdup(params->perms); 01359 else 01360 perms = g_strdup_printf("%cb%s", *params->perms, params->perms + 1); 01361 01362 file = gzopen(conv_name, perms); 01363 g_free(perms); 01364 g_free(conv_name); 01365 } 01366 #else /* !G_OS_WIN32 */ 01367 file = gzopen(params->filename, params->perms); 01368 #endif /* G_OS_WIN32 */ 01369 01370 if (file == NULL) 01371 { 01372 g_warning("Child threads gzopen failed"); 01373 success = 0; 01374 goto cleanup_gz_thread_func; 01375 } 01376 01377 if (params->compress) 01378 { 01379 while (success) 01380 { 01381 bytes = read(params->fd, buffer, BUFLEN); 01382 if (bytes > 0) 01383 { 01384 if (gzwrite(file, buffer, bytes) <= 0) 01385 { 01386 gint errnum; 01387 const gchar *error = gzerror(file, &errnum); 01388 g_warning("Could not write the compressed file '%s'. The error is: '%s' (%d)", 01389 params->filename, error, errnum); 01390 success = 0; 01391 } 01392 } 01393 else if (bytes == 0) 01394 { 01395 break; 01396 } 01397 else 01398 { 01399 g_warning("Could not read from pipe. The error is '%s' (errno %d)", 01400 g_strerror(errno) ? g_strerror(errno) : "", errno); 01401 success = 0; 01402 } 01403 } 01404 } 01405 else 01406 { 01407 while (success) 01408 { 01409 gzval = gzread(file, buffer, BUFLEN); 01410 if (gzval > 0) 01411 { 01412 if ( 01413 #if COMPILER(MSVC) 01414 _write 01415 #else 01416 write 01417 #endif 01418 (params->fd, buffer, gzval) < 0) 01419 { 01420 g_warning("Could not write to pipe. The error is '%s' (%d)", 01421 g_strerror(errno) ? g_strerror(errno) : "", errno); 01422 success = 0; 01423 } 01424 } 01425 else if (gzval == 0) 01426 { 01427 break; 01428 } 01429 else 01430 { 01431 gint errnum; 01432 const gchar *error = gzerror(file, &errnum); 01433 g_warning("Could not read from compressed file '%s'. The error is: '%s' (%d)", 01434 params->filename, error, errnum); 01435 success = 0; 01436 } 01437 } 01438 } 01439 01440 if ((gzval = gzclose(file)) != Z_OK) 01441 { 01442 g_warning("Could not close the compressed file '%s' (errnum %d)", 01443 params->filename, gzval); 01444 success = 0; 01445 } 01446 01447 cleanup_gz_thread_func: 01448 close(params->fd); 01449 g_free(params->filename); 01450 g_free(params->perms); 01451 g_free(params); 01452 01453 return GINT_TO_POINTER(success); 01454 } 01455 01456 static FILE * 01457 try_gz_open (const char *filename, const char *perms, gboolean use_gzip, 01458 gboolean compress) 01459 { 01460 if (strstr(filename, ".gz.") != NULL) /* its got a temp extension */ 01461 use_gzip = TRUE; 01462 01463 if (!use_gzip) 01464 return g_fopen(filename, perms); 01465 01466 { 01467 int filedes[2]; 01468 GThread *thread; 01469 GError *error = NULL; 01470 gz_thread_params_t *params; 01471 FILE *file; 01472 01473 #ifdef G_OS_WIN32 01474 if (_pipe(filedes, 4096, _O_BINARY) < 0) 01475 { 01476 #else 01477 if (pipe(filedes) < 0) 01478 { 01479 #endif 01480 g_warning("Pipe call failed. Opening uncompressed file."); 01481 return g_fopen(filename, perms); 01482 } 01483 01484 params = g_new(gz_thread_params_t, 1); 01485 params->fd = filedes[compress ? 0 : 1]; 01486 params->filename = g_strdup(filename); 01487 params->perms = g_strdup(perms); 01488 params->compress = compress; 01489 01490 thread = g_thread_create((GThreadFunc) gz_thread_func, params, TRUE, &error); 01491 if (!thread) 01492 { 01493 g_warning("Could not create thread for (de)compression: %s", 01494 error->message); 01495 g_error_free(error); 01496 g_free(params->filename); 01497 g_free(params->perms); 01498 g_free(params); 01499 close(filedes[0]); 01500 close(filedes[1]); 01501 01502 return g_fopen(filename, perms); 01503 } 01504 01505 if (compress) 01506 file = fdopen(filedes[1], "w"); 01507 else 01508 file = fdopen(filedes[0], "r"); 01509 01510 G_LOCK(threads); 01511 if (!threads) 01512 threads = g_hash_table_new(g_direct_hash, g_direct_equal); 01513 01514 g_hash_table_insert(threads, file, thread); 01515 G_UNLOCK(threads); 01516 01517 return file; 01518 } 01519 } 01520 01521 static gboolean 01522 wait_for_gzip(FILE *file) 01523 { 01524 gboolean retval = TRUE; 01525 01526 G_LOCK(threads); 01527 if (threads) 01528 { 01529 GThread *thread = g_hash_table_lookup(threads, file); 01530 if (thread) 01531 { 01532 g_hash_table_remove(threads, file); 01533 retval = GPOINTER_TO_INT(g_thread_join(thread)); 01534 } 01535 } 01536 G_UNLOCK(threads); 01537 01538 return retval; 01539 } 01540 01541 gboolean 01542 gnc_book_write_to_xml_file_v2( 01543 QofBook *book, 01544 const char *filename, 01545 gboolean compress) 01546 { 01547 FILE *out; 01548 gboolean success = TRUE; 01549 01550 out = try_gz_open(filename, "w", compress, TRUE); 01551 01552 /* Try to write as much as possible */ 01553 if (!out 01554 || !gnc_book_write_to_xml_filehandle_v2(book, out) 01555 || !write_emacs_trailer(out)) 01556 success = FALSE; 01557 01558 /* Close the output stream */ 01559 if (out && fclose(out)) 01560 success = FALSE; 01561 01562 /* Optionally wait for parallel compression threads */ 01563 if (out && compress) 01564 if (!wait_for_gzip(out)) 01565 success = FALSE; 01566 01567 return success; 01568 } 01569 01570 /* 01571 * Have to pass in the backend as this routine needs the temporary 01572 * backend for file export, not the real backend which could be 01573 * postgress or anything else. 01574 */ 01575 gboolean 01576 gnc_book_write_accounts_to_xml_file_v2( 01577 QofBackend *be, 01578 QofBook *book, 01579 const char *filename) 01580 { 01581 FILE *out; 01582 gboolean success = TRUE; 01583 01584 out = g_fopen(filename, "w"); 01585 01586 /* Try to write as much as possible */ 01587 if (!out 01588 || !gnc_book_write_accounts_to_xml_filehandle_v2 (be, book, out) 01589 || !write_emacs_trailer(out)) 01590 success = FALSE; 01591 01592 /* Close the output stream */ 01593 if (out && fclose(out)) 01594 success = FALSE; 01595 01596 if (!success 01597 && qof_backend_get_error(be) == ERR_BACKEND_NO_ERR) 01598 { 01599 01600 /* Use a generic write error code */ 01601 qof_backend_set_error(be, ERR_FILEIO_WRITE_ERROR); 01602 } 01603 01604 return success; 01605 } 01606 01607 /***********************************************************************/ 01608 static gboolean 01609 is_gzipped_file(const gchar *name) 01610 { 01611 unsigned char buf[2]; 01612 int fd = g_open(name, O_RDONLY, 0); 01613 01614 if (fd == -1) 01615 { 01616 return FALSE; 01617 } 01618 01619 if (read(fd, buf, 2) != 2) 01620 { 01621 close(fd); 01622 return FALSE; 01623 } 01624 close(fd); 01625 01626 if (buf[0] == 037 && buf[1] == 0213) 01627 { 01628 return TRUE; 01629 } 01630 01631 return FALSE; 01632 } 01633 01634 QofBookFileType 01635 gnc_is_xml_data_file_v2(const gchar *name, gboolean *with_encoding) 01636 { 01637 if (is_gzipped_file(name)) 01638 { 01639 gzFile file = NULL; 01640 char first_chunk[256]; 01641 int num_read; 01642 01643 #ifdef G_OS_WIN32 01644 { 01645 gchar *conv_name = g_win32_locale_filename_from_utf8(name); 01646 if (!conv_name) 01647 g_warning("Could not convert '%s' to system codepage", name); 01648 else 01649 { 01650 file = gzopen(conv_name, "rb"); 01651 g_free(conv_name); 01652 } 01653 } 01654 #else 01655 file = gzopen(name, "r"); 01656 #endif 01657 if (file == NULL) 01658 return GNC_BOOK_NOT_OURS; 01659 01660 num_read = gzread(file, first_chunk, sizeof(first_chunk) - 1); 01661 gzclose(file); 01662 01663 if (num_read < 1) 01664 return GNC_BOOK_NOT_OURS; 01665 01666 return gnc_is_our_first_xml_chunk(first_chunk, with_encoding); 01667 } 01668 01669 return (gnc_is_our_xml_file(name, with_encoding)); 01670 } 01671 01672 01673 static void 01674 replace_character_references(gchar *string) 01675 { 01676 gchar *cursor, *semicolon, *tail; 01677 glong number; 01678 01679 for (cursor = strstr(string, "&#"); 01680 cursor && *cursor; 01681 cursor = strstr(cursor, "&#")) 01682 { 01683 semicolon = strchr(cursor, ';'); 01684 if (semicolon && *semicolon) 01685 { 01686 01687 /* parse number */ 01688 errno = 0; 01689 if (*(cursor + 2) == 'x') 01690 { 01691 number = strtol(cursor + 3, &tail, 16); 01692 } 01693 else 01694 { 01695 number = strtol(cursor + 2, &tail, 10); 01696 } 01697 if (errno || tail != semicolon || number < 0 || number > 255) 01698 { 01699 PWARN("Illegal character reference"); 01700 return; 01701 } 01702 01703 /* overwrite '&' with the specified character */ 01704 *cursor = (gchar) number; 01705 cursor++; 01706 if (*(semicolon + 1)) 01707 { 01708 /* move text after semicolon the the left */ 01709 tail = g_strdup(semicolon + 1); 01710 strcpy(cursor, tail); 01711 g_free(tail); 01712 } 01713 else 01714 { 01715 /* cut here */ 01716 *cursor = '\0'; 01717 } 01718 01719 } 01720 else 01721 { 01722 PWARN("Unclosed character reference"); 01723 return; 01724 } 01725 } 01726 } 01727 01728 static void 01729 conv_free(conv_type *conv) 01730 { 01731 if (conv) 01732 { 01733 g_free(conv->utf8_string); 01734 g_free(conv); 01735 } 01736 } 01737 01738 static void 01739 conv_list_free(GList *conv_list) 01740 { 01741 g_list_foreach(conv_list, (GFunc) conv_free, NULL); 01742 g_list_free(conv_list); 01743 } 01744 01745 typedef struct 01746 { 01747 GQuark encoding; 01748 GIConv iconv; 01749 } iconv_item_type; 01750 01751 gint 01752 gnc_xml2_find_ambiguous(const gchar *filename, GList *encodings, 01753 GHashTable **unique, GHashTable **ambiguous, 01754 GList **impossible) 01755 { 01756 FILE *file = NULL; 01757 GList *iconv_list = NULL, *conv_list = NULL, *iter; 01758 iconv_item_type *iconv_item = NULL, *ascii = NULL; 01759 const gchar *enc; 01760 GHashTable *processed = NULL; 01761 gint n_impossible = 0; 01762 GError *error = NULL; 01763 gboolean is_compressed; 01764 gboolean clean_return = FALSE; 01765 01766 is_compressed = is_gzipped_file(filename); 01767 file = try_gz_open(filename, "r", is_compressed, FALSE); 01768 if (file == NULL) 01769 { 01770 PWARN("Unable to open file %s", filename); 01771 goto cleanup_find_ambs; 01772 } 01773 01774 /* we need ascii */ 01775 ascii = g_new(iconv_item_type, 1); 01776 ascii->encoding = g_quark_from_string("ASCII"); 01777 ascii->iconv = g_iconv_open("UTF-8", "ASCII"); 01778 if (ascii->iconv == (GIConv) - 1) 01779 { 01780 PWARN("Unable to open ASCII ICONV conversion descriptor"); 01781 goto cleanup_find_ambs; 01782 } 01783 01784 /* call iconv_open on encodings */ 01785 for (iter = encodings; iter; iter = iter->next) 01786 { 01787 iconv_item = g_new(iconv_item_type, 1); 01788 iconv_item->encoding = GPOINTER_TO_UINT (iter->data); 01789 if (iconv_item->encoding == ascii->encoding) 01790 { 01791 continue; 01792 } 01793 01794 enc = g_quark_to_string(iconv_item->encoding); 01795 iconv_item->iconv = g_iconv_open("UTF-8", enc); 01796 if (iconv_item->iconv == (GIConv) - 1) 01797 { 01798 PWARN("Unable to open IConv conversion descriptor for '%s'", enc); 01799 goto cleanup_find_ambs; 01800 } 01801 else 01802 { 01803 iconv_list = g_list_prepend(iconv_list, iconv_item); 01804 } 01805 } 01806 01807 /* prepare data containers */ 01808 if (unique) 01809 *unique = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, 01810 (GDestroyNotify) conv_free); 01811 if (ambiguous) 01812 *ambiguous = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, 01813 (GDestroyNotify) conv_list_free); 01814 if (impossible) 01815 *impossible = NULL; 01816 processed = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); 01817 01818 /* loop through lines */ 01819 while (1) 01820 { 01821 gchar line[256], *word, *utf8; 01822 gchar **word_array, **word_cursor; 01823 conv_type *conv = NULL; 01824 01825 if (!fgets(line, sizeof(line) - 1, file)) 01826 { 01827 if (feof(file)) 01828 { 01829 break; 01830 } 01831 else 01832 { 01833 goto cleanup_find_ambs; 01834 } 01835 } 01836 01837 g_strchomp(line); 01838 replace_character_references(line); 01839 word_array = g_strsplit_set(line, "> <", 0); 01840 01841 /* loop through words */ 01842 for (word_cursor = word_array; *word_cursor; word_cursor++) 01843 { 01844 word = *word_cursor; 01845 if (!word) 01846 continue; 01847 01848 utf8 = g_convert_with_iconv(word, -1, ascii->iconv, 01849 NULL, NULL, &error); 01850 if (utf8) 01851 { 01852 /* pure ascii */ 01853 g_free(utf8); 01854 continue; 01855 } 01856 g_error_free(error); 01857 error = NULL; 01858 01859 if (g_hash_table_lookup_extended(processed, word, NULL, NULL)) 01860 { 01861 /* already processed */ 01862 continue; 01863 } 01864 01865 /* loop through encodings */ 01866 conv_list = NULL; 01867 for (iter = iconv_list; iter; iter = iter->next) 01868 { 01869 iconv_item = iter->data; 01870 utf8 = g_convert_with_iconv(word, -1, iconv_item->iconv, 01871 NULL, NULL, &error); 01872 if (utf8) 01873 { 01874 conv = g_new(conv_type, 1); 01875 conv->encoding = iconv_item->encoding; 01876 conv->utf8_string = utf8; 01877 conv_list = g_list_prepend(conv_list, conv); 01878 } 01879 else 01880 { 01881 g_error_free(error); 01882 error = NULL; 01883 } 01884 } 01885 01886 /* no successful conversion */ 01887 if (!conv_list) 01888 { 01889 if (impossible) 01890 *impossible = g_list_append(*impossible, g_strdup(word)); 01891 n_impossible++; 01892 } 01893 01894 /* more than one successful conversion */ 01895 else if (conv_list->next) 01896 { 01897 if (ambiguous) 01898 { 01899 g_hash_table_insert(*ambiguous, g_strdup(word), conv_list); 01900 } 01901 else 01902 { 01903 conv_list_free(conv_list); 01904 } 01905 } 01906 01907 /* only one successful conversion */ 01908 else 01909 { 01910 if (unique) 01911 { 01912 g_hash_table_insert(*unique, g_strdup(word), conv); 01913 } 01914 else 01915 { 01916 conv_free(conv); 01917 } 01918 g_list_free(conv_list); 01919 } 01920 01921 g_hash_table_insert(processed, g_strdup(word), NULL); 01922 } 01923 g_strfreev(word_array); 01924 } 01925 01926 clean_return = TRUE; 01927 01928 cleanup_find_ambs: 01929 01930 if (iconv_list) 01931 { 01932 for (iter = iconv_list; iter; iter = iter->next) 01933 { 01934 if (iter->data) 01935 { 01936 g_iconv_close(((iconv_item_type*) iter->data)->iconv); 01937 g_free(iter->data); 01938 } 01939 } 01940 g_list_free(iconv_list); 01941 } 01942 if (processed) 01943 g_hash_table_destroy(processed); 01944 if (ascii) 01945 g_free(ascii); 01946 if (file) 01947 { 01948 fclose(file); 01949 if (is_compressed) 01950 wait_for_gzip(file); 01951 } 01952 01953 return (clean_return) ? n_impossible : -1; 01954 } 01955 01956 typedef struct 01957 { 01958 gchar *filename; 01959 GHashTable *subst; 01960 } push_data_type; 01961 01962 static void 01963 parse_with_subst_push_handler (xmlParserCtxtPtr xml_context, 01964 push_data_type *push_data) 01965 { 01966 const gchar *filename; 01967 FILE *file = NULL; 01968 GIConv ascii = (GIConv) - 1; 01969 GString *output = NULL; 01970 GError *error = NULL; 01971 gboolean is_compressed; 01972 01973 filename = push_data->filename; 01974 is_compressed = is_gzipped_file(filename); 01975 file = try_gz_open(filename, "r", is_compressed, FALSE); 01976 if (file == NULL) 01977 { 01978 PWARN("Unable to open file %s", filename); 01979 goto cleanup_push_handler; 01980 } 01981 01982 ascii = g_iconv_open("UTF-8", "ASCII"); 01983 if (ascii == (GIConv) - 1) 01984 { 01985 PWARN("Unable to open ASCII ICONV conversion descriptor"); 01986 goto cleanup_push_handler; 01987 } 01988 01989 /* loop through lines */ 01990 while (1) 01991 { 01992 gchar line[256], *word, *repl, *utf8; 01993 gint pos, len; 01994 gchar *start, *cursor; 01995 01996 if (!fgets(line, sizeof(line) - 1, file)) 01997 { 01998 if (feof(file)) 01999 { 02000 break; 02001 } 02002 else 02003 { 02004 goto cleanup_push_handler; 02005 } 02006 } 02007 02008 replace_character_references(line); 02009 output = g_string_new(line); 02010 02011 /* loop through words */ 02012 cursor = output->str; 02013 pos = 0; 02014 while (1) 02015 { 02016 /* ignore delimiters */ 02017 while (*cursor == '>' || *cursor == ' ' || *cursor == '<' || 02018 *cursor == '\n') 02019 { 02020 cursor++; 02021 pos += 1; 02022 } 02023 02024 if (!*cursor) 02025 /* welcome to EOL */ 02026 break; 02027 02028 /* search for a delimiter */ 02029 start = cursor; 02030 len = 0; 02031 while (*cursor && *cursor != '>' && *cursor != ' ' && *cursor != '<' && 02032 *cursor != '\n') 02033 { 02034 cursor++; 02035 len++; 02036 } 02037 02038 utf8 = g_convert_with_iconv(start, len, ascii, NULL, NULL, &error); 02039 02040 if (utf8) 02041 { 02042 /* pure ascii */ 02043 g_free(utf8); 02044 pos += len; 02045 } 02046 else 02047 { 02048 g_error_free(error); 02049 error = NULL; 02050 02051 word = g_strndup(start, len); 02052 repl = g_hash_table_lookup(push_data->subst, word); 02053 g_free(word); 02054 if (repl) 02055 { 02056 /* there is a replacement */ 02057 output = g_string_insert(g_string_erase(output, pos, len), 02058 pos, repl); 02059 pos += strlen(repl); 02060 cursor = output->str + pos; 02061 } 02062 else 02063 { 02064 /* there is no replacement, return immediately */ 02065 goto cleanup_push_handler; 02066 } 02067 } 02068 } 02069 02070 if (xmlParseChunk(xml_context, output->str, output->len, 0) != 0) 02071 { 02072 goto cleanup_push_handler; 02073 } 02074 } 02075 02076 /* last chunk */ 02077 xmlParseChunk(xml_context, "", 0, 1); 02078 02079 cleanup_push_handler: 02080 02081 if (output) 02082 g_string_free(output, TRUE); 02083 if (ascii != (GIConv) - 1) 02084 g_iconv_close(ascii); 02085 if (file) 02086 { 02087 fclose(file); 02088 if (is_compressed) 02089 wait_for_gzip(file); 02090 } 02091 } 02092 02093 gboolean 02094 gnc_xml2_parse_with_subst (FileBackend *fbe, QofBook *book, GHashTable *subst) 02095 { 02096 push_data_type *push_data; 02097 gboolean success; 02098 02099 push_data = g_new(push_data_type, 1); 02100 push_data->filename = fbe->fullpath; 02101 push_data->subst = subst; 02102 02103 success = qof_session_load_from_xml_file_v2_full( 02104 fbe, book, (sixtp_push_handler) parse_with_subst_push_handler, 02105 push_data, GNC_BOOK_XML2_FILE); 02106 02107 if (success) 02108 qof_book_kvp_changed(book); 02109 02110 return success; 02111 } 02112
1.7.4