Files
gnucash/src/engine/Group.c
T
Dave Peticolas 654c4cb31e Move entity tables into sessions.
git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@5483 57a11ea4-9604-0410-9ed3-97b8803252fd
2001-10-10 00:21:34 +00:00

1181 lines
29 KiB
C

/********************************************************************\
* Group.c -- chart of accounts (hierarchical tree of accounts) *
* Copyright (C) 1997 Robin D. Clark *
* Copyright (C) 1997-2000 Linas Vepstas <linas@linas.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 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
#include "config.h"
#include <assert.h>
#include <glib.h>
#include <string.h>
#include "Account.h"
#include "AccountP.h"
#include "BackendP.h"
#include "GNCIdP.h"
#include "Group.h"
#include "GroupP.h"
#include "TransactionP.h"
#include "gnc-engine-util.h"
#include "gnc-event-p.h"
#include "gnc-numeric.h"
#include "gnc-session-p.h"
static short module = MOD_ENGINE;
/********************************************************************\
* Because I can't use C++ for this project, doesn't mean that I *
* can't pretend to! These functions perform actions on the *
* AccountGroup data structure, in order to encapsulate the *
* knowledge of the internals of the AccountGroup in one file. *
\********************************************************************/
/********************************************************************\
\********************************************************************/
static void
xaccInitializeAccountGroup (AccountGroup *grp, GNCEntityTable *entity_table)
{
grp->saved = 1;
grp->parent = NULL;
grp->accounts = NULL;
grp->backend = NULL;
grp->book = NULL;
grp->entity_table = entity_table;
}
/********************************************************************\
\********************************************************************/
static AccountGroup *
xaccMallocAccountGroupEntityTable (GNCEntityTable *entity_table)
{
AccountGroup *grp;
g_return_val_if_fail (entity_table, NULL);
grp = g_new (AccountGroup, 1);
xaccInitializeAccountGroup (grp, entity_table);
return grp;
}
AccountGroup *
xaccMallocAccountGroup (GNCSession *session)
{
g_return_val_if_fail (session, NULL);
return xaccMallocAccountGroupEntityTable
(gnc_session_get_entity_table (session));
}
/********************************************************************\
\********************************************************************/
gboolean
xaccGroupEqual(AccountGroup *ga,
AccountGroup *gb,
gboolean check_guids)
{
GList *na;
GList *nb;
if (!ga && !gb) return(TRUE);
if (!ga) return(FALSE);
if (!gb) return(FALSE);
na = ga->accounts;
nb = gb->accounts;
if (!na && nb) return(FALSE);
if (na && !nb) return(FALSE);
while (na && nb)
{
Account *aa = na->data;
Account *ab = nb->data;
if (!xaccAccountEqual(aa, ab, check_guids)) return(FALSE);
na = na->next;
nb = nb->next;
}
if (na) return(FALSE);
if (nb) return(FALSE);
return(TRUE);
}
/********************************************************************\
\********************************************************************/
void
xaccAccountGroupBeginEdit (AccountGroup *grp)
{
GList *node;
if (!grp) return;
for (node = grp->accounts; node; node = node->next)
{
Account *account = node->data;
xaccAccountBeginEdit (account);
xaccAccountGroupBeginEdit (account->children);
}
}
/********************************************************************\
\********************************************************************/
void
xaccAccountGroupCommitEdit (AccountGroup *grp)
{
GList *node;
if (!grp) return;
for (node = grp->accounts; node; node = node->next)
{
Account *account = node->data;
xaccAccountGroupCommitEdit (account->children);
xaccAccountCommitEdit (account);
}
}
/********************************************************************\
\********************************************************************/
void
xaccGroupMarkDoFree (AccountGroup *grp)
{
GList *node;
if (!grp) return;
for (node = grp->accounts; node; node = node->next)
{
Account *account = node->data;
account->do_free = TRUE;
xaccGroupMarkDoFree (account->children);
}
}
/********************************************************************\
\********************************************************************/
GNCBook *
xaccGroupGetBook (AccountGroup *group)
{
if (!group) return NULL;
return group->book;
}
void
xaccGroupSetBook (AccountGroup *group, GNCBook *book)
{
GList *node;
if (!group) return;
group->book = book;
for (node = group->accounts; node; node = node->next)
{
Account *account = node->data;
xaccGroupSetBook (account->children, book);
}
}
GNCBook *
xaccAccountGetBook (Account *account)
{
AccountGroup *group;
if (!account) return NULL;
group = xaccAccountGetParent (account);
return xaccGroupGetBook (group);
}
/********************************************************************\
\********************************************************************/
void
xaccFreeAccountGroup (AccountGroup *grp)
{
gboolean root_grp;
if (!grp) return;
root_grp = grp->parent == NULL;
if (grp->accounts)
{
Account *account;
/* This is a weird iterator & needs some explanation.
* xaccAccountDestroy() will rip the account out of the list, thus
* iterating while grp->accounts is non-null is enough to iterate
* the loop. But when it deletes the last account, then it will
* also delete the group, unless it's the root group, making the
* grp pointer invalid. So we have to be careful with the last
* deletion: in particular, g_free(grp) would be freeing that
* memory a second time, so don't do it. */
while (grp->accounts->next)
{
account = grp->accounts->next->data;
xaccAccountBeginEdit (account);
xaccAccountDestroy (account);
}
account = grp->accounts->data;
xaccAccountBeginEdit (account);
xaccAccountDestroy (account);
if (!root_grp) return;
}
if (grp->parent) grp->parent->children = NULL;
grp->parent = NULL;
g_free (grp);
}
/********************************************************************\
\********************************************************************/
void
xaccGroupMarkSaved (AccountGroup *grp)
{
GList *node;
if (!grp) return;
grp->saved = 1;
for (node = grp->accounts; node; node = node->next)
{
Account *account = node->data;
xaccGroupMarkSaved (account->children);
}
}
/********************************************************************\
\********************************************************************/
void
xaccGroupMarkNotSaved (AccountGroup *grp)
{
if (!grp) return;
grp->saved = 0;
}
/********************************************************************\
\********************************************************************/
gboolean
xaccGroupNotSaved (AccountGroup *grp)
{
GList *node;
if (!grp) return FALSE;
if (grp->saved == 0) return TRUE;
for (node = grp->accounts; node; node = node->next)
{
Account *account = node->data;
if (xaccGroupNotSaved (account->children))
return TRUE;
}
return FALSE;
}
/********************************************************************\
* Get the number of accounts, including subaccounts *
\********************************************************************/
int
xaccGroupGetNumSubAccounts (AccountGroup *grp)
{
GList *node;
int num_acc;
if (!grp) return 0;
num_acc = g_list_length (grp->accounts);
for (node = grp->accounts; node; node = node->next)
{
Account *account = node->data;
num_acc += xaccGroupGetNumSubAccounts (account->children);
}
return num_acc;
}
/********************************************************************\
* Get all of the accounts, including subaccounts *
\********************************************************************/
static void
xaccPrependAccounts (AccountGroup *grp, GList **accounts_p)
{
GList *node;
if (!grp || !accounts_p) return;
for (node = grp->accounts; node; node = node->next)
{
Account *account = node->data;
*accounts_p = g_list_prepend (*accounts_p, account);
xaccPrependAccounts (account->children, accounts_p);
}
}
GList *
xaccGroupGetSubAccounts (AccountGroup *grp)
{
GList *accounts = NULL;
if (!grp) return NULL;
xaccPrependAccounts (grp, &accounts);
return g_list_reverse (accounts);
}
GList *
xaccGroupGetAccountList (AccountGroup *grp)
{
if (!grp) return NULL;
return grp->accounts;
}
/********************************************************************\
* Fetch the root of the tree *
\********************************************************************/
AccountGroup *
xaccAccountGetRoot (Account * acc)
{
AccountGroup * grp;
AccountGroup * root = NULL;
if (!acc) return NULL;
/* find the root of the account group structure */
grp = acc->parent;
while (grp)
{
Account *parent_acc;
root = grp;
parent_acc = grp->parent;
if (parent_acc)
grp = parent_acc->parent;
else
grp = NULL;
}
return root;
}
/********************************************************************\
* Fetch an account, given its name *
\********************************************************************/
Account *
xaccGetAccountFromName (AccountGroup *grp, const char * name)
{
GList *node;
if (!grp) return NULL;
if (!name) return NULL;
/* first, look for accounts hanging off the root */
for (node = grp->accounts; node; node = node->next)
{
Account *account = node->data;
if (safe_strcmp(xaccAccountGetName (account), name) == 0)
return account;
}
/* if we are still here, then we haven't found the account yet.
* Recursively search the subgroups next */
/* first, look for accounts hanging off the root */
for (node = grp->accounts; node; node = node->next)
{
Account *account = node->data;
Account *acc;
acc = xaccGetAccountFromName (account->children, name);
if (acc)
return acc;
}
return NULL;
}
/********************************************************************\
* Fetch an account, given its full name *
\********************************************************************/
Account *
xaccGetAccountFromFullName (AccountGroup *grp,
const char *name,
const char separator)
{
GList *node;
Account *found;
char *p;
if (!grp) return NULL;
if (!name) return NULL;
p = (char *) name;
found = NULL;
while (1)
{
/* Look for the first separator. */
p = strchr(p, separator);
/* If found, switch it to the null char. */
if (p != NULL)
*p = 0;
/* Now look for that name in the children. */
for (node = grp->accounts; node; node = node->next)
{
Account *account = node->data;
if (safe_strcmp(xaccAccountGetName (account), name) == 0)
{
/* We found an account.
* If p == NULL, there is nothing left
* in the name, so just return the account.
* We don't need to put back the separator,
* because it was never erased (p == NULL). */
if (p == NULL)
return account;
/* There's stuff left to search for.
* Search recursively after the separator. */
found = xaccGetAccountFromFullName(account->children,
p + 1, separator);
/* If we found the account, break out. */
if (found != NULL)
break;
}
}
/* If found != NULL, an account was found. */
/* If p == NULL, there are no more separators left. */
/* Put back the separator. */
if (p != NULL)
*p = separator;
/* If we found the account, return it. */
if (found != NULL)
return found;
/* We didn't find the account. If there
* are no more separators, return NULL. */
if (p == NULL)
return NULL;
/* If we are here, we didn't find anything and there
* must be more separators. So, continue looking with
* a longer name, in case there is a name with the
* separator character in it. */
p++;
}
}
/********************************************************************\
* Fetch an account, given its name *
\********************************************************************/
Account *
xaccGetPeerAccountFromName (Account *acc, const char * name)
{
AccountGroup * root;
Account *peer_acc;
if (!acc) return NULL;
if (!name) return NULL;
/* first, find the root of the account group structure */
root = xaccAccountGetRoot (acc);
/* now search all accounts hanging off the root */
peer_acc = xaccGetAccountFromName (root, name);
return peer_acc;
}
/********************************************************************\
* Fetch an account, given its full name *
\********************************************************************/
Account *
xaccGetPeerAccountFromFullName (Account *acc, const char * name,
const char separator)
{
AccountGroup * root;
Account *peer_acc;
if (!acc) return NULL;
if (!name) return NULL;
/* first, find the root of the account group structure */
root = xaccAccountGetRoot (acc);
/* now search all acounts hanging off the root */
peer_acc = xaccGetAccountFromFullName (root, name, separator);
return peer_acc;
}
/********************************************************************\
\********************************************************************/
void
xaccAccountRemoveGroup (Account *acc)
{
AccountGroup *grp;
/* if this group has no parent, it must be the topgroup */
if (!acc) return;
grp = acc->children;
if (grp) grp->parent = NULL;
acc->children = NULL;
/* make sure that the parent of the group is marked
* as having been modified. */
grp = acc->parent;
if (!grp) return;
grp->saved = 0;
gnc_engine_generate_event (&acc->guid, GNC_EVENT_MODIFY);
}
/********************************************************************\
\********************************************************************/
void
xaccGroupRemoveAccount (AccountGroup *grp, Account *acc)
{
if (!acc) return;
/* this routine might be called on accounts which
* are not yet parented. */
if (!grp) return;
if (acc->parent != grp)
{
PERR ("account not in group");
return;
}
acc->parent = NULL;
grp->accounts = g_list_remove (grp->accounts, acc);
grp->saved = 0;
/* if this was the last account in a group, delete
* the group as well (unless its a root group) */
if ((grp->accounts == NULL) && (grp->parent))
{
xaccAccountRemoveGroup (grp->parent);
xaccFreeAccountGroup (grp);
}
xaccGroupSetBook (acc->children, NULL);
gnc_engine_generate_event (&acc->guid, GNC_EVENT_MODIFY);
}
/********************************************************************\
\********************************************************************/
void
xaccAccountInsertSubAccount (Account *adult, Account *child)
{
if (!adult || !child) return;
g_return_if_fail (adult->entity_table);
g_return_if_fail (adult->entity_table == child->entity_table);
/* if a container for the children doesn't yet exist, add it */
if (adult->children == NULL)
{
adult->children = xaccMallocAccountGroupEntityTable (adult->entity_table);
xaccGroupSetBook (adult->children, xaccGroupGetBook (adult->parent));
}
/* set back-pointer to parent */
adult->children->parent = adult;
/* allow side-effect of creating a child-less account group */
if (!child) return;
xaccGroupInsertAccount (adult->children, child);
gnc_engine_generate_event (&adult->guid, GNC_EVENT_MODIFY);
}
/********************************************************************\
\********************************************************************/
static int
group_sort_helper (gconstpointer a, gconstpointer b)
{
Account *aa = (Account *) a;
Account *bb = (Account *) b;
/* return > 1 if aa should come after bb */
return xaccAccountOrder (&aa, &bb);
}
void
xaccGroupInsertAccount (AccountGroup *grp, Account *acc)
{
if (!grp) return;
if (!acc) return;
g_return_if_fail (grp->entity_table == acc->entity_table);
/* If the account is currently in another group, remove it there
* first. Basically, we can't have accounts being in two places at
* once. If old and new parents are the same, reinsertion causes
* the sort order to be checked. */
if (acc->parent == grp)
{
grp->accounts = g_list_sort (grp->accounts, group_sort_helper);
}
else
{
if (acc->parent)
xaccGroupRemoveAccount (acc->parent, acc);
/* set back-pointer to the account's parent */
acc->parent = grp;
grp->accounts = g_list_insert_sorted (grp->accounts, acc,
group_sort_helper);
}
grp->saved = 0;
xaccGroupSetBook (acc->children, xaccGroupGetBook (grp));
gnc_engine_generate_event (&acc->guid, GNC_EVENT_MODIFY);
}
/********************************************************************\
\********************************************************************/
void
xaccGroupConcatGroup (AccountGroup *togrp, AccountGroup *fromgrp)
{
if (!togrp) return;
if (!fromgrp) return;
g_return_if_fail (togrp->entity_table == fromgrp->entity_table);
/* The act of inserting the account into togrp also causes it to
* automatically be deleted from fromgrp. Be careful! */
while (TRUE)
{
Account *account;
GList *accounts;
GList *next;
accounts = fromgrp->accounts;
if (!accounts)
return;
next = accounts->next;
account = accounts->data;
xaccGroupInsertAccount (togrp, account);
if (!next)
return;
}
}
/********************************************************************\
\********************************************************************/
void
xaccGroupMergeAccounts (AccountGroup *grp)
{
GList *node_a;
GList *node_b;
if (!grp) return;
for (node_a = grp->accounts; node_a; node_a = node_a->next)
{
Account *acc_a = node_a->data;
for (node_b = node_a->next; node_b; node_b = node_b->next)
{
Account *acc_b = node_b->data;
if ((0 == safe_strcmp(xaccAccountGetName(acc_a),
xaccAccountGetName(acc_b))) &&
(0 == safe_strcmp(xaccAccountGetCode(acc_a),
xaccAccountGetCode(acc_b))) &&
(0 == safe_strcmp(xaccAccountGetDescription(acc_a),
xaccAccountGetDescription(acc_b))) &&
(gnc_commodity_equiv(xaccAccountGetCommodity(acc_a),
xaccAccountGetCommodity(acc_b))) &&
(0 == safe_strcmp(xaccAccountGetNotes(acc_a),
xaccAccountGetNotes(acc_b))) &&
(xaccAccountGetType(acc_a) == xaccAccountGetType(acc_b)))
{
AccountGroup *ga, *gb;
GList *lp;
/* consolidate children */
ga = (AccountGroup *) acc_a->children;
gb = (AccountGroup *) acc_b->children;
if (gb)
{
if (!ga)
{
acc_a->children = gb;
gb->parent = acc_a;
acc_b->children = NULL;
gnc_engine_generate_event (&acc_a->guid, GNC_EVENT_MODIFY);
gnc_engine_generate_event (&acc_b->guid, GNC_EVENT_MODIFY);
}
else
{
xaccGroupConcatGroup (ga, gb);
acc_b->children = NULL;
gnc_engine_generate_event (&acc_b->guid, GNC_EVENT_MODIFY);
}
}
/* recurse to do the children's children */
xaccGroupMergeAccounts (ga);
/* consolidate transactions */
lp = acc_b->splits;
for (lp = acc_b->splits; lp; lp = lp->next)
{
Split *split = lp->data;
gnc_engine_generate_event (&xaccSplitGetAccount(split)->guid,
GNC_EVENT_MODIFY);
xaccSplitSetAccount(split, NULL);
xaccAccountInsertSplit (acc_a, split);
}
g_list_free(acc_b->splits);
acc_b->splits = NULL;
/* move back one before removal */
node_b = node_b->prev;
/* remove from list -- node_a is ok, it's before node_b */
grp->accounts = g_list_remove (grp->accounts, acc_b);
xaccAccountBeginEdit (acc_b);
xaccAccountDestroy (acc_b);
break;
}
}
}
}
/********************************************************************\
\********************************************************************/
int
xaccGroupGetNumAccounts (AccountGroup *grp)
{
if (!grp) return 0;
return g_list_length (grp->accounts);
}
Account *
xaccGroupGetAccount (AccountGroup *grp, int i)
{
if (!grp) return NULL;
return g_list_nth_data (grp->accounts, i);
}
Account *
xaccGroupGetParentAccount (AccountGroup * grp)
{
if (!grp) return NULL;
return grp->parent;
}
/********************************************************************\
\********************************************************************/
int
xaccGroupGetDepth (AccountGroup *grp)
{
GList *node;
int depth = 0;
int maxdepth = 0;
if (!grp) return 0;
for (node = grp->accounts; node; node = node->next)
{
Account *account = node->data;
depth = xaccGroupGetDepth (account->children);
if (depth > maxdepth)
maxdepth = depth;
}
maxdepth++;
return maxdepth;
}
/********************************************************************\
\********************************************************************/
void
xaccSplitsBeginStagedTransactionTraversals (GList *splits)
{
GList *lp;
for (lp = splits; lp; lp = lp->next)
{
Split *s = lp->data;
Transaction *trans = s->parent;
if (trans)
trans->marker = 0;
}
}
void
xaccAccountBeginStagedTransactionTraversals (Account *account)
{
if (account == NULL) return;
xaccSplitsBeginStagedTransactionTraversals (account->splits);
}
gboolean
xaccTransactionTraverse (Transaction *trans, int stage)
{
if (trans == NULL) return FALSE;
if (trans->marker < stage)
{
trans->marker = stage;
return TRUE;
}
return FALSE;
}
gboolean
xaccSplitTransactionTraverse (Split *split, int stage)
{
if (split == NULL) return FALSE;
return xaccTransactionTraverse (split->parent, stage);
}
void
xaccGroupBeginStagedTransactionTraversals (AccountGroup *grp)
{
GList *node;
if (!grp) return;
for (node = grp->accounts; node; node = node->next)
{
Account *account = node->data;
GList *lp;
/* recursively do sub-accounts */
xaccGroupBeginStagedTransactionTraversals (account->children);
for (lp = account->splits; lp; lp = lp->next)
{
Split *s = lp->data;
Transaction *trans = s->parent;
trans->marker = 0;
}
}
}
int
xaccAccountStagedTransactionTraversal (Account *acc,
unsigned int stage,
int (*callback)(Transaction *t,
void *cb_data),
void *cb_data)
{
if (!acc) return 0;
if (callback)
{
GList *lp;
for(lp = acc->splits; lp; lp = lp->next)
{
Split *s = (Split *) lp->data;
Transaction *trans = s->parent;
if (trans && (trans->marker < stage))
{
int retval;
trans->marker = stage;
retval = callback(trans, cb_data);
if (retval) return retval;
}
}
}
else
{
GList *lp;
for(lp = acc->splits; lp; lp = lp->next)
{
Split *s = (Split *) lp->data;
Transaction *trans = s->parent;
if (trans && (trans->marker < stage))
trans->marker = stage;
}
}
return 0;
}
int
xaccGroupStagedTransactionTraversal (AccountGroup *grp,
unsigned int stage,
int (*callback)(Transaction *t,
void *cb_data),
void *cb_data)
{
GList *node;
if (!grp) return 0;
for (node = grp->accounts; node; node = node->next)
{
Account *account = node->data;
int retval;
/* recursively do sub-accounts */
retval = xaccGroupStagedTransactionTraversal (account->children, stage,
callback, cb_data);
if (retval) return retval;
retval = xaccAccountStagedTransactionTraversal (account, stage,
callback, cb_data);
if (retval) return retval;
}
return 0;
}
/********************************************************************\
\********************************************************************/
struct group_visit_data
{
gboolean (*proc)(Transaction *t, void *data);
void *up_data;
GHashTable *visit_table;
};
static gboolean
xaccGroupVisitUnvisitedTransactions_thunk(Transaction *trn,
void *data)
{
gpointer test_trn;
struct group_visit_data *grdata = (struct group_visit_data*)data;
test_trn = g_hash_table_lookup(grdata->visit_table, trn);
if(!test_trn)
{
g_hash_table_insert(grdata->visit_table, trn, "");
grdata->proc(trn, grdata->up_data);
}
return TRUE;
}
gboolean
xaccGroupVisitUnvisitedTransactions (AccountGroup *g,
gboolean (*proc)(Transaction *t,
void *data),
void *data,
GHashTable *visited_txns)
{
gboolean keep_going = TRUE;
GList *list;
GList *node;
struct group_visit_data grdata;
if (!g) return(FALSE);
if (!proc) return(FALSE);
if (!visited_txns) return(FALSE);
list = xaccGroupGetSubAccounts (g);
grdata.proc = proc;
grdata.up_data = data;
grdata.visit_table = visited_txns;
for (node = list; node && keep_going; node = node->next)
{
Account *account = node->data;
keep_going = xaccAccountForEachTransaction(
account, xaccGroupVisitUnvisitedTransactions_thunk, (void*)&grdata);
}
g_list_free (list);
return(keep_going);
}
gboolean
xaccGroupForEachTransaction (AccountGroup *g,
gboolean (*proc)(Transaction *t, void *data),
void *data)
{
GHashTable *visited_txns = NULL;
gboolean result = FALSE;
if (!g) return(FALSE);
if (!proc) return(FALSE);
visited_txns = guid_hash_table_new();
if (visited_txns)
result = xaccGroupVisitUnvisitedTransactions(g, proc, data, visited_txns);
/* cleanup */
if (visited_txns)
g_hash_table_destroy(visited_txns);
return(result);
}
/********************************************************************\
\********************************************************************/
GSList *
xaccGroupMapAccounts (AccountGroup *grp,
gpointer (*thunk)(Account *a, gpointer data),
gpointer data)
{
GSList *result = NULL;
GList *node;
if (!grp) return(NULL);
if (!thunk) return(NULL);
for (node = grp->accounts; node; node = node->next)
{
Account *account = node->data;
gpointer thunk_result = thunk (account, data);
if (thunk_result)
result = g_slist_prepend (result, thunk_result);
}
return(g_slist_reverse (result));
}
gpointer
xaccGroupForEachAccount (AccountGroup *grp,
gpointer (*thunk)(Account *a, gpointer data),
gpointer data,
gboolean deeply)
{
GList *node;
if (!grp) return(NULL);
if (!thunk) return(NULL);
for (node = grp->accounts; node; node = node->next)
{
Account *account = node->data;
gpointer result = thunk (account, data);
if (result)
return(result);
if(deeply)
result = xaccGroupForEachAccount (account->children,
thunk, data, TRUE);
if (result)
return(result);
}
return(NULL);
}