mirror of
https://github.com/Gnucash/gnucash.git
synced 2024-11-26 02:40:43 -06:00
74d0630867
significantly Modify the DEBUG and PINFO macros to return unless qof_log_check is true. Replace almost all direct calls to g_debug and g_message with DEBUG and PINFO respectively. Track the highest logging level sent to qof_log_set_level to provide a short-circuit return in qof_log_check. Remove setting GNC_MOD_TESTS to QOF_LOG_DEBUG so that the short-circuit threshold isn't defeated by always being DEBUG. Net result: 33% improvement in xml load times.
1012 lines
30 KiB
C++
1012 lines
30 KiB
C++
/********************************************************************
|
|
* sixtp.c -- functions for XML parsing *
|
|
* Copyright (c) 2001 Gnumatic, Inc. *
|
|
* *
|
|
* This program is free software; you can redistribute it and/or *
|
|
* modify it under the terms of the GNU General Public License as *
|
|
* published by the Free Software Foundation; either version 2 of *
|
|
* the License, or (at your option) any later version. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License*
|
|
* along with this program; if not, contact: *
|
|
* *
|
|
* Free Software Foundation Voice: +1-617-542-5942 *
|
|
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
|
|
* Boston, MA 02110-1301, USA gnu@gnu.org *
|
|
* *
|
|
********************************************************************/
|
|
#include <glib.h>
|
|
#include <glib/gstdio.h>
|
|
|
|
extern "C"
|
|
{
|
|
#include <config.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <sys/types.h>
|
|
#include <qoflog.h>
|
|
#ifdef _MSC_VER
|
|
typedef int ssize_t;
|
|
# define g_fopen fopen
|
|
#endif
|
|
}
|
|
|
|
#include "sixtp.h"
|
|
#include "sixtp-parsers.h"
|
|
#include "sixtp-stack.h"
|
|
|
|
#undef G_LOG_DOMAIN
|
|
#define G_LOG_DOMAIN "gnc.backend.file.sixtp"
|
|
static QofLogModule log_module = "gnc.backend.file.sixtp";
|
|
|
|
extern const gchar* gnc_v2_xml_version_string; /* see io-gncxml-v2.c */
|
|
|
|
/************************************************************************/
|
|
gboolean
|
|
is_child_result_from_node_named (sixtp_child_result* cr, const char* tag)
|
|
{
|
|
return ((cr->type == SIXTP_CHILD_RESULT_NODE)
|
|
&&
|
|
(g_strcmp0 (cr->tag, tag) == 0));
|
|
}
|
|
|
|
void
|
|
sixtp_child_free_data (sixtp_child_result* result)
|
|
{
|
|
if (result->data) g_free (result->data);
|
|
}
|
|
|
|
void
|
|
sixtp_child_result_destroy (sixtp_child_result* r)
|
|
{
|
|
if (r->should_cleanup && r->cleanup_handler)
|
|
{
|
|
r->cleanup_handler (r);
|
|
}
|
|
if (r->type == SIXTP_CHILD_RESULT_NODE) g_free (r->tag);
|
|
g_free (r);
|
|
}
|
|
|
|
void
|
|
sixtp_child_result_print (sixtp_child_result* cr, FILE* f)
|
|
{
|
|
fprintf (f, "((tag %s) (data %p))",
|
|
cr->tag ? cr->tag : "(null)",
|
|
cr->data);
|
|
}
|
|
|
|
/************************************************************************/
|
|
|
|
|
|
void
|
|
sixtp_set_start (sixtp* parser, sixtp_start_handler start_handler)
|
|
{
|
|
parser->start_handler = start_handler;
|
|
}
|
|
|
|
void
|
|
sixtp_set_before_child (sixtp* parser, sixtp_before_child_handler handler)
|
|
{
|
|
parser->before_child = handler;
|
|
}
|
|
|
|
void
|
|
sixtp_set_after_child (sixtp* parser, sixtp_after_child_handler handler)
|
|
{
|
|
parser->after_child = handler;
|
|
}
|
|
|
|
void
|
|
sixtp_set_end (sixtp* parser, sixtp_end_handler end_handler)
|
|
{
|
|
parser->end_handler = end_handler;
|
|
}
|
|
|
|
void
|
|
sixtp_set_chars (sixtp* parser, sixtp_characters_handler char_handler)
|
|
{
|
|
parser->characters_handler = char_handler;
|
|
}
|
|
|
|
void
|
|
sixtp_set_cleanup_result (sixtp* parser, sixtp_result_handler handler)
|
|
{
|
|
parser->cleanup_result = handler;
|
|
}
|
|
|
|
void
|
|
sixtp_set_cleanup_chars (sixtp* parser, sixtp_result_handler handler)
|
|
{
|
|
parser->cleanup_chars = handler;
|
|
}
|
|
|
|
void
|
|
sixtp_set_fail (sixtp* parser,
|
|
sixtp_fail_handler handler)
|
|
{
|
|
parser->fail_handler = handler;
|
|
}
|
|
|
|
void
|
|
sixtp_set_result_fail (sixtp* parser, sixtp_result_handler handler)
|
|
{
|
|
parser->result_fail_handler = handler;
|
|
}
|
|
|
|
void
|
|
sixtp_set_chars_fail (sixtp* parser, sixtp_result_handler handler)
|
|
{
|
|
parser->chars_fail_handler = handler;
|
|
}
|
|
|
|
sixtp*
|
|
sixtp_new (void)
|
|
{
|
|
sixtp* s = g_new0 (sixtp, 1);
|
|
|
|
if (s)
|
|
{
|
|
s->child_parsers = g_hash_table_new (g_str_hash, g_str_equal);
|
|
if (!s->child_parsers)
|
|
{
|
|
g_free (s);
|
|
s = NULL;
|
|
}
|
|
}
|
|
return (s);
|
|
}
|
|
|
|
sixtp*
|
|
sixtp_set_any (sixtp* tochange, int cleanup, ...)
|
|
{
|
|
va_list ap;
|
|
sixtp_handler_type type;
|
|
|
|
if (!tochange)
|
|
{
|
|
PWARN ("Null tochange passed");
|
|
return NULL;
|
|
}
|
|
|
|
va_start (ap, cleanup);
|
|
|
|
do
|
|
{
|
|
type = static_cast<decltype (type)> (va_arg (ap, int));
|
|
|
|
switch (type)
|
|
{
|
|
case SIXTP_NO_MORE_HANDLERS:
|
|
va_end (ap);
|
|
return tochange;
|
|
|
|
case SIXTP_START_HANDLER_ID:
|
|
sixtp_set_start (tochange, va_arg (ap, sixtp_start_handler));
|
|
break;
|
|
|
|
case SIXTP_BEFORE_CHILD_HANDLER_ID:
|
|
sixtp_set_before_child (tochange,
|
|
va_arg (ap, sixtp_before_child_handler));
|
|
break;
|
|
|
|
case SIXTP_AFTER_CHILD_HANDLER_ID:
|
|
sixtp_set_after_child (tochange,
|
|
va_arg (ap, sixtp_after_child_handler));
|
|
break;
|
|
|
|
case SIXTP_END_HANDLER_ID:
|
|
sixtp_set_end (tochange, va_arg (ap, sixtp_end_handler));
|
|
break;
|
|
|
|
case SIXTP_CHARACTERS_HANDLER_ID:
|
|
sixtp_set_chars (tochange, va_arg (ap, sixtp_characters_handler));
|
|
break;
|
|
|
|
case SIXTP_FAIL_HANDLER_ID:
|
|
sixtp_set_fail (tochange, va_arg (ap, sixtp_fail_handler));
|
|
break;
|
|
|
|
case SIXTP_CLEANUP_RESULT_ID:
|
|
sixtp_set_cleanup_result (tochange,
|
|
va_arg (ap, sixtp_result_handler));
|
|
break;
|
|
|
|
case SIXTP_CLEANUP_CHARS_ID:
|
|
sixtp_set_cleanup_chars (tochange,
|
|
va_arg (ap, sixtp_result_handler));
|
|
break;
|
|
|
|
case SIXTP_RESULT_FAIL_ID:
|
|
sixtp_set_result_fail (tochange, va_arg (ap, sixtp_result_handler));
|
|
break;
|
|
|
|
case SIXTP_CHARS_FAIL_ID:
|
|
sixtp_set_chars_fail (tochange, va_arg (ap, sixtp_result_handler));
|
|
break;
|
|
|
|
default:
|
|
va_end (ap);
|
|
g_critical ("Bogus sixtp type %d", type);
|
|
if (cleanup)
|
|
{
|
|
sixtp_destroy (tochange);
|
|
}
|
|
return NULL;
|
|
}
|
|
}
|
|
while (1);
|
|
|
|
va_end (ap);
|
|
return tochange;
|
|
}
|
|
|
|
static void sixtp_destroy_child (gpointer key, gpointer value,
|
|
gpointer user_data);
|
|
|
|
static void
|
|
sixtp_destroy_node (sixtp* sp, GHashTable* corpses)
|
|
{
|
|
g_return_if_fail (sp);
|
|
g_return_if_fail (corpses);
|
|
g_hash_table_foreach (sp->child_parsers, sixtp_destroy_child, corpses);
|
|
g_hash_table_destroy (sp->child_parsers);
|
|
g_free (sp);
|
|
}
|
|
|
|
static void
|
|
sixtp_destroy_child (gpointer key, gpointer value, gpointer user_data)
|
|
{
|
|
GHashTable* corpses = (GHashTable*) user_data;
|
|
sixtp* child = (sixtp*) value;
|
|
gpointer lookup_key;
|
|
gpointer lookup_value;
|
|
|
|
DEBUG ("Killing sixtp child under key <%s>", key ? (char*) key : "(null)");
|
|
|
|
if (!corpses)
|
|
{
|
|
g_critical ("no corpses in sixtp_destroy_child <%s>",
|
|
key ? (char*) key : "(null)");
|
|
g_free (key);
|
|
return;
|
|
}
|
|
if (!child)
|
|
{
|
|
g_critical ("no child in sixtp_destroy_child <%s>",
|
|
key ? (char*) key : "");
|
|
g_free (key);
|
|
return;
|
|
}
|
|
g_free (key);
|
|
|
|
if (!g_hash_table_lookup_extended (corpses, (gconstpointer) child,
|
|
&lookup_key, &lookup_value))
|
|
{
|
|
/* haven't killed this one yet. */
|
|
g_hash_table_insert (corpses, child, (gpointer) 1);
|
|
sixtp_destroy_node (child, corpses);
|
|
}
|
|
}
|
|
|
|
void
|
|
sixtp_destroy (sixtp* sp)
|
|
{
|
|
GHashTable* corpses;
|
|
g_return_if_fail (sp);
|
|
corpses = g_hash_table_new (g_direct_hash, g_direct_equal);
|
|
sixtp_destroy_node (sp, corpses);
|
|
g_hash_table_destroy (corpses);
|
|
}
|
|
|
|
|
|
/***********************************************************************/
|
|
|
|
gboolean
|
|
sixtp_add_sub_parser (sixtp* parser, const gchar* tag, sixtp* sub_parser)
|
|
{
|
|
g_return_val_if_fail (parser, FALSE);
|
|
g_return_val_if_fail (tag, FALSE);
|
|
g_return_val_if_fail (sub_parser, FALSE);
|
|
|
|
g_hash_table_insert (parser->child_parsers,
|
|
g_strdup (tag), (gpointer) sub_parser);
|
|
return (TRUE);
|
|
}
|
|
|
|
/*
|
|
* This is a bit complex because of having to make sure to
|
|
* cleanup things we haven't looked at on an error condition
|
|
*/
|
|
sixtp*
|
|
sixtp_add_some_sub_parsers (sixtp* tochange, int cleanup, ...)
|
|
{
|
|
int have_error;
|
|
va_list ap;
|
|
char* tag;
|
|
sixtp* handler;
|
|
|
|
va_start (ap, cleanup);
|
|
|
|
have_error = 0;
|
|
|
|
if (!tochange)
|
|
{
|
|
have_error = 1;
|
|
}
|
|
|
|
do
|
|
{
|
|
tag = va_arg (ap, char*);
|
|
if (!tag)
|
|
{
|
|
break;
|
|
}
|
|
|
|
handler = va_arg (ap, sixtp*);
|
|
if (!handler)
|
|
{
|
|
PWARN ("Handler for tag %s is null",
|
|
tag ? tag : "(null)");
|
|
|
|
if (cleanup)
|
|
{
|
|
sixtp_destroy (tochange);
|
|
tochange = NULL;
|
|
have_error = 1;
|
|
}
|
|
else
|
|
{
|
|
va_end (ap);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (have_error)
|
|
{
|
|
sixtp_destroy (handler);
|
|
}
|
|
else
|
|
{
|
|
sixtp_add_sub_parser (tochange, tag, handler);
|
|
}
|
|
}
|
|
while (1);
|
|
|
|
va_end (ap);
|
|
return tochange;
|
|
}
|
|
|
|
/************************************************************************/
|
|
|
|
void
|
|
sixtp_sax_start_handler (void* user_data,
|
|
const xmlChar* name,
|
|
const xmlChar** attrs)
|
|
{
|
|
sixtp_sax_data* pdata = (sixtp_sax_data*) user_data;
|
|
sixtp_stack_frame* current_frame = NULL;
|
|
sixtp* current_parser = NULL;
|
|
sixtp* next_parser = NULL;
|
|
gchar* next_parser_tag = NULL;
|
|
gboolean lookup_success = FALSE;
|
|
sixtp_stack_frame* new_frame = NULL;
|
|
|
|
current_frame = (sixtp_stack_frame*) pdata->stack->data;
|
|
current_parser = current_frame->parser;
|
|
|
|
/* Use an extended lookup so we can get *our* copy of the key.
|
|
Since we've strduped it, we know its lifetime... */
|
|
lookup_success =
|
|
g_hash_table_lookup_extended (current_parser->child_parsers,
|
|
name,
|
|
reinterpret_cast<void**> (&next_parser_tag),
|
|
reinterpret_cast<void**> (&next_parser));
|
|
|
|
|
|
if (!lookup_success)
|
|
{
|
|
/* magic catch all value */
|
|
lookup_success = g_hash_table_lookup_extended (
|
|
current_parser->child_parsers, SIXTP_MAGIC_CATCHER,
|
|
reinterpret_cast<void**> (&next_parser_tag),
|
|
reinterpret_cast<void**> (&next_parser));
|
|
if (!lookup_success)
|
|
{
|
|
g_critical ("Tag <%s> not allowed in current context.",
|
|
name ? (char*) name : "(null)");
|
|
pdata->parsing_ok = FALSE;
|
|
next_parser = pdata->bad_xml_parser;
|
|
}
|
|
}
|
|
|
|
if (current_frame->parser->before_child)
|
|
{
|
|
GSList* parent_data_from_children = NULL;
|
|
gpointer parent_data_for_children = NULL;
|
|
|
|
if (g_slist_length (pdata->stack) > 1)
|
|
{
|
|
/* we're not in the top level node */
|
|
sixtp_stack_frame* parent_frame =
|
|
(sixtp_stack_frame*) pdata->stack->next->data;
|
|
parent_data_from_children = static_cast<decltype (parent_data_from_children)>
|
|
(parent_frame->data_from_children);
|
|
}
|
|
|
|
pdata->parsing_ok &=
|
|
current_frame->parser->before_child (current_frame->data_for_children,
|
|
current_frame->data_from_children,
|
|
parent_data_from_children,
|
|
parent_data_for_children,
|
|
pdata->global_data,
|
|
& (current_frame->frame_data),
|
|
current_frame->tag,
|
|
(gchar*) name);
|
|
}
|
|
|
|
/* now allocate the new stack frame and shift to it */
|
|
new_frame = sixtp_stack_frame_new (next_parser, g_strdup ((char*) name));
|
|
|
|
new_frame->line = xmlSAX2GetLineNumber (pdata->saxParserCtxt);
|
|
new_frame->col = xmlSAX2GetColumnNumber (pdata->saxParserCtxt);
|
|
|
|
pdata->stack = g_slist_prepend (pdata->stack, (gpointer) new_frame);
|
|
|
|
if (next_parser->start_handler)
|
|
{
|
|
pdata->parsing_ok &=
|
|
next_parser->start_handler (current_frame->data_from_children,
|
|
current_frame->data_for_children,
|
|
pdata->global_data,
|
|
&new_frame->data_for_children,
|
|
&new_frame->frame_data,
|
|
(gchar*) name,
|
|
(gchar**)attrs);
|
|
}
|
|
}
|
|
|
|
void
|
|
sixtp_sax_characters_handler (void* user_data, const xmlChar* text, int len)
|
|
{
|
|
sixtp_sax_data* pdata = (sixtp_sax_data*) user_data;
|
|
sixtp_stack_frame* frame;
|
|
|
|
frame = (sixtp_stack_frame*) pdata->stack->data;
|
|
if (frame->parser->characters_handler)
|
|
{
|
|
gpointer result = NULL;
|
|
|
|
pdata->parsing_ok &=
|
|
frame->parser->characters_handler (frame->data_from_children,
|
|
frame->data_for_children,
|
|
pdata->global_data,
|
|
&result,
|
|
(gchar*) text,
|
|
len);
|
|
if (pdata->parsing_ok && result)
|
|
{
|
|
/* push the result onto the current "child" list. */
|
|
sixtp_child_result* child_data = g_new0 (sixtp_child_result, 1);
|
|
|
|
child_data->type = SIXTP_CHILD_RESULT_CHARS;
|
|
child_data->tag = NULL;
|
|
child_data->data = result;
|
|
child_data->should_cleanup = TRUE;
|
|
child_data->cleanup_handler = frame->parser->cleanup_chars;
|
|
child_data->fail_handler = frame->parser->chars_fail_handler;
|
|
frame->data_from_children = g_slist_prepend (frame->data_from_children,
|
|
child_data);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
sixtp_sax_end_handler (void* user_data, const xmlChar* name)
|
|
{
|
|
sixtp_sax_data* pdata = (sixtp_sax_data*) user_data;
|
|
sixtp_stack_frame* current_frame;
|
|
sixtp_stack_frame* parent_frame;
|
|
sixtp_child_result* child_result_data = NULL;
|
|
gchar* end_tag = NULL;
|
|
|
|
current_frame = (sixtp_stack_frame*) pdata->stack->data;
|
|
parent_frame = (sixtp_stack_frame*) pdata->stack->next->data;
|
|
|
|
/* time to make sure we got the right closing tag. Is this really
|
|
necessary? */
|
|
if (g_strcmp0 (current_frame->tag, (gchar*) name) != 0)
|
|
{
|
|
PWARN ("bad closing tag (start <%s>, end <%s>)", current_frame->tag, name);
|
|
pdata->parsing_ok = FALSE;
|
|
|
|
/* See if we're just off by one and try to recover */
|
|
if (g_strcmp0 (parent_frame->tag, (gchar*) name) == 0)
|
|
{
|
|
pdata->stack = sixtp_pop_and_destroy_frame (pdata->stack);
|
|
current_frame = (sixtp_stack_frame*) pdata->stack->data;
|
|
parent_frame = (sixtp_stack_frame*) pdata->stack->next->data;
|
|
PWARN ("found matching start <%s> tag up one level", name);
|
|
}
|
|
}
|
|
|
|
/* tag's OK, proceed. */
|
|
if (current_frame->parser->end_handler)
|
|
{
|
|
pdata->parsing_ok &=
|
|
current_frame->parser->end_handler (current_frame->data_for_children,
|
|
current_frame->data_from_children,
|
|
parent_frame->data_from_children,
|
|
parent_frame->data_for_children,
|
|
pdata->global_data,
|
|
¤t_frame->frame_data,
|
|
current_frame->tag);
|
|
}
|
|
|
|
if (current_frame->frame_data)
|
|
{
|
|
/* push the result onto the parent's child result list. */
|
|
child_result_data = g_new (sixtp_child_result, 1);
|
|
|
|
child_result_data->type = SIXTP_CHILD_RESULT_NODE;
|
|
child_result_data->tag = g_strdup (current_frame->tag);
|
|
child_result_data->data = current_frame->frame_data;
|
|
child_result_data->should_cleanup = TRUE;
|
|
child_result_data->cleanup_handler = current_frame->parser->cleanup_result;
|
|
child_result_data->fail_handler =
|
|
current_frame->parser->result_fail_handler;
|
|
parent_frame->data_from_children =
|
|
g_slist_prepend (parent_frame->data_from_children, child_result_data);
|
|
}
|
|
|
|
/* grab it before it goes away - we own the reference */
|
|
end_tag = current_frame->tag;
|
|
|
|
DEBUG ("Finished with end of <%s>", end_tag ? end_tag : "(null)");
|
|
|
|
/*sixtp_print_frame_stack(pdata->stack, stderr);*/
|
|
|
|
pdata->stack = sixtp_pop_and_destroy_frame (pdata->stack);
|
|
|
|
/* reset pointer after stack pop */
|
|
current_frame = (sixtp_stack_frame*) pdata->stack->data;
|
|
/* reset the parent, checking to see if we're at the top level node */
|
|
parent_frame = (sixtp_stack_frame*)
|
|
((g_slist_length (pdata->stack) > 1) ? (pdata->stack->next->data) : NULL);
|
|
|
|
if (current_frame->parser->after_child)
|
|
{
|
|
/* reset pointer after stack pop */
|
|
GSList* parent_data_from_children = NULL;
|
|
gpointer parent_data_for_children = NULL;
|
|
|
|
if (parent_frame)
|
|
{
|
|
/* we're not in the top level node */
|
|
sixtp_stack_frame* parent_frame =
|
|
(sixtp_stack_frame*) pdata->stack->next->data;
|
|
parent_data_from_children = static_cast<decltype (parent_data_from_children)>
|
|
(parent_frame->data_for_children);
|
|
}
|
|
|
|
pdata->parsing_ok &=
|
|
current_frame->parser->after_child (current_frame->data_for_children,
|
|
current_frame->data_from_children,
|
|
parent_data_from_children,
|
|
parent_data_for_children,
|
|
pdata->global_data,
|
|
& (current_frame->frame_data),
|
|
current_frame->tag,
|
|
end_tag,
|
|
child_result_data);
|
|
}
|
|
|
|
g_free (end_tag);
|
|
}
|
|
|
|
xmlEntityPtr
|
|
sixtp_sax_get_entity_handler (void* user_data, const xmlChar* name)
|
|
{
|
|
return xmlGetPredefinedEntity (name);
|
|
}
|
|
|
|
|
|
void
|
|
sixtp_handle_catastrophe (sixtp_sax_data* sax_data)
|
|
{
|
|
/* Something has gone wrong. To handle it, we have to traverse the
|
|
stack, calling, at each level, the frame failure handler (the
|
|
handler for the current, unfinished block) and then the sibling
|
|
handlers. The order is reverse chronological - oldest child
|
|
results cleaned up last. This holds overall as well, stack
|
|
frames are cleaned up in their order on the stack which will be
|
|
youngest to oldest. */
|
|
|
|
GSList* lp;
|
|
GSList** stack = & (sax_data->stack);
|
|
|
|
g_critical ("parse failed at:");
|
|
sixtp_print_frame_stack (sax_data->stack, stderr);
|
|
|
|
while (*stack)
|
|
{
|
|
sixtp_stack_frame* current_frame = (sixtp_stack_frame*) (*stack)->data;
|
|
|
|
/* cleanup the current frame */
|
|
if (current_frame->parser->fail_handler)
|
|
{
|
|
GSList* sibling_data;
|
|
gpointer parent_data;
|
|
|
|
if ((*stack)->next == NULL)
|
|
{
|
|
/* This is the top of the stack... */
|
|
parent_data = NULL;
|
|
sibling_data = NULL;
|
|
}
|
|
else
|
|
{
|
|
sixtp_stack_frame* parent_frame =
|
|
(sixtp_stack_frame*) (*stack)->next->data;
|
|
parent_data = parent_frame->data_for_children;
|
|
sibling_data = parent_frame->data_from_children;
|
|
}
|
|
|
|
current_frame->parser->fail_handler (current_frame->data_for_children,
|
|
current_frame->data_from_children,
|
|
sibling_data,
|
|
parent_data,
|
|
sax_data->global_data,
|
|
¤t_frame->frame_data,
|
|
current_frame->tag);
|
|
}
|
|
|
|
/* now cleanup any children's results */
|
|
for (lp = current_frame->data_from_children; lp; lp = lp->next)
|
|
{
|
|
sixtp_child_result* cresult = (sixtp_child_result*) lp->data;
|
|
if (cresult->fail_handler)
|
|
{
|
|
cresult->fail_handler (cresult);
|
|
}
|
|
}
|
|
|
|
if ((*stack)->next == NULL)
|
|
{
|
|
/* This is the top of the stack. The top frame seems to want to
|
|
* be destroyed by sixtp_context_destroy. */
|
|
break;
|
|
}
|
|
|
|
*stack = sixtp_pop_and_destroy_frame (*stack);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gnc_bad_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)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
sixtp_parse_file_common (sixtp* sixtp,
|
|
xmlParserCtxtPtr xml_context,
|
|
gpointer data_for_top_level,
|
|
gpointer global_data,
|
|
gpointer* parse_result)
|
|
{
|
|
sixtp_parser_context* ctxt;
|
|
int parse_ret;
|
|
|
|
if (! (ctxt = sixtp_context_new (sixtp, global_data, data_for_top_level)))
|
|
{
|
|
g_critical ("sixtp_context_new returned null");
|
|
return FALSE;
|
|
}
|
|
|
|
ctxt->data.saxParserCtxt = xml_context;
|
|
ctxt->data.saxParserCtxt->sax = &ctxt->handler;
|
|
ctxt->data.saxParserCtxt->userData = &ctxt->data;
|
|
ctxt->data.bad_xml_parser = sixtp_dom_parser_new (gnc_bad_xml_end_handler,
|
|
NULL, NULL);
|
|
parse_ret = xmlParseDocument (ctxt->data.saxParserCtxt);
|
|
//xmlSAXUserParseFile(&ctxt->handler, &ctxt->data, filename);
|
|
|
|
sixtp_context_run_end_handler (ctxt);
|
|
|
|
if (parse_ret == 0 && ctxt->data.parsing_ok)
|
|
{
|
|
if (parse_result)
|
|
*parse_result = ctxt->top_frame->frame_data;
|
|
sixtp_context_destroy (ctxt);
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
if (parse_result)
|
|
*parse_result = NULL;
|
|
if (g_slist_length (ctxt->data.stack) > 1)
|
|
sixtp_handle_catastrophe (&ctxt->data);
|
|
sixtp_context_destroy (ctxt);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
sixtp_parse_file (sixtp* sixtp,
|
|
const char* filename,
|
|
gpointer data_for_top_level,
|
|
gpointer global_data,
|
|
gpointer* parse_result)
|
|
{
|
|
gboolean ret;
|
|
xmlParserCtxtPtr context;
|
|
|
|
#ifdef G_OS_WIN32
|
|
{
|
|
gchar* conv_name = g_win32_locale_filename_from_utf8 (filename);
|
|
if (!conv_name)
|
|
{
|
|
PWARN ("Could not convert '%s' to system codepage", filename);
|
|
return FALSE;
|
|
}
|
|
context = xmlCreateFileParserCtxt (conv_name);
|
|
g_free (conv_name);
|
|
}
|
|
#else
|
|
context = xmlCreateFileParserCtxt (filename);
|
|
#endif
|
|
ret = sixtp_parse_file_common (sixtp, context, data_for_top_level,
|
|
global_data, parse_result);
|
|
return ret;
|
|
}
|
|
|
|
/* Call back function for libxml2 to read from compressed or uncompressed stream */
|
|
static int
|
|
sixtp_parser_read (void* context, char* buffer, int len)
|
|
{
|
|
int ret;
|
|
|
|
ret = fread (&buffer[0], sizeof (char), len, (FILE*) context);
|
|
if (ret < 0)
|
|
PWARN ("Error reading XML file");
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
sixtp_parse_fd (sixtp* sixtp,
|
|
FILE* fd,
|
|
gpointer data_for_top_level,
|
|
gpointer global_data,
|
|
gpointer* parse_result)
|
|
{
|
|
gboolean ret;
|
|
xmlParserCtxtPtr context = xmlCreateIOParserCtxt (NULL, NULL,
|
|
sixtp_parser_read, NULL /*no close */, fd,
|
|
XML_CHAR_ENCODING_NONE);
|
|
ret = sixtp_parse_file_common (sixtp, context, data_for_top_level,
|
|
global_data, parse_result);
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
sixtp_parse_buffer (sixtp* sixtp,
|
|
char* bufp,
|
|
int bufsz,
|
|
gpointer data_for_top_level,
|
|
gpointer global_data,
|
|
gpointer* parse_result)
|
|
{
|
|
gboolean ret;
|
|
xmlParserCtxtPtr context = xmlCreateMemoryParserCtxt (bufp, bufsz);
|
|
ret = sixtp_parse_file_common (sixtp, context, data_for_top_level,
|
|
global_data, parse_result);
|
|
return ret;
|
|
}
|
|
|
|
gboolean
|
|
sixtp_parse_push (sixtp* sixtp,
|
|
sixtp_push_handler push_handler,
|
|
gpointer push_user_data,
|
|
gpointer data_for_top_level,
|
|
gpointer global_data,
|
|
gpointer* parse_result)
|
|
{
|
|
sixtp_parser_context* ctxt;
|
|
xmlParserCtxtPtr xml_context;
|
|
|
|
if (!push_handler)
|
|
{
|
|
g_critical ("No push handler specified");
|
|
return FALSE;
|
|
}
|
|
|
|
if (! (ctxt = sixtp_context_new (sixtp, global_data, data_for_top_level)))
|
|
{
|
|
g_critical ("sixtp_context_new returned null");
|
|
return FALSE;
|
|
}
|
|
|
|
xml_context = xmlCreatePushParserCtxt (&ctxt->handler, &ctxt->data,
|
|
NULL, 0, NULL);
|
|
ctxt->data.saxParserCtxt = xml_context;
|
|
ctxt->data.bad_xml_parser = sixtp_dom_parser_new (gnc_bad_xml_end_handler,
|
|
NULL, NULL);
|
|
|
|
(*push_handler) (xml_context, push_user_data);
|
|
|
|
sixtp_context_run_end_handler (ctxt);
|
|
|
|
if (ctxt->data.parsing_ok)
|
|
{
|
|
if (parse_result)
|
|
*parse_result = ctxt->top_frame->frame_data;
|
|
sixtp_context_destroy (ctxt);
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
if (parse_result)
|
|
*parse_result = NULL;
|
|
if (g_slist_length (ctxt->data.stack) > 1)
|
|
sixtp_handle_catastrophe (&ctxt->data);
|
|
sixtp_context_destroy (ctxt);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/***********************************************************************/
|
|
static gboolean
|
|
eat_whitespace (char** cursor)
|
|
{
|
|
while (**cursor && isspace (**cursor))
|
|
{
|
|
(*cursor)++;
|
|
}
|
|
|
|
if (**cursor == '\0')
|
|
{
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
search_for (unsigned char marker, char** cursor)
|
|
{
|
|
while (**cursor &&** cursor != marker)
|
|
{
|
|
(*cursor)++;
|
|
}
|
|
|
|
if (**cursor == '\0')
|
|
{
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
(*cursor)++;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
QofBookFileType
|
|
gnc_is_our_xml_file (const char* filename, gboolean* with_encoding)
|
|
{
|
|
FILE* f = NULL;
|
|
char first_chunk[256];
|
|
ssize_t num_read;
|
|
|
|
g_return_val_if_fail (filename, GNC_BOOK_NOT_OURS);
|
|
|
|
f = g_fopen (filename, "r");
|
|
if (f == NULL)
|
|
{
|
|
return GNC_BOOK_NOT_OURS;
|
|
}
|
|
|
|
num_read = fread (first_chunk, sizeof (char), sizeof (first_chunk) - 1, f);
|
|
fclose (f);
|
|
|
|
if (num_read == 0)
|
|
{
|
|
return GNC_BOOK_NOT_OURS;
|
|
}
|
|
|
|
first_chunk[num_read] = '\0';
|
|
|
|
return gnc_is_our_first_xml_chunk (first_chunk, with_encoding);
|
|
}
|
|
|
|
QofBookFileType
|
|
gnc_is_our_first_xml_chunk (char* chunk, gboolean* with_encoding)
|
|
{
|
|
char* cursor = NULL;
|
|
size_t n;
|
|
|
|
if (with_encoding)
|
|
{
|
|
*with_encoding = FALSE;
|
|
}
|
|
|
|
cursor = chunk;
|
|
|
|
if (!eat_whitespace (&cursor))
|
|
{
|
|
return GNC_BOOK_NOT_OURS;
|
|
}
|
|
|
|
if (strncmp (cursor, "<?xml", 5) == 0)
|
|
{
|
|
if (!search_for ('>', &cursor))
|
|
{
|
|
return GNC_BOOK_NOT_OURS;
|
|
}
|
|
|
|
if (!eat_whitespace (&cursor))
|
|
{
|
|
return GNC_BOOK_NOT_OURS;
|
|
}
|
|
|
|
if (*cursor != '<')
|
|
{
|
|
return GNC_BOOK_NOT_OURS;
|
|
}
|
|
|
|
n = strlen (gnc_v2_xml_version_string);
|
|
if ((strncmp (cursor + 1, gnc_v2_xml_version_string, n) == 0)
|
|
&& isspace (* (cursor + 1 + n)))
|
|
{
|
|
if (with_encoding)
|
|
{
|
|
*cursor = '\0';
|
|
cursor = chunk;
|
|
while (search_for ('e', &cursor))
|
|
{
|
|
if (strncmp (cursor, "ncoding=", 8) == 0)
|
|
{
|
|
*with_encoding = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return GNC_BOOK_XML2_FILE;
|
|
}
|
|
|
|
if (strncmp (cursor, "<gnc>", strlen ("<gnc>")) == 0)
|
|
return GNC_BOOK_XML1_FILE;
|
|
|
|
/* If it doesn't match any of the above but has '<gnc-v...', it must */
|
|
/* be a later version */
|
|
if (strncmp (cursor, "<gnc-v", strlen ("<gnc-v")) == 0)
|
|
return GNC_BOOK_POST_XML2_0_0_FILE;
|
|
|
|
return GNC_BOOK_NOT_OURS;
|
|
}
|
|
|
|
return GNC_BOOK_NOT_OURS;
|
|
}
|
|
|
|
void
|
|
sixtp_run_callback (sixtp_gdv2* data, const char* type)
|
|
{
|
|
if (data->countCallback)
|
|
{
|
|
data->countCallback (data, type);
|
|
}
|
|
}
|
|
|
|
/************************* END OF FILE *********************************/
|