* src/import-export/import-parse.[ch]: routines to parse numbers

and dates in a mostly-locale-agnostic way.
	* src/import-export/import-format-dialog.c: a dialog to ask the user
	  to choose a format, when parsing just isn't enough.
	* src/import-export/generic-import.glade: add the format_match dialog
	* src/import-export/test/test-import-parse.c: test the import-parse
	  routines.


git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@8851 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
Derek Atkins 2003-07-11 03:28:00 +00:00
parent 7b3bb91ed7
commit 06aa3a6845
8 changed files with 901 additions and 14 deletions

View File

@ -1,3 +1,13 @@
2003-07-10 Derek Atkins <derek@ihtfp.com>
* src/import-export/import-parse.[ch]: routines to parse numbers
and dates in a mostly-locale-agnostic way.
* src/import-export/import-format-dialog.c: a dialog to ask the user
to choose a format, when parsing just isn't enough.
* src/import-export/generic-import.glade: add the format_match dialog
* src/import-export/test/test-import-parse.c: test the import-parse
routines.
2003-07-09 Derek Atkins <derek@ihtfp.com>
* configure.in: change (and comment out) checks for pthreads

View File

@ -6,7 +6,9 @@ libgncmod_generic_import_la_SOURCES = \
import-account-matcher.c \
import-commodity-matcher.c \
import-backend.c \
import-format-dialog.c \
import-match-picker.c \
import-parse.c \
import-utilities.c \
import-settings.c \
import-match-map.c \
@ -15,7 +17,8 @@ libgncmod_generic_import_la_SOURCES = \
gncincludedir = ${GNC_INCLUDE_DIR}
gncinclude_HEADERS = \
import-match-map.h
import-match-map.h \
import-parse.h
noinst_HEADERS = \
import-account-matcher.h \
@ -68,16 +71,24 @@ EXTRA_DIST = \
.cvsignore \
generic-import-design.txt
#noinst_DATA = .scm-links
if GNUCASH_SEPARATE_BUILDDIR
#For compiling
SCM_FILE_LINKS =
#For executing test cases
SCM_FILE_LINKS += generic-import.scm
endif
#.scm-links:
# rm -f g-wrapped gnucash import-export qif-import
# ln -sf . qif-import # to fix (load "qif-import/foo.scm")
# ln -sf . import-export
# ln -sf . gnucash
# ln -sf . g-wrapped
# touch .scm-links
DISTCLEANFILES = gnucash g-wrapped .scm-links import-export
noinst_DATA = .scm-links
.scm-links:
rm -f g-wrapped gnucash generic-import import-export
ln -sf . import-export
ln -sf . gnucash
ln -sf . generic-import
ln -sf . g-wrapped
touch .scm-links
DISTCLEANFILES = gnucash generic-import g-wrapped .scm-links import-export
# FIXME remove this when qif-io-core is finished
DIST_SUBDIRS = binary-import qif-import qif-io-core ofx test hbci log-replay

View File

@ -1092,4 +1092,140 @@ click &quot;Ok&quot;.</label>
</widget>
</widget>
<widget>
<class>GnomeDialog</class>
<name>format_picker</name>
<title>Choose a format</title>
<type>GTK_WINDOW_TOPLEVEL</type>
<position>GTK_WIN_POS_CENTER</position>
<modal>True</modal>
<allow_shrink>False</allow_shrink>
<allow_grow>False</allow_grow>
<auto_shrink>False</auto_shrink>
<auto_close>False</auto_close>
<hide_on_close>False</hide_on_close>
<widget>
<class>GtkVBox</class>
<child_name>GnomeDialog:vbox</child_name>
<name>dialog-vbox4</name>
<homogeneous>False</homogeneous>
<spacing>8</spacing>
<child>
<padding>4</padding>
<expand>True</expand>
<fill>True</fill>
</child>
<widget>
<class>GtkHButtonBox</class>
<child_name>GnomeDialog:action_area</child_name>
<name>dialog-action_area5</name>
<layout_style>GTK_BUTTONBOX_END</layout_style>
<spacing>8</spacing>
<child_min_width>85</child_min_width>
<child_min_height>27</child_min_height>
<child_ipad_x>7</child_ipad_x>
<child_ipad_y>0</child_ipad_y>
<child>
<padding>0</padding>
<expand>False</expand>
<fill>True</fill>
<pack>GTK_PACK_END</pack>
</child>
<widget>
<class>GtkButton</class>
<name>button79</name>
<can_default>True</can_default>
<can_focus>True</can_focus>
<stock_button>GNOME_STOCK_BUTTON_OK</stock_button>
</widget>
</widget>
<widget>
<class>GtkFrame</class>
<name>frame2</name>
<label>Choose a format</label>
<label_xalign>0</label_xalign>
<shadow_type>GTK_SHADOW_ETCHED_IN</shadow_type>
<child>
<padding>0</padding>
<expand>True</expand>
<fill>True</fill>
</child>
<widget>
<class>GtkVBox</class>
<name>vbox8</name>
<border_width>5</border_width>
<homogeneous>False</homogeneous>
<spacing>15</spacing>
<widget>
<class>GtkLabel</class>
<name>msg_label</name>
<label>do not translate</label>
<justify>GTK_JUSTIFY_CENTER</justify>
<wrap>True</wrap>
<xalign>0.5</xalign>
<yalign>0.5</yalign>
<xpad>0</xpad>
<ypad>0</ypad>
<child>
<padding>0</padding>
<expand>False</expand>
<fill>False</fill>
</child>
</widget>
<widget>
<class>GtkHBox</class>
<name>hbox1</name>
<homogeneous>False</homogeneous>
<spacing>3</spacing>
<child>
<padding>0</padding>
<expand>True</expand>
<fill>True</fill>
</child>
<widget>
<class>GtkLabel</class>
<name>label847791</name>
<label>Format:</label>
<justify>GTK_JUSTIFY_LEFT</justify>
<wrap>False</wrap>
<xalign>1</xalign>
<yalign>0.5</yalign>
<xpad>0</xpad>
<ypad>0</ypad>
<child>
<padding>0</padding>
<expand>False</expand>
<fill>False</fill>
</child>
</widget>
<widget>
<class>GtkHBox</class>
<name>menu_box</name>
<homogeneous>False</homogeneous>
<spacing>0</spacing>
<child>
<padding>0</padding>
<expand>True</expand>
<fill>True</fill>
</child>
<widget>
<class>Placeholder</class>
</widget>
</widget>
</widget>
</widget>
</widget>
</widget>
</widget>
</GTK-Interface>

View File

@ -0,0 +1,119 @@
/*
* import-format-dialog.c -- provides a UI to ask for users to resolve ambiguities.
*
* Created by: Derek Atkins <derek@ihtfp.com>
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <glib.h>
#include <glade/glade.h>
#include "import-parse.h"
#include "dialog-utils.h"
#include "gnc-ui-util.h"
#define MAX_CHOICES 6
static void
choice_option_changed (GtkWidget *widget, gint index, gpointer index_p)
{
gint *my_index = index_p;
*my_index = index;
}
static GncImportFormat
add_menu_and_run_dialog(GtkWidget *dialog, GtkWidget *menu_box, GncImportFormat fmt)
{
GtkWidget *menu;
gint index = 0, count = 0;
GncImportFormat formats[MAX_CHOICES];
GNCOptionInfo menus[MAX_CHOICES];
memset(&menus, 0, sizeof(menus));
if (fmt & GNCIF_NUM_PERIOD) {
formats[count] = GNCIF_NUM_PERIOD;
menus[count].name = _("Period: 123,456.78");
menus[count].callback = choice_option_changed;
menus[count].user_data = &index;
count++;
}
if (fmt & GNCIF_NUM_COMMA) {
formats[count] = GNCIF_NUM_COMMA;
menus[count].name = _("Comma: 123.456,78");
menus[count].callback = choice_option_changed;
menus[count].user_data = &index;
count++;
}
if (fmt & GNCIF_DATE_MDY) {
formats[count] = GNCIF_DATE_MDY;
menus[count].name = _("m/d/y");
menus[count].callback = choice_option_changed;
menus[count].user_data = &index;
count++;
}
if (fmt & GNCIF_DATE_DMY) {
formats[count] = GNCIF_DATE_DMY;
menus[count].name = _("d/m/y");
menus[count].callback = choice_option_changed;
menus[count].user_data = &index;
count++;
}
if (fmt & GNCIF_DATE_YMD) {
formats[count] = GNCIF_DATE_YMD;
menus[count].name = _("y/m/d");
menus[count].callback = choice_option_changed;
menus[count].user_data = &index;
count++;
}
if (fmt & GNCIF_DATE_YDM) {
formats[count] = GNCIF_DATE_YDM;
menus[count].name = _("y/d/m");
menus[count].callback = choice_option_changed;
menus[count].user_data = &index;
count++;
}
g_assert(count > 1);
menu = gnc_build_option_menu(menus, count);
gtk_box_pack_start(GTK_BOX(menu_box), menu, TRUE, TRUE, 0);
gtk_widget_show_all(dialog);
gnome_dialog_run_and_close(GNOME_DIALOG(dialog));
return formats[index];
}
GncImportFormat
gnc_import_choose_fmt(const char* msg, GncImportFormat fmts, gpointer data)
{
GladeXML *xml;
GtkWidget *dialog;
GtkWidget *widget;
g_return_val_if_fail(fmts, FALSE);
/* if there is only one format availble, just return it */
if (!(fmts & (fmts-1))) {
return fmts;
}
xml = gnc_glade_xml_new("generic-import.glade", "format_picker");
dialog = glade_xml_get_widget(xml, "format_picker");
widget = glade_xml_get_widget(xml, "msg_label");
gtk_label_set_text(GTK_LABEL(widget), msg);
widget = glade_xml_get_widget(xml, "menu_box");
return add_menu_and_run_dialog(dialog, widget, fmts);
}

View File

@ -0,0 +1,363 @@
/*
* import-parse.c -- a generic "parser" API for importers.. Allows importers
* to parse dates and numbers, and provides a UI to ask for users to
* resolve ambiguities.
*
* Created by: Derek Atkins <derek@ihtfp.com>
*
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <glib.h>
#include <string.h>
/* For regex */
#include <sys/types.h>
#include <regex.h>
#include "gnc-engine-util.h"
#include "gnc-ui-util.h"
#include "import-parse.h"
static short module = MOD_IMPORT;
/* numeric regular expressions */
static regex_t decimal_radix_regex;
static regex_t comma_radix_regex;
/* date regular expressions */
static regex_t date_regex;
static regex_t date_mdy_regex;
static regex_t date_ymd_regex;
static gboolean regex_compiled = FALSE;
static void
compile_regex(void)
{
int flags = REG_EXTENDED;
/* compile the numeric regular expressions */
regcomp(&decimal_radix_regex,
"^ *\\$?[+-]?\\$?[0-9]+ *$|^ *\\$?[+-]?\\$?[0-9]?[0-9]?[0-9]?(,[0-9][0-9][0-9])*(\\.[0-9]*)? *$|^ *\\$?[+-]?\\$?[0-9]+\\.[0-9]* *$", flags);
regcomp(&comma_radix_regex,
"^ *\\$?[+-]?\\$?[0-9]+ *$|^ *\\$?[+-]?\\$?[0-9]?[0-9]?[0-9]?(\\.[0-9][0-9][0-9])*(,[0-9]*)? *$|^ *\\$?[+-]?\\$?[0-9]+,[0-9]* *$", flags);
/* compile the date-parsing regular expressions */
regcomp(&date_regex,
"^ *([0-9]+) *[-/.'] *([0-9]+) *[-/.'] *([0-9]+).*$|^ *([0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]).*$", flags);
regcomp(&date_mdy_regex, "([0-9][0-9])([0-9][0-9])([0-9][0-9][0-9][0-9])", flags);
regcomp(&date_ymd_regex, "([0-9][0-9][0-9][0-9])([0-9][0-9])([0-9][0-9])", flags);
regex_compiled = TRUE;
}
static gint
my_strntol(const char *str, int len)
{
gint res = 0;
g_return_val_if_fail(str, 0);
g_return_val_if_fail(len, 0);
while(len--) {
if (*str < '0' || *str > '9') {
str++;
continue;
}
res *= 10;
res += *(str++) - '0';
}
return res;
}
/*
* based on a trio match (matches in spaces 1, 2, and 3), and a list
* of possible date formats, return the list of formats that this string
* could actually be.
*/
static GncImportFormat
check_date_format(const char * str, regmatch_t *match, GncImportFormat fmts)
{
GncImportFormat res = 0;
int len0=0, len1=0, len2=0;
int val0=0, val1=0, val2=0;
g_return_val_if_fail(match, res);
g_return_val_if_fail(fmts, res);
/* Compute the lengths */
len0 = match[1].rm_eo - match[1].rm_so;
len1 = match[2].rm_eo - match[2].rm_so;
len2 = match[3].rm_eo - match[3].rm_so;
/* compute the numeric values */
val0 = my_strntol(str + match[1].rm_so, len0);
val1 = my_strntol(str + match[2].rm_so, len1);
val2 = my_strntol(str + match[3].rm_so, len2);
/* Filter out the possibilities. Hopefully only one will remain */
if (val0 > 12) import_clear_flag(fmts, GNCIF_DATE_MDY);
if (val0 > 31) import_clear_flag(fmts, GNCIF_DATE_DMY);
if (val0 < 1) {
import_clear_flag(fmts, GNCIF_DATE_DMY);
import_clear_flag(fmts, GNCIF_DATE_MDY);
}
if (val1 > 12) {
import_clear_flag(fmts, GNCIF_DATE_DMY);
import_clear_flag(fmts, GNCIF_DATE_YMD);
}
if (val1 > 31) {
import_clear_flag(fmts, GNCIF_DATE_MDY);
import_clear_flag(fmts, GNCIF_DATE_YDM);
}
if (val2 > 12) import_clear_flag(fmts, GNCIF_DATE_YDM);
if (val2 > 31) import_clear_flag(fmts, GNCIF_DATE_YMD);
if (val2 < 1) {
import_clear_flag(fmts, GNCIF_DATE_YMD);
import_clear_flag(fmts, GNCIF_DATE_YDM);
}
/* if we've got a 4-character year, make sure the value is greater
* than 1930 and less than 2100. XXX: be sure to fix this by 2100!
*/
if (len0 == 4 && (val0 < 1930 || val0 > 2100)) {
import_clear_flag(fmts, GNCIF_DATE_YMD);
import_clear_flag(fmts, GNCIF_DATE_YDM);
}
if (len2 == 4 && (val2 < 1930 || val2 > 2100)) {
import_clear_flag(fmts, GNCIF_DATE_MDY);
import_clear_flag(fmts, GNCIF_DATE_DMY);
}
/* If the first string has a length of only 1, then it is definitely
* not a year (although it could be a month or day).
*/
if (len0 == 1) {
import_clear_flag(fmts, GNCIF_DATE_YMD);
import_clear_flag(fmts, GNCIF_DATE_YDM);
}
return fmts;
}
GncImportFormat
gnc_import_test_numeric(const char* str, GncImportFormat fmts)
{
GncImportFormat res = 0;
g_return_val_if_fail(str, fmts);
if (!regex_compiled)
compile_regex();
if ((fmts & GNCIF_NUM_PERIOD) && !regexec(&decimal_radix_regex, str, 0, NULL, 0))
res |= GNCIF_NUM_PERIOD;
if ((fmts & GNCIF_NUM_COMMA) && !regexec(&comma_radix_regex, str, 0, NULL, 0))
res |= GNCIF_NUM_COMMA;
return res;
}
GncImportFormat
gnc_import_test_date(const char* str, GncImportFormat fmts)
{
regmatch_t match[5];
GncImportFormat res = 0;
g_return_val_if_fail(str, fmts);
g_return_val_if_fail(strlen(str) > 1, fmts);
if (!regex_compiled)
compile_regex();
if (!regexec(&date_regex, str, 5, match, 0)) {
if (match[1].rm_so != -1)
res = check_date_format(str, match, fmts);
else {
/* Hmm, it matches XXXXXXXX, but is this YYYYxxxx or xxxxYYYY?
* let's try both ways and let the parser check that YYYY is
* valid.
*/
char temp[9];
g_return_val_if_fail(match[4].rm_so != -1, fmts);
g_return_val_if_fail(match[4].rm_eo - match[4].rm_so == 8, fmts);
/* make a temp copy of the XXXXXXXX string */
strncpy(temp, str+match[4].rm_so, 8);
temp[8] = '\0';
/* then check it against the ymd or mdy formats, as necessary */
if (((fmts & GNCIF_DATE_YDM) || (fmts & GNCIF_DATE_YMD)) &&
!regexec(&date_ymd_regex, temp, 4, match, 0))
res |= check_date_format(temp, match, fmts);
if (((fmts & GNCIF_DATE_DMY) || (fmts & GNCIF_DATE_MDY)) &&
!regexec(&date_mdy_regex, temp, 4, match, 0))
res |= check_date_format(temp, match, fmts);
}
}
return res;
}
gboolean
gnc_import_parse_numeric(const char* str, GncImportFormat fmt, gnc_numeric *val)
{
g_return_val_if_fail(str, FALSE);
g_return_val_if_fail(val, FALSE);
g_return_val_if_fail(fmt, FALSE);
g_return_val_if_fail(!(fmt & (fmt-1)), FALSE);
switch(fmt) {
case GNCIF_NUM_PERIOD:
return xaccParseAmountExtended(str, TRUE, '-', '.', ',', NULL, "$+",
val, NULL);
case GNCIF_NUM_COMMA:
return xaccParseAmountExtended(str, TRUE, '-', ',', '.', NULL, "$+",
val, NULL);
default:
PERR("invalid format: %d", fmt);
return FALSE;
}
}
/* Handle y2k fixes, etc.
* obtaining the year "00", "2000", and "19100" all mean the same thing.
* output is an integer representing the year in the C.E.
*/
static int
fix_year(int y)
{
/* two-digit numbers less than "70" are interpretted to be post-2000. */
if (y < 70)
return (y + 2000);
/* fix a common bug in printing post-2000 dates as 19100, etc. */
if (y > 19000)
return (1900 + (y - 19000));
/* At this point we just want to make sure that this is a real date.
* y _should_ be a 'unix year' (which is the number of years since
* 1900), but it _COULD_ be a full date (1999, 2001, etc.). At some
* point in the future we can't tell the difference, but are we really
* going to care if this code fails in 3802?
*/
if (y < 1902)
return (y + 1900);
/* y is good as it is */
return y;
}
gboolean
gnc_import_parse_date(const char *str, GncImportFormat fmt, Timespec *val)
{
regmatch_t match[5];
char temp[9];
const char *datestr;
int v0 = 0, v1=0, v2=0;
int m=0, d=0, y=0;
g_return_val_if_fail(str, FALSE);
g_return_val_if_fail(val, FALSE);
g_return_val_if_fail(fmt, FALSE);
g_return_val_if_fail(!(fmt & (fmt-1)), FALSE);
if (!regexec(&date_regex, str, 5, match, 0)) {
if (match[1].rm_so != -1)
datestr = str;
else {
/* date is of the form XXXXXXX; save it to a temp string and
* split it based on the format, either YYYYaabb or aabbYYYY
*/
g_return_val_if_fail(match[4].rm_so != -1, FALSE);
g_return_val_if_fail(match[4].rm_eo - match[4].rm_so == 8, FALSE);
strncpy(temp, str+match[4].rm_so, 8);
temp[8] = '\0';
switch (fmt) {
case GNCIF_DATE_DMY:
case GNCIF_DATE_MDY:
g_return_val_if_fail(!regexec(&date_mdy_regex, temp, 4, match, 0), FALSE);
break;
case GNCIF_DATE_YMD:
case GNCIF_DATE_YDM:
g_return_val_if_fail(!regexec(&date_ymd_regex, temp, 4, match, 0), FALSE);
break;
default:
PERR("Invalid date format provided: %d", fmt);
return FALSE;
}
datestr = temp;
}
/* datestr points to the date string, and match[123] contains the matches. */
if (match[1].rm_so == -1 || match[2].rm_so == -1 || match[3].rm_so == -1) {
PERR("can't interpret date %s", str);
return FALSE;
}
/* grab the numerics */
v0 = my_strntol(datestr + match[1].rm_so, match[1].rm_eo-match[1].rm_so);
v1 = my_strntol(datestr + match[2].rm_so, match[2].rm_eo-match[2].rm_so);
v2 = my_strntol(datestr + match[3].rm_so, match[3].rm_eo-match[3].rm_so);
switch (fmt) {
case GNCIF_DATE_DMY:
if (v0 > 0 && v0 <= 31 && v1 > 0 && v1 <= 12 && v2 > 0) {
d = v0; m = v1; y = v2;
} else
PERR("format is d/m/y but date is %s", str);
break;
case GNCIF_DATE_MDY:
if (v0 > 0 && v0 <= 12 && v1 > 0 && v1 <= 31 && v2 > 0) {
m = v0; d = v1; y = v2;
} else
PERR("format is m/d/y but date is %s", str);
break;
case GNCIF_DATE_YMD:
if (v0 > 0 && v1 > 0 && v1 <= 12 && v2 > 0 && v2 <= 31) {
y = v0; m = v1; d = v2;
} else
PERR("format is y/m/d but date is %s", str);
break;
case GNCIF_DATE_YDM:
if (v0 > 0 && v1 > 0 && v1 <= 31 && v2 > 0 && v2 <= 12) {
y = v0; d = v1; m = v2;
} else
PERR("format is y/d/m but date is %s", str);
break;
default:
PERR("invalid date format: %d", fmt);
}
if (!m || !d || !y)
return FALSE;
y = fix_year(y);
*val = gnc_dmy2timespec(d, m, y);
return TRUE;
}
return FALSE;
}

View File

@ -0,0 +1,46 @@
/*
* import-parse.h -- a generic "parser" API for importers.. Allows importers
* to parse dates and numbers, and provides a UI to ask for users to
* resolve ambiguities.
*
* Created by: Derek Atkins <derek@ihtfp.com>
*
*/
#ifndef IMPORT_PARSE_H
#define IMPORT_PARSE_H
#include "gnc-numeric.h"
#include "gnc-date.h"
typedef enum {
/* number formats */
GNCIF_NUM_PERIOD = (1 << 1),
GNCIF_NUM_COMMA = (1 << 2),
/* date formats */
GNCIF_DATE_MDY = (1 << 8),
GNCIF_DATE_DMY = (1 << 9),
GNCIF_DATE_YMD = (1 << 10),
GNCIF_DATE_YDM = (1 << 11)
} GncImportFormat;
GncImportFormat gnc_import_test_numeric(const char* str, GncImportFormat fmts);
GncImportFormat gnc_import_test_date(const char* str, GncImportFormat fmts);
GncImportFormat gnc_import_choose_fmt(const char* msg, GncImportFormat fmts,
gpointer user_data);
gboolean gnc_import_parse_numeric(const char* str, GncImportFormat fmt,
gnc_numeric *val);
gboolean gnc_import_parse_date(const char *date, GncImportFormat fmt,
Timespec *val);
/* Set and clear flags in bit-flags */
#define import_set_flag(i,f) (i |= f)
#define import_clear_flag(i,f) (i &= ~f)
#endif /* IMPORT_PARSE_H */

View File

@ -1,6 +1,45 @@
TESTS=test-link
AM_CFLAGS = \
-I${top_srcdir}/src \
-I${top_srcdir}/src/gnc-module \
-I${top_srcdir}/src/test-core \
-I${top_srcdir}/src/engine \
-I${top_srcdir}/src/app-utils \
-I${top_srcdir}/src/import-export \
${GUILE_INCS} \
${GLIB_CFLAGS}
noinst_PROGRAMS=test-link
LDADD = \
${top_builddir}/src/gnc-module/libgncmodule.la \
${top_builddir}/src/test-core/libgncmod-test.la \
../libgncmod-generic-import.la \
${GLIB_LIBS}
test_link_SOURCES=test-link.c
test_link_LDADD=../libgncmod-generic-import.la
TESTS = \
test-link \
test-import-parse
GNC_TEST_DEPS := @GNC_TEST_SRFI_LOAD_CMD@ \
--gnc-module-dir ${top_builddir}/src/gnc-module \
--gnc-module-dir ${top_builddir}/src/engine \
--gnc-module-dir ${top_builddir}/src/app-utils \
--gnc-module-dir ${top_builddir}/src/import-export \
--gnc-module-dir ${top_builddir}/src/calculation \
--gnc-module-dir ${top_builddir}/src/gnome-utils \
--gnc-module-dir ${top_srcdir}/src/gnc-module \
--gnc-module-dir ${top_srcdir}/src/engine \
--gnc-module-dir ${top_srcdir}/src/app-utils \
--gnc-module-dir ${top_srcdir}/src/gnome-utils \
--gnc-module-dir ${top_builddir}/src/gnome-utils \
--gnc-module-dir ${top_builddir}/src/network-utils \
--gnc-module-dir ${top_builddir}/src/gnome \
--library-dir ${G_WRAP_LIB_DIR} \
--guile-load-dir ${G_WRAP_MODULE_DIR} \
--guile-load-dir ${top_srcdir}/src/scm \
--guile-load-dir ${top_srcdir}/src/import-export
TESTS_ENVIRONMENT := \
$(shell ${top_srcdir}/src/gnc-test-env --no-exports ${GNC_TEST_DEPS})
noinst_PROGRAMS = \
test-link \
test-import-parse

View File

@ -0,0 +1,163 @@
/*
* test-import-parse.c -- Test the import-parse routines.
*
* Created by: Derek Atkins <derek@ihtfp.com>
*
*/
#include <glib.h>
#include <libguile.h>
#include "gnc-module.h"
#include "import-parse.h"
#include "test-stuff.h"
typedef struct { int y; int m; int d; } my_ymd_t;
/* make sure the numbers are the same in the two sets of lists */
const char* period_numbers[] = { " $+2000.00", "-2.00", "1,182,183.1827", NULL };
const char* comma_numbers[] = { " $2000,00", "-2,00", "1.182.183,1827", NULL };
const char* period_numbers_ambig[] = { " -$1,000 ", "100.277", NULL };
const char* comma_numbers_ambig[] = { " -$1.000 ", "100,277", NULL };
/* Make sure the strings and numbers match... */
const char* dates_ymd[] = { "1999/12/31", "2001-6-17", "20020726", NULL };
my_ymd_t dates_ymd_vals[] = { {1999,12,31}, {2001,6,17}, {2002,7,26}, {0,0,0} };
const char* dates_ydm[] = { "1999/31/12", "2001-17-6", "20012311", NULL };
my_ymd_t dates_ydm_vals[] = { {1999,12,31}, {2001,6,17}, {2001,11,23}, {0,0,0} };
const char* dates_mdy[] = { "1/16/2001", "12-31-1999", "01171983", NULL };
my_ymd_t dates_mdy_vals[] = { {2001,1,16}, {1999,12,31}, {1983,1,17}, {0,0,0} };
const char* dates_dmy[] = { "16/1/2001", "31-12-1999", "17011976", NULL };
my_ymd_t dates_dmy_vals[] = { {2001,1,16}, {1999,12,31}, {1976,1,17}, {0,0,0} };
const char* dates_yxx[] = { "99/1/6", "1999-12'10", "20010306", NULL };
const char* dates_xxy[] = { "1/3/99", "12-10'1999", "03062001", NULL };
static void
run_check(GncImportFormat (*check_fcn)(const char*, GncImportFormat),
const char *numbers[], GncImportFormat formats,
const char* msg, GncImportFormat expected)
{
while (*numbers) {
do_test(check_fcn(*numbers, formats) == expected, msg);
numbers++;
}
}
static void
test_check_numeric(void)
{
GncImportFormat fmts;
fmts = GNCIF_NUM_PERIOD | GNCIF_NUM_COMMA | GNCIF_DATE_MDY;
run_check(gnc_import_test_numeric, period_numbers, fmts,
"Period numbers", GNCIF_NUM_PERIOD);
run_check(gnc_import_test_numeric, comma_numbers, fmts,
"Comma numbers", GNCIF_NUM_COMMA);
run_check(gnc_import_test_numeric, period_numbers_ambig, fmts,
"Ambiguous Period numbers", GNCIF_NUM_PERIOD | GNCIF_NUM_COMMA);
run_check(gnc_import_test_numeric, comma_numbers_ambig, fmts,
"Ambiguous Comma numbers", GNCIF_NUM_PERIOD | GNCIF_NUM_COMMA);
}
static void
test_check_date(void)
{
GncImportFormat fmts;
fmts = GNCIF_DATE_DMY | GNCIF_DATE_MDY | GNCIF_DATE_YMD | GNCIF_DATE_YDM;
run_check(gnc_import_test_date, dates_ymd, fmts, "y/m/d dates", GNCIF_DATE_YMD);
run_check(gnc_import_test_date, dates_ydm, fmts, "y/d/m dates", GNCIF_DATE_YDM);
run_check(gnc_import_test_date, dates_mdy, fmts, "m/d/y dates", GNCIF_DATE_MDY);
run_check(gnc_import_test_date, dates_dmy, fmts, "d/m/y dates", GNCIF_DATE_DMY);
run_check(gnc_import_test_date, dates_yxx, fmts, "y/x/x dates",
GNCIF_DATE_YMD | GNCIF_DATE_YDM);
run_check(gnc_import_test_date, dates_xxy, fmts, "x/x/y dates",
GNCIF_DATE_DMY | GNCIF_DATE_MDY);
}
static void
test_numbers(const char* pstr, const char* cstr)
{
gnc_numeric pval, cval;
do_test(gnc_import_parse_numeric(pstr, GNCIF_NUM_PERIOD, &pval), "Parsing Period");
do_test(gnc_import_parse_numeric(cstr, GNCIF_NUM_COMMA, &cval), "Parsing Comma");
do_test(gnc_numeric_equal(pval, cval), "Numbers equal?");
}
static void
test_number_strings(const char** pstrs, const char** cstrs)
{
while (*pstrs && *cstrs) {
test_numbers(*pstrs, *cstrs);
pstrs++;
cstrs++;
}
}
static void
test_parse_numeric(void)
{
test_number_strings(period_numbers, comma_numbers);
test_number_strings(period_numbers_ambig, comma_numbers_ambig);
}
static void
test_date(const char* str, GncImportFormat fmt, my_ymd_t date)
{
Timespec ts, ts2;;
do_test(gnc_import_parse_date(str, fmt, &ts), "Parsing date");
ts2 = gnc_dmy2timespec(date.d, date.m, date.y);
do_test(timespec_equal(&ts, &ts2), "Date Equal");
}
static void
test_date_list(const char** strs, GncImportFormat fmt, my_ymd_t *dates)
{
while (*strs && (*dates).y) {
test_date(*strs, fmt, *dates);
strs++; dates++;
}
}
static void
test_parse_date(void)
{
test_date_list(dates_ymd, GNCIF_DATE_YMD, dates_ymd_vals);
test_date_list(dates_ydm, GNCIF_DATE_YDM, dates_ydm_vals);
test_date_list(dates_mdy, GNCIF_DATE_MDY, dates_mdy_vals);
test_date_list(dates_dmy, GNCIF_DATE_DMY, dates_dmy_vals);
}
static void
test_import_parse(void)
{
test_check_numeric();
test_check_date();
test_parse_numeric();
test_parse_date();
}
static void
main_helper(void *closure, int argc, char **argv)
{
gnc_module_load("gnucash/import-export", 0);
test_import_parse();
print_test_results();
exit(get_rv());
}
int
main(int argc, char **argv)
{
scm_boot_guile(argc, argv, main_helper, NULL);
return 0;
}