1 /* HomeBank -- Free, easy, personal accounting for everyone.
2 * Copyright (C) 1995-2018 Maxime DOYEN
4 * This file is part of HomeBank.
6 * HomeBank is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * HomeBank is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "hb-category.h"
27 /****************************************************************************/
29 /****************************************************************************/
38 /* our global datas */
39 extern struct HomeBank
*GLOBALS
;
41 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
44 da_cat_clone(Category
*src_item
)
46 Category
*new_item
= rc_dup(src_item
, sizeof(Category
));
48 DB( g_print("da_cat_clone\n") );
51 //duplicate the string
52 new_item
->name
= g_strdup(src_item
->name
);
59 da_cat_free(Category
*item
)
61 DB( g_print("da_cat_free\n") );
64 DB( g_print(" => %d, %s\n", item
->key
, item
->name
) );
75 DB( g_print("da_cat_malloc\n") );
76 return rc_alloc(sizeof(Category
));
83 DB( g_print("da_cat_destroy\n") );
84 g_hash_table_destroy(GLOBALS
->h_cat
);
93 DB( g_print("da_cat_new\n") );
94 GLOBALS
->h_cat
= g_hash_table_new_full(g_int_hash
, g_int_equal
, (GDestroyNotify
)g_free
, (GDestroyNotify
)da_cat_free
);
96 // insert our 'no category'
97 item
= da_cat_malloc();
98 item
->name
= g_strdup("");
103 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
108 * Return value: the number of elements
113 return g_hash_table_size(GLOBALS
->h_cat
);
119 * da_cat_remove_grfunc:
121 * GRFunc to get the max id
123 * Return value: TRUE if the key/value must be deleted
127 da_cat_remove_grfunc(gpointer key
, Category
*cat
, guint32
*remkey
)
129 if(cat
->key
== *remkey
|| cat
->parent
== *remkey
)
139 * delete a category from the GHashTable
141 * Return value: TRUE if the key was found and deleted
145 da_cat_remove(guint32 key
)
147 DB( g_print("da_cat_remove %d\n", key
) );
149 return g_hash_table_foreach_remove(GLOBALS
->h_cat
, (GHRFunc
)da_cat_remove_grfunc
, &key
);
155 * insert a category into the GHashTable
157 * Return value: TRUE if inserted
161 da_cat_insert(Category
*item
)
165 DB( g_print("da_cat_insert\n") );
167 new_key
= g_new0(guint32
, 1);
168 *new_key
= item
->key
;
169 g_hash_table_insert(GLOBALS
->h_cat
, new_key
, item
);
178 * append a category into the GHashTable
180 * Return value: TRUE if inserted
184 da_cat_append(Category
*cat
)
190 DB( g_print("da_cat_append\n") );
192 if( cat
->name
!= NULL
)
195 fullname
= da_cat_get_fullname(cat
);
196 existitem
= da_cat_get_by_fullname( fullname
);
199 if( existitem
== NULL
)
201 new_key
= g_new0(guint32
, 1);
202 *new_key
= da_cat_get_max_key() + 1;
205 DB( g_print(" -> insert id: %d\n", *new_key
) );
207 g_hash_table_insert(GLOBALS
->h_cat
, new_key
, cat
);
213 DB( g_print(" -> %s already exist\n", cat
->name
) );
220 * da_cat_max_key_ghfunc:
222 * GHFunc for biggest key
226 da_cat_max_key_ghfunc(gpointer key
, Category
*cat
, guint32
*max_key
)
229 *max_key
= MAX(*max_key
, cat
->key
);
233 * da_cat_get_max_key:
235 * Get the biggest key from the GHashTable
237 * Return value: the biggest key value
241 da_cat_get_max_key(void)
245 g_hash_table_foreach(GLOBALS
->h_cat
, (GHFunc
)da_cat_max_key_ghfunc
, &max_key
);
250 * da_cat_get_fullname:
252 * Get category the fullname 'xxxx:yyyyy'
254 * Return value: the category fullname (free it with g_free)
258 da_cat_get_fullname(Category
*cat
)
262 if( cat
->parent
== 0 )
263 return g_strdup(cat
->name
);
266 parent
= da_cat_get(cat
->parent
);
269 return g_strdup_printf("%s:%s", parent
->name
, cat
->name
);
278 * da_cat_name_grfunc:
280 * GRFunc to get the max id
282 * Return value: TRUE if the key/value pair match our name
286 da_cat_name_grfunc(gpointer key
, Category
*cat
, gchar
*name
)
289 // DB( g_print("%s == %s\n", name, cat->name) );
290 if( name
&& cat
->name
)
292 if(!strcasecmp(name
, cat
->name
))
299 * da_cat_get_key_by_name:
301 * Get a category key by its name
303 * Return value: the category key or -1 if not found
307 da_cat_get_key_by_name(gchar
*name
)
311 DB( g_print("da_cat_get_key_by_name\n") );
313 cat
= g_hash_table_find(GLOBALS
->h_cat
, (GHRFunc
)da_cat_name_grfunc
, name
);
321 * da_cat_get_by_name:
323 * Get a category structure by its name
325 * Return value: Category * or NULL if not found
329 da_cat_get_by_name(gchar
*name
)
331 DB( g_print("da_cat_get_by_name\n") );
333 return g_hash_table_find(GLOBALS
->h_cat
, (GHRFunc
)da_cat_name_grfunc
, name
);
337 /* fullname i.e. car:refuel */
338 struct fullcatcontext
346 da_cat_fullname_grfunc(gpointer key
, Category
*item
, struct fullcatcontext
*ctx
)
349 //DB( g_print("'%s' == '%s'\n", ctx->name, item->name) );
350 if( item
->parent
== ctx
->parent
)
352 if(!strcasecmp(ctx
->name
, item
->name
))
359 da_cat_get_by_fullname(gchar
*fullname
)
361 struct fullcatcontext ctx
;
363 Category
*item
= NULL
;
365 DB( g_print("da_cat_get_by_fullname\n") );
367 typestr
= g_strsplit(fullname
, ":", 2);
368 if( g_strv_length(typestr
) == 2 )
371 ctx
.name
= typestr
[0];
372 DB( g_print(" [x:x] try to find the parent : '%s'\n", typestr
[0]) );
374 Category
*parent
= g_hash_table_find(GLOBALS
->h_cat
, (GHRFunc
)da_cat_fullname_grfunc
, &ctx
);
377 ctx
.parent
= parent
->key
;
378 ctx
.name
= typestr
[1];
380 DB( g_print(" [x:x] and searching sub %d '%s'\n", ctx
.parent
, ctx
.name
) );
382 item
= g_hash_table_find(GLOBALS
->h_cat
, (GHRFunc
)da_cat_fullname_grfunc
, &ctx
);
390 DB( g_print(" [x] try to '%s'\n", fullname
) );
392 item
= g_hash_table_find(GLOBALS
->h_cat
, (GHRFunc
)da_cat_fullname_grfunc
, &ctx
);
397 DB( g_print(" return value %p\n", item
) );
404 * da_cat_append_ifnew_by_fullname:
406 * append a category if it is new by fullname
412 da_cat_append_ifnew_by_fullname(gchar
*fullname
, gboolean imported
)
414 struct fullcatcontext ctx
;
416 Category
*newcat
, *item
, *retval
= NULL
;
419 DB( g_print("da_cat_append_ifnew_by_fullname\n") );
421 DB( g_print(" -> fullname: '%s' %d\n", fullname
, strlen(fullname
)) );
423 if( strlen(fullname
) > 0 )
425 typestr
= g_strsplit(fullname
, ":", 2);
427 /* if we have a subcategory : aaaa:bbb */
428 if( g_strv_length(typestr
) == 2 )
431 ctx
.name
= typestr
[0];
432 DB( g_print(" try to find the parent:'%s'\n", typestr
[0]) );
434 Category
*parent
= g_hash_table_find(GLOBALS
->h_cat
, (GHRFunc
)da_cat_fullname_grfunc
, &ctx
);
437 DB( g_print(" -> not found\n") );
439 // append a new category
440 new_key
= g_new0(guint32
, 1);
441 *new_key
= da_cat_get_max_key() + 1;
443 newcat
= da_cat_malloc();
444 newcat
->key
= *new_key
;
445 newcat
->name
= g_strdup(typestr
[0]);
446 newcat
->imported
= imported
;
450 DB( g_print(" -> insert cat '%s' id: %d\n", newcat
->name
, newcat
->key
) );
452 g_hash_table_insert(GLOBALS
->h_cat
, new_key
, newcat
);
455 ctx
.parent
= parent
->key
;
456 ctx
.name
= typestr
[1];
457 DB( g_print(" searching %d '%s'\n", ctx
.parent
, ctx
.name
) );
459 item
= g_hash_table_find(GLOBALS
->h_cat
, (GHRFunc
)da_cat_fullname_grfunc
, &ctx
);
462 // append a new subcategory
463 new_key
= g_new0(guint32
, 1);
464 *new_key
= da_cat_get_max_key() + 1;
466 newcat
= da_cat_malloc();
467 newcat
->key
= *new_key
;
468 newcat
->parent
= parent
->key
;
469 newcat
->name
= g_strdup(typestr
[1]);
470 newcat
->imported
= imported
;
472 newcat
->flags
|= GF_SUB
;
473 //#1713413 take parent type into account
474 if(parent
->flags
& GF_INCOME
)
475 newcat
->flags
|= GF_INCOME
;
477 DB( g_print(" -> insert subcat '%s' id: %d\n", newcat
->name
, newcat
->key
) );
479 g_hash_table_insert(GLOBALS
->h_cat
, new_key
, newcat
);
486 /* this a single category : aaaa */
490 ctx
.name
= typestr
[0];
491 DB( g_print(" searching %d '%s'\n", ctx
.parent
, ctx
.name
) );
493 item
= g_hash_table_find(GLOBALS
->h_cat
, (GHRFunc
)da_cat_fullname_grfunc
, &ctx
);
496 // append a new category
497 new_key
= g_new0(guint32
, 1);
498 *new_key
= da_cat_get_max_key() + 1;
500 newcat
= da_cat_malloc();
501 newcat
->key
= *new_key
;
502 newcat
->name
= g_strdup(typestr
[0]);
503 newcat
->imported
= imported
;
505 DB( g_print(" -> insert cat '%s' id: %d\n", newcat
->name
, newcat
->key
) );
507 g_hash_table_insert(GLOBALS
->h_cat
, new_key
, newcat
);
527 * Get a category structure by key
529 * Return value: Category * or NULL if not found
533 da_cat_get(guint32 key
)
535 //DB( g_print("da_cat_get\n") );
537 return g_hash_table_lookup(GLOBALS
->h_cat
, &key
);
541 void da_cat_consistency(Category
*item
)
545 if((item
->flags
& GF_SUB
) && item
->key
> 0)
547 //check for existing parent
548 if( da_cat_get(item
->parent
) == NULL
)
550 Category
*parent
= da_cat_append_ifnew_by_fullname ("orphaned", FALSE
);
552 item
->parent
= parent
->key
;
554 g_warning("category consistency: fixed missing parent %d", item
->parent
);
558 // ensure type equal for categories and its children
559 if(!(item
->flags
& GF_SUB
) && item
->key
> 0)
561 isIncome
= (item
->flags
& GF_INCOME
) ? TRUE
: FALSE
;
562 if( category_change_type(item
, isIncome
) > 0 )
564 g_warning("category consistency: fixed type for child");
565 GLOBALS
->changes_count
++;
569 g_strstrip(item
->name
);
574 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
579 da_cat_debug_list_ghfunc(gpointer key
, gpointer value
, gpointer user_data
)
582 Category
*cat
= value
;
584 DB( g_print(" %d :: %s (parent=%d\n", *id
, cat
->name
, cat
->parent
) );
589 da_cat_debug_list(void)
592 DB( g_print("\n** debug **\n") );
594 g_hash_table_foreach(GLOBALS
->h_cat
, da_cat_debug_list_ghfunc
, NULL
);
596 DB( g_print("\n** end debug **\n") );
604 /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
606 guint32
category_report_id(guint32 key
, gboolean subcat
)
608 Category
*catentry
= da_cat_get(key
);
615 retval
= (catentry
->flags
& GF_SUB
) ? catentry
->parent
: catentry
->key
;
619 retval
= catentry
->key
;
627 category_delete_unused(void)
631 lcat
= list
= g_hash_table_get_values(GLOBALS
->h_cat
);
634 Category
*entry
= list
->data
;
636 if(entry
->usage_count
<= 0 && entry
->key
> 0)
637 da_cat_remove (entry
->key
);
639 list
= g_list_next(list
);
646 category_fill_usage_count(guint32 kcat
)
648 Category
*cat
= da_cat_get (kcat
);
654 if( cat
->parent
> 0 )
656 parent
= da_cat_get(cat
->parent
);
659 parent
->usage_count
++;
667 category_fill_usage(void)
670 GList
*lst_acc
, *lnk_acc
;
672 GList
*lpay
, *lrul
, *list
;
675 lcat
= list
= g_hash_table_get_values(GLOBALS
->h_cat
);
678 Category
*entry
= list
->data
;
679 entry
->usage_count
= 0;
680 list
= g_list_next(list
);
685 lst_acc
= g_hash_table_get_values(GLOBALS
->h_acc
);
686 lnk_acc
= g_list_first(lst_acc
);
687 while (lnk_acc
!= NULL
)
689 Account
*acc
= lnk_acc
->data
;
691 lnk_txn
= g_queue_peek_head_link(acc
->txn_queue
);
692 while (lnk_txn
!= NULL
)
694 Transaction
*txn
= lnk_txn
->data
;
696 //#1689308 count split as well
697 if( txn
->flags
& OF_SPLIT
)
699 nbsplit
= da_splits_count(txn
->splits
);
700 for(i
=0;i
<nbsplit
;i
++)
702 Split
*split
= txn
->splits
[i
];
704 category_fill_usage_count(split
->kcat
);
708 category_fill_usage_count(txn
->kcat
);
710 lnk_txn
= g_list_next(lnk_txn
);
712 lnk_acc
= g_list_next(lnk_acc
);
714 g_list_free(lst_acc
);
716 lpay
= list
= g_hash_table_get_values(GLOBALS
->h_pay
);
719 Payee
*entry
= list
->data
;
721 category_fill_usage_count(entry
->kcat
);
722 list
= g_list_next(list
);
727 list
= g_list_first(GLOBALS
->arc_list
);
730 Archive
*entry
= list
->data
;
732 //#1689308 count split as well
733 if( entry
->flags
& OF_SPLIT
)
735 nbsplit
= da_splits_count(entry
->splits
);
736 for(i
=0;i
<nbsplit
;i
++)
738 Split
*split
= entry
->splits
[i
];
740 category_fill_usage_count(split
->kcat
);
744 category_fill_usage_count(entry
->kcat
);
746 list
= g_list_next(list
);
750 lrul
= list
= g_hash_table_get_values(GLOBALS
->h_rul
);
753 Assign
*entry
= list
->data
;
755 category_fill_usage_count(entry
->kcat
);
756 list
= g_list_next(list
);
764 category_move(guint32 key1
, guint32 key2
)
766 GList
*lst_acc
, *lnk_acc
;
771 lst_acc
= g_hash_table_get_values(GLOBALS
->h_acc
);
772 lnk_acc
= g_list_first(lst_acc
);
773 while (lnk_acc
!= NULL
)
775 Account
*acc
= lnk_acc
->data
;
777 lnk_txn
= g_queue_peek_head_link(acc
->txn_queue
);
778 while (lnk_txn
!= NULL
)
780 Transaction
*txn
= lnk_txn
->data
;
782 if(txn
->kcat
== key1
)
785 txn
->flags
|= OF_CHANGED
;
788 // move split category #1340142
789 nbsplit
= da_splits_count(txn
->splits
);
790 for(i
=0;i
<nbsplit
;i
++)
792 Split
*split
= txn
->splits
[i
];
794 if( split
->kcat
== key1
)
797 txn
->flags
|= OF_CHANGED
;
801 lnk_txn
= g_list_next(lnk_txn
);
804 lnk_acc
= g_list_next(lnk_acc
);
806 g_list_free(lst_acc
);
809 list
= g_list_first(GLOBALS
->arc_list
);
812 Archive
*entry
= list
->data
;
813 if(entry
->kcat
== key1
)
817 list
= g_list_next(list
);
820 lrul
= list
= g_hash_table_get_values(GLOBALS
->h_rul
);
823 Assign
*entry
= list
->data
;
825 if(entry
->kcat
== key1
)
829 list
= g_list_next(list
);
837 category_rename(Category
*item
, const gchar
*newname
)
839 Category
*parent
, *existitem
;
840 gchar
*fullname
= NULL
;
844 DB( g_print("(category) rename\n") );
846 stripname
= g_strdup(newname
);
847 g_strstrip(stripname
);
849 if( item
->parent
== 0)
850 fullname
= g_strdup(stripname
);
853 parent
= da_cat_get(item
->parent
);
856 fullname
= g_strdup_printf("%s:%s", parent
->name
, stripname
);
860 DB( g_print(" - search: %s\n", fullname
) );
862 existitem
= da_cat_get_by_fullname( fullname
);
864 if( existitem
!= NULL
&& existitem
->key
!= item
->key
)
866 DB( g_print("error, same name already exist with other key %d <> %d\n",existitem
->key
, item
->key
) );
871 DB( g_print(" -renaming\n") );
874 item
->name
= g_strdup(stripname
);
885 static gint
category_glist_name_compare_func(Category
*c1
, Category
*c2
)
887 gchar
*name1
, *name2
;
890 if( c1
!= NULL
&& c2
!= NULL
)
892 name1
= da_cat_get_fullname(c1
);
893 name2
= da_cat_get_fullname(c2
);
895 retval
= hb_string_utf8_compare(name1
, name2
);
904 static gint
category_glist_key_compare_func(Category
*a
, Category
*b
)
906 gint ka
, kb
, retval
= 0;
908 if(a
->parent
== 0 && b
->parent
== a
->key
)
911 if(b
->parent
== 0 && a
->parent
== b
->key
)
915 ka
= a
->parent
!= 0 ? a
->parent
: a
->key
;
916 kb
= b
->parent
!= 0 ? b
->parent
: b
->key
;
933 DB( g_print("compare a=%2d:%2d to b=%2d:%2d :: %d [%s]\n", a
->key
, a
->parent
, b
->key
, b
->parent
, retval
, str
) );
940 GList
*category_glist_sorted(gint column
)
942 GList
*list
= g_hash_table_get_values(GLOBALS
->h_cat
);
945 return g_list_sort(list
, (GCompareFunc
)category_glist_key_compare_func
);
947 return g_list_sort(list
, (GCompareFunc
)category_glist_name_compare_func
);
952 category_load_csv(gchar
*filename
, gchar
**error
)
959 gchar
*lastcatname
= NULL
;
964 const gchar
*encoding
;
966 encoding
= homebank_file_getencoding(filename
);
968 DB( g_print(" -> encoding should be %s\n", encoding
) );
973 io
= g_io_channel_new_file(filename
, "r", NULL
);
977 if( encoding
!= NULL
)
979 g_io_channel_set_encoding(io
, encoding
, NULL
);
986 io_stat
= g_io_channel_read_line(io
, &tmpstr
, NULL
, NULL
, &err
);
988 DB( g_print(" + iostat %d\n", io_stat
) );
990 if( io_stat
== G_IO_STATUS_ERROR
)
992 DB (g_print(" + ERROR %s\n",err
->message
));
995 if( io_stat
== G_IO_STATUS_EOF
)
997 if( io_stat
== G_IO_STATUS_NORMAL
)
1001 DB( g_print(" + strip %s\n", tmpstr
) );
1002 hb_string_strip_crlf(tmpstr
);
1004 DB( g_print(" + split\n") );
1005 str_array
= g_strsplit (tmpstr
, ";", 3);
1008 if( g_strv_length (str_array
) != 3 )
1010 *error
= _("invalid CSV format");
1012 DB( g_print(" + error %s\n", *error
) );
1016 DB( g_print(" + read %s : %s : %s\n", str_array
[0], str_array
[1], str_array
[2]) );
1019 if( g_str_has_prefix(str_array
[0], "1") )
1021 fullcatname
= g_strdup(str_array
[2]);
1022 g_free(lastcatname
);
1023 lastcatname
= g_strdup(str_array
[2]);
1025 type
= g_str_has_prefix(str_array
[1], "+") ? GF_INCOME
: 0;
1027 DB( g_print(" + type = %d\n", type
) );
1031 if( g_str_has_prefix(str_array
[0], "2") )
1033 fullcatname
= g_strdup_printf("%s:%s", lastcatname
, str_array
[2]);
1036 DB( g_print(" + fullcatname %s\n", fullcatname
) );
1038 item
= da_cat_append_ifnew_by_fullname(fullcatname
, FALSE
);
1040 DB( g_print(" + item %p\n", item
) );
1044 DB( g_print(" + assign flags: '%c'\n", type
) );
1046 item
->flags
|= type
;
1050 g_free(fullcatname
);
1051 g_strfreev (str_array
);
1059 g_io_channel_unref (io
);
1064 g_free(lastcatname
);
1072 category_save_csv(gchar
*filename
, gchar
**error
)
1074 gboolean retval
= FALSE
;
1080 io
= g_io_channel_new_file(filename
, "w", NULL
);
1083 lcat
= list
= category_glist_sorted(1);
1085 while (list
!= NULL
)
1087 Category
*item
= list
->data
;
1093 if( item
->parent
== 0)
1096 type
= (item
->flags
& GF_INCOME
) ? '+' : '-';
1104 outstr
= g_strdup_printf("%c;%c;%s\n", lvel
, type
, item
->name
);
1106 DB( g_print(" + export %s\n", outstr
) );
1108 g_io_channel_write_chars(io
, outstr
, -1, NULL
, NULL
);
1112 list
= g_list_next(list
);
1119 g_io_channel_unref (io
);
1126 gint
category_type_get(Category
*item
)
1128 if( (item
->flags
& (GF_INCOME
)) )
1135 static gint
category_change_type_eval(Category
*item
, gboolean isIncome
)
1137 if( (item
->flags
& (GF_INCOME
)) && !isIncome
)
1143 gint
category_change_type(Category
*item
, gboolean isIncome
)
1148 changes
+= category_change_type_eval(item
, isIncome
);
1150 item
->flags
&= ~(GF_INCOME
); //delete flag
1151 if(isIncome
== TRUE
)
1152 item
->flags
|= GF_INCOME
;
1154 // change also childs
1155 lcat
= list
= g_hash_table_get_values(GLOBALS
->h_cat
);
1156 while (list
!= NULL
)
1158 Category
*child
= list
->data
;
1160 if(child
->parent
== item
->key
)
1162 changes
+= category_change_type_eval(child
, isIncome
);
1163 child
->flags
&= ~(GF_INCOME
); //delete flag
1164 if(isIncome
== TRUE
)
1165 child
->flags
|= GF_INCOME
;
1167 list
= g_list_next(list
);
1180 * category_find_preset:
1182 * find a user language compatible file for category preset
1184 * Return value: a pathname to the file or NULL
1187 gchar
*category_find_preset(gchar
**lang
)
1194 DB( g_print("** category_find_preset **\n") );
1196 langs
= (gchar
**)g_get_language_names ();
1198 DB( g_print(" -> %d languages detected\n", g_strv_length(langs
)) );
1200 for(i
=0;i
<g_strv_length(langs
);i
++)
1202 DB( g_print(" -> %d '%s'\n", i
, langs
[i
]) );
1203 filename
= g_strdup_printf("hb-categories-%s.csv", langs
[i
]);
1204 gchar
*pathfilename
= g_build_filename(homebank_app_get_datas_dir(), filename
, NULL
);
1205 exists
= g_file_test(pathfilename
, G_FILE_TEST_EXISTS
);
1206 DB( g_print(" -> '%s' exists=%d\n", pathfilename
, exists
) );
1211 return pathfilename
;
1214 g_free(pathfilename
);
1217 DB( g_print("return NULL\n") );