mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
* 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:
parent
7b3bb91ed7
commit
06aa3a6845
10
ChangeLog
10
ChangeLog
@ -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
|
||||
|
@ -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
|
||||
|
@ -1092,4 +1092,140 @@ click "Ok".</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>
|
||||
|
119
src/import-export/import-format-dialog.c
Normal file
119
src/import-export/import-format-dialog.c
Normal 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);
|
||||
}
|
363
src/import-export/import-parse.c
Normal file
363
src/import-export/import-parse.c
Normal 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;
|
||||
}
|
46
src/import-export/import-parse.h
Normal file
46
src/import-export/import-parse.h
Normal 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 */
|
@ -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
|
||||
|
163
src/import-export/test/test-import-parse.c
Normal file
163
src/import-export/test/test-import-parse.c
Normal 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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user