/********************************************************************
 * gnc-schedxactions-xml-v2.c -- xml routines for transactions      *
 * Copyright (C) 2001,2007 Joshua Sled <jsled@asynchronous.org>     *
 *                                                                  *
 * 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 <config.h>
#include <string.h>

#include "SX-book.h"

#include "gnc-xml-helper.h"
#include "sixtp.h"
#include "sixtp-utils.h"
#include "sixtp-parsers.h"
#include "sixtp-utils.h"
#include "sixtp-dom-parsers.h"
#include "sixtp-dom-generators.h"

#include "gnc-xml.h"

#include "io-gncxml-v2.h"
#include "io-gncxml-gen.h"

#include "sixtp-dom-parsers.h"

#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "gnc.backend.file.sx"
static const QofLogModule log_module = G_LOG_DOMAIN;

#define SX_ID                   "sx:id"
#define SX_NAME                 "sx:name"
#define SX_ENABLED              "sx:enabled"
#define SX_AUTOCREATE           "sx:autoCreate"
#define SX_AUTOCREATE_NOTIFY    "sx:autoCreateNotify"
#define SX_ADVANCE_CREATE_DAYS  "sx:advanceCreateDays"
#define SX_ADVANCE_REMIND_DAYS  "sx:advanceRemindDays"
#define SX_INSTANCE_COUNT       "sx:instanceCount"
#define SX_START                "sx:start"
#define SX_LAST                 "sx:last"
#define SX_NUM_OCCUR            "sx:num-occur"
#define SX_REM_OCCUR            "sx:rem-occur"
#define SX_END                  "sx:end"
#define SX_TEMPL_ACCT           "sx:templ-acct"
#define SX_FREQSPEC             "sx:freqspec"
#define SX_SCHEDULE             "sx:schedule"
#define SX_SLOTS                "sx:slots"
#define SX_DEFER_INSTANCE       "sx:deferredInstance"

/*
 * FIXME: These should be defined in a header somewhere
 */

#define GNC_ACCOUNT_TAG         "gnc:account"
#define GNC_TRANSACTION_TAG     "gnc:transaction"
#define GNC_SCHEDXACTION_TAG    "gnc:schedxaction"

const gchar* schedxaction_version_string = "1.0.0";
const gchar* schedxaction_version2_string = "2.0.0";

xmlNodePtr
gnc_schedXaction_dom_tree_create (SchedXaction* sx)
{
    xmlNodePtr  ret;
    const GDate* date;
    gint        instCount;
    const GncGUID*        templ_acc_guid;
    gboolean allow_2_2_incompat = TRUE;
    gchar* name = g_strdup (xaccSchedXactionGetName (sx));

    templ_acc_guid = xaccAccountGetGUID (sx->template_acct);

    /* FIXME: this should be the same as the def in io-gncxml-v2.c */
    ret = xmlNewNode (NULL, BAD_CAST GNC_SCHEDXACTION_TAG);

    if (allow_2_2_incompat)
        xmlSetProp (ret, BAD_CAST "version", BAD_CAST schedxaction_version2_string);
    else
        xmlSetProp (ret, BAD_CAST "version", BAD_CAST schedxaction_version_string);

    xmlAddChild (ret,
                 guid_to_dom_tree (SX_ID,
                                   xaccSchedXactionGetGUID (sx)));

    xmlNewTextChild (ret, NULL, BAD_CAST SX_NAME, checked_char_cast (name));
    g_free (name);

    if (allow_2_2_incompat)
    {
        xmlNewTextChild (ret, NULL, BAD_CAST SX_ENABLED,
                         BAD_CAST (sx->enabled ? "y" : "n"));
    }

    xmlNewTextChild (ret, NULL, BAD_CAST SX_AUTOCREATE,
                     BAD_CAST (sx->autoCreateOption ? "y" : "n"));
    xmlNewTextChild (ret, NULL, BAD_CAST SX_AUTOCREATE_NOTIFY,
                     BAD_CAST (sx->autoCreateNotify ? "y" : "n"));
    xmlAddChild (ret, int_to_dom_tree (SX_ADVANCE_CREATE_DAYS,
                                       sx->advanceCreateDays));
    xmlAddChild (ret, int_to_dom_tree (SX_ADVANCE_REMIND_DAYS,
                                       sx->advanceRemindDays));

    instCount = gnc_sx_get_instance_count (sx, NULL);
    xmlAddChild (ret, int_to_dom_tree (SX_INSTANCE_COUNT,
                                       instCount));

    xmlAddChild (ret,
                 gdate_to_dom_tree (SX_START,
                                    xaccSchedXactionGetStartDate (sx)));

    date = xaccSchedXactionGetLastOccurDate (sx);
    if (g_date_valid (date))
    {
        xmlAddChild (ret, gdate_to_dom_tree (SX_LAST, date));
    }

    if (xaccSchedXactionHasOccurDef (sx))
    {

        xmlAddChild (ret, int_to_dom_tree (SX_NUM_OCCUR,
                                           xaccSchedXactionGetNumOccur (sx)));
        xmlAddChild (ret, int_to_dom_tree (SX_REM_OCCUR,
                                           xaccSchedXactionGetRemOccur (sx)));

    }
    else if (xaccSchedXactionHasEndDate (sx))
    {
        xmlAddChild (ret,
                     gdate_to_dom_tree (SX_END,
                                        xaccSchedXactionGetEndDate (sx)));
    }

    /* output template account GncGUID */
    xmlAddChild (ret,
                 guid_to_dom_tree (SX_TEMPL_ACCT,
                                   templ_acc_guid));

    if (allow_2_2_incompat)
    {
        xmlNodePtr schedule_node = xmlNewNode (NULL,
                                               BAD_CAST "sx:schedule");
        GList* schedule = gnc_sx_get_schedule (sx);
        for (; schedule != NULL; schedule = schedule->next)
        {
            xmlAddChild (schedule_node, recurrence_to_dom_tree ("gnc:recurrence",
                                                                (Recurrence*)schedule->data));
        }
        xmlAddChild (ret, schedule_node);
    }

    /* Output deferred-instance list. */
    {
        xmlNodePtr instNode;
        SXTmpStateData* tsd;
        GList* l;

        for (l = gnc_sx_get_defer_instances (sx); l; l = l->next)
        {
            tsd = (SXTmpStateData*)l->data;

            instNode = xmlNewNode (NULL, BAD_CAST SX_DEFER_INSTANCE);
            if (g_date_valid (&tsd->last_date))
            {
                xmlAddChild (instNode, gdate_to_dom_tree (SX_LAST,
                                                          &tsd->last_date));
            }
            xmlAddChild (instNode, int_to_dom_tree (SX_REM_OCCUR,
                                                    tsd->num_occur_rem));
            xmlAddChild (instNode, int_to_dom_tree (SX_INSTANCE_COUNT,
                                                    tsd->num_inst));
            xmlAddChild (ret, instNode);
        }
    }

    /* xmlAddChild won't do anything with a NULL, so tests are superfluous. */
    xmlAddChild (ret, qof_instance_slots_to_dom_tree (SX_SLOTS,
                                                      QOF_INSTANCE (sx)));
    return ret;
}

struct sx_pdata
{
    SchedXaction* sx;
    QofBook* book;
    gboolean saw_freqspec;
    gboolean saw_recurrence;
};

static
gboolean
sx_id_handler (xmlNodePtr node, gpointer sx_pdata)
{
    struct sx_pdata* pdata = static_cast<decltype (pdata)> (sx_pdata);
    SchedXaction* sx = pdata->sx;
    GncGUID*        tmp = dom_tree_to_guid (node);

    g_return_val_if_fail (tmp, FALSE);
    xaccSchedXactionSetGUID (sx, tmp);
    guid_free (tmp);

    return TRUE;
}

static
gboolean
sx_name_handler (xmlNodePtr node, gpointer sx_pdata)
{
    struct sx_pdata* pdata = static_cast<decltype (pdata)> (sx_pdata);
    SchedXaction* sx = pdata->sx;
    gchar* tmp = dom_tree_to_text (node);
    DEBUG ("sx named [%s]", tmp);
    g_return_val_if_fail (tmp, FALSE);
    xaccSchedXactionSetName (sx, tmp);
    g_free (tmp);
    return TRUE;
}

static gboolean
sx_enabled_handler (xmlNodePtr node, gpointer sx_pdata)
{
    struct sx_pdata* pdata = static_cast<decltype (pdata)> (sx_pdata);
    SchedXaction* sx = pdata->sx;
    gchar* tmp = dom_tree_to_text (node);

    sx->enabled = (g_strcmp0 (tmp, "y") == 0 ? TRUE : FALSE);
    g_free (tmp);

    return TRUE;
}

static gboolean
sx_autoCreate_handler (xmlNodePtr node, gpointer sx_pdata)
{
    struct sx_pdata* pdata = static_cast<decltype (pdata)> (sx_pdata);
    SchedXaction* sx = pdata->sx;
    gchar* tmp = dom_tree_to_text (node);

    sx->autoCreateOption = (g_strcmp0 (tmp, "y") == 0 ? TRUE : FALSE);
    g_free (tmp);

    return TRUE;
}

static gboolean
sx_notify_handler (xmlNodePtr node, gpointer sx_pdata)
{
    struct sx_pdata* pdata = static_cast<decltype (pdata)> (sx_pdata);
    SchedXaction* sx = pdata->sx;
    gchar* tmp = dom_tree_to_text (node);

    sx->autoCreateNotify = (g_strcmp0 (tmp, "y") == 0 ? TRUE : FALSE);
    g_free (tmp);

    return TRUE;
}

static gboolean
sx_advCreate_handler (xmlNodePtr node, gpointer sx_pdata)
{
    struct sx_pdata* pdata = static_cast<decltype (pdata)> (sx_pdata);
    SchedXaction* sx = pdata->sx;
    gint64 advCreate;

    if (! dom_tree_to_integer (node, &advCreate))
    {
        return FALSE;
    }

    xaccSchedXactionSetAdvanceCreation (sx, advCreate);
    return TRUE;
}

static gboolean
sx_advRemind_handler (xmlNodePtr node, gpointer sx_pdata)
{
    struct sx_pdata* pdata = static_cast<decltype (pdata)> (sx_pdata);
    SchedXaction* sx = pdata->sx;
    gint64 advRemind;

    if (! dom_tree_to_integer (node, &advRemind))
    {
        return FALSE;
    }

    xaccSchedXactionSetAdvanceReminder (sx, advRemind);
    return TRUE;
}

static
gboolean
sx_set_date (xmlNodePtr node, SchedXaction* sx,
             void (*settor) (SchedXaction* sx, const GDate* d))
{
    GDate* date;
    date = dom_tree_to_gdate (node);
    g_return_val_if_fail (date, FALSE);
    (*settor) (sx, date);
    g_date_free (date);

    return TRUE;
}

static
gboolean
sx_instcount_handler (xmlNodePtr node, gpointer sx_pdata)
{
    struct sx_pdata* pdata = static_cast<decltype (pdata)> (sx_pdata);
    SchedXaction* sx = pdata->sx;
    gint64 instanceNum;

    if (! dom_tree_to_integer (node, &instanceNum))
    {
        return FALSE;
    }

    gnc_sx_set_instance_count (sx, instanceNum);
    return TRUE;
}

static
gboolean
sx_start_handler (xmlNodePtr node, gpointer sx_pdata)
{
    struct sx_pdata* pdata = static_cast<decltype (pdata)> (sx_pdata);
    SchedXaction* sx = pdata->sx;

    return sx_set_date (node, sx, xaccSchedXactionSetStartDate);
}

static
gboolean
sx_last_handler (xmlNodePtr node, gpointer sx_pdata)
{
    struct sx_pdata* pdata = static_cast<decltype (pdata)> (sx_pdata);
    SchedXaction* sx = pdata->sx;

    return sx_set_date (node, sx, xaccSchedXactionSetLastOccurDate);
}

static
gboolean
sx_end_handler (xmlNodePtr node, gpointer sx_pdata)
{
    struct sx_pdata* pdata = static_cast<decltype (pdata)> (sx_pdata);
    SchedXaction* sx = pdata->sx;

    return sx_set_date (node, sx, xaccSchedXactionSetEndDate);
}

static void
_fixup_recurrence_start_dates (const GDate* sx_start_date, GList* schedule)
{
    GList* iter;
    for (iter = schedule; iter != NULL; iter = iter->next)
    {
        Recurrence* r;
        GDate start, next;

        r = (Recurrence*)iter->data;

        start = *sx_start_date;
        g_date_subtract_days (&start, 1);

        g_date_clear (&next, 1);

        recurrenceNextInstance (r, &start, &next);
        g_return_if_fail (g_date_valid (&next));

        {
            gchar date_str[128];
            gchar* sched_str;

            g_date_strftime (date_str, 127, "%x", &next);
            sched_str = recurrenceToString (r);
            DEBUG ("setting recurrence [%s] start date to [%s]",
                     sched_str, date_str);
            g_free (sched_str);
        }

        recurrenceSet (r,
                       recurrenceGetMultiplier (r),
                       recurrenceGetPeriodType (r),
                       &next,
                       recurrenceGetWeekendAdjust (r));
    }

    if (g_list_length (schedule) == 1
        && recurrenceGetPeriodType ((Recurrence*)g_list_nth_data (schedule,
                                    0)) == PERIOD_ONCE)
    {
        char date_buf[128];
        Recurrence* fixup = (Recurrence*)g_list_nth_data (schedule, 0);
        g_date_strftime (date_buf, 127, "%x", sx_start_date);
        recurrenceSet (fixup, 1, PERIOD_ONCE, sx_start_date, WEEKEND_ADJ_NONE);
        DEBUG ("fixed up period=ONCE Recurrence to date [%s]", date_buf);
    }
}

static gboolean
sx_freqspec_handler (xmlNodePtr node, gpointer sx_pdata)
{
    struct sx_pdata* pdata = static_cast<decltype (pdata)> (sx_pdata);
    SchedXaction* sx = pdata->sx;
    GList* schedule;
    gchar* debug_str;

    g_return_val_if_fail (node, FALSE);

    schedule = dom_tree_freqSpec_to_recurrences (node, pdata->book);
    gnc_sx_set_schedule (sx, schedule);
    debug_str = recurrenceListToString (schedule);
    DEBUG ("parsed from freqspec [%s]", debug_str);
    g_free (debug_str);

    _fixup_recurrence_start_dates (xaccSchedXactionGetStartDate (sx), schedule);
    pdata->saw_freqspec = TRUE;

    return TRUE;
}

static gboolean
sx_schedule_recurrence_handler (xmlNodePtr node, gpointer parsing_data)
{
    GList** schedule = (GList**)parsing_data;
    gchar* sched_str;
    Recurrence* r = dom_tree_to_recurrence (node);
    g_return_val_if_fail (r, FALSE);
    sched_str = recurrenceToString (r);
    DEBUG ("parsed recurrence [%s]", sched_str);
    g_free (sched_str);
    *schedule = g_list_append (*schedule, r);
    return TRUE;
}

struct dom_tree_handler sx_recurrence_list_handlers[] =
{
    { "gnc:recurrence", sx_schedule_recurrence_handler, 0, 0 },
    { NULL, NULL, 0, 0 }
};

static gboolean
sx_recurrence_handler (xmlNodePtr node, gpointer _pdata)
{
    struct sx_pdata* parsing_data = static_cast<decltype (parsing_data)> (_pdata);
    GList* schedule = NULL;
    gchar* debug_str;

    g_return_val_if_fail (node, FALSE);

    if (!dom_tree_generic_parse (node, sx_recurrence_list_handlers, &schedule))
        return FALSE;
    // g_return_val_if_fail(schedule, FALSE);
    debug_str = recurrenceListToString (schedule);
    DEBUG ("setting freshly-parsed schedule: [%s]", debug_str);
    g_free (debug_str);
    gnc_sx_set_schedule (parsing_data->sx, schedule);
    parsing_data->saw_recurrence = TRUE;
    return TRUE;
}

static
gboolean
sx_defer_last_handler (xmlNodePtr node, gpointer gpTSD)
{
    GDate* gd;
    SXTmpStateData* tsd = (SXTmpStateData*)gpTSD;

    g_return_val_if_fail (node, FALSE);
    gd = dom_tree_to_gdate (node);
    g_return_val_if_fail (gd, FALSE);
    tsd->last_date = *gd;
    g_date_free (gd);
    return TRUE;
}

static
gboolean
sx_defer_rem_occur_handler (xmlNodePtr node, gpointer gpTSD)
{
    gint64 remOccur;
    SXTmpStateData* tsd = (SXTmpStateData*)gpTSD;
    g_return_val_if_fail (node, FALSE);

    if (! dom_tree_to_integer (node, &remOccur))
    {
        return FALSE;
    }
    tsd->num_occur_rem = remOccur;
    return TRUE;
}

static
gboolean
sx_defer_inst_count_handler (xmlNodePtr node, gpointer gpTSD)
{
    gint64 instCount;
    SXTmpStateData* tsd = (SXTmpStateData*)gpTSD;
    g_return_val_if_fail (node, FALSE);

    if (! dom_tree_to_integer (node, &instCount))
    {
        return FALSE;
    }
    tsd->num_inst = instCount;
    return TRUE;
}

struct dom_tree_handler sx_defer_dom_handlers[] =
{
    /* tag name, handler, opt, ? */
    { SX_LAST,           sx_defer_last_handler,       1, 0 },
    { SX_REM_OCCUR,      sx_defer_rem_occur_handler,  1, 0 },
    { SX_INSTANCE_COUNT, sx_defer_inst_count_handler, 1, 0 },
    { NULL, NULL, 0, 0 }
};

static
gboolean
sx_defer_inst_handler (xmlNodePtr node, gpointer sx_pdata)
{
    struct sx_pdata* pdata = static_cast<decltype (pdata)> (sx_pdata);
    SchedXaction* sx = pdata->sx;
    SXTmpStateData* tsd;

    g_return_val_if_fail (node, FALSE);

    tsd = g_new0 (SXTmpStateData, 1);
    g_assert (sx_defer_dom_handlers != NULL);
    if (!dom_tree_generic_parse (node,
                                 sx_defer_dom_handlers,
                                 tsd))
    {
        xmlElemDump (stdout, NULL, node);
        g_free (tsd);
        tsd = NULL;
        return FALSE;
    }

    /* We assume they were serialized in sorted order, here. */
    sx->deferredList = g_list_append (sx->deferredList, tsd);
    return TRUE;
}

static
gboolean
sx_numOccur_handler (xmlNodePtr node, gpointer sx_pdata)
{
    struct sx_pdata* pdata = static_cast<decltype (pdata)> (sx_pdata);
    SchedXaction* sx = pdata->sx;
    gint64 numOccur;

    if (! dom_tree_to_integer (node, &numOccur))
    {
        return FALSE;
    }

    xaccSchedXactionSetNumOccur (sx, numOccur);

    return TRUE;
}


static
gboolean
sx_templ_acct_handler (xmlNodePtr node, gpointer sx_pdata)
{
    struct sx_pdata* pdata = static_cast<decltype (pdata)> (sx_pdata);
    SchedXaction* sx = pdata->sx;
    GncGUID* templ_acct_guid = dom_tree_to_guid (node);
    Account* account;

    if (!templ_acct_guid)
    {
        return FALSE;
    }

    account = xaccAccountLookup (templ_acct_guid, pdata->book);
    sx_set_template_account (sx, account);
    guid_free (templ_acct_guid);

    return TRUE;
}


static
gboolean
sx_remOccur_handler (xmlNodePtr node, gpointer sx_pdata)
{
    struct sx_pdata* pdata = static_cast<decltype (pdata)> (sx_pdata);
    SchedXaction* sx = pdata->sx;
    gint64        remOccur;

    if (! dom_tree_to_integer (node, &remOccur))
    {
        return FALSE;
    }

    xaccSchedXactionSetRemOccur (sx, remOccur);

    return TRUE;
}

static
gboolean
sx_slots_handler (xmlNodePtr node, gpointer sx_pdata)
{
    struct sx_pdata* pdata = static_cast<decltype (pdata)> (sx_pdata);
    SchedXaction* sx = pdata->sx;

    return dom_tree_create_instance_slots (node, QOF_INSTANCE (sx));
}

struct dom_tree_handler sx_dom_handlers[] =
{
    { SX_ID,                  sx_id_handler,         1, 0 },
    { SX_NAME,                sx_name_handler,       1, 0 },
    { SX_ENABLED,             sx_enabled_handler,    0, 0 },
    { SX_AUTOCREATE,          sx_autoCreate_handler, 1, 0 },
    { SX_AUTOCREATE_NOTIFY,   sx_notify_handler,     1, 0 },
    { SX_ADVANCE_CREATE_DAYS, sx_advCreate_handler,  1, 0 },
    { SX_ADVANCE_REMIND_DAYS, sx_advRemind_handler,  1, 0 },
    { SX_INSTANCE_COUNT,      sx_instcount_handler,  0, 0 },
    { SX_START,               sx_start_handler,      1, 0 },
    { SX_LAST,                sx_last_handler,       0, 0 },
    { SX_NUM_OCCUR,           sx_numOccur_handler,   0, 0 },
    { SX_REM_OCCUR,           sx_remOccur_handler,   0, 0 },
    { SX_END,                 sx_end_handler,        0, 0 },
    { SX_TEMPL_ACCT,          sx_templ_acct_handler, 0, 0 },
    { SX_FREQSPEC,            sx_freqspec_handler,   0, 0 },
    { SX_SCHEDULE,            sx_recurrence_handler, 0, 0 },
    { SX_DEFER_INSTANCE,      sx_defer_inst_handler, 0, 0 },
    { SX_SLOTS,               sx_slots_handler,      0, 0 },
    { NULL,                   NULL, 0, 0 }
};

static gboolean
gnc_schedXaction_end_handler (gpointer data_for_children,
                              GSList* data_from_children, GSList* sibling_data,
                              gpointer parent_data, gpointer global_data,
                              gpointer* result, const gchar* tag)
{
    SchedXaction* sx;
    gboolean     successful = FALSE;
    xmlNodePtr   tree = (xmlNodePtr)data_for_children;
    gxpf_data*    gdata = (gxpf_data*)global_data;
    struct sx_pdata sx_pdata;

    if (parent_data)
    {
        return TRUE;
    }

    if (!tag)
    {
        return TRUE;
    }

    g_return_val_if_fail (tree, FALSE);

    sx = xaccSchedXactionMalloc (static_cast<QofBook*> (gdata->bookdata));

    memset (&sx_pdata, 0, sizeof (sx_pdata));
    sx_pdata.sx = sx;
    sx_pdata.book = static_cast<decltype (sx_pdata.book)> (gdata->bookdata);

    g_assert (sx_dom_handlers != NULL);

    successful = dom_tree_generic_parse (tree, sx_dom_handlers, &sx_pdata);
    if (!successful)
    {
        g_critical ("failed to parse scheduled xaction");
        xmlElemDump (stdout, NULL, tree);
        gnc_sx_begin_edit (sx);
        xaccSchedXactionDestroy (sx);
        goto done;
    }

    if (tree->properties)
    {
        gchar* sx_name = xaccSchedXactionGetName (sx);
        xmlAttr* attr;
        for (attr = tree->properties; attr != NULL; attr = attr->next)
        {
            xmlChar* attr_value = attr->children->content;
            DEBUG ("sx attribute name[%s] value[%s]", attr->name, attr_value);
            if (strcmp ((const char*)attr->name, "version") != 0)
            {
                g_warning ("unknown sx attribute [%s]", attr->name);
                continue;
            }

            // if version == 1.0.0: ensure freqspec, no recurrence
            // if version == 2.0.0: ensure recurrence, no freqspec.
            if (strcmp ((const char*)attr_value,
                        schedxaction_version_string) == 0)
            {
                if (!sx_pdata.saw_freqspec)
                    g_critical ("did not see freqspec in version 1 sx [%s]", sx_name);
                if (sx_pdata.saw_recurrence)
                    g_warning ("saw recurrence in supposedly version 1 sx [%s]", sx_name);
            }

            if (strcmp ((const char*)attr_value,
                        schedxaction_version2_string) == 0)
            {
                if (sx_pdata.saw_freqspec)
                    g_warning ("saw freqspec in version 2 sx [%s]", sx_name);
                if (!sx_pdata.saw_recurrence)
                    g_critical ("did not find recurrence in version 2 sx [%s]", sx_name);
            }
        }
    }

    // generic_callback -> book_callback: insert the SX in the book
    gdata->cb (tag, gdata->parsedata, sx);

    /* FIXME: this should be removed somewhere near 1.8 release time. */
    if (sx->template_acct == NULL)
    {
        Account* ra = NULL;
        Account* acct = NULL;
        sixtp_gdv2* sixdata = static_cast<decltype (sixdata)> (gdata->parsedata);
        QofBook* book;
        gchar guidstr[GUID_ENCODING_LENGTH + 1];

        book = sixdata->book;

        /* We're dealing with a pre-200107<near-end-of-month> rgmerk
           change re: storing template accounts. */
        /* Fix: get account with name of our GncGUID from the template
           accounts.  Make that our template_acct pointer. */
        guid_to_string_buff (xaccSchedXactionGetGUID (sx), guidstr);
        ra = gnc_book_get_template_root (book);
        if (ra == NULL)
        {
            g_warning ("Error getting template root account from being-parsed Book.");
            xmlFreeNode (tree);
            return FALSE;
        }
        acct = gnc_account_lookup_by_name (ra, guidstr);
        if (acct == NULL)
        {
            g_warning ("no template account with name [%s]", guidstr);
            xmlFreeNode (tree);
            return FALSE;
        }
        DEBUG ("template account name [%s] for SX with GncGUID [%s]",
                 xaccAccountGetName (acct), guidstr);

        /* FIXME: free existing template account.
         *  HUH????? We only execute this if there isn't
         * currently an existing template account, don't we?
         * <rgmerk>
         */

        sx->template_acct = acct;
    }

done:
    xmlFreeNode (tree);

    return successful;
}

sixtp*
gnc_schedXaction_sixtp_parser_create (void)
{
    return sixtp_dom_parser_new (gnc_schedXaction_end_handler, NULL, NULL);
}

static
gboolean
tt_act_handler (xmlNodePtr node, gpointer data)
{
    gnc_template_xaction_data* txd = static_cast<decltype (txd)> (data);
    Account* acc;
    gnc_commodity* com;

    acc = dom_tree_to_account (node, txd->book);

    if (acc == NULL)
    {
        return FALSE;
    }
    else
    {
        xaccAccountBeginEdit (acc);

        /* Check for the lack of a commodity [signifying that the
           pre-7/11/2001-CIT-change SX template Account was parsed [but
           incorrectly]. */
        if (xaccAccountGetCommodity (acc) == NULL)
        {
#if 1
            gnc_commodity_table* table;

            table = gnc_commodity_table_get_table (txd->book);
            com = gnc_commodity_table_lookup (table,
                                              GNC_COMMODITY_NS_TEMPLATE, "template");
#else
            /* FIXME: This should first look in the table of the
               book, maybe? The right thing happens [WRT file
               load/save] if we just _new all the time, but it
               doesn't seem right. This whole block should go
               away at some point, but the same concern still
               applies for
               SchedXaction.c:xaccSchedXactionInit... */
            com = gnc_commodity_new (txd->book,
                                     "template", GNC_COMMODITY_NS_TEMPLATE,
                                     "template", "template",
                                     1);
#endif
            xaccAccountSetCommodity (acc, com);
        }

        txd->accts = g_list_append (txd->accts, acc);
    }

    return TRUE;
}

static
gboolean
tt_trn_handler (xmlNodePtr node, gpointer data)
{
    gnc_template_xaction_data* txd = static_cast<decltype (txd)> (data);
    Transaction*        trn;

    trn = dom_tree_to_transaction (node, txd->book);

    if (trn == NULL)
    {
        return FALSE;
    }
    else
    {
        txd->transactions = g_list_append (txd->transactions, trn);
    }

    return TRUE;
}

struct dom_tree_handler tt_dom_handlers[] =
{
    { GNC_ACCOUNT_TAG,     tt_act_handler, 0, 0 },
    { GNC_TRANSACTION_TAG, tt_trn_handler, 0, 0 },
    { NULL, NULL, 0, 0 },
};

static gboolean
gnc_template_transaction_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   successful = FALSE;
    xmlNodePtr tree = static_cast<decltype (tree)> (data_for_children);
    gxpf_data*  gdata = static_cast<decltype (gdata)> (global_data);
    QofBook*    book = static_cast<decltype (book)> (gdata->bookdata);
    GList*      n;
    gnc_template_xaction_data txd;

    txd.book = book;
    txd.accts = NULL;
    txd.transactions = NULL;

    /* the DOM tree will have an account tree [the template
       accounts] and a list of transactions [which will be members
       of the template account].

       we want to parse through the dom trees for each, placing
       the null-parent account in the book's template-group slot,
       the others under it, and the transactions as normal. */

    if (parent_data)
    {
        return TRUE;
    }

    if (!tag)
    {
        return TRUE;
    }

    g_return_val_if_fail (tree, FALSE);

    successful = dom_tree_generic_parse (tree, tt_dom_handlers, &txd);

    if (successful)
    {
        gdata->cb (tag, gdata->parsedata, &txd);
    }
    else
    {
        g_warning ("failed to parse template transaction");
        xmlElemDump (stdout, NULL, tree);
    }

    /* cleanup */
    for (n = txd.accts; n; n = n->next)
    {
        n->data = NULL;
    }
    for (n = txd.transactions; n; n = n->next)
    {
        n->data = NULL;
    }
    g_list_free (txd.accts);
    g_list_free (txd.transactions);

    xmlFreeNode (tree);

    return successful;
}

sixtp*
gnc_template_transaction_sixtp_parser_create (void)
{
    return sixtp_dom_parser_new (gnc_template_transaction_end_handler,
                                 NULL, NULL);
}
