diff options
-rw-r--r-- | Makefile.am | 191 | ||||
-rw-r--r-- | NEWS | 25 | ||||
-rwxr-xr-x | acprep | 66 | ||||
-rw-r--r-- | amount.cc | 237 | ||||
-rw-r--r-- | amount.h | 140 | ||||
-rw-r--r-- | balance.cc | 9 | ||||
-rw-r--r-- | balance.h | 28 | ||||
-rw-r--r-- | binary.cc | 448 | ||||
-rw-r--r-- | binary.h | 238 | ||||
-rw-r--r-- | configure.in | 16 | ||||
-rw-r--r-- | csv.cc | 105 | ||||
-rw-r--r-- | csv.h | 24 | ||||
-rw-r--r-- | datetime.cc | 315 | ||||
-rw-r--r-- | datetime.h | 21 | ||||
-rw-r--r-- | debug.cc | 8 | ||||
-rw-r--r-- | debug.h | 4 | ||||
-rw-r--r-- | derive.cc | 30 | ||||
-rw-r--r-- | derive.h | 10 | ||||
-rw-r--r-- | docs/ledger.1 | 79 | ||||
-rw-r--r-- | emacs.cc | 80 | ||||
-rw-r--r-- | emacs.h | 30 | ||||
-rw-r--r-- | error.h | 16 | ||||
-rw-r--r-- | format.cc | 1045 | ||||
-rw-r--r-- | format.h | 259 | ||||
-rw-r--r-- | gnucash.cc | 341 | ||||
-rw-r--r-- | gnucash.h | 72 | ||||
-rw-r--r-- | journal.cc | 457 | ||||
-rw-r--r-- | journal.h | 98 | ||||
-rw-r--r-- | ledger.el | 507 | ||||
-rw-r--r-- | ledger.h | 37 | ||||
-rw-r--r-- | main.cc | 584 | ||||
-rw-r--r-- | mask.cc | 12 | ||||
-rw-r--r-- | mask.h | 4 | ||||
-rw-r--r-- | ofx.cc | 9 | ||||
-rw-r--r-- | ofx.h | 1 | ||||
-rw-r--r-- | option.cc | 1126 | ||||
-rw-r--r-- | option.h | 45 | ||||
-rw-r--r-- | parser.cc | 212 | ||||
-rw-r--r-- | parser.h | 32 | ||||
-rw-r--r-- | py_eval.cc | 205 | ||||
-rw-r--r-- | py_eval.h | 77 | ||||
-rw-r--r-- | pyfstream.h | 146 | ||||
-rw-r--r-- | pyledger.cc | 10 | ||||
-rw-r--r-- | pyledger.h | 16 | ||||
-rw-r--r-- | qif.cc | 5 | ||||
-rw-r--r-- | qif.h | 1 | ||||
-rw-r--r-- | quotes.cc | 4 | ||||
-rw-r--r-- | reconcile.cc | 88 | ||||
-rw-r--r-- | reconcile.h | 33 | ||||
-rw-r--r-- | report.cc | 536 | ||||
-rw-r--r-- | report.h | 172 | ||||
-rw-r--r-- | sample.dat | 6 | ||||
-rw-r--r-- | session.cc | 239 | ||||
-rw-r--r-- | session.h | 185 | ||||
-rwxr-xr-x | setup.py | 24 | ||||
-rw-r--r-- | tests/UnitTests.cc | 111 | ||||
-rw-r--r-- | tests/UnitTests.h | 14 | ||||
-rw-r--r-- | tests/corelib/numerics/#BasicAmountTest.cc# | 425 | ||||
-rw-r--r-- | tests/corelib/numerics/.#BasicAmountTest.cc | 1 | ||||
-rw-r--r-- | tests/corelib/numerics/BasicAmountTest.cc | 345 | ||||
-rw-r--r-- | tests/corelib/numerics/BasicAmountTest.h | 50 | ||||
-rw-r--r-- | textual.cc | 297 | ||||
-rw-r--r-- | textual.h | 8 | ||||
-rw-r--r-- | trace.cc | 187 | ||||
-rw-r--r-- | trace.h | 51 | ||||
-rw-r--r-- | transform.cc | 349 | ||||
-rw-r--r-- | transform.h | 138 | ||||
-rw-r--r-- | util.cc | 161 | ||||
-rw-r--r-- | util.h | 37 | ||||
-rw-r--r-- | value.cc | 1165 | ||||
-rw-r--r-- | value.h | 206 | ||||
-rw-r--r-- | xml.cc | 679 | ||||
-rw-r--r-- | xml.h | 329 | ||||
-rw-r--r-- | xmlparse.cc | 473 | ||||
-rw-r--r-- | xmlparse.h | 25 | ||||
-rw-r--r-- | xpath.cc | 2560 | ||||
-rw-r--r-- | xpath.h | 773 |
77 files changed, 11998 insertions, 5094 deletions
diff --git a/Makefile.am b/Makefile.am index 767a5480..fdd1f673 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,40 +1,77 @@ -lib_LTLIBRARIES = libamounts.la libledger.la - -libamounts_la_CXXFLAGS = -libamounts_la_SOURCES = \ - amount.cc \ - balance.cc \ - datetime.cc \ - value.cc -if HAVE_BOOST_PYTHON -libamounts_la_CXXFLAGS += -DUSE_BOOST_PYTHON=1 -endif -if DEBUG -libamounts_la_CXXFLAGS += -DDEBUG_LEVEL=4 -libamounts_la_SOURCES += debug.cc +if USE_PCH +#BUILT_SOURCES = pchpic.h.gch pchnopic.h.gch +BUILT_SOURCES = pchnopic.h.gch +#CLEANFILES = pchpic.h.gch pchnopic.h.gch +CLEANFILES = pchnopic.h.gch endif +###################################################################### + +#bin_PROGRAMS = xpath +# +#xpath_CXXFLAGS = -DTEST +#xpath_SOURCES = \ +# amount.cc \ +# datetime.cc \ +# quotes.cc \ +# balance.cc \ +# value.cc \ +# mask.cc \ +# xml.cc \ +# xpath.cc \ +# trace.cc \ +# util.cc +#xpath_LDADD = $(LIBOBJS) +#if HAVE_EXPAT +#xpath_CXXFLAGS += -DHAVE_EXPAT=1 +#endif +#if HAVE_XMLPARSE +#xpath_CXXFLAGS += -DHAVE_XMLPARSE=1 +#endif +#if DEBUG +#xpath_CXXFLAGS += -DDEBUG_LEVEL=4 +#xpath_SOURCES += debug.cc +#endif +#xpath_LDFLAGS = -static # for the sake of command-line speed + +###################################################################### + +lib_LTLIBRARIES = libledger.la + libledger_la_CXXFLAGS = +if USE_PCH +libledger_la_CXXFLAGS += -DUSE_PCH -Winvalid-pch -fpch-deps +endif libledger_la_SOURCES = \ - binary.cc \ - config.cc \ - csv.cc \ - derive.cc \ - emacs.cc \ + amount.cc \ + quotes.cc \ + balance.cc \ + value.cc \ + datetime.cc \ + xml.cc \ + xpath.cc \ + mask.cc \ format.cc \ + \ + trace.cc \ + util.cc \ + \ + session.cc \ journal.cc \ - mask.cc \ - option.cc \ parser.cc \ + textual.cc \ + binary.cc \ + xmlparse.cc \ qif.cc \ - quotes.cc \ - reconcile.cc \ + \ report.cc \ - startup.cc \ - textual.cc \ - valexpr.cc \ - walk.cc \ - xml.cc + transform.cc \ + \ + dump.cc \ + csv.cc \ + derive.cc \ + emacs.cc \ + reconcile.cc if HAVE_EXPAT libledger_la_CXXFLAGS += -DHAVE_EXPAT=1 libledger_la_SOURCES += gnucash.cc @@ -47,50 +84,66 @@ if HAVE_LIBOFX libledger_la_CXXFLAGS += -DHAVE_LIBOFX=1 libledger_la_SOURCES += ofx.cc endif +if HAVE_BOOST_PYTHON +libledger_la_CXXFLAGS += -DUSE_BOOST_PYTHON=1 +libledger_la_SOURCES += py_eval.cc +endif if DEBUG libledger_la_CXXFLAGS += -DDEBUG_LEVEL=4 +libledger_la_SOURCES += debug.cc endif -libledger_la_LDFLAGS = -release 2.6 +libledger_la_LDFLAGS = -release 3.0 pkginclude_HEADERS = \ acconf.h \ - \ amount.h \ balance.h \ + binary.h \ + csv.h \ datetime.h \ - value.h \ debug.h \ - util.h \ - \ - binary.h \ - config.h \ - csv.h \ derive.h \ - emacs.h \ - error.h \ + dump.h \ + emacs.h \ + error.h \ format.h \ gnucash.h \ journal.h \ ledger.h \ - mask.h \ + mask.h \ + ofx.h \ option.h \ parser.h \ + py_eval.h \ + pyfstream.h \ + pyledger.h \ qif.h \ quotes.h \ reconcile.h \ report.h \ + session.h \ textual.h \ timing.h \ - valexpr.h \ - walk.h \ - xml.h + trace.h \ + transform.h \ + util.h \ + value.h \ + xml.h \ + xpath.h + +pchpic.h.gch: pch.h pchdata.h $(pkginclude_HEADERS) + $(CXXCOMPILE) $(CXXFLAGS) -DPIC -o $@ pchdata.h + +pchnopic.h.gch: pch.h pchdata.h $(pkginclude_HEADERS) + $(CXXCOMPILE) $(CXXFLAGS) -o $@ pchdata.h ###################################################################### bin_PROGRAMS = ledger + ledger_CXXFLAGS = -ledger_SOURCES = main.cc -ledger_LDADD = $(LIBOBJS) libamounts.la libledger.la +ledger_SOURCES = option.cc main.cc +ledger_LDADD = $(LIBOBJS) libledger.la if HAVE_EXPAT ledger_CXXFLAGS += -DHAVE_EXPAT=1 endif @@ -100,6 +153,9 @@ endif if HAVE_LIBOFX ledger_CXXFLAGS += -DHAVE_LIBOFX=1 endif +if HAVE_BOOST_PYTHON +ledger_CXXFLAGS += -DUSE_BOOST_PYTHON=1 +endif if DEBUG ledger_CXXFLAGS += -DDEBUG_LEVEL=4 endif @@ -109,16 +165,16 @@ info_TEXINFOS = ledger.texi ###################################################################### -lisp_LISP = ledger.el timeclock.el -dist_lisp_LISP = ledger.el timeclock.el +#lisp_LISP = ledger.el timeclock.el +#dist_lisp_LISP = ledger.el timeclock.el ###################################################################### if HAVE_BOOST_PYTHON -noinst_PROGRAMS = amounts.so +noinst_PROGRAMS = ledger.so -amounts.so: amounts.cc libamounts.la +ledger.so: pyledger.cc libledger.la CFLAGS="$(CPPFLAGS)" LDFLAGS="$(LDFLAGS) -L. -L.libs" \ python setup.py build --build-lib=. @@ -130,41 +186,34 @@ endif ###################################################################### -TESTS = alltests +TESTS = UnitTests + +check_PROGRAMS = $(TESTS) -CXXTEST_DIR = /usr/local/cxxtest -TESTGEN = $(CXXTEST_DIR)/cxxtestgen.py -TESTSUITES = tests/*.h +UnitTests_SOURCES = tests/UnitTests.cc \ + tests/corelib/numerics/BasicAmountTest.cc -AM_CXXFLAGS = +UnitTests_LDADD = $(lib_LTLIBRARIES) -lcppunit +UnitTests_LDFLAGS = $(LIBADD_DL) + +UnitTests_CXXFLAGS = -Itests if HAVE_EXPAT -AM_CXXFLAGS += -DHAVE_EXPAT=1 +UnitTests_CXXFLAGS += -DHAVE_EXPAT=1 endif if HAVE_XMLPARSE -AM_CXXFLAGS += -DHAVE_XMLPARSE=1 +UnitTests_CXXFLAGS += -DHAVE_XMLPARSE=1 endif if HAVE_LIBOFX -AM_CXXFLAGS += -DHAVE_LIBOFX=1 +UnitTests_CXXFLAGS += -DHAVE_LIBOFX=1 endif if DEBUG -AM_CXXFLAGS += -DDEBUG_LEVEL=4 +UnitTests_CXXFLAGS += -DDEBUG_LEVEL=4 endif -alltests.cc: $(TESTSUITES) - test -f $(TESTGEN) && python $(TESTGEN) -o $@ --error-printer $(TESTSUITES) - -alltests: alltests.cc ledger - $(CXXCOMPILE) -I$(CXXTEST_DIR) -lexpat -lgmp -lpcre -o $@ \ - alltests.cc -L. -L.libs -lamounts -lledger - -runtests: alltests - LD_LIBRARY_PATH=.libs ./alltests && tests/regress && tests/regtest - -verify: runtests - python tests/runtests.py - ###################################################################### +all: check + all-clean: maintainer-clean rm -fr *~ .*~ .\#* *.html *.info *.pdf *.a *.so *.o *.lo *.la \ *.elc *.aux *.cp *.fn *.ky *.log *.pg *.toc *.tp *.vr \ @@ -173,4 +222,4 @@ all-clean: maintainer-clean acconf.h.in aclocal.m4 autom4te config.guess config.sub \ configure depcomp install-sh libtool ltconfig ltmain.sh \ missing stamp texinfo.tex Makefile.in mkinstalldirs \ - elisp-comp elc-stamp py-compile + elisp-comp elc-stamp py-compile *.gch UnitTests @@ -1,6 +1,6 @@ Ledger NEWS -* 2.6 +* 3.0 - The style for eliding long account names (for example, in the register report) has been changed. Previously Ledger would elide @@ -86,13 +86,13 @@ --only "a>100" - This flag happens much later than --limit, and corresponding - more directly to what one normally expects. If --limit isn't - used, then ALL your dining expenses contribute to the report, - *but only those calculated transactions whose value is greater - than $100 are used*. This becomes important when doing a - monthly costs report, for example, because it makes the - following command possible: + This flag happens much later than --limit, and corresponds more + directly with what one normally expects. If --limit isn't used, + then ALL your dining expenses contribute to the report, *but + only those calculated transactions whose value is greater than + $100 are used*. This becomes important when doing a monthly + costs report, for example, because it makes the following + command possible: ledger -M --only "a>100" reg ^Expenses:Food @@ -133,7 +133,7 @@ For example, say you request a --monthtly expenses report: - $ ledger --monthly --descend "\$500.00" register ^Expenses + $ ledger --monthly register ^Expenses Now, in one of the reported months you see $500.00 spent on Expenses:Food. You can ask Ledger to "descend" into, and show the @@ -155,7 +155,7 @@ The second command will load significantly faster (usually about six times on my machine). -- There have a few changes to value expression syntax. The most +- There have been a few changes to value expression syntax. The most significant incompatibilities being: * Equality is now ==, not = @@ -181,7 +181,6 @@ --- --- m now a amount - a amount b cost i price d date @@ -218,8 +217,8 @@ These commands can be used to test value expressions, or for doing calculation of commoditized amounts from a script. - A new "--debug" will also dump the resulting parse tree, useful for - submitting bug reports. + A new "--debug" option will also dump the resulting parse tree, + useful for submitting bug reports. - Added new min(x,y) and max(x,y) value expression functions. @@ -17,31 +17,71 @@ else fi autoconf -INCDIRS="-I/sw/include -I/sw/include/boost -I/usr/include/httpd/xml" -#INCDIRS="$INCDIRS -I/sw/include/libofx" -INCDIRS="$INCDIRS -I/usr/include/python2.3" -INCDIRS="$INCDIRS -Wno-long-double" -LIBDIRS="-L/sw/lib -L/usr/local/lib -L/usr/lib/python2.3/config" +INCDIRS="-I/usr/local/include" +INCDIRS="$INCDIRS -I/usr/local/include/boost" +INCDIRS="$INCDIRS -I/usr/include/httpd/xml" +INCDIRS="$INCDIRS -I/Library/Frameworks/Python.framework/Versions/2.5/include/python2.5" + +LIBDIRS="-L/usr/local/lib" +LIBDIRS="$LIBDIRS -L/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5" + +SYSTEM=`uname -s` +if [ $SYSTEM = Linux ]; then + CXXFLAGS="-pthread" +elif [ $SYSTEM = Solaris ]; then + CXXFLAGS="-pthreads" +elif [ $SYSTEM = Darwin ]; then + CXXFLAGS="-Wno-long-double" +else + CXXFLAGS="" +fi + +# Building the command-line tool as a shared library is a luxury, +# since there are no clients except a GUI tool which might use it (and +# that is built again anyway by Xcode). +SWITCHES="--disable-shared" + +HERE="$PWD" + +#if [ -d "$HOME/Products" ]; then +# projdir="$HOME/Products/$(basename $HERE)" +# if [ ! -d "$projdir" ]; then +# mkdir -p "$projdir" +# fi +# cd "$projdir" || (echo "Cannot change to $projdir"; exit 1) +#fi if [ "$1" = "--debug" ]; then shift 1 - ./configure CPPFLAGS="$INCDIRS" LDFLAGS="$LIBDIRS" CXXFLAGS="-g" \ + "$HERE/configure" --srcdir="$HERE" \ + CPPFLAGS="$INCDIRS" LDFLAGS="$LIBDIRS" CXXFLAGS="-g" $SWITCHES \ + --enable-debug "$@" +elif [ "$1" = "--python-debug" ]; then + shift 1 + "$HERE/configure" --srcdir="$HERE" \ + CPPFLAGS="$INCDIRS" LDFLAGS="$LIBDIRS" CXXFLAGS="-g" $SWITCHES \ --enable-debug --enable-python "$@" elif [ "$1" = "--opt" ]; then shift 1 - ./configure CPPFLAGS="$INCDIRS" LDFLAGS="$LIBDIRS" \ - CXXFLAGS="-fomit-frame-pointer -O3 -mcpu=7450 -fPIC" "$@" + "$HERE/configure" --srcdir="$HERE" \ + CPPFLAGS="$INCDIRS" LDFLAGS="$LIBDIRS" \ + CXXFLAGS="-fomit-frame-pointer -O3 -mcpu=7450 -fPIC" "$@" $SWITCHES elif [ "$1" = "--flat-opt" ]; then shift 1 - ./configure CPPFLAGS="$INCDIRS" LDFLAGS="$LIBDIRS" \ - CXXFLAGS="-fomit-frame-pointer -O3 -mcpu=7450" "$@" + "$HERE/configure" --srcdir="$HERE" \ + CPPFLAGS="$INCDIRS" LDFLAGS="$LIBDIRS" \ + CXXFLAGS="-fomit-frame-pointer -O3 -mcpu=7450" "$@" $SWITCHES elif [ "$1" = "--safe-opt" ]; then shift 1 - ./configure CPPFLAGS="$INCDIRS" LDFLAGS="$LIBDIRS" \ - CXXFLAGS="-fomit-frame-pointer -O3 -mcpu=7450 -fPIC -DDEBUG_LEVEL=1" "$@" + "$HERE/configure" --srcdir="$HERE" \ + CPPFLAGS="$INCDIRS" LDFLAGS="$LIBDIRS" \ + CXXFLAGS="-fomit-frame-pointer -O3 -mcpu=7450 -fPIC -DDEBUG_LEVEL=1" "$@" \ + $SWITCHES elif [ "$1" = "--perf" ]; then shift 1 - ./configure CPPFLAGS="$INCDIRS" LDFLAGS="$LIBDIRS" CXXFLAGS="-g -pg" "$@" + "$HERE/configure" --srcdir="$HERE" \ + CPPFLAGS="$INCDIRS" LDFLAGS="$LIBDIRS" CXXFLAGS="-g -pg" "$@" \ + $SWITCHES fi rm AUTHORS COPYING @@ -1,4 +1,8 @@ +#ifdef USE_PCH +#include "pch.h" +#else #include "amount.h" +#include "binary.h" #include "util.h" #include <list> @@ -6,6 +10,7 @@ #include <cstdlib> #include <gmp.h> +#endif namespace ledger { @@ -19,7 +24,8 @@ bool amount_t::keep_base = false; #define BIGINT_BULK_ALLOC 0x0001 #define BIGINT_KEEP_PREC 0x0002 -class amount_t::bigint_t { +class amount_t::bigint_t +{ public: mpz_t val; unsigned char prec; @@ -28,14 +34,17 @@ class amount_t::bigint_t { unsigned int index; bigint_t() : prec(0), flags(0), ref(1), index(0) { + TRACE_CTOR("bigint_t()"); mpz_init(val); } bigint_t(mpz_t _val) : prec(0), flags(0), ref(1), index(0) { + TRACE_CTOR("bigint_t(mpz_t)"); mpz_init_set(val, _val); } bigint_t(const bigint_t& other) : prec(other.prec), flags(other.flags & BIGINT_KEEP_PREC), ref(1), index(0) { + TRACE_CTOR("bigint_t(copy)"); mpz_init_set(val, other.val); } ~bigint_t(); @@ -47,26 +56,33 @@ unsigned int sizeof_bigint_t() { #define MPZ(x) ((x)->val) +#ifndef THREADSAFE static mpz_t temp; // these are the global temp variables static mpz_t divisor; +#endif static amount_t::bigint_t true_value; inline amount_t::bigint_t::~bigint_t() { + TRACE_DTOR("bigint_t"); assert(ref == 0 || (! do_cleanup && this == &true_value)); mpz_clear(val); } +#ifndef THREADSAFE base_commodities_map commodity_base_t::commodities; commodity_base_t::updater_t * commodity_base_t::updater = NULL; -commodities_map commodity_t::commodities; -bool commodity_t::commodities_sorted = false; -commodity_t * commodity_t::null_commodity; -commodity_t * commodity_t::default_commodity = NULL; +commodities_map commodity_t::commodities; +commodities_array commodity_t::commodities_by_ident; +bool commodity_t::commodities_sorted = false; +commodity_t * commodity_t::null_commodity; +commodity_t * commodity_t::default_commodity = NULL; +#endif -static struct _init_amounts { +static struct _init_amounts +{ _init_amounts() { mpz_init(temp); mpz_init(divisor); @@ -89,16 +105,6 @@ static struct _init_amounts { parse_conversion("1.0m", "60s"); parse_conversion("1.0h", "60m"); - -#if 0 - commodity = commodity_t::create("b"); - commodity->add_flags(COMMODITY_STYLE_NOMARKET | COMMODITY_STYLE_BUILTIN); - - parse_conversion("1.00 Kb", "1024 b"); - parse_conversion("1.00 Mb", "1024 Kb"); - parse_conversion("1.00 Gb", "1024 Mb"); - parse_conversion("1.00 Tb", "1024 Gb"); -#endif } ~_init_amounts() { @@ -119,6 +125,7 @@ static struct _init_amounts { delete (*i).second; commodity_t::commodities.clear(); + commodity_t::commodities_by_ident.clear(); true_value.ref--; } @@ -172,6 +179,7 @@ static void mpz_round(mpz_t out, mpz_t value, int value_prec, int round_prec) amount_t::amount_t(const bool value) { + TRACE_CTOR("amount_t(const bool)"); if (value) { quantity = &true_value; quantity->ref++; @@ -183,6 +191,7 @@ amount_t::amount_t(const bool value) amount_t::amount_t(const long value) { + TRACE_CTOR("amount_t(const long)"); if (value != 0) { quantity = new bigint_t; mpz_set_si(MPZ(quantity), value); @@ -194,6 +203,7 @@ amount_t::amount_t(const long value) amount_t::amount_t(const unsigned long value) { + TRACE_CTOR("amount_t(const unsigned long)"); if (value != 0) { quantity = new bigint_t; mpz_set_ui(MPZ(quantity), value); @@ -203,11 +213,34 @@ amount_t::amount_t(const unsigned long value) commodity_ = NULL; } +namespace { + unsigned char convert_double(mpz_t dest, double value) + { + mpf_t temp; + mpf_init_set_d(temp, value); + + mp_exp_t exp; + char * buf = mpf_get_str(NULL, &exp, 10, 10, temp); + + int len = std::strlen(buf); + if (len > 0 && buf[0] == '-') + exp++; + + exp = len - exp; + + mpz_set_str(dest, buf, 10); + free(buf); + + return (unsigned char)exp; + } +} + amount_t::amount_t(const double value) { + TRACE_CTOR("amount_t(const double)"); if (value != 0.0) { quantity = new bigint_t; - mpz_set_d(MPZ(quantity), value); + quantity->prec = convert_double(MPZ(quantity), value); } else { quantity = NULL; } @@ -342,7 +375,7 @@ amount_t& amount_t::operator=(const double value) } else { commodity_ = NULL; _init(); - mpz_set_d(MPZ(quantity), value); + quantity->prec = convert_double(MPZ(quantity), value); } return *this; } @@ -369,6 +402,18 @@ void amount_t::_resize(unsigned int prec) } +void amount_t::_clear() +{ + if (quantity) { + _release(); + quantity = NULL; + commodity_ = NULL; + } else { + assert(! commodity_); + } +} + + amount_t& amount_t::operator+=(const amount_t& amt) { if (! amt.quantity) @@ -451,10 +496,12 @@ amount_t& amount_t::operator*=(const amount_t& amt) mpz_mul(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); quantity->prec += amt.quantity->prec; - unsigned int comm_prec = commodity().precision(); - if (quantity->prec > comm_prec + 6U) { - mpz_round(MPZ(quantity), MPZ(quantity), quantity->prec, comm_prec + 6U); - quantity->prec = comm_prec + 6U; + if (has_commodity()) { + unsigned int comm_prec = commodity().precision(); + if (quantity->prec > comm_prec + 6U) { + mpz_round(MPZ(quantity), MPZ(quantity), quantity->prec, comm_prec + 6U); + quantity->prec = comm_prec + 6U; + } } return *this; @@ -471,15 +518,17 @@ amount_t& amount_t::operator/=(const amount_t& amt) // Increase the value's precision, to capture fractional parts after // the divide. - mpz_ui_pow_ui(divisor, 10, amt.quantity->prec + 6U); + mpz_ui_pow_ui(divisor, 10, amt.quantity->prec + quantity->prec + 6U); mpz_mul(MPZ(quantity), MPZ(quantity), divisor); mpz_tdiv_q(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity)); - quantity->prec += 6U; + quantity->prec += quantity->prec + 6U; - unsigned int comm_prec = commodity().precision(); - if (quantity->prec > comm_prec + 6U) { - mpz_round(MPZ(quantity), MPZ(quantity), quantity->prec, comm_prec + 6U); - quantity->prec = comm_prec + 6U; + if (has_commodity()) { + unsigned int comm_prec = commodity().precision(); + if (quantity->prec > comm_prec + 6U) { + mpz_round(MPZ(quantity), MPZ(quantity), quantity->prec, comm_prec + 6U); + quantity->prec = comm_prec + 6U; + } } return *this; @@ -509,7 +558,7 @@ int amount_t::compare(const amount_t& amt) const if (! amt.quantity) return sign(); - if (commodity() && amt.commodity() && commodity() != amt.commodity()) + if (has_commodity() && amt.commodity() && commodity() != amt.commodity()) throw new amount_error (std::string("Cannot compare amounts with different commodities: ") + commodity().symbol() + " and " + amt.commodity().symbol()); @@ -548,11 +597,11 @@ amount_t::operator bool() const if (! quantity) return false; - if (quantity->prec <= commodity().precision()) { + if (has_commodity() && quantity->prec <= commodity().precision()) { return mpz_sgn(MPZ(quantity)) != 0; } else { mpz_set(temp, MPZ(quantity)); - if (quantity->flags & BIGINT_KEEP_PREC) + if (quantity->flags & BIGINT_KEEP_PREC || ! has_commodity()) mpz_ui_pow_ui(divisor, 10, quantity->prec); else mpz_ui_pow_ui(divisor, 10, quantity->prec - commodity().precision()); @@ -657,12 +706,12 @@ amount_t amount_t::unround() const return temp; } -std::string amount_t::quantity_string() const +void amount_t::print_quantity(std::ostream& out) const { - if (! quantity) - return "0"; - - std::ostringstream out; + if (! quantity) { + out << "0"; + return; + } mpz_t quotient; mpz_t rquotient; @@ -717,8 +766,10 @@ std::string amount_t::quantity_string() const } mpz_set(rquotient, remainder); - if (mpz_sgn(quotient) == 0 && mpz_sgn(rquotient) == 0) - return "0"; + if (mpz_sgn(quotient) == 0 && mpz_sgn(rquotient) == 0) { + out << "0"; + return; + } if (negative) out << "-"; @@ -745,24 +796,22 @@ std::string amount_t::quantity_string() const mpz_clear(quotient); mpz_clear(rquotient); mpz_clear(remainder); - - return out.str(); } -std::ostream& operator<<(std::ostream& _out, const amount_t& amt) +void amount_t::print(std::ostream& _out) const { - if (! amt.quantity) { + if (! quantity) { _out << "0"; - return _out; + return; } - amount_t base(amt); - if (! amount_t::keep_base && amt.commodity().larger()) { - amount_t last(amt); + amount_t base(*this); + if (! amount_t::keep_base && commodity().larger()) { + amount_t last(*this); while (last.commodity().larger()) { last /= *last.commodity().larger(); last.commodity_ = last.commodity().larger()->commodity_; - if (ledger::abs(last) < 1) + if (::abs(last) < 1) break; base = last.round(); } @@ -826,7 +875,7 @@ std::ostream& operator<<(std::ostream& _out, const amount_t& amt) if (mpz_sgn(quotient) == 0 && mpz_sgn(rquotient) == 0) { _out << "0"; - return _out; + return; } if (! (comm.flags() & COMMODITY_STYLE_SUFFIXED)) { @@ -926,7 +975,7 @@ std::ostream& operator<<(std::ostream& _out, const amount_t& amt) if (comm.annotated) { annotated_commodity_t& ann(static_cast<annotated_commodity_t&>(comm)); - assert(&ann.price != &amt); + assert(&ann.price != this); ann.write_annotations(out); } @@ -936,10 +985,10 @@ std::ostream& operator<<(std::ostream& _out, const amount_t& amt) _out << out.str(); - return _out; + return; } -void parse_quantity(std::istream& in, std::string& value) +static void parse_quantity(std::istream& in, std::string& value) { char buf[256]; char c = peek_next_nonws(in); @@ -980,7 +1029,7 @@ int invalid_chars[256] = { /* f0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; -void parse_commodity(std::istream& in, std::string& symbol) +static void parse_commodity(std::istream& in, std::string& symbol) { char buf[256]; char c = peek_next_nonws(in); @@ -1021,7 +1070,8 @@ void parse_annotations(std::istream& in, amount_t& price, // it is at least as large as the base commodity, since the user // may have only specified {$1} or something similar. - if (price.quantity->prec < price.commodity().precision()) + if (price.has_commodity() && + price.quantity->prec < price.commodity().precision()) price = price.round(); // no need to retain individual precision } else if (c == '[') { @@ -1125,7 +1175,7 @@ void amount_t::parse(std::istream& in, unsigned char flags) bool newly_created = false; if (symbol.empty()) { - commodity_ = commodity_t::null_commodity; + commodity_ = NULL; } else { commodity_ = commodity_t::find(symbol); if (! commodity_) { @@ -1155,9 +1205,7 @@ void amount_t::parse(std::istream& in, unsigned char flags) } } else if (last_comma != std::string::npos && - (! commodity_t::default_commodity || - commodity_t::default_commodity->flags() & COMMODITY_STYLE_EUROPEAN)) { - comm_flags |= COMMODITY_STYLE_EUROPEAN; + commodity().flags() & COMMODITY_STYLE_EUROPEAN) { quantity->prec = quant.length() - last_comma - 1; } else if (last_period != std::string::npos && @@ -1170,7 +1218,7 @@ void amount_t::parse(std::istream& in, unsigned char flags) // Set the commodity's flags and precision accordingly - if (newly_created || ! (flags & AMOUNT_PARSE_NO_MIGRATE)) { + if (commodity_ && (newly_created || ! (flags & AMOUNT_PARSE_NO_MIGRATE))) { commodity().add_flags(comm_flags); if (quantity->prec > commodity().precision()) commodity().set_precision(quantity->prec); @@ -1216,12 +1264,6 @@ void amount_t::reduce() } } -void amount_t::parse(const std::string& str, unsigned char flags) -{ - std::istringstream stream(str); - parse(stream, flags); -} - void parse_conversion(const std::string& larger_str, const std::string& smaller_str) { @@ -1241,11 +1283,51 @@ void parse_conversion(const std::string& larger_str, smaller.commodity().set_larger(larger); } +void amount_t::read(std::istream& in) +{ + commodity_t::ident_t ident; + read_binary_long(in, ident); + if (ident == 0xffffffff) + commodity_ = NULL; + else if (ident == 0) + commodity_ = commodity_t::null_commodity; + else + commodity_ = commodity_t::commodities_by_ident[ident - 1]; + + read_quantity(in); +} + +void amount_t::read(char *& data) +{ + commodity_t::ident_t ident; + read_binary_long(data, ident); + if (ident == 0xffffffff) + commodity_ = NULL; + else if (ident == 0) + commodity_ = commodity_t::null_commodity; + else + commodity_ = commodity_t::commodities_by_ident[ident - 1]; + + read_quantity(data); +} + +void amount_t::write(std::ostream& out) const +{ + if (commodity_) + write_binary_long(out, commodity_->ident); + else + write_binary_long<commodity_t::ident_t>(out, 0xffffffff); + + write_quantity(out); +} + -char * bigints; -char * bigints_next; -unsigned int bigints_index; -unsigned int bigints_count; +#ifndef THREADSAFE +static char * bigints; +static char * bigints_next; +static unsigned int bigints_index; +static unsigned int bigints_count; +#endif void amount_t::read_quantity(char *& data) { @@ -1284,7 +1366,9 @@ void amount_t::read_quantity(char *& data) } } +#ifndef THREADSAFE static char buf[4096]; +#endif void amount_t::read_quantity(std::istream& in) { @@ -1567,6 +1651,9 @@ commodity_t * commodity_t::create(const std::string& symbol) if (! result.second) return NULL; + commodity->ident = commodities_by_ident.size(); + commodities_by_ident.push_back(commodity.get()); + // Start out the new commodity with the default commodity's flags // and precision, if one has been defined. if (default_commodity) @@ -1714,6 +1801,9 @@ annotated_commodity_t::create(const commodity_t& comm, if (! result.second) return NULL; + commodity->ident = commodities_by_ident.size(); + commodities_by_ident.push_back(commodity.get()); + return commodity.release(); } @@ -1837,16 +1927,19 @@ bool compare_amount_commodities::operator()(const amount_t * left, #ifdef USE_BOOST_PYTHON +#ifndef USE_PCH #include <boost/python.hpp> #include <Python.h> +#endif using namespace boost::python; using namespace ledger; int py_amount_quantity(amount_t& amount) { - std::string quant = amount.quantity_string(); - return std::atol(quant.c_str()); + std::ostringstream quant; + amount.print_quantity(quant); + return std::atol(quant.str().c_str()); } void py_parse_1(amount_t& amount, const std::string& str, @@ -1967,7 +2060,6 @@ void export_amount() class_< commodity_t > ("Commodity") .add_property("symbol", &commodity_t::symbol) -#if 0 .add_property("name", &commodity_t::name, &commodity_t::set_name) .add_property("note", &commodity_t::note, &commodity_t::set_note) .add_property("precision", &commodity_t::precision, @@ -1975,9 +2067,7 @@ void export_amount() .add_property("flags", &commodity_t::flags, &commodity_t::set_flags) .add_property("add_flags", &commodity_t::add_flags) .add_property("drop_flags", &commodity_t::drop_flags) -#if 0 .add_property("updater", &commodity_t::updater) -#endif .add_property("smaller", make_getter(&commodity_t::smaller, @@ -1995,7 +2085,6 @@ void export_amount() .def("find", py_find_commodity, return_value_policy<reference_existing_object>()) .staticmethod("find") -#endif .def("add_price", &commodity_t::add_price) .def("remove_price", &commodity_t::remove_price) @@ -2,6 +2,7 @@ #define _AMOUNT_H #include <map> +#include <deque> #include <stack> #include <string> #include <cctype> @@ -36,34 +37,29 @@ class amount_t void _release(); void _dup(); void _resize(unsigned int prec); - - void _clear() { - if (quantity) { - assert(commodity_); - _release(); - quantity = NULL; - commodity_ = NULL; - } else { - assert(! commodity_); - } - } + void _clear(); bigint_t * quantity; commodity_t * commodity_; public: // constructors - amount_t() : quantity(NULL), commodity_(NULL) {} + amount_t() : quantity(NULL), commodity_(NULL) { + TRACE_CTOR("amount_t()"); + } amount_t(const amount_t& amt) : quantity(NULL) { + TRACE_CTOR("amount_t(copy)"); if (amt.quantity) _copy(amt); else commodity_ = NULL; } amount_t(const std::string& value) : quantity(NULL) { + TRACE_CTOR("amount_t(const std::string&)"); parse(value); } amount_t(const char * value) : quantity(NULL) { + TRACE_CTOR("amount_t(const char *)"); parse(value); } amount_t(const bool value); @@ -73,10 +69,12 @@ class amount_t // destructor ~amount_t() { + TRACE_DTOR("amount_t"); if (quantity) _release(); } + bool has_commodity() const; commodity_t& commodity() const; void set_commodity(commodity_t& comm) { commodity_ = &comm; @@ -260,35 +258,15 @@ class amount_t negate(); } -#define AMOUNT_PARSE_NO_MIGRATE 0x01 -#define AMOUNT_PARSE_NO_REDUCE 0x02 - - void parse(std::istream& in, unsigned char flags = 0); - void parse(const std::string& str, unsigned char flags = 0); void reduce(); - amount_t reduced() const { amount_t temp(*this); temp.reduce(); return temp; } - void read_quantity(char *& data); - void read_quantity(std::istream& in); - void write_quantity(std::ostream& out) const; - bool valid() const; - // Classes that are friends, and help to implement this class - - friend std::ostream& operator<<(std::ostream& out, const amount_t& amt); - friend std::istream& operator>>(std::istream& in, amount_t& amt); - - friend unsigned int sizeof_bigint_t(); - - friend void read_binary_amount(char *& data, amount_t& amt); - friend void write_binary_amount(std::ostream& out, const amount_t& amt); - // This function is special, and exists only to support a custom // optimization in binary.cc (which offers a significant enough gain // to be worth the trouble). @@ -298,52 +276,44 @@ class amount_t friend void parse_annotations(std::istream& in, amount_t& price, datetime_t& date, std::string& tag); -}; -unsigned int sizeof_bigint_t(); + // Streaming interface -void parse_quantity(std::istream& in, std::string& value); -void parse_commodity(std::istream& in, std::string& symbol); -void parse_annotations(std::istream& in, const std::string& symbol, - std::string& name, std::string& price, - std::string& date, std::string& tag); -void parse_conversion(const std::string& larger, - const std::string& smaller); + void dump(std::ostream& out) const { + out << "AMOUNT("; + print(out); + out << ")"; + } -inline bool is_quote_or_paren(char * p) { - return *p == '"' || *p == '{' || *p == '[' || *p == '('; -} +#define AMOUNT_PARSE_NO_MIGRATE 0x01 +#define AMOUNT_PARSE_NO_REDUCE 0x02 -inline char * scan_past_quotes_and_parens(char * expr) -{ - std::stack<char> paren_stack; - - char * p; - for (p = expr; *p; p++) { - if (*p == '"' || - ((*p == '(' || ((*p == '{' || *p == '[') && - paren_stack.top() != '(')) && - paren_stack.top() != '"')) { - paren_stack.push(*p); - } - else if ((*p == ')' && paren_stack.top() == '(') || - (*p == '}' && paren_stack.top() == '{') || - (*p == ']' && paren_stack.top() == '[') || - (*p == '"' && paren_stack.top() == '"')) { - paren_stack.pop(); - if (paren_stack.size() == 0) - break; - } - } - return p; -} + void print(std::ostream& out) const; + void parse(std::istream& in, unsigned char flags = 0); + void parse(const std::string& str, unsigned char flags = 0) { + std::istringstream stream(str); + parse(stream, flags); + } + + void print_quantity(std::ostream& out) const; + + void write(std::ostream& out) const; + void read(std::istream& in); + void read(char *& data); + + void write_quantity(std::ostream& out) const; + void read_quantity(std::istream& in); + void read_quantity(char *& data); +}; inline amount_t abs(const amount_t& amt) { return amt < 0 ? amt.negated() : amt; } -std::ostream& operator<<(std::ostream& out, const amount_t& amt); - +inline std::ostream& operator<<(std::ostream& out, const amount_t& amt) { + amt.print(out); + return out; +} inline std::istream& operator>>(std::istream& in, amount_t& amt) { amt.parse(in); return in; @@ -432,6 +402,8 @@ class commodity_base_t typedef std::map<const std::string, commodity_t *> commodities_map; typedef std::pair<const std::string, commodity_t *> commodities_pair; +typedef std::deque<commodity_t *> commodities_array; + class commodity_t { friend class annotated_commodity_t; @@ -439,10 +411,11 @@ class commodity_t public: // This map remembers all commodities that have been defined. - static commodities_map commodities; - static bool commodities_sorted; - static commodity_t * null_commodity; - static commodity_t * default_commodity; + static commodities_map commodities; + static commodities_array commodities_by_ident; + static bool commodities_sorted; + static commodity_t * null_commodity; + static commodity_t * default_commodity; static commodity_t * create(const std::string& symbol); static commodity_t * find(const std::string& name); @@ -463,8 +436,12 @@ class commodity_t bool annotated; public: - explicit commodity_t() : base(NULL), annotated(false) {} - virtual ~commodity_t() {} + explicit commodity_t() : base(NULL), annotated(false) { + TRACE_CTOR("commodity_t()"); + } + virtual ~commodity_t() { + TRACE_DTOR("commodity_t"); + } operator bool() const { return this != null_commodity; @@ -568,6 +545,7 @@ class annotated_commodity_t : public commodity_t std::string tag; explicit annotated_commodity_t() { + TRACE_CTOR("annotated_commodity_t()"); annotated = true; } @@ -597,8 +575,7 @@ class annotated_commodity_t : public commodity_t friend class amount_t; }; -inline std::ostream& operator<<(std::ostream& out, - const commodity_t& comm) { +inline std::ostream& operator<<(std::ostream& out, const commodity_t& comm) { out << comm.symbol(); return out; } @@ -607,6 +584,10 @@ inline amount_t amount_t::round() const { return round(commodity().precision()); } +inline bool amount_t::has_commodity() const { + return commodity_ && commodity_ != commodity_t::null_commodity; +} + inline commodity_t& amount_t::commodity() const { if (! commodity_) return *commodity_t::null_commodity; @@ -614,6 +595,11 @@ inline commodity_t& amount_t::commodity() const { return *commodity_; } + +void parse_conversion(const std::string& larger_str, + const std::string& smaller_str); + + class amount_error : public error { public: amount_error(const std::string& reason) throw() : error(reason) {} @@ -1,8 +1,12 @@ +#ifdef USE_PCH +#include "pch.h" +#else #include "balance.h" #include "util.h" #include <deque> #include <algorithm> +#endif namespace ledger { @@ -125,7 +129,8 @@ void balance_t::write(std::ostream& out, if ((*i).second) sorted.push_back(&(*i).second); - std::stable_sort(sorted.begin(), sorted.end(), compare_amount_commodities()); + std::stable_sort(sorted.begin(), sorted.end(), + compare_amount_commodities()); for (amounts_deque::const_iterator i = sorted.begin(); i != sorted.end(); @@ -321,7 +326,9 @@ balance_t::operator amount_t() const #ifdef USE_BOOST_PYTHON +#ifndef USE_PCH #include <boost/python.hpp> +#endif using namespace boost::python; using namespace ledger; @@ -3,9 +3,6 @@ #include "amount.h" -#include <map> -#include <iostream> - namespace ledger { typedef std::map<const commodity_t *, amount_t> amounts_map; @@ -26,19 +23,24 @@ class balance_t } // constructors - balance_t() {} + balance_t() { + TRACE_CTOR("balance_t()"); + } balance_t(const balance_t& bal) { + TRACE_CTOR("balance_t(copy)"); for (amounts_map::const_iterator i = bal.amounts.begin(); i != bal.amounts.end(); i++) *this += (*i).second; } balance_t(const amount_t& amt) { + TRACE_CTOR("balance_t(const amount_t&)"); if (! amt.realzero()) amounts.insert(amounts_pair(&amt.commodity(), amt)); } template <typename T> balance_t(T value) { + TRACE_CTOR("balance_t(T)"); amount_t amt(value); if (! amt.realzero()) amounts.insert(amounts_pair(&amt.commodity(), amt)); @@ -497,21 +499,31 @@ class balance_pair_t balance_t * cost; // constructors - balance_pair_t() : cost(NULL) {} + balance_pair_t() : cost(NULL) { + TRACE_CTOR("balance_pair_t()"); + } balance_pair_t(const balance_pair_t& bal_pair) : quantity(bal_pair.quantity), cost(NULL) { + TRACE_CTOR("balance_pair_t(copy)"); if (bal_pair.cost) cost = new balance_t(*bal_pair.cost); } balance_pair_t(const balance_t& _quantity) - : quantity(_quantity), cost(NULL) {} + : quantity(_quantity), cost(NULL) { + TRACE_CTOR("balance_pair_t(const balance_t&)"); + } balance_pair_t(const amount_t& _quantity) - : quantity(_quantity), cost(NULL) {} + : quantity(_quantity), cost(NULL) { + TRACE_CTOR("balance_pair_t(const amount_t&)"); + } template <typename T> - balance_pair_t(T value) : quantity(value), cost(NULL) {} + balance_pair_t(T value) : quantity(value), cost(NULL) { + TRACE_CTOR("balance_pair_t(T)"); + } // destructor ~balance_pair_t() { + TRACE_DTOR("balance_pair_t"); if (cost) delete cost; } @@ -1,19 +1,20 @@ -#include "journal.h" -#include "valexpr.h" +#ifdef USE_PCH +#include "pch.h" +#else #include "binary.h" #include <fstream> #include <sys/stat.h> - -#define TIMELOG_SUPPORT 1 +#endif namespace ledger { -static unsigned long binary_magic_number = 0xFFEED765; +#if 0 +static unsigned long binary_magic_number = 0xFFEED765; #ifdef DEBUG_ENABLED -static unsigned long format_version = 0x0002060b; +static unsigned long format_version = 0x00030000; #else -static unsigned long format_version = 0x0002060a; +static unsigned long format_version = 0x00030000; #endif static account_t ** accounts; @@ -32,51 +33,10 @@ extern char * bigints; extern char * bigints_next; extern unsigned int bigints_index; extern unsigned int bigints_count; - -template <typename T> -inline void read_binary_number_nocheck(std::istream& in, T& num) { - in.read((char *)&num, sizeof(num)); -} - -template <typename T> -inline T read_binary_number_nocheck(std::istream& in) { - T num; - read_binary_number_nocheck(in, num); - return num; -} - -template <typename T> -inline void read_binary_number_nocheck(char *& data, T& num) { - num = *((T *) data); - data += sizeof(T); -} - -template <typename T> -inline T read_binary_number_nocheck(char *& data) { - T num; - read_binary_number_nocheck(data, num); - return num; -} - -#if DEBUG_LEVEL >= ALPHA -static void assert_failed() { - assert(0); -} -#define read_binary_guard(in, id) \ - if (read_binary_number_nocheck<unsigned short>(in) != id) \ - assert_failed(); -#else -#define read_binary_guard(in, id) #endif -template <typename T> -inline void read_binary_number(std::istream& in, T& num) { - read_binary_guard(in, 0x2003); - in.read((char *)&num, sizeof(num)); - read_binary_guard(in, 0x2004); -} - -inline void read_binary_bool(std::istream& in, bool& num) { +void read_binary_bool(std::istream& in, bool& num) +{ read_binary_guard(in, 0x2005); unsigned char val; in.read((char *)&val, sizeof(val)); @@ -84,55 +44,16 @@ inline void read_binary_bool(std::istream& in, bool& num) { read_binary_guard(in, 0x2006); } -template <typename T> -inline void read_binary_long(std::istream& in, T& num) { - read_binary_guard(in, 0x2001); - - unsigned char len; - read_binary_number_nocheck(in, len); - - num = 0; - unsigned char temp; - if (len > 3) { - read_binary_number_nocheck(in, temp); - num |= ((unsigned long)temp) << 24; - } - if (len > 2) { - read_binary_number_nocheck(in, temp); - num |= ((unsigned long)temp) << 16; - } - if (len > 1) { - read_binary_number_nocheck(in, temp); - num |= ((unsigned long)temp) << 8; - } - - read_binary_number_nocheck(in, temp); - num |= ((unsigned long)temp); - - read_binary_guard(in, 0x2002); -} - -template <typename T> -inline T read_binary_number(std::istream& in) { - T num; - read_binary_number(in, num); - return num; -} - -inline bool read_binary_bool(std::istream& in) { - bool num; - read_binary_bool(in, num); - return num; -} - -template <typename T> -inline T read_binary_long(std::istream& in) { - T num; - read_binary_long(in, num); - return num; +void read_binary_bool(char *& data, bool& num) +{ + read_binary_guard(data, 0x2005); + unsigned char val = *((unsigned char *) data); + data += sizeof(unsigned char); + num = val == 1; + read_binary_guard(data, 0x2006); } -inline void read_binary_string(std::istream& in, std::string& str) +void read_binary_string(std::istream& in, std::string& str) { read_binary_guard(in, 0x3001); @@ -159,77 +80,7 @@ inline void read_binary_string(std::istream& in, std::string& str) read_binary_guard(in, 0x3002); } -inline std::string read_binary_string(std::istream& in) { - std::string temp; - read_binary_string(in, temp); - return temp; -} - -template <typename T> -inline void read_binary_number(char *& data, T& num) { - read_binary_guard(data, 0x2003); - num = *((T *) data); - data += sizeof(T); - read_binary_guard(data, 0x2004); -} - -inline void read_binary_bool(char *& data, bool& num) { - read_binary_guard(data, 0x2005); - unsigned char val = *((unsigned char *) data); - data += sizeof(unsigned char); - num = val == 1; - read_binary_guard(data, 0x2006); -} - -template <typename T> -inline void read_binary_long(char *& data, T& num) { - read_binary_guard(data, 0x2001); - - unsigned char len; - read_binary_number_nocheck(data, len); - - num = 0; - unsigned char temp; - if (len > 3) { - read_binary_number_nocheck(data, temp); - num |= ((unsigned long)temp) << 24; - } - if (len > 2) { - read_binary_number_nocheck(data, temp); - num |= ((unsigned long)temp) << 16; - } - if (len > 1) { - read_binary_number_nocheck(data, temp); - num |= ((unsigned long)temp) << 8; - } - - read_binary_number_nocheck(data, temp); - num |= ((unsigned long)temp); - - read_binary_guard(data, 0x2002); -} - -template <typename T> -inline T read_binary_number(char *& data) { - T num; - read_binary_number(data, num); - return num; -} - -inline bool read_binary_bool(char *& data) { - bool num; - read_binary_bool(data, num); - return num; -} - -template <typename T> -inline T read_binary_long(char *& data) { - T num; - read_binary_long(data, num); - return num; -} - -inline void read_binary_string(char *& data, std::string& str) +void read_binary_string(char *& data, std::string& str) { read_binary_guard(data, 0x3001); @@ -252,14 +103,7 @@ inline void read_binary_string(char *& data, std::string& str) read_binary_guard(data, 0x3002); } -inline std::string read_binary_string(char *& data) -{ - std::string temp; - read_binary_string(data, temp); - return temp; -} - -inline void read_binary_string(char *& data, std::string * str) +void read_binary_string(char *& data, std::string * str) { read_binary_guard(data, 0x3001); @@ -282,20 +126,7 @@ inline void read_binary_string(char *& data, std::string * str) read_binary_guard(data, 0x3002); } -inline void read_binary_amount(char *& data, amount_t& amt) -{ - commodity_t::ident_t ident; - read_binary_long(data, ident); - if (ident == 0xffffffff) - amt.commodity_ = NULL; - else if (ident == 0) - amt.commodity_ = commodity_t::null_commodity; - else - amt.commodity_ = commodities[ident - 1]; - - amt.read_quantity(data); -} - +#if 0 inline void read_binary_value(char *& data, value_t& val) { val.type = static_cast<value_t::type_t>(read_binary_long<int>(data)); @@ -332,53 +163,6 @@ inline void read_binary_mask(char *& data, mask_t *& mask) mask->exclude = exclude; } -inline void read_binary_value_expr(char *& data, value_expr_t *& expr) -{ - if (! read_binary_bool(data)) { - expr = NULL; - return; - } - - value_expr_t::kind_t kind; - read_binary_number(data, kind); - - expr = new value_expr_t(kind); - - if (kind > value_expr_t::TERMINALS) { - read_binary_value_expr(data, expr->left); - if (expr->left) expr->left->acquire(); - } - - switch (expr->kind) { - case value_expr_t::O_ARG: - case value_expr_t::INDEX: - read_binary_long(data, expr->arg_index); - break; - case value_expr_t::CONSTANT: - expr->value = new value_t; - read_binary_value(data, *expr->value); - break; - - case value_expr_t::F_CODE_MASK: - case value_expr_t::F_PAYEE_MASK: - case value_expr_t::F_NOTE_MASK: - case value_expr_t::F_ACCOUNT_MASK: - case value_expr_t::F_SHORT_ACCOUNT_MASK: - case value_expr_t::F_COMMODITY_MASK: - if (read_binary_bool(data)) - read_binary_mask(data, expr->mask); - break; - - default: - if (kind > value_expr_t::TERMINALS) { - read_binary_value_expr(data, expr->right); - if (expr->right) expr->right->acquire(); - } - break; - } -} - - inline void read_binary_transaction(char *& data, transaction_t * xact) { read_binary_number(data, xact->_date); @@ -390,15 +174,15 @@ inline void read_binary_transaction(char *& data, transaction_t * xact) read_binary_amount(data, xact->amount); } else if (flag == 1) { - read_binary_amount(data, xact->amount); - read_binary_string(data, xact->amount_expr.expr); - } - else { - value_expr_t * ptr = NULL; - read_binary_value_expr(data, ptr); - assert(ptr); - xact->amount_expr.reset(ptr); - read_binary_string(data, xact->amount_expr.expr); + std::string expr; + read_binary_string(data, expr); + xact->amount_expr = expr; + + repitem_t * item = + repitem_t::wrap(xact, static_cast<repitem_t *>(xact->entry->data)); + xact->data = item; + + xact->amount = valexpr_t(xact->amount_expr).calc(item).to_amount(); } if (read_binary_bool(data)) { @@ -420,9 +204,6 @@ inline void read_binary_transaction(char *& data, transaction_t * xact) read_binary_long(data, xact->end_line); xact->data = NULL; - - if (xact->amount_expr) - compute_amount(xact->amount_expr, xact->amount, xact); } inline void read_binary_entry_base(char *& data, entry_base_t * entry, @@ -440,6 +221,7 @@ inline void read_binary_entry_base(char *& data, entry_base_t * entry, i < count; i++) { new(xact_pool) transaction_t; + xact_pool->entry = static_cast<entry_t *>(entry); read_binary_transaction(data, xact_pool); if (ignore_calculated && xact_pool->flags & TRANSACTION_CALCULATED) finalize = true; @@ -450,6 +232,9 @@ inline void read_binary_entry_base(char *& data, entry_base_t * entry, inline void read_binary_entry(char *& data, entry_t * entry, transaction_t *& xact_pool, bool& finalize) { + entry->data = + repitem_t::wrap(entry, static_cast<repitem_t *>(entry->journal->data)); + read_binary_entry_base(data, entry, xact_pool, finalize); read_binary_number(data, entry->_date); read_binary_number(data, entry->_date_eff); @@ -462,10 +247,10 @@ inline void read_binary_auto_entry(char *& data, auto_entry_t * entry, { bool ignore; read_binary_entry_base(data, entry, xact_pool, ignore); - value_expr_t * expr; - read_binary_value_expr(data, expr); - // the item_predicate constructor will acquire the reference - entry->predicate = new item_predicate<transaction_t>(expr); + + std::string pred_str; + read_binary_string(data, &pred_str); + entry->predicate.parse(pred_str); } inline void read_binary_period_entry(char *& data, period_entry_t * entry, @@ -613,19 +398,19 @@ account_t * read_binary_account(char *& data, journal_t * journal, return acct; } -unsigned int read_binary_journal(std::istream& in, - const std::string& file, - journal_t * journal, - account_t * master) +unsigned int read_binary_journal(std::istream& in, + journal_t * journal, + account_t * master, + const std::string& original_file) { - account_index = - base_commodity_index = + account_index = + base_commodity_index = commodity_index = 0; // Read in the files that participated in this journal, so that they // can be checked for changes on reading. - if (! file.empty()) { + if (! original_file.empty()) { for (unsigned short i = 0, count = read_binary_number<unsigned short>(in); i < count; @@ -751,7 +536,7 @@ unsigned int read_binary_journal(std::istream& in, std::pair<commodities_map::iterator, bool> result = commodity_t::commodities.insert(commodities_pair - (mapping_key, commodity)); + (mapping_key, commodity)); if (! result.second) { commodities_map::iterator c = commodity_t::commodities.find(mapping_key); @@ -779,8 +564,8 @@ unsigned int read_binary_journal(std::istream& in, for (unsigned long i = 0; i < count; i++) { new(entry_pool) entry_t; bool finalize = false; - read_binary_entry(data, entry_pool, xact_pool, finalize); entry_pool->journal = journal; + read_binary_entry(data, entry_pool, xact_pool, finalize); if (finalize && ! entry_pool->finalize()) continue; journal->entries.push_back(entry_pool++); @@ -813,7 +598,9 @@ unsigned int read_binary_journal(std::istream& in, return count; } +#endif +#if 0 bool binary_parser_t::test(std::istream& in) const { if (read_binary_number_nocheck<unsigned long>(in) == binary_magic_number && @@ -825,76 +612,28 @@ bool binary_parser_t::test(std::istream& in) const return false; } -unsigned int binary_parser_t::parse(std::istream& in, - config_t& config, +unsigned int binary_parser_t::parse(std::istream& in, journal_t * journal, account_t * master, const std::string * original_file) { - return read_binary_journal(in, original_file ? *original_file : "", - journal, master); -} - -template <typename T> -inline void write_binary_number_nocheck(std::ostream& out, T num) { - out.write((char *)&num, sizeof(num)); +#if 0 + return read_binary_journal(in, journal, master, + original_file ? *original_file : ""); +#endif } - -#if DEBUG_LEVEL >= ALPHA -#define write_binary_guard(out, id) \ - write_binary_number_nocheck<unsigned short>(out, id) -#else -#define write_binary_guard(in, id) #endif -template <typename T> -inline void write_binary_number(std::ostream& out, T num) { - write_binary_guard(out, 0x2003); - out.write((char *)&num, sizeof(num)); - write_binary_guard(out, 0x2004); -} -inline void write_binary_bool(std::ostream& out, bool num) { +void write_binary_bool(std::ostream& out, bool num) +{ write_binary_guard(out, 0x2005); unsigned char val = num ? 1 : 0; out.write((char *)&val, sizeof(val)); write_binary_guard(out, 0x2006); } -template <typename T> -inline void write_binary_long(std::ostream& out, T num) { - write_binary_guard(out, 0x2001); - - unsigned char len = 4; - if (((unsigned long)num) < 0x00000100UL) - len = 1; - else if (((unsigned long)num) < 0x00010000UL) - len = 2; - else if (((unsigned long)num) < 0x01000000UL) - len = 3; - write_binary_number_nocheck<unsigned char>(out, len); - - unsigned char temp; - if (len > 3) { - temp = (((unsigned long)num) & 0xFF000000UL) >> 24; - write_binary_number_nocheck(out, temp); - } - if (len > 2) { - temp = (((unsigned long)num) & 0x00FF0000UL) >> 16; - write_binary_number_nocheck(out, temp); - } - if (len > 1) { - temp = (((unsigned long)num) & 0x0000FF00UL) >> 8; - write_binary_number_nocheck(out, temp); - } - - temp = (((unsigned long)num) & 0x000000FFUL); - write_binary_number_nocheck(out, temp); - - write_binary_guard(out, 0x2002); -} - -inline void write_binary_string(std::ostream& out, const std::string& str) +void write_binary_string(std::ostream& out, const std::string& str) { write_binary_guard(out, 0x3001); @@ -913,16 +652,7 @@ inline void write_binary_string(std::ostream& out, const std::string& str) write_binary_guard(out, 0x3002); } -void write_binary_amount(std::ostream& out, const amount_t& amt) -{ - if (amt.commodity_) - write_binary_long(out, amt.commodity_->ident); - else - write_binary_long<commodity_t::ident_t>(out, 0xffffffff); - - amt.write_quantity(out); -} - +#if 0 void write_binary_value(std::ostream& out, const value_t& val) { write_binary_long(out, (int)val.type); @@ -953,49 +683,6 @@ void write_binary_mask(std::ostream& out, mask_t * mask) write_binary_string(out, mask->pattern); } -void write_binary_value_expr(std::ostream& out, const value_expr_t * expr) -{ - if (! expr) { - write_binary_bool(out, false); - return; - } - write_binary_bool(out, true); - write_binary_number(out, expr->kind); - - if (expr->kind > value_expr_t::TERMINALS) - write_binary_value_expr(out, expr->left); - - switch (expr->kind) { - case value_expr_t::O_ARG: - case value_expr_t::INDEX: - write_binary_long(out, expr->arg_index); - break; - case value_expr_t::CONSTANT: - write_binary_value(out, *expr->value); - break; - - case value_expr_t::F_CODE_MASK: - case value_expr_t::F_PAYEE_MASK: - case value_expr_t::F_NOTE_MASK: - case value_expr_t::F_ACCOUNT_MASK: - case value_expr_t::F_SHORT_ACCOUNT_MASK: - case value_expr_t::F_COMMODITY_MASK: - if (expr->mask) { - write_binary_bool(out, true); - write_binary_mask(out, expr->mask); - } else { - write_binary_bool(out, false); - } - break; - - default: - if (expr->kind > value_expr_t::TERMINALS) - write_binary_value_expr(out, expr->right); - break; - } - -} - void write_binary_transaction(std::ostream& out, transaction_t * xact, bool ignore_calculated) { @@ -1007,15 +694,9 @@ void write_binary_transaction(std::ostream& out, transaction_t * xact, write_binary_number<unsigned char>(out, 0); write_binary_amount(out, amount_t()); } - else if (xact->amount_expr) { - write_binary_number<unsigned char>(out, 2); - write_binary_value_expr(out, xact->amount_expr.get()); - write_binary_string(out, xact->amount_expr.expr); - } - else if (! xact->amount_expr.expr.empty()) { + else if (! xact->amount_expr.empty()) { write_binary_number<unsigned char>(out, 1); - write_binary_amount(out, xact->amount); - write_binary_string(out, xact->amount_expr.expr); + write_binary_string(out, xact->amount_expr); } else { write_binary_number<unsigned char>(out, 0); @@ -1053,7 +734,7 @@ void write_binary_entry_base(std::ostream& out, entry_base_t * entry) for (transactions_list::const_iterator i = entry->transactions.begin(); i != entry->transactions.end(); i++) - if ((*i)->amount_expr) { + if (! (*i)->amount_expr.empty()) { ignore_calculated = true; break; } @@ -1079,7 +760,7 @@ void write_binary_entry(std::ostream& out, entry_t * entry) void write_binary_auto_entry(std::ostream& out, auto_entry_t * entry) { write_binary_entry_base(out, entry); - write_binary_value_expr(out, entry->predicate->predicate); + write_binary_string(out, entry->predicate.expr); } void write_binary_period_entry(std::ostream& out, period_entry_t * entry) @@ -1192,8 +873,8 @@ void write_binary_account(std::ostream& out, account_t * account) void write_binary_journal(std::ostream& out, journal_t * journal) { - account_index = - base_commodity_index = + account_index = + base_commodity_index = commodity_index = 0; write_binary_number_nocheck(out, binary_magic_number); @@ -1334,5 +1015,6 @@ void write_binary_journal(std::ostream& out, journal_t * journal) out.seekp(bigints_val); write_binary_number<unsigned long>(out, bigints_count); } +#endif } // namespace ledger @@ -1,25 +1,257 @@ #ifndef _BINARY_H #define _BINARY_H +#if 0 #include "journal.h" #include "parser.h" +#endif + +#include <string> +#include <iostream> namespace ledger { +#if 0 class binary_parser_t : public parser_t { public: virtual bool test(std::istream& in) const; virtual unsigned int parse(std::istream& in, - config_t& config, journal_t * journal, account_t * master = NULL, const std::string * original_file = NULL); }; +#endif + +template <typename T> +inline void read_binary_number_nocheck(std::istream& in, T& num) { + in.read((char *)&num, sizeof(num)); +} + +template <typename T> +inline void read_binary_number_nocheck(char *& data, T& num) { + num = *((T *) data); + data += sizeof(T); +} + +template <typename T> +inline T read_binary_number_nocheck(std::istream& in) { + T num; + read_binary_number_nocheck(in, num); + return num; +} + +template <typename T> +inline T read_binary_number_nocheck(char *& data) { + T num; + read_binary_number_nocheck(data, num); + return num; +} + +#if DEBUG_LEVEL >= ALPHA +#define read_binary_guard(in, id) \ + if (read_binary_number_nocheck<unsigned short>(in) != id) \ + assert(0); +#else +#define read_binary_guard(in, id) +#endif + +template <typename T> +inline void read_binary_number(std::istream& in, T& num) { + read_binary_guard(in, 0x2003); + in.read((char *)&num, sizeof(num)); + read_binary_guard(in, 0x2004); +} + +template <typename T> +inline void read_binary_number(char *& data, T& num) { + read_binary_guard(data, 0x2003); + num = *((T *) data); + data += sizeof(T); + read_binary_guard(data, 0x2004); +} + +template <typename T> +inline T read_binary_number(std::istream& in) { + T num; + read_binary_number(in, num); + return num; +} + +template <typename T> +inline T read_binary_number(char *& data) { + T num; + read_binary_number(data, num); + return num; +} + +void read_binary_bool(std::istream& in, bool& num); +void read_binary_bool(char *& data, bool& num); + +inline bool read_binary_bool(std::istream& in) { + bool num; + read_binary_bool(in, num); + return num; +} + +inline bool read_binary_bool(char *& data) { + bool num; + read_binary_bool(data, num); + return num; +} + +template <typename T> +void read_binary_long(std::istream& in, T& num) +{ + read_binary_guard(in, 0x2001); + + unsigned char len; + read_binary_number_nocheck(in, len); + + num = 0; + unsigned char temp; + if (len > 3) { + read_binary_number_nocheck(in, temp); + num |= ((unsigned long)temp) << 24; + } + if (len > 2) { + read_binary_number_nocheck(in, temp); + num |= ((unsigned long)temp) << 16; + } + if (len > 1) { + read_binary_number_nocheck(in, temp); + num |= ((unsigned long)temp) << 8; + } + + read_binary_number_nocheck(in, temp); + num |= ((unsigned long)temp); + + read_binary_guard(in, 0x2002); +} + +template <typename T> +void read_binary_long(char *& data, T& num) +{ + read_binary_guard(data, 0x2001); + + unsigned char len; + read_binary_number_nocheck(data, len); + + num = 0; + unsigned char temp; + if (len > 3) { + read_binary_number_nocheck(data, temp); + num |= ((unsigned long)temp) << 24; + } + if (len > 2) { + read_binary_number_nocheck(data, temp); + num |= ((unsigned long)temp) << 16; + } + if (len > 1) { + read_binary_number_nocheck(data, temp); + num |= ((unsigned long)temp) << 8; + } + + read_binary_number_nocheck(data, temp); + num |= ((unsigned long)temp); + + read_binary_guard(data, 0x2002); +} + +template <typename T> +inline T read_binary_long(std::istream& in) { + T num; + read_binary_long(in, num); + return num; +} + +template <typename T> +inline T read_binary_long(char *& data) { + T num; + read_binary_long(data, num); + return num; +} + +void read_binary_string(std::istream& in, std::string& str); +void read_binary_string(char *& data, std::string& str); +void read_binary_string(char *& data, std::string * str); + +inline std::string read_binary_string(std::istream& in) { + std::string temp; + read_binary_string(in, temp); + return temp; +} + +inline std::string read_binary_string(char *& data) { + std::string temp; + read_binary_string(data, temp); + return temp; +} + + +template <typename T> +inline void write_binary_number_nocheck(std::ostream& out, T num) { + out.write((char *)&num, sizeof(num)); +} + +#if DEBUG_LEVEL >= ALPHA +#define write_binary_guard(out, id) \ + write_binary_number_nocheck<unsigned short>(out, id) +#else +#define write_binary_guard(in, id) +#endif + +template <typename T> +inline void write_binary_number(std::ostream& out, T num) { + write_binary_guard(out, 0x2003); + out.write((char *)&num, sizeof(num)); + write_binary_guard(out, 0x2004); +} + +void write_binary_bool(std::ostream& out, bool num); + +template <typename T> +void write_binary_long(std::ostream& out, T num) +{ + write_binary_guard(out, 0x2001); + + unsigned char len = 4; + if (((unsigned long)num) < 0x00000100UL) + len = 1; + else if (((unsigned long)num) < 0x00010000UL) + len = 2; + else if (((unsigned long)num) < 0x01000000UL) + len = 3; + write_binary_number_nocheck<unsigned char>(out, len); + + unsigned char temp; + if (len > 3) { + temp = (((unsigned long)num) & 0xFF000000UL) >> 24; + write_binary_number_nocheck(out, temp); + } + if (len > 2) { + temp = (((unsigned long)num) & 0x00FF0000UL) >> 16; + write_binary_number_nocheck(out, temp); + } + if (len > 1) { + temp = (((unsigned long)num) & 0x0000FF00UL) >> 8; + write_binary_number_nocheck(out, temp); + } + + temp = (((unsigned long)num) & 0x000000FFUL); + write_binary_number_nocheck(out, temp); + + write_binary_guard(out, 0x2002); +} + +void write_binary_string(std::ostream& out, const std::string& str); + + -void write_binary_journal(std::ostream& out, - journal_t * journal); +#if 0 +void write_binary_journal(std::ostream& out, journal_t * journal); +#endif } // namespace ledger diff --git a/configure.in b/configure.in index 0aae061b..51fcec85 100644 --- a/configure.in +++ b/configure.in @@ -2,8 +2,8 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ(2.59) -AC_INIT(ledger, 2.6, johnw@newartisans.com) -AM_INIT_AUTOMAKE(ledger, 2.6) +AC_INIT(ledger, 3.0, johnw@newartisans.com) +AM_INIT_AUTOMAKE(ledger, 3.0) AC_CONFIG_SRCDIR([main.cc]) AC_CONFIG_HEADER([acconf.h]) @@ -252,6 +252,16 @@ else AM_CONDITIONAL(HAVE_BOOST_PYTHON, false) fi +# check for Precompiled headers (gcc4-style) +AC_ARG_ENABLE(pch, + [ --enable-pch Enable support for pre-compiled headers], + [case "${enableval}" in + yes) pch=true ;; + no) pch=false ;; + *) AC_MSG_ERROR(bad value ${enableval} for --enable-pch) ;; + esac],[pch=false]) +AM_CONDITIONAL(USE_PCH, test x$pch = xtrue) + # Check for options AC_ARG_ENABLE(debug, [ --enable-debug Turn on debugging], @@ -274,7 +284,7 @@ AC_STRUCT_TM # Checks for library functions. #AC_FUNC_ERROR_AT_LINE AC_HEADER_STDC -AC_CHECK_FUNCS([access mktime realpath stat strftime strptime getpwuid getpwnam]) +AC_CHECK_FUNCS([access mktime realpath strftime strptime getpwuid getpwnam]) AC_CONFIG_FILES([Makefile]) AC_OUTPUT @@ -1,105 +0,0 @@ -#include "csv.h" - -namespace ledger { - -namespace { - inline void write_escaped_string(std::ostream& out, const std::string& xact) - { - out << "\""; - for (std::string::const_iterator i = xact.begin(); i != xact.end(); i++) - if (*i == '"') { - out << "\\"; - out << "\""; - } else { - out << *i; - } - out << "\""; - } -} - -void format_csv_transactions::operator()(transaction_t& xact) -{ - if (! transaction_has_xdata(xact) || - ! (transaction_xdata_(xact).dflags & TRANSACTION_DISPLAYED)) { - - { - format_t fmt("%D"); - std::ostringstream str; - fmt.format(str, details_t(xact)); - write_escaped_string(out, str.str()); - } - out << ','; - - { - format_t fmt("%P"); - std::ostringstream str; - fmt.format(str, details_t(xact)); - write_escaped_string(out, str.str()); - } - out << ','; - - { - format_t fmt("%A"); - std::ostringstream str; - fmt.format(str, details_t(xact)); - write_escaped_string(out, str.str()); - } - out << ','; - - { - format_t fmt("%t"); - std::ostringstream str; - fmt.format(str, details_t(xact)); - write_escaped_string(out, str.str()); - } - out << ','; - - { - format_t fmt("%T"); - std::ostringstream str; - fmt.format(str, details_t(xact)); - write_escaped_string(out, str.str()); - } - out << ','; - - switch (xact.state) { - case transaction_t::CLEARED: - write_escaped_string(out, "*"); - break; - case transaction_t::PENDING: - write_escaped_string(out, "!"); - break; - default: { - transaction_t::state_t state; - if (xact.entry->get_state(&state)) - switch (state) { - case transaction_t::CLEARED: - write_escaped_string(out, "*"); - break; - case transaction_t::PENDING: - write_escaped_string(out, "!"); - break; - default: - write_escaped_string(out, ""); - break; - } - } - } - out << ','; - - write_escaped_string(out, xact.entry->code); - out << ','; - - { - format_t fmt("%N"); - std::ostringstream str; - fmt.format(str, details_t(xact)); - write_escaped_string(out, str.str()); - } - out << '\n'; - - transaction_xdata(xact).dflags |= TRANSACTION_DISPLAYED; - } -} - -} // namespace ledger @@ -1,24 +0,0 @@ -#ifndef _CSV_H -#define _CSV_H - -#include "journal.h" -#include "format.h" - -namespace ledger { - -class format_csv_transactions : public item_handler<transaction_t> -{ - protected: - std::ostream& out; - - public: - format_csv_transactions(std::ostream& _out) : out(_out) {} - virtual void flush() { - out.flush(); - } - virtual void operator()(transaction_t& xact); -}; - -} // namespace ledger - -#endif // _REPORT_H diff --git a/datetime.cc b/datetime.cc index 2e47c554..acf968bc 100644 --- a/datetime.cc +++ b/datetime.cc @@ -1,11 +1,16 @@ +#ifdef USE_PCH +#include "pch.h" +#else #if defined(__GNUG__) && __GNUG__ < 3 #define _XOPEN_SOURCE #endif #include "datetime.h" +#include "util.h" #include <ctime> #include <cctype> +#endif date_t date_t::now(std::time(NULL)); int date_t::current_year = date_t::now.year(); @@ -37,10 +42,50 @@ namespace { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; - bool parse_date_mask(const char * date_str, struct std::tm * result); - bool parse_date(const char * date_str, std::time_t * result, - const int year = -1); - bool quick_parse_date(const char * date_str, std::time_t * result); + bool parse_date_mask(const char * date_str, struct std::tm * result) + { + if (! date_t::input_format.empty()) { + std::memset(result, INT_MAX, sizeof(struct std::tm)); + if (strptime(date_str, date_t::input_format.c_str(), result)) + return true; + } + for (const char ** f = date_t::formats; *f; f++) { + std::memset(result, INT_MAX, sizeof(struct std::tm)); + if (strptime(date_str, *f, result)) + return true; + } + return false; + } + + bool parse_date(const char * date_str, std::time_t * result, const int year) + { + struct std::tm when; + + if (! parse_date_mask(date_str, &when)) + return false; + + when.tm_hour = 0; + when.tm_min = 0; + when.tm_sec = 0; + + if (when.tm_year == -1) + when.tm_year = ((year == -1) ? date_t::current_year : (year - 1900)); + + if (when.tm_mon == -1) + when.tm_mon = 0; + + if (when.tm_mday == -1) + when.tm_mday = 1; + + *result = std::mktime(&when); + + return true; + } + + inline bool quick_parse_date(const char * date_str, std::time_t * result) + { + return parse_date(date_str, result, date_t::current_year + 1900); + } } date_t::date_t(const std::string& _when) @@ -50,20 +95,116 @@ date_t::date_t(const std::string& _when) (std::string("Invalid date string: ") + _when); } +void date_t::parse(std::istream& in) +{ + char buf[256]; + char c = peek_next_nonws(in); + READ_INTO(in, buf, 255, c, + std::isalnum(c) || c == '-' || c == '.' || c == '/'); + + if (! quick_parse_date(buf, &when)) + throw new date_error + (std::string("Invalid date string: ") + buf); +} + datetime_t::datetime_t(const std::string& _when) { - if (const char * p = std::strchr(_when.c_str(), ' ')) { - date_t date(std::string(_when, 0, p - _when.c_str())); + std::istringstream datestr(_when); + parse(datestr); // parse both the date and optional time +} + +void datetime_t::parse(std::istream& in) +{ + date_t::parse(in); // first grab the date part + + istream_pos_type beg_pos = in.tellg(); + + int hour = 0; + int min = 0; + int sec = 0; + + // Now look for the (optional) time specifier. If no time is given, + // we use midnight of the given day. + char buf[256]; + char c = peek_next_nonws(in); + if (! std::isdigit(c)) + goto abort; + READ_INTO(in, buf, 255, c, std::isdigit(c)); + if (buf[0] == '\0') + goto abort; + + hour = std::atoi(buf); + if (hour > 23) + goto abort; + + if (in.peek() == ':') { + in.get(c); + READ_INTO(in, buf, 255, c, std::isdigit(c)); + if (buf[0] == '\0') + goto abort; + + min = std::atoi(buf); + if (min > 59) + goto abort; + + if (in.peek() == ':') { + in.get(c); + READ_INTO(in, buf, 255, c, std::isdigit(c)); + if (buf[0] == '\0') + goto abort; + + sec = std::atoi(buf); + if (sec > 59) + goto abort; + } + } - struct std::tm moment = *std::localtime(&date.when); - if (! strptime(++p, "%H:%M:%S", &moment)) - throw new datetime_error - (std::string("Invalid date/time string: ") + _when); + c = peek_next_nonws(in); + if (c == 'a' || c == 'p' || c == 'A' || c == 'P') { + if (hour > 12) + goto abort; + in.get(c); - when = std::mktime(&moment); - } else { - when = date_t(_when).when; + if (c == 'p' || c == 'P') { + if (hour != 12) + hour += 12; + } else { + if (hour == 12) + hour = 0; + } + + c = in.peek(); + if (c == 'm' || c == 'M') + in.get(c); } + + struct std::tm * desc = std::localtime(&when); + + desc->tm_hour = hour; + desc->tm_min = min; + desc->tm_sec = sec; + desc->tm_isdst = -1; + + when = std::mktime(desc); + + return; // the time has been successfully parsed + + abort: // there was no valid time string to parse + in.clear(); + in.seekg(beg_pos, std::ios::beg); +} + +std::ostream& operator<<(std::ostream& out, const datetime_t& moment) +{ + std::string format = datetime_t::output_format; + std::tm * when = moment.localtime(); + if (when->tm_hour != 0 || when->tm_min != 0 || when->tm_sec != 0) + format += " %H:%M:%S"; + + char buf[64]; + std::strftime(buf, 63, format.c_str(), when); + out << buf; + return out; } datetime_t interval_t::first(const datetime_t& moment) const @@ -314,49 +455,123 @@ void interval_t::parse(std::istream& in) } } -namespace { - bool parse_date_mask(const char * date_str, struct std::tm * result) - { - if (! date_t::input_format.empty()) { - std::memset(result, INT_MAX, sizeof(struct std::tm)); - if (strptime(date_str, date_t::input_format.c_str(), result)) - return true; - } - for (const char ** f = date_t::formats; *f; f++) { - std::memset(result, INT_MAX, sizeof(struct std::tm)); - if (strptime(date_str, *f, result)) - return true; - } - return false; - } +#ifdef USE_BOOST_PYTHON - bool parse_date(const char * date_str, std::time_t * result, const int year) - { - struct std::tm when; - - if (! parse_date_mask(date_str, &when)) - return false; +#ifndef USE_PCH +#include <boost/python.hpp> +#endif - when.tm_hour = 0; - when.tm_min = 0; - when.tm_sec = 0; +using namespace boost::python; - if (when.tm_year == -1) - when.tm_year = ((year == -1) ? date_t::current_year : (year - 1900)); +unsigned int interval_len(interval_t& interval) +{ + int periods = 1; + std::time_t when = interval.first(); + while (interval.end && when < interval.end) { + when = interval.increment(when); + if (when < interval.end) + periods++; + } + return periods; +} - if (when.tm_mon == -1) - when.tm_mon = 0; +std::time_t interval_getitem(interval_t& interval, int i) +{ + static std::time_t last_index = 0; + static std::time_t last_moment = 0; - if (when.tm_mday == -1) - when.tm_mday = 1; + if (i == 0) { + last_index = 0; + last_moment = interval.first(); + } + else { + last_moment = interval.increment(last_moment); + if (interval.end && last_moment >= interval.end) { + PyErr_SetString(PyExc_IndexError, "Index out of range"); + throw_error_already_set(); + } + } + return last_moment; +} - *result = std::mktime(&when); +std::time_t py_parse_date(const char * date_str) +{ + std::time_t temp; + if (parse_date(date_str, &temp)) + return temp; + return 0; +} - return true; - } +std::time_t py_parse_date_yr(const char * date_str, const int year) +{ + std::time_t temp; + if (parse_date(date_str, &temp, year)) + return temp; + return 0; +} - bool quick_parse_date(const char * date_str, std::time_t * result) - { - return parse_date(date_str, result, date_t::current_year + 1900); - } +void export_datetime() +{ + class_< date_t > ("Date") + .def("now", &date_t::now) + .def("formats", &date_t::formats) + .def("current_year", &date_t::current_year) + .def("input_format", &date_t::input_format) + .def("output_format", &date_t::output_format) + + .def(init<>()) + .def(init<const date_t&>()) + .def(init<const std::time_t&>()) + .def(init<const interval_t&>()) + .def(init<const std::string&>()) + + .def(self += other<const interval_t&>()) + .def(self -= other<const date_t&>()) + .def(self += long()) + .def(self -= long()) + + .def(self < other<const date_t&>()) + .def(self <= other<const date_t&>()) + .def(self > other<const date_t&>()) + .def(self >= other<const date_t&>()) + .def(self == other<const date_t&>()) + .def(self != other<const date_t&>()) + + .def("year", &date_t::year) + .def("month", &date_t::month) + .def("day", &date_t::day) + .def("wday", &date_t::wday) + .def("localtime", &date_t::localtime) + + .def("write", &date_t::write) + .def("parse", &date_t::parse) + ; + + class_< interval_t > + ("Interval", init<optional<int, int, int, std::time_t, std::time_t> >()) + .def(init<std::string>()) + .def(! self) + + .def_readwrite("years", &interval_t::years) + .def_readwrite("months", &interval_t::months) + .def_readwrite("days", &interval_t::days) + .def_readwrite("hours", &interval_t::hours) + .def_readwrite("minutes", &interval_t::minutes) + .def_readwrite("seconds", &interval_t::seconds) + + .def_readwrite("begin", &interval_t::begin) + .def_readwrite("end", &interval_t::end) + + .def("__len__", interval_len) + .def("__getitem__", interval_getitem) + + .def("start", &interval_t::start) + .def("first", &interval_t::first) + .def("increment", &interval_t::increment) + ; + + def("parse_date", py_parse_date); + def("parse_date", py_parse_date_yr); } + +#endif // USE_BOOST_PYTHON @@ -131,6 +131,8 @@ class date_t out << to_string(format); } + void parse(std::istream& in); + friend class datetime_t; friend struct interval_t; }; @@ -158,6 +160,11 @@ inline std::ostream& operator<<(std::ostream& out, const date_t& moment) { return out; } +inline std::istream& operator>>(std::istream& in, date_t& moment) { + moment.parse(in); + return in; +} + class datetime_error : public error { public: datetime_error(const std::string& reason) throw() : error(reason) {} @@ -225,6 +232,8 @@ class datetime_t : public date_t int sec() const { return localtime()->tm_sec; } + + void parse(std::istream& in); }; inline long operator-(const datetime_t& left, const datetime_t& right) { @@ -245,13 +254,11 @@ inline datetime_t operator-(const datetime_t& left, const long seconds) { return temp; } -inline std::ostream& operator<<(std::ostream& out, - const datetime_t& moment) { - char buf[64]; - std::strftime(buf, 63, (date_t::output_format + " %H:%M:%S").c_str(), - moment.localtime()); - out << buf; - return out; +std::ostream& operator<<(std::ostream& out, const datetime_t& moment); + +inline std::istream& operator>>(std::istream& in, datetime_t& moment) { + moment.parse(in); + return in; } struct interval_t @@ -1,11 +1,17 @@ +#ifdef USE_PCH +#include "pch.h" +#else #include "debug.h" +#endif #ifdef DEBUG_ENABLED +#ifndef USE_PCH #include <map> #include <fstream> #include <unistd.h> // for the `write' method +#endif int offset = 0; @@ -113,7 +119,9 @@ static struct init_streams { #if DEBUG_LEVEL >= BETA +#ifndef USE_PCH #include <string> +#endif void debug_assert(const std::string& reason, const std::string& file, @@ -100,6 +100,8 @@ bool _debug_active(const char * const cls); #define VALIDATE(x) #endif +#include "trace.h" + void * operator new(std::size_t) throw (std::bad_alloc); void * operator new[](std::size_t) throw (std::bad_alloc); void operator delete(void*) throw(); @@ -139,6 +141,8 @@ void operator delete[](void*, const std::nothrow_t&) throw(); #define CONFIRM(x) assert(x) +#include "trace.h" + #endif #endif // DEBUG_LEVEL @@ -2,22 +2,27 @@ #include "datetime.h" #include "error.h" #include "mask.h" -#include "walk.h" #include <memory> namespace ledger { -entry_t * derive_new_entry(journal_t& journal, - strings_list::iterator i, - strings_list::iterator end) +void derive_command::operator()(value_t& result, + xml::xpath_t::scope_t * locals) { +#if 0 + std::ostream& out = *get_ptr<std::ostream>(locals, 0); + repitem_t * items = get_ptr<repitem_t>(locals, 1); + strings_list& args = *get_ptr<strings_list *>(locals, 2); + std::auto_ptr<entry_t> added(new entry_t); entry_t * matching = NULL; + strings_list::iterator i = args.begin(); + added->_date = *i++; - if (i == end) + if (i == args.end()) throw new error("Too few arguments to 'entry'"); mask_t regexp(*i++); @@ -35,10 +40,10 @@ entry_t * derive_new_entry(journal_t& journal, if (! matching) { account_t * acct; - if (i == end || ((*i)[0] == '-' || std::isdigit((*i)[0]))) { + if (i == args.end() || ((*i)[0] == '-' || std::isdigit((*i)[0]))) { acct = journal.find_account("Expenses"); } - else if (i != end) { + else if (i != args.end()) { acct = journal.find_account_re(*i); if (! acct) acct = journal.find_account(*i); @@ -46,7 +51,7 @@ entry_t * derive_new_entry(journal_t& journal, i++; } - if (i == end) { + if (i == args.end()) { added->add_transaction(new transaction_t(acct)); } else { transaction_t * xact = new transaction_t(acct, amount_t(*i++)); @@ -79,7 +84,7 @@ entry_t * derive_new_entry(journal_t& journal, added->add_transaction(new transaction_t(acct)); } - else if (i == end) { + else if (i == args.end()) { // If no argument were given but the payee, assume the user wants // to see the same transaction as last time. added->code = matching->code; @@ -104,7 +109,7 @@ entry_t * derive_new_entry(journal_t& journal, xact = new transaction_t(m_xact->account, - first->amount); added->add_transaction(xact); - if (i != end) { + if (i != args.end()) { account_t * acct = journal.find_account_re(*i); if (! acct) acct = journal.find_account(*i); @@ -113,7 +118,7 @@ entry_t * derive_new_entry(journal_t& journal, } } else { - while (i != end) { + while (i != args.end()) { std::string& re_pat(*i++); account_t * acct = NULL; amount_t * amt = NULL; @@ -142,7 +147,7 @@ entry_t * derive_new_entry(journal_t& journal, acct = journal.find_account(re_pat); transaction_t * xact; - if (i == end) { + if (i == args.end()) { if (amt) xact = new transaction_t(acct, *amt); else @@ -171,6 +176,7 @@ entry_t * derive_new_entry(journal_t& journal, throw new error("Failed to finalize derived entry (check commodities)"); return added.release(); +#endif } } // namespace ledger @@ -5,9 +5,13 @@ namespace ledger { -entry_t * derive_new_entry(journal_t& journal, - strings_list::iterator begin, - strings_list::iterator end); +class derive_command : public xml::xpath_t::functor_t +{ + public: + derive_command() : xml::xpath_t::functor_t("entry", true) {} + + virtual void operator()(value_t& result, xml::xpath_t::scope_t * locals); +}; } // namespace ledger diff --git a/docs/ledger.1 b/docs/ledger.1 new file mode 100644 index 00000000..d9c1c538 --- /dev/null +++ b/docs/ledger.1 @@ -0,0 +1,79 @@ +.\"Modified from man(1) of FreeBSD, the NetBSD mdoc.template, and mdoc.samples.
+.\"See Also:
+.\"man mdoc.samples for a complete listing of options
+.\"man mdoc for the short list of editing options
+.\"/usr/share/misc/mdoc.template
+.Dd 2/11/07 \" DATE
+.Dt ledger 1 \" Program name and manual section number
+.Os Darwin
+.Sh NAME \" Section Header - required - don't modify
+.Nm ledger,
+.\" The following lines are read in generating the apropos(man -k) database. Use only key
+.\" words here as the database is built based on the words here and in the .ND line.
+.Nm Other_name_for_same_program(),
+.Nm Yet another name for the same program.
+.\" Use .Nm macro to designate other names for the documented program.
+.Nd This line parsed for whatis database.
+.Sh SYNOPSIS \" Section Header - required - don't modify
+.Nm
+.Op Fl abcd \" [-abcd]
+.Op Fl a Ar path \" [-a path]
+.Op Ar file \" [file]
+.Op Ar \" [file ...]
+.Ar arg0 \" Underlined argument - use .Ar anywhere to underline
+arg2 ... \" Arguments
+.Sh DESCRIPTION \" Section Header - required - don't modify
+Use the .Nm macro to refer to your program throughout the man page like such:
+.Nm
+Underlining is accomplished with the .Ar macro like this:
+.Ar underlined text .
+.Pp \" Inserts a space
+A list of items with descriptions:
+.Bl -tag -width -indent \" Begins a tagged list
+.It item a \" Each item preceded by .It macro
+Description of item a
+.It item b
+Description of item b
+.El \" Ends the list
+.Pp
+A list of flags and their descriptions:
+.Bl -tag -width -indent \" Differs from above in tag removed
+.It Fl a \"-a flag as a list item
+Description of -a flag
+.It Fl b
+Description of -b flag
+.El \" Ends the list
+.Pp
+.\" .Sh ENVIRONMENT \" May not be needed
+.\" .Bl -tag -width "ENV_VAR_1" -indent \" ENV_VAR_1 is width of the string ENV_VAR_1
+.\" .It Ev ENV_VAR_1
+.\" Description of ENV_VAR_1
+.\" .It Ev ENV_VAR_2
+.\" Description of ENV_VAR_2
+.\" .El
+.Sh FILES \" File used or created by the topic of the man page
+.Bl -tag -width "/Users/joeuser/Library/really_long_file_name" -compact
+.It Pa /usr/share/file_name
+FILE_1 description
+.It Pa /Users/joeuser/Library/really_long_file_name
+FILE_2 description
+.El \" Ends the list
+.\" .Sh DIAGNOSTICS \" May not be needed
+.\" .Bl -diag
+.\" .It Diagnostic Tag
+.\" Diagnostic informtion here.
+.\" .It Diagnostic Tag
+.\" Diagnostic informtion here.
+.\" .El
+.Sh SEE ALSO
+.\" List links in ascending order by section, alphabetically within a section.
+.\" Please do not reference files that do not exist without filing a bug report
+.Xr a 1 ,
+.Xr b 1 ,
+.Xr c 1 ,
+.Xr a 2 ,
+.Xr b 2 ,
+.Xr a 3 ,
+.Xr b 3
+.\" .Sh BUGS \" Document known, unremedied bugs
+.\" .Sh HISTORY \" Document history if command behaves in a unique manner
\ No newline at end of file @@ -1,80 +0,0 @@ -#include "emacs.h" - -namespace ledger { - -void format_emacs_transactions::write_entry(entry_t& entry) -{ - int idx = entry.src_idx; - for (strings_list::iterator i = entry.journal->sources.begin(); - i != entry.journal->sources.end(); - i++) - if (! idx--) { - out << "\"" << *i << "\" "; - break; - } - - out << (((unsigned long)entry.beg_pos) + 1) << " "; - - std::time_t date = entry.date(); - out << "(" << (date / 65536) << " " << (date % 65536) << " 0) "; - - if (entry.code.empty()) - out << "nil "; - else - out << "\"" << entry.code << "\" "; - - if (entry.payee.empty()) - out << "nil"; - else - out << "\"" << entry.payee << "\""; - - out << "\n"; -} - -void format_emacs_transactions::operator()(transaction_t& xact) -{ - if (! transaction_has_xdata(xact) || - ! (transaction_xdata_(xact).dflags & TRANSACTION_DISPLAYED)) { - if (! last_entry) { - out << "(("; - write_entry(*xact.entry); - } - else if (xact.entry != last_entry) { - out << ")\n ("; - write_entry(*xact.entry); - } - else { - out << "\n"; - } - - out << " (" << (((unsigned long)xact.beg_pos) + 1) << " "; - out << "\"" << xact_account(xact)->fullname() << "\" \"" - << xact.amount << "\""; - - switch (xact.state) { - case transaction_t::CLEARED: - out << " t"; - break; - case transaction_t::PENDING: - out << " pending"; - break; - default: - out << " nil"; - break; - } - - if (xact.cost) - out << " \"" << *xact.cost << "\""; - else if (! xact.note.empty()) - out << " nil"; - if (! xact.note.empty()) - out << " \"" << xact.note << "\""; - out << ")"; - - last_entry = xact.entry; - - transaction_xdata(xact).dflags |= TRANSACTION_DISPLAYED; - } -} - -} // namespace ledger @@ -1,30 +0,0 @@ -#ifndef _EMACS_H -#define _EMACS_H - -#include "journal.h" -#include "format.h" - -namespace ledger { - -class format_emacs_transactions : public item_handler<transaction_t> -{ - protected: - std::ostream& out; - entry_t * last_entry; - - public: - format_emacs_transactions(std::ostream& _out) - : out(_out), last_entry(NULL) {} - - virtual void write_entry(entry_t& entry); - virtual void flush() { - if (last_entry) - out << "))\n"; - out.flush(); - } - virtual void operator()(transaction_t& xact); -}; - -} // namespace ledger - -#endif // _REPORT_H @@ -122,4 +122,20 @@ class fatal_assert : public fatal { virtual ~fatal_assert() throw() {} }; +inline void unexpected(char c, char wanted) +{ + if ((unsigned char) c == 0xff) { + if (wanted) + throw new error(std::string("Missing '") + wanted + "'"); + else + throw new error("Unexpected end of input"); + } else { + if (wanted) + throw new error(std::string("Invalid char '") + c + + "' (wanted '" + wanted + "')"); + else + throw new error(std::string("Invalid char '") + c + "'"); + } +} + #endif // _ERROR_H @@ -1,985 +1,264 @@ +#ifdef USE_PCH +#include "pch.h" +#else #include "format.h" #include "error.h" #include "util.h" +#ifdef USE_BOOST_PYTHON +#include "py_eval.h" +#endif #include <cstdlib> +#endif namespace ledger { -format_t::elision_style_t format_t::elision_style = ABBREVIATE; -int format_t::abbrev_length = 2; - -bool format_t::ansi_codes = false; -bool format_t::ansi_invert = false; - -std::string format_t::truncate(const std::string& str, unsigned int width, - const bool is_account) -{ - const int len = str.length(); - if (len <= width) - return str; - - assert(width < 4095); - - char buf[4096]; - - switch (elision_style) { - case TRUNCATE_LEADING: - // This method truncates at the beginning. - std::strncpy(buf, str.c_str() + (len - width), width); - buf[0] = '.'; - buf[1] = '.'; - break; - - case TRUNCATE_MIDDLE: - // This method truncates in the middle. - std::strncpy(buf, str.c_str(), width / 2); - std::strncpy(buf + width / 2, - str.c_str() + (len - (width / 2 + width % 2)), - width / 2 + width % 2); - buf[width / 2 - 1] = '.'; - buf[width / 2] = '.'; - break; - - case ABBREVIATE: - if (is_account) { - std::list<std::string> parts; - std::string::size_type beg = 0; - for (std::string::size_type pos = str.find(':'); - pos != std::string::npos; - beg = pos + 1, pos = str.find(':', beg)) - parts.push_back(std::string(str, beg, pos - beg)); - parts.push_back(std::string(str, beg)); - - std::string result; - int newlen = len; - for (std::list<std::string>::iterator i = parts.begin(); - i != parts.end(); - i++) { - // Don't contract the last element - std::list<std::string>::iterator x = i; - if (++x == parts.end()) { - result += *i; - break; - } - - if (newlen > width) { - result += std::string(*i, 0, abbrev_length); - result += ":"; - newlen -= (*i).length() - abbrev_length; - } else { - result += *i; - result += ":"; - } - } - - if (newlen > width) { - // Even abbreviated its too big to show the last account, so - // abbreviate all but the last and truncate at the beginning. - std::strncpy(buf, result.c_str() + (result.length() - width), width); - buf[0] = '.'; - buf[1] = '.'; - } else { - std::strcpy(buf, result.c_str()); - } - break; - } - // fall through... - - case TRUNCATE_TRAILING: - // This method truncates at the end (the default). - std::strncpy(buf, str.c_str(), width - 2); - buf[width - 2] = '.'; - buf[width - 1] = '.'; - break; - } - buf[width] = '\0'; - - return buf; -} - -std::string partial_account_name(const account_t& account) -{ - std::string name; - - for (const account_t * acct = &account; - acct && acct->parent; - acct = acct->parent) { - if (account_has_xdata(*acct) && - account_xdata_(*acct).dflags & ACCOUNT_DISPLAYED) - break; - - if (name.empty()) - name = acct->name; - else - name = acct->name + ":" + name; - } - - return name; -} - -element_t * format_t::parse_elements(const std::string& fmt) +void format_t::parse(const std::string& fmt) { - std::auto_ptr<element_t> result; - element_t * current = NULL; char buf[1024]; char * q = buf; + if (elements.size() > 0) + clear_elements(); + format_string = fmt; + for (const char * p = fmt.c_str(); *p; p++) { if (*p != '%' && *p != '\\') { *q++ = *p; continue; } - - if (! result.get()) { - result.reset(new element_t); - current = result.get(); - } else { - current->next = new element_t; - current = current->next; + else if (*p == '\\') { + p++; + switch (*p) { + case 'b': *q++ = '\b'; break; + case 'f': *q++ = '\f'; break; + case 'n': *q++ = '\n'; break; + case 'r': *q++ = '\r'; break; + case 't': *q++ = '\t'; break; + case 'v': *q++ = '\v'; break; + default: + *q++ = *p; + break; + } + continue; + } + else { + assert(*p == '%'); + if (*(p + 1) == '%') { + p++; // %% is the same as \% + *q++ = *p; + continue; + } } + current = new element_t; + elements.push_back(current); + if (q != buf) { - current->type = element_t::STRING; - current->chars = std::string(buf, q); + current->kind = element_t::TEXT; + current->chars = new std::string(buf, q); q = buf; - current->next = new element_t; - current = current->next; - } - - if (*p == '\\') { - p++; - current->type = element_t::STRING; - switch (*p) { - case 'b': current->chars = "\b"; break; - case 'f': current->chars = "\f"; break; - case 'n': current->chars = "\n"; break; - case 'r': current->chars = "\r"; break; - case 't': current->chars = "\t"; break; - case 'v': current->chars = "\v"; break; - } - continue; + current = new element_t; + elements.push_back(current); } ++p; - while (*p == '!' || *p == '-') { - switch (*p) { - case '-': - current->flags |= ELEMENT_ALIGN_LEFT; - break; - case '!': - current->flags |= ELEMENT_HIGHLIGHT; - break; - } + if (*p == '-') { + current->align_left = true; ++p; } - int num = 0; - while (*p && std::isdigit(*p)) { - num *= 10; - num += *p++ - '0'; + if (*p && std::isdigit(*p)) { + int num = *p++ - '0'; + while (*p && std::isdigit(*p)) { + num *= 10; + num += *p++ - '0'; + } + current->min_width = num; } - current->min_width = num; if (*p == '.') { ++p; - num = 0; + int num = 0; while (*p && std::isdigit(*p)) { num *= 10; num += *p++ - '0'; } + current->max_width = num; - if (current->min_width == 0) + if (current->min_width == -1) current->min_width = current->max_width; } + if (current->max_width != -1 && current->min_width != -1 && + current->max_width < current->min_width) + throw new format_error("Maximum width is less than the minimum width"); + switch (*p) { - case '%': - current->type = element_t::STRING; - current->chars = "%"; + case '|': + current->kind = element_t::COLUMN; break; + case '{': case '(': { + char open = *p; + char close = *p == '{' ? '}' : ')'; ++p; const char * b = p; int depth = 1; while (*p) { - if (*p == ')' && --depth == 0) + if (*p == close && --depth == 0) break; - else if (*p == '(') + else if (*p == open) ++depth; p++; } - if (*p != ')') - throw new format_error("Missing ')'"); - - current->type = element_t::VALUE_EXPR; + if (*p != close) + throw new format_error(std::string("Missing '") + close + "'"); - assert(! current->val_expr); - current->val_expr = std::string(b, p); - break; - } - - case '[': { - ++p; - const char * b = p; - int depth = 1; - while (*p) { - if (*p == ']' && --depth == 0) - break; - else if (*p == '[') - ++depth; - p++; + if (open == '{') { + assert(! current->xpath); + current->kind = element_t::XPATH; + current->xpath = new xml::xpath_t(std::string(b, p)); + } else { + assert(! current->format); + current->kind = element_t::GROUP; + current->format = new format_t(std::string(b, p)); } - if (*p != ']') - throw new format_error("Missing ']'"); - - current->type = element_t::DATE_STRING; - current->chars = std::string(b, p); break; } - case 'x': - switch (*++p) { - case 'B': current->type = element_t::XACT_BEG_POS; break; - case 'b': current->type = element_t::XACT_BEG_LINE; break; - case 'E': current->type = element_t::XACT_END_POS; break; - case 'e': current->type = element_t::XACT_END_LINE; break; - case '\0': - goto END; - } - break; - - case 'd': - current->type = element_t::COMPLETE_DATE_STRING; - current->chars = datetime_t::output_format; - break; - case 'D': - current->type = element_t::DATE_STRING; - current->chars = datetime_t::output_format; + default: + assert(! current->xpath); + current->kind = element_t::XPATH; + current->xpath = new xml::xpath_t(std::string(p, p + 1)); break; - - case 'S': current->type = element_t::SOURCE; break; - case 'B': current->type = element_t::ENTRY_BEG_POS; break; - case 'b': current->type = element_t::ENTRY_BEG_LINE; break; - case 'E': current->type = element_t::ENTRY_END_POS; break; - case 'e': current->type = element_t::ENTRY_END_LINE; break; - case 'X': current->type = element_t::CLEARED; break; - case 'Y': current->type = element_t::ENTRY_CLEARED; break; - case 'C': current->type = element_t::CODE; break; - case 'P': current->type = element_t::PAYEE; break; - case 'W': current->type = element_t::OPT_ACCOUNT; break; - case 'a': current->type = element_t::ACCOUNT_NAME; break; - case 'A': current->type = element_t::ACCOUNT_FULLNAME; break; - case 't': current->type = element_t::AMOUNT; break; - case 'o': current->type = element_t::OPT_AMOUNT; break; - case 'T': current->type = element_t::TOTAL; break; - case 'N': current->type = element_t::NOTE; break; - case 'n': current->type = element_t::OPT_NOTE; break; - case '|': current->type = element_t::SPACER; break; - case '_': current->type = element_t::DEPTH_SPACER; break; } } END: if (q != buf) { - if (! result.get()) { - result.reset(new element_t); - current = result.get(); - } else { - current->next = new element_t; - current = current->next; - } - current->type = element_t::STRING; - current->chars = std::string(buf, q); - } + current = new element_t; + elements.push_back(current); - return result.release(); -} - -namespace { - inline void mark_red(std::ostream& out, const element_t * elem) { - out.setf(std::ios::left); - out.width(0); - out << "\e[31m"; - - if (elem->flags & ELEMENT_ALIGN_LEFT) - out << std::left; - else - out << std::right; - - if (elem->min_width > 0) - out.width(elem->min_width); + current->kind = element_t::TEXT; + current->chars = new std::string(buf, q); } +} - inline void mark_plain(std::ostream& out) { - out << "\e[0m"; +void format_t::compile(xml::node_t * context) +{ + for (std::list<element_t *>::iterator i = elements.begin(); + i != elements.end(); + i++) + switch ((*i)->kind) { + case element_t::XPATH: + assert((*i)->xpath); + (*i)->xpath->compile(context); + break; + case element_t::GROUP: + assert((*i)->format); + (*i)->format->compile(context); + break; } } -void format_t::format(std::ostream& out_str, const details_t& details) const +int format_t::element_formatter_t::operator() + (std::ostream& out_str, element_t * elem, xml::node_t * context, + int column) const { - for (const element_t * elem = elements; elem; elem = elem->next) { - std::ostringstream out; - std::string name; - bool ignore_max_width = false; - - if (elem->flags & ELEMENT_ALIGN_LEFT) - out << std::left; - else - out << std::right; - - if (elem->min_width > 0) - out.width(elem->min_width); - - switch (elem->type) { - case element_t::STRING: - out << elem->chars; - break; - - case element_t::AMOUNT: - case element_t::TOTAL: - case element_t::VALUE_EXPR: { - value_expr calc; - switch (elem->type) { - case element_t::AMOUNT: calc = amount_expr; break; - case element_t::TOTAL: calc = total_expr; break; - case element_t::VALUE_EXPR: calc = elem->val_expr; break; - default: - assert(0); - break; - } - if (! calc) - break; - - value_t value; - balance_t * bal = NULL; - - calc->compute(value, details); - - if (! amount_t::keep_price || - ! amount_t::keep_date || - ! amount_t::keep_tag) { - switch (value.type) { - case value_t::AMOUNT: - case value_t::BALANCE: - case value_t::BALANCE_PAIR: - value = value.strip_annotations(); - break; - default: - break; - } - } - - bool highlighted = false; - - switch (value.type) { - case value_t::BOOLEAN: - out << (*((bool *) value.data) ? "true" : "false"); - break; - - case value_t::INTEGER: - if (ansi_codes && elem->flags & ELEMENT_HIGHLIGHT) { - if (ansi_invert) { - if (*((long *) value.data) > 0) { - mark_red(out, elem); - highlighted = true; - } - } else { - if (*((long *) value.data) < 0) { - mark_red(out, elem); - highlighted = true; - } - } - } - out << *((long *) value.data); - break; - - case value_t::DATETIME: - out << *((datetime_t *) value.data); - break; - - case value_t::AMOUNT: - if (ansi_codes && elem->flags & ELEMENT_HIGHLIGHT) { - if (ansi_invert) { - if (*((amount_t *) value.data) > 0) { - mark_red(out, elem); - highlighted = true; - } - } else { - if (*((amount_t *) value.data) < 0) { - mark_red(out, elem); - highlighted = true; - } - } - } - out << *((amount_t *) value.data); - break; - - case value_t::BALANCE: - bal = (balance_t *) value.data; - // fall through... - - case value_t::BALANCE_PAIR: - if (! bal) - bal = &((balance_pair_t *) value.data)->quantity; - - if (ansi_codes && elem->flags & ELEMENT_HIGHLIGHT) { - if (ansi_invert) { - if (*bal > 0) { - mark_red(out, elem); - highlighted = true; - } - } else { - if (*bal < 0) { - mark_red(out, elem); - highlighted = true; - } - } - } - bal->write(out, elem->min_width, - (elem->max_width > 0 ? - elem->max_width : elem->min_width)); - - ignore_max_width = true; - break; - default: - assert(0); - break; - } - - if (highlighted) - mark_plain(out); - break; + if (elem->kind == element_t::COLUMN) { + if (elem->max_width != -1 && elem->max_width < column) { + out_str << '\n'; + column = 0; } - case element_t::OPT_AMOUNT: - if (details.xact) { - std::string disp; - bool use_disp = false; - - if (details.xact->cost && details.xact->amount) { - std::ostringstream stream; - if (! details.xact->amount_expr.expr.empty()) - stream << details.xact->amount_expr.expr; - else - stream << details.xact->amount.strip_annotations(); - - if (! details.xact->cost_expr.empty()) - stream << details.xact->cost_expr; - else - stream << " @ " << amount_t(*details.xact->cost / - details.xact->amount).unround(); - disp = stream.str(); - use_disp = true; - } - else if (details.entry) { - unsigned int xacts_count = 0; - transaction_t * first = NULL; - transaction_t * last = NULL; - - for (transactions_list::const_iterator i - = details.entry->transactions.begin(); - i != details.entry->transactions.end(); - i++) - if (transaction_has_xdata(**i) && - transaction_xdata_(**i).dflags & TRANSACTION_TO_DISPLAY) { - xacts_count++; - if (! first) - first = *i; - last = *i; - } - - use_disp = (xacts_count == 2 && details.xact == last && - first->amount == - last->amount); - } - - if (! use_disp) { - if (! details.xact->amount_expr.expr.empty()) - out << details.xact->amount_expr.expr; - else - out << details.xact->amount.strip_annotations(); - } else { - out << disp; - } - } - break; - - case element_t::SOURCE: - if (details.entry && details.entry->journal) { - int idx = details.entry->src_idx; - for (strings_list::iterator i = details.entry->journal->sources.begin(); - i != details.entry->journal->sources.end(); - i++) - if (! idx--) { - out << *i; - break; - } - } - break; - - case element_t::ENTRY_BEG_POS: - if (details.entry) - out << (unsigned long)details.entry->beg_pos; - break; - - case element_t::ENTRY_BEG_LINE: - if (details.entry) - out << details.entry->beg_line; - break; - - case element_t::ENTRY_END_POS: - if (details.entry) - out << (unsigned long)details.entry->end_pos; - break; - - case element_t::ENTRY_END_LINE: - if (details.entry) - out << details.entry->end_line; - break; - - case element_t::XACT_BEG_POS: - if (details.xact) - out << (unsigned long)details.xact->beg_pos; - break; - - case element_t::XACT_BEG_LINE: - if (details.xact) - out << details.xact->beg_line; - break; - - case element_t::XACT_END_POS: - if (details.xact) - out << (unsigned long)details.xact->end_pos; - break; - - case element_t::XACT_END_LINE: - if (details.xact) - out << details.xact->end_line; - break; - - case element_t::DATE_STRING: { - datetime_t date; - if (details.xact) - date = details.xact->date(); - else if (details.entry) - date = details.entry->date(); - - char buf[256]; - std::strftime(buf, 255, elem->chars.c_str(), date.localtime()); - out << (elem->max_width == 0 ? buf : truncate(buf, elem->max_width)); - break; - } - - case element_t::COMPLETE_DATE_STRING: { - datetime_t actual_date; - datetime_t effective_date; - if (details.xact) { - actual_date = details.xact->actual_date(); - effective_date = details.xact->effective_date(); - } - else if (details.entry) { - actual_date = details.entry->actual_date(); - effective_date = details.entry->effective_date(); - } - - char abuf[256]; - std::strftime(abuf, 255, elem->chars.c_str(), actual_date.localtime()); - - if (effective_date && effective_date != actual_date) { - char buf[512]; - char ebuf[256]; - std::strftime(ebuf, 255, elem->chars.c_str(), - effective_date.localtime()); - - std::strcpy(buf, abuf); - std::strcat(buf, "="); - std::strcat(buf, ebuf); - - out << (elem->max_width == 0 ? buf : truncate(buf, elem->max_width)); - } else { - out << (elem->max_width == 0 ? abuf : truncate(abuf, elem->max_width)); - } - break; - } - - case element_t::CLEARED: - if (details.xact) { - switch (details.xact->state) { - case transaction_t::CLEARED: - out << "* "; - break; - case transaction_t::PENDING: - out << "! "; - break; - } - } - break; - - case element_t::ENTRY_CLEARED: - if (details.entry) { - transaction_t::state_t state; - if (details.entry->get_state(&state)) - switch (state) { - case transaction_t::CLEARED: - out << "* "; - break; - case transaction_t::PENDING: - out << "! "; - break; - } - } - break; - - case element_t::CODE: { - std::string temp; - if (details.entry && ! details.entry->code.empty()) { - temp += "("; - temp += details.entry->code; - temp += ") "; - } - out << temp; - break; + if (elem->min_width != -1 && elem->min_width > column) { + out_str << std::string(elem->min_width - column, ' '); + column = elem->min_width; } - - case element_t::PAYEE: - if (details.entry) - out << (elem->max_width == 0 ? - details.entry->payee : truncate(details.entry->payee, - elem->max_width)); - break; - - case element_t::OPT_NOTE: - if (details.xact && ! details.xact->note.empty()) - out << " ; "; - // fall through... - - case element_t::NOTE: - if (details.xact) - out << (elem->max_width == 0 ? - details.xact->note : truncate(details.xact->note, - elem->max_width)); - break; - - case element_t::OPT_ACCOUNT: - if (details.entry && details.xact) { - transaction_t::state_t state; - if (! details.entry->get_state(&state)) - switch (details.xact->state) { - case transaction_t::CLEARED: - name = "* "; - break; - case transaction_t::PENDING: - name = "! "; - break; - } - } - // fall through... - - case element_t::ACCOUNT_NAME: - case element_t::ACCOUNT_FULLNAME: - if (details.account) { - name += (elem->type == element_t::ACCOUNT_FULLNAME ? - details.account->fullname() : - partial_account_name(*details.account)); - - if (details.xact && details.xact->flags & TRANSACTION_VIRTUAL) { - if (elem->max_width > 2) - name = truncate(name, elem->max_width - 2, true); - - if (details.xact->flags & TRANSACTION_BALANCE) - name = "[" + name + "]"; - else - name = "(" + name + ")"; - } - else if (elem->max_width > 0) - name = truncate(name, elem->max_width, true); - - out << name; - } else { - out << " "; - } - break; - - case element_t::SPACER: - out << " "; - break; - - case element_t::DEPTH_SPACER: - for (const account_t * acct = details.account; - acct; - acct = acct->parent) - if (account_has_xdata(*acct) && - account_xdata_(*acct).dflags & ACCOUNT_DISPLAYED) { - if (elem->min_width > 0 || elem->max_width > 0) - out.width(elem->min_width > elem->max_width ? - elem->min_width : elem->max_width); - out << " "; - } - break; - - default: - assert(0); - break; - } - - std::string temp = out.str(); - if (! ignore_max_width && - elem->max_width > 0 && elem->max_width < temp.length()) - temp.erase(elem->max_width); - out_str << temp; + return column; } -} -format_transactions::format_transactions(std::ostream& _output_stream, - const std::string& format) - : output_stream(_output_stream), last_entry(NULL), last_xact(NULL) -{ - const char * f = format.c_str(); - if (const char * p = std::strstr(f, "%/")) { - first_line_format.reset(std::string(f, 0, p - f)); - next_lines_format.reset(std::string(p + 2)); - } else { - first_line_format.reset(format); - next_lines_format.reset(format); - } -} + std::ostringstream out; -void format_transactions::operator()(transaction_t& xact) -{ - if (! transaction_has_xdata(xact) || - ! (transaction_xdata_(xact).dflags & TRANSACTION_DISPLAYED)) { - if (last_entry != xact.entry) { - first_line_format.format(output_stream, details_t(xact)); - last_entry = xact.entry; - } - else if (last_xact && last_xact->date() != xact.date()) { - first_line_format.format(output_stream, details_t(xact)); - } - else { - next_lines_format.format(output_stream, details_t(xact)); - } + if (elem->align_left) + out << std::left; + else + out << std::right; - transaction_xdata(xact).dflags |= TRANSACTION_DISPLAYED; - last_xact = &xact; - } -} + if (elem->min_width > 0) + out.width(elem->min_width); -void format_entries::format_last_entry() -{ - bool first = true; - for (transactions_list::const_iterator i = last_entry->transactions.begin(); - i != last_entry->transactions.end(); - i++) { - if (transaction_has_xdata(**i) && - transaction_xdata_(**i).dflags & TRANSACTION_TO_DISPLAY) { - if (first) { - first_line_format.format(output_stream, details_t(**i)); - first = false; - } else { - next_lines_format.format(output_stream, details_t(**i)); - } - transaction_xdata_(**i).dflags |= TRANSACTION_DISPLAYED; - } - } -} - -void format_entries::operator()(transaction_t& xact) -{ - transaction_xdata(xact).dflags |= TRANSACTION_TO_DISPLAY; + int start_column = column; - if (last_entry && xact.entry != last_entry) - format_last_entry(); + if (elem->kind == element_t::XPATH) + elem->xpath->calc(context).strip_annotations() + .write(out, elem->min_width, elem->max_width); + else if (elem->kind == element_t::GROUP) + column = elem->format->format(out, context, column); + else if (elem->kind == element_t::TEXT) + out << *elem->chars; + else + assert(0); - last_entry = xact.entry; -} + std::string temp = out.str(); + for (std::string::const_iterator i = temp.begin(); + i != temp.end(); + i++) + if (*i == '\n' || *i == '\r') + column = 0; + else + column++; -void print_entry(std::ostream& out, const entry_base_t& entry_base, - const std::string& prefix) -{ - std::string print_format; + int virtual_width = column - start_column; - if (const entry_t * entry = dynamic_cast<const entry_t *>(&entry_base)) { - print_format = (prefix + "%D %X%C%P\n" + - prefix + " %-34A %12o\n%/" + - prefix + " %-34A %12o\n"); + if (elem->min_width != -1 && virtual_width < elem->min_width) { + out_str << temp << std::string(' ', elem->min_width - virtual_width); } - else if (const auto_entry_t * entry = - dynamic_cast<const auto_entry_t *>(&entry_base)) { - out << "= " << entry->predicate_string << '\n'; - print_format = prefix + " %-34A %12o\n"; - } - else if (const period_entry_t * entry = - dynamic_cast<const period_entry_t *>(&entry_base)) { - out << "~ " << entry->period_string << '\n'; - print_format = prefix + " %-34A %12o\n"; + else if (elem->max_width != -1 && virtual_width > elem->max_width) { + temp.erase(temp.length() - (virtual_width - elem->max_width)); + out_str << temp; } else { - assert(0); + out_str << temp; } - format_entries formatter(out, print_format); - walk_transactions(const_cast<transactions_list&>(entry_base.transactions), - formatter); - formatter.flush(); - - clear_transaction_xdata cleaner; - walk_transactions(const_cast<transactions_list&>(entry_base.transactions), - cleaner); + return column; } -bool disp_subaccounts_p(const account_t& account, - const item_predicate<account_t>& disp_pred, - const account_t *& to_show) +int format_t::format(std::ostream& out, xml::node_t * context, + int column, const element_formatter_t& formatter) const { - bool display = false; - unsigned int counted = 0; - bool matches = disp_pred(account); - value_t acct_total; - bool computed = false; - value_t result; - - to_show = NULL; - - for (accounts_map::const_iterator i = account.accounts.begin(); - i != account.accounts.end(); - i++) { - if (! disp_pred(*(*i).second)) - continue; - - compute_total(result, details_t(*(*i).second)); - if (! computed) { - compute_total(acct_total, details_t(account)); - computed = true; - } + for (std::list<element_t *>::const_iterator i = elements.begin(); + i != elements.end(); + i++) + column = formatter(out, *i, context, column); - if ((result != acct_total) || counted > 0) { - display = matches; - break; - } - to_show = (*i).second; - counted++; - } - - return display; -} - -bool display_account(const account_t& account, - const item_predicate<account_t>& disp_pred) -{ - // Never display an account that has already been displayed. - if (account_has_xdata(account) && - account_xdata_(account).dflags & ACCOUNT_DISPLAYED) - return false; - - // At this point, one of two possibilities exists: the account is a - // leaf which matches the predicate restrictions; or it is a parent - // and two or more children must be subtotaled; or it is a parent - // and its child has been hidden by the predicate. So first, - // determine if it is a parent that must be displayed regardless of - // the predicate. - - const account_t * account_to_show = NULL; - if (disp_subaccounts_p(account, disp_pred, account_to_show)) - return true; - - return ! account_to_show && disp_pred(account); + return column; } -void format_account::operator()(account_t& account) -{ - if (display_account(account, disp_pred)) { - if (! account.parent) { - account_xdata(account).dflags |= ACCOUNT_TO_DISPLAY; - } else { - format.format(output_stream, details_t(account)); - account_xdata(account).dflags |= ACCOUNT_DISPLAYED; - } - } -} +} // namespace ledger -format_equity::format_equity(std::ostream& _output_stream, - const std::string& _format, - const std::string& display_predicate) - : output_stream(_output_stream), disp_pred(display_predicate) -{ - const char * f = _format.c_str(); - if (const char * p = std::strstr(f, "%/")) { - first_line_format.reset(std::string(f, 0, p - f)); - next_lines_format.reset(std::string(p + 2)); - } else { - first_line_format.reset(_format); - next_lines_format.reset(_format); - } +#ifdef USE_BOOST_PYTHON - entry_t header_entry; - header_entry.payee = "Opening Balances"; - header_entry._date = datetime_t::now; - first_line_format.format(output_stream, details_t(header_entry)); -} +#ifndef USE_PCH +#include <boost/python.hpp> +#endif -void format_equity::flush() -{ - account_xdata_t xdata; - xdata.value = total; - xdata.value.negate(); - account_t summary(NULL, "Equity:Opening Balances"); - summary.data = &xdata; - - if (total.type >= value_t::BALANCE) { - balance_t * bal; - if (total.type == value_t::BALANCE) - bal = (balance_t *) total.data; - else if (total.type == value_t::BALANCE_PAIR) - bal = &((balance_pair_t *) total.data)->quantity; - else - assert(0); - - for (amounts_map::const_iterator i = bal->amounts.begin(); - i != bal->amounts.end(); - i++) { - xdata.value = (*i).second; - xdata.value.negate(); - next_lines_format.format(output_stream, details_t(summary)); - } - } else { - next_lines_format.format(output_stream, details_t(summary)); - } - output_stream.flush(); -} +using namespace boost::python; +using namespace ledger; -void format_equity::operator()(account_t& account) +void export_format() { - if (display_account(account, disp_pred)) { - if (account_has_xdata(account)) { - value_t val = account_xdata_(account).value; - - if (val.type >= value_t::BALANCE) { - balance_t * bal; - if (val.type == value_t::BALANCE) - bal = (balance_t *) val.data; - else if (val.type == value_t::BALANCE_PAIR) - bal = &((balance_pair_t *) val.data)->quantity; - else - assert(0); - - for (amounts_map::const_iterator i = bal->amounts.begin(); - i != bal->amounts.end(); - i++) { - account_xdata_(account).value = (*i).second; - next_lines_format.format(output_stream, details_t(account)); - } - account_xdata_(account).value = val; - } else { - next_lines_format.format(output_stream, details_t(account)); - } - total += val; - } - account_xdata(account).dflags |= ACCOUNT_DISPLAYED; - } + class_< format_t > ("Format") + .def(init<std::string>()) + .def("parse", &format_t::parse) + .def("format", &format_t::format) + ; } -} // namespace ledger +#endif // USE_BOOST_PYTHON @@ -1,209 +1,108 @@ #ifndef _FORMAT_H #define _FORMAT_H -#include "journal.h" -#include "valexpr.h" -#include "walk.h" +#include "xpath.h" +#include "error.h" +#include "debug.h" -namespace ledger { - -std::string truncated(const std::string& str, unsigned int width, - const int style = 2); +#include <list> -std::string partial_account_name(const account_t& account, - const unsigned int start_depth); - -#define ELEMENT_ALIGN_LEFT 0x01 -#define ELEMENT_HIGHLIGHT 0x02 +namespace ledger { -struct element_t +class format_t { - enum kind_t { - STRING, - VALUE_EXPR, - SOURCE, - ENTRY_BEG_POS, - ENTRY_BEG_LINE, - ENTRY_END_POS, - ENTRY_END_LINE, - XACT_BEG_POS, - XACT_BEG_LINE, - XACT_END_POS, - XACT_END_LINE, - DATE_STRING, - COMPLETE_DATE_STRING, - CLEARED, - ENTRY_CLEARED, - CODE, - PAYEE, - OPT_ACCOUNT, - ACCOUNT_NAME, - ACCOUNT_FULLNAME, - AMOUNT, - OPT_AMOUNT, - TOTAL, - NOTE, - OPT_NOTE, - SPACER, - DEPTH_SPACER - }; - - kind_t type; - unsigned char flags; - std::string chars; - unsigned char min_width; - unsigned char max_width; - value_expr val_expr; - - struct element_t * next; + public: + struct element_t + { + bool align_left; + short min_width; + short max_width; + + enum kind_t { UNKNOWN, TEXT, COLUMN, XPATH, GROUP } kind; + union { + std::string * chars; + xml::xpath_t * xpath; + format_t * format; + }; + + element_t() + : align_left(false), min_width(-1), max_width(-1), + kind(UNKNOWN), chars(NULL) { + TRACE_CTOR("element_t()"); + } - element_t() : type(STRING), flags(false), - min_width(0), max_width(0), next(NULL) { - DEBUG_PRINT("ledger.memory.ctors", "ctor element_t"); - } + ~element_t() { + TRACE_DTOR("element_t"); + + switch (kind) { + case TEXT: + delete chars; + break; + case XPATH: + delete xpath; + break; + case GROUP: + delete format; + break; + default: + assert(! chars); + break; + } + } - ~element_t() { - DEBUG_PRINT("ledger.memory.dtors", "dtor element_t"); - if (next) delete next; // recursive, but not too deep - } -}; + private: + element_t(const element_t& other); + }; -struct format_t -{ - std::string format_string; - element_t * elements; - - enum elision_style_t { - TRUNCATE_TRAILING, - TRUNCATE_MIDDLE, - TRUNCATE_LEADING, - ABBREVIATE + struct element_formatter_t { + virtual ~element_formatter_t() {} + virtual int operator()(std::ostream& out, element_t * element, + xml::node_t * context, int column) const; }; - static elision_style_t elision_style; - static int abbrev_length; + std::string format_string; + std::list<element_t *> elements; - static bool ansi_codes; - static bool ansi_invert; + private: + format_t(const format_t&); - format_t() : elements(NULL) { - DEBUG_PRINT("ledger.memory.ctors", "ctor format_t"); - } - format_t(const std::string& _format) : elements(NULL) { - DEBUG_PRINT("ledger.memory.ctors", "ctor format_t"); - reset(_format); + public: + format_t() { + TRACE_CTOR("format_t()"); } - ~format_t() { - DEBUG_PRINT("ledger.memory.dtors", "dtor format_t"); - if (elements) delete elements; + format_t(const std::string& fmt) { + TRACE_CTOR("format_t(const std::string&)"); + parse(fmt); } - void reset(const std::string& _format) { - if (elements) - delete elements; - elements = parse_elements(_format); - format_string = _format; + void clear_elements() { + for (std::list<element_t *>::iterator i = elements.begin(); + i != elements.end(); + i++) + delete *i; + elements.clear(); } - static element_t * parse_elements(const std::string& fmt); - - static std::string truncate(const std::string& str, unsigned int width, - const bool is_account = false); - - void format(std::ostream& out, const details_t& details) const; -}; - -class format_transactions : public item_handler<transaction_t> -{ - protected: - std::ostream& output_stream; - format_t first_line_format; - format_t next_lines_format; - entry_t * last_entry; - transaction_t * last_xact; - - public: - format_transactions(std::ostream& _output_stream, - const std::string& format); - - virtual void flush() { - output_stream.flush(); + virtual ~format_t() { + TRACE_DTOR("format_t"); + clear_elements(); } - virtual void operator()(transaction_t& xact); -}; - -class format_entries : public format_transactions -{ - public: - format_entries(std::ostream& output_stream, const std::string& format) - : format_transactions(output_stream, format) {} - virtual void format_last_entry(); + void parse(const std::string& fmt); - virtual void flush() { - if (last_entry) { - format_last_entry(); - last_entry = NULL; - } - format_transactions::flush(); + void compile(const std::string& fmt, xml::node_t * context = NULL) { + parse(fmt); + compile(context); } - virtual void operator()(transaction_t& xact); -}; - -void print_entry(std::ostream& out, const entry_base_t& entry, - const std::string& prefix = ""); - -bool disp_subaccounts_p(const account_t& account, - const item_predicate<account_t>& disp_pred, - const account_t *& to_show); - -inline bool disp_subaccounts_p(const account_t& account) { - const account_t * temp; - return disp_subaccounts_p(account, item_predicate<account_t>(NULL), temp); -} - -bool display_account(const account_t& account, - const item_predicate<account_t>& disp_pred); - -class format_account : public item_handler<account_t> -{ - std::ostream& output_stream; + void compile(xml::node_t * context = NULL); - item_predicate<account_t> disp_pred; + int format(std::ostream& out, xml::node_t * context = NULL, + int column = 0, const element_formatter_t& formatter = + element_formatter_t()) const; - public: - format_t format; - - format_account(std::ostream& _output_stream, - const std::string& _format, - const std::string& display_predicate = NULL) - : output_stream(_output_stream), disp_pred(display_predicate), - format(_format) {} - - virtual void flush() { - output_stream.flush(); + operator bool() const { + return ! format_string.empty(); } - - virtual void operator()(account_t& account); -}; - -class format_equity : public item_handler<account_t> -{ - std::ostream& output_stream; - format_t first_line_format; - format_t next_lines_format; - - item_predicate<account_t> disp_pred; - - mutable value_t total; - - public: - format_equity(std::ostream& _output_stream, - const std::string& _format, - const std::string& display_predicate); - - virtual void flush(); - virtual void operator()(account_t& account); }; class format_error : public error { @@ -1,217 +1,160 @@ -#include "gnucash.h" -#include "journal.h" -#include "format.h" -#include "error.h" -#include "acconf.h" - -#include <iostream> -#include <sstream> -#include <cstring> - -extern "C" { -#if defined(HAVE_EXPAT) -#include <expat.h> // expat XML parser -#elif defined(HAVE_XMLPARSE) -#include <xmlparse.h> // expat XML parser +#ifdef USE_PCH +#include "pch.h" #else -#error "No XML parser library defined." +#include "gnucash.h" #endif -} namespace ledger { -typedef std::map<const std::string, account_t *> accounts_map; -typedef std::pair<const std::string, account_t *> accounts_pair; - -typedef std::map<account_t *, commodity_t *> account_comm_map; -typedef std::pair<account_t *, commodity_t *> account_comm_pair; - -static journal_t * curr_journal; -static account_t * master_account; -static account_t * curr_account; -static std::string curr_account_id; -static entry_t * curr_entry; -static commodity_t * entry_comm; -static commodity_t * curr_comm; -static amount_t curr_value; -static amount_t curr_quant; -static XML_Parser current_parser; -static accounts_map accounts_by_id; -static account_comm_map account_comms; -static unsigned int count; -static std::string have_error; - -static std::istream * instreamp; -static unsigned int offset; -static XML_Parser parser; -static std::string path; -static unsigned int src_idx; -static istream_pos_type beg_pos; -static unsigned long beg_line; - -static transaction_t::state_t curr_state; - -static enum action_t { - NO_ACTION, - ACCOUNT_NAME, - ACCOUNT_ID, - ACCOUNT_PARENT, - COMM_SYM, - COMM_NAME, - COMM_PREC, - ENTRY_NUM, - ALMOST_ENTRY_DATE, - ENTRY_DATE, - ENTRY_DESC, - XACT_STATE, - XACT_AMOUNT, - XACT_VALUE, - XACT_QUANTITY, - XACT_ACCOUNT, - XACT_NOTE -} action; - -static void startElement(void *userData, const char *name, const char **atts) +void startElement(void *userData, const char *name, const char **atts) { + gnucash_parser_t * parser = static_cast<gnucash_parser_t *>(userData); + if (std::strcmp(name, "gnc:account") == 0) { - curr_account = new account_t(master_account); + parser->curr_account = new account_t(parser->master_account); } else if (std::strcmp(name, "act:name") == 0) - action = ACCOUNT_NAME; + parser->action = gnucash_parser_t::ACCOUNT_NAME; else if (std::strcmp(name, "act:id") == 0) - action = ACCOUNT_ID; + parser->action = gnucash_parser_t::ACCOUNT_ID; else if (std::strcmp(name, "act:parent") == 0) - action = ACCOUNT_PARENT; + parser->action = gnucash_parser_t::ACCOUNT_PARENT; else if (std::strcmp(name, "gnc:commodity") == 0) { - assert(! curr_comm); + assert(! parser->curr_comm); #if 0 // jww (2006-03-02): !!! - curr_comm = new commodity_t(""); + parser->curr_comm = new commodity_t(""); #endif } else if (std::strcmp(name, "cmdty:id") == 0) - action = COMM_SYM; + parser->action = gnucash_parser_t::COMM_SYM; else if (std::strcmp(name, "cmdty:name") == 0) - action = COMM_NAME; + parser->action = gnucash_parser_t::COMM_NAME; else if (std::strcmp(name, "cmdty:fraction") == 0) - action = COMM_PREC; + parser->action = gnucash_parser_t::COMM_PREC; else if (std::strcmp(name, "gnc:transaction") == 0) { - assert(! curr_entry); - curr_entry = new entry_t; + assert(! parser->curr_entry); + parser->curr_entry = new entry_t; } else if (std::strcmp(name, "trn:num") == 0) - action = ENTRY_NUM; + parser->action = gnucash_parser_t::ENTRY_NUM; else if (std::strcmp(name, "trn:date-posted") == 0) - action = ALMOST_ENTRY_DATE; - else if (action == ALMOST_ENTRY_DATE && std::strcmp(name, "ts:date") == 0) - action = ENTRY_DATE; + parser->action = gnucash_parser_t::ALMOST_ENTRY_DATE; + else if (parser->action == gnucash_parser_t::ALMOST_ENTRY_DATE && + std::strcmp(name, "ts:date") == 0) + parser->action = gnucash_parser_t::ENTRY_DATE; else if (std::strcmp(name, "trn:description") == 0) - action = ENTRY_DESC; + parser->action = gnucash_parser_t::ENTRY_DESC; else if (std::strcmp(name, "trn:split") == 0) { - assert(curr_entry); - curr_entry->add_transaction(new transaction_t(curr_account)); + assert(parser->curr_entry); + parser->curr_entry->add_transaction(new transaction_t(parser->curr_account)); } else if (std::strcmp(name, "split:reconciled-state") == 0) - action = XACT_STATE; + parser->action = gnucash_parser_t::XACT_STATE; else if (std::strcmp(name, "split:amount") == 0) - action = XACT_AMOUNT; + parser->action = gnucash_parser_t::XACT_AMOUNT; else if (std::strcmp(name, "split:value") == 0) - action = XACT_VALUE; + parser->action = gnucash_parser_t::XACT_VALUE; else if (std::strcmp(name, "split:quantity") == 0) - action = XACT_QUANTITY; + parser->action = gnucash_parser_t::XACT_QUANTITY; else if (std::strcmp(name, "split:account") == 0) - action = XACT_ACCOUNT; + parser->action = gnucash_parser_t::XACT_ACCOUNT; else if (std::strcmp(name, "split:memo") == 0) - action = XACT_NOTE; + parser->action = gnucash_parser_t::XACT_NOTE; } -static void endElement(void *userData, const char *name) +void endElement(void *userData, const char *name) { + gnucash_parser_t * parser = static_cast<gnucash_parser_t *>(userData); + if (std::strcmp(name, "gnc:account") == 0) { - assert(curr_account); - if (curr_account->parent == master_account) - curr_journal->add_account(curr_account); - accounts_by_id.insert(accounts_pair(curr_account_id, curr_account)); - curr_account = NULL; + assert(parser->curr_account); + if (parser->curr_account->parent == parser->master_account) + parser->curr_journal->add_account(parser->curr_account); + parser->accounts_by_id.insert(accounts_pair(parser->curr_account_id, + parser->curr_account)); + parser->curr_account = NULL; } else if (std::strcmp(name, "gnc:commodity") == 0) { - assert(curr_comm); + assert(parser->curr_comm); #if 0 // jww (2006-03-02): !!! - commodity_t::add_commodity(curr_comm); + commodity_t::add_commodity(parser->curr_comm); #endif - curr_comm = NULL; + parser->curr_comm = NULL; } else if (std::strcmp(name, "gnc:transaction") == 0) { - assert(curr_entry); + assert(parser->curr_entry); // Add the new entry (what gnucash calls a 'transaction') to the // journal - if (! curr_journal->add_entry(curr_entry)) { - print_entry(std::cerr, *curr_entry); - have_error = "The above entry does not balance"; - delete curr_entry; + if (! parser->curr_journal->add_entry(parser->curr_entry)) { + print_entry(std::cerr, *parser->curr_entry); + parser->have_error = "The above entry does not balance"; + delete parser->curr_entry; } else { - curr_entry->src_idx = src_idx; - curr_entry->beg_pos = beg_pos; - curr_entry->beg_line = beg_line; - curr_entry->end_pos = instreamp->tellg(); - curr_entry->end_line = XML_GetCurrentLineNumber(parser) - offset; - count++; + parser->curr_entry->src_idx = parser->src_idx; + parser->curr_entry->beg_pos = parser->beg_pos; + parser->curr_entry->beg_line = parser->beg_line; + parser->curr_entry->end_pos = parser->instreamp->tellg(); + parser->curr_entry->end_line = + XML_GetCurrentLineNumber(parser->expat_parser) - parser->offset; + parser->count++; } // Clear the relevant variables for the next run - curr_entry = NULL; - entry_comm = NULL; + parser->curr_entry = NULL; + parser->entry_comm = NULL; } else if (std::strcmp(name, "trn:split") == 0) { - transaction_t * xact = curr_entry->transactions.back(); + transaction_t * xact = parser->curr_entry->transactions.back(); // Identify the commodity to use for the value of this // transaction. The quantity indicates how many times that value // the transaction is worth. amount_t value; commodity_t * default_commodity = NULL; - if (entry_comm) { - default_commodity = entry_comm; + if (parser->entry_comm) { + default_commodity = parser->entry_comm; } else { - account_comm_map::iterator ac = account_comms.find(xact->account); - if (ac != account_comms.end()) + gnucash_parser_t::account_comm_map::iterator ac = + parser->account_comms.find(xact->account); + if (ac != parser->account_comms.end()) default_commodity = (*ac).second; } if (default_commodity) { - curr_quant.set_commodity(*default_commodity); - value = curr_quant.round(); + parser->curr_quant.set_commodity(*default_commodity); + value = parser->curr_quant.round(); - if (curr_value.commodity() == *default_commodity) - curr_value = value; + if (parser->curr_value.commodity() == *default_commodity) + parser->curr_value = value; } else { - value = curr_quant; + value = parser->curr_quant; } - xact->state = curr_state; + xact->state = parser->curr_state; xact->amount = value; - if (value != curr_value) - xact->cost = new amount_t(curr_value); + if (value != parser->curr_value) + xact->cost = new amount_t(parser->curr_value); - xact->beg_pos = beg_pos; - xact->beg_line = beg_line; - xact->end_pos = instreamp->tellg(); - xact->end_line = XML_GetCurrentLineNumber(parser) - offset; + xact->beg_pos = parser->beg_pos; + xact->beg_line = parser->beg_line; + xact->end_pos = parser->instreamp->tellg(); + xact->end_line = + XML_GetCurrentLineNumber(parser->expat_parser) - parser->offset; // Clear the relevant variables for the next run - curr_state = transaction_t::UNCLEARED; - curr_value = amount_t(); - curr_quant = amount_t(); + parser->curr_state = transaction_t::UNCLEARED; + parser->curr_value = amount_t(); + parser->curr_quant = amount_t(); } - action = NO_ACTION; + parser->action = gnucash_parser_t::NO_ACTION; } - -static amount_t convert_number(const std::string& number, - int * precision = NULL) +amount_t gnucash_parser_t::convert_number(const std::string& number, + int * precision) { const char * num = number.c_str(); @@ -236,116 +179,120 @@ static amount_t convert_number(const std::string& number, } } -static void dataHandler(void *userData, const char *s, int len) +void dataHandler(void *userData, const char *s, int len) { - switch (action) { - case ACCOUNT_NAME: - curr_account->name = std::string(s, len); + gnucash_parser_t * parser = static_cast<gnucash_parser_t *>(userData); + + switch (parser->action) { + case gnucash_parser_t::ACCOUNT_NAME: + parser->curr_account->name = std::string(s, len); break; - case ACCOUNT_ID: - curr_account_id = std::string(s, len); + case gnucash_parser_t::ACCOUNT_ID: + parser->curr_account_id = std::string(s, len); break; - case ACCOUNT_PARENT: { - accounts_map::iterator i = accounts_by_id.find(std::string(s, len)); - assert(i != accounts_by_id.end()); - curr_account->parent = (*i).second; - curr_account->depth = curr_account->parent->depth + 1; - (*i).second->add_account(curr_account); + case gnucash_parser_t::ACCOUNT_PARENT: { + accounts_map::iterator i = parser->accounts_by_id.find(std::string(s, len)); + assert(i != parser->accounts_by_id.end()); + parser->curr_account->parent = (*i).second; + parser->curr_account->depth = parser->curr_account->parent->depth + 1; + (*i).second->add_account(parser->curr_account); break; } - case COMM_SYM: - if (curr_comm) { + case gnucash_parser_t::COMM_SYM: + if (parser->curr_comm) { #if 0 // jww (2006-03-02): !!! - curr_comm->set_symbol(std::string(s, len)); + parser->curr_comm->set_symbol(std::string(s, len)); #endif } - else if (curr_account) { + else if (parser->curr_account) { std::string symbol(s, len); commodity_t * comm = commodity_t::find_or_create(symbol); assert(comm); if (symbol != "$" && symbol != "USD") comm->add_flags(COMMODITY_STYLE_SEPARATED); - account_comms.insert(account_comm_pair(curr_account, comm)); + parser->account_comms.insert + (gnucash_parser_t::account_comm_pair(parser->curr_account, comm)); } - else if (curr_entry) { + else if (parser->curr_entry) { std::string symbol(s, len); - entry_comm = commodity_t::find_or_create(symbol); - assert(entry_comm); + parser->entry_comm = commodity_t::find_or_create(symbol); + assert(parser->entry_comm); if (symbol != "$" && symbol != "USD") - entry_comm->add_flags(COMMODITY_STYLE_SEPARATED); + parser->entry_comm->add_flags(COMMODITY_STYLE_SEPARATED); } break; - case COMM_NAME: - curr_comm->name() = std::string(s, len); + case gnucash_parser_t::COMM_NAME: + parser->curr_comm->name() = std::string(s, len); break; - case COMM_PREC: - curr_comm->set_precision(len - 1); + case gnucash_parser_t::COMM_PREC: + parser->curr_comm->set_precision(len - 1); break; - case ENTRY_NUM: - curr_entry->code = std::string(s, len); + case gnucash_parser_t::ENTRY_NUM: + parser->curr_entry->code = std::string(s, len); break; - case ENTRY_DATE: - curr_entry->_date = std::string(s, len); + case gnucash_parser_t::ENTRY_DATE: + parser->curr_entry->_date = std::string(s, len); break; - case ENTRY_DESC: - curr_entry->payee = std::string(s, len); + case gnucash_parser_t::ENTRY_DESC: + parser->curr_entry->payee = std::string(s, len); break; - case XACT_STATE: + case gnucash_parser_t::XACT_STATE: if (*s == 'y') - curr_state = transaction_t::CLEARED; + parser->curr_state = transaction_t::CLEARED; else if (*s == 'n') - curr_state = transaction_t::UNCLEARED; + parser->curr_state = transaction_t::UNCLEARED; else - curr_state = transaction_t::PENDING; + parser->curr_state = transaction_t::PENDING; break; - case XACT_VALUE: { + case gnucash_parser_t::XACT_VALUE: { int precision; - assert(entry_comm); - curr_value = convert_number(std::string(s, len), &precision); - curr_value.set_commodity(*entry_comm); + assert(parser->entry_comm); + parser->curr_value = parser->convert_number(std::string(s, len), &precision); + parser->curr_value.set_commodity(*parser->entry_comm); - if (precision > entry_comm->precision()) - entry_comm->set_precision(precision); + if (precision > parser->entry_comm->precision()) + parser->entry_comm->set_precision(precision); break; } - case XACT_QUANTITY: - curr_quant = convert_number(std::string(s, len)); + case gnucash_parser_t::XACT_QUANTITY: + parser->curr_quant = parser->convert_number(std::string(s, len)); break; - case XACT_ACCOUNT: { - transaction_t * xact = curr_entry->transactions.back(); + case gnucash_parser_t::XACT_ACCOUNT: { + transaction_t * xact = parser->curr_entry->transactions.back(); - accounts_map::iterator i = accounts_by_id.find(std::string(s, len)); - if (i != accounts_by_id.end()) { + accounts_map::iterator i = + parser->accounts_by_id.find(std::string(s, len)); + if (i != parser->accounts_by_id.end()) { xact->account = (*i).second; } else { - xact->account = curr_journal->find_account("<Unknown>"); + xact->account = parser->curr_journal->find_account("<Unknown>"); - have_error = (std::string("Could not find account ") + - std::string(s, len)); + parser->have_error = (std::string("Could not find account ") + + std::string(s, len)); } break; } - case XACT_NOTE: - curr_entry->transactions.back()->note = std::string(s, len); + case gnucash_parser_t::XACT_NOTE: + parser->curr_entry->transactions.back()->note = std::string(s, len); break; - case NO_ACTION: - case ALMOST_ENTRY_DATE: - case XACT_AMOUNT: + case gnucash_parser_t::NO_ACTION: + case gnucash_parser_t::ALMOST_ENTRY_DATE: + case gnucash_parser_t::XACT_AMOUNT: break; default: @@ -365,7 +312,6 @@ bool gnucash_parser_t::test(std::istream& in) const } unsigned int gnucash_parser_t::parse(std::istream& in, - config_t& config, journal_t * journal, account_t * master, const std::string * original_file) @@ -374,6 +320,8 @@ unsigned int gnucash_parser_t::parse(std::istream& in, // This is the date format used by Gnucash, so override whatever the // user specified. + // + // jww (2006-09-13): Make this parser local somehow. date_t::input_format = "%Y-%m-%d %H:%M:%S %z"; count = 0; @@ -401,10 +349,11 @@ unsigned int gnucash_parser_t::parse(std::istream& in, #endif offset = 2; - parser = current_parser = XML_ParserCreate(NULL); + expat_parser = XML_ParserCreate(NULL); XML_SetElementHandler(parser, startElement, endElement); XML_SetCharacterDataHandler(parser, dataHandler); + XML_SetUserData(parser, this); while (in.good() && ! in.eof()) { beg_pos = in.tellg(); @@ -2,19 +2,87 @@ #define _GNUCASH_H #include "parser.h" +#include "journal.h" +#include "acconf.h" + +#include <iostream> +#include <string> +#include <cstring> + +extern "C" { +#if defined(HAVE_EXPAT) +#include <expat.h> // expat XML parser +#elif defined(HAVE_XMLPARSE) +#include <xmlparse.h> // expat XML parser +#else +#error "No XML parser library defined." +#endif +} namespace ledger { -class gnucash_parser_t : public parser_t +struct gnucash_parser_t : public parser_t { + typedef std::map<const std::string, account_t *> accounts_map; + typedef std::pair<const std::string, account_t *> accounts_pair; + + typedef std::map<account_t *, commodity_t *> account_comm_map; + typedef std::pair<account_t *, commodity_t *> account_comm_pair; + + journal_t * curr_journal; + account_t * master_account; + account_t * curr_account; + std::string curr_account_id; + entry_t * curr_entry; + commodity_t * entry_comm; + commodity_t * curr_comm; + amount_t curr_value; + amount_t curr_quant; + XML_Parser expat_parser; + accounts_map accounts_by_id; + account_comm_map account_comms; + unsigned int count; + std::string have_error; + + std::istream * instreamp; + unsigned int offset; + XML_Parser parser; + std::string path; + unsigned int src_idx; + istream_pos_type beg_pos; + unsigned long beg_line; + + transaction_t::state_t curr_state; + + enum action_t { + NO_ACTION, + ACCOUNT_NAME, + ACCOUNT_ID, + ACCOUNT_PARENT, + COMM_SYM, + COMM_NAME, + COMM_PREC, + ENTRY_NUM, + ALMOST_ENTRY_DATE, + ENTRY_DATE, + ENTRY_DESC, + XACT_STATE, + XACT_AMOUNT, + XACT_VALUE, + XACT_QUANTITY, + XACT_ACCOUNT, + XACT_NOTE + } action; + public: virtual bool test(std::istream& in) const; virtual unsigned int parse(std::istream& in, - config_t& config, journal_t * journal, account_t * master = NULL, const std::string * original_file = NULL); + + amount_t convert_number(const std::string& number, int * precision = NULL); }; } // namespace ledger @@ -1,11 +1,17 @@ +#ifdef USE_PCH +#include "pch.h" +#else #include "journal.h" #include "datetime.h" -#include "valexpr.h" #include "mask.h" #include "format.h" +#ifdef USE_BOOST_PYTHON +#include "py_eval.h" +#endif #include "acconf.h" #include <fstream> +#endif namespace ledger { @@ -15,7 +21,7 @@ bool transaction_t::use_effective_date = false; transaction_t::~transaction_t() { - DEBUG_PRINT("ledger.memory.dtors", "dtor transaction_t"); + TRACE_DTOR("transaction_t"); if (cost) delete cost; } @@ -271,10 +277,9 @@ bool entry_base_t::finalize() entry_t::entry_t(const entry_t& e) : entry_base_t(e), _date(e._date), _date_eff(e._date_eff), - code(e.code), payee(e.payee) + code(e.code), payee(e.payee), data(NULL) { - DEBUG_PRINT("ledger.memory.ctors", "ctor entry_t"); - + TRACE_CTOR("entry_t(copy)"); for (transactions_list::const_iterator i = transactions.begin(); i != transactions.end(); i++) @@ -326,20 +331,6 @@ bool entry_t::valid() const return true; } -auto_entry_t::auto_entry_t(const std::string& _predicate) - : predicate_string(_predicate) -{ - DEBUG_PRINT("ledger.memory.ctors", "ctor auto_entry_t"); - predicate = new item_predicate<transaction_t>(predicate_string); -} - -auto_entry_t::~auto_entry_t() -{ - DEBUG_PRINT("ledger.memory.dtors", "dtor auto_entry_t"); - if (predicate) - delete predicate; -} - void auto_entry_t::extend_entry(entry_base_t& entry, bool post) { transactions_list initial_xacts(entry.transactions.begin(), @@ -348,7 +339,8 @@ void auto_entry_t::extend_entry(entry_base_t& entry, bool post) for (transactions_list::iterator i = initial_xacts.begin(); i != initial_xacts.end(); i++) { - if ((*predicate)(**i)) { + // jww (2006-09-10): Create a scope here based on entry + if (predicate.calc((xml::node_t *) NULL)) { for (transactions_list::iterator t = transactions.begin(); t != transactions.end(); t++) { @@ -379,8 +371,7 @@ void auto_entry_t::extend_entry(entry_base_t& entry, bool post) account_t::~account_t() { - DEBUG_PRINT("ledger.memory.dtors", "dtor account_t " << this); - //assert(! data); + TRACE_DTOR("account_t"); for (accounts_map::iterator i = accounts.begin(); i != accounts.end(); @@ -507,7 +498,7 @@ bool account_t::valid() const journal_t::~journal_t() { - DEBUG_PRINT("ledger.memory.dtors", "dtor journal_t"); + TRACE_DTOR("journal_t"); assert(master); delete master; @@ -613,6 +604,42 @@ bool journal_t::valid() const return true; } +void print_entry(std::ostream& out, const entry_base_t& entry_base, + const std::string& prefix) +{ + std::string print_format; + + if (const entry_t * entry = dynamic_cast<const entry_t *>(&entry_base)) { + print_format = (prefix + "%D %X%C%P\n" + + prefix + " %-34A %12o\n%/" + + prefix + " %-34A %12o\n"); + } + else if (const auto_entry_t * entry = + dynamic_cast<const auto_entry_t *>(&entry_base)) { + out << "= " << entry->predicate.expr << '\n'; + print_format = prefix + " %-34A %12o\n"; + } + else if (const period_entry_t * entry = + dynamic_cast<const period_entry_t *>(&entry_base)) { + out << "~ " << entry->period_string << '\n'; + print_format = prefix + " %-34A %12o\n"; + } + else { + assert(0); + } + +#if 0 + format_entries formatter(out, print_format); + walk_transactions(const_cast<transactions_list&>(entry_base.transactions), + formatter); + formatter.flush(); + + clear_transaction_xdata cleaner; + walk_transactions(const_cast<transactions_list&>(entry_base.transactions), + cleaner); +#endif +} + void entry_context::describe(std::ostream& out) const throw() { if (! desc.empty()) @@ -638,3 +665,387 @@ xact_context::xact_context(const ledger::transaction_t& _xact, } } // namespace ledger + +#ifdef USE_BOOST_PYTHON + +#ifndef USE_PCH +#include <boost/python.hpp> +#include <boost/python/exception_translator.hpp> +#endif + +using namespace boost::python; +using namespace ledger; + +entry_t& transaction_entry(const transaction_t& xact) +{ + return *xact.entry; +} + +unsigned int transactions_len(entry_base_t& entry) +{ + return entry.transactions.size(); +} + +transaction_t& transactions_getitem(entry_base_t& entry, int i) +{ + static int last_index = 0; + static entry_base_t * last_entry = NULL; + static transactions_list::iterator elem; + + std::size_t len = entry.transactions.size(); + + if (abs(i) >= len) { + PyErr_SetString(PyExc_IndexError, "Index out of range"); + throw_error_already_set(); + } + + if (&entry == last_entry && i == last_index + 1) { + last_index = i; + return **++elem; + } + + int x = i < 0 ? len + i : i; + elem = entry.transactions.begin(); + while (--x >= 0) + elem++; + + last_entry = &entry; + last_index = i; + + return **elem; +} + +unsigned int entries_len(journal_t& journal) +{ + return journal.entries.size(); +} + +entry_t& entries_getitem(journal_t& journal, int i) +{ + static int last_index = 0; + static journal_t * last_journal = NULL; + static entries_list::iterator elem; + + std::size_t len = journal.entries.size(); + + if (abs(i) >= len) { + PyErr_SetString(PyExc_IndexError, "Index out of range"); + throw_error_already_set(); + } + + if (&journal == last_journal && i == last_index + 1) { + last_index = i; + return **++elem; + } + + int x = i < 0 ? len + i : i; + elem = journal.entries.begin(); + while (--x >= 0) + elem++; + + last_journal = &journal; + last_index = i; + + return **elem; +} + +unsigned int accounts_len(account_t& account) +{ + return account.accounts.size(); +} + +account_t& accounts_getitem(account_t& account, int i) +{ + static int last_index = 0; + static account_t * last_account = NULL; + static accounts_map::iterator elem; + + std::size_t len = account.accounts.size(); + + if (abs(i) >= len) { + PyErr_SetString(PyExc_IndexError, "Index out of range"); + throw_error_already_set(); + } + + if (&account == last_account && i == last_index + 1) { + last_index = i; + return *(*++elem).second; + } + + int x = i < 0 ? len + i : i; + elem = account.accounts.begin(); + while (--x >= 0) + elem++; + + last_account = &account; + last_index = i; + + return *(*elem).second; +} + +PyObject * py_account_get_data(account_t& account) +{ + return (PyObject *) account.data; +} + +void py_account_set_data(account_t& account, PyObject * obj) +{ + account.data = obj; +} + +account_t * py_find_account_1(journal_t& journal, const std::string& name) +{ + return journal.find_account(name); +} + +account_t * py_find_account_2(journal_t& journal, const std::string& name, + const bool auto_create) +{ + return journal.find_account(name, auto_create); +} + +bool py_add_entry(journal_t& journal, entry_t * entry) { + return journal.add_entry(new entry_t(*entry)); +} + +void py_add_transaction(entry_base_t& entry, transaction_t * xact) { + return entry.add_transaction(new transaction_t(*xact)); +} + +struct entry_base_wrap : public entry_base_t +{ + PyObject * self; + entry_base_wrap(PyObject * self_) : self(self_) {} + + virtual bool valid() const { + return call_method<bool>(self, "valid"); + } +}; + +struct py_entry_finalizer_t : public entry_finalizer_t { + object pyobj; + py_entry_finalizer_t() {} + py_entry_finalizer_t(object obj) : pyobj(obj) {} + py_entry_finalizer_t(const py_entry_finalizer_t& other) + : pyobj(other.pyobj) {} + virtual bool operator()(entry_t& entry, bool post) { + return call<bool>(pyobj.ptr(), entry, post); + } +}; + +std::list<py_entry_finalizer_t> py_finalizers; + +void py_add_entry_finalizer(journal_t& journal, object x) +{ + py_finalizers.push_back(py_entry_finalizer_t(x)); + journal.add_entry_finalizer(&py_finalizers.back()); +} + +void py_remove_entry_finalizer(journal_t& journal, object x) +{ + for (std::list<py_entry_finalizer_t>::iterator i = py_finalizers.begin(); + i != py_finalizers.end(); + i++) + if ((*i).pyobj == x) { + journal.remove_entry_finalizer(&(*i)); + py_finalizers.erase(i); + return; + } +} + +void py_run_entry_finalizers(journal_t& journal, entry_t& entry, bool post) +{ + run_hooks(journal.entry_finalize_hooks, entry, post); +} + +#define EXC_TRANSLATOR(type) \ + void exc_translate_ ## type(const type& err) { \ + PyErr_SetString(PyExc_RuntimeError, err.what()); \ + } + +EXC_TRANSLATOR(balance_error) +EXC_TRANSLATOR(interval_expr_error) +EXC_TRANSLATOR(format_error) +EXC_TRANSLATOR(parse_error) + +value_t py_transaction_amount(transaction_t * xact) { + return value_t(xact->amount); +} + +transaction_t::state_t py_entry_state(entry_t * entry) { + transaction_t::state_t state; + if (entry->get_state(&state)) + return state; + else + return transaction_t::UNCLEARED; +} + +void export_journal() +{ + scope().attr("TRANSACTION_NORMAL") = TRANSACTION_NORMAL; + scope().attr("TRANSACTION_VIRTUAL") = TRANSACTION_VIRTUAL; + scope().attr("TRANSACTION_BALANCE") = TRANSACTION_BALANCE; + scope().attr("TRANSACTION_AUTO") = TRANSACTION_AUTO; + scope().attr("TRANSACTION_BULK_ALLOC") = TRANSACTION_BULK_ALLOC; + scope().attr("TRANSACTION_CALCULATED") = TRANSACTION_CALCULATED; + + enum_< transaction_t::state_t > ("State") + .value("Uncleared", transaction_t::UNCLEARED) + .value("Cleared", transaction_t::CLEARED) + .value("Pending", transaction_t::PENDING) + ; + + class_< transaction_t > ("Transaction") + .def(init<optional<account_t *> >()) + .def(init<account_t *, amount_t, optional<unsigned int, const std::string&> >()) + + .def(self == self) + .def(self != self) + + .add_property("entry", + make_getter(&transaction_t::entry, + return_value_policy<reference_existing_object>())) + .add_property("account", + make_getter(&transaction_t::account, + return_value_policy<reference_existing_object>())) + + .add_property("amount", &py_transaction_amount) + .def_readonly("amount_expr", &transaction_t::amount_expr) + .add_property("cost", + make_getter(&transaction_t::cost, + return_internal_reference<1>())) + .def_readonly("cost_expr", &transaction_t::cost_expr) + + .def_readwrite("state", &transaction_t::state) + .def_readwrite("flags", &transaction_t::flags) + .def_readwrite("note", &transaction_t::note) + + .def_readonly("beg_pos", &transaction_t::beg_pos) + .def_readonly("beg_line", &transaction_t::beg_line) + .def_readonly("end_pos", &transaction_t::end_pos) + .def_readonly("end_line", &transaction_t::end_line) + + .def("actual_date", &transaction_t::actual_date) + .def("effective_date", &transaction_t::effective_date) + .def("date", &transaction_t::date) + + .def("use_effective_date", &transaction_t::use_effective_date) + + .def("valid", &transaction_t::valid) + ; + + class_< account_t > + ("Account", + init<optional<account_t *, std::string, std::string> >() + [with_custodian_and_ward<1, 2>()]) + .def(self == self) + .def(self != self) + + .def(self_ns::str(self)) + + .def("__len__", accounts_len) + .def("__getitem__", accounts_getitem, return_internal_reference<1>()) + + .add_property("journal", + make_getter(&account_t::journal, + return_value_policy<reference_existing_object>())) + .add_property("parent", + make_getter(&account_t::parent, + return_value_policy<reference_existing_object>())) + .def_readwrite("name", &account_t::name) + .def_readwrite("note", &account_t::note) + .def_readonly("depth", &account_t::depth) + .add_property("data", py_account_get_data, py_account_set_data) + .def_readonly("ident", &account_t::ident) + + .def("fullname", &account_t::fullname) + + .def("add_account", &account_t::add_account) + .def("remove_account", &account_t::remove_account) + + .def("find_account", &account_t::find_account, + return_value_policy<reference_existing_object>()) + + .def("valid", &account_t::valid) + ; + + class_< journal_t > ("Journal") + .def(self == self) + .def(self != self) + + .def("__len__", entries_len) + .def("__getitem__", entries_getitem, return_internal_reference<1>()) + + .add_property("master", make_getter(&journal_t::master, + return_internal_reference<1>())) + .add_property("basket", make_getter(&journal_t::basket, + return_internal_reference<1>())) + + .def_readonly("sources", &journal_t::sources) + + .def_readwrite("price_db", &journal_t::price_db) + + .def("add_account", &journal_t::add_account) + .def("remove_account", &journal_t::remove_account) + + .def("find_account", py_find_account_1, return_internal_reference<1>()) + .def("find_account", py_find_account_2, return_internal_reference<1>()) + .def("find_account_re", &journal_t::find_account_re, + return_internal_reference<1>()) + + .def("add_entry", py_add_entry) + .def("remove_entry", &journal_t::remove_entry) + + .def("add_entry_finalizer", py_add_entry_finalizer) + .def("remove_entry_finalizer", py_remove_entry_finalizer) + .def("run_entry_finalizers", py_run_entry_finalizers) + + .def("valid", &journal_t::valid) + ; + + class_< entry_base_t, entry_base_wrap, boost::noncopyable > ("EntryBase") + .def("__len__", transactions_len) + .def("__getitem__", transactions_getitem, + return_internal_reference<1>()) + + .def_readonly("journal", &entry_base_t::journal) + + .def_readonly("src_idx", &entry_base_t::src_idx) + .def_readonly("beg_pos", &entry_base_t::beg_pos) + .def_readonly("beg_line", &entry_base_t::beg_line) + .def_readonly("end_pos", &entry_base_t::end_pos) + .def_readonly("end_line", &entry_base_t::end_line) + + .def("add_transaction", py_add_transaction) + .def("remove_transaction", &entry_base_t::remove_transaction) + + .def(self == self) + .def(self != self) + + .def("finalize", &entry_base_t::finalize) + .def("valid", &entry_base_t::valid) + ; + + class_< entry_t, bases<entry_base_t> > ("Entry") + .add_property("date", &entry_t::date) + .add_property("effective_date", &entry_t::effective_date) + .add_property("actual_date", &entry_t::actual_date) + + .def_readwrite("code", &entry_t::code) + .def_readwrite("payee", &entry_t::payee) + + .add_property("state", &py_entry_state) + + .def("valid", &entry_t::valid) + ; + +#define EXC_TRANSLATE(type) \ + register_exception_translator<type>(&exc_translate_ ## type); + + EXC_TRANSLATE(balance_error); + EXC_TRANSLATE(interval_expr_error); + EXC_TRANSLATE(format_error); + EXC_TRANSLATE(parse_error); +} + +#endif // USE_BOOST_PYTHON @@ -1,17 +1,7 @@ #ifndef _JOURNAL_H #define _JOURNAL_H -#include <map> -#include <list> -#include <string> -#include <iostream> - -#include "amount.h" -#include "datetime.h" -#include "value.h" -#include "valexpr.h" -#include "error.h" -#include "debug.h" +#include "xpath.h" #include "util.h" namespace ledger { @@ -37,7 +27,7 @@ class transaction_t datetime_t _date_eff; account_t * account; amount_t amount; - value_expr amount_expr; + std::string amount_expr; amount_t * cost; std::string cost_expr; state_t state; @@ -47,15 +37,16 @@ class transaction_t unsigned long beg_line; istream_pos_type end_pos; unsigned long end_line; - mutable void * data; - static bool use_effective_date; + mutable void * data; + + static bool use_effective_date; transaction_t(account_t * _account = NULL) : entry(NULL), account(_account), cost(NULL), state(UNCLEARED), flags(TRANSACTION_NORMAL), beg_pos(0), beg_line(0), end_pos(0), end_line(0), data(NULL) { - DEBUG_PRINT("ledger.memory.ctors", "ctor transaction_t"); + TRACE_CTOR("transaction_t(account_t *)"); } transaction_t(account_t * _account, const amount_t& _amount, @@ -65,14 +56,14 @@ class transaction_t state(UNCLEARED), flags(_flags), note(_note), beg_pos(0), beg_line(0), end_pos(0), end_line(0), data(NULL) { - DEBUG_PRINT("ledger.memory.ctors", "ctor transaction_t"); + TRACE_CTOR("transaction_t(account_t *, const amount_t&, unsigned int, const std::string&)"); } transaction_t(const transaction_t& xact) : entry(xact.entry), account(xact.account), amount(xact.amount), cost(xact.cost ? new amount_t(*xact.cost) : NULL), state(xact.state), flags(xact.flags), note(xact.note), beg_pos(0), beg_line(0), end_pos(0), end_line(0), data(NULL) { - DEBUG_PRINT("ledger.memory.ctors", "ctor transaction_t"); + TRACE_CTOR("transaction_t(copy)"); } ~transaction_t(); @@ -120,21 +111,20 @@ class entry_base_t transactions_list transactions; entry_base_t() : journal(NULL), - beg_pos(0), beg_line(0), end_pos(0), end_line(0) - { - DEBUG_PRINT("ledger.memory.ctors", "ctor entry_base_t"); + beg_pos(0), beg_line(0), end_pos(0), end_line(0) { + TRACE_CTOR("entry_base_t()"); } entry_base_t(const entry_base_t& e) : journal(NULL), beg_pos(0), beg_line(0), end_pos(0), end_line(0) { - DEBUG_PRINT("ledger.memory.ctors", "ctor entry_base_t"); + TRACE_CTOR("entry_base_t(copy)"); for (transactions_list::const_iterator i = e.transactions.begin(); i != e.transactions.end(); i++) transactions.push_back(new transaction_t(**i)); } virtual ~entry_base_t() { - DEBUG_PRINT("ledger.memory.dtors", "dtor entry_base_t"); + TRACE_DTOR("entry_base_t"); for (transactions_list::iterator i = transactions.begin(); i != transactions.end(); i++) @@ -166,13 +156,15 @@ class entry_t : public entry_base_t std::string code; std::string payee; - entry_t() { - DEBUG_PRINT("ledger.memory.ctors", "ctor entry_t"); + mutable void * data; + + entry_t() : data(NULL) { + TRACE_CTOR("entry_t()"); } entry_t(const entry_t& e); virtual ~entry_t() { - DEBUG_PRINT("ledger.memory.dtors", "dtor entry_t"); + TRACE_DTOR("entry_t"); } datetime_t actual_date() const { @@ -202,6 +194,9 @@ struct entry_finalizer_t { virtual bool operator()(entry_t& entry, bool post) = 0; }; +void print_entry(std::ostream& out, const entry_base_t& entry, + const std::string& prefix = ""); + class entry_context : public error_context { public: const entry_base_t& entry; @@ -222,21 +217,22 @@ class balance_error : public error { }; -template <typename T> -class item_predicate; - class auto_entry_t : public entry_base_t { public: - item_predicate<transaction_t> * predicate; - std::string predicate_string; + xml::xpath_t predicate; - auto_entry_t() : predicate(NULL) { - DEBUG_PRINT("ledger.memory.ctors", "ctor auto_entry_t"); + auto_entry_t() { + TRACE_CTOR("auto_entry_t()"); + } + auto_entry_t(const std::string& _predicate) + : predicate(_predicate) { + TRACE_CTOR("auto_entry_t(const std::string&)"); } - auto_entry_t(const std::string& _predicate); - virtual ~auto_entry_t(); + virtual ~auto_entry_t() { + TRACE_DTOR("auto_entry_t"); + } virtual void extend_entry(entry_base_t& entry, bool post); virtual bool valid() const { @@ -245,6 +241,7 @@ public: }; class journal_t; + struct auto_entry_finalizer_t : public entry_finalizer_t { journal_t * journal; auto_entry_finalizer_t(journal_t * _journal) : journal(_journal) {} @@ -259,19 +256,19 @@ class period_entry_t : public entry_base_t std::string period_string; period_entry_t() { - DEBUG_PRINT("ledger.memory.ctors", "ctor period_entry_t"); + TRACE_CTOR("period_entry_t()"); } period_entry_t(const std::string& _period) : period(_period), period_string(_period) { - DEBUG_PRINT("ledger.memory.ctors", "ctor period_entry_t"); + TRACE_CTOR("period_entry_t(const std::string&)"); } period_entry_t(const period_entry_t& e) : entry_base_t(e), period(e.period), period_string(e.period_string) { - DEBUG_PRINT("ledger.memory.ctors", "ctor period_entry_t"); + TRACE_CTOR("period_entry_t(copy)"); } virtual ~period_entry_t() { - DEBUG_PRINT("ledger.memory.dtors", "dtor period_entry_t"); + TRACE_DTOR("period_entry_t"); } virtual bool valid() const { @@ -295,8 +292,8 @@ class account_t unsigned short depth; accounts_map accounts; - mutable void * data; - mutable ident_t ident; + mutable void * data; + mutable ident_t ident; mutable std::string _fullname; account_t(account_t * _parent = NULL, @@ -304,7 +301,7 @@ class account_t const std::string& _note = "") : parent(_parent), name(_name), note(_note), depth(parent ? parent->depth + 1 : 0), data(NULL), ident(0) { - DEBUG_PRINT("ledger.memory.ctors", "ctor account_t " << this); + TRACE_CTOR("account_t(account_t *, const std::string&, const std::string&)"); } ~account_t(); @@ -380,9 +377,12 @@ typedef std::list<auto_entry_t *> auto_entries_list; typedef std::list<period_entry_t *> period_entries_list; typedef std::list<std::string> strings_list; +class session_t; + class journal_t { public: + session_t * session; account_t * master; account_t * basket; entries_list entries; @@ -391,17 +391,27 @@ class journal_t char * item_pool; char * item_pool_end; + // This is used for dynamically representing the journal data as an + // XML tree, to facilitate transformations without modifying any of + // the underlying structures (the transformers modify the XML tree + // -- perhaps even adding, changing or deleting nodes -- but they do + // not affect the basic data parsed from the journal file). + xml::document_t * document; + auto_entries_list auto_entries; period_entries_list period_entries; + mutable void * data; mutable accounts_map accounts_cache; std::list<entry_finalizer_t *> entry_finalize_hooks; - journal_t() : basket(NULL) { - DEBUG_PRINT("ledger.memory.ctors", "ctor journal_t"); + journal_t(session_t * _session) + : session(_session), basket(NULL), + item_pool(NULL), item_pool_end(NULL), + document(NULL), data(NULL) { + TRACE_CTOR("journal_t()"); master = new account_t(NULL, ""); master->journal = this; - item_pool = item_pool_end = NULL; } ~journal_t(); @@ -35,10 +35,10 @@ ;; To use this module: Load this file, open a ledger data file, and ;; type M-x ledger-mode. Once this is done, you can type: ;; -;; C-c C-a add a new entry, based on previous entries -;; C-c C-y set default year for entry mode -;; C-c C-m set default month for entry mode -;; C-c C-r reconcile uncleared entries related to an account +;; C-c C-a add a new entry, based on previous entries +;; C-c C-y set default year for entry mode +;; C-c C-m set default month for entry mode +;; C-c C-r reconcile uncleared entries related to an account ;; C-c C-o C-r run a ledger report ;; C-C C-o C-g goto the ledger report buffer ;; C-c C-o C-e edit the defined ledger reports @@ -90,18 +90,45 @@ :group 'ledger) (defcustom ledger-reports - '(("bal" "ledger bal") - ("reg" "ledger reg")) - "Definition of reports to run. - -Each element has the form (NAME CMDLINE)" + '(("bal" "ledger -f %(ledger-file) bal") + ("reg" "ledger -f %(ledger-file) reg") + ("payee" "ledger -f %(ledger-file) reg -- %(payee)") + ("account" "ledger -f %(ledger-file) reg %(account)")) + "Definition of reports to run. + +Each element has the form (NAME CMDLINE). The command line can +contain format specifiers that are replaced with context sensitive +information. Format specifiers have the format '%(<name>)' where +<name> is an identifier for the information to be replaced. The +`ledger-report-format-specifiers' alist variable contains a mapping +from format specifier identifier to a lisp function that implements +the substitution. See the documentation of the individual functions +in that variable for more information on the behavior of each +specifier." :type '(repeat (list (string :tag "Report Name") - (string :tag "Command Line"))) + (string :tag "Command Line"))) + :group 'ledger) + +(defcustom ledger-report-format-specifiers + '(("ledger-file" . ledger-report-ledger-file-format-specifier) + ("payee" . ledger-report-payee-format-specifier) + ("account" . ledger-report-account-format-specifier)) + "Alist mapping ledger report format specifiers to implementing functions + +The function is called with no parameters and expected to return the +text that should replace the format specifier." + :type 'alist + :group 'ledger) + +(defcustom ledger-default-acct-transaction-indent " " + "Default indentation for account transactions in an entry." + :type 'string :group 'ledger) (defvar bold 'bold) (defvar ledger-font-lock-keywords - '(("^[0-9./=]+\\s-+\\(?:([^)]+)\\s-+\\)?\\([^*].+\\)" 1 bold) + `((,(concat "^[0-9/.=-]+\\(\\s-+\\*\\)?\\(\\s-+(.*?)\\)?\\s-+" + "\\(.+?\\)\\(\t\\|\n\\| [ \t]\\)") 3 bold) ("^\\s-+.+?\\( \\|\t\\|\\s-+$\\)" . font-lock-keyword-face)) "Default expressions to highlight in Ledger mode.") @@ -349,9 +376,17 @@ dropped." (set (make-local-variable 'comment-start) ";") (set (make-local-variable 'comment-end) "") (set (make-local-variable 'indent-tabs-mode) nil) + (if (boundp 'font-lock-defaults) (set (make-local-variable 'font-lock-defaults) '(ledger-font-lock-keywords nil t))) + + (set (make-local-variable 'pcomplete-parse-arguments-function) + 'ledger-parse-arguments) + (set (make-local-variable 'pcomplete-command-completion-function) + 'ledger-complete-at-point) + (set (make-local-variable 'pcomplete-termination-string) "") + (let ((map (current-local-map))) (define-key map [(control ?c) (control ?a)] 'ledger-add-entry) (define-key map [(control ?c) (control ?d)] 'ledger-delete-current-entry) @@ -359,16 +394,21 @@ dropped." (define-key map [(control ?c) (control ?m)] 'ledger-set-month) (define-key map [(control ?c) (control ?c)] 'ledger-toggle-current) (define-key map [(control ?c) (control ?r)] 'ledger-reconcile) + (define-key map [(control ?c) (control ?s)] 'ledger-sort) + (define-key map [tab] 'pcomplete) + (define-key map [(control ?i)] 'pcomplete) + (define-key map [(control ?c) tab] 'ledger-fully-complete-entry) + (define-key map [(control ?c) (control ?i)] 'ledger-fully-complete-entry) (define-key map [(control ?c) (control ?o) (control ?r)] 'ledger-report) - (define-key map [(control ?c) (control ?o) (control ?g)] + (define-key map [(control ?c) (control ?o) (control ?g)] 'ledger-report-goto) - (define-key map [(control ?c) (control ?o) (control ?a)] + (define-key map [(control ?c) (control ?o) (control ?a)] 'ledger-report-redo) - (define-key map [(control ?c) (control ?o) (control ?s)] + (define-key map [(control ?c) (control ?o) (control ?s)] 'ledger-report-save) - (define-key map [(control ?c) (control ?o) (control ?e)] + (define-key map [(control ?c) (control ?o) (control ?e)] 'ledger-report-edit) - (define-key map [(control ?c) (control ?o) (control ?k)] + (define-key map [(control ?c) (control ?o) (control ?k)] 'ledger-report-kill))) ;; Reconcile mode @@ -585,6 +625,152 @@ dropped." (define-key map [?q] 'ledger-reconcile-quit) (use-local-map map))) +;; Context sensitivity + +(defconst ledger-line-config + '((entry + (("^\\(\\([0-9][0-9][0-9][0-9]/\\)?[01]?[0-9]/[0123]?[0-9]\\)[ \t]+\\(\\([!*]\\)[ \t]\\)?[ \t]*\\((\\(.*\\))\\)?[ \t]*\\(.*?\\)[ \t]*;\\(.*\\)[ \t]*$" + (date nil status nil nil code payee comment)) + ("^\\(\\([0-9][0-9][0-9][0-9]/\\)?[01]?[0-9]/[0123]?[0-9]\\)[ \t]+\\(\\([!*]\\)[ \t]\\)?[ \t]*\\((\\(.*\\))\\)?[ \t]*\\(.*\\)[ \t]*$" + (date nil status nil nil code payee)))) + (acct-transaction + (("\\(^[ \t]+\\)\\(.*?\\)[ \t]+\\([$]\\)\\(-?[0-9]*\\(\\.[0-9]*\\)?\\)[ \t]*;[ \t]*\\(.*?\\)[ \t]*$" + (indent account commodity amount nil comment)) + ("\\(^[ \t]+\\)\\(.*?\\)[ \t]+\\([$]\\)\\(-?[0-9]*\\(\\.[0-9]*\\)?\\)[ \t]*$" + (indent account commodity amount nil)) + ("\\(^[ \t]+\\)\\(.*?\\)[ \t]+\\(-?[0-9]+\\(\\.[0-9]*\\)?\\)[ \t]+\\(.*?\\)[ \t]*;[ \t]*\\(.*?\\)[ \t]*$" + (indent account amount nil commodity comment)) + ("\\(^[ \t]+\\)\\(.*?\\)[ \t]+\\(-?[0-9]+\\(\\.[0-9]*\\)?\\)[ \t]+\\(.*?\\)[ \t]*$" + (indent account amount nil commodity)) + ("\\(^[ \t]+\\)\\(.*?\\)[ \t]+\\(-?\\(\\.[0-9]*\\)\\)[ \t]+\\(.*?\\)[ \t]*;[ \t]*\\(.*?\\)[ \t]*$" + (indent account amount nil commodity comment)) + ("\\(^[ \t]+\\)\\(.*?\\)[ \t]+\\(-?\\(\\.[0-9]*\\)\\)[ \t]+\\(.*?\\)[ \t]*$" + (indent account amount nil commodity)) + ("\\(^[ \t]+\\)\\(.*?\\)[ \t]*;[ \t]*\\(.*?\\)[ \t]*$" + (indent account comment)) + ("\\(^[ \t]+\\)\\(.*?\\)[ \t]*$" + (indent account)))))) + +(defun ledger-extract-context-info (line-type pos) + "Get context info for current line. + +Assumes point is at beginning of line, and the pos argument specifies +where the \"users\" point was." + (let ((linfo (assoc line-type ledger-line-config)) + found field fields) + (dolist (re-info (nth 1 linfo)) + (let ((re (nth 0 re-info)) + (names (nth 1 re-info))) + (unless found + (when (looking-at re) + (setq found t) + (dotimes (i (length names)) + (when (nth i names) + (setq fields (append fields + (list + (list (nth i names) + (match-string-no-properties (1+ i)) + (match-beginning (1+ i)))))))) + (dolist (f fields) + (and (nth 1 f) + (>= pos (nth 2 f)) + (setq field (nth 0 f)))))))) + (list line-type field fields))) + +(defun ledger-context-at-point () + "Return a list describing the context around point. + +The contents of the list are the line type, the name of the field +point containing point, and for selected line types, the content of +the fields in the line in a association list." + (let ((pos (point))) + (save-excursion + (beginning-of-line) + (let ((first-char (char-after))) + (cond ((equal (point) (line-end-position)) + '(empty-line nil nil)) + ((memq first-char '(?\ ?\t)) + (ledger-extract-context-info 'acct-transaction pos)) + ((memq first-char '(?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9)) + (ledger-extract-context-info 'entry pos)) + ((equal first-char ?\=) + '(automated-entry nil nil)) + ((equal first-char ?\~) + '(period-entry nil nil)) + ((equal first-char ?\!) + '(command-directive)) + ((equal first-char ?\;) + '(comment nil nil)) + ((equal first-char ?Y) + '(default-year nil nil)) + ((equal first-char ?P) + '(commodity-price nil nil)) + ((equal first-char ?N) + '(price-ignored-commodity nil nil)) + ((equal first-char ?D) + '(default-commodity nil nil)) + ((equal first-char ?C) + '(commodity-conversion nil nil)) + ((equal first-char ?i) + '(timeclock-i nil nil)) + ((equal first-char ?o) + '(timeclock-o nil nil)) + ((equal first-char ?b) + '(timeclock-b nil nil)) + ((equal first-char ?h) + '(timeclock-h nil nil)) + (t + '(unknown nil nil))))))) + +(defun ledger-context-other-line (offset) + "Return a list describing context of line offset for existing position. + +Offset can be positive or negative. If run out of buffer before reaching +specified line, returns nil." + (save-excursion + (let ((left (forward-line offset))) + (if (not (equal left 0)) + nil + (ledger-context-at-point))))) + +(defun ledger-context-line-type (context-info) + (nth 0 context-info)) + +(defun ledger-context-current-field (context-info) + (nth 1 context-info)) + +(defun ledger-context-field-info (context-info field-name) + (assoc field-name (nth 2 context-info))) + +(defun ledger-context-field-present-p (context-info field-name) + (not (null (ledger-context-field-info context-info field-name)))) + +(defun ledger-context-field-value (context-info field-name) + (nth 1 (ledger-context-field-info context-info field-name))) + +(defun ledger-context-field-position (context-info field-name) + (nth 2 (ledger-context-field-info context-info field-name))) + +(defun ledger-context-field-end-position (context-info field-name) + (+ (ledger-context-field-position context-info field-name) + (length (ledger-context-field-value context-info field-name)))) + +(defun ledger-context-goto-field-start (context-info field-name) + (goto-char (ledger-context-field-position context-info field-name))) + +(defun ledger-context-goto-field-end (context-info field-name) + (goto-char (ledger-context-field-end-position context-info field-name))) + +(defun ledger-entry-payee () + "Returns the payee of the entry containing point or nil." + (let ((i 0)) + (while (eq (ledger-context-line-type (ledger-context-other-line i)) 'acct-transaction) + (setq i (- i 1))) + (let ((context-info (ledger-context-other-line i))) + (if (eq (ledger-context-line-type context-info) 'entry) + (ledger-context-field-value context-info 'payee) + nil)))) + ;; Ledger report mode (defvar ledger-report-buffer-name "*Ledger Report*") @@ -606,13 +792,13 @@ dropped." (define-key map [?k] 'ledger-report-kill) (define-key map [?e] 'ledger-report-edit) (define-key map [?q] 'ledger-report-quit) - (define-key map [(control ?c) (control ?l) (control ?r)] + (define-key map [(control ?c) (control ?l) (control ?r)] 'ledger-report-redo) - (define-key map [(control ?c) (control ?l) (control ?S)] + (define-key map [(control ?c) (control ?l) (control ?S)] 'ledger-report-save) - (define-key map [(control ?c) (control ?l) (control ?k)] + (define-key map [(control ?c) (control ?l) (control ?k)] 'ledger-report-kill) - (define-key map [(control ?c) (control ?l) (control ?e)] + (define-key map [(control ?c) (control ?l) (control ?e)] 'ledger-report-edit) (use-local-map map))) @@ -620,9 +806,9 @@ dropped." "Read the name of a ledger report to use, with completion. The empty string and unknown names are allowed." - (completing-read "Report name: " - ledger-reports nil nil nil - 'ledger-report-name-prompt-history nil)) + (completing-read "Report name: " + ledger-reports nil nil nil + 'ledger-report-name-prompt-history nil)) (defun ledger-report (report-name edit) "Run a user-specified report from `ledger-reports'. @@ -638,13 +824,17 @@ editing before the command is run. The output buffer will be in `ledger-report-mode', which defines commands for saving a new named report based on the command line used to generate the buffer, navigating the buffer, etc." - (interactive - (let ((rname (ledger-report-read-name)) - (edit (not (null current-prefix-arg)))) - (list rname edit))) + (interactive + (progn + (when (and (buffer-modified-p) + (y-or-n-p "Buffer modified, save it? ")) + (save-buffer)) + (let ((rname (ledger-report-read-name)) + (edit (not (null current-prefix-arg)))) + (list rname edit)))) (let ((buf (current-buffer)) (rbuf (get-buffer ledger-report-buffer-name)) - (wcfg (current-window-configuration))) + (wcfg (current-window-configuration))) (if rbuf (kill-buffer rbuf)) (with-current-buffer @@ -671,15 +861,73 @@ If name exists, returns the object naming the report, otherwise returns nil." "Add a new report to `ledger-reports'." (setq ledger-reports (cons (list name cmd) ledger-reports))) -(defun ledger-reports-custom-save () +(defun ledger-reports-custom-save () "Save the `ledger-reports' variable using the customize framework." (customize-save-variable 'ledger-reports ledger-reports)) (defun ledger-report-read-command (report-cmd) "Read the command line to create a report." (read-from-minibuffer "Report command line: " - (if (null report-cmd) "ledger " report-cmd) - nil nil 'ledger-report-cmd-prompt-history)) + (if (null report-cmd) "ledger " report-cmd) + nil nil 'ledger-report-cmd-prompt-history)) + +(defun ledger-report-ledger-file-format-specifier () + "Substitute the full path to master or current ledger file + +The master file name is determined by the ledger-master-file buffer-local +variable which can be set using file variables. If it is set, it is used, +otherwise the current buffer file is used." + (ledger-master-file)) + +(defun ledger-read-string-with-default (prompt default) + (let ((default-prompt (concat prompt + (if default + (concat " (" default "): ") + ": ")))) + (read-string default-prompt nil nil default))) + +(defun ledger-report-payee-format-specifier () + "Substitute a payee name + +The user is prompted to enter a payee and that is substitued. If +point is in an entry, the payee for that entry is used as the +default." + ;; It is intended copmletion should be available on existing + ;; payees, but the list of possible completions needs to be + ;; developed to allow this. + (ledger-read-string-with-default "Payee" (regexp-quote (ledger-entry-payee)))) + +(defun ledger-report-account-format-specifier () + "Substitute an account name + +The user is prompted to enter an account name, which can be any +regular expression identifying an account. If point is on an account +transaction line for an entry, the full account name on that line is +the default." + ;; It is intended completion should be available on existing account + ;; names, but it remains to be implemented. + (let* ((context (ledger-context-at-point)) + (default + (if (eq (ledger-context-line-type context) 'acct-transaction) + (regexp-quote (ledger-context-field-value context 'account)) + nil))) + (ledger-read-string-with-default "Account" default))) + +(defun ledger-report-expand-format-specifiers (report-cmd) + (let ((expanded-cmd report-cmd)) + (while (string-match "%(\\([^)]*\\))" expanded-cmd) + (let* ((specifier (match-string 1 expanded-cmd)) + (f (cdr (assoc specifier ledger-report-format-specifiers)))) + (if f + (setq expanded-cmd (replace-match + (save-match-data + (with-current-buffer ledger-buf + (shell-quote-argument (funcall f)))) + t t expanded-cmd)) + (progn + (set-window-configuration ledger-original-window-cfg) + (error "Invalid ledger report format specifier '%s'" specifier))))) + expanded-cmd)) (defun ledger-report-cmd (report-name edit) "Get the command line to run the report." @@ -687,17 +935,18 @@ If name exists, returns the object naming the report, otherwise returns nil." ;; logic for substitution goes here (when (or (null report-cmd) edit) (setq report-cmd (ledger-report-read-command report-cmd))) + (setq report-cmd (ledger-report-expand-format-specifiers report-cmd)) (set (make-local-variable 'ledger-report-cmd) report-cmd) (or (string-empty-p report-name) - (ledger-report-name-exists report-name) - (ledger-reports-add report-name report-cmd) - (ledger-reports-custom-save)) + (ledger-report-name-exists report-name) + (ledger-reports-add report-name report-cmd) + (ledger-reports-custom-save)) report-cmd)) (defun ledger-do-report (cmd) "Run a report command line." (goto-char (point-min)) - (insert (format "Report: %s\n" cmd) + (insert (format "Report: %s\n" cmd) (make-string (- (window-width) 1) ?=) "\n") (shell-command cmd t nil)) @@ -707,7 +956,7 @@ If name exists, returns the object naming the report, otherwise returns nil." (interactive) (let ((rbuf (get-buffer ledger-report-buffer-name))) (if (not rbuf) - (error "There is no ledger report buffer")) + (error "There is no ledger report buffer")) (pop-to-buffer rbuf) (shrink-window-if-larger-than-buffer))) @@ -740,7 +989,7 @@ If name exists, returns the object naming the report, otherwise returns nil." (let ((name "")) (while (string-empty-p name) (setq name (read-from-minibuffer "Report name: " nil nil nil - 'ledger-report-name-prompt-history))) + 'ledger-report-name-prompt-history))) name)) (defun ledger-report-save () @@ -752,20 +1001,153 @@ If name exists, returns the object naming the report, otherwise returns nil." (setq ledger-report-name (ledger-report-read-new-name))) (while (setq existing-name (ledger-report-name-exists ledger-report-name)) - (cond ((y-or-n-p (format "Overwrite existing report named '%s' " - ledger-report-name)) - (when (string-equal - ledger-report-cmd - (car (cdr (assq existing-name ledger-reports)))) - (error "Current command is identical to existing saved one")) - (setq ledger-reports - (assq-delete-all existing-name ledger-reports))) - (t - (setq ledger-report-name (ledger-report-read-new-name))))) + (cond ((y-or-n-p (format "Overwrite existing report named '%s' " + ledger-report-name)) + (when (string-equal + ledger-report-cmd + (car (cdr (assq existing-name ledger-reports)))) + (error "Current command is identical to existing saved one")) + (setq ledger-reports + (assq-delete-all existing-name ledger-reports))) + (t + (setq ledger-report-name (ledger-report-read-new-name))))) (ledger-reports-add ledger-report-name ledger-report-cmd) (ledger-reports-custom-save))) +;; In-place completion support + +(defun ledger-thing-at-point () + (let ((here (point))) + (goto-char (line-beginning-position)) + (cond ((looking-at "^[0-9/.=-]+\\(\\s-+\\*\\)?\\(\\s-+(.+?)\\)?\\s-+") + (goto-char (match-end 0)) + 'entry) + ((looking-at "^\\s-+\\([*!]\\s-+\\)?[[(]?\\(.\\)") + (goto-char (match-beginning 2)) + 'transaction) + (t + (ignore (goto-char here)))))) + +(defun ledger-parse-arguments () + "Parse whitespace separated arguments in the current region." + (let* ((info (save-excursion + (cons (ledger-thing-at-point) (point)))) + (begin (cdr info)) + (end (point)) + begins args) + (save-excursion + (goto-char begin) + (when (< (point) end) + (skip-chars-forward " \t\n") + (setq begins (cons (point) begins)) + (setq args (cons (buffer-substring-no-properties + (car begins) end) + args))) + (cons (reverse args) (reverse begins))))) + +(defun ledger-entries () + (let ((origin (point)) + entries-list) + (save-excursion + (goto-char (point-min)) + (while (re-search-forward + (concat "^[0-9/.=-]+\\(\\s-+\\*\\)?\\(\\s-+(.*?)\\)?\\s-+" + "\\(.+?\\)\\(\t\\|\n\\| [ \t]\\)") nil t) + (unless (and (>= origin (match-beginning 0)) + (< origin (match-end 0))) + (setq entries-list (cons (match-string-no-properties 3) + entries-list))))) + (pcomplete-uniqify-list (nreverse entries-list)))) + +(defvar ledger-account-tree nil) + +(defun ledger-find-accounts () + (let ((origin (point)) account-path elements) + (save-excursion + (setq ledger-account-tree (list t)) + (goto-char (point-min)) + (while (re-search-forward + "^[ \t]+\\([*!]\\s-+\\)?[[(]?\\(.+?\\)\\(\t\\|\n\\| [ \t]\\)" nil t) + (unless (and (>= origin (match-beginning 0)) + (< origin (match-end 0))) + (setq account-path (match-string-no-properties 2)) + (setq elements (split-string account-path ":")) + (let ((root ledger-account-tree)) + (while elements + (let ((entry (assoc (car elements) root))) + (if entry + (setq root (cdr entry)) + (setq entry (cons (car elements) (list t))) + (nconc root (list entry)) + (setq root (cdr entry)))) + (setq elements (cdr elements))))))))) + +(defun ledger-accounts () + (ledger-find-accounts) + (let* ((current (caar (ledger-parse-arguments))) + (elements (and current (split-string current ":"))) + (root ledger-account-tree) + (prefix nil)) + (while (cdr elements) + (let ((entry (assoc (car elements) root))) + (if entry + (setq prefix (concat prefix (and prefix ":") + (car elements)) + root (cdr entry)) + (setq root nil elements nil))) + (setq elements (cdr elements))) + (and root + (sort + (mapcar (function + (lambda (x) + (let ((term (if prefix + (concat prefix ":" (car x)) + (car x)))) + (if (> (length (cdr x)) 1) + (concat term ":") + term)))) + (cdr root)) + 'string-lessp)))) + +(defun ledger-complete-at-point () + "Do appropriate completion for the thing at point" + (interactive) + (while (pcomplete-here + (if (eq (save-excursion + (ledger-thing-at-point)) 'entry) + (ledger-entries) + (ledger-accounts))))) + +(defun ledger-fully-complete-entry () + "Do appropriate completion for the thing at point" + (interactive) + (let ((name (caar (ledger-parse-arguments))) + xacts) + (save-excursion + (when (eq 'entry (ledger-thing-at-point)) + (when (re-search-backward + (concat "^[0-9/.=-]+\\(\\s-+\\*\\)?\\(\\s-+(.*?)\\)?\\s-+" + (regexp-quote name) "\\(\t\\|\n\\| [ \t]\\)") nil t) + (forward-line) + (while (looking-at "^\\s-+") + (setq xacts (cons (buffer-substring-no-properties + (line-beginning-position) + (line-end-position)) + xacts)) + (forward-line)) + (setq xacts (nreverse xacts))))) + (when xacts + (save-excursion + (insert ?\n) + (while xacts + (insert (car xacts) ?\n) + (setq xacts (cdr xacts)))) + (forward-line) + (goto-char (line-end-position)) + (if (re-search-backward "\\(\t\\| [ \t]\\)" nil t) + (goto-char (match-end 0)))))) + ;; A sample function for $ users (defun ledger-align-dollars (&optional column) @@ -792,6 +1174,26 @@ If name exists, returns the object naming the report, otherwise returns nil." (insert " "))) (forward-line)))) +;; A sample entry sorting function, which works if entry dates are of +;; the form YYYY/mm/dd. + +(defun ledger-sort () + (interactive) + (save-excursion + (goto-char (point-min)) + (sort-subr + nil + (function + (lambda () + (if (re-search-forward + (concat "^[0-9/.=-]+\\(\\s-+\\*\\)?\\(\\s-+(.*?)\\)?\\s-+" + "\\(.+?\\)\\(\t\\|\n\\| [ \t]\\)") nil t) + (goto-char (match-beginning 0)) + (goto-char (point-max))))) + (function + (lambda () + (forward-paragraph)))))) + ;; General helper functions (defvar ledger-delete-after nil) @@ -834,6 +1236,17 @@ If name exists, returns the object naming the report, otherwise returns nil." (setq ledger-month (read-string "Month: " (ledger-current-month))) (setq ledger-month (format "%02d" newmonth)))) +(defun ledger-master-file () + "Return the master file for a ledger file. + +The master file is either the file for the current ledger buffer or the +file specified by the buffer-local variable ledger-master-file. Typically +this variable would be set in a file local variable comment block at the +end of a ledger file which is included in some other file." + (if (boundp 'ledger-master-file) + (expand-file-name ledger-master-file) + (buffer-file-name))) + (provide 'ledger) ;;; ledger.el ends here @@ -13,39 +13,34 @@ #include <amount.h> #include <balance.h> #include <value.h> - -#include <journal.h> - #include <datetime.h> +#include <xml.h> +#include <xpath.h> #include <format.h> -#include <emacs.h> -#include <csv.h> #include <quotes.h> -#include <valexpr.h> -#include <walk.h> -#include <derive.h> -#include <reconcile.h> + #include <error.h> -#include <option.h> +#include <util.h> +#include <session.h> +#include <journal.h> #include <parser.h> #include <textual.h> #include <binary.h> -#include <xml.h> +#include <xmlparse.h> #include <gnucash.h> #include <qif.h> #include <ofx.h> -namespace ledger { - extern parser_t * binary_parser_ptr; - extern parser_t * xml_parser_ptr; - extern parser_t * gnucash_parser_ptr; - extern parser_t * ofx_parser_ptr; - extern parser_t * qif_parser_ptr; - extern parser_t * textual_parser_ptr; -} - -#include <config.h> #include <report.h> +#include <transform.h> + +#include <dump.h> +#if 0 +#include <emacs.h> +#include <csv.h> +#include <derive.h> +#include <reconcile.h> +#endif #endif // _LEDGER_H @@ -1,3 +1,6 @@ +#ifdef USE_PCH +#include "pch.h" +#else #include <iostream> #include <fstream> #include <sstream> @@ -9,6 +12,7 @@ #include <cstdlib> #include <cstring> +#include "option.h" #include "acconf.h" #ifdef HAVE_UNIX_PIPES @@ -18,202 +22,184 @@ #include "fdstream.hpp" #endif +#ifdef USE_BOOST_PYTHON +#include "pyledger.h" +#else #include "ledger.h" +#endif +#include "debug.h" +#endif using namespace ledger; -int parse_and_report(config_t& config, report_t& report, - int argc, char * argv[], char * envp[]) +static inline +const std::string& either_or(const std::string& first, + const std::string& second) { - // Configure the terminus for value expressions + if (first.empty()) + return second; + else + return first; +} - ledger::terminus = datetime_t::now; +#if 0 +class print_addr : public repitem_t::select_callback_t { + virtual void operator()(repitem_t * item) { + std::cout << item << std::endl; + } +}; +#endif - // Parse command-line arguments, and those set in the environment +static int read_and_report(report_t * report, int argc, char * argv[], + char * envp[]) +{ + session_t& session(*report->session); + + // Handle the command-line arguments std::list<std::string> args; - process_arguments(ledger::config_options, argc - 1, argv + 1, false, args); + process_arguments(argc - 1, argv + 1, false, report, args); if (args.empty()) { - option_help(std::cerr); +#if 0 + help(std::cerr); +#endif return 1; } strings_list::iterator arg = args.begin(); - if (config.cache_file == "<none>") - config.use_cache = false; + if (session.cache_file == "<none>") + session.use_cache = false; else - config.use_cache = config.data_file.empty() && config.price_db.empty(); - DEBUG_PRINT("ledger.config.cache", "1. use_cache = " << config.use_cache); + session.use_cache = session.data_file.empty() && session.price_db.empty(); - TRACE(main, "Processing options and environment variables"); + DEBUG_PRINT("ledger.session.cache", "1. use_cache = " << session.use_cache); - process_environment(ledger::config_options, - const_cast<const char **>(envp), "LEDGER_"); + // Process the environment settings -#if 1 - // These are here for backwards compatability, but are deprecated. - - if (const char * p = std::getenv("LEDGER")) - process_option(ledger::config_options, "file", p); - if (const char * p = std::getenv("LEDGER_INIT")) - process_option(ledger::config_options, "init-file", p); - if (const char * p = std::getenv("PRICE_HIST")) - process_option(ledger::config_options, "price-db", p); - if (const char * p = std::getenv("PRICE_EXP")) - process_option(ledger::config_options, "price-exp", p); -#endif + TRACE(main, "Processing options and environment settings"); - const char * p = std::getenv("HOME"); - std::string home = p ? p : ""; + process_environment(const_cast<const char **>(envp), "LEDGER_", report); - if (config.init_file.empty()) - config.init_file = home + "/.ledgerrc"; - if (config.price_db.empty()) - config.price_db = home + "/.pricedb"; + const char * p = std::getenv("HOME"); + std::string home = p ? p : ""; - if (config.cache_file.empty()) - config.cache_file = home + "/.ledger-cache"; + if (session.init_file.empty()) + session.init_file = home + "/.ledgerrc"; + if (session.price_db.empty()) + session.price_db = home + "/.pricedb"; - if (config.data_file == config.cache_file) - config.use_cache = false; - DEBUG_PRINT("ledger.config.cache", "2. use_cache = " << config.use_cache); + if (session.cache_file.empty()) + session.cache_file = home + "/.ledger-cache"; - TRACE(main, std::string("Initialization file is ") + config.init_file); - TRACE(main, std::string("Price database is ") + config.price_db); - TRACE(main, std::string("Binary cache is ") + config.cache_file); - TRACE(main, std::string("Main journal is ") + config.data_file); + if (session.data_file == session.cache_file) + session.use_cache = false; + + DEBUG_PRINT("ledger.session.cache", "2. use_cache = " << session.use_cache); + + TRACE(main, std::string("Initialization file is ") + session.init_file); + TRACE(main, std::string("Price database is ") + session.price_db); + TRACE(main, std::string("Binary cache is ") + session.cache_file); + TRACE(main, std::string("Main journal is ") + session.data_file); TRACE(main, std::string("Based on option settings, binary cache ") + - (config.use_cache ? "WILL " : "will NOT ") + "be used"); - - // Read the command word, canonicalize it to its one letter form, - // then configure the system based on the kind of report to be - // generated - - std::string command = *arg++; - - if (command == "balance" || command == "bal" || command == "b") - command = "b"; - else if (command == "register" || command == "reg" || command == "r") - command = "r"; - else if (command == "print" || command == "p") - command = "p"; - else if (command == "output") - command = "w"; - else if (command == "dump") - command = "W"; - else if (command == "emacs" || command == "lisp") - command = "x"; - else if (command == "xml") - command = "X"; - else if (command == "entry") - command = "e"; - else if (command == "equity") - command = "E"; - else if (command == "prices") - command = "P"; - else if (command == "pricesdb") - command = "D"; - else if (command == "csv") - command = "c"; - else if (command == "parse") { - value_expr expr(ledger::parse_value_expr(*arg)); - - if (config.verbose_mode) { + (session.use_cache ? "WILL " : "will NOT ") + "be used"); + + // Read the command word and create a command object based on it + + std::string verb = *arg++; + + xml::xpath_t::functor_t * command = NULL; + + if (false) { + ; + } +#if 0 + if (verb == "register" || verb == "reg" || verb == "r") { + command = new format_command + ("register", either_or(report->format_string, + report->session->register_format)); + } + else if (verb == "balance" || verb == "bal" || verb == "b") { + if (! report->raw_mode) { + report->transforms.push_back(new accounts_transform); + report->transforms.push_back(new clean_transform); + report->transforms.push_back(new compact_transform); + } + command = new format_command + ("balance", either_or(report->format_string, + report->session->balance_format)); + } + else if (verb == "print" || verb == "p") { + if (! report->raw_mode) + report->transforms.push_back(new optimize_transform); + command = new format_command + ("print", either_or(report->format_string, + report->session->print_format)); + } + else if (verb == "equity") { + if (! report->raw_mode) + report->transforms.push_back(new accounts_transform); + command = new format_command + ("equity", either_or(report->format_string, + report->session->equity_format)); + } + else if (verb == "entry") + command = new entry_command; + else if (verb == "dump") + command = new dump_command; + else if (verb == "output") + command = new output_command; + else if (verb == "prices") + command = new prices_command; + else if (verb == "pricesdb") + command = new pricesdb_command; + else if (verb == "csv") + command = new csv_command; + else if (verb == "emacs" || verb == "lisp") + command = new emacs_command; +#endif + else if (verb == "xml") + command = new xml_command; + else if (verb == "expr") + ; + else if (verb == "xpath") + ; + else if (verb == "parse") { + xml::xpath_t expr(*arg); + + if (session.verbose_mode) { std::cout << "Value expression tree:" << std::endl; - ledger::dump_value_expr(std::cout, expr.get()); + expr.dump(std::cout); std::cout << std::endl; std::cout << "Value expression parsed was:" << std::endl; - ledger::write_value_expr(std::cout, expr.get()); + expr.write(std::cout); std::cout << std::endl << std::endl; - std::cout << "Result of computation: "; + std::cout << "Result of calculation: "; } - value_t result = guarded_compute(expr.get()); - std::cout << result.strip_annotations() << std::endl; + std::cout << expr.calc((xml::document_t *)NULL, report). + strip_annotations() << std::endl; return 0; } - else if (command == "expr") { - // this gets done below... - } else { - throw new error(std::string("Unrecognized command '") + command + "'"); + char buf[128]; + std::strcpy(buf, "command_"); + std::strcat(buf, verb.c_str()); + if (xml::xpath_t::op_t * def = report->lookup(buf)) + command = def->functor_obj(); + + if (! command) + throw new error(std::string("Unrecognized command '") + verb + "'"); } - // Parse initialization files, ledger data, price database, etc. - - std::auto_ptr<journal_t> journal(new journal_t); + // Parse the initialization file, which can only be textual; then + // parse the journal data. - { TRACE_PUSH(parser, "Parsing journal file"); - - if (parse_ledger_data(config, journal.get()) == 0) - throw new error("Please specify ledger file using -f" - " or LEDGER_FILE environment variable."); - - TRACE_POP(parser, "Finished parsing"); } - - // process the command word and its following arguments - - std::string first_arg; - if (command == "w") { - if (arg != args.end()) - first_arg = *arg++; - } - else if (command == "W") { - if (report.output_file.empty()) - throw new error("The 'dump' command requires use of the --output option"); - } + session.read_init(); - TRACE(options, std::string("Post-processing options ") + - "for command \"" + command + "\""); - - report.process_options(command, arg, args.end()); - - // If downloading is to be supported, configure the updater - - if (! commodity_base_t::updater && config.download_quotes) - commodity_base_t::updater = - new quotes_by_script(config.price_db, config.pricing_leeway, - config.cache_dirty); - - std::auto_ptr<entry_t> new_entry; - if (command == "e") { - if (arg == args.end()) { - std::cout << "\ -The entry command requires at least one argument, so Ledger can intelligently\n\ -create a new entry for you. The possible arguments are:\n\ - DATE PAYEE [ACCOUNT] [AMOUNT] [DRAW ACCOUNT]\n\n\ -Some things to note:\n\ - - The ACCOUNT is optional; if no account is given, the last account affected\n\ - by PAYEE is used. If no payee can be found, the generic account 'Expenses'\n\ - is used.\n\ - - The AMOUNT is optional; if not specified, the same amount is used as the\n\ - last time PAYEE was seen, or 0 if not applicable.\n\ - - The AMOUNT does not require a commodity; if none is given, the commodity\n\ - currently contained within ACCOUNT is used, or no commodity at all if\n\ - either: the ACCOUNT was not found, or it contains more than one commodity.\n\ - - Lastly, the DRAW ACCOUNT is optional; if not present, the last account\n\ - drawn from by PAYEE is used, or the 'basket' account (specified with\n\ - 'A ACCOUNT' in your Ledger file) if that does not apply, or the generic\n\ - account 'Equity' is used.\n\n\ -Here are a few examples, all of which may be equivalent depending on your\n\ -Ledger data:\n\ - ledger entry 3/25 chevron\n\ - ledger entry 3/25 chevron 20\n\ - ledger entry 3/25 chevron \\$20\n\ - ledger entry 3/25 chevron gas 20\n\ - ledger entry 3/25 chevron gas \\$20 checking\n\n\ -A final note: Ledger never modifies your data! You are responsible for\n\ -appending the output of this command to your Ledger file if you so choose." - << std::endl; - return 1; - } - new_entry.reset(derive_new_entry(*journal, arg, args.end())); - if (! new_entry.get()) - return 1; - } + journal_t * journal = session.read_data(report->account); // Configure the output stream @@ -222,11 +208,11 @@ appending the output of this command to your Ledger file if you so choose." #endif std::ostream * out = &std::cout; - if (! report.output_file.empty()) { - out = new std::ofstream(report.output_file.c_str()); + if (! report->output_file.empty()) { + out = new std::ofstream(report->output_file.c_str()); } #ifdef HAVE_UNIX_PIPES - else if (! config.pager.empty()) { + else if (! report->pager.empty()) { status = pipe(pfd); if (status == -1) throw new error("Failed to create pipe"); @@ -252,13 +238,13 @@ appending the output of this command to your Ledger file if you so choose." // Find command name: its the substring starting right of the // rightmost '/' character in the pager pathname. See manpage // for strrchr. - arg0 = std::strrchr(config.pager.c_str(), '/'); + arg0 = std::strrchr(report->pager.c_str(), '/'); if (arg0) arg0++; else - arg0 = config.pager.c_str(); // No slashes in pager. + arg0 = report->pager.c_str(); // No slashes in pager. - execlp(config.pager.c_str(), arg0, (char *)0); + execlp(report->pager.c_str(), arg0, (char *)0); perror("execl"); exit(1); } @@ -269,162 +255,147 @@ appending the output of this command to your Ledger file if you so choose." } #endif - // Are we handling the parse or expr commands? Do so now. + // Are we handling the expr commands? Do so now. - if (command == "expr") { - value_expr expr(ledger::parse_value_expr(*arg)); + if (verb == "expr") { + xml::xpath_t expr(*arg); - if (config.verbose_mode) { - std::cout << "Value expression tree:" << std::endl; - ledger::dump_value_expr(std::cout, expr.get()); - std::cout << std::endl; - std::cout << "Value expression parsed was:" << std::endl; - ledger::write_value_expr(std::cout, expr.get()); - std::cout << std::endl << std::endl; - std::cout << "Result of computation: "; + if (session.verbose_mode) { + *out << "Value expression tree:" << std::endl; + expr.dump(*out); + *out << std::endl; + *out << "Value expression parsed was:" << std::endl; + expr.write(*out); + *out << std::endl << std::endl; + *out << "Result of calculation: "; } - value_t result = guarded_compute(expr.get()); - std::cout << result.strip_annotations() << std::endl; + *out << expr.calc((xml::document_t *)NULL, report). + strip_annotations() << std::endl; return 0; } - - // Compile the format strings - - const std::string * format; - - if (! report.format_string.empty()) - format = &report.format_string; - else if (command == "b") - format = &config.balance_format; - else if (command == "r") - format = &config.register_format; - else if (command == "E") - format = &config.equity_format; - else if (command == "P") - format = &config.prices_format; - else if (command == "D") - format = &config.pricesdb_format; - else if (command == "w") - format = &config.write_xact_format; - else - format = &config.print_format; - - // Walk the entries based on the report type and the options - - item_handler<transaction_t> * formatter; - std::list<item_handler<transaction_t> *> formatter_ptrs; - - if (command == "b" || command == "E") - formatter = new set_account_value; - else if (command == "p" || command == "e") - formatter = new format_entries(*out, *format); - else if (command == "x") - formatter = new format_emacs_transactions(*out); - else if (command == "X") - formatter = new format_xml_entries(*out, report.show_totals); - else if (command == "c") - formatter = new format_csv_transactions(*out); - else - formatter = new format_transactions(*out, *format); - - if (command == "w") { - TRACE_PUSH(text_writer, "Writing journal file"); - write_textual_journal(*journal, first_arg, *formatter, - config.write_hdr_format, *out); - TRACE_POP(text_writer, "Finished writing"); - } - else if (command == "W") { - TRACE_PUSH(binary_writer, "Writing binary file"); - std::ofstream stream(report.output_file.c_str()); - write_binary_journal(stream, journal.get()); - TRACE_POP(binary_writer, "Finished writing"); + else if (verb == "xpath") { + std::cout << "XPath parsed:" << std::endl; + xml::xpath_t xpath(*arg); + xpath.write(*out); + *out << std::endl; + +#if 0 + std::auto_ptr<repitem_t> items(repitem_t::wrap(&session, report, true)); + print_addr cb; + items->select(path.get(), cb); +#endif + return 0; } - else { - TRACE_PUSH(main, "Walking journal entries"); - formatter = report.chain_xact_handlers(command, formatter, journal.get(), - journal->master, formatter_ptrs); - if (command == "e") - walk_transactions(new_entry->transactions, *formatter); - else if (command == "P" || command == "D") - walk_commodities(commodity_t::commodities, *formatter); - else - walk_entries(journal->entries, *formatter); + // Cleanup memory -- if this is a beta or development build. - if (command != "P" && command != "D") - formatter->flush(); +#if DEBUG_LEVEL >= BETA + { TRACE_PUSH(cleanup, "Cleaning up allocated memory"); - TRACE_POP(main, "Finished entry walk"); - } +#ifdef USE_BOOST_PYTHON + shutdown_ledger_for_python(); +#endif - // For the balance and equity reports, output the sum totals. + if (! report->output_file.empty()) + delete out; - if (command == "b") { - TRACE_PUSH(main, "Walking journal accounts"); + TRACE_POP(cleanup, "Finished cleaning"); } +#endif - format_account acct_formatter(*out, *format, report.display_predicate); - sum_accounts(*journal->master); - walk_accounts(*journal->master, acct_formatter, report.sort_string); - acct_formatter.flush(); + // Create the an argument scope containing the report command's + // arguments, and then invoke the command. - if (account_has_xdata(*journal->master)) { - account_xdata_t& xdata = account_xdata(*journal->master); - if (! report.show_collapsed && xdata.total) { - *out << "--------------------\n"; - xdata.value = xdata.total; - acct_formatter.format.format(*out, details_t(*journal->master)); - } - } - TRACE_POP(main, "Finished account walk"); - } - else if (command == "E") { - TRACE_PUSH(main, "Walking journal accounts"); + xml::xpath_t::scope_t * locals = + new xml::xpath_t::scope_t(report, xml::xpath_t::scope_t::ARGUMENT); - format_equity acct_formatter(*out, *format, report.display_predicate); - sum_accounts(*journal->master); - walk_accounts(*journal->master, acct_formatter, report.sort_string); - acct_formatter.flush(); + locals->args = new value_t::sequence_t; + locals->args.push_back(out); + locals->args.push_back(journal->document); - TRACE_POP(main, "Finished account walk"); - } + if (command->wants_args) { +#if 1 + locals->args.push_back(&args); +#else + for (strings_list::iterator i = args.begin(); + i != args.end(); + i++) + locals->args.push_back(*i); +#endif + } else { + std::string regexps[4]; + + // Treat the remaining command-line arguments as regular + // expressions, used for refining report results. + + int base = 0; + for (strings_list::iterator i = arg; i != args.end(); i++) + if ((*i)[0] == '-') { + if ((*i)[1] == '-') { + if (base == 0) + base += 2; + continue; + } + if (! regexps[base + 1].empty()) + regexps[base + 1] += "|"; + regexps[base + 1] += (*i).substr(1); + } else { + if (! regexps[base].empty()) + regexps[base] += "|"; + regexps[base] += *i; + } -#if DEBUG_LEVEL >= BETA - { TRACE_PUSH(cleanup, "Cleaning up allocated memory"); +#if 0 + // jww (2006-09-21): Escape the \ in these strings! - clear_transaction_xdata xact_cleaner; - walk_entries(journal->entries, xact_cleaner); + if (! regexps[3].empty()) + report->transforms.push_front + (new remove_transform + (std::string("//entry[payee =~ /(") + regexps[3] + ")/]")); - clear_account_xdata acct_cleaner; - walk_accounts(*journal->master, acct_cleaner); + if (! regexps[2].empty()) + report->transforms.push_front + (new select_transform + (std::string("//entry[payee =~ /(") + regexps[2] + ")/]")); - if (! report.output_file.empty()) - delete out; + if (! regexps[1].empty()) + report->transforms.push_front + (new remove_transform + (std::string("//xact[account =~ /(") + regexps[1] + ")/]")); - for (std::list<item_handler<transaction_t> *>::iterator i - = formatter_ptrs.begin(); - i != formatter_ptrs.end(); - i++) - delete *i; + if (! regexps[0].empty()) + report->transforms.push_front + (new select_transform + (std::string("//xact[account =~ /(") + regexps[0] + ")/]")); +#endif + } - TRACE_POP(cleanup, "Finished cleaning"); } +#if 0 + report->apply_transforms(items.get()); #endif + value_t temp; + (*command)(temp, locals); + // Write out the binary cache, if need be - if (config.use_cache && config.cache_dirty && - ! config.cache_file.empty()) { + if (session.use_cache && session.cache_dirty && + ! session.cache_file.empty()) { TRACE_PUSH(binary_cache, "Writing journal file"); - std::ofstream stream(config.cache_file.c_str()); - write_binary_journal(stream, journal.get()); + std::ofstream stream(session.cache_file.c_str()); +#if 0 + write_binary_journal(stream, journal); +#endif TRACE_POP(binary_cache, "Finished writing"); } + // If the user specified a pager, wait for it to exit now + #ifdef HAVE_UNIX_PIPES - if (! config.pager.empty()) { + if (report->output_file.empty() && ! report->pager.empty()) { delete out; close(pfd[1]); @@ -441,16 +412,47 @@ appending the output of this command to your Ledger file if you so choose." int main(int argc, char * argv[], char * envp[]) { try { + std::ios::sync_with_stdio(false); + + ledger::tracing_active = true; + #if DEBUG_LEVEL < BETA ledger::do_cleanup = false; #endif - config_t config; - report_t report; - ledger::config = &config; - ledger::report = &report; TRACE_PUSH(main, "Ledger starting"); - int status = parse_and_report(config, report, argc, argv, envp); + + std::auto_ptr<ledger::session_t> session(new ledger::session_t); + +#if 0 + session->register_parser(new binary_parser_t); +#endif +#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) + session->register_parser(new xml_parser_t); + session->register_parser(new gnucash_parser_t); +#endif +#ifdef HAVE_LIBOFX + session->register_parser(new ofx_parser_t); +#endif + session->register_parser(new qif_parser_t); + session->register_parser(new textual_parser_t); + + std::auto_ptr<ledger::report_t> report(new ledger::report_t(session.get())); + + int status = read_and_report(report.get(), argc, argv, envp); + + if (! ledger::do_cleanup) { + report.release(); + session.release(); + } + TRACE_POP(main, "Ledger done"); + + DEBUG_IF("ledger.trace.memory") { + report_memory(std::cout); + } + + ledger::tracing_active = false; + return status; } catch (error * err) { @@ -462,6 +464,7 @@ int main(int argc, char * argv[], char * envp[]) err->reveal_context(std::cerr, "Error"); std::cerr << err->what() << std::endl; delete err; + ledger::tracing_active = false; return 1; } catch (fatal * err) { @@ -473,14 +476,17 @@ int main(int argc, char * argv[], char * envp[]) err->reveal_context(std::cerr, "Fatal"); std::cerr << err->what() << std::endl; delete err; + ledger::tracing_active = false; return 1; } catch (const std::exception& err) { std::cout.flush(); std::cerr << "Error: " << err.what() << std::endl; + ledger::tracing_active = false; return 1; } catch (int status) { + ledger::tracing_active = false; return status; } } @@ -1,3 +1,6 @@ +#ifdef USE_PCH +#include "pch.h" +#else #include "mask.h" #include "debug.h" #include "util.h" @@ -5,9 +8,12 @@ #include <cstdlib> #include <pcre.h> +#endif mask_t::mask_t(const std::string& pat) : exclude(false) { + TRACE_CTOR("mask_t(const std::string&)"); + const char * p = pat.c_str(); if (*p == '-') { exclude = true; @@ -33,6 +39,8 @@ mask_t::mask_t(const std::string& pat) : exclude(false) mask_t::mask_t(const mask_t& m) : exclude(m.exclude), pattern(m.pattern) { + TRACE_CTOR("mask_t(copy)"); + const char *error; int erroffset; regexp = pcre_compile(pattern.c_str(), PCRE_CASELESS, @@ -41,7 +49,9 @@ mask_t::mask_t(const mask_t& m) : exclude(m.exclude), pattern(m.pattern) } mask_t::~mask_t() { - pcre_free((pcre *)regexp); + TRACE_DTOR("mask_t"); + if (regexp) + pcre_free((pcre *)regexp); } bool mask_t::match(const std::string& str) const @@ -1,11 +1,11 @@ #ifndef _MASK_H #define _MASK_H +#include "error.h" + #include <string> #include <exception> -#include "error.h" - class mask_t { public: @@ -1,3 +1,6 @@ +#ifdef USE_PCH +#include "pch.h" +#else #include "journal.h" #include "ofx.h" #include "format.h" @@ -7,6 +10,7 @@ #include "util.h" #include <libofx.h> +#endif namespace ledger { @@ -116,8 +120,10 @@ int ofx_proc_transaction_cb(struct OfxTransactionData data, if (! curr_journal->add_entry(entry)) { print_entry(std::cerr, *entry); +#if 0 // jww (2005-02-09): uncomment - //have_error = "The above entry does not balance"; + have_error = "The above entry does not balance"; +#endif delete entry; return -1; } @@ -195,7 +201,6 @@ bool ofx_parser_t::test(std::istream& in) const } unsigned int ofx_parser_t::parse(std::istream& in, - config_t& config, journal_t * journal, account_t * master, const std::string * original_file) @@ -11,7 +11,6 @@ class ofx_parser_t : public parser_t virtual bool test(std::istream& in) const; virtual unsigned int parse(std::istream& in, - config_t& config, journal_t * journal, account_t * master = NULL, const std::string * original_file = NULL); @@ -1,75 +1,136 @@ +#ifdef USE_PCH +#include "pch.h" +#else #include "option.h" -#include "config.h" #include "report.h" #include "debug.h" #include "error.h" +#ifdef USE_BOOST_PYTHON +#include "py_eval.h" +#endif #include <iostream> #include <cstdarg> #include "util.h" +#endif + +#ifdef USE_BOOST_PYTHON +static ledger::option_t * find_option(const std::string& name); +#endif + +namespace ledger { namespace { - inline void process_option(option_t * opt, const char * arg = NULL) { - if (! opt->handled) { - try { - opt->handler(arg); - } - catch (error * err) { - err->context.push_back - (new error_context - (std::string("While parsing option '--") + opt->long_opt + - "'" + (opt->short_opt != '\0' ? - (std::string(" (-") + opt->short_opt + "):") : ":"))); - throw err; - } - opt->handled = true; + xml::xpath_t::functor_t * find_option(xml::xpath_t::scope_t * scope, + const std::string& name) + { + char buf[128]; + std::strcpy(buf, "option_"); + char * p = &buf[7]; + for (const char * q = name.c_str(); *q; q++) { + if (*q == '-') + *p++ = '_'; + else + *p++ = *q; } + *p = '\0'; + + if (xml::xpath_t::op_t * def = scope->lookup(buf)) + return def->functor_obj(); + else + return NULL; } - option_t * search_options(option_t * array, const char * name) + xml::xpath_t::functor_t * find_option(xml::xpath_t::scope_t * scope, + const char letter) { - int first = 0; - int last = CONFIG_OPTIONS_SIZE; - while (first <= last) { - int mid = (first + last) / 2; // compute mid point. + char buf[9]; + std::strcpy(buf, "option_"); + buf[7] = letter; + buf[8] = '\0'; - int result; - if ((result = (int)name[0] - (int)array[mid].long_opt[0]) == 0) - result = std::strcmp(name, array[mid].long_opt); - - if (result > 0) - first = mid + 1; // repeat search in top half. - else if (result < 0) - last = mid - 1; // repeat search in bottom half. - else - return &array[mid]; - } - return NULL; + if (xml::xpath_t::op_t * def = scope->lookup(buf)) + return def->functor_obj(); + else + return NULL; } - option_t * search_options(option_t * array, const char letter) + void process_option(xml::xpath_t::functor_t * opt, xml::xpath_t::scope_t * scope, + const char * arg) { - for (int i = 0; i < CONFIG_OPTIONS_SIZE; i++) - if (letter == array[i].short_opt) - return &array[i]; - return NULL; + try { + xml::xpath_t::scope_t * args = NULL; + if (arg) { + args = new xml::xpath_t::scope_t(scope, xml::xpath_t::scope_t::ARGUMENT); + args->args.set_string(arg); + } + + value_t temp; + (*opt)(temp, args); + } + catch (error * err) { +#if 0 + err->context.push_back + (new error_context + (std::string("While parsing option '--") + opt->long_opt + + "'" + (opt->short_opt != '\0' ? + (std::string(" (-") + opt->short_opt + "):") : ":"))); +#endif + throw err; + } } } -bool process_option(option_t * options, const std::string& name, +bool process_option(const std::string& name, xml::xpath_t::scope_t * scope, const char * arg) { - option_t * opt = search_options(options, name.c_str()); - if (opt) { - process_option(opt, arg); + if (xml::xpath_t::functor_t * opt = find_option(scope, name)) { + process_option(opt, scope, arg); return true; } return false; } -void process_arguments(option_t * options, int argc, char ** argv, - const bool anywhere, std::list<std::string>& args) +void process_environment(const char ** envp, const std::string& tag, + xml::xpath_t::scope_t * scope) +{ + const char * tag_p = tag.c_str(); + unsigned int tag_len = tag.length(); + + for (const char ** p = envp; *p; p++) + if (! tag_p || std::strncmp(*p, tag_p, tag_len) == 0) { + char buf[128]; + char * r = buf; + const char * q; + for (q = *p + tag_len; + *q && *q != '=' && r - buf < 128; + q++) + if (*q == '_') + *r++ = '-'; + else + *r++ = std::tolower(*q); + *r = '\0'; + + if (*q == '=') { + try { + if (! process_option(buf, scope, q + 1)) + /*throw new option_error("unknown option")*/; + } + catch (error * err) { + err->context.push_back + (new error_context + (std::string("While parsing environment variable option '") + + *p + "':")); + throw err; + } + } + } +} + +void process_arguments(int argc, char ** argv, const bool anywhere, + xml::xpath_t::scope_t * scope, + std::list<std::string>& args) { int index = 0; for (char ** i = argv; *i; i++) { @@ -97,42 +158,48 @@ void process_arguments(option_t * options, int argc, char ** argv, value = p; } - option_t * opt = search_options(options, name); + xml::xpath_t::functor_t * opt = find_option(scope, name); if (! opt) throw new option_error(std::string("illegal option --") + name); - if (opt->wants_arg && value == NULL) { + if (opt->wants_args && value == NULL) { value = *++i; if (value == NULL) throw new option_error(std::string("missing option argument for --") + name); } - process_option(opt, value); + process_option(opt, scope, value); } else if ((*i)[1] == '\0') { throw new option_error(std::string("illegal option -")); } else { - std::list<option_t *> opt_queue; + std::list<xml::xpath_t::functor_t *> option_queue; int x = 1; for (char c = (*i)[x]; c != '\0'; x++, c = (*i)[x]) { - option_t * opt = search_options(options, c); + xml::xpath_t::functor_t * opt = find_option(scope, c); if (! opt) throw new option_error(std::string("illegal option -") + c); - opt_queue.push_back(opt); + option_queue.push_back(opt); } - for (std::list<option_t *>::iterator o = opt_queue.begin(); - o != opt_queue.end(); o++) { + for (std::list<xml::xpath_t::functor_t *>::iterator + o = option_queue.begin(); + o != option_queue.end(); + o++) { char * value = NULL; - if ((*o)->wants_arg) { + if ((*o)->wants_args) { value = *++i; if (value == NULL) throw new option_error(std::string("missing option argument for -") + +#if 0 (*o)->short_opt); +#else + '?'); +#endif } - process_option(*o, value); + process_option(*o, scope, value); } } @@ -141,923 +208,88 @@ void process_arguments(option_t * options, int argc, char ** argv, } } -void process_environment(option_t * options, const char ** envp, - const std::string& tag) -{ - const char * tag_p = tag.c_str(); - unsigned int tag_len = tag.length(); - - for (const char ** p = envp; *p; p++) - if (! tag_p || std::strncmp(*p, tag_p, tag_len) == 0) { - char buf[128]; - char * r = buf; - const char * q; - for (q = *p + tag_len; - *q && *q != '=' && r - buf < 128; - q++) - if (*q == '_') - *r++ = '-'; - else - *r++ = std::tolower(*q); - *r = '\0'; - - if (*q == '=') { - try { - process_option(options, buf, q + 1); - } - catch (error * err) { - err->context.pop_back(); - err->context.push_back - (new error_context - (std::string("While parsing environment variable option '") + - *p + "':")); - throw err; - } - } - } -} - -////////////////////////////////////////////////////////////////////// - -namespace ledger { +} // namespace ledger -config_t * config = NULL; -report_t * report = NULL; +#ifdef USE_BOOST_PYTHON -static void show_version(std::ostream& out) -{ - out << "Ledger " << ledger::version << ", the command-line accounting tool"; - out << "\n\nCopyright (c) 2003-2006, John Wiegley. All rights reserved.\n\n\ -This program is made available under the terms of the BSD Public License.\n\ -See LICENSE file included with the distribution for details and disclaimer.\n"; - out << "\n(modules: gmp, pcre"; -#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) - out << ", xml"; -#endif -#ifdef HAVE_LIBOFX - out << ", ofx"; +#ifndef USE_PCH +#include <boost/python.hpp> +#include <boost/python/detail/api_placeholder.hpp> +#include <boost/python/suite/indexing/map_indexing_suite.hpp> #endif - out << ")\n"; -} - -void option_full_help(std::ostream& out) -{ - out << "usage: ledger [options] COMMAND [ACCT REGEX]... [-- [PAYEE REGEX]...]\n\n\ -Basic options:\n\ - -H, --full-help display this help text\n\ - -h, --help display summarized help text\n\ - -v, --version show version information\n\ - -f, --file FILE read ledger data from FILE\n\ - -o, --output FILE write output to FILE\n\ - -i, --init-file FILE initialize ledger using FILE (default: ~/.ledgerrc)\n\ - --cache FILE use FILE as a binary cache when --file is not used\n\ - --no-cache don't use a cache, even if it would be appropriate\n\ - -a, --account NAME use NAME for the default account (useful with QIF)\n\n\ -Report filtering:\n\ - -c, --current show only current and past entries (not future)\n\ - -b, --begin DATE set report begin date\n\ - -e, --end DATE set report end date\n\ - -p, --period STR report using the given period\n\ - --period-sort EXPR sort each report period's entries by EXPR\n\ - -C, --cleared consider only cleared transactions\n\ - -U, --uncleared consider only uncleared transactions\n\ - -R, --real consider only real (non-virtual) transactions\n\ - -L, --actual consider only actual (non-automated) transactions\n\ - -r, --related calculate report using related transactions\n\ - --budget generate budget entries based on FILE\n\ - --add-budget show all transactions plus the budget\n\ - --unbudgeted show only unbudgeted transactions\n\ - --forecast EXPR generate forecast entries while EXPR is true\n\ - -l, --limit EXPR calculate only transactions matching EXPR\n\ - -t, --amount EXPR use EXPR to calculate the displayed amount\n\ - -T, --total EXPR use EXPR to calculate the displayed total\n\n\ -Output customization:\n\ - -n, --collapse register: collapse entries; balance: no grand total\n\ - -s, --subtotal balance: show sub-accounts; other: show subtotals\n\ - -P, --by-payee show summarized totals by payee\n\ - -x, --comm-as-payee set commodity name as the payee, for reporting\n\ - -E, --empty balance: show accounts with zero balance\n\ - -W, --weekly show weekly sub-totals\n\ - -M, --monthly show monthly sub-totals\n\ - -Y, --yearly show yearly sub-totals\n\ - --dow show a days-of-the-week report\n\ - -S, --sort EXPR sort report according to the value expression EXPR\n\ - -w, --wide for the default register report, use 132 columns\n\ - --head COUNT show only the first COUNT entries (negative inverts)\n\ - --tail COUNT show only the last COUNT entries (negative inverts)\n\ - --pager PAGER send all output through the given PAGER program\n\ - -A, --average report average transaction amount\n\ - -D, --deviation report deviation from the average\n\ - -%, --percentage report balance totals as a percentile of the parent\n\ - --totals in the \"xml\" report, include running total\n\ - -j, --amount-data print only raw amount data (useful for scripting)\n\ - -J, --total-data print only raw total data\n\ - -d, --display EXPR display only transactions matching EXPR\n\ - -y, --date-format STR use STR as the date format (default: %Y/%m/%d)\n\ - -F, --format STR use STR as the format; for each report type, use:\n\ - --balance-format --register-format --print-format\n\ - --plot-amount-format --plot-total-format --equity-format\n\ - --prices-format --wide-register-format\n\n\ -Commodity reporting:\n\ - --price-db FILE sets the price database to FILE (def: ~/.pricedb)\n\ - -L, --price-exp MINS download quotes only if newer than MINS (def: 1440)\n\ - -Q, --download download price information when needed\n\ - -O, --quantity report commodity totals (this is the default)\n\ - -B, --basis report cost basis of commodities\n\ - -V, --market report last known market value\n\ - -g, --performance report gain/loss for each displayed transaction\n\ - -G, --gain report net gain/loss\n\n\ -Commands:\n\ - balance [REGEXP]... show balance totals for matching accounts\n\ - register [REGEXP]... show register of matching transactions\n\ - print [REGEXP]... print all matching entries\n\ - xml [REGEXP]... print matching entries in XML format\n\ - equity [REGEXP]... output equity entries for matching accounts\n\ - prices [REGEXP]... display price history for matching commodities\n\ - entry DATE PAYEE AMT output a derived entry, based on the arguments\n"; -} - -void option_help(std::ostream& out) -{ - out << "usage: ledger [options] COMMAND [ACCT REGEX]... [-- [PAYEE REGEX]...]\n\n\ -Use -H to see all the help text on one page, or:\n\ - --help-calc calculation options\n\ - --help-disp display options\n\ - --help-comm commodity options\n\n\ -Basic options:\n\ - -h, --help display this help text\n\ - -v, --version show version information\n\ - -f, --file FILE read ledger data from FILE\n\ - -o, --output FILE write output to FILE\n\ - -i, --init-file FILE initialize ledger using FILE (default: ~/.ledgerrc)\n\ - --cache FILE use FILE as a binary cache when --file is not used\n\ - --no-cache don't use a cache, even if it would be appropriate\n\ - -a, --account NAME use NAME for the default account (useful with QIF)\n\n\ -Commands:\n\ - balance [REGEXP]... show balance totals for matching accounts\n\ - register [REGEXP]... show register of matching transactions\n\ - print [REGEXP]... print all matching entries\n\ - xml [REGEXP]... print matching entries in XML format\n\ - equity [REGEXP]... output equity entries for matching accounts\n\ - prices [REGEXP]... display price history for matching commodities\n\ - entry DATE PAYEE AMT output a derived entry, based on the arguments\n"; -} - -void option_calc_help(std::ostream& out) -{ - out << "Options to control how a report is calculated:\n\ - -c, --current show only current and past entries (not future)\n\ - -b, --begin DATE set report begin date\n\ - -e, --end DATE set report end date\n\ - -p, --period STR report using the given period\n\ - --period-sort EXPR sort each report period's entries by EXPR\n\ - -C, --cleared consider only cleared transactions\n\ - -U, --uncleared consider only uncleared transactions\n\ - -R, --real consider only real (non-virtual) transactions\n\ - -L, --actual consider only actual (non-automated) transactions\n\ - -r, --related calculate report using related transactions\n\ - --budget generate budget entries based on FILE\n\ - --add-budget show all transactions plus the budget\n\ - --unbudgeted show only unbudgeted transactions\n\ - --forecast EXPR generate forecast entries while EXPR is true\n\ - -l, --limit EXPR calculate only transactions matching EXPR\n\ - -t, --amount EXPR use EXPR to calculate the displayed amount\n\ - -T, --total EXPR use EXPR to calculate the displayed total\n"; -} -void option_disp_help(std::ostream& out) -{ - out << "Output to control how report results are displayed:\n\ - -n, --collapse register: collapse entries; balance: no grand total\n\ - -s, --subtotal balance: show sub-accounts; other: show subtotals\n\ - -P, --by-payee show summarized totals by payee\n\ - -x, --comm-as-payee set commodity name as the payee, for reporting\n\ - -E, --empty balance: show accounts with zero balance\n\ - -W, --weekly show weekly sub-totals\n\ - -M, --monthly show monthly sub-totals\n\ - -Y, --yearly show yearly sub-totals\n\ - --dow show a days-of-the-week report\n\ - -S, --sort EXPR sort report according to the value expression EXPR\n\ - -w, --wide for the default register report, use 132 columns\n\ - --head COUNT show only the first COUNT entries (negative inverts)\n\ - --tail COUNT show only the last COUNT entries (negative inverts)\n\ - --pager PAGER send all output through the given PAGER program\n\ - -A, --average report average transaction amount\n\ - -D, --deviation report deviation from the average\n\ - -%, --percentage report balance totals as a percentile of the parent\n\ - --totals in the \"xml\" report, include running total\n\ - -j, --amount-data print only raw amount data (useful for scripting)\n\ - -J, --total-data print only raw total data\n\ - -d, --display EXPR display only transactions matching EXPR\n\ - -y, --date-format STR use STR as the date format (default: %Y/%m/%d)\n\ - -F, --format STR use STR as the format; for each report type, use:\n\ - --balance-format --register-format --print-format\n\ - --plot-amount-format --plot-total-format --equity-format\n\ - --prices-format --wide-register-format\n"; -} +using namespace boost::python; +using namespace ledger; -void option_comm_help(std::ostream& out) +struct py_option_t : public option_t { - out << "Options to control how commodity values are determined:\n\ - --price-db FILE sets the price database to FILE (def: ~/.pricedb)\n\ - -Z, --price-exp MINS download quotes only if newer than MINS (def: 1440)\n\ - -Q, --download download price information when needed\n\ - -O, --quantity report commodity totals (this is the default)\n\ - -B, --basis report cost basis of commodities\n\ - -V, --market report last known market value\n\ - -g, --performance report gain/loss for each displayed transaction\n\ - -G, --gain report net gain/loss\n"; -} - -////////////////////////////////////////////////////////////////////// -// -// Basic options - -OPT_BEGIN(full_help, "H") { - option_full_help(std::cout); - throw 0; -} OPT_END(full_help); + PyObject * self; -OPT_BEGIN(help, "h") { - option_help(std::cout); - throw 0; -} OPT_END(help); + py_option_t(PyObject * self_, + const std::string& long_opt, + const bool wants_arg) + : self(self_), option_t(long_opt, wants_arg) {} -OPT_BEGIN(help_calc, "") { - option_calc_help(std::cout); - throw 0; -} OPT_END(help_calc); + virtual ~py_option_t() {} -OPT_BEGIN(help_disp, "") { - option_disp_help(std::cout); - throw 0; -} OPT_END(help_disp); - -OPT_BEGIN(help_comm, "") { - option_comm_help(std::cout); - throw 0; -} OPT_END(help_comm); - -OPT_BEGIN(version, "v") { - show_version(std::cout); - throw 0; -} OPT_END(version); - -OPT_BEGIN(init_file, "i:") { - std::string path = resolve_path(optarg); - if (access(path.c_str(), R_OK) != -1) - config->init_file = path; - else - throw new error(std::string("The init file '") + path + - "' does not exist or is not readable"); -} OPT_END(init_file); - -OPT_BEGIN(file, "f:") { - if (std::string(optarg) == "-") { - config->data_file = optarg; - } else { - std::string path = resolve_path(optarg); - if (access(path.c_str(), R_OK) != -1) - config->data_file = path; - else - throw new error(std::string("The ledger file '") + path + - "' does not exist or is not readable"); + virtual bool check(option_source_t source) { + return call_method<bool>(self, "check", source); } -} OPT_END(file); - -OPT_BEGIN(cache, ":") { - config->cache_file = resolve_path(optarg); -} OPT_END(cache); - -OPT_BEGIN(no_cache, "") { - config->cache_file = "<none>"; -} OPT_END(no_cache); -OPT_BEGIN(output, "o:") { - if (std::string(optarg) != "-") { - std::string path = resolve_path(optarg); - report->output_file = path; - } -} OPT_END(output); - -OPT_BEGIN(account, "a:") { - config->account = optarg; -} OPT_END(account); - -OPT_BEGIN(debug, ":") { - config->debug_mode = true; - ::setenv("DEBUG_CLASS", optarg, 1); -} OPT_END(debug); - -OPT_BEGIN(verbose, "") { - config->verbose_mode = true; -} OPT_END(verbose); - -OPT_BEGIN(trace, "") { - config->trace_mode = true; -} OPT_END(trace); - -////////////////////////////////////////////////////////////////////// -// -// Report filtering - -OPT_BEGIN(effective, "") { - transaction_t::use_effective_date = true; -} OPT_END(effective); - -OPT_BEGIN(begin, "b:") { - char buf[128]; - interval_t interval(optarg); - if (! interval.begin) - throw new error(std::string("Could not determine beginning of period '") + - optarg + "'"); - - if (! report->predicate.empty()) - report->predicate += "&"; - report->predicate += "d>=["; - report->predicate += interval.begin.to_string(); - report->predicate += "]"; -} OPT_END(begin); - -OPT_BEGIN(end, "e:") { - char buf[128]; - interval_t interval(optarg); - if (! interval.end) - throw new error(std::string("Could not determine end of period '") + - optarg + "'"); - - if (! report->predicate.empty()) - report->predicate += "&"; - report->predicate += "d<["; - report->predicate += interval.end.to_string(); - report->predicate += "]"; - - terminus = interval.end; -} OPT_END(end); - -OPT_BEGIN(current, "c") { - if (! report->predicate.empty()) - report->predicate += "&"; - report->predicate += "d<=m"; -} OPT_END(current); - -OPT_BEGIN(cleared, "C") { - if (! report->predicate.empty()) - report->predicate += "&"; - report->predicate += "X"; -} OPT_END(cleared); - -OPT_BEGIN(uncleared, "U") { - if (! report->predicate.empty()) - report->predicate += "&"; - report->predicate += "!X"; -} OPT_END(uncleared); - -OPT_BEGIN(real, "R") { - if (! report->predicate.empty()) - report->predicate += "&"; - report->predicate += "R"; -} OPT_END(real); - -OPT_BEGIN(actual, "L") { - if (! report->predicate.empty()) - report->predicate += "&"; - report->predicate += "L"; -} OPT_END(actual); - -OPT_BEGIN(lots, "") { - report->keep_price = - report->keep_date = - report->keep_tag = true; -} OPT_END(lots); - -OPT_BEGIN(lot_prices, "") { - report->keep_price = true; -} OPT_END(lots_prices); - -OPT_BEGIN(lot_dates, "") { - report->keep_date = true; -} OPT_END(lots_dates); - -OPT_BEGIN(lot_tags, "") { - report->keep_tag = true; -} OPT_END(lots_tags); - -////////////////////////////////////////////////////////////////////// -// -// Output customization - -OPT_BEGIN(format, "F:") { - report->format_string = optarg; -} OPT_END(format); - -OPT_BEGIN(date_format, "y:") { - report->date_output_format = optarg; -} OPT_END(date_format); - -OPT_BEGIN(input_date_format, ":") { - config->date_input_format = optarg; -} OPT_END(input_date_format); - -OPT_BEGIN(balance_format, ":") { - config->balance_format = optarg; -} OPT_END(balance_format); - -OPT_BEGIN(register_format, ":") { - config->register_format = optarg; -} OPT_END(register_format); - -OPT_BEGIN(wide_register_format, ":") { - config->wide_register_format = optarg; -} OPT_END(wide_register_format); - -OPT_BEGIN(plot_amount_format, ":") { - config->plot_amount_format = optarg; -} OPT_END(plot_amount_format); - -OPT_BEGIN(plot_total_format, ":") { - config->plot_total_format = optarg; -} OPT_END(plot_total_format); - -OPT_BEGIN(print_format, ":") { - config->print_format = optarg; -} OPT_END(print_format); - -OPT_BEGIN(write_hdr_format, ":") { - config->write_hdr_format = optarg; -} OPT_END(write_hdr_format); - -OPT_BEGIN(write_xact_format, ":") { - config->write_xact_format = optarg; -} OPT_END(write_xact_format); - -OPT_BEGIN(equity_format, ":") { - config->equity_format = optarg; -} OPT_END(equity_format); - -OPT_BEGIN(prices_format, ":") { - config->prices_format = optarg; -} OPT_END(prices_format); - -OPT_BEGIN(wide, "w") { - config->register_format = config->wide_register_format; -} OPT_END(wide); - -OPT_BEGIN(head, ":") { - report->head_entries = std::atoi(optarg); -} OPT_END(head); - -OPT_BEGIN(tail, ":") { - report->tail_entries = std::atoi(optarg); -} OPT_END(tail); - -OPT_BEGIN(pager, ":") { - config->pager = optarg; -} OPT_END(pager); - -OPT_BEGIN(truncate, ":") { - std::string style(optarg); - if (style == "leading") - format_t::elision_style = format_t::TRUNCATE_LEADING; - else if (style == "middle") - format_t::elision_style = format_t::TRUNCATE_MIDDLE; - else if (style == "trailing") - format_t::elision_style = format_t::TRUNCATE_TRAILING; - else if (style == "abbrev") - format_t::elision_style = format_t::ABBREVIATE; -} OPT_END(truncate); - -OPT_BEGIN(abbrev_len, ":") { - format_t::abbrev_length = std::atoi(optarg); -} OPT_END(abbrev_len); - -OPT_BEGIN(empty, "E") { - report->show_empty = true; -} OPT_END(empty); - -OPT_BEGIN(collapse, "n") { - report->show_collapsed = true; -} OPT_END(collapse); - -OPT_BEGIN(subtotal, "s") { - report->show_subtotal = true; -} OPT_END(subtotal); - -OPT_BEGIN(totals, "") { - report->show_totals = true; -} OPT_END(totals); - -OPT_BEGIN(sort, "S:") { - report->sort_string = optarg; -} OPT_END(sort); - -OPT_BEGIN(sort_entries, "") { - report->sort_string = optarg; - report->entry_sort = true; -} OPT_END(sort_entries); - -OPT_BEGIN(sort_all, "") { - report->sort_string = optarg; - report->entry_sort = false; - report->sort_all = true; -} OPT_END(sort_all); - -OPT_BEGIN(period_sort, ":") { - report->sort_string = optarg; - report->entry_sort = true; -} OPT_END(period_sort); - -OPT_BEGIN(related, "r") { - report->show_related = true; -} OPT_END(related); - -OPT_BEGIN(descend, "") { - std::string arg(optarg); - std::string::size_type beg = 0; - report->descend_expr = ""; - for (std::string::size_type pos = arg.find(';'); - pos != std::string::npos; - beg = pos + 1, pos = arg.find(';', beg)) - report->descend_expr += (std::string("t=={") + - std::string(arg, beg, pos - beg) + "};"); - report->descend_expr += (std::string("t=={") + - std::string(arg, beg) + "}"); -} OPT_END(descend); - -OPT_BEGIN(descend_if, "") { - report->descend_expr = optarg; -} OPT_END(descend_if); - -OPT_BEGIN(period, "p:") { - if (report->report_period.empty()) { - report->report_period = optarg; - } else { - report->report_period += " "; - report->report_period += optarg; - } - - // If the period gives a beginning and/or ending date, make sure to - // modify the calculation predicate (via the --begin and --end - // options) to take this into account. - - interval_t interval(report->report_period); - - if (interval.begin) { - if (! report->predicate.empty()) - report->predicate += "&"; - report->predicate += "d>=["; - report->predicate += interval.begin.to_string(); - report->predicate += "]"; - } - - if (interval.end) { - if (! report->predicate.empty()) - report->predicate += "&"; - report->predicate += "d<["; - report->predicate += interval.end.to_string(); - report->predicate += "]"; - - terminus = interval.end; + virtual void select(report_t * report, const char * optarg = NULL) { + if (optarg) + return call_method<void>(self, "select", report, optarg); + else + return call_method<void>(self, "select", report); } -} OPT_END(period); - -OPT_BEGIN(daily, "") { - if (report->report_period.empty()) - report->report_period = "daily"; - else - report->report_period = std::string("daily ") + report->report_period; -} OPT_END(daily); - -OPT_BEGIN(weekly, "W") { - if (report->report_period.empty()) - report->report_period = "weekly"; - else - report->report_period = std::string("weekly ") + report->report_period; -} OPT_END(weekly); - -OPT_BEGIN(monthly, "M") { - if (report->report_period.empty()) - report->report_period = "monthly"; - else - report->report_period = std::string("monthly ") + report->report_period; -} OPT_END(monthly); - -OPT_BEGIN(quarterly, "") { - if (report->report_period.empty()) - report->report_period = "quarterly"; - else - report->report_period = std::string("quarterly ") + report->report_period; -} OPT_END(quarterly); - -OPT_BEGIN(yearly, "Y") { - if (report->report_period.empty()) - report->report_period = "yearly"; - else - report->report_period = std::string("yearly ") + report->report_period; -} OPT_END(yearly); - -OPT_BEGIN(dow, "") { - report->days_of_the_week = true; -} OPT_END(dow); - -OPT_BEGIN(by_payee, "P") { - report->by_payee = true; -} OPT_END(by_payee); - -OPT_BEGIN(comm_as_payee, "x") { - report->comm_as_payee = true; -} OPT_END(comm_as_payee); - -OPT_BEGIN(code_as_payee, "") { - report->code_as_payee = true; -} OPT_END(code_as_payee); - -OPT_BEGIN(budget, "") { - report->budget_flags = BUDGET_BUDGETED; -} OPT_END(budget); - -OPT_BEGIN(add_budget, "") { - report->budget_flags = BUDGET_BUDGETED | BUDGET_UNBUDGETED; -} OPT_END(add_budget); - -OPT_BEGIN(unbudgeted, "") { - report->budget_flags = BUDGET_UNBUDGETED; -} OPT_END(unbudgeted); - -OPT_BEGIN(forecast, ":") { - report->forecast_limit = optarg; -} OPT_END(forecast); - -OPT_BEGIN(reconcile, ":") { - report->reconcile_balance = optarg; -} OPT_END(reconcile); - -OPT_BEGIN(reconcile_date, ":") { - report->reconcile_date = optarg; -} OPT_END(reconcile_date); - -OPT_BEGIN(limit, "l:") { - if (! report->predicate.empty()) - report->predicate += "&"; - report->predicate += "("; - report->predicate += optarg; - report->predicate += ")"; -} OPT_END(limit); - -OPT_BEGIN(only, ":") { - if (! report->secondary_predicate.empty()) - report->secondary_predicate += "&"; - report->secondary_predicate += "("; - report->secondary_predicate += optarg; - report->secondary_predicate += ")"; -} OPT_END(only); - -OPT_BEGIN(display, "d:") { - if (! report->display_predicate.empty()) - report->display_predicate += "&"; - report->display_predicate += "("; - report->display_predicate += optarg; - report->display_predicate += ")"; -} OPT_END(display); - -OPT_BEGIN(amount, "t:") { - ledger::amount_expr = optarg; -} OPT_END(amount); - -OPT_BEGIN(total, "T:") { - ledger::total_expr = optarg; -} OPT_END(total); - -OPT_BEGIN(amount_data, "j") { - report->format_string = config->plot_amount_format; -} OPT_END(amount_data); - -OPT_BEGIN(total_data, "J") { - report->format_string = config->plot_total_format; -} OPT_END(total_data); - -OPT_BEGIN(ansi, "") { - format_t::ansi_codes = true; - format_t::ansi_invert = false; -} OPT_END(ansi); - -OPT_BEGIN(ansi_invert, "") { - format_t::ansi_codes = - format_t::ansi_invert = true; -} OPT_END(ansi); - -////////////////////////////////////////////////////////////////////// -// -// Commodity reporting - -OPT_BEGIN(base, ":") { - amount_t::keep_base = true; -} OPT_END(base); - -OPT_BEGIN(price_db, ":") { - config->price_db = optarg; -} OPT_END(price_db); - -OPT_BEGIN(price_exp, "Z:") { - config->pricing_leeway = std::atol(optarg) * 60; -} OPT_END(price_exp); - -OPT_BEGIN(download, "Q") { - config->download_quotes = true; -} OPT_END(download); - -OPT_BEGIN(quantity, "O") { - ledger::amount_expr = "@a"; - ledger::total_expr = "@O"; -} OPT_END(quantity); - -OPT_BEGIN(basis, "B") { - ledger::amount_expr = "@b"; - ledger::total_expr = "@B"; -} OPT_END(basis); +}; -OPT_BEGIN(price, "I") { - ledger::amount_expr = "@i"; - ledger::total_expr = "@I"; -} OPT_END(price); +BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(option_select_overloads, + py_option_t::select, 1, 2) -OPT_BEGIN(market, "V") { - report->show_revalued = true; +typedef std::map<const std::string, object> options_map; +typedef std::pair<const std::string, object> options_pair; - ledger::amount_expr = "@v"; - ledger::total_expr = "@V"; -} OPT_END(market); +options_map options; -namespace { - void parse_price_setting(const char * optarg) - { - char * equals = std::strchr(optarg, '='); - if (! equals) - return; - - while (std::isspace(*optarg)) - optarg++; - while (equals > optarg && std::isspace(*(equals - 1))) - equals--; - - std::string symbol(optarg, 0, equals - optarg); - amount_t price(equals + 1); +static option_t * find_option(const std::string& name) +{ + options_map::const_iterator i = options.find(name); + if (i != options.end()) + return extract<py_option_t *>((*i).second.ptr()); - if (commodity_t * commodity = commodity_t::find_or_create(symbol)) { - commodity->add_price(datetime_t::now, price); - commodity->history()->bogus_time = datetime_t::now; - } - } + return NULL; } -OPT_BEGIN(set_price, ":") { - std::string arg(optarg); - std::string::size_type beg = 0; - for (std::string::size_type pos = arg.find(';'); - pos != std::string::npos; - beg = pos + 1, pos = arg.find(';', beg)) - parse_price_setting(std::string(arg, beg, pos - beg).c_str()); - parse_price_setting(std::string(arg, beg).c_str()); -} OPT_END(set_price); - -OPT_BEGIN(performance, "g") { - ledger::amount_expr = "@P(@a,@m)-@b"; - ledger::total_expr = "@P(@O,@m)-@B"; -} OPT_END(performance); - -OPT_BEGIN(gain, "G") { - report->show_revalued = - report->show_revalued_only = true; - - ledger::amount_expr = "@a"; - ledger::total_expr = "@G"; -} OPT_END(gain); - -static std::string expand_value_expr(const std::string& tmpl, - const std::string& expr) +void shutdown_option() { - std::string xp = tmpl; - for (std::string::size_type i = xp.find('#'); - i != std::string::npos; - i = xp.find('#')) - xp = (std::string(xp, 0, i) + "(" + expr + ")" + - std::string(xp, i + 1)); - return xp; + options.clear(); } -OPT_BEGIN(average, "A") { - ledger::total_expr = expand_value_expr("@A(#)", ledger::total_expr.expr); -} OPT_END(average); - -OPT_BEGIN(deviation, "D") { - ledger::total_expr = expand_value_expr("@t-@A(#)", ledger::total_expr.expr); -} OPT_END(deviation); +void export_option() +{ + class_< option_t, py_option_t, boost::noncopyable > + ("Option", init<const std::string&, bool>()) + .def_readonly("long_opt", &py_option_t::long_opt) + .def_readonly("short_opt", &py_option_t::short_opt) + .def_readonly("wants_arg", &py_option_t::wants_arg) + .def_readwrite("handled", &py_option_t::handled) + .def("check", &py_option_t::check) + .def("select", &py_option_t::select, option_select_overloads()) + ; -OPT_BEGIN(percentage, "%") { - ledger::total_expr = expand_value_expr("^#&{100.0%}*(#/^#)", - ledger::total_expr.expr); -} OPT_END(percentage); + enum_< option_t::option_source_t > ("OptionSource") + .value("InitFile", option_t::INIT_FILE) + .value("Environment", option_t::ENVIRONMENT) + .value("DataFile", option_t::DATA_FILE) + .value("CommandLine", option_t::COMMAND_LINE) + ; -////////////////////////////////////////////////////////////////////// + class_< options_map > ("OptionsMap") + .def(map_indexing_suite<options_map>()) + ; -option_t config_options[CONFIG_OPTIONS_SIZE] = { - { "abbrev-len", '\0', true, opt_abbrev_len, false }, - { "account", 'a', true, opt_account, false }, - { "actual", 'L', false, opt_actual, false }, - { "add-budget", '\0', false, opt_add_budget, false }, - { "amount", 't', true, opt_amount, false }, - { "amount-data", 'j', false, opt_amount_data, false }, - { "ansi", '\0', false, opt_ansi, false }, - { "ansi-invert", '\0', false, opt_ansi_invert, false }, - { "average", 'A', false, opt_average, false }, - { "balance-format", '\0', true, opt_balance_format, false }, - { "base", '\0', false, opt_base, false }, - { "basis", 'B', false, opt_basis, false }, - { "begin", 'b', true, opt_begin, false }, - { "budget", '\0', false, opt_budget, false }, - { "by-payee", 'P', false, opt_by_payee, false }, - { "cache", '\0', true, opt_cache, false }, - { "cleared", 'C', false, opt_cleared, false }, - { "code-as-payee", '\0', false, opt_code_as_payee, false }, - { "collapse", 'n', false, opt_collapse, false }, - { "comm-as-payee", 'x', false, opt_comm_as_payee, false }, - { "cost", '\0', false, opt_basis, false }, - { "current", 'c', false, opt_current, false }, - { "daily", '\0', false, opt_daily, false }, - { "date-format", 'y', true, opt_date_format, false }, - { "debug", '\0', true, opt_debug, false }, - { "descend", '\0', true, opt_descend, false }, - { "descend-if", '\0', true, opt_descend_if, false }, - { "deviation", 'D', false, opt_deviation, false }, - { "display", 'd', true, opt_display, false }, - { "dow", '\0', false, opt_dow, false }, - { "download", 'Q', false, opt_download, false }, - { "effective", '\0', false, opt_effective, false }, - { "empty", 'E', false, opt_empty, false }, - { "end", 'e', true, opt_end, false }, - { "equity-format", '\0', true, opt_equity_format, false }, - { "file", 'f', true, opt_file, false }, - { "forecast", '\0', true, opt_forecast, false }, - { "format", 'F', true, opt_format, false }, - { "full-help", 'H', false, opt_full_help, false }, - { "gain", 'G', false, opt_gain, false }, - { "head", '\0', true, opt_head, false }, - { "help", 'h', false, opt_help, false }, - { "help-calc", '\0', false, opt_help_calc, false }, - { "help-comm", '\0', false, opt_help_comm, false }, - { "help-disp", '\0', false, opt_help_disp, false }, - { "init-file", 'i', true, opt_init_file, false }, - { "input-date-format", '\0', true, opt_input_date_format, false }, - { "limit", 'l', true, opt_limit, false }, - { "lot-dates", '\0', false, opt_lot_dates, false }, - { "lot-prices", '\0', false, opt_lot_prices, false }, - { "lot-tags", '\0', false, opt_lot_tags, false }, - { "lots", '\0', false, opt_lots, false }, - { "market", 'V', false, opt_market, false }, - { "monthly", 'M', false, opt_monthly, false }, - { "no-cache", '\0', false, opt_no_cache, false }, - { "only", '\0', true, opt_only, false }, - { "output", 'o', true, opt_output, false }, - { "pager", '\0', true, opt_pager, false }, - { "percentage", '%', false, opt_percentage, false }, - { "performance", 'g', false, opt_performance, false }, - { "period", 'p', true, opt_period, false }, - { "period-sort", '\0', true, opt_period_sort, false }, - { "plot-amount-format", '\0', true, opt_plot_amount_format, false }, - { "plot-total-format", '\0', true, opt_plot_total_format, false }, - { "price", 'I', false, opt_price, false }, - { "price-db", '\0', true, opt_price_db, false }, - { "price-exp", 'Z', true, opt_price_exp, false }, - { "prices-format", '\0', true, opt_prices_format, false }, - { "print-format", '\0', true, opt_print_format, false }, - { "quantity", 'O', false, opt_quantity, false }, - { "quarterly", '\0', false, opt_quarterly, false }, - { "real", 'R', false, opt_real, false }, - { "reconcile", '\0', true, opt_reconcile, false }, - { "reconcile-date", '\0', true, opt_reconcile_date, false }, - { "register-format", '\0', true, opt_register_format, false }, - { "related", 'r', false, opt_related, false }, - { "set-price", '\0', true, opt_set_price, false }, - { "sort", 'S', true, opt_sort, false }, - { "sort-all", '\0', true, opt_sort_all, false }, - { "sort-entries", '\0', true, opt_sort_entries, false }, - { "subtotal", 's', false, opt_subtotal, false }, - { "tail", '\0', true, opt_tail, false }, - { "total", 'T', true, opt_total, false }, - { "total-data", 'J', false, opt_total_data, false }, - { "totals", '\0', false, opt_totals, false }, - { "trace", '\0', false, opt_trace, false }, - { "truncate", '\0', true, opt_truncate, false }, - { "unbudgeted", '\0', false, opt_unbudgeted, false }, - { "uncleared", 'U', false, opt_uncleared, false }, - { "verbose", '\0', false, opt_verbose, false }, - { "version", 'v', false, opt_version, false }, - { "weekly", 'W', false, opt_weekly, false }, - { "wide", 'w', false, opt_wide, false }, - { "wide-register-format", '\0', true, opt_wide_register_format, false }, - { "write-hdr-format", '\0', true, opt_write_hdr_format, false }, - { "write-xact-format", '\0', true, opt_write_xact_format, false }, - { "yearly", 'Y', false, opt_yearly, false }, -}; + scope().attr("options") = ptr(&options); +} -} // namespace ledger +#endif // USE_BOOST_PYTHON @@ -2,20 +2,24 @@ #define _OPTION_H #include <list> +#include <map> #include <string> #include <exception> +#include "xpath.h" #include "error.h" -typedef void (*handler_t)(const char * arg); +namespace ledger { -struct option_t { - const char * long_opt; - char short_opt; - bool wants_arg; - handler_t handler; - bool handled; -}; +bool process_option(const std::string& name, xml::xpath_t::scope_t * scope, + const char * arg = NULL); + +void process_environment(const char ** envp, const std::string& tag, + xml::xpath_t::scope_t * scope); + +void process_arguments(int argc, char ** argv, const bool anywhere, + xml::xpath_t::scope_t * scope, + std::list<std::string>& args); class option_error : public error { public: @@ -23,31 +27,6 @@ class option_error : public error { virtual ~option_error() throw() {} }; -bool process_option(option_t * options, const std::string& opt, - const char * arg = NULL); -void process_arguments(option_t * options, int argc, char ** argv, - const bool anywhere, std::list<std::string>& args); -void process_environment(option_t * options, const char ** envp, - const std::string& tag); - -namespace ledger { - -class config_t; -class report_t; - -extern config_t * config; -extern report_t * report; - -#define CONFIG_OPTIONS_SIZE 97 -extern option_t config_options[CONFIG_OPTIONS_SIZE]; - -void option_help(std::ostream& out); - -#define OPT_BEGIN(tag, chars) \ - void opt_ ## tag(const char * optarg) - -#define OPT_END(tag) - } // namespace ledger #endif // _OPTION_H @@ -1,193 +1,55 @@ -#include "parser.h" -#include "journal.h" -#include "config.h" - -#include <fstream> -#ifdef WIN32 -#include <io.h> +#ifdef USE_PCH +#include "pch.h" #else -#include <unistd.h> +#include "parser.h" #endif -namespace ledger { - -typedef std::list<parser_t *> parsers_list; - -static parsers_list * parsers = NULL; - -void initialize_parser_support() -{ - parsers = new parsers_list; -} - -void shutdown_parser_support() -{ - if (parsers) { - delete parsers; - parsers = NULL; - } -} - -bool register_parser(parser_t * parser) -{ - parsers_list::iterator i; - for (i = parsers->begin(); i != parsers->end(); i++) - if (*i == parser) - break; - if (i != parsers->end()) - return false; - - parsers->push_back(parser); - - return true; -} - -bool unregister_parser(parser_t * parser) -{ - parsers_list::iterator i; - for (i = parsers->begin(); i != parsers->end(); i++) - if (*i == parser) - break; - if (i == parsers->end()) - return false; - - parsers->erase(i); - - return true; -} - -unsigned int parse_journal(std::istream& in, - config_t& config, - journal_t * journal, - account_t * master, - const std::string * original_file) -{ - if (! master) - master = journal->master; - - for (parsers_list::iterator i = parsers->begin(); - i != parsers->end(); - i++) - if ((*i)->test(in)) - return (*i)->parse(in, config, journal, master, original_file); - - return 0; -} - -unsigned int parse_journal_file(const std::string& path, - config_t& config, - journal_t * journal, - account_t * master, - const std::string * original_file) -{ - journal->sources.push_back(path); +#ifdef USE_BOOST_PYTHON - if (access(path.c_str(), R_OK) == -1) - throw new error(std::string("Cannot read file '") + path + "'"); - - if (! original_file) - original_file = &path; - - std::ifstream stream(path.c_str()); - return parse_journal(stream, config, journal, master, original_file); -} +#ifndef USE_PCH +#include <boost/python.hpp> +#include <Python.h> +#endif -extern parser_t * binary_parser_ptr; -extern parser_t * xml_parser_ptr; -extern parser_t * textual_parser_ptr; +using namespace boost::python; +using namespace ledger; -unsigned int parse_ledger_data(config_t& config, - journal_t * journal, - parser_t * cache_parser, - parser_t * xml_parser, - parser_t * stdin_parser) +struct py_parser_t : public parser_t { - unsigned int entry_count = 0; - - if (! cache_parser) - cache_parser = binary_parser_ptr; - if (! xml_parser) - xml_parser = xml_parser_ptr; - if (! stdin_parser) - stdin_parser = textual_parser_ptr; + PyObject * self; + py_parser_t(PyObject * self_) : self(self_) {} - DEBUG_PRINT("ledger.config.cache", - "3. use_cache = " << config.use_cache); - - if (! config.init_file.empty() && - access(config.init_file.c_str(), R_OK) != -1) { - if (parse_journal_file(config.init_file, config, journal) || - journal->auto_entries.size() > 0 || - journal->period_entries.size() > 0) - throw new error(std::string("Entries found in initialization file '") + - config.init_file + "'"); - - journal->sources.pop_front(); // remove init file + virtual bool test(std::istream& in) const { + return call_method<bool>(self, "test", in); } - if (config.use_cache && ! config.cache_file.empty() && - ! config.data_file.empty()) { - DEBUG_PRINT("ledger.config.cache", - "using_cache " << config.cache_file); - config.cache_dirty = true; - if (access(config.cache_file.c_str(), R_OK) != -1) { - std::ifstream stream(config.cache_file.c_str()); - if (cache_parser && cache_parser->test(stream)) { - std::string price_db_orig = journal->price_db; - journal->price_db = config.price_db; - entry_count += cache_parser->parse(stream, config, journal, - NULL, &config.data_file); - if (entry_count > 0) - config.cache_dirty = false; - else - journal->price_db = price_db_orig; - } - } + virtual repitem_t * parse(std::istream& in, + journal_t * journal, + account_t * master = NULL, + const std::string * original_file = NULL) { + return call_method<unsigned int>(self, "parse", in, journal, master, + original_file); } +}; - if (entry_count == 0 && ! config.data_file.empty()) { - account_t * acct = NULL; - if (! config.account.empty()) - acct = journal->find_account(config.account); +BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(parser_parse_overloads, + py_parser_t::parse, 2, 4) - journal->price_db = config.price_db; - if (! journal->price_db.empty() && - access(journal->price_db.c_str(), R_OK) != -1) { - if (parse_journal_file(journal->price_db, config, journal)) { - throw new error("Entries not allowed in price history file"); - } else { - DEBUG_PRINT("ledger.config.cache", - "read price database " << journal->price_db); - journal->sources.pop_back(); - } - } +BOOST_PYTHON_FUNCTION_OVERLOADS(parse_journal_overloads, parse_journal, 2, 4) +BOOST_PYTHON_FUNCTION_OVERLOADS(parse_journal_file_overloads, + parse_journal_file, 2, 4) - DEBUG_PRINT("ledger.config.cache", - "rejected cache, parsing " << config.data_file); - if (config.data_file == "-") { - config.use_cache = false; - journal->sources.push_back("<stdin>"); -#if 0 - // jww (2006-03-23): Why doesn't XML work on stdin? - if (xml_parser && std::cin.peek() == '<') - entry_count += xml_parser->parse(std::cin, config, journal, - acct); - else if (stdin_parser) -#endif - entry_count += stdin_parser->parse(std::cin, config, - journal, acct); - } - else if (access(config.data_file.c_str(), R_OK) != -1) { - entry_count += parse_journal_file(config.data_file, config, - journal, acct); - if (! journal->price_db.empty()) - journal->sources.push_back(journal->price_db); - } - } +void export_parser() { + class_< parser_t, py_parser_t, boost::noncopyable > ("Parser") + .def("test", &py_parser_t::test) + .def("parse", &py_parser_t::parse, parser_parse_overloads()) + ; - VALIDATE(journal->valid()); + def("register_parser", register_parser); + def("unregister_parser", unregister_parser); - return entry_count; + def("parse_journal", parse_journal, parse_journal_overloads()); + def("parse_journal_file", parse_journal_file, parse_journal_file_overloads()); } -} // namespace ledger +#endif // USE_BOOST_PYTHON @@ -1,16 +1,15 @@ #ifndef _PARSER_H #define _PARSER_H -#include <iostream> -#include <string> - #include "error.h" +#include <string> +#include <istream> + namespace ledger { class account_t; class journal_t; -class config_t; class parser_t { @@ -20,36 +19,11 @@ class parser_t virtual bool test(std::istream& in) const = 0; virtual unsigned int parse(std::istream& in, - config_t& config, journal_t * journal, account_t * master = NULL, const std::string * original_file = NULL) = 0; }; -bool register_parser(parser_t * parser); -bool unregister_parser(parser_t * parser); - -unsigned int parse_journal(std::istream& in, - config_t& config, - journal_t * journal, - account_t * master = NULL, - const std::string * original_file = NULL); - -unsigned int parse_journal_file(const std::string& path, - config_t& config, - journal_t * journal, - account_t * master = NULL, - const std::string * original_file = NULL); - -unsigned int parse_ledger_data(config_t& config, - journal_t * journal, - parser_t * cache_parser = NULL, - parser_t * xml_parser = NULL, - parser_t * stdin_parser = NULL); - -void initialize_parser_support(); -void shutdown_parser_support(); - class parse_error : public error { public: parse_error(const std::string& reason, error_context * ctxt = NULL) throw() diff --git a/py_eval.cc b/py_eval.cc new file mode 100644 index 00000000..ec656035 --- /dev/null +++ b/py_eval.cc @@ -0,0 +1,205 @@ +#ifdef USE_PCH +#include "pch.h" +#else +#include "py_eval.h" +#include "error.h" +#include "acconf.h" + +#include <istream> +#endif + +void export_amount(); +void export_balance(); +void export_value(); +void export_datetime(); + +void export_journal(); +void export_parser(); +void export_option(); +void export_walk(); +void export_report(); +void export_format(); +void export_valexpr(); + +void shutdown_option(); + +namespace ledger { + +namespace { + void initialize_ledger_for_python() + { + export_amount(); + export_balance(); + export_value(); + export_datetime(); + + export_journal(); + export_parser(); + export_option(); + export_walk(); + export_format(); + export_report(); + export_valexpr(); + } +} + +void shutdown_ledger_for_python() +{ + shutdown_option(); +} + +struct python_run +{ + object result; + python_run(python_interpreter_t * intepreter, + const std::string& str, int input_mode) + : result(handle<>(borrowed(PyRun_String(str.c_str(), input_mode, + intepreter->nspace.ptr(), + intepreter->nspace.ptr())))) {} + operator object() { + return result; + } +}; + +python_interpreter_t::python_interpreter_t(valexpr_t::scope_t * parent) + : valexpr_t::scope_t(parent), + mmodule(borrowed(PyImport_AddModule("__main__"))), + nspace(handle<>(borrowed(PyModule_GetDict(mmodule.get())))) +{ + Py_Initialize(); + detail::init_module("ledger", &initialize_ledger_for_python); +} + +object python_interpreter_t::import(const std::string& str) +{ + assert(Py_IsInitialized()); + + try { + PyObject * mod = PyImport_Import(PyString_FromString(str.c_str())); + if (! mod) + throw error(std::string("Failed to import Python module ") + str); + + object newmod(handle<>(borrowed(mod))); + +#if 1 + // Import all top-level entries directly into the main namespace + dict m_nspace(handle<>(borrowed(PyModule_GetDict(mod)))); + nspace.update(m_nspace); +#else + nspace[std::string(PyModule_GetName(mod))] = newmod; +#endif + return newmod; + } + catch (const error_already_set&) { + PyErr_Print(); + throw error(std::string("Importing Python module ") + str); + } +} + +object python_interpreter_t::eval(std::istream& in, py_eval_mode_t mode) +{ + bool first = true; + std::string buffer; + buffer.reserve(4096); + + while (! in.eof()) { + char buf[256]; + in.getline(buf, 255); + if (buf[0] == '!') + break; + if (first) + first = false; + else + buffer += "\n"; + buffer += buf; + } + + try { + int input_mode; + switch (mode) { + case PY_EVAL_EXPR: input_mode = Py_eval_input; break; + case PY_EVAL_STMT: input_mode = Py_single_input; break; + case PY_EVAL_MULTI: input_mode = Py_file_input; break; + } + assert(Py_IsInitialized()); + return python_run(this, buffer, input_mode); + } + catch (const error_already_set&) { + PyErr_Print(); + throw error("Evaluating Python code"); + } +} + +object python_interpreter_t::eval(const std::string& str, py_eval_mode_t mode) +{ + try { + int input_mode; + switch (mode) { + case PY_EVAL_EXPR: input_mode = Py_eval_input; break; + case PY_EVAL_STMT: input_mode = Py_single_input; break; + case PY_EVAL_MULTI: input_mode = Py_file_input; break; + } + assert(Py_IsInitialized()); + return python_run(this, str, input_mode); + } + catch (const error_already_set&) { + PyErr_Print(); + throw error("Evaluating Python code"); + } +} + +void python_interpreter_t::functor_t::operator()(value_t& result, + valexpr_t::scope_t * locals) +{ + try { + if (! PyCallable_Check(func.ptr())) { + result = extract<value_t>(func.ptr()); + } else { + if (locals->arg_scope && locals->args.size() > 0) { + list arglist; + for (valexpr_t::scope_t::args_list::iterator i = locals->args.begin(); + i != locals->args.end(); + i++) + arglist.append(*i); + + if (PyObject * val = + PyObject_CallObject(func.ptr(), tuple(arglist).ptr())) { + result = extract<value_t>(val)(); + Py_DECREF(val); + } + else if (PyObject * err = PyErr_Occurred()) { + PyErr_Print(); + throw new valexpr_t::calc_error + (std::string("While calling Python function '") + name() + "'"); + } else { + assert(0); + } + } else { + result = call<value_t>(func.ptr()); + } + } + } + catch (const error_already_set&) { + PyErr_Print(); + throw new valexpr_t::calc_error + (std::string("While calling Python function '") + name() + "'"); + } +} + +void python_interpreter_t::lambda_t::operator()(value_t& result, + valexpr_t::scope_t * locals) +{ + try { + assert(locals->arg_scope && locals->args.size() == 1); + value_t item = locals->args[0]; + assert(item.type == value_t::POINTER); + result = call<value_t>(func.ptr(), (repitem_t *)*(void **)item.data); + } + catch (const error_already_set&) { + PyErr_Print(); + throw new valexpr_t::calc_error + ("While evaluating Python lambda expression"); + } +} + +} // namespace ledger diff --git a/py_eval.h b/py_eval.h new file mode 100644 index 00000000..109f5fc4 --- /dev/null +++ b/py_eval.h @@ -0,0 +1,77 @@ +#ifndef _PY_EVAL_H +#define _PY_EVAL_H + +#include "valexpr.h" +#include "pyfstream.h" + +#include <string> +#include <iostream> + +#include <boost/python.hpp> + +using namespace boost::python; + +namespace ledger { + +void shutdown_ledger_for_python(); + +class python_interpreter_t : public valexpr_t::scope_t +{ + handle<> mmodule; + dict nspace; + + public: + python_interpreter_t(valexpr_t::scope_t * parent); + + virtual ~python_interpreter_t() { + Py_Finalize(); + } + + object import(const std::string& name); + + enum py_eval_mode_t { + PY_EVAL_EXPR, + PY_EVAL_STMT, + PY_EVAL_MULTI + }; + + object eval(std::istream& in, py_eval_mode_t mode = PY_EVAL_EXPR); + object eval(const std::string& str, py_eval_mode_t mode = PY_EVAL_EXPR); + object eval(const char * c_str, py_eval_mode_t mode = PY_EVAL_EXPR) { + std::string str(c_str); + return eval(str, mode); + } + + class functor_t : public valexpr_t::functor_t { + protected: + object func; + public: + python_functor_t(const std::string& name, object _func) + : valexpr_t::functor_t(name), func(_func) {} + + virtual void operator()(value_t& result, valexpr_t::scope_t * locals); + }; + + virtual void define(const std::string& name, valexpr_t::node_t * def) { + // Pass any definitions up to our parent + parent->define(name, def); + } + + virtual node_t * lookup(const std::string& name) { + object func = eval(name); + if (! func) + return parent ? parent->lookup(name) : NULL; + return valexpr_t::wrap_functor(new python_functor_t(name, func)); + } + + class lambda_t : public functor_t { + public: + python_lambda_t(object code) : python_functor_t("<lambda>"> code) {} + + virtual void operator()(value_t& result, valexpr_t::scope_t * locals); + }; +; + +} // namespace ledger + +#endif // _PY_EVAL_H diff --git a/pyfstream.h b/pyfstream.h new file mode 100644 index 00000000..c41940f5 --- /dev/null +++ b/pyfstream.h @@ -0,0 +1,146 @@ +#ifndef _PYFSTREAM_H +#define _PYFSTREAM_H + +#include <istream> +#include <ostream> +#include <streambuf> +#include <cstdio> +#include <cstring> + +#include "Python.h" + +// pyofstream +// - a stream that writes on a Python file object + +class pyoutbuf : public std::streambuf { + protected: + PyFileObject * fo; // Python file object + public: + // constructor + pyoutbuf (PyFileObject * _fo) : fo(_fo) {} + + protected: + // write one character + virtual int_type overflow (int_type c) { + if (c != EOF) { + char z[2]; + z[0] = c; + z[1] = '\0'; + if (PyFile_WriteString(z, (PyObject *)fo) < 0) { + return EOF; + } + } + return c; + } + + // write multiple characters + virtual std::streamsize xsputn (const char* s, std::streamsize num) { + char * buf = new char[num + 1]; + std::strncpy(buf, s, num); + buf[num] = '\0'; + if (PyFile_WriteString(buf, (PyObject *)fo) < 0) + num = 0; + delete[] buf; + return num; + } +}; + +class pyofstream : public std::ostream { + protected: + pyoutbuf buf; + public: + pyofstream (PyFileObject * fo) : std::ostream(0), buf(fo) { + rdbuf(&buf); + } +}; + +// pyifstream +// - a stream that reads on a file descriptor + +class pyinbuf : public std::streambuf { + protected: + PyFileObject * fo; // Python file object + protected: + /* data buffer: + * - at most, pbSize characters in putback area plus + * - at most, bufSize characters in ordinary read buffer + */ + static const int pbSize = 4; // size of putback area + static const int bufSize = 1024; // size of the data buffer + char buffer[bufSize + pbSize]; // data buffer + + public: + /* constructor + * - initialize file descriptor + * - initialize empty data buffer + * - no putback area + * => force underflow() + */ + pyinbuf (PyFileObject * _fo) : fo(_fo) { + setg (buffer+pbSize, // beginning of putback area + buffer+pbSize, // read position + buffer+pbSize); // end position + } + + protected: + // insert new characters into the buffer + virtual int_type underflow () { +#ifndef _MSC_VER + using std::memmove; +#endif + + // is read position before end of buffer? + if (gptr() < egptr()) { + return traits_type::to_int_type(*gptr()); + } + + /* process size of putback area + * - use number of characters read + * - but at most size of putback area + */ + int numPutback; + numPutback = gptr() - eback(); + if (numPutback > pbSize) { + numPutback = pbSize; + } + + /* copy up to pbSize characters previously read into + * the putback area + */ + memmove (buffer+(pbSize-numPutback), gptr()-numPutback, + numPutback); + + // read at most bufSize new characters + int num; + PyObject *line = PyFile_GetLine((PyObject *)fo, bufSize); + if (! line || ! PyString_Check(line)) { + // ERROR or EOF + return EOF; + } + + num = PyString_Size(line); + if (num == 0) + return EOF; + + memmove (buffer+pbSize, PyString_AsString(line), num); + + // reset buffer pointers + setg (buffer+(pbSize-numPutback), // beginning of putback area + buffer+pbSize, // read position + buffer+pbSize+num); // end of buffer + + // return next character + return traits_type::to_int_type(*gptr()); + } +}; + +class pyifstream : public std::istream { + protected: + pyinbuf buf; + public: + pyifstream (PyFileObject * fo) : std::istream(0), buf(fo) { + rdbuf(&buf); + } +}; + +#endif // _PYFSTREAM_H diff --git a/pyledger.cc b/pyledger.cc new file mode 100644 index 00000000..27d06776 --- /dev/null +++ b/pyledger.cc @@ -0,0 +1,10 @@ +#include <boost/python.hpp> + +using namespace boost::python; + +void initialize_ledger_for_python(); + +BOOST_PYTHON_MODULE(ledger) +{ + initialize_ledger_for_python(); +} diff --git a/pyledger.h b/pyledger.h new file mode 100644 index 00000000..9d8cafdf --- /dev/null +++ b/pyledger.h @@ -0,0 +1,16 @@ +#ifndef _PYLEDGER_H +#define _PYLEDGER_H + +////////////////////////////////////////////////////////////////////// +// +// Ledger Accounting Tool (with Python support via Boost.Python) +// +// A command-line tool for general double-entry accounting. +// +// Copyright (c) 2003,2004 John Wiegley <johnw@newartisans.com> +// + +#include <ledger.h> +#include <py_eval.h> + +#endif // _PYLEDGER_H @@ -1,3 +1,6 @@ +#ifdef USE_PCH +#include "pch.h" +#else #include "journal.h" #include "qif.h" #include "datetime.h" @@ -6,6 +9,7 @@ #include <cstring> #include <memory> +#endif namespace ledger { @@ -39,7 +43,6 @@ bool qif_parser_t::test(std::istream& in) const } unsigned int qif_parser_t::parse(std::istream& in, - config_t& config, journal_t * journal, account_t * master, const std::string * original_file) @@ -11,7 +11,6 @@ class qif_parser_t : public parser_t virtual bool test(std::istream& in) const; virtual unsigned int parse(std::istream& in, - config_t& config, journal_t * journal, account_t * master = NULL, const std::string * original_file = NULL); @@ -1,3 +1,6 @@ +#ifdef USE_PCH +#include "pch.h" +#else #include "quotes.h" #include "datetime.h" #include "error.h" @@ -6,6 +9,7 @@ #include <fstream> #include <cstdlib> #include <cstdio> +#endif namespace ledger { diff --git a/reconcile.cc b/reconcile.cc index 5b6dba24..e69de29b 100644 --- a/reconcile.cc +++ b/reconcile.cc @@ -1,88 +0,0 @@ -#include "reconcile.h" -#include "walk.h" - -namespace ledger { - -#define xact_next(x) ((transaction_t *)transaction_xdata(*x).ptr) -#define xact_next_ptr(x) ((transaction_t **)&transaction_xdata(*x).ptr) - -static bool search_for_balance(amount_t& amount, - transaction_t ** prev, transaction_t * next) -{ - for (; next; next = xact_next(next)) { - transaction_t * temp = *prev; - *prev = next; - - amount -= next->amount; - if (! amount || - search_for_balance(amount, xact_next_ptr(next), xact_next(next))) - return true; - amount += next->amount; - - *prev = temp; - } - return false; -} - -void reconcile_transactions::push_to_handler(transaction_t * first) -{ - for (; first; first = xact_next(first)) - item_handler<transaction_t>::operator()(*first); - - item_handler<transaction_t>::flush(); -} - -void reconcile_transactions::flush() -{ - value_t cleared_balance; - value_t pending_balance; - - transaction_t * first = NULL; - transaction_t ** last_ptr = &first; - - bool found_pending = false; - for (transactions_list::iterator x = xacts.begin(); - x != xacts.end(); - x++) { - if (! cutoff || (*x)->date() < cutoff) { - switch ((*x)->state) { - case transaction_t::CLEARED: - cleared_balance += (*x)->amount; - break; - case transaction_t::UNCLEARED: - case transaction_t::PENDING: - pending_balance += (*x)->amount; - *last_ptr = *x; - last_ptr = xact_next_ptr(*x); - break; - } - } - } - - if (cleared_balance.type >= value_t::BALANCE) - throw new error("Cannot reconcile accounts with multiple commodities"); - - cleared_balance.cast(value_t::AMOUNT); - balance.cast(value_t::AMOUNT); - - commodity_t& cb_comm = ((amount_t *) cleared_balance.data)->commodity(); - commodity_t& b_comm = ((amount_t *) balance.data)->commodity(); - - balance -= cleared_balance; - if (balance.type >= value_t::BALANCE) - throw new error(std::string("Reconcile balance is not of the same commodity ('") + - b_comm.symbol() + "' != '" + cb_comm.symbol() + "')"); - - // If the amount to reconcile is the same as the pending balance, - // then assume an exact match and return the results right away. - amount_t to_reconcile = *((amount_t *) balance.data); - pending_balance.cast(value_t::AMOUNT); - if (to_reconcile == *((amount_t *) pending_balance.data) || - search_for_balance(to_reconcile, &first, first)) { - push_to_handler(first); - } else { - throw new error("Could not reconcile account!"); - } -} - -} // namespace ledger diff --git a/reconcile.h b/reconcile.h index 7fd0d581..e69de29b 100644 --- a/reconcile.h +++ b/reconcile.h @@ -1,33 +0,0 @@ -#ifndef _RECONCILE_H -#define _RECONCILE_H - -#include "value.h" -#include "walk.h" - -namespace ledger { - -class reconcile_transactions : public item_handler<transaction_t> -{ - value_t balance; - datetime_t cutoff; - - transactions_list xacts; - - public: - reconcile_transactions(item_handler<transaction_t> * handler, - const value_t& _balance, - const datetime_t& _cutoff) - : item_handler<transaction_t>(handler), - balance(_balance), cutoff(_cutoff) {} - - void push_to_handler(transaction_t * first); - - virtual void flush(); - virtual void operator()(transaction_t& xact) { - xacts.push_back(&xact); - } -}; - -} // namespace ledger - -#endif // _RECONCILE_H @@ -1,413 +1,213 @@ +#ifdef USE_PCH +#include "pch.h" +#else #include "report.h" +#include "transform.h" +#include "util.h" +#endif namespace ledger { -report_t::report_t() +report_t::~report_t() { - ledger::amount_expr = "@a"; - ledger::total_expr = "@O"; - - predicate = ""; - secondary_predicate = ""; - display_predicate = ""; - descend_expr = ""; - - budget_flags = BUDGET_NO_BUDGET; - - head_entries = 0; - tail_entries = 0; - - show_collapsed = false; - show_subtotal = false; - show_totals = false; - show_related = false; - show_all_related = false; - show_inverted = false; - show_empty = false; - days_of_the_week = false; - by_payee = false; - comm_as_payee = false; - code_as_payee = false; - show_revalued = false; - show_revalued_only = false; - keep_price = false; - keep_date = false; - keep_tag = false; - entry_sort = false; - sort_all = false; + for (std::list<transform_t *>::const_iterator i = transforms.begin(); + i != transforms.end(); + i++) + delete *i; } -void -report_t::regexps_to_predicate(const std::string& command, - std::list<std::string>::const_iterator begin, - std::list<std::string>::const_iterator end, - const bool account_regexp, - const bool add_account_short_masks, - const bool logical_and) +void report_t::apply_transforms(xml::document_t * document) { - std::string regexps[2]; + for (std::list<transform_t *>::const_iterator i = transforms.begin(); + i != transforms.end(); + i++) + (*i)->execute(document); +} - assert(begin != end); +void report_t::abbrev(value_t& result, xml::xpath_t::scope_t * locals) +{ + if (locals->args.size() < 2) + throw new error("usage: abbrev(STRING, WIDTH [, STYLE, ABBREV_LEN])"); - // Treat the remaining command-line arguments as regular - // expressions, used for refining report results. + std::string str = locals->args[0].to_string(); + long wid = locals->args[1]; - for (std::list<std::string>::const_iterator i = begin; - i != end; - i++) - if ((*i)[0] == '-') { - if (! regexps[1].empty()) - regexps[1] += "|"; - regexps[1] += (*i).substr(1); - } - else if ((*i)[0] == '+') { - if (! regexps[0].empty()) - regexps[0] += "|"; - regexps[0] += (*i).substr(1); - } - else { - if (! regexps[0].empty()) - regexps[0] += "|"; - regexps[0] += *i; - } + elision_style_t style = session->elision_style; + if (locals->args.size() == 3) + style = (elision_style_t)locals->args[2].to_integer(); - for (int i = 0; i < 2; i++) { - if (regexps[i].empty()) - continue; + long abbrev_len = session->abbrev_length; + if (locals->args.size() == 4) + abbrev_len = locals->args[3].to_integer(); - if (! predicate.empty()) - predicate += logical_and ? "&" : "|"; + result.set_string(abbreviate(str, wid, style, true, (int)abbrev_len)); +} - int add_predicate = 0; // 1 adds /.../, 2 adds ///.../ - if (i == 1) { - predicate += "!"; - } - else if (add_account_short_masks) { - if (regexps[i].find(':') != std::string::npos || - regexps[i].find('.') != std::string::npos || - regexps[i].find('*') != std::string::npos || - regexps[i].find('+') != std::string::npos || - regexps[i].find('[') != std::string::npos || - regexps[i].find('(') != std::string::npos) { - show_subtotal = true; - add_predicate = 1; - } else { - add_predicate = 2; - } - } - else { - add_predicate = 1; - } +void report_t::ftime(value_t& result, xml::xpath_t::scope_t * locals) +{ + if (locals->args.size() < 1) + throw new error("usage: ftime(DATE [, DATE_FORMAT])"); - if (i != 1 && command == "b" && account_regexp) { - if (! show_related && ! show_all_related) { - if (! display_predicate.empty()) - display_predicate += "&"; - if (! show_empty) - display_predicate += "T&"; - - if (add_predicate == 2) - display_predicate += "//"; - display_predicate += "/(?:"; - display_predicate += regexps[i]; - display_predicate += ")/"; - } - else if (! show_empty) { - if (! display_predicate.empty()) - display_predicate += "&"; - display_predicate += "T"; - } - } + datetime_t date = locals->args[0].to_datetime(); - if (! account_regexp) - predicate += "/"; - predicate += "/(?:"; - predicate += regexps[i]; - predicate += ")/"; - } + std::string date_format; + if (locals->args.size() == 2) + date_format = locals->args[1].to_string(); + else + date_format = datetime_t::output_format; + + result.set_string(date.to_string(date_format)); } -void report_t::process_options(const std::string& command, - strings_list::iterator arg, - strings_list::iterator args_end) +bool report_t::resolve(const std::string& name, value_t& result, + xml::xpath_t::scope_t * locals) { - // Configure some other options depending on report type + const char * p = name.c_str(); + switch (*p) { + case 'a': + if (name == "abbrev") { + abbrev(result, locals); + return true; + } + break; - if (command == "p" || command == "e" || command == "w") { - show_related = - show_all_related = true; - } - else if (command == "E") { - show_subtotal = true; - } - else if (show_related) { - if (command == "r") { - show_inverted = true; - } else { - show_subtotal = true; - show_all_related = true; + case 'f': + if (name == "ftime") { + ftime(result, locals); + return true; } + break; } - if (command != "b" && command != "r") - amount_t::keep_base = true; - - // Process remaining command-line arguments + return xml::xpath_t::scope_t::resolve(name, result, locals); +} - if (command != "e") { - // Treat the remaining command-line arguments as regular - // expressions, used for refining report results. +xml::xpath_t::op_t * report_t::lookup(const std::string& name) +{ + const char * p = name.c_str(); + switch (*p) { + case 'o': + if (std::strncmp(p, "option_", 7) == 0) { + p = p + 7; + switch (*p) { + case 'a': +#if 0 + if (std::strcmp(p, "accounts") == 0) + return MAKE_FUNCTOR(report_t, option_accounts); + else +#endif + if (std::strcmp(p, "amount") == 0) + return MAKE_FUNCTOR(report_t, option_amount); + break; - std::list<std::string>::iterator i = arg; - for (; i != args_end; i++) - if (*i == "--") + case 'b': + if (std::strcmp(p, "bar") == 0) + return MAKE_FUNCTOR(report_t, option_bar); break; - if (i != arg) - regexps_to_predicate(command, arg, i, true, - (command == "b" && ! show_subtotal && - display_predicate.empty())); - if (i != args_end && ++i != args_end) - regexps_to_predicate(command, i, args_end); - } +#if 0 + case 'c': + if (std::strcmp(p, "clean") == 0) + return MAKE_FUNCTOR(report_t, option_clean); + else if (std::strcmp(p, "compact") == 0) + return MAKE_FUNCTOR(report_t, option_compact); + break; +#endif + + case 'e': +#if 0 + if (std::strcmp(p, "entries") == 0) + return MAKE_FUNCTOR(report_t, option_entries); + else if (std::strcmp(p, "eval") == 0) + return MAKE_FUNCTOR(report_t, option_eval); + else if (std::strcmp(p, "exclude") == 0) + return MAKE_FUNCTOR(report_t, option_remove); +#endif + break; - // Setup the default value for the display predicate + case 'f': + if (std::strcmp(p, "foo") == 0) + return MAKE_FUNCTOR(report_t, option_foo); + else if (std::strcmp(p, "format") == 0) + return MAKE_FUNCTOR(report_t, option_format); + break; - if (display_predicate.empty()) { - if (command == "b") { - if (! show_empty) - display_predicate = "T"; - if (! show_subtotal) { - if (! display_predicate.empty()) - display_predicate += "&"; - display_predicate += "l<=1"; - } - } - else if (command == "E") { - display_predicate = "t"; - } - else if (command == "r" && ! show_empty) { - display_predicate = "a"; - } - } + case 'i': +#if 0 + if (std::strcmp(p, "include") == 0) + return MAKE_FUNCTOR(report_t, option_select); +#endif + break; - DEBUG_PRINT("ledger.config.predicates", "Predicate: " << predicate); - DEBUG_PRINT("ledger.config.predicates", "Display P: " << display_predicate); + case 'l': +#if 0 + if (! *(p + 1) || std::strcmp(p, "limit") == 0) + return MAKE_FUNCTOR(report_t, option_limit); +#endif + break; - // Setup the values of %t and %T, used in format strings +#if 0 + case 'm': + if (std::strcmp(p, "merge") == 0) + return MAKE_FUNCTOR(report_t, option_merge); + break; +#endif - if (! amount_expr.empty()) - ledger::amount_expr = amount_expr; - if (! total_expr.empty()) - ledger::total_expr = total_expr; + case 'r': +#if 0 + if (std::strcmp(p, "remove") == 0) + return MAKE_FUNCTOR(report_t, option_remove); +#endif + break; - // Now setup the various formatting strings +#if 0 + case 's': + if (std::strcmp(p, "select") == 0) + return MAKE_FUNCTOR(report_t, option_select); + else if (std::strcmp(p, "split") == 0) + return MAKE_FUNCTOR(report_t, option_split); + break; +#endif - if (! date_output_format.empty()) - date_t::output_format = date_output_format; + case 't': + if (! *(p + 1)) + return MAKE_FUNCTOR(report_t, option_amount); + else if (std::strcmp(p, "total") == 0) + return MAKE_FUNCTOR(report_t, option_total); + break; - amount_t::keep_price = keep_price; - amount_t::keep_date = keep_date; - amount_t::keep_tag = keep_tag; + case 'T': + if (! *(p + 1)) + return MAKE_FUNCTOR(report_t, option_total); + break; + } + } + break; + } - if (! report_period.empty() && ! sort_all) - entry_sort = true; + return xml::xpath_t::scope_t::lookup(name); } -item_handler<transaction_t> * -report_t::chain_xact_handlers(const std::string& command, - item_handler<transaction_t> * base_formatter, - journal_t * journal, - account_t * master, - std::list<item_handler<transaction_t> *>& ptrs) -{ - bool remember_components = false; - - item_handler<transaction_t> * formatter = NULL; - - ptrs.push_back(formatter = base_formatter); - - // format_transactions write each transaction received to the - // output stream. - if (! (command == "b" || command == "E")) { - // truncate_entries cuts off a certain number of _entries_ from - // being displayed. It does not affect calculation. - if (head_entries || tail_entries) - ptrs.push_back(formatter = - new truncate_entries(formatter, - head_entries, tail_entries)); - - // filter_transactions will only pass through transactions - // matching the `display_predicate'. - if (! display_predicate.empty()) - ptrs.push_back(formatter = - new filter_transactions(formatter, - display_predicate)); - - // calc_transactions computes the running total. When this - // appears will determine, for example, whether filtered - // transactions are included or excluded from the running total. - ptrs.push_back(formatter = new calc_transactions(formatter)); - - // component_transactions looks for reported transaction that - // match the given `descend_expr', and then reports the - // transactions which made up the total for that reported - // transaction. - if (! descend_expr.empty()) { - std::list<std::string> descend_exprs; - - std::string::size_type beg = 0; - for (std::string::size_type pos = descend_expr.find(';'); - pos != std::string::npos; - beg = pos + 1, pos = descend_expr.find(';', beg)) - descend_exprs.push_back(std::string(descend_expr, beg, pos - beg)); - descend_exprs.push_back(std::string(descend_expr, beg)); - - for (std::list<std::string>::reverse_iterator i = - descend_exprs.rbegin(); - i != descend_exprs.rend(); - i++) - ptrs.push_back(formatter = - new component_transactions(formatter, *i)); - - remember_components = true; - } - - // reconcile_transactions will pass through only those - // transactions which can be reconciled to a given balance - // (calculated against the transactions which it receives). - if (! reconcile_balance.empty()) { - datetime_t cutoff = datetime_t::now; - if (! reconcile_date.empty()) - cutoff = reconcile_date; - ptrs.push_back(formatter = - new reconcile_transactions - (formatter, value_t(reconcile_balance), cutoff)); - } +} // namespace ledger - // filter_transactions will only pass through transactions - // matching the `secondary_predicate'. - if (! secondary_predicate.empty()) - ptrs.push_back(formatter = - new filter_transactions(formatter, - secondary_predicate)); - - // sort_transactions will sort all the transactions it sees, based - // on the `sort_order' value expression. - if (! sort_string.empty()) { - if (entry_sort) - ptrs.push_back(formatter = - new sort_entries(formatter, sort_string)); - else - ptrs.push_back(formatter = - new sort_transactions(formatter, sort_string)); - } +#ifdef USE_BOOST_PYTHON - // changed_value_transactions adds virtual transactions to the - // list to account for changes in market value of commodities, - // which otherwise would affect the running total unpredictably. - if (show_revalued) - ptrs.push_back(formatter = - new changed_value_transactions(formatter, - show_revalued_only)); - - // collapse_transactions causes entries with multiple transactions - // to appear as entries with a subtotaled transaction for each - // commodity used. - if (show_collapsed) - ptrs.push_back(formatter = new collapse_transactions(formatter)); - - // subtotal_transactions combines all the transactions it receives - // into one subtotal entry, which has one transaction for each - // commodity in each account. - // - // period_transactions is like subtotal_transactions, but it - // subtotals according to time periods rather than totalling - // everything. - // - // dow_transactions is like period_transactions, except that it - // reports all the transactions that fall on each subsequent day - // of the week. - if (show_subtotal) - ptrs.push_back(formatter = - new subtotal_transactions(formatter, remember_components)); - - if (days_of_the_week) - ptrs.push_back(formatter = - new dow_transactions(formatter, remember_components)); - else if (by_payee) - ptrs.push_back(formatter = - new by_payee_transactions(formatter, remember_components)); - - // interval_transactions groups transactions together based on a - // time period, such as weekly or monthly. - if (! report_period.empty()) { - ptrs.push_back(formatter = - new interval_transactions(formatter, report_period, - remember_components)); - ptrs.push_back(formatter = new sort_transactions(formatter, "d")); - } - } +#ifndef USE_PCH +#include <boost/python.hpp> +#endif - // invert_transactions inverts the value of the transactions it - // receives. - if (show_inverted) - ptrs.push_back(formatter = new invert_transactions(formatter)); - - // related_transactions will pass along all transactions related - // to the transaction received. If `show_all_related' is true, - // then all the entry's transactions are passed; meaning that if - // one transaction of an entry is to be printed, all the - // transaction for that entry will be printed. - if (show_related) - ptrs.push_back(formatter = - new related_transactions(formatter, - show_all_related)); - - // This filter_transactions will only pass through transactions - // matching the `predicate'. - if (! predicate.empty()) - ptrs.push_back(formatter = new filter_transactions(formatter, predicate)); - - // budget_transactions takes a set of transactions from a data - // file and uses them to generate "budget transactions" which - // balance against the reported transactions. - // - // forecast_transactions is a lot like budget_transactions, except - // that it adds entries only for the future, and does not balance - // them against anything but the future balance. - - if (budget_flags) { - budget_transactions * handler - = new budget_transactions(formatter, budget_flags); - handler->add_period_entries(journal->period_entries); - ptrs.push_back(formatter = handler); - - // Apply this before the budget handler, so that only matching - // transactions are calculated toward the budget. The use of - // filter_transactions above will further clean the results so - // that no automated transactions that don't match the filter get - // reported. - if (! predicate.empty()) - ptrs.push_back(formatter = new filter_transactions(formatter, predicate)); - } - else if (! forecast_limit.empty()) { - forecast_transactions * handler - = new forecast_transactions(formatter, forecast_limit); - handler->add_period_entries(journal->period_entries); - ptrs.push_back(formatter = handler); - - // See above, under budget_transactions. - if (! predicate.empty()) - ptrs.push_back(formatter = new filter_transactions(formatter, predicate)); - } +using namespace boost::python; +using namespace ledger; - if (comm_as_payee) - ptrs.push_back(formatter = new set_comm_as_payee(formatter)); - else if (code_as_payee) - ptrs.push_back(formatter = new set_code_as_payee(formatter)); +void export_report() +{ + class_< report_t > ("Report") + .add_property("session", + make_getter(&report_t::session, + return_value_policy<reference_existing_object>())) - return formatter; + .def("apply_transforms", &report_t::apply_transforms) + ; } -} // namespace ledger +#endif // USE_BOOST_PYTHON @@ -1,79 +1,143 @@ #ifndef _REPORT_H #define _REPORT_H -#include "ledger.h" -#include "timing.h" +#include "session.h" +#include "transform.h" -#include <iostream> -#include <memory> +#include <string> #include <list> namespace ledger { -class report_t +typedef std::list<std::string> strings_list; + +class report_t : public xml::xpath_t::scope_t { public: std::string output_file; - std::string predicate; - std::string secondary_predicate; - std::string display_predicate; - std::string report_period; - std::string report_period_sort; std::string format_string; - std::string sort_string; std::string amount_expr; std::string total_expr; - std::string descend_expr; - std::string forecast_limit; - std::string reconcile_balance; - std::string reconcile_date; std::string date_output_format; unsigned long budget_flags; - int head_entries; - int tail_entries; + std::string account; + std::string pager; - bool show_collapsed; - bool show_subtotal; bool show_totals; - bool show_related; - bool show_all_related; - bool show_inverted; - bool show_empty; - bool days_of_the_week; - bool by_payee; - bool comm_as_payee; - bool code_as_payee; - bool show_revalued; - bool show_revalued_only; - bool keep_price; - bool keep_date; - bool keep_tag; - bool entry_sort; - bool sort_all; - - report_t(); - - void regexps_to_predicate(const std::string& command, - std::list<std::string>::const_iterator begin, - std::list<std::string>::const_iterator end, - const bool account_regexp = false, - const bool add_account_short_masks = false, - const bool logical_and = true); - - void process_options(const std::string& command, - strings_list::iterator arg, - strings_list::iterator args_end); - - item_handler<transaction_t> * - chain_xact_handlers(const std::string& command, - item_handler<transaction_t> * base_formatter, - journal_t * journal, - account_t * master, - std::list<item_handler<transaction_t> *>& ptrs); + bool raw_mode; + + session_t * session; + transform_t * last_transform; + + std::list<transform_t *> transforms; + + report_t(session_t * _session) + : xml::xpath_t::scope_t(_session), + show_totals(false), + raw_mode(false), + session(_session), + last_transform(NULL) + { + eval("t=total,TOT=0,T()=(TOT=TOT+t,TOT)"); + } + + virtual ~report_t(); + + void apply_transforms(xml::document_t * document); + + // + // Utility functions for value expressions + // + + void ftime(value_t& result, xml::xpath_t::scope_t * locals); + void abbrev(value_t& result, xml::xpath_t::scope_t * locals); + + // + // Config options + // + + void eval(const std::string& expr) { + xml::xpath_t(expr).compile((xml::document_t *)NULL, this); + } + void option_eval(value_t&, xml::xpath_t::scope_t * locals) { + eval(locals->args[0].to_string()); + } + + void option_amount(value_t&, xml::xpath_t::scope_t * locals) { + eval(std::string("t=") + locals->args[0].to_string()); + } + void option_total(value_t&, xml::xpath_t::scope_t * locals) { + eval(std::string("T()=") + locals->args[0].to_string()); + } + + void option_format(value_t&, xml::xpath_t::scope_t * locals) { + format_string = locals->args[0].to_string(); + } + + void option_raw(value_t&) { + raw_mode = true; + } + + void option_foo(value_t&) { + std::cout << "This is foo" << std::endl; + } + void option_bar(value_t&, xml::xpath_t::scope_t * locals) { + std::cout << "This is bar: " << locals->args[0] << std::endl; + } + + // + // Transform options + // + +#if 0 + void option_select(value_t&, xml::xpath_t::scope_t * locals) { + transforms.push_back(new select_transform(locals->args[0].to_string())); + } + void option_limit(value_t&, xml::xpath_t::scope_t * locals) { + std::string expr = (std::string("//xact[") + + locals->args[0].to_string() + "]"); + transforms.push_back(new select_transform(expr)); + } + + void option_remove(value_t&, xml::xpath_t::scope_t * locals) { + transforms.push_back(new remove_transform(locals->args[0].to_string())); + } + + void option_accounts(value_t&) { + transforms.push_back(new accounts_transform); + } + void option_compact(value_t&) { + transforms.push_back(new compact_transform); + } + void option_clean(value_t&) { + transforms.push_back(new clean_transform); + } + void option_entries(value_t&) { + transforms.push_back(new entries_transform); + } + + void option_split(value_t&) { + transforms.push_back(new split_transform); + } + void option_merge(value_t&) { + transforms.push_back(new merge_transform); + } +#endif + + // + // Scope members + // + + virtual bool resolve(const std::string& name, value_t& result, + xml::xpath_t::scope_t * locals); + virtual xml::xpath_t::op_t * lookup(const std::string& name); }; +std::string abbrev(const std::string& str, unsigned int width, + const bool is_account); + } // namespace ledger #endif // _REPORT_H @@ -1,11 +1,11 @@ -= /^Expenses:Books/ - (Liabilities:Taxes) -0.10 +;= acct =~ /^Expenses:Books/ +; (Liabilities:Taxes) -0.10 ~ Monthly Assets:Bank:Checking $500.00 Income:Salary -2004/05/01 * Checking balance +2004/05/01 * (22:15) Checking balance Assets:Bank:Checking $1,000.00 Equity:Opening Balances diff --git a/session.cc b/session.cc new file mode 100644 index 00000000..d952142d --- /dev/null +++ b/session.cc @@ -0,0 +1,239 @@ +#ifdef USE_PCH +#include "pch.h" +#else +#include "session.h" + +#include <fstream> +#endif + +namespace ledger { + +unsigned int session_t::read_journal(std::istream& in, + journal_t * journal, + account_t * master, + const std::string * original_file) +{ + if (! master) + master = journal->master; + +#if 0 + journal->data = repitem_t::wrap(journal); +#endif + + for (std::list<parser_t *>::iterator i = parsers.begin(); + i != parsers.end(); + i++) + if ((*i)->test(in)) + return (*i)->parse(in, journal, master, original_file); + + return 0; +} + +unsigned int session_t::read_journal(const std::string& path, + journal_t * journal, + account_t * master, + const std::string * original_file) +{ + journal->sources.push_back(path); + + if (access(path.c_str(), R_OK) == -1) + throw new error(std::string("Cannot read file '") + path + "'"); + + if (! original_file) + original_file = &path; + + std::ifstream stream(path.c_str()); + return read_journal(stream, journal, master, original_file); +} + +void session_t::read_init() +{ + if (init_file.empty()) + return; + + if (access(init_file.c_str(), R_OK) == -1) + throw new error(std::string("Cannot read init file '") + init_file + "'"); + + std::ifstream init(init_file.c_str()); + + // jww (2006-09-15): Read initialization options here! +} + +journal_t * session_t::read_data(const std::string& master_account) +{ + TRACE_PUSH(parser, "Parsing journal file"); + + journal_t * journal = new_journal(); + journal->document = new xml::document_t; + journal->document->top = xml::wrap_node(journal->document, journal); + + unsigned int entry_count = 0; + + DEBUG_PRINT("ledger.cache", + "3. use_cache = " << use_cache); + + if (use_cache && ! cache_file.empty() && + ! data_file.empty()) { + DEBUG_PRINT("ledger.cache", + "using_cache " << cache_file); + cache_dirty = true; + if (access(cache_file.c_str(), R_OK) != -1) { + std::ifstream stream(cache_file.c_str()); + + std::string price_db_orig = journal->price_db; + journal->price_db = price_db; + entry_count += read_journal(stream, journal, NULL, + &data_file); + if (entry_count > 0) + cache_dirty = false; + else + journal->price_db = price_db_orig; + } + } + + if (entry_count == 0 && ! data_file.empty()) { + account_t * acct = NULL; + if (! master_account.empty()) + acct = journal->find_account(master_account); + + journal->price_db = price_db; + if (! journal->price_db.empty() && + access(journal->price_db.c_str(), R_OK) != -1) { + if (read_journal(journal->price_db, journal)) { + throw new error("Entries not allowed in price history file"); + } else { + DEBUG_PRINT("ledger.cache", + "read price database " << journal->price_db); + journal->sources.pop_back(); + } + } + + DEBUG_PRINT("ledger.cache", + "rejected cache, parsing " << data_file); + if (data_file == "-") { + use_cache = false; + journal->sources.push_back("<stdin>"); + entry_count += read_journal(std::cin, journal, acct); + } + else if (access(data_file.c_str(), R_OK) != -1) { + entry_count += read_journal(data_file, journal, acct); + if (! journal->price_db.empty()) + journal->sources.push_back(journal->price_db); + } + } + + VALIDATE(journal->valid()); + + if (entry_count == 0) + throw new error("Failed to locate any journal entries; " + "did you specify a valid file with -f?"); + + TRACE_POP(parser, "Finished parsing"); + + return journal; +} + +bool session_t::resolve(const std::string& name, value_t& result, + xml::xpath_t::scope_t * locals) +{ + const char * p = name.c_str(); + switch (*p) { + case 'd': + if (name == "date_format") { + result.set_string(datetime_t::output_format); + return true; + } + break; + + case 'n': + switch (*++p) { + case 'o': + if (name == "now") { + result = now; + return true; + } + break; + } + break; + + case 'r': + if (name == "register_format") { + result = register_format; + return true; + } + break; + } + + return xml::xpath_t::scope_t::resolve(name, result, locals); +} + +xml::xpath_t::op_t * session_t::lookup(const std::string& name) +{ + const char * p = name.c_str(); + switch (*p) { + case 'o': + if (std::strncmp(p, "option_", 7) == 0) { + p = p + 7; + switch (*p) { + case 'f': + if (! *(p + 1) || std::strcmp(p, "file") == 0) + return MAKE_FUNCTOR(session_t, option_file); + break; + + case 'v': + if (std::strcmp(p, "verbose") == 0) + return MAKE_FUNCTOR(session_t, option_verbose); + break; + } + } + break; + } + + return xml::xpath_t::scope_t::lookup(name); +} + +} // namespace ledger + +#ifdef USE_BOOST_PYTHON + +#ifndef USE_PCH +#include <boost/python.hpp> +#endif + +using namespace boost::python; +using namespace ledger; + +void export_session() +{ + class_< session_t > ("Session") + .def_readwrite("init_file", &session_t::init_file) + .def_readwrite("data_file", &session_t::data_file) + .def_readwrite("cache_file", &session_t::cache_file) + .def_readwrite("price_db", &session_t::price_db) + + .def_readwrite("balance_format", &session_t::balance_format) + .def_readwrite("register_format", &session_t::register_format) + .def_readwrite("wide_register_format", &session_t::wide_register_format) + .def_readwrite("plot_amount_format", &session_t::plot_amount_format) + .def_readwrite("plot_total_format", &session_t::plot_total_format) + .def_readwrite("print_format", &session_t::print_format) + .def_readwrite("write_hdr_format", &session_t::write_hdr_format) + .def_readwrite("write_xact_format", &session_t::write_xact_format) + .def_readwrite("equity_format", &session_t::equity_format) + .def_readwrite("prices_format", &session_t::prices_format) + .def_readwrite("pricesdb_format", &session_t::pricesdb_format) + + .def_readwrite("pricing_leeway", &session_t::pricing_leeway) + + .def_readwrite("download_quotes", &session_t::download_quotes) + .def_readwrite("use_cache", &session_t::use_cache) + .def_readwrite("cache_dirty", &session_t::cache_dirty) + .def_readwrite("debug_mode", &session_t::debug_mode) + .def_readwrite("verbose_mode", &session_t::verbose_mode) + .def_readwrite("trace_mode", &session_t::trace_mode) + + .def_readwrite("journals", &session_t::journals) + ; +} + +#endif // USE_BOOST_PYTHON diff --git a/session.h b/session.h new file mode 100644 index 00000000..02da25d8 --- /dev/null +++ b/session.h @@ -0,0 +1,185 @@ +#ifndef _SESSION_H +#define _SESSION_H + +#include "journal.h" +#include "parser.h" + +#include <list> + +namespace ledger { + +class session_t : public xml::xpath_t::scope_t +{ + public: + std::string init_file; + std::string data_file; + std::string cache_file; + std::string price_db; + + std::string register_format; + std::string wide_register_format; + std::string print_format; + std::string balance_format; + std::string equity_format; + std::string plot_amount_format; + std::string plot_total_format; + std::string write_hdr_format; + std::string write_xact_format; + std::string prices_format; + std::string pricesdb_format; + + unsigned long pricing_leeway; + + bool download_quotes; + bool use_cache; + bool cache_dirty; + bool debug_mode; + bool verbose_mode; + bool trace_mode; + + datetime_t now; + + elision_style_t elision_style; + + int abbrev_length; + + bool ansi_codes; + bool ansi_invert; + + std::list<journal_t *> journals; + std::list<parser_t *> parsers; + + session_t(xml::xpath_t::scope_t * parent = NULL) : + xml::xpath_t::scope_t(parent), + + register_format + ("%((//entry)%{date} %-.20{payee}" + "%((./xact)%32|%-22{abbrev(account, 22)} %12.67t %12.80T\n))"), + wide_register_format + ("%D %-.35P %-.38A %22.108t %!22.132T\n%/" + "%48|%-.38A %22.108t %!22.132T\n"), + print_format +#if 1 + ("%(/%(/%{date} %-.20{payee}\n%(: %-34{account} %12t\n)\n))"), +#else + ("\n%d %Y%C%P\n %-34W %12o%n\n%/ %-34W %12o%n\n"), +#endif + balance_format + ("%(/%(//%20t %{\" \" * rdepth}%{rname}\n))--------------------\n%20t\n"), + equity_format + ("%((/)%{ftime(now, date_format)} %-.20{\"Opening Balance\"}\n%((.//account[value != 0]) %-34{fullname} %12{value}\n)\n)"), + plot_amount_format + ("%D %(@S(@t))\n"), + plot_total_format + ("%D %(@S(@T))\n"), + write_hdr_format + ("%d %Y%C%P\n"), + write_xact_format + (" %-34W %12o%n\n"), + prices_format + ("%[%Y/%m/%d %H:%M:%S %Z] %-10A %12t %12T\n"), + pricesdb_format + ("P %[%Y/%m/%d %H:%M:%S] %A %t\n"), + + pricing_leeway(24 * 3600), + + download_quotes(false), + use_cache(false), + cache_dirty(false), + debug_mode(false), + verbose_mode(false), + trace_mode(false), + + now(datetime_t::now), + + elision_style(ABBREVIATE), + abbrev_length(2), + + ansi_codes(false), + ansi_invert(false) {} + + virtual ~session_t() { + for (std::list<journal_t *>::iterator i = journals.begin(); + i != journals.end(); + i++) + delete *i; + + for (std::list<parser_t *>::iterator i = parsers.begin(); + i != parsers.end(); + i++) + delete *i; + } + + journal_t * new_journal() { + journal_t * journal = new journal_t(this); + journals.push_back(journal); + return journal; + } + void close_journal(journal_t * journal) { + journals.remove(journal); + delete journal; + } + + unsigned int read_journal(std::istream& in, + journal_t * journal, + account_t * master = NULL, + const std::string * original_file = NULL); + + unsigned int read_journal(const std::string& path, + journal_t * journal, + account_t * master = NULL, + const std::string * original_file = NULL); + + void read_init(); + + journal_t * read_data(const std::string& master_account = ""); + + void register_parser(parser_t * parser) { + parsers.push_back(parser); + } + bool unregister_parser(parser_t * parser) { + std::list<parser_t *>::iterator i; + for (i = parsers.begin(); i != parsers.end(); i++) + if (*i == parser) + break; + if (i == parsers.end()) + return false; + + parsers.erase(i); + + return true; + } + + // + // Scope members + // + + virtual bool resolve(const std::string& name, value_t& result, + xml::xpath_t::scope_t * locals = NULL); + virtual xml::xpath_t::op_t * lookup(const std::string& name); + + // + // Option handlers + // + + void option_file(value_t&, xml::xpath_t::scope_t * locals) { + data_file = locals->args.to_string(); + } + + void option_verbose(value_t&) { + verbose_mode = true; + } + +#ifdef USE_BOOST_PYTHON + void option_import(value_t&) { + python_import(optarg); + } + void option_import_stdin(value_t&) { + python_eval(std::cin, PY_EVAL_MULTI); + } +#endif +}; + +} // namespace ledger + +#endif // _SESSION_H @@ -4,15 +4,27 @@ from distutils.core import setup, Extension import os -libs = ["amounts", "boost_python", "gmp"] +libs = ["ledger", "boost_python", "gmp", "pcre"] -setup(name = "Amounts", - version = "2.6", - description = "Amounts and Commodities Library", +if os.environ.has_key ("HAVE_EXPAT") and\ + os.environ["HAVE_EXPAT"] == "true": + libs.extend (["expat"]) + +if os.environ.has_key ("HAVE_XMLPARSE") and\ + os.environ["HAVE_XMLPARSE"] == "true": + libs.extend (["xmlparse", "xmltok"]) + +if os.environ.has_key ("HAVE_LIBOFX") and\ + os.environ["HAVE_LIBOFX"] == "true": + libs.extend (["ofx"]) + +setup(name = "Ledger", + version = "3.0", + description = "Ledger Accounting Library", author = "John Wiegley", author_email = "johnw@newartisans.com", - url = "http://www.newartisans.com/johnw/", + url = "http://johnwiegley.com/", ext_modules = [ - Extension("amounts", ["amounts.cc"], + Extension("ledger", ["pyledger.cc"], define_macros = [('PYTHON_MODULE', 1)], libraries = libs)]) diff --git a/tests/UnitTests.cc b/tests/UnitTests.cc new file mode 100644 index 00000000..ee9c163e --- /dev/null +++ b/tests/UnitTests.cc @@ -0,0 +1,111 @@ +#include <cppunit/CompilerOutputter.h> +#include <cppunit/TestResult.h> +#include <cppunit/TestResultCollector.h> +#include <cppunit/TestRunner.h> +#include <cppunit/TextTestProgressListener.h> +#include <cppunit/BriefTestProgressListener.h> +#include <cppunit/XmlOutputter.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include <stdexcept> +#include <fstream> + +#include "UnitTests.h" + + +// Create the CppUnit registry + +CPPUNIT_REGISTRY_ADD_TO_DEFAULT("Framework"); + +CPPUNIT_REGISTRY_ADD_TO_DEFAULT("corelib"); + +CPPUNIT_REGISTRY_ADD("numerics", "corelib"); +CPPUNIT_REGISTRY_ADD("balances", "corelib"); +CPPUNIT_REGISTRY_ADD("values", "corelib"); + +CPPUNIT_REGISTRY_ADD_TO_DEFAULT("driver"); +CPPUNIT_REGISTRY_ADD_TO_DEFAULT("journal"); +CPPUNIT_REGISTRY_ADD_TO_DEFAULT("reports"); +CPPUNIT_REGISTRY_ADD_TO_DEFAULT("transforms"); + + +// Create a sample test, which acts both as a template, and a +// verification that the basic framework is functioning. + +class UnitTests : public CPPUNIT_NS::TestCase +{ + CPPUNIT_TEST_SUITE( UnitTests ); + CPPUNIT_TEST( testInitialization ); + CPPUNIT_TEST_SUITE_END(); + +public: + UnitTests() {} + virtual ~UnitTests() {} + + virtual void setUp() {} + virtual void tearDown() {} + + void testInitialization() { + assertEquals(std::string("Hello, world!"), + std::string("Hello, world!")); + } + +private: + UnitTests( const UnitTests © ); + void operator =( const UnitTests © ); +}; + +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(UnitTests, "framework"); + + +// Create the various runners and commence running the tests! + +int main(int argc, char* argv[]) +{ + // Retreive test path from command line first argument. Default to + // "" which resolves to the top level suite. + std::string testPath = (argc > 1) ? std::string(argv[1]) : std::string(""); + + // Create the event manager and test controller + CPPUNIT_NS::TestResult controller; + + // Add a listener that collects test results + CPPUNIT_NS::TestResultCollector result; + controller.addListener(&result); + + // Add a listener that print dots as test run. +#if 1 + CPPUNIT_NS::TextTestProgressListener progress; +#else + CPPUNIT_NS::BriefTestProgressListener progress; +#endif + controller.addListener(&progress); + + // Add the top suite to the test runner + CPPUNIT_NS::TestRunner runner; + runner.addTest(CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest()); + try { + runner.run(controller, testPath); + + // Print test in a compiler compatible format. + CPPUNIT_NS::CompilerOutputter outputter(&result, CPPUNIT_NS::stdCOut()); + outputter.write(); + +#if 0 + // Uncomment this for XML output + std::ofstream file("tests.xml"); + CPPUNIT_NS::XmlOutputter xml(&result, file); + xml.setStyleSheet("report.xsl"); + xml.write(); + file.close(); +#endif + } + catch (std::invalid_argument &e) { // Test path not resolved + CPPUNIT_NS::stdCOut() << "\n" + << "ERROR: " << e.what() + << "\n"; + return 0; + } + + return result.wasSuccessful() ? 0 : 1; +} diff --git a/tests/UnitTests.h b/tests/UnitTests.h new file mode 100644 index 00000000..e97456b4 --- /dev/null +++ b/tests/UnitTests.h @@ -0,0 +1,14 @@ +#ifndef _UNITTESTS_H +#define _UNITTESTS_H + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/Exception.h> +#include <cppunit/Portability.h> + +#define assertDoublesEqual(x,y,z,w) CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(x,y,z,w) +#define assertEquals(x,y) CPPUNIT_ASSERT_EQUAL(x,y) +#define assertEqualsMessage(x,y,z) CPPUNIT_ASSERT_EQUAL_MESSAGE(x,y,z) +#define assertMessage(x,y) CPPUNIT_ASSERT_MESSAGE(x,y) +#define assertThrow(x,y) CPPUNIT_ASSERT_THROW(x,y) + +#endif /* _UNITTESTS_H */ diff --git a/tests/corelib/numerics/#BasicAmountTest.cc# b/tests/corelib/numerics/#BasicAmountTest.cc# new file mode 100644 index 00000000..acf2c45e --- /dev/null +++ b/tests/corelib/numerics/#BasicAmountTest.cc# @@ -0,0 +1,425 @@ +#include "BasicAmountTestCase.h" +#include "ledger.h" + +using namespace ledger; + +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(BasicAmountTestCase, "numerics"); + +void BasicAmountTestCase::setUp() {} +void BasicAmountTestCase::tearDown() {} + +void BasicAmountTestCase::testConstructors() +{ + amount_t x0; + amount_t x1(123456L); + amount_t x2(123456UL); + amount_t x3(123.456); + amount_t x4(true); + amount_t x5("123456"); + amount_t x6("123.456"); + amount_t x7(std::string("123456")); + amount_t x8(std::string("123.456")); + amount_t x9(x3); + amount_t x10(x6); + amount_t x11(x8); + + assertEqual(amount_t(0L), x0); + assertEqual(x2, x1); + assertEqual(x5, x1); + assertEqual(x7, x1); + assertEqual(x6, x3); + assertEqual(x8, x3); + assertEqual(x10, x3); + assertEqual(amount_t(1L), x4); + assertEqual(x10, x9); +} + +void BasicAmountTestCase::testNegation() +{ + amount_t x0; + amount_t x1(-123456L); + amount_t x3(-123.456); + amount_t x5("-123456"); + amount_t x6("-123.456"); + amount_t x7(std::string("-123456")); + amount_t x8(std::string("-123.456")); + amount_t x9(- x3); + + assertEqual(amount_t(0L), x0); + assertEqual(x5, x1); + assertEqual(x7, x1); + assertEqual(x6, x3); + assertEqual(x8, x3); + assertEqual(- x6, x9); + assertEqual(x3.negated(), x9); + + amount_t x10(x9); + x10.negate(); + + assertEqual(x3, x10); +} + +void BasicAmountTestCase::testAssignment() +{ + amount_t x0; + amount_t x1 = 123456L; + amount_t x2 = 123456UL; + amount_t x3 = 123.456; + amount_t x4 = true; + amount_t x5 = "123456"; + amount_t x6 = "123.456"; + amount_t x7 = std::string("123456"); + amount_t x8 = std::string("123.456"); + amount_t x9 = x3; + amount_t x10 = amount_t(x6); + + assertEqual(amount_t(0L), x0); + assertEqual(x2, x1); + assertEqual(x5, x1); + assertEqual(x7, x1); + assertEqual(x6, x3); + assertEqual(x8, x3); + assertEqual(x10, x3); + assertEqual(amount_t(1L), x4); + assertEqual(x10, x9); + + x0 = amount_t(); + x1 = 123456L; + x2 = 123456UL; + x3 = 123.456; + x4 = true; + x5 = "123456"; + x6 = "123.456"; + x7 = std::string("123456"); + x8 = std::string("123.456"); + x9 = x3; + x10 = amount_t(x6); + + assertEqual(amount_t(0L), x0); + assertEqual(x2, x1); + assertEqual(x5, x1); + assertEqual(x7, x1); + assertEqual(x6, x3); + assertEqual(x8, x3); + assertEqual(x10, x3); + assertEqual(amount_t(1L), x4); + assertEqual(x10, x9); +} + +void BasicAmountTestCase::testEquality() +{ + amount_t x1(123456L); + amount_t x2(456789L); + amount_t x3(333333L); + amount_t x4(123456.0); + amount_t x5("123456.0"); + amount_t x6(123456.0F); + + CPPUNIT_ASSERT(x1 == 123456L); + CPPUNIT_ASSERT(x1 != x2); + CPPUNIT_ASSERT(x1 == (x2 - x3)); + CPPUNIT_ASSERT(x1 == x4); + CPPUNIT_ASSERT(x4 == x5); + CPPUNIT_ASSERT(x4 == x6); +} + +void BasicAmountTestCase::testIntegerAddition() +{ + amount_t x1(123L); + amount_t y1(456L); + + assertEqual(amount_t(579L), x1 + y1); + assertEqual(amount_t(579L), x1 + 456L); + assertEqual(amount_t(579L), 456L + x1); + + x1 += amount_t(456L); + assertEqual(amount_t(579L), x1); + x1 += 456L; + assertEqual(amount_t(1035L), x1); + + amount_t x3(true); + amount_t y3(true); + + assertEqual(amount_t(2L), x3 + y3); + assertEqual(amount_t(2L), x3 + true); + + amount_t x4("123456789123456789123456789"); + + assertEqual(amount_t("246913578246913578246913578"), x4 + x4); +} + +void BasicAmountTestCase::testFractionalAddition() +{ + amount_t x1(123.123); + amount_t y1(456.456); + +<<<<<<< HEAD:tests/corelib/numerics/BasicAmountTest.cc + assertEquals(amount_t(579.579), x1 + y1); + assertEquals(amount_t(579.579), x1 + 456.456); +======= + assertEqual(amount_t(579.579), x1 + y1); + assertEqual(amount_t(579.579), x1 + 456.456); + assertEqual(amount_t(579.579), 456.456 + x1); +>>>>>>> d2f9bb7... Miscellaneous changes:tests/corelib/numerics/BasicAmountTest.cc + + x1 += amount_t(456.456); + assertEqual(amount_t(579.579), x1); + x1 += 456.456; + assertEqual(amount_t(1036.035), x1); + x1 += 456L; + assertEqual(amount_t(1492.035), x1); + + amount_t x2("123456789123456789.123456789123456789"); + + assertEqual(amount_t("246913578246913578.246913578246913578"), x2 + x2); +} + +void BasicAmountTestCase::testIntegerSubtraction() +{ + amount_t x1(123L); + amount_t y1(456L); + +<<<<<<< HEAD:tests/corelib/numerics/BasicAmountTest.cc + assertEquals(amount_t(333L), y1 - x1); + assertEquals(amount_t(-333L), x1 - y1); +======= + assertEqual(amount_t(333L), y1 - x1); + assertEqual(amount_t(-333L), x1 - y1); + assertEqual(amount_t(23L), x1 - 100L); + assertEqual(amount_t(-23L), 100L - x1); +>>>>>>> d2f9bb7... Miscellaneous changes:tests/corelib/numerics/BasicAmountTest.cc + + x1 -= amount_t(456L); + assertEqual(amount_t(-333L), x1); + x1 -= 456L; + assertEqual(amount_t(-789L), x1); + + amount_t x3(true); + amount_t y3(true); + + assertEqual(amount_t(false), x3 - y3); + + amount_t x4("123456789123456789123456789"); + amount_t y4("8238725986235986"); + + assertEqual(amount_t("123456789115218063137220803"), x4 - y4); + assertEqual(amount_t("-123456789115218063137220803"), y4 - x4); +} + +void BasicAmountTestCase::testFractionalSubtraction() +{ + amount_t x1(123.123); + amount_t y1(456.456); + + assertEqual(amount_t(-333.333), x1 - y1); + assertEqual(amount_t(333.333), y1 - x1); + + x1 -= amount_t(456.456); + assertEqual(amount_t(-333.333), x1); + x1 -= 456.456; + assertEqual(amount_t(-789.789), x1); + x1 -= 456L; + assertEqual(amount_t(-1245.789), x1); + + amount_t x2("123456789123456789.123456789123456789"); + amount_t y2("9872345982459.248974239578"); + + assertEqual(amount_t("123446916777474329.874482549545456789"), x2 - y2); + assertEqual(amount_t("-123446916777474329.874482549545456789"), y2 - x2); +} + +void BasicAmountTestCase::testIntegerMultiplication() +{ + amount_t x1(123L); + amount_t y1(456L); + +<<<<<<< HEAD:tests/corelib/numerics/BasicAmountTest.cc + assertEquals(amount_t(0L), x1 * 0L); + assertEquals(amount_t(0L), amount_t(0L) * x1); + assertEquals(x1, x1 * 1L); + assertEquals(x1, amount_t(1L) * x1); + assertEquals(- x1, x1 * -1L); + assertEquals(- x1, amount_t(-1L) * x1); + assertEquals(amount_t(56088L), x1 * y1); + assertEquals(amount_t(56088L), y1 * x1); + assertEquals(amount_t(56088L), x1 * 456L); + assertEquals(amount_t(56088L), amount_t(456L) * x1); +======= + assertEqual(amount_t(0L), x1 * 0L); + assertEqual(amount_t(0L), amount_t(0L) * x1); + assertEqual(amount_t(0L), 0L * x1); + assertEqual(x1, x1 * 1L); + assertEqual(x1, amount_t(1L) * x1); + assertEqual(x1, 1L * x1); + assertEqual(- x1, x1 * -1L); + assertEqual(- x1, amount_t(-1L) * x1); + assertEqual(- x1, -1L * x1); + assertEqual(amount_t(56088L), x1 * y1); + assertEqual(amount_t(56088L), y1 * x1); + assertEqual(amount_t(56088L), x1 * 456L); + assertEqual(amount_t(56088L), amount_t(456L) * x1); + assertEqual(amount_t(56088L), 456L * x1); +>>>>>>> d2f9bb7... Miscellaneous changes:tests/corelib/numerics/BasicAmountTest.cc + + x1 *= amount_t(123L); + assertEqual(amount_t(15129L), x1); + x1 *= 123L; + assertEqual(amount_t(1860867L), x1); + + amount_t x3(true); + amount_t y3(true); + + assertEqual(amount_t(true), x3 * y3); + + amount_t x4("123456789123456789123456789"); + + assertEqual(amount_t("15241578780673678546105778281054720515622620750190521"), + x4 * x4); +} + +void BasicAmountTestCase::testFractionalMultiplication() +{ + amount_t x1(123.123); + amount_t y1(456.456); + +<<<<<<< HEAD:tests/corelib/numerics/BasicAmountTest.cc + assertEquals(amount_t(0L), x1 * 0L); + assertEquals(amount_t(0L), amount_t(0L) * x1); + assertEquals(x1, x1 * 1L); + assertEquals(x1, amount_t(1L) * x1); + assertEquals(- x1, x1 * -1L); + assertEquals(- x1, amount_t(-1L) * x1); + assertEquals(amount_t("56200.232088"), x1 * y1); + assertEquals(amount_t("56200.232088"), y1 * x1); + assertEquals(amount_t("56200.232088"), x1 * 456.456); + assertEquals(amount_t("56200.232088"), amount_t(456.456) * x1); +======= + assertEqual(amount_t(0L), x1 * 0L); + assertEqual(amount_t(0L), amount_t(0L) * x1); + assertEqual(amount_t(0L), 0L * x1); + assertEqual(x1, x1 * 1L); + assertEqual(x1, amount_t(1L) * x1); + assertEqual(x1, 1L * x1); + assertEqual(- x1, x1 * -1L); + assertEqual(- x1, amount_t(-1L) * x1); + assertEqual(- x1, -1L * x1); + assertEqual(amount_t("56200.232088"), x1 * y1); + assertEqual(amount_t("56200.232088"), y1 * x1); + assertEqual(amount_t("56200.232088"), x1 * 456.456); + assertEqual(amount_t("56200.232088"), amount_t(456.456) * x1); + assertEqual(amount_t("56200.232088"), 456.456 * x1); +>>>>>>> d2f9bb7... Miscellaneous changes:tests/corelib/numerics/BasicAmountTest.cc + + x1 *= amount_t(123.123); + assertEqual(amount_t("15159.273129"), x1); + x1 *= 123.123; + assertEqual(amount_t("1866455.185461867"), x1); + x1 *= 123L; + assertEqual(amount_t("229573987.811809641"), x1); + + amount_t x2("123456789123456789.123456789123456789"); + + assertEqual(amount_t("15241578780673678546105778311537878.046486820281054720515622620750190521"), + x2 * x2); +} + +void BasicAmountTestCase::testIntegerDivision() +{ + amount_t x1(123L); + amount_t y1(456L); + + assertThrow(x1 / 0L, amount_error *); +<<<<<<< HEAD:tests/corelib/numerics/BasicAmountTest.cc + assertEquals(amount_t(0L), amount_t(0L) / x1); + assertEquals(x1, x1 / 1L); + assertEquals(amount_t("0.008130"), amount_t(1L) / x1); + assertEquals(- x1, x1 / -1L); + assertEquals(- amount_t("0.008130"), amount_t(-1L) / x1); + assertEquals(amount_t("0.269736"), x1 / y1); + assertEquals(amount_t("3.707317"), y1 / x1); + assertEquals(amount_t("0.269736"), x1 / 456L); + assertEquals(amount_t("3.707317"), amount_t(456L) / x1); +======= + assertEqual(amount_t(0L), amount_t(0L) / x1); + assertEqual(amount_t(0L), 0L / x1); + assertEqual(x1, x1 / 1L); + assertEqual(amount_t("0.008130"), amount_t(1L) / x1); + assertEqual(amount_t("0.008130"), 1L / x1); + assertEqual(- x1, x1 / -1L); + assertEqual(- amount_t("0.008130"), amount_t(-1L) / x1); + assertEqual(- amount_t("0.008130"), -1L / x1); + assertEqual(amount_t("0.269736"), x1 / y1); + assertEqual(amount_t("3.707317"), y1 / x1); + assertEqual(amount_t("0.269736"), x1 / 456L); + assertEqual(amount_t("3.707317"), amount_t(456L) / x1); + assertEqual(amount_t("3.707317"), 456L / x1); +>>>>>>> d2f9bb7... Miscellaneous changes:tests/corelib/numerics/BasicAmountTest.cc + + x1 /= amount_t(456L); + assertEqual(amount_t("0.269736"), x1); + x1 /= 456L; + assertEqual(amount_t("0.000591526315789473"), x1); + + amount_t x4("123456789123456789123456789"); + amount_t y4("56"); + + assertEqual(amount_t(1L), x4 / x4); + assertEqual(amount_t("2204585520061728377204585.517857"), x4 / y4); +} + +void BasicAmountTestCase::testFractionalDivision() +{ + amount_t x1(123.123); + amount_t y1(456.456); + + assertThrow(x1 / 0L, amount_error *); +<<<<<<< HEAD:tests/corelib/numerics/BasicAmountTest.cc + assertEquals(amount_t("0.008121"), amount_t(1.0) / x1); + assertEquals(x1, x1 / 1.0); + assertEquals(amount_t("0.008121"), amount_t(1.0) / x1); + assertEquals(- x1, x1 / -1.0); + assertEquals(- amount_t("0.008121"), amount_t(-1.0) / x1); + assertEquals(amount_t("0.269736842105"), x1 / y1); + assertEquals(amount_t("3.707317073170"), y1 / x1); + assertEquals(amount_t("0.269736842105"), x1 / 456.456); + assertEquals(amount_t("3.707317073170"), amount_t(456.456) / x1); +======= + assertEqual(amount_t("0.008121"), amount_t(1.0) / x1); + assertEqual(amount_t("0.008121"), 1.0 / x1); + assertEqual(x1, x1 / 1.0); + assertEqual(amount_t("0.008121"), amount_t(1.0) / x1); + assertEqual(amount_t("0.008121"), 1.0 / x1); + assertEqual(- x1, x1 / -1.0); + assertEqual(- amount_t("0.008121"), amount_t(-1.0) / x1); + assertEqual(- amount_t("0.008121"), -1.0 / x1); + assertEqual(amount_t("0.269736842105"), x1 / y1); + assertEqual(amount_t("3.707317073170"), y1 / x1); + assertEqual(amount_t("0.269736842105"), x1 / 456.456); + assertEqual(amount_t("3.707317073170"), amount_t(456.456) / x1); + assertEqual(amount_t("3.707317073170"), 456.456 / x1); +>>>>>>> d2f9bb7... Miscellaneous changes:tests/corelib/numerics/BasicAmountTest.cc + + x1 /= amount_t(456.456); + assertEqual(amount_t("0.269736842105"), x1); + x1 /= 456.456; + assertEqual(amount_t("0.0005909372252856792330476541"), x1); + x1 /= 456L; + assertEqual(amount_t("0.00000129591496773175270405187302631578947368421052631578947368421"), x1); + + amount_t x4("1234567891234567.89123456789"); + amount_t y4("56.789"); + + assertEqual(amount_t(1.0), x4 / x4); + assertEqual(amount_t("21739560323910.7554497273748437197344556164"), + x4 / y4); +} + +// round +// conversion +// truth tests +// test for real zero +// comparison operators +// sign check +// abs +// reduce +// printing to a string buffer diff --git a/tests/corelib/numerics/.#BasicAmountTest.cc b/tests/corelib/numerics/.#BasicAmountTest.cc new file mode 100644 index 00000000..adee05ce --- /dev/null +++ b/tests/corelib/numerics/.#BasicAmountTest.cc @@ -0,0 +1 @@ +johnw@Hermes.local.438
\ No newline at end of file diff --git a/tests/corelib/numerics/BasicAmountTest.cc b/tests/corelib/numerics/BasicAmountTest.cc new file mode 100644 index 00000000..f9279ce8 --- /dev/null +++ b/tests/corelib/numerics/BasicAmountTest.cc @@ -0,0 +1,345 @@ +#include "BasicAmountTest.h" +#include "ledger.h" + +using namespace ledger; + +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(BasicAmountTest, "numerics"); + +void BasicAmountTest::setUp() {} +void BasicAmountTest::tearDown() {} + +void BasicAmountTest::testConstructors() +{ + amount_t x0; + amount_t x1(123456L); + amount_t x2(123456UL); + amount_t x3(123.456); + amount_t x4(true); + amount_t x5("123456"); + amount_t x6("123.456"); + amount_t x7(std::string("123456")); + amount_t x8(std::string("123.456")); + amount_t x9(x3); + amount_t x10(x6); + amount_t x11(x8); + + assertEquals(amount_t(0L), x0); + assertEquals(x2, x1); + assertEquals(x5, x1); + assertEquals(x7, x1); + assertEquals(x6, x3); + assertEquals(x8, x3); + assertEquals(x10, x3); + assertEquals(amount_t(1L), x4); + assertEquals(x10, x9); +} + +void BasicAmountTest::testNegation() +{ + amount_t x0; + amount_t x1(-123456L); + amount_t x3(-123.456); + amount_t x5("-123456"); + amount_t x6("-123.456"); + amount_t x7(std::string("-123456")); + amount_t x8(std::string("-123.456")); + amount_t x9(- x3); + + assertEquals(amount_t(0L), x0); + assertEquals(x5, x1); + assertEquals(x7, x1); + assertEquals(x6, x3); + assertEquals(x8, x3); + assertEquals(- x6, x9); + assertEquals(x3.negated(), x9); + + amount_t x10(x9); + x10.negate(); + + assertEquals(x3, x10); +} + +void BasicAmountTest::testAssignment() +{ + amount_t x0; + amount_t x1 = 123456L; + amount_t x2 = 123456UL; + amount_t x3 = 123.456; + amount_t x4 = true; + amount_t x5 = "123456"; + amount_t x6 = "123.456"; + amount_t x7 = std::string("123456"); + amount_t x8 = std::string("123.456"); + amount_t x9 = x3; + amount_t x10 = amount_t(x6); + + assertEquals(amount_t(0L), x0); + assertEquals(x2, x1); + assertEquals(x5, x1); + assertEquals(x7, x1); + assertEquals(x6, x3); + assertEquals(x8, x3); + assertEquals(x10, x3); + assertEquals(amount_t(1L), x4); + assertEquals(x10, x9); + + x0 = amount_t(); + x1 = 123456L; + x2 = 123456UL; + x3 = 123.456; + x4 = true; + x5 = "123456"; + x6 = "123.456"; + x7 = std::string("123456"); + x8 = std::string("123.456"); + x9 = x3; + x10 = amount_t(x6); + + assertEquals(amount_t(0L), x0); + assertEquals(x2, x1); + assertEquals(x5, x1); + assertEquals(x7, x1); + assertEquals(x6, x3); + assertEquals(x8, x3); + assertEquals(x10, x3); + assertEquals(amount_t(1L), x4); + assertEquals(x10, x9); +} + +void BasicAmountTest::testEquality() +{ + amount_t x1(123456L); + amount_t x2(456789L); + amount_t x3(333333L); + amount_t x4(123456.0); + amount_t x5("123456.0"); + amount_t x6(123456.0F); + + CPPUNIT_ASSERT(x1 == 123456L); + CPPUNIT_ASSERT(x1 != x2); + CPPUNIT_ASSERT(x1 == (x2 - x3)); + CPPUNIT_ASSERT(x1 == x4); + CPPUNIT_ASSERT(x4 == x5); + CPPUNIT_ASSERT(x4 == x6); +} + +void BasicAmountTest::testIntegerAddition() +{ + amount_t x1(123L); + amount_t y1(456L); + + assertEquals(amount_t(579L), x1 + y1); + assertEquals(amount_t(579L), x1 + 456L); + + x1 += amount_t(456L); + assertEquals(amount_t(579L), x1); + x1 += 456L; + assertEquals(amount_t(1035L), x1); + + amount_t x3(true); + amount_t y3(true); + + assertEquals(amount_t(2L), x3 + y3); + assertEquals(amount_t(2L), x3 + true); + + amount_t x4("123456789123456789123456789"); + + assertEquals(amount_t("246913578246913578246913578"), x4 + x4); +} + +void BasicAmountTest::testFractionalAddition() +{ + amount_t x1(123.123); + amount_t y1(456.456); + + assertEquals(amount_t(579.579), x1 + y1); + assertEquals(amount_t(579.579), x1 + 456.456); + + x1 += amount_t(456.456); + assertEquals(amount_t(579.579), x1); + x1 += 456.456; + assertEquals(amount_t(1036.035), x1); + x1 += 456L; + assertEquals(amount_t(1492.035), x1); + + amount_t x2("123456789123456789.123456789123456789"); + + assertEquals(amount_t("246913578246913578.246913578246913578"), x2 + x2); +} + +void BasicAmountTest::testIntegerSubtraction() +{ + amount_t x1(123L); + amount_t y1(456L); + + assertEquals(amount_t(333L), y1 - x1); + assertEquals(amount_t(-333L), x1 - y1); + + x1 -= amount_t(456L); + assertEquals(amount_t(-333L), x1); + x1 -= 456L; + assertEquals(amount_t(-789L), x1); + + amount_t x3(true); + amount_t y3(true); + + assertEquals(amount_t(false), x3 - y3); + + amount_t x4("123456789123456789123456789"); + amount_t y4("8238725986235986"); + + assertEquals(amount_t("123456789115218063137220803"), x4 - y4); + assertEquals(amount_t("-123456789115218063137220803"), y4 - x4); +} + +void BasicAmountTest::testFractionalSubtraction() +{ + amount_t x1(123.123); + amount_t y1(456.456); + + assertEquals(amount_t(-333.333), x1 - y1); + assertEquals(amount_t(333.333), y1 - x1); + + x1 -= amount_t(456.456); + assertEquals(amount_t(-333.333), x1); + x1 -= 456.456; + assertEquals(amount_t(-789.789), x1); + x1 -= 456L; + assertEquals(amount_t(-1245.789), x1); + + amount_t x2("123456789123456789.123456789123456789"); + amount_t y2("9872345982459.248974239578"); + + assertEquals(amount_t("123446916777474329.874482549545456789"), x2 - y2); + assertEquals(amount_t("-123446916777474329.874482549545456789"), y2 - x2); +} + +void BasicAmountTest::testIntegerMultiplication() +{ + amount_t x1(123L); + amount_t y1(456L); + + assertEquals(amount_t(0L), x1 * 0L); + assertEquals(amount_t(0L), amount_t(0L) * x1); + assertEquals(x1, x1 * 1L); + assertEquals(x1, amount_t(1L) * x1); + assertEquals(- x1, x1 * -1L); + assertEquals(- x1, amount_t(-1L) * x1); + assertEquals(amount_t(56088L), x1 * y1); + assertEquals(amount_t(56088L), y1 * x1); + assertEquals(amount_t(56088L), x1 * 456L); + assertEquals(amount_t(56088L), amount_t(456L) * x1); + + x1 *= amount_t(123L); + assertEquals(amount_t(15129L), x1); + x1 *= 123L; + assertEquals(amount_t(1860867L), x1); + + amount_t x3(true); + amount_t y3(true); + + assertEquals(amount_t(true), x3 * y3); + + amount_t x4("123456789123456789123456789"); + + assertEquals(amount_t("15241578780673678546105778281054720515622620750190521"), + x4 * x4); +} + +void BasicAmountTest::testFractionalMultiplication() +{ + amount_t x1(123.123); + amount_t y1(456.456); + + assertEquals(amount_t(0L), x1 * 0L); + assertEquals(amount_t(0L), amount_t(0L) * x1); + assertEquals(x1, x1 * 1L); + assertEquals(x1, amount_t(1L) * x1); + assertEquals(- x1, x1 * -1L); + assertEquals(- x1, amount_t(-1L) * x1); + assertEquals(amount_t("56200.232088"), x1 * y1); + assertEquals(amount_t("56200.232088"), y1 * x1); + assertEquals(amount_t("56200.232088"), x1 * 456.456); + assertEquals(amount_t("56200.232088"), amount_t(456.456) * x1); + + x1 *= amount_t(123.123); + assertEquals(amount_t("15159.273129"), x1); + x1 *= 123.123; + assertEquals(amount_t("1866455.185461867"), x1); + x1 *= 123L; + assertEquals(amount_t("229573987.811809641"), x1); + + amount_t x2("123456789123456789.123456789123456789"); + + assertEquals(amount_t("15241578780673678546105778311537878.046486820281054720515622620750190521"), + x2 * x2); +} + +void BasicAmountTest::testIntegerDivision() +{ + amount_t x1(123L); + amount_t y1(456L); + + assertThrow(x1 / 0L, amount_error *); + assertEquals(amount_t(0L), amount_t(0L) / x1); + assertEquals(x1, x1 / 1L); + assertEquals(amount_t("0.008130"), amount_t(1L) / x1); + assertEquals(- x1, x1 / -1L); + assertEquals(- amount_t("0.008130"), amount_t(-1L) / x1); + assertEquals(amount_t("0.269736"), x1 / y1); + assertEquals(amount_t("3.707317"), y1 / x1); + assertEquals(amount_t("0.269736"), x1 / 456L); + assertEquals(amount_t("3.707317"), amount_t(456L) / x1); + + x1 /= amount_t(456L); + assertEquals(amount_t("0.269736"), x1); + x1 /= 456L; + assertEquals(amount_t("0.000591526315789473"), x1); + + amount_t x4("123456789123456789123456789"); + amount_t y4("56"); + + assertEquals(amount_t(1L), x4 / x4); + assertEquals(amount_t("2204585520061728377204585.517857"), x4 / y4); +} + +void BasicAmountTest::testFractionalDivision() +{ + amount_t x1(123.123); + amount_t y1(456.456); + + assertThrow(x1 / 0L, amount_error *); + assertEquals(amount_t("0.008121"), amount_t(1.0) / x1); + assertEquals(x1, x1 / 1.0); + assertEquals(amount_t("0.008121"), amount_t(1.0) / x1); + assertEquals(- x1, x1 / -1.0); + assertEquals(- amount_t("0.008121"), amount_t(-1.0) / x1); + assertEquals(amount_t("0.269736842105"), x1 / y1); + assertEquals(amount_t("3.707317073170"), y1 / x1); + assertEquals(amount_t("0.269736842105"), x1 / 456.456); + assertEquals(amount_t("3.707317073170"), amount_t(456.456) / x1); + + x1 /= amount_t(456.456); + assertEquals(amount_t("0.269736842105"), x1); + x1 /= 456.456; + assertEquals(amount_t("0.0005909372252856792330476541"), x1); + x1 /= 456L; + assertEquals(amount_t("0.00000129591496773175270405187302631578947368421052631578947368421"), x1); + + amount_t x4("1234567891234567.89123456789"); + amount_t y4("56.789"); + + assertEquals(amount_t(1.0), x4 / x4); + assertEquals(amount_t("21739560323910.7554497273748437197344556164"), + x4 / y4); +} + +// round +// conversion +// truth tests +// test for real zero +// comparison operators +// sign check +// abs +// reduce +// printing to a string buffer diff --git a/tests/corelib/numerics/BasicAmountTest.h b/tests/corelib/numerics/BasicAmountTest.h new file mode 100644 index 00000000..bd1360d5 --- /dev/null +++ b/tests/corelib/numerics/BasicAmountTest.h @@ -0,0 +1,50 @@ +#ifndef _BASICAMOUNTTEST_H +#define _BASICAMOUNTTEST_H + +#include "UnitTests.h" + +class BasicAmountTest : public CPPUNIT_NS::TestCase +{ + CPPUNIT_TEST_SUITE(BasicAmountTest); + + CPPUNIT_TEST(testConstructors); + CPPUNIT_TEST(testNegation); + CPPUNIT_TEST(testAssignment); + CPPUNIT_TEST(testEquality); + CPPUNIT_TEST(testIntegerAddition); + CPPUNIT_TEST(testFractionalAddition); + CPPUNIT_TEST(testIntegerSubtraction); + CPPUNIT_TEST(testFractionalSubtraction); + CPPUNIT_TEST(testIntegerMultiplication); + CPPUNIT_TEST(testFractionalMultiplication); + CPPUNIT_TEST(testIntegerDivision); + CPPUNIT_TEST(testFractionalDivision); + + CPPUNIT_TEST_SUITE_END(); + +public: + BasicAmountTest() {} + virtual ~BasicAmountTest() {} + + virtual void setUp(); + virtual void tearDown(); + + void testConstructors(); + void testNegation(); + void testAssignment(); + void testEquality(); + void testIntegerAddition(); + void testFractionalAddition(); + void testIntegerSubtraction(); + void testFractionalSubtraction(); + void testIntegerMultiplication(); + void testFractionalMultiplication(); + void testIntegerDivision(); + void testFractionalDivision(); + +private: + BasicAmountTest(const BasicAmountTest ©); + void operator=(const BasicAmountTest ©); +}; + +#endif /* _BASICAMOUNTTEST_H */ @@ -1,27 +1,21 @@ -#if defined(__GNUG__) && __GNUG__ < 3 -#define _XOPEN_SOURCE -#endif - -#include "journal.h" +#ifdef USE_PCH +#include "pch.h" +#else #include "textual.h" -#include "datetime.h" -#include "valexpr.h" -#include "error.h" -#include "option.h" -#include "config.h" -#include "timing.h" +#include "session.h" #include "util.h" #include "acconf.h" +#if defined(__GNUG__) && __GNUG__ < 3 +#define _XOPEN_SOURCE +#endif + #include <fstream> #include <sstream> #include <cstring> #include <cctype> #include <cstdio> #include <cstdlib> - -#ifdef HAVE_REALPATH -extern "C" char *realpath(const char *, char resolved_path[]); #endif #define TIMELOG_SUPPORT 1 @@ -68,12 +62,12 @@ inline char * next_element(char * buf, bool variable = false) return NULL; } -static value_expr parse_amount_expr(std::istream& in, amount_t& amount, - transaction_t * xact, - unsigned short flags = 0) +static inline void +parse_amount_expr(std::istream& in, journal_t * journal, + transaction_t& xact, amount_t& amount, + unsigned short flags = 0) { - value_expr expr(parse_value_expr(in, NULL, flags | PARSE_VALEXPR_RELAXED | - PARSE_VALEXPR_PARTIAL)->acquire()); + xml::xpath_t xpath(in, flags | XPATH_PARSE_RELAXED | XPATH_PARSE_PARTIAL); DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << "Parsed an amount expression"); @@ -81,35 +75,33 @@ static value_expr parse_amount_expr(std::istream& in, amount_t& amount, #ifdef DEBUG_ENABLED DEBUG_IF("ledger.textual.parse") { if (_debug_stream) { - ledger::dump_value_expr(*_debug_stream, expr); + xpath.dump(*_debug_stream); *_debug_stream << std::endl; } } #endif - if (! compute_amount(expr, amount, xact)) - throw new parse_error("Amount expression failed to compute"); - - if (expr->kind == value_expr_t::CONSTANT) - expr = NULL; + amount = xpath.calc(static_cast<xml::transaction_node_t *>(xact.data)).to_amount(); DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << - "The transaction amount is " << xact->amount); - return expr; + "The transaction amount is " << amount); } -transaction_t * parse_transaction(char * line, account_t * account, - entry_t * entry = NULL) +transaction_t * parse_transaction(char * line, + journal_t * journal, + account_t * account, + entry_t * entry = NULL) { - std::istringstream in(line); + // The account will be determined later... + std::auto_ptr<transaction_t> xact(new transaction_t(NULL)); + std::istringstream in(line); std::string err_desc; try { - // The account will be determined later... - std::auto_ptr<transaction_t> xact(new transaction_t(NULL)); - if (entry) - xact->entry = entry; + xact->entry = entry; // this might be NULL + if (xact->entry) + xact->data = xml::wrap_node(journal->document, xact.get(), xact->entry->data); // Parse the state flag @@ -182,14 +174,13 @@ transaction_t * parse_transaction(char * line, account_t * account, goto parse_note; try { - unsigned long beg = (long)in.tellg(); - - xact->amount_expr = - parse_amount_expr(in, xact->amount, xact.get(), - PARSE_VALEXPR_NO_REDUCE); + // jww (2006-09-15): Make sure it doesn't gobble up the upcoming @ symbol + unsigned long beg = (long)in.tellg(); + parse_amount_expr(in, journal, *xact, xact->amount, + XPATH_PARSE_NO_REDUCE); unsigned long end = (long)in.tellg(); - xact->amount_expr.expr = std::string(line, beg, end - beg); + xact->amount_expr = std::string(line, beg, end - beg); } catch (error * err) { err_desc = "While parsing transaction amount:"; @@ -219,10 +210,8 @@ transaction_t * parse_transaction(char * line, account_t * account, try { unsigned long beg = (long)in.tellg(); - if (parse_amount_expr(in, *xact->cost, xact.get(), - PARSE_VALEXPR_NO_MIGRATE)) - throw new parse_error - ("A transaction's cost must evalute to a constant value"); + parse_amount_expr(in, journal, *xact, *xact->cost, + XPATH_PARSE_NO_MIGRATE); unsigned long end = (long)in.tellg(); @@ -312,6 +301,7 @@ transaction_t * parse_transaction(char * line, account_t * account, } bool parse_transactions(std::istream& in, + journal_t * journal, account_t * account, entry_base_t& entry, const std::string& kind, @@ -332,7 +322,7 @@ bool parse_transactions(std::istream& in, if (! *p || *p == '\r') break; } - if (transaction_t * xact = parse_transaction(line, account)) { + if (transaction_t * xact = parse_transaction(line, journal, account)) { entry.add_transaction(xact); added = true; } @@ -348,22 +338,25 @@ namespace { TIMER_DEF(entry_date, "parsing entry date"); } -entry_t * parse_entry(std::istream& in, char * line, account_t * master, - textual_parser_t& parser, unsigned long beg_pos) +entry_t * parse_entry(std::istream& in, char * line, journal_t * journal, + account_t * master, textual_parser_t& parser, + unsigned long beg_pos) { std::auto_ptr<entry_t> curr(new entry_t); + std::istringstream line_in(line); + char c; + // Parse the date TIMER_START(entry_date); - char * next = next_element(line); + curr->_date.parse(line_in); - if (char * p = std::strchr(line, '=')) { - *p++ = '\0'; - curr->_date_eff = p; + if (peek_next_nonws(line_in) == '=') { + line_in.get(c); + curr->_date_eff.parse(line_in); } - curr->_date = line; TIMER_STOP(entry_date); @@ -372,35 +365,43 @@ entry_t * parse_entry(std::istream& in, char * line, account_t * master, TIMER_START(entry_details); transaction_t::state_t state = transaction_t::UNCLEARED; - if (next) { - switch (*next) { - case '*': - state = transaction_t::CLEARED; - next = skip_ws(++next); - break; - case '!': - state = transaction_t::PENDING; - next = skip_ws(++next); - break; - } + switch (peek_next_nonws(line_in)) { + case '*': + state = transaction_t::CLEARED; + line_in.get(c); + break; + case '!': + state = transaction_t::PENDING; + line_in.get(c); + break; } // Parse the optional code: (TEXT) - if (next && *next == '(') { - if (char * p = std::strchr(next++, ')')) { - *p++ = '\0'; - curr->code = next; - next = skip_ws(p); - } + char buf[256]; + + if (peek_next_nonws(line_in) == '(') { + line_in.get(c); + READ_INTO(line_in, buf, 255, c, c != ')'); + curr->code = buf; + if (c == ')') + line_in.get(c); + peek_next_nonws(line_in); } - // Parse the description text + // Parse the payee/description text - curr->payee = next ? next : "<Unspecified payee>"; + std::memset(buf, 0, 255); + line_in.read(buf, 255); + curr->payee = buf[0] != '\0' ? buf : "<Unspecified payee>"; TIMER_STOP(entry_details); + // Create a report item for this entry, so the transaction below may + // refer to it + + curr->data = xml::wrap_node(journal->document, curr.get(), journal->data); + // Parse all of the transactions associated with this entry TIMER_START(entry_xacts); @@ -422,7 +423,8 @@ entry_t * parse_entry(std::istream& in, char * line, account_t * master, break; } - if (transaction_t * xact = parse_transaction(line, master, curr.get())) { + if (transaction_t * xact = + parse_transaction(line, journal, master, curr.get())) { if (state != transaction_t::UNCLEARED && xact->state == transaction_t::UNCLEARED) xact->state = state; @@ -559,29 +561,29 @@ static void clock_out_from_timelog(const datetime_t& when, } unsigned int textual_parser_t::parse(std::istream& in, - config_t& config, journal_t * journal, account_t * master, const std::string * original_file) { - static bool added_auto_entry_hook = false; - static char line[MAX_LINE + 1]; - char c; - unsigned int count = 0; - unsigned int errors = 0; + static bool added_auto_entry_hook = false; + static char line[MAX_LINE + 1]; + char c; + unsigned int count = 0; + unsigned int errors = 0; TIMER_START(parsing_total); std::list<account_t *> account_stack; + auto_entry_finalizer_t auto_entry_finalizer(journal); - if (! master) + if (! master && journal) master = journal->master; account_stack.push_front(master); - path = journal->sources.back(); - src_idx = journal->sources.size() - 1; + path = journal ? journal->sources.back() : *original_file; + src_idx = journal ? journal->sources.size() - 1 : 0; linenum = 1; unsigned long beg_pos = in.tellg(); @@ -707,7 +709,7 @@ unsigned int textual_parser_t::parse(std::istream& in, break; } - case 'Y': // set the current year + case 'Y': // set current year date_t::current_year = std::atoi(skip_ws(line + 1)) - 1900; break; @@ -715,19 +717,11 @@ unsigned int textual_parser_t::parse(std::istream& in, case 'h': case 'b': #endif - case ';': // a comment line + case ';': // comment break; - case '-': { // option setting - char * p = next_element(line); - if (! p) { - p = std::strchr(line, '='); - if (p) - *p++ = '\0'; - } - process_option(config_options, line + 2, p); - break; - } + case '-': // option setting + throw new parse_error("Option settings are not allowed in journal files"); case '=': { // automated entry if (! added_auto_entry_hook) { @@ -736,7 +730,7 @@ unsigned int textual_parser_t::parse(std::istream& in, } auto_entry_t * ae = new auto_entry_t(skip_ws(line + 1)); - if (parse_transactions(in, account_stack.front(), *ae, + if (parse_transactions(in, journal, account_stack.front(), *ae, "automated", end_pos)) { journal->auto_entries.push_back(ae); ae->src_idx = src_idx; @@ -753,7 +747,7 @@ unsigned int textual_parser_t::parse(std::istream& in, if (! pe->period) throw new parse_error(std::string("Parsing time period '") + line + "'"); - if (parse_transactions(in, account_stack.front(), *pe, + if (parse_transactions(in, journal, account_stack.front(), *pe, "period", end_pos)) { if (pe->finalize()) { extend_entry_base(journal, *pe, true); @@ -796,8 +790,8 @@ unsigned int textual_parser_t::parse(std::istream& in, include_stack.push_back(std::pair<std::string, int> (journal->sources.back(), linenum - 1)); - count += parse_journal_file(path, config, journal, - account_stack.front()); + count += journal->session->read_journal(path, journal, + account_stack.front()); include_stack.pop_back(); } else if (word == "account") { @@ -827,10 +821,14 @@ unsigned int textual_parser_t::parse(std::istream& in, assert(result.second); } } - else if (word == "def") { - if (! global_scope.get()) - init_value_expr(); - parse_value_definition(p); + else if (word == "def" || word == "eval") { + // jww (2006-09-13): Read the string after and evaluate it. + // But also keep a list of these value expressions, and a + // way to know where they fall in the transaction sequence. + // This will be necessary so that binary file reading can + // re-evaluate them at the appopriate time. + + // compile(&journal->defs); } break; } @@ -838,8 +836,9 @@ unsigned int textual_parser_t::parse(std::istream& in, default: { unsigned int first_line = linenum; unsigned long pos = end_pos; - if (entry_t * entry = - parse_entry(in, line, account_stack.front(), *this, pos)) { + if (entry_t * entry = parse_entry(in, line, journal, + account_stack.front(), + *this, pos)) { if (journal->add_entry(entry)) { entry->src_idx = src_idx; entry->beg_pos = beg_pos; @@ -899,96 +898,4 @@ unsigned int textual_parser_t::parse(std::istream& in, return count; } -void write_textual_journal(journal_t& journal, std::string path, - item_handler<transaction_t>& formatter, - const std::string& write_hdr_format, - std::ostream& out) -{ - unsigned long index = 0; - std::string found; - - if (path.empty()) { - if (! journal.sources.empty()) - found = *journal.sources.begin(); - } else { -#ifdef HAVE_REALPATH - char buf1[PATH_MAX]; - char buf2[PATH_MAX]; - - ::realpath(path.c_str(), buf1); - - for (strings_list::iterator i = journal.sources.begin(); - i != journal.sources.end(); - i++) { - ::realpath((*i).c_str(), buf2); - if (std::strcmp(buf1, buf2) == 0) { - found = *i; - break; - } - index++; - } -#else - for (strings_list::iterator i = journal.sources.begin(); - i != journal.sources.end(); - i++) { - if (path == *i) { - found = *i; - break; - } - index++; - } -#endif - } - - if (found.empty()) - throw new error(std::string("Journal does not refer to file '") + - path + "'"); - - entries_list::iterator el = journal.entries.begin(); - auto_entries_list::iterator al = journal.auto_entries.begin(); - period_entries_list::iterator pl = journal.period_entries.begin(); - - unsigned long pos = 0; - - format_t hdr_fmt(write_hdr_format); - std::ifstream in(found.c_str()); - - while (! in.eof()) { - entry_base_t * base = NULL; - if (el != journal.entries.end() && pos == (*el)->beg_pos) { - hdr_fmt.format(out, details_t(**el)); - base = *el++; - } - else if (al != journal.auto_entries.end() && pos == (*al)->beg_pos) { - out << "= " << (*al)->predicate_string << '\n'; - base = *al++; - } - else if (pl != journal.period_entries.end() && pos == (*pl)->beg_pos) { - out << "~ " << (*pl)->period_string << '\n'; - base = *pl++; - } - - char c; - if (base) { - for (transactions_list::iterator x = base->transactions.begin(); - x != base->transactions.end(); - x++) - if (! ((*x)->flags & TRANSACTION_AUTO)) { - transaction_xdata(**x).dflags |= TRANSACTION_TO_DISPLAY; - formatter(**x); - } - formatter.flush(); - - while (pos < base->end_pos) { - in.get(c); - pos = in.tellg(); // pos++; - } - } else { - in.get(c); - pos = in.tellg(); // pos++; - out.put(c); - } - } -} - } // namespace ledger @@ -2,8 +2,6 @@ #define _TEXTUAL_H #include "parser.h" -#include "format.h" -#include "walk.h" namespace ledger { @@ -13,19 +11,17 @@ class textual_parser_t : public parser_t virtual bool test(std::istream& in) const; virtual unsigned int parse(std::istream& in, - config_t& config, journal_t * journal, account_t * master = NULL, const std::string * original_file = NULL); }; -transaction_t * parse_transaction_text(char * line, account_t * account); -transaction_t * parse_transaction(std::istream& in, account_t * account); - +#if 0 void write_textual_journal(journal_t& journal, std::string path, item_handler<transaction_t>& formatter, const std::string& write_hdr_format, std::ostream& out); +#endif class include_context : public file_context { public: diff --git a/trace.cc b/trace.cc new file mode 100644 index 00000000..46f250e1 --- /dev/null +++ b/trace.cc @@ -0,0 +1,187 @@ +#ifdef USE_PCH +#include "pch.h" +#else +#include "trace.h" +#include "acconf.h" +#endif + +namespace ledger { + +bool trace_mode; + +void trace(const std::string& cat, const std::string& str) +{ + char buf[32]; + std::strftime(buf, 31, "%H:%M:%S", datetime_t::now.localtime()); + std::cerr << buf << " " << cat << ": " << str << std::endl; +} + +void trace_push(const std::string& cat, const std::string& str, + timing_t& timer) +{ + timer.start(); + trace(cat, str); +} + +void trace_pop(const std::string& cat, const std::string& str, + timing_t& timer) +{ + timer.stop(); + std::ostringstream out; + out << str << ": " << (double(timer.cumulative) / double(CLOCKS_PER_SEC)) << "s"; + trace(cat, out.str()); +} + +live_objects_map live_objects; +object_count_map ctor_count; +object_count_map object_count; +object_count_map live_count; + +bool tracing_active = false; + +bool trace_ctor(void * ptr, const std::string& name) +{ + if (! tracing_active) + return true; + + DEBUG_PRINT("ledger.trace.debug", "trace_ctor " << ptr << " " << name); + + std::string::size_type pos = name.find_first_of('('); + std::string cls_name(name, 0, pos); + + live_objects.insert(live_objects_pair(ptr, cls_name)); + + object_count_map::iterator i = ctor_count.find(name); + if (i != ctor_count.end()) { + (*i).second++; + } else { + std::pair<object_count_map::iterator, bool> result + = ctor_count.insert(object_count_pair(name, 1)); + if (! result.second) { + tracing_active = false; + return false; + } + } + + object_count_map::iterator j = object_count.find(cls_name); + if (j != object_count.end()) { + (*j).second++; + } else { + std::pair<object_count_map::iterator, bool> result + = object_count.insert(object_count_pair(cls_name, 1)); + if (! result.second) { + tracing_active = false; + return false; + } + } + + object_count_map::iterator k = live_count.find(cls_name); + if (k != live_count.end()) { + (*k).second++; + } else { + std::pair<object_count_map::iterator, bool> result + = live_count.insert(object_count_pair(cls_name, 1)); + if (! result.second) { + tracing_active = false; + return false; + } + } + + return true; +} + +bool trace_dtor(void * ptr, const std::string& name) +{ + if (! tracing_active) + return true; + + DEBUG_PRINT("ledger.trace.debug", "trace_dtor " << ptr << " " << name); + + live_objects_map::iterator i = live_objects.find(ptr); + if (i == live_objects.end()) { + std::cerr << "Destruction of unknown object " << name << " " << ptr + << std::endl;; + tracing_active = false; + return false; + } + + std::string::size_type pos = name.find_first_of('('); + std::string cls_name(name, 0, pos); + + int ptr_count = live_objects.count(ptr); + for (int x = 0; x < ptr_count; x++) { + if ((*i).second == cls_name) { + live_objects.erase(i); + break; + } else { + i++; + } + } + + object_count_map::iterator k = live_count.find(name); + if (k == live_count.end()) { + std::cerr << "Destruction of unregistered class " << name + << std::endl;; + tracing_active = false; + return false; + } + if (--(*k).second == 0) + live_count.erase(k); + + return true; +} + +void report_memory(std::ostream& out) +{ + if (live_count.size() > 0) + out << "Live object counts:" << std::endl; + + for (object_count_map::iterator i = live_count.begin(); + i != live_count.end(); + i++) { + out << " "; + out << std::right; + out.width(5); + out << (*i).second << " " << (*i).first << std::endl; + } + + DEBUG_IF("ledger.trace.verbose") { + if (live_objects.size() > 0) + out << "Live objects:" << std::endl; + + for (live_objects_map::iterator i = live_objects.begin(); + i != live_objects.end(); + i++) { + out << " "; + out << std::right; + out.width(5); + out << (*i).first << " " << (*i).second << std::endl; + } + } + + if (object_count.size() > 0) + out << "Object counts:" << std::endl; + + for (object_count_map::iterator i = object_count.begin(); + i != object_count.end(); + i++) { + out << " "; + out << std::right; + out.width(5); + out << (*i).second << " " << (*i).first << std::endl; + } + + if (ctor_count.size() > 0) + out << "Constructor counts:" << std::endl; + + for (object_count_map::iterator i = ctor_count.begin(); + i != ctor_count.end(); + i++) { + out << " "; + out << std::right; + out.width(5); + out << (*i).second << " " << (*i).first << std::endl; + } +} + +} // namespace ledger diff --git a/trace.h b/trace.h new file mode 100644 index 00000000..367910ad --- /dev/null +++ b/trace.h @@ -0,0 +1,51 @@ +#ifndef _TRACE_H +#define _TRACE_H + +#include "timing.h" + +#include <string> +#include <map> + +namespace ledger { + +extern bool trace_mode; + +void trace(const std::string& cat, const std::string& str); +void trace_push(const std::string& cat, const std::string& str, + timing_t& timer); +void trace_pop(const std::string& cat, const std::string& str, + timing_t& timer); + +#define TRACE(cat, msg) if (trace_mode) trace(#cat, msg) +#define TRACE_(cat, msg) if (trace_mode) trace(#cat, msg) + +#define TRACE_PUSH(cat, msg) \ + timing_t timer_ ## cat(#cat); \ + if (trace_mode) trace_push(#cat, msg, timer_ ## cat) + +#define TRACE_POP(cat, msg) \ + if (trace_mode) trace_pop(#cat, msg, timer_ ## cat) + +typedef std::multimap<void *, std::string> live_objects_map; +typedef std::pair<void *, std::string> live_objects_pair; +typedef std::map<std::string, int> object_count_map; +typedef std::pair<std::string, int> object_count_pair; + +extern live_objects_map live_objects; +extern object_count_map ctor_count; +extern object_count_map object_count; +extern object_count_map live_count; + +extern bool tracing_active; + +bool trace_ctor(void * ptr, const std::string& name); +bool trace_dtor(void * ptr, const std::string& name); + +void report_memory(std::ostream& out); + +#define TRACE_CTOR(cls) CONFIRM(ledger::trace_ctor(this, cls)) +#define TRACE_DTOR(cls) CONFIRM(ledger::trace_dtor(this, cls)) + +} // namespace ledger + +#endif // _TRACE_H diff --git a/transform.cc b/transform.cc new file mode 100644 index 00000000..58ee091e --- /dev/null +++ b/transform.cc @@ -0,0 +1,349 @@ +#ifdef USE_PCH +#include "pch.h" +#else +#include "transform.h" +#endif + +namespace ledger { + +#if 0 +void populate_account(account_t& acct, xml::document_t * document) +{ + if (! acct.parent) + return; + + account_repitem_t * acct_item; + if (acct.data == NULL) { + acct.data = acct_item = + static_cast<account_repitem_t *>(repitem_t::wrap(&acct)); + if (acct.parent) { + if (acct.parent->data == NULL) + populate_account(*acct.parent, acct_item); + else + static_cast<account_repitem_t *>(acct.parent->data)-> + add_child(acct_item); + } + } else { + acct_item = static_cast<account_repitem_t *>(acct.data); + } + + if (item->kind == repitem_t::ACCOUNT) + acct_item->add_child(item); + else + acct_item->add_content(item); +} + +class populate_accounts : public repitem_t::select_callback_t { + virtual void operator()(xml::document_t * document) { + if (item->kind == repitem_t::TRANSACTION) { + item->extract(); + populate_account(*static_cast<xact_repitem_t *>(item)->account(), item); + } + } +}; + +class clear_account_data : public repitem_t::select_callback_t { + virtual void operator()(xml::document_t * document) { + if (item->kind == repitem_t::ACCOUNT) + static_cast<account_repitem_t *>(item)->account->data = NULL; + } +}; + +void accounts_transform::execute(xml::document_t * document) +{ + populate_accounts cb1; + items->select_all(cb1); + + for (repitem_t * j = items->children; j; j = j->next) { + assert(j->kind == repitem_t::JOURNAL); + + j->clear(); + + for (accounts_map::iterator i = j->journal->master->accounts.begin(); + i != j->journal->master->accounts.end(); + i++) { + assert((*i).second->data); + j->add_child(static_cast<account_repitem_t *>((*i).second->data)); + (*i).second->data = NULL; + } + } + + clear_account_data cb2; + items->select_all(cb2); +} + +void compact_transform::execute(xml::document_t * document) +{ + for (repitem_t * i = items; i; i = i->next) { + if (i->kind == repitem_t::ACCOUNT) { + while (! i->contents && + i->children && ! i->children->next) { + account_repitem_t * p = static_cast<account_repitem_t *>(i); + i = p->children; + p->children = NULL; + p->last_child = NULL; + + i->set_parent(p->parent); + p->set_parent(NULL); + i->prev = p->prev; + if (p->prev) + p->prev->next = i; + p->prev = NULL; + i->next = p->next; + if (p->next) + p->next->prev = i; + p->next = NULL; + + if (i->parent->children == p) + i->parent->children = i; + if (i->parent->last_child == p) + i->parent->last_child = i; + + account_repitem_t * acct = static_cast<account_repitem_t *>(i); + acct->parents_elided = p->parents_elided + 1; + + delete p; + } + } + + if (i->children) + execute(i->children); + } +} + +void clean_transform::execute(xml::document_t * document) +{ + repitem_t * i = items; + while (i) { + if (i->kind == repitem_t::ACCOUNT) { + value_t temp; + i->add_total(temp); + if (! temp) { + repitem_t * next = i->next; + delete i; + i = next; + continue; + } + } +#if 0 + else if (i->kind == repitem_t::ENTRY && ! i->contents) { + assert(! i->children); + repitem_t * next = i->next; + delete i; + i = next; + continue; + } +#endif + + if (i->children) + execute(i->children); + + i = i->next; + } +} + +void entries_transform::execute(xml::document_t * document) +{ +} + +void optimize_transform::execute(xml::document_t * document) +{ + for (repitem_t * i = items; i; i = i->next) { + if (i->kind == repitem_t::ENTRY) { + if (i->contents && + i->contents->next && + ! i->contents->next->next) { // exactly two transactions + xact_repitem_t * first = + static_cast<xact_repitem_t *>(i->contents); + xact_repitem_t * second = + static_cast<xact_repitem_t *>(i->contents->next); + if (first->xact->amount == - second->xact->amount) + ; + } + } + + if (i->children) + execute(i->children); + } +} + +void split_transform::execute(xml::document_t * document) +{ + for (repitem_t * i = items; i; i = i->next) { + if (i->contents && i->contents->next) { + repitem_t * j; + + switch (i->kind) { + case repitem_t::TRANSACTION: + assert(0); + j = new xact_repitem_t(static_cast<xact_repitem_t *>(i)->xact); + break; + case repitem_t::ENTRY: + j = new entry_repitem_t(static_cast<entry_repitem_t *>(i)->entry); + break; + case repitem_t::ACCOUNT: + j = new account_repitem_t(static_cast<account_repitem_t *>(i)->account); + break; + default: + j = new repitem_t(i->kind); + break; + } + + j->set_parent(i->parent); + j->prev = i; + j->next = i->next; + i->next = j; + + j->contents = i->contents->next; + j->contents->prev = NULL; + j->contents->set_parent(j); + i->contents->next = NULL; + + j->last_content = i->last_content; + if (j->contents == i->last_content) + i->last_content = i->contents; + } + + if (i->children) + execute(i->children); + } +} + +void merge_transform::execute(xml::document_t * document) +{ + for (repitem_t * i = items; i; i = i->next) { + if (i->next) { + assert(i->kind == i->next->kind); + bool merge = false; + switch (i->kind) { + case repitem_t::TRANSACTION: + assert(0); + break; + case repitem_t::ENTRY: + if (static_cast<entry_repitem_t *>(i)->entry == + static_cast<entry_repitem_t *>(i->next)->entry) + merge = true; + break; + case repitem_t::ACCOUNT: +#if 0 + if (static_cast<account_repitem_t *>(i)->account == + static_cast<account_repitem_t *>(i->next)->account) + merge = true; +#endif + break; + default: + break; + } + + if (merge) { + repitem_t * j = i->next; + + i->next = i->next->next; + if (i->next) + i->next->prev = i; + + for (repitem_t * k = j->contents; k; k = k->next) + k->set_parent(i); + + i->last_content->next = j->contents; + i->last_content = j->last_content; + + j->contents = NULL; + assert(! j->children); + delete j; + } + } + + if (i->children) + execute(i->children); + } +} + +namespace { +#define REPITEM_FLAGGED 0x1 + + class mark_selected : public repitem_t::select_callback_t { + virtual void operator()(xml::document_t * document) { + item->flags |= REPITEM_FLAGGED; + } + }; + + class mark_selected_and_ancestors : public repitem_t::select_callback_t { + virtual void operator()(xml::document_t * document) { + while (item->parent) { + item->flags |= REPITEM_FLAGGED; + item = item->parent; + } + } + }; + + class delete_unmarked : public repitem_t::select_callback_t { + virtual void operator()(xml::document_t * document) { + if (item->parent && ! (item->flags & REPITEM_FLAGGED)) + delete item; + } + }; + + class delete_marked : public repitem_t::select_callback_t { + virtual void operator()(xml::document_t * document) { + if (item->flags & REPITEM_FLAGGED) + delete item; + } + }; + + class clear_flags : public repitem_t::select_callback_t { + virtual void operator()(xml::document_t * document) { + item->flags = 0; + } + }; +} + +void select_transform::execute(xml::document_t * document) +{ + if (! path) { + items->clear(); + return; + } + mark_selected_and_ancestors cb1; + items->select(path, cb1); + + delete_unmarked cb2; + items->select_all(cb2); + clear_flags cb3; + items->select_all(cb3); +} + +void remove_transform::execute(xml::document_t * document) +{ + if (! path) + return; + mark_selected cb1; + items->select(path, cb1); + + delete_marked cb2; + items->select_all(cb2); + clear_flags cb3; + items->select_all(cb3); +} +#endif + +} // namespace ledger + +#if 0 +#ifdef USE_BOOST_PYTHON + +#ifndef USE_PCH +#include <boost/python.hpp> +#endif + +using namespace boost::python; +using namespace ledger; + +void export_transform() +{ + class_< repitem_t > ("Transform") + ; +} + +#endif // USE_BOOST_PYTHON +#endif diff --git a/transform.h b/transform.h new file mode 100644 index 00000000..2310d4be --- /dev/null +++ b/transform.h @@ -0,0 +1,138 @@ +#ifndef _TRANSFORM_H +#define _TRANSFORM_H + +#include "xpath.h" + +#include <list> +#include <deque> + +namespace ledger { + +class transform_t { + public: + virtual void execute(xml::document_t * document) = 0; +}; + +class check_transform : public transform_t { + // --check checks the validity of the item list. + public: + virtual void execute(xml::document_t * document); +}; + +class accounts_transform : public transform_t { + // --accounts transforms the report tree into an account-wise view. + public: + virtual void execute(xml::document_t * document); +}; + +class compact_transform : public transform_t { + // --compact compacts an account tree to remove accounts with only + // one child account. + public: + virtual void execute(xml::document_t * document); +}; + +class clean_transform : public transform_t { + // --clean clears out entries and accounts that have no contents. + public: + virtual void execute(xml::document_t * document); +}; + +class entries_transform : public transform_t { + // --entries transforms the report tree into an entries-wise view. + public: + virtual void execute(xml::document_t * document); +}; + +class optimize_transform : public transform_t { + // --optimize optimizes entries for display by the print command. + // What this means is that if an entry has two transactions of the + // commodity (one the negative of the other), the amount of the + // second transaction will be nulled out. + public: + virtual void execute(xml::document_t * document); +}; + +class split_transform : public transform_t { + // --split breaks entry with two or more transactions into what + // seems like two entries each with one transaction -- even though + // it is the same entry being reported in both cases. This is + // useful before sorting, for exampel, in order to sort by + // transaction instead of by entry. + public: + virtual void execute(xml::document_t * document); +}; + +class merge_transform : public transform_t { + // --merge is the opposite of --split: any adjacent transactions + // which share the same entry will be merged into a group of + // transactions under one reported entry. + public: + virtual void execute(xml::document_t * document); +}; + +class combine_transform : public transform_t { + // --combine EXPR combines all transactions matching EXPR so that + // they appear within the same virtual entry (whose date will span + // the earliest to the latest of those entries, and whose payee name + // will show the terminating date or a label that is characteristic + // of the set). + public: + virtual void execute(xml::document_t * document); +}; + +class group_transform : public transform_t { + // --group groups all transactions that affect the same account + // within an entry, so that they appear as a single transaction. + public: + virtual void execute(xml::document_t * document); +}; + +class collapse_transform : public transform_t { + // --collapse makes all transactions within an entry appear as a + // single transaction, even if they affect different accounts. The + // fictitous account "<total>" is used to represent the final sum, + // if multiple accounts are involved. + public: + virtual void execute(xml::document_t * document); +}; + +class subtotal_transform : public transform_t { + // --subtotal will combine the transactions from all entries into + // one giant entry. When used in conjunction with --group, the + // affect is very similar to a regular balance report. + public: + virtual void execute(xml::document_t * document); +}; + +#if 0 +class select_transform : public transform_t +{ + protected: + xml::xpath_t xpath; + + public: + select_transform(const std::string& selection_path) { + xpath.parse(selection_path); + } + virtual ~select_transform() { + if (path) + delete path; + } + + virtual void execute(xml::document_t * document); +}; + +class remove_transform : public select_transform +{ + public: + remove_transform(const std::string& selection_path) + : select_transform(selection_path) {} + + virtual void execute(xml::document_t * document); +}; +#endif + +} // namespace ledger + +#endif // _TRANSFORM_H diff --git a/util.cc b/util.cc new file mode 100644 index 00000000..afa71c44 --- /dev/null +++ b/util.cc @@ -0,0 +1,161 @@ +#ifdef USE_PCH +#include "pch.h" +#else +#include "util.h" + +#include <list> +#include <string> +#include <cstring> + +#include <cstdlib> +#ifdef WIN32 +#include <io.h> +#else +#include <unistd.h> +#endif + +#if defined(HAVE_GETPWUID) || defined(HAVE_GETPWNAM) +#include <pwd.h> +#endif +#endif + +std::string expand_path(const std::string& path) +{ + if (path.length() == 0 || path[0] != '~') + return path; + + const char * pfx = NULL; + std::string::size_type pos = path.find_first_of('/'); + + if (path.length() == 1 || pos == 1) { + pfx = std::getenv("HOME"); +#ifdef HAVE_GETPWUID + if (! pfx) { + // Punt. We're trying to expand ~/, but HOME isn't set + struct passwd * pw = getpwuid(getuid()); + if (pw) + pfx = pw->pw_dir; + } +#endif + } +#ifdef HAVE_GETPWNAM + else { + std::string user(path, 1, pos == std::string::npos ? + std::string::npos : pos - 1); + struct passwd * pw = getpwnam(user.c_str()); + if (pw) + pfx = pw->pw_dir; + } +#endif + + // if we failed to find an expansion, return the path unchanged. + + if (! pfx) + return path; + + std::string result(pfx); + + if (pos == std::string::npos) + return result; + + if (result.length() == 0 || result[result.length() - 1] != '/') + result += '/'; + + result += path.substr(pos + 1); + + return result; +} + +std::string resolve_path(const std::string& path) +{ + if (path[0] == '~') + return expand_path(path); + return path; +} + +std::string abbreviate(const std::string& str, unsigned int width, + elision_style_t elision_style, const bool is_account, + int abbrev_length) +{ + const int len = str.length(); + if (len <= width) + return str; + + assert(width < 4095); + + char buf[4096]; + + switch (elision_style) { + case TRUNCATE_LEADING: + // This method truncates at the beginning. + std::strncpy(buf, str.c_str() + (len - width), width); + buf[0] = '.'; + buf[1] = '.'; + break; + + case TRUNCATE_MIDDLE: + // This method truncates in the middle. + std::strncpy(buf, str.c_str(), width / 2); + std::strncpy(buf + width / 2, + str.c_str() + (len - (width / 2 + width % 2)), + width / 2 + width % 2); + buf[width / 2 - 1] = '.'; + buf[width / 2] = '.'; + break; + + case ABBREVIATE: + if (is_account) { + std::list<std::string> parts; + std::string::size_type beg = 0; + for (std::string::size_type pos = str.find(':'); + pos != std::string::npos; + beg = pos + 1, pos = str.find(':', beg)) + parts.push_back(std::string(str, beg, pos - beg)); + parts.push_back(std::string(str, beg)); + + std::string result; + int newlen = len; + for (std::list<std::string>::iterator i = parts.begin(); + i != parts.end(); + i++) { + // Don't contract the last element + std::list<std::string>::iterator x = i; + if (++x == parts.end()) { + result += *i; + break; + } + + if (newlen > width) { + result += std::string(*i, 0, abbrev_length); + result += ":"; + newlen -= (*i).length() - abbrev_length; + } else { + result += *i; + result += ":"; + } + } + + if (newlen > width) { + // Even abbreviated its too big to show the last account, so + // abbreviate all but the last and truncate at the beginning. + std::strncpy(buf, result.c_str() + (result.length() - width), width); + buf[0] = '.'; + buf[1] = '.'; + } else { + std::strcpy(buf, result.c_str()); + } + break; + } + // fall through... + + case TRUNCATE_TRAILING: + // This method truncates at the end (the default). + std::strncpy(buf, str.c_str(), width - 2); + buf[width - 2] = '.'; + buf[width - 1] = '.'; + break; + } + buf[width] = '\0'; + + return buf; +} @@ -59,4 +59,41 @@ inline char peek_next_nonws(std::istream& in) { *_p = '\0'; \ } +#define READ_INTO_(str, targ, size, var, idx, cond) { \ + char * _p = targ; \ + var = str.peek(); \ + while (! str.eof() && var != '\n' && (cond) && _p - targ < size) { \ + str.get(var); \ + if (str.eof()) \ + break; \ + idx++; \ + if (var == '\\') { \ + str.get(var); \ + if (in.eof()) \ + break; \ + idx++; \ + } \ + *_p++ = var; \ + var = str.peek(); \ + } \ + *_p = '\0'; \ +} + +std::string resolve_path(const std::string& path); + +#ifdef HAVE_REALPATH +extern "C" char *realpath(const char *, char resolved_path[]); +#endif + +enum elision_style_t { + TRUNCATE_TRAILING, + TRUNCATE_MIDDLE, + TRUNCATE_LEADING, + ABBREVIATE +}; + +std::string abbreviate(const std::string& str, unsigned int width, + elision_style_t elision_style = TRUNCATE_TRAILING, + const bool is_account = false, int abbrev_length = 2); + #endif // _UTIL_H @@ -1,9 +1,115 @@ +#ifdef USE_PCH +#include "pch.h" +#else #include "value.h" +#include "xml.h" #include "debug.h" #include "error.h" +#endif namespace ledger { +bool value_t::to_boolean() const +{ + if (type == BOOLEAN) { + return *(bool *) data; + } else { + value_t temp(*this); + temp.cast(BOOLEAN); + return *(bool *) temp.data; + } +} + +long value_t::to_integer() const +{ + if (type == INTEGER) { + return *(long *) data; + } else { + value_t temp(*this); + temp.cast(INTEGER); + return *(long *) temp.data; + } +} + +datetime_t value_t::to_datetime() const +{ + if (type == DATETIME) { + return *(datetime_t *) data; + } else { + value_t temp(*this); + temp.cast(DATETIME); + return *(datetime_t *) temp.data; + } +} + +amount_t value_t::to_amount() const +{ + if (type == AMOUNT) { + return *(amount_t *) data; + } else { + value_t temp(*this); + temp.cast(AMOUNT); + return *(amount_t *) temp.data; + } +} + +balance_t value_t::to_balance() const +{ + if (type == BALANCE) { + return *(balance_t *) data; + } else { + value_t temp(*this); + temp.cast(BALANCE); + return *(balance_t *) temp.data; + } +} + +balance_pair_t value_t::to_balance_pair() const +{ + if (type == BALANCE_PAIR) { + return *(balance_pair_t *) data; + } else { + value_t temp(*this); + temp.cast(BALANCE_PAIR); + return *(balance_pair_t *) temp.data; + } +} + +std::string value_t::to_string() const +{ + if (type == STRING) { + return **(std::string **) data; + } else { + std::ostringstream out; + out << *this; + return out.str(); + } +} + +xml::node_t * value_t::to_xml_node() const +{ + if (type == XML_NODE) + return *(xml::node_t **) data; + else + throw new value_error("Value is not an XML node"); +} + +void * value_t::to_pointer() const +{ + if (type == POINTER) + return *(void **) data; + else + throw new value_error("Value is not a pointer"); +} + +value_t::sequence_t * value_t::to_sequence() const +{ + if (type == SEQUENCE) + return *(sequence_t **) data; + else + throw new value_error("Value is not a sequence"); +} + void value_t::destroy() { switch (type) { @@ -16,6 +122,12 @@ void value_t::destroy() case BALANCE_PAIR: ((balance_pair_t *)data)->~balance_pair_t(); break; + case STRING: + delete *(std::string **) data; + break; + case SEQUENCE: + delete *(sequence_t **) data; + break; default: break; } @@ -54,6 +166,39 @@ value_t& value_t::operator=(const value_t& value) if (this == &value) return *this; + if (type == BOOLEAN && value.type == BOOLEAN) { + *((bool *) data) = *((bool *) value.data); + return *this; + } + else if (type == INTEGER && value.type == INTEGER) { + *((long *) data) = *((long *) value.data); + return *this; + } + else if (type == DATETIME && value.type == DATETIME) { + *((datetime_t *) data) = *((datetime_t *) value.data); + return *this; + } + else if (type == AMOUNT && value.type == AMOUNT) { + *(amount_t *) data = *(amount_t *) value.data; + return *this; + } + else if (type == BALANCE && value.type == BALANCE) { + *(balance_t *) data = *(balance_t *) value.data; + return *this; + } + else if (type == BALANCE_PAIR && value.type == BALANCE_PAIR) { + *(balance_pair_t *) data = *(balance_pair_t *) value.data; + return *this; + } + else if (type == STRING && value.type == STRING) { + **(std::string **) data = **(std::string **) value.data; + return *this; + } + else if (type == SEQUENCE && value.type == SEQUENCE) { + **(sequence_t **) data = **(sequence_t **) value.data; + return *this; + } + destroy(); switch (value.type) { @@ -81,6 +226,22 @@ value_t& value_t::operator=(const value_t& value) new((balance_pair_t *)data) balance_pair_t(*((balance_pair_t *) value.data)); break; + case STRING: + *(std::string **) data = new std::string(**(std::string **) value.data); + break; + + case XML_NODE: + *(xml::node_t **) data = *(xml::node_t **) value.data; + break; + + case POINTER: + *(void **) data = *(void **) value.data; + break; + + case SEQUENCE: + *(sequence_t **) data = new sequence_t(**(sequence_t **) value.data); + break; + default: assert(0); break; @@ -97,6 +258,12 @@ value_t& value_t::operator+=(const value_t& value) throw new value_error("Cannot add a boolean to a value"); else if (value.type == DATETIME) throw new value_error("Cannot add a date/time to a value"); + else if (value.type == XML_NODE) + throw new value_error("Cannot add an XML node to a value"); + else if (value.type == POINTER) + throw new value_error("Cannot add a pointer to a value"); + else if (value.type == SEQUENCE) + throw new value_error("Cannot add a sequence to a value"); switch (type) { case BOOLEAN: @@ -119,6 +286,8 @@ value_t& value_t::operator+=(const value_t& value) cast(BALANCE_PAIR); *((balance_pair_t *) data) += *((balance_pair_t *) value.data); break; + case STRING: + throw new value_error("Cannot add a string to an integer"); default: assert(0); break; @@ -139,6 +308,8 @@ value_t& value_t::operator+=(const value_t& value) case BALANCE_PAIR: *((datetime_t *) data) += long(*((balance_pair_t *) value.data)); break; + case STRING: + throw new value_error("Cannot add a string to an date/time"); default: assert(0); break; @@ -175,6 +346,9 @@ value_t& value_t::operator+=(const value_t& value) *((balance_pair_t *) data) += *((balance_pair_t *) value.data); break; + case STRING: + throw new value_error("Cannot add a string to an amount"); + default: assert(0); break; @@ -196,6 +370,8 @@ value_t& value_t::operator+=(const value_t& value) cast(BALANCE_PAIR); *((balance_pair_t *) data) += *((balance_pair_t *) value.data); break; + case STRING: + throw new value_error("Cannot add a string to an balance"); default: assert(0); break; @@ -216,12 +392,42 @@ value_t& value_t::operator+=(const value_t& value) case BALANCE_PAIR: *((balance_pair_t *) data) += *((balance_pair_t *) value.data); break; + case STRING: + throw new value_error("Cannot add a string to an balance pair"); default: assert(0); break; } break; + case STRING: + switch (value.type) { + case INTEGER: + throw new value_error("Cannot add an integer to a string"); + case AMOUNT: + throw new value_error("Cannot add an amount to a string"); + case BALANCE: + throw new value_error("Cannot add a balance to a string"); + case BALANCE_PAIR: + throw new value_error("Cannot add a balance pair to a string"); + case STRING: + **(std::string **) data += **(std::string **) value.data; + break; + default: + assert(0); + break; + } + break; + + case XML_NODE: + throw new value_error("Cannot add a value to an XML node"); + + case POINTER: + throw new value_error("Cannot add a value to a pointer"); + + case SEQUENCE: + throw new value_error("Cannot add a value to a sequence"); + default: assert(0); break; @@ -235,6 +441,14 @@ value_t& value_t::operator-=(const value_t& value) throw new value_error("Cannot subtract a boolean from a value"); else if (value.type == DATETIME && type != DATETIME) throw new value_error("Cannot subtract a date/time from a value"); + else if (value.type == STRING) + throw new value_error("Cannot subtract a string from a value"); + else if (value.type == XML_NODE) + throw new value_error("Cannot subtract an XML node from a value"); + else if (value.type == POINTER) + throw new value_error("Cannot subtract a pointer from a value"); + else if (value.type == SEQUENCE) + throw new value_error("Cannot subtract a sequence from a value"); switch (type) { case BOOLEAN: @@ -366,6 +580,15 @@ value_t& value_t::operator-=(const value_t& value) } break; + case STRING: + throw new value_error("Cannot subtract a value from a string"); + case XML_NODE: + throw new value_error("Cannot subtract a value from an XML node"); + case POINTER: + throw new value_error("Cannot subtract a value from a pointer"); + case SEQUENCE: + throw new value_error("Cannot subtract a value from a sequence"); + default: assert(0); break; @@ -379,11 +602,19 @@ value_t& value_t::operator-=(const value_t& value) value_t& value_t::operator*=(const value_t& value) { if (value.type == BOOLEAN) - throw new value_error("Cannot multiply a boolean by a value"); + throw new value_error("Cannot multiply a value by a boolean"); else if (value.type == DATETIME) - throw new value_error("Cannot multiply a date/time by a value"); - - if (value.realzero()) { + throw new value_error("Cannot multiply a value by a date/time"); + else if (value.type == STRING) + throw new value_error("Cannot multiply a value by a string"); + else if (value.type == XML_NODE) + throw new value_error("Cannot multiply a value by an XML node"); + else if (value.type == POINTER) + throw new value_error("Cannot multiply a value by a pointer"); + else if (value.type == SEQUENCE) + throw new value_error("Cannot multiply a value by a sequence"); + + if (value.realzero() && type != STRING) { *this = 0L; return *this; } @@ -478,6 +709,41 @@ value_t& value_t::operator*=(const value_t& value) } break; + case STRING: + switch (value.type) { + case INTEGER: { + std::string temp; + for (long i = 0; i < *(long *) value.data; i++) + temp += **(std::string **) data; + **(std::string **) data = temp; + break; + } + case AMOUNT: { + std::string temp; + value_t num(value); + num.cast(INTEGER); + for (long i = 0; i < *(long *) num.data; i++) + temp += **(std::string **) data; + **(std::string **) data = temp; + break; + } + case BALANCE: + throw new value_error("Cannot multiply a string by a balance"); + case BALANCE_PAIR: + throw new value_error("Cannot multiply a string by a balance pair"); + default: + assert(0); + break; + } + break; + + case XML_NODE: + throw new value_error("Cannot multiply an XML node by a value"); + case POINTER: + throw new value_error("Cannot multiply a pointer by a value"); + case SEQUENCE: + throw new value_error("Cannot multiply a sequence by a value"); + default: assert(0); break; @@ -491,6 +757,14 @@ value_t& value_t::operator/=(const value_t& value) throw new value_error("Cannot divide a boolean by a value"); else if (value.type == DATETIME) throw new value_error("Cannot divide a date/time by a value"); + else if (value.type == STRING) + throw new value_error("Cannot divide a string by a value"); + else if (value.type == XML_NODE) + throw new value_error("Cannot divide a value by an XML node"); + else if (value.type == POINTER) + throw new value_error("Cannot divide a pointer by a value"); + else if (value.type == SEQUENCE) + throw new value_error("Cannot divide a value by a sequence"); switch (type) { case BOOLEAN: @@ -582,6 +856,15 @@ value_t& value_t::operator/=(const value_t& value) } break; + case STRING: + throw new value_error("Cannot divide a value from a string"); + case XML_NODE: + throw new value_error("Cannot divide a value from an XML node"); + case POINTER: + throw new value_error("Cannot divide a value from a pointer"); + case SEQUENCE: + throw new value_error("Cannot divide a value from a sequence"); + default: assert(0); break; @@ -589,6 +872,172 @@ value_t& value_t::operator/=(const value_t& value) return *this; } +template <> +value_t::operator bool() const +{ + switch (type) { + case BOOLEAN: + return *(bool *) data; + case INTEGER: + return *(long *) data; + case DATETIME: + return *(datetime_t *) data; + case AMOUNT: + return *(amount_t *) data; + case BALANCE: + return *(balance_t *) data; + case BALANCE_PAIR: + return *(balance_pair_t *) data; + case STRING: + return ! (**((std::string **) data)).empty(); + case XML_NODE: + return *(xml::node_t **) data != NULL; + case POINTER: + return *(void **) data != NULL; + case SEQUENCE: + return (*(sequence_t **) data != NULL && + ! (*(sequence_t **) data)->empty()); + + default: + assert(0); + break; + } + assert(0); + return 0; +} + +template <> +value_t::operator long() const +{ + switch (type) { + case BOOLEAN: + throw new value_error("Cannot convert a boolean to an integer"); + case INTEGER: + return *((long *) data); + case DATETIME: + return *((datetime_t *) data); + case AMOUNT: + return *((amount_t *) data); + case BALANCE: + throw new value_error("Cannot convert a balance to an integer"); + case BALANCE_PAIR: + throw new value_error("Cannot convert a balance pair to an integer"); + case STRING: + throw new value_error("Cannot convert a string to an integer"); + case XML_NODE: + throw new value_error("Cannot convert an XML node to an integer"); + case POINTER: + throw new value_error("Cannot convert a pointer to an integer"); + case SEQUENCE: + throw new value_error("Cannot convert a sequence to an integer"); + + default: + assert(0); + break; + } + assert(0); + return 0; +} + +template <> +value_t::operator datetime_t() const +{ + switch (type) { + case BOOLEAN: + throw new value_error("Cannot convert a boolean to a date/time"); + case INTEGER: + return *((long *) data); + case DATETIME: + return *((datetime_t *) data); + case AMOUNT: + throw new value_error("Cannot convert an amount to a date/time"); + case BALANCE: + throw new value_error("Cannot convert a balance to a date/time"); + case BALANCE_PAIR: + throw new value_error("Cannot convert a balance pair to a date/time"); + case STRING: + throw new value_error("Cannot convert a string to a date/time"); + case XML_NODE: + throw new value_error("Cannot convert an XML node to a date/time"); + case POINTER: + throw new value_error("Cannot convert a pointer to a date/time"); + case SEQUENCE: + throw new value_error("Cannot convert a sequence to a date/time"); + + default: + assert(0); + break; + } + assert(0); + return 0; +} + +template <> +value_t::operator double() const +{ + switch (type) { + case BOOLEAN: + throw new value_error("Cannot convert a boolean to a double"); + case INTEGER: + return *((long *) data); + case DATETIME: + throw new value_error("Cannot convert a date/time to a double"); + case AMOUNT: + return *((amount_t *) data); + case BALANCE: + throw new value_error("Cannot convert a balance to a double"); + case BALANCE_PAIR: + throw new value_error("Cannot convert a balance pair to a double"); + case STRING: + throw new value_error("Cannot convert a string to a double"); + case XML_NODE: + throw new value_error("Cannot convert an XML node to a double"); + case POINTER: + throw new value_error("Cannot convert a pointer to a double"); + case SEQUENCE: + throw new value_error("Cannot convert a sequence to a double"); + + default: + assert(0); + break; + } + assert(0); + return 0; +} + +template <> +value_t::operator std::string() const +{ + switch (type) { + case BOOLEAN: + case INTEGER: + case DATETIME: + case AMOUNT: + case BALANCE: + case BALANCE_PAIR: { + value_t temp(*this); + temp.cast(STRING); + return temp; + } + + case STRING: + return **(std::string **) data; + + case XML_NODE: + return (*(xml::node_t **) data)->text(); + case POINTER: + throw new value_error("Cannot convert a pointer to a string"); + case SEQUENCE: + throw new value_error("Cannot convert a sequence to a string"); + + default: + assert(0); + break; + } + assert(0); + return 0; +} + #define DEF_VALUE_CMP_OP(OP) \ bool value_t::operator OP(const value_t& value) \ { \ @@ -613,6 +1062,15 @@ bool value_t::operator OP(const value_t& value) \ case BALANCE_PAIR: \ return *((bool *) data) OP bool(*((balance_pair_t *) value.data)); \ \ + case STRING: \ + throw new value_error("Cannot compare a boolean to a string"); \ + case XML_NODE: \ + throw new value_error("Cannot compare a boolean to an XML node"); \ + case POINTER: \ + throw new value_error("Cannot compare a boolean to a pointer"); \ + case SEQUENCE: \ + throw new value_error("Cannot compare a boolean to a sequence"); \ + \ default: \ assert(0); \ break; \ @@ -644,6 +1102,15 @@ bool value_t::operator OP(const value_t& value) \ return (balance_pair_t(*((long *) data)) OP \ *((balance_pair_t *) value.data)); \ \ + case STRING: \ + throw new value_error("Cannot compare an integer to a string"); \ + case XML_NODE: \ + throw new value_error("Cannot compare an integer to an XML node"); \ + case POINTER: \ + throw new value_error("Cannot compare an integer to a pointer"); \ + case SEQUENCE: \ + throw new value_error("Cannot compare an integer to a sequence"); \ + \ default: \ assert(0); \ break; \ @@ -665,12 +1132,18 @@ bool value_t::operator OP(const value_t& value) \ \ case AMOUNT: \ throw new value_error("Cannot compare a date/time to an amount"); \ - \ case BALANCE: \ throw new value_error("Cannot compare a date/time to a balance"); \ - \ case BALANCE_PAIR: \ throw new value_error("Cannot compare a date/time to a balance pair"); \ + case STRING: \ + throw new value_error("Cannot compare a date/time to a string"); \ + case XML_NODE: \ + throw new value_error("Cannot compare a date/time to an XML node"); \ + case POINTER: \ + throw new value_error("Cannot compare a date/time to a pointer"); \ + case SEQUENCE: \ + throw new value_error("Cannot compare a date/time to a sequence"); \ \ default: \ assert(0); \ @@ -701,6 +1174,15 @@ bool value_t::operator OP(const value_t& value) \ return (balance_t(*((amount_t *) data)) OP \ *((balance_pair_t *) value.data)); \ \ + case STRING: \ + throw new value_error("Cannot compare an amount to a string"); \ + case XML_NODE: \ + throw new value_error("Cannot compare an amount to an XML node"); \ + case POINTER: \ + throw new value_error("Cannot compare an amount to a pointer"); \ + case SEQUENCE: \ + throw new value_error("Cannot compare an amount to a sequence"); \ + \ default: \ assert(0); \ break; \ @@ -728,6 +1210,15 @@ bool value_t::operator OP(const value_t& value) \ return (*((balance_t *) data) OP \ ((balance_pair_t *) value.data)->quantity); \ \ + case STRING: \ + throw new value_error("Cannot compare a balance to a string"); \ + case XML_NODE: \ + throw new value_error("Cannot compare a balance to an XML node"); \ + case POINTER: \ + throw new value_error("Cannot compare a balance to a pointer"); \ + case SEQUENCE: \ + throw new value_error("Cannot compare a balance to a sequence"); \ + \ default: \ assert(0); \ break; \ @@ -758,12 +1249,121 @@ bool value_t::operator OP(const value_t& value) \ return (*((balance_pair_t *) data) OP \ *((balance_pair_t *) value.data)); \ \ + case STRING: \ + throw new value_error("Cannot compare a balance pair to a string"); \ + case XML_NODE: \ + throw new value_error("Cannot compare a balance pair to an XML node"); \ + case POINTER: \ + throw new value_error("Cannot compare a balance pair to a pointer"); \ + case SEQUENCE: \ + throw new value_error("Cannot compare a balance pair to a sequence"); \ + \ + default: \ + assert(0); \ + break; \ + } \ + break; \ + \ + case STRING: \ + switch (value.type) { \ + case BOOLEAN: \ + throw new value_error("Cannot compare a string to a boolean"); \ + case INTEGER: \ + throw new value_error("Cannot compare a string to an integer"); \ + case DATETIME: \ + throw new value_error("Cannot compare a string to a date/time"); \ + case AMOUNT: \ + throw new value_error("Cannot compare a string to an amount"); \ + case BALANCE: \ + throw new value_error("Cannot compare a string to a balance"); \ + case BALANCE_PAIR: \ + throw new value_error("Cannot compare a string to a balance pair"); \ + \ + case STRING: \ + return (**((std::string **) data) OP \ + **((std::string **) value.data)); \ + \ + case XML_NODE: \ + return (**((std::string **) data) OP \ + (*(xml::node_t **) value.data)->text()); \ + \ + case POINTER: \ + throw new value_error("Cannot compare a string to a pointer"); \ + case SEQUENCE: \ + throw new value_error("Cannot compare a string to a sequence"); \ + \ default: \ assert(0); \ break; \ } \ break; \ \ + case XML_NODE: \ + switch (value.type) { \ + case BOOLEAN: \ + throw new value_error("Cannot compare an XML node to a boolean"); \ + case INTEGER: \ + throw new value_error("Cannot compare an XML node to an integer"); \ + case DATETIME: \ + throw new value_error("Cannot compare an XML node to a date/time"); \ + case AMOUNT: \ + throw new value_error("Cannot compare an XML node to an amount"); \ + case BALANCE: \ + throw new value_error("Cannot compare an XML node to a balance"); \ + case BALANCE_PAIR: \ + throw new value_error("Cannot compare an XML node to a balance pair"); \ + \ + case STRING: \ + return ((*(xml::node_t **) data)->text() OP \ + **((std::string **) value.data)); \ + \ + case XML_NODE: \ + return (*((xml::node_t **) data) OP \ + *((xml::node_t **) value.data)); \ + \ + case POINTER: \ + throw new value_error("Cannot compare an XML node to a pointer"); \ + case SEQUENCE: \ + throw new value_error("Cannot compare an XML node to a sequence"); \ + \ + default: \ + assert(0); \ + break; \ + } \ + break; \ + \ + case POINTER: \ + switch (value.type) { \ + case BOOLEAN: \ + throw new value_error("Cannot compare a pointer to a boolean"); \ + case INTEGER: \ + throw new value_error("Cannot compare a pointer to an integer"); \ + case DATETIME: \ + throw new value_error("Cannot compare a pointer to a date/time"); \ + case AMOUNT: \ + throw new value_error("Cannot compare a pointer to an amount"); \ + case BALANCE: \ + throw new value_error("Cannot compare a pointer to a balance"); \ + case BALANCE_PAIR: \ + throw new value_error("Cannot compare a pointer to a balance pair"); \ + case STRING: \ + throw new value_error("Cannot compare a pointer to a string node"); \ + case XML_NODE: \ + throw new value_error("Cannot compare a pointer to an XML node"); \ + case POINTER: \ + return (*((void **) data) OP *((void **) value.data)); \ + case SEQUENCE: \ + throw new value_error("Cannot compare a pointer to a sequence"); \ + \ + default: \ + assert(0); \ + break; \ + } \ + break; \ + \ + case SEQUENCE: \ + throw new value_error("Cannot compare a value to a sequence"); \ + \ default: \ assert(0); \ break; \ @@ -777,81 +1377,6 @@ DEF_VALUE_CMP_OP(<=) DEF_VALUE_CMP_OP(>) DEF_VALUE_CMP_OP(>=) -template <> -value_t::operator long() const -{ - switch (type) { - case BOOLEAN: - throw new value_error("Cannot convert a boolean to an integer"); - case INTEGER: - return *((long *) data); - case DATETIME: - return *((datetime_t *) data); - case AMOUNT: - return *((amount_t *) data); - case BALANCE: - throw new value_error("Cannot convert a balance to an integer"); - case BALANCE_PAIR: - throw new value_error("Cannot convert a balance pair to an integer"); - - default: - assert(0); - break; - } - assert(0); - return 0; -} - -template <> -value_t::operator datetime_t() const -{ - switch (type) { - case BOOLEAN: - throw new value_error("Cannot convert a boolean to a date/time"); - case INTEGER: - return *((long *) data); - case DATETIME: - return *((datetime_t *) data); - case AMOUNT: - throw new value_error("Cannot convert an amount to a date/time"); - case BALANCE: - throw new value_error("Cannot convert a balance to a date/time"); - case BALANCE_PAIR: - throw new value_error("Cannot convert a balance pair to a date/time"); - - default: - assert(0); - break; - } - assert(0); - return 0; -} - -template <> -value_t::operator double() const -{ - switch (type) { - case BOOLEAN: - throw new value_error("Cannot convert a boolean to a double"); - case INTEGER: - return *((long *) data); - case DATETIME: - throw new value_error("Cannot convert a date/time to a double"); - case AMOUNT: - return *((amount_t *) data); - case BALANCE: - throw new value_error("Cannot convert a balance to a double"); - case BALANCE_PAIR: - throw new value_error("Cannot convert a balance pair to a double"); - - default: - assert(0); - break; - } - assert(0); - return 0; -} - void value_t::cast(type_t cast_type) { switch (type) { @@ -869,6 +1394,15 @@ void value_t::cast(type_t cast_type) throw new value_error("Cannot convert a boolean to a balance"); case BALANCE_PAIR: throw new value_error("Cannot convert a boolean to a balance pair"); + case STRING: + *(std::string **) data = new std::string(*((bool *) data) ? "true" : "false"); + break; + case XML_NODE: + throw new value_error("Cannot convert a boolean to an XML node"); + case POINTER: + throw new value_error("Cannot convert a boolean to a pointer"); + case SEQUENCE: + throw new value_error("Cannot convert a boolean to a sequence"); default: assert(0); @@ -895,6 +1429,18 @@ void value_t::cast(type_t cast_type) case BALANCE_PAIR: new((balance_pair_t *)data) balance_pair_t(*((long *) data)); break; + case STRING: { + char buf[32]; + std::sprintf(buf, "%ld", *(long *) data); + *(std::string **) data = new std::string(buf); + break; + } + case XML_NODE: + throw new value_error("Cannot convert an integer to an XML node"); + case POINTER: + throw new value_error("Cannot convert an integer to a pointer"); + case SEQUENCE: + throw new value_error("Cannot convert an integer to a sequence"); default: assert(0); @@ -918,6 +1464,14 @@ void value_t::cast(type_t cast_type) throw new value_error("Cannot convert a date/time to a balance"); case BALANCE_PAIR: throw new value_error("Cannot convert a date/time to a balance pair"); + case STRING: + throw new value_error("Cannot convert a date/time to a string"); + case XML_NODE: + throw new value_error("Cannot convert a date/time to an XML node"); + case POINTER: + throw new value_error("Cannot convert a date/time to a pointer"); + case SEQUENCE: + throw new value_error("Cannot convert a date/time to a sequence"); default: assert(0); @@ -955,6 +1509,19 @@ void value_t::cast(type_t cast_type) new((balance_pair_t *)data) balance_pair_t(temp); break; } + case STRING: { + std::ostringstream out; + out << *(amount_t *) data; + destroy(); + *(std::string **) data = new std::string(out.str()); + break; + } + case XML_NODE: + throw new value_error("Cannot convert an amount to an XML node"); + case POINTER: + throw new value_error("Cannot convert an amount to a pointer"); + case SEQUENCE: + throw new value_error("Cannot convert an amount to a sequence"); default: assert(0); @@ -987,7 +1554,7 @@ void value_t::cast(type_t cast_type) } else { throw new value_error("Cannot convert a balance with " - "multiple commodities to an amount"); + "multiple commodities to an amount"); } break; } @@ -999,6 +1566,14 @@ void value_t::cast(type_t cast_type) new((balance_pair_t *)data) balance_pair_t(temp); break; } + case STRING: + throw new value_error("Cannot convert a balance to a string"); + case XML_NODE: + throw new value_error("Cannot convert a balance to an XML node"); + case POINTER: + throw new value_error("Cannot convert a balance to a pointer"); + case SEQUENCE: + throw new value_error("Cannot convert a balance to a sequence"); default: assert(0); @@ -1031,7 +1606,7 @@ void value_t::cast(type_t cast_type) } else { throw new value_error("Cannot convert a balance pair with " - "multiple commodities to an amount"); + "multiple commodities to an amount"); } break; } @@ -1043,6 +1618,164 @@ void value_t::cast(type_t cast_type) } case BALANCE_PAIR: break; + case STRING: + throw new value_error("Cannot convert a balance pair to a string"); + case XML_NODE: + throw new value_error("Cannot convert a balance pair to an XML node"); + case POINTER: + throw new value_error("Cannot convert a balance pair to a pointer"); + case SEQUENCE: + throw new value_error("Cannot convert a balance pair to a sequence"); + + default: + assert(0); + break; + } + break; + + case STRING: + switch (cast_type) { + case BOOLEAN: { + if (**(std::string **) data == "true") { + destroy(); + *(bool *) data = true; + } + else if (**(std::string **) data == "false") { + destroy(); + *(bool *) data = false; + } + else { + throw new value_error("Cannot convert string to an boolean"); + } + break; + } + case INTEGER: { + int l = (*(std::string **) data)->length(); + const char * p = (*(std::string **) data)->c_str(); + bool alldigits = true; + for (int i = 0; i < l; i++) + if (! std::isdigit(p[i])) { + alldigits = false; + break; + } + if (alldigits) { + long temp = std::atol((*(std::string **) data)->c_str()); + destroy(); + *(long *) data = temp; + } else { + throw new value_error("Cannot convert string to an integer"); + } + break; + } + + case DATETIME: + throw new value_error("Cannot convert a string to a date/time"); + + case AMOUNT: { + amount_t temp = **(std::string **) data; + destroy(); + new((amount_t *)data) amount_t(temp); + break; + } + case BALANCE: + throw new value_error("Cannot convert a string to a balance"); + case BALANCE_PAIR: + throw new value_error("Cannot convert a string to a balance pair"); + case STRING: + break; + case XML_NODE: + throw new value_error("Cannot convert a string to an XML node"); + case POINTER: + throw new value_error("Cannot convert a string to a pointer"); + case SEQUENCE: + throw new value_error("Cannot convert a string to a sequence"); + + default: + assert(0); + break; + } + break; + + case XML_NODE: + switch (cast_type) { + case BOOLEAN: + throw new value_error("Cannot convert an XML node to a boolean"); + case INTEGER: + throw new value_error("Cannot convert an XML node to an integer"); + case DATETIME: + throw new value_error("Cannot convert an XML node to a date/time"); + case AMOUNT: + throw new value_error("Cannot convert an XML node to an amount"); + case BALANCE: + throw new value_error("Cannot convert an XML node to a balance"); + case BALANCE_PAIR: + throw new value_error("Cannot convert an XML node to a balance pair"); + case STRING: + throw new value_error("Cannot convert an XML node to a string"); + case XML_NODE: + break; + case POINTER: + throw new value_error("Cannot convert an XML node to a pointer"); + case SEQUENCE: + throw new value_error("Cannot convert an XML node to a sequence"); + + default: + assert(0); + break; + } + break; + + case POINTER: + switch (cast_type) { + case BOOLEAN: + throw new value_error("Cannot convert a pointer to a boolean"); + case INTEGER: + throw new value_error("Cannot convert a pointer to an integer"); + case DATETIME: + throw new value_error("Cannot convert a pointer to a date/time"); + case AMOUNT: + throw new value_error("Cannot convert a pointer to an amount"); + case BALANCE: + throw new value_error("Cannot convert a pointer to a balance"); + case BALANCE_PAIR: + throw new value_error("Cannot convert a pointer to a balance pair"); + case STRING: + throw new value_error("Cannot convert a pointer to a string"); + case XML_NODE: + throw new value_error("Cannot convert a pointer to an XML node"); + case POINTER: + break; + case SEQUENCE: + throw new value_error("Cannot convert a pointer to a sequence"); + + default: + assert(0); + break; + } + break; + + case SEQUENCE: + switch (cast_type) { + case BOOLEAN: + throw new value_error("Cannot convert a sequence to a boolean"); + case INTEGER: + throw new value_error("Cannot convert a sequence to an integer"); + case DATETIME: + throw new value_error("Cannot convert a sequence to a date/time"); + case AMOUNT: + throw new value_error("Cannot convert a sequence to an amount"); + case BALANCE: + throw new value_error("Cannot convert a sequence to a balance"); + case BALANCE_PAIR: + throw new value_error("Cannot convert a sequence to a balance pair"); + case STRING: + throw new value_error("Cannot convert a sequence to a string"); + case XML_NODE: \ + throw new value_error("Cannot compare a sequence to an XML node"); \ + case POINTER: + throw new value_error("Cannot convert a sequence to a pointer"); + case SEQUENCE: + break; default: assert(0); @@ -1077,6 +1810,14 @@ void value_t::negate() case BALANCE_PAIR: ((balance_pair_t *) data)->negate(); break; + case STRING: + throw new value_error("Cannot negate a string"); + case XML_NODE: + throw new value_error("Cannot negate an XML node"); + case POINTER: + throw new value_error("Cannot negate a pointer"); + case SEQUENCE: + throw new value_error("Cannot negate a sequence"); default: assert(0); @@ -1104,6 +1845,14 @@ void value_t::abs() case BALANCE_PAIR: ((balance_pair_t *) data)->abs(); break; + case STRING: + throw new value_error("Cannot take the absolute value of a string"); + case XML_NODE: + throw new value_error("Cannot take the absolute value of an XML node"); + case POINTER: + throw new value_error("Cannot take the absolute value of a pointer"); + case SEQUENCE: + throw new value_error("Cannot take the absolute value of a sequence"); default: assert(0); @@ -1126,6 +1875,14 @@ value_t value_t::value(const datetime_t& moment) const return ((balance_t *) data)->value(moment); case BALANCE_PAIR: return ((balance_pair_t *) data)->quantity.value(moment); + case STRING: + throw new value_error("Cannot find the value of a string"); + case XML_NODE: + throw new value_error("Cannot find the value of an XML node"); + case POINTER: + throw new value_error("Cannot find the value of a pointer"); + case SEQUENCE: + throw new value_error("Cannot find the value of a sequence"); } } @@ -1145,6 +1902,14 @@ void value_t::reduce() case BALANCE_PAIR: ((balance_pair_t *) data)->reduce(); break; + case STRING: + throw new value_error("Cannot reduce a string"); + case XML_NODE: + throw new value_error("Cannot reduce an XML node"); + case POINTER: + throw new value_error("Cannot reduce a pointer"); + case SEQUENCE: + throw new value_error("Cannot reduce a sequence"); } } @@ -1166,6 +1931,14 @@ void value_t::round() case BALANCE_PAIR: ((balance_pair_t *) data)->round(); break; + case STRING: + throw new value_error("Cannot round a string"); + case XML_NODE: + throw new value_error("Cannot round an XML node"); + case POINTER: + throw new value_error("Cannot round a pointer"); + case SEQUENCE: + throw new value_error("Cannot round a sequence"); } } @@ -1188,6 +1961,14 @@ value_t value_t::unround() const case BALANCE_PAIR: temp = ((balance_pair_t *) data)->unround(); break; + case STRING: + throw new value_error("Cannot un-round a string"); + case XML_NODE: + throw new value_error("Cannot un-round an XML node"); + case POINTER: + throw new value_error("Cannot un-round a pointer"); + case SEQUENCE: + throw new value_error("Cannot un-round a sequence"); } return temp; } @@ -1211,6 +1992,15 @@ value_t value_t::price() const case BALANCE_PAIR: return ((balance_pair_t *) data)->quantity.price(); + case STRING: + throw new value_error("Cannot find the price of a string"); + case XML_NODE: + throw new value_error("Cannot find the price of an XML node"); + case POINTER: + throw new value_error("Cannot find the price of a pointer"); + case SEQUENCE: + throw new value_error("Cannot find the price of a sequence"); + default: assert(0); break; @@ -1238,6 +2028,15 @@ value_t value_t::date() const case BALANCE_PAIR: return datetime_t(((balance_pair_t *) data)->quantity.date()); + case STRING: + throw new value_error("Cannot find the date of a string"); + case XML_NODE: + throw new value_error("Cannot find the date of an XML node"); + case POINTER: + throw new value_error("Cannot find the date of a pointer"); + case SEQUENCE: + throw new value_error("Cannot find the date of a sequence"); + default: assert(0); break; @@ -1254,8 +2053,15 @@ value_t value_t::strip_annotations(const bool keep_price, case BOOLEAN: case INTEGER: case DATETIME: + case STRING: + case XML_NODE: + case POINTER: return *this; + case SEQUENCE: + assert(0); // jww (2006-09-28): strip them all! + break; + case AMOUNT: return ((amount_t *) data)->strip_annotations (keep_price, keep_date, keep_tag); @@ -1293,6 +2099,15 @@ value_t value_t::cost() const else return ((balance_pair_t *) data)->quantity; + case STRING: + throw new value_error("Cannot find the cost of a string"); + case XML_NODE: + throw new value_error("Cannot find the cost of an XML node"); + case POINTER: + throw new value_error("Cannot find the cost of a pointer"); + case SEQUENCE: + throw new value_error("Cannot find the cost of a sequence"); + default: assert(0); break; @@ -1338,6 +2153,15 @@ value_t& value_t::add(const amount_t& amount, const amount_t * cost) ((balance_pair_t *) data)->add(amount, cost); break; + case STRING: + throw new value_error("Cannot add an amount to a string"); + case XML_NODE: + throw new value_error("Cannot add an amount to an XML node"); + case POINTER: + throw new value_error("Cannot add an amount to a pointer"); + case SEQUENCE: + throw new value_error("Cannot add an amount to a sequence"); + default: assert(0); break; @@ -1346,6 +2170,94 @@ value_t& value_t::add(const amount_t& amount, const amount_t * cost) return *this; } +void value_t::write(std::ostream& out, const int first_width, + const int latter_width) const +{ + switch (type) { + case BOOLEAN: + case DATETIME: + case INTEGER: + case AMOUNT: + case STRING: + case POINTER: + out << *this; + break; + + case XML_NODE: + (*(xml::node_t **) data)->write(out); + break; + + case SEQUENCE: + assert(0); // jww (2006-09-28): write them all out! + throw new value_error("Cannot write out a sequence"); + + case BALANCE: + ((balance_t *) data)->write(out, first_width, latter_width); + break; + case BALANCE_PAIR: + ((balance_pair_t *) data)->write(out, first_width, latter_width); + break; + } +} + +std::ostream& operator<<(std::ostream& out, const value_t& value) +{ + switch (value.type) { + case value_t::BOOLEAN: + out << (*((bool *) value.data) ? "true" : "false"); + break; + case value_t::INTEGER: + out << *(long *) value.data; + break; + case value_t::DATETIME: + out << *(datetime_t *) value.data; + break; + case value_t::AMOUNT: + out << *(amount_t *) value.data; + break; + case value_t::BALANCE: + out << *(balance_t *) value.data; + break; + case value_t::BALANCE_PAIR: + out << *(balance_pair_t *) value.data; + break; + case value_t::STRING: + out << **(std::string **) value.data; + break; + case value_t::XML_NODE: + if ((*(xml::node_t **) value.data)->flags & XML_NODE_IS_PARENT) + out << '<' << (*(xml::node_t **) value.data)->name() << '>'; + else + out << (*(xml::node_t **) value.data)->text(); + break; + + case value_t::POINTER: + throw new value_error("Cannot output a pointer value"); + + case value_t::SEQUENCE: { + out << '('; + bool first = true; + for (value_t::sequence_t::iterator + i = (*(value_t::sequence_t **) value.data)->begin(); + i != (*(value_t::sequence_t **) value.data)->end(); + i++) { + if (first) + first = false; + else + out << ", "; + out << *i; + } + out << ')'; + break; + } + + default: + assert(0); + break; + } + return out; +} + value_context::value_context(const value_t& _bal, const std::string& desc) throw() : bal(new value_t(_bal)), error_context(desc) {} @@ -1360,31 +2272,31 @@ void value_context::describe(std::ostream& out) const throw() if (! desc.empty()) out << desc << std::endl; - ledger::balance_t * ptr = NULL; + balance_t * ptr = NULL; out << std::right; out.width(20); switch (bal->type) { - case ledger::value_t::BOOLEAN: + case value_t::BOOLEAN: out << (*((bool *) bal->data) ? "true" : "false"); break; - case ledger::value_t::INTEGER: + case value_t::INTEGER: out << *((long *) bal->data); break; - case ledger::value_t::DATETIME: + case value_t::DATETIME: out << *((datetime_t *) bal->data); break; - case ledger::value_t::AMOUNT: - out << *((ledger::amount_t *) bal->data); + case value_t::AMOUNT: + out << *((amount_t *) bal->data); break; - case ledger::value_t::BALANCE: - ptr = (ledger::balance_t *) bal->data; + case value_t::BALANCE: + ptr = (balance_t *) bal->data; // fall through... - case ledger::value_t::BALANCE_PAIR: + case value_t::BALANCE_PAIR: if (! ptr) - ptr = &((ledger::balance_pair_t *) bal->data)->quantity; + ptr = &((balance_pair_t *) bal->data)->quantity; ptr->write(out, 20); break; @@ -1399,7 +2311,9 @@ void value_context::describe(std::ostream& out) const throw() #ifdef USE_BOOST_PYTHON +#ifndef USE_PCH #include <boost/python.hpp> +#endif using namespace boost::python; using namespace ledger; @@ -1424,6 +2338,14 @@ long value_len(value_t& value) case value_t::BALANCE_PAIR: return balance_pair_len(*((balance_pair_t *) value.data)); + case value_t::STRING: + case value_t::XML_NODE: + case value_t::POINTER: + return 1; + + case value_t::SEQUENCE: + return (*(value_t::sequence_t **) value.data)->size(); + default: assert(0); break; @@ -1460,6 +2382,18 @@ amount_t value_getitem(value_t& value, int i) case value_t::BALANCE_PAIR: return balance_pair_getitem(*((balance_pair_t *) value.data), i); + case value_t::STRING: + throw new value_error("Cannot cast a string to an amount"); + + case value_t::XML_NODE: + throw new value_error("Cannot cast an XML node to an amount"); + + case value_t::POINTER: + throw new value_error("Cannot cast a pointer to an amount"); + + case value_t::SEQUENCE: + return (*(value_t::sequence_t **) value.data)[i]; + default: assert(0); break; @@ -1475,7 +2409,7 @@ double py_to_float(value_t& value) void export_value() { - scope in_value = class_< value_t > ("Value") + class_< value_t > ("Value") .def(init<value_t>()) .def(init<balance_pair_t>()) .def(init<balance_t>()) @@ -1486,12 +2420,14 @@ void export_value() .def(init<datetime_t>()) .def(self + self) + .def(self + other<std::string>()) .def(self + other<balance_pair_t>()) .def(self + other<balance_t>()) .def(self + other<amount_t>()) .def(self + long()) .def(self + double()) + .def(other<std::string>() + self) .def(other<balance_pair_t>() + self) .def(other<balance_t>() + self) .def(other<amount_t>() + self) @@ -1499,12 +2435,14 @@ void export_value() .def(double() + self) .def(self - self) + .def(self - other<std::string>()) .def(self - other<balance_pair_t>()) .def(self - other<balance_t>()) .def(self - other<amount_t>()) .def(self - long()) .def(self - double()) + .def(other<std::string>() - self) .def(other<balance_pair_t>() - self) .def(other<balance_t>() - self) .def(other<amount_t>() - self) @@ -1512,12 +2450,14 @@ void export_value() .def(double() - self) .def(self * self) + .def(self * other<std::string>()) .def(self * other<balance_pair_t>()) .def(self * other<balance_t>()) .def(self * other<amount_t>()) .def(self * long()) .def(self * double()) + .def(other<std::string>() * self) .def(other<balance_pair_t>() * self) .def(other<balance_t>() * self) .def(other<amount_t>() * self) @@ -1525,12 +2465,14 @@ void export_value() .def(double() * self) .def(self / self) + .def(self / other<std::string>()) .def(self / other<balance_pair_t>()) .def(self / other<balance_t>()) .def(self / other<amount_t>()) .def(self / long()) .def(self / double()) + .def(other<std::string>() / self) .def(other<balance_pair_t>() / self) .def(other<balance_t>() / self) .def(other<amount_t>() / self) @@ -1540,6 +2482,7 @@ void export_value() .def(- self) .def(self += self) + .def(self += other<std::string>()) .def(self += other<balance_pair_t>()) .def(self += other<balance_t>()) .def(self += other<amount_t>()) @@ -1547,6 +2490,7 @@ void export_value() .def(self += double()) .def(self -= self) + .def(self -= other<std::string>()) .def(self -= other<balance_pair_t>()) .def(self -= other<balance_t>()) .def(self -= other<amount_t>()) @@ -1554,6 +2498,7 @@ void export_value() .def(self -= double()) .def(self *= self) + .def(self *= other<std::string>()) .def(self *= other<balance_pair_t>()) .def(self *= other<balance_t>()) .def(self *= other<amount_t>()) @@ -1561,6 +2506,7 @@ void export_value() .def(self *= double()) .def(self /= self) + .def(self /= other<std::string>()) .def(self /= other<balance_pair_t>()) .def(self /= other<balance_t>()) .def(self /= other<amount_t>()) @@ -1568,6 +2514,7 @@ void export_value() .def(self /= double()) .def(self < self) + .def(self < other<std::string>()) .def(self < other<balance_pair_t>()) .def(self < other<balance_t>()) .def(self < other<amount_t>()) @@ -1575,6 +2522,7 @@ void export_value() .def(self < other<datetime_t>()) .def(self < double()) + .def(other<std::string>() < self) .def(other<balance_pair_t>() < self) .def(other<balance_t>() < self) .def(other<amount_t>() < self) @@ -1583,6 +2531,7 @@ void export_value() .def(double() < self) .def(self <= self) + .def(self <= other<std::string>()) .def(self <= other<balance_pair_t>()) .def(self <= other<balance_t>()) .def(self <= other<amount_t>()) @@ -1590,6 +2539,7 @@ void export_value() .def(self <= other<datetime_t>()) .def(self <= double()) + .def(other<std::string>() <= self) .def(other<balance_pair_t>() <= self) .def(other<balance_t>() <= self) .def(other<amount_t>() <= self) @@ -1597,7 +2547,8 @@ void export_value() .def(other<datetime_t>() <= self) .def(double() <= self) - .def(self > self) + .def(self > self) + .def(self > other<std::string>()) .def(self > other<balance_pair_t>()) .def(self > other<balance_t>()) .def(self > other<amount_t>()) @@ -1605,6 +2556,7 @@ void export_value() .def(self > other<datetime_t>()) .def(self > double()) + .def(other<std::string>() > self) .def(other<balance_pair_t>() > self) .def(other<balance_t>() > self) .def(other<amount_t>() > self) @@ -1613,6 +2565,7 @@ void export_value() .def(double() > self) .def(self >= self) + .def(self >= other<std::string>()) .def(self >= other<balance_pair_t>()) .def(self >= other<balance_t>()) .def(self >= other<amount_t>()) @@ -1620,6 +2573,7 @@ void export_value() .def(self >= other<datetime_t>()) .def(self >= double()) + .def(other<std::string>() >= self) .def(other<balance_pair_t>() >= self) .def(other<balance_t>() >= self) .def(other<amount_t>() >= self) @@ -1628,6 +2582,7 @@ void export_value() .def(double() >= self) .def(self == self) + .def(self == other<std::string>()) .def(self == other<balance_pair_t>()) .def(self == other<balance_t>()) .def(self == other<amount_t>()) @@ -1635,6 +2590,7 @@ void export_value() .def(self == other<datetime_t>()) .def(self == double()) + .def(other<std::string>() == self) .def(other<balance_pair_t>() == self) .def(other<balance_t>() == self) .def(other<amount_t>() == self) @@ -1643,6 +2599,7 @@ void export_value() .def(double() == self) .def(self != self) + .def(self != other<std::string>()) .def(self != other<balance_pair_t>()) .def(self != other<balance_t>()) .def(self != other<amount_t>()) @@ -1650,6 +2607,7 @@ void export_value() .def(self != other<datetime_t>()) .def(self != double()) + .def(other<std::string>() != self) .def(other<balance_pair_t>() != self) .def(other<balance_t>() != self) .def(other<amount_t>() != self) @@ -1679,15 +2637,20 @@ void export_value() .def("round", &value_t::round) .def("negate", &value_t::negate) .def("negated", &value_t::negated) + .def("write", &value_t::write) ; enum_< value_t::type_t > ("ValueType") - .value("BOOLEAN", value_t::BOOLEAN) - .value("INTEGER", value_t::INTEGER) - .value("DATETIME", value_t::DATETIME) - .value("AMOUNT", value_t::AMOUNT) - .value("BALANCE", value_t::BALANCE) - .value("BALANCE_PAIR", value_t::BALANCE_PAIR) + .value("Boolean", value_t::BOOLEAN) + .value("Integer", value_t::INTEGER) + .value("DateTime", value_t::DATETIME) + .value("Amount", value_t::AMOUNT) + .value("Balance", value_t::BALANCE) + .value("BalancePair", value_t::BALANCE_PAIR) + .value("String", value_t::STRING) + .value("XmlNode", value_t::XML_NODE) + .value("Pointer", value_t::POINTER) + .value("Sequence", value_t::SEQUENCE) ; } @@ -5,10 +5,15 @@ #include "balance.h" #include "error.h" +#include <deque> #include <exception> namespace ledger { +namespace xml { + class node_t; +} + // The following type is a polymorphous value type used solely for // performance reasons. The alternative is to compute value // expressions (valexpr.cc) in terms of the largest data type, @@ -21,6 +26,8 @@ namespace ledger { class value_t { public: + typedef std::deque<value_t> sequence_t; + char data[sizeof(balance_pair_t)]; enum type_t { @@ -29,57 +36,91 @@ class value_t DATETIME, AMOUNT, BALANCE, - BALANCE_PAIR + BALANCE_PAIR, + STRING, + XML_NODE, + POINTER, + SEQUENCE } type; value_t() { + TRACE_CTOR("value_t()"); *((long *) data) = 0; type = INTEGER; } value_t(const value_t& value) : type(INTEGER) { + TRACE_CTOR("value_t(copy)"); *this = value; } value_t(const bool value) { + TRACE_CTOR("value_t(const bool)"); *((bool *) data) = value; type = BOOLEAN; } value_t(const long value) { + TRACE_CTOR("value_t(const long)"); *((long *) data) = value; type = INTEGER; } value_t(const datetime_t value) { + TRACE_CTOR("value_t(const datetime_t)"); *((datetime_t *) data) = value; type = DATETIME; } value_t(const unsigned long value) { + TRACE_CTOR("value_t(const unsigned long)"); new((amount_t *) data) amount_t(value); type = AMOUNT; } value_t(const double value) { + TRACE_CTOR("value_t(const double)"); new((amount_t *) data) amount_t(value); type = AMOUNT; } - value_t(const std::string& value) { - new((amount_t *) data) amount_t(value); - type = AMOUNT; + value_t(const std::string& value, bool literal = false) { + TRACE_CTOR("value_t(const std::string&, bool)"); + if (literal) { + type = INTEGER; + set_string(value); + } else { + new((amount_t *) data) amount_t(value); + type = AMOUNT; + } } value_t(const char * value) { + TRACE_CTOR("value_t(const char *)"); new((amount_t *) data) amount_t(value); type = AMOUNT; } value_t(const amount_t& value) { + TRACE_CTOR("value_t(const amount_t&)"); new((amount_t *)data) amount_t(value); type = AMOUNT; } value_t(const balance_t& value) : type(INTEGER) { + TRACE_CTOR("value_t(const balance_t&)"); *this = value; } value_t(const balance_pair_t& value) : type(INTEGER) { + TRACE_CTOR("value_t(const balance_pair_t&)"); *this = value; } + value_t(xml::node_t * xml_node) : type(INTEGER) { // gets set in = + TRACE_CTOR("value_t(xml::node_t *)"); + *this = xml_node; + } + value_t(void * item) : type(INTEGER) { // gets set in = + TRACE_CTOR("value_t(void *)"); + *this = item; + } + value_t(sequence_t * seq) : type(INTEGER) { // gets set in = + TRACE_CTOR("value_t(sequence_t *)"); + *this = seq; + } ~value_t() { + TRACE_DTOR("value_t"); destroy(); } @@ -127,7 +168,7 @@ class value_t if (type == AMOUNT && (amount_t *) data == &value) return *this; - + if (value.realzero()) { return *this = 0L; } else { @@ -141,7 +182,7 @@ class value_t if (type == BALANCE && (balance_t *) data == &value) return *this; - + if (value.realzero()) { return *this = 0L; } @@ -159,7 +200,7 @@ class value_t if (type == BALANCE_PAIR && (balance_pair_t *) data == &value) return *this; - + if (value.realzero()) { return *this = 0L; } @@ -173,6 +214,94 @@ class value_t return *this; } } + value_t& operator=(xml::node_t * xml_node) { + assert(xml_node); + if (type == XML_NODE && *(xml::node_t **) data == xml_node) + return *this; + + if (! xml_node) { + type = XML_NODE; + return *this = 0L; + } + else { + destroy(); + *(xml::node_t **)data = xml_node; + type = XML_NODE; + return *this; + } + } + value_t& operator=(void * item) { + assert(item); + if (type == POINTER && *(void **) data == item) + return *this; + + if (! item) { + type = POINTER; + return *this = 0L; + } + else { + destroy(); + *(void **)data = item; + type = POINTER; + return *this; + } + } + value_t& operator=(sequence_t * seq) { + assert(seq); + if (type == SEQUENCE && *(sequence_t **) data == seq) + return *this; + + if (! seq) { + type = SEQUENCE; + return *this = 0L; + } + else { + destroy(); + *(sequence_t **)data = seq; + type = SEQUENCE; + return *this; + } + } + + value_t& set_string(const std::string& str = "") { + if (type != STRING) { + destroy(); + *(std::string **) data = new std::string(str); + type = STRING; + } else { + **(std::string **) data = str; + } + return *this; + } + + bool to_boolean() const; + long to_integer() const; + datetime_t to_datetime() const; + amount_t to_amount() const; + balance_t to_balance() const; + balance_pair_t to_balance_pair() const; + std::string to_string() const; + xml::node_t * to_xml_node() const; + void * to_pointer() const; + sequence_t * to_sequence() const; + + value_t& operator[](const int index) { + sequence_t * seq = to_sequence(); + assert(seq); + return (*seq)[index]; + } + + void push_back(const value_t& value) { + sequence_t * seq = to_sequence(); + assert(seq); + return seq->push_back(value); + } + + std::size_t size() const { + sequence_t * seq = to_sequence(); + assert(seq); + return seq->size(); + } value_t& operator+=(const value_t& value); value_t& operator-=(const value_t& value); @@ -295,6 +424,12 @@ class value_t return ((balance_t *) data)->realzero(); case BALANCE_PAIR: return ((balance_pair_t *) data)->realzero(); + case STRING: + return ((std::string *) data)->empty(); + case XML_NODE: + case POINTER: + case SEQUENCE: + return *(void **) data == NULL; default: assert(0); @@ -324,8 +459,11 @@ class value_t return temp; } - void round(); - value_t unround() const; + void round(); + value_t unround() const; + + void write(std::ostream& out, const int first_width, + const int latter_width = -1) const; }; #define DEF_VALUE_AUX_OP(OP) \ @@ -363,17 +501,23 @@ value_t::operator T() const { switch (type) { case BOOLEAN: - return *((bool *) data); + return *(bool *) data; case INTEGER: - return *((long *) data); + return *(long *) data; case DATETIME: - return *((datetime_t *) data); + return *(datetime_t *) data; case AMOUNT: - return *((amount_t *) data); + return *(amount_t *) data; case BALANCE: - return *((balance_t *) data); - case BALANCE_PAIR: - return *((balance_pair_t *) data); + return *(balance_t *) data; + case STRING: + return **(std::string **) data; + case XML_NODE: + return *(xml::node_t **) data; + case POINTER: + return *(void **) data; + case SEQUENCE: + return *(sequence_t **) data; default: assert(0); @@ -383,9 +527,11 @@ value_t::operator T() const return 0; } +template <> value_t::operator bool() const; template <> value_t::operator long() const; template <> value_t::operator datetime_t() const; template <> value_t::operator double() const; +template <> value_t::operator std::string() const; inline value_t abs(const value_t& value) { value_t temp(value); @@ -393,33 +539,7 @@ inline value_t abs(const value_t& value) { return temp; } -inline std::ostream& operator<<(std::ostream& out, const value_t& value) { - switch (value.type) { - case value_t::BOOLEAN: - out << (*((bool *) value.data) ? "true" : "false"); - break; - case value_t::INTEGER: - out << *((long *) value.data); - break; - case value_t::DATETIME: - out << *((datetime_t *) value.data); - break; - case value_t::AMOUNT: - out << *((amount_t *) value.data); - break; - case value_t::BALANCE: - out << *((balance_t *) value.data); - break; - case value_t::BALANCE_PAIR: - out << *((balance_pair_t *) value.data); - break; - - default: - assert(0); - break; - } - return out; -} +std::ostream& operator<<(std::ostream& out, const value_t& value); class value_context : public error_context { @@ -1,3 +1,6 @@ +#ifdef USE_PCH +#include "pch.h" +#else #include "xml.h" #include "journal.h" #include "datetime.h" @@ -6,170 +9,299 @@ #include <iostream> #include <sstream> #include <cstring> - -extern "C" { -#if defined(HAVE_EXPAT) -#include <expat.h> // expat XML parser -#elif defined(HAVE_XMLPARSE) -#include <xmlparse.h> // expat XML parser #endif -} namespace ledger { +namespace xml { -#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) +document_t::document_t(node_t * _top, const char ** _builtins, + const int _builtins_size) + : builtins(_builtins), builtins_size(_builtins_size), + top(new terminal_node_t(this)) {} -static XML_Parser current_parser; -static unsigned int count; - -static journal_t * curr_journal; -static entry_t * curr_entry; -static commodity_t * curr_comm; -static std::string comm_flags; +int document_t::register_name(const std::string& name) +{ + int index = lookup_name_id(name); + if (index != -1) + return index; -static transaction_t::state_t curr_state; + names.push_back(name); + index = names.size() - 1; -static std::string data; -static bool ignore; -static std::string have_error; + DEBUG_PRINT("xml.lookup", this << " Inserting name: " << names.back()); -static void startElement(void *userData, const char *name, const char **attrs) -{ - if (ignore) - return; + std::pair<names_map::iterator, bool> result = + names_index.insert(names_pair(names.back(), index)); + assert(result.second); - if (std::strcmp(name, "entry") == 0) { - assert(! curr_entry); - curr_entry = new entry_t; - curr_state = transaction_t::UNCLEARED; - } - else if (std::strcmp(name, "transaction") == 0) { - assert(curr_entry); - curr_entry->add_transaction(new transaction_t); - if (curr_state != transaction_t::UNCLEARED) - curr_entry->transactions.back()->state = curr_state; - } - else if (std::strcmp(name, "commodity") == 0) { - if (std::string(attrs[0]) == "flags") - comm_flags = attrs[1]; - } - else if (std::strcmp(name, "total") == 0) { - ignore = true; - } + return index + 1000; } -static void endElement(void *userData, const char *name) +int document_t::lookup_name_id(const std::string& name) const { - if (ignore) { - if (std::strcmp(name, "total") == 0) - ignore = false; - return; + if (builtins) { + int first = 0; + int last = builtins_size; + while (first <= last) { + int mid = (first + last) / 2; // compute mid point. + + int result; + if ((result = (int)name[0] - (int)builtins[mid][0]) == 0) + result = std::strcmp(name.c_str(), builtins[mid]); + + if (result > 0) + first = mid + 1; // repeat search in top half. + else if (result < 0) + last = mid - 1; // repeat search in bottom half. + else + return mid; + } } - if (std::strcmp(name, "entry") == 0) { - assert(curr_entry); - if (curr_journal->add_entry(curr_entry)) { - count++; - } else { - account_t * acct = curr_journal->find_account("<Unknown>"); - curr_entry->add_transaction(new transaction_t(acct)); - if (curr_journal->add_entry(curr_entry)) { - count++; - } else { - delete curr_entry; - have_error = "Entry cannot be balanced"; - } + DEBUG_PRINT("xml.lookup", this << " Finding name: " << name); + + names_map::const_iterator i = names_index.find(name); + if (i != names_index.end()) + return (*i).second + 1000; + + return -1; +} + +const char * document_t::lookup_name(int id) const +{ + if (id < 1000) { + switch (id) { + case CURRENT: + return "CURRENT"; + case PARENT: + return "PARENT"; + case ROOT: + return "ROOT"; + case ALL: + return "ALL"; + default: + assert(id >= 10); + assert(builtins); + return builtins[id - 10]; } - curr_entry = NULL; - } - else if (std::strcmp(name, "en:date") == 0) { - curr_entry->_date = data; - } - else if (std::strcmp(name, "en:date_eff") == 0) { - curr_entry->_date_eff = data; - } - else if (std::strcmp(name, "en:code") == 0) { - curr_entry->code = data; - } - else if (std::strcmp(name, "en:cleared") == 0) { - curr_state = transaction_t::CLEARED; + } else { + return names[id - 1000].c_str(); } - else if (std::strcmp(name, "en:pending") == 0) { - curr_state = transaction_t::PENDING; +} + +void document_t::write(std::ostream& out) const +{ + if (top) { + out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; + top->write(out); } - else if (std::strcmp(name, "en:payee") == 0) { - curr_entry->payee = data; +} + +#ifndef THREADSAFE +document_t * node_t::document; +#endif + +node_t::node_t(document_t * _document, parent_node_t * _parent, + unsigned int _flags) + : name_id(-1), + parent(_parent), + next(NULL), prev(NULL), flags(_flags), info(NULL), attrs(NULL) +{ + TRACE_CTOR("node_t(document_t *, node_t *)"); +#ifdef THREADSAFE + document = _document; +#else + if (! document) + document = _document; +#if 0 + else + assert(document == _document); +#endif +#endif + if (parent) + parent->add_child(this); +} + +void node_t::extract() +{ + if (prev) + prev->next = next; + + if (parent) { + if (parent->_children == this) + parent->_children = next; + + if (parent->_last_child == this) + parent->_last_child = prev; + + parent = NULL; } - else if (std::strcmp(name, "tr:account") == 0) { - curr_entry->transactions.back()->account = curr_journal->find_account(data); + + if (next) + next->prev = prev; + + next = NULL; + prev = NULL; +} + +void parent_node_t::clear() +{ + node_t * child = _children; + while (child) { + node_t * next = child->next; + delete child; + child = next; } - else if (std::strcmp(name, "tr:cleared") == 0) { - curr_entry->transactions.back()->state = transaction_t::CLEARED; +} + +void parent_node_t::add_child(node_t * node) +{ + // It is important that this node is not called before children(), + // otherwise, this node will not get auto-populated. + if (_children == NULL) { + assert(_last_child == NULL); + _children = node; + node->prev = NULL; + } else { + assert(_last_child != NULL); + _last_child->next = node; + node->prev = _last_child; } - else if (std::strcmp(name, "tr:pending") == 0) { - curr_entry->transactions.back()->state = transaction_t::PENDING; + + node->parent = this; + + while (node->next) { + node_t * next_node = node->next; + assert(next_node->prev == node); + next_node->parent = this; + node = next_node; } - else if (std::strcmp(name, "tr:virtual") == 0) { - curr_entry->transactions.back()->flags |= TRANSACTION_VIRTUAL; + + _last_child = node; +} + +void parent_node_t::write(std::ostream& out, int depth) const +{ + for (int i = 0; i < depth; i++) out << " "; + out << '<' << name() << ">\n"; + + for (node_t * child = children(); child; child = child->next) + child->write(out, depth + 1); + + for (int i = 0; i < depth; i++) out << " "; + out << "</" << name() << ">\n"; +} + +void terminal_node_t::write(std::ostream& out, int depth) const +{ + for (int i = 0; i < depth; i++) out << " "; + + if (data.empty()) { + out << '<' << name() << " />\n"; + } else { + out << '<' << name() << ">" + << text() + << "</" << name() << ">\n"; } - else if (std::strcmp(name, "tr:generated") == 0) { - curr_entry->transactions.back()->flags |= TRANSACTION_AUTO; +} + +#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) + +template <typename T> +inline T * create_node(parser_t * parser) +{ + T * node = new T(parser->document, parser->node_stack.empty() ? + NULL : parser->node_stack.front()); + + node->set_name(parser->pending); + node->attrs = parser->pending_attrs; + + parser->pending = NULL; + parser->pending_attrs = NULL; + + return node; +} + +static void startElement(void *userData, const char *name, const char **attrs) +{ + parser_t * parser = static_cast<parser_t *>(userData); + + DEBUG_PRINT("xml.parse", "startElement(" << name << ")"); + + if (parser->pending) { + parent_node_t * node = create_node<parent_node_t>(parser); + if (parser->node_stack.empty()) + parser->document->top = node; + parser->node_stack.push_front(node); } - else if (std::strcmp(name, "symbol") == 0) { - assert(! curr_comm); - curr_comm = commodity_t::find_or_create(data); - assert(curr_comm); - curr_comm->add_flags(COMMODITY_STYLE_SUFFIXED); - if (! comm_flags.empty()) { - for (std::string::size_type i = 0, l = comm_flags.length(); i < l; i++) { - switch (comm_flags[i]) { - case 'P': curr_comm->drop_flags(COMMODITY_STYLE_SUFFIXED); break; - case 'S': curr_comm->add_flags(COMMODITY_STYLE_SEPARATED); break; - case 'T': curr_comm->add_flags(COMMODITY_STYLE_THOUSANDS); break; - case 'E': curr_comm->add_flags(COMMODITY_STYLE_EUROPEAN); break; - } - } + + parser->pending = name; + + if (attrs) { + for (const char ** p = attrs; *p; p += 2) { + if (! parser->pending_attrs) + parser->pending_attrs = new node_t::attrs_map; + + std::pair<node_t::attrs_map::iterator, bool> result + = parser->pending_attrs->insert(node_t::attrs_pair(*p, *(p + 1))); + assert(result.second); } } -#if 0 - // jww (2006-03-02): !!! - else if (std::strcmp(name, "price") == 0) { - assert(curr_comm); - amount_t * price = new amount_t(data); - std::ostringstream symstr; - symstr << curr_comm->symbol << " {" << *price << "}"; - commodity_t * priced_comm = - commodity_t::find_commodity(symstr.str(), true); - priced_comm->price = price; - priced_comm->base = curr_comm; - curr_comm = priced_comm; - } -#endif - else if (std::strcmp(name, "quantity") == 0) { - curr_entry->transactions.back()->amount.parse(data); - if (curr_comm) { - std::string::size_type i = data.find('.'); - if (i != std::string::npos) { - int precision = data.length() - i - 1; - if (precision > curr_comm->precision()) - curr_comm->set_precision(precision); - } - curr_entry->transactions.back()->amount.set_commodity(*curr_comm); - curr_comm = NULL; +} + +static void endElement(void *userData, const char *name) +{ + parser_t * parser = static_cast<parser_t *>(userData); + + DEBUG_PRINT("xml.parse", "endElement(" << name << ")"); + + if (parser->pending) { + terminal_node_t * node = create_node<terminal_node_t>(parser); + if (parser->node_stack.empty()) { + parser->document->top = node; + return; } } - else if (std::strcmp(name, "tr:amount") == 0) { - curr_comm = NULL; + else if (! parser->handled_data) { + assert(! parser->node_stack.empty()); + parser->node_stack.pop_front(); + } + else { + parser->handled_data = false; } } static void dataHandler(void *userData, const char *s, int len) { - if (! ignore) - data = std::string(s, len); + parser_t * parser = static_cast<parser_t *>(userData); + + DEBUG_PRINT("xml.parse", "dataHandler(" << std::string(s, len) << ")"); + + bool all_whitespace = true; + for (int i = 0; i < len; i++) { + if (! std::isspace(s[i])) { + all_whitespace = false; + break; + } + } + + // jww (2006-09-28): I currently do not support text nodes within a + // node that has children. + + if (! all_whitespace) { + terminal_node_t * node = create_node<terminal_node_t>(parser); + + node->set_text(std::string(s, len)); + parser->handled_data = true; + + if (parser->node_stack.empty()) { + parser->document->top = node; + return; + } + } } -bool xml_parser_t::test(std::istream& in) const +bool parser_t::test(std::istream& in) const { char buf[80]; @@ -180,39 +312,26 @@ bool xml_parser_t::test(std::istream& in) const return false; } - in.getline(buf, 79); - if (! std::strstr(buf, "<ledger")) { - in.clear(); - in.seekg(0, std::ios::beg); - return false; - } - in.clear(); in.seekg(0, std::ios::beg); return true; } -unsigned int xml_parser_t::parse(std::istream& in, - config_t& config, - journal_t * journal, - account_t * master, - const std::string * original_file) +document_t * parser_t::parse(std::istream& in, const char ** builtins, + const int builtins_size) { - char buf[BUFSIZ]; + std::auto_ptr<document_t> doc(new document_t(NULL, builtins, builtins_size)); - count = 0; - curr_journal = journal; - curr_entry = NULL; - curr_comm = NULL; - ignore = false; + document = doc.get(); unsigned int offset = 2; - XML_Parser parser = XML_ParserCreate(NULL); - current_parser = parser; + parser = XML_ParserCreate(NULL); XML_SetElementHandler(parser, startElement, endElement); XML_SetCharacterDataHandler(parser, dataHandler); + XML_SetUserData(parser, this); + char buf[BUFSIZ]; while (! in.eof()) { in.getline(buf, BUFSIZ - 1); std::strcat(buf, "\n"); @@ -243,110 +362,91 @@ unsigned int xml_parser_t::parse(std::istream& in, XML_ParserFree(parser); - return count; + document = NULL; + return doc.release(); } -#endif // defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) - -void xml_write_amount(std::ostream& out, const amount_t& amount, - const int depth = 0) +node_t * transaction_node_t::children() const { - for (int i = 0; i < depth; i++) out << ' '; - out << "<amount>\n"; - - commodity_t& c = amount.commodity(); - for (int i = 0; i < depth + 2; i++) out << ' '; - out << "<commodity flags=\""; - if (! (c.flags() & COMMODITY_STYLE_SUFFIXED)) out << 'P'; - if (c.flags() & COMMODITY_STYLE_SEPARATED) out << 'S'; - if (c.flags() & COMMODITY_STYLE_THOUSANDS) out << 'T'; - if (c.flags() & COMMODITY_STYLE_EUROPEAN) out << 'E'; - out << "\">\n"; - for (int i = 0; i < depth + 4; i++) out << ' '; -#if 0 - // jww (2006-03-02): !!! - if (c.price) { - out << "<symbol>" << c.base->symbol << "</symbol>\n"; - for (int i = 0; i < depth + 4; i++) out << ' '; - out << "<price>\n"; - xml_write_amount(out, *c.price, depth + 6); - for (int i = 0; i < depth + 4; i++) out << ' '; - out << "</price>\n"; - } else { - out << "<symbol>" << c.symbol << "</symbol>\n"; + if (! _children) { + terminal_node_t * account_node = + new terminal_node_t(document, const_cast<transaction_node_t *>(this)); + account_node->set_name("account"); + account_node->set_text(transaction->account->fullname()); } -#endif - for (int i = 0; i < depth + 2; i++) out << ' '; - out << "</commodity>\n"; - - for (int i = 0; i < depth + 2; i++) out << ' '; - out << "<quantity>"; - out << amount.quantity_string() << "</quantity>\n"; - - for (int i = 0; i < depth; i++) out << ' '; - out << "</amount>\n"; + return parent_node_t::children(); } -void xml_write_value(std::ostream& out, const value_t& value, - const int depth = 0) +node_t * entry_node_t::children() const { - balance_t * bal = NULL; - - for (int i = 0; i < depth; i++) out << ' '; - out << "<value type=\""; - switch (value.type) { - case value_t::BOOLEAN: out << "boolean"; break; - case value_t::INTEGER: out << "integer"; break; - case value_t::AMOUNT: out << "amount"; break; - case value_t::BALANCE: - case value_t::BALANCE_PAIR: out << "balance"; break; - } - out << "\">\n"; - - switch (value.type) { - case value_t::BOOLEAN: - for (int i = 0; i < depth + 2; i++) out << ' '; - out << "<boolean>" << *((bool *) value.data) << "</boolean>\n"; - break; - - case value_t::INTEGER: - for (int i = 0; i < depth + 2; i++) out << ' '; - out << "<integer>" << *((long *) value.data) << "</integer>\n"; - break; + if (! _children) { + if (! entry->code.empty()) { + terminal_node_t * code_node = + new terminal_node_t(document, const_cast<entry_node_t *>(this)); + code_node->set_name("code"); + code_node->set_text(entry->code); + } - case value_t::AMOUNT: - xml_write_amount(out, *((amount_t *) value.data), depth + 2); - break; + if (! entry->payee.empty()) { + terminal_node_t * payee_node = + new terminal_node_t(document, const_cast<entry_node_t *>(this)); + payee_node->set_name("payee"); + payee_node->set_text(entry->payee); + } - case value_t::BALANCE: - bal = (balance_t *) value.data; - // fall through... + for (transactions_list::iterator i = entry->transactions.begin(); + i != entry->transactions.end(); + i++) + new transaction_node_t(document, *i, const_cast<entry_node_t *>(this)); + } + return parent_node_t::children(); +} - case value_t::BALANCE_PAIR: - if (! bal) - bal = &((balance_pair_t *) value.data)->quantity; +node_t * account_node_t::children() const +{ + if (! _children) { + if (! account->name.empty()) { + terminal_node_t * name_node = + new terminal_node_t(document, const_cast<account_node_t *>(this)); + name_node->set_name("name"); + name_node->set_text(account->name); + } - for (int i = 0; i < depth + 2; i++) out << ' '; - out << "<balance>\n"; + if (! account->note.empty()) { + terminal_node_t * note_node = + new terminal_node_t(document, const_cast<account_node_t *>(this)); + note_node->set_name("note"); + note_node->set_text(account->note); + } - for (amounts_map::const_iterator i = bal->amounts.begin(); - i != bal->amounts.end(); + for (accounts_map::iterator i = account->accounts.begin(); + i != account->accounts.end(); i++) - xml_write_amount(out, (*i).second, depth + 4); + new account_node_t(document, (*i).second, const_cast<account_node_t *>(this)); + } + return parent_node_t::children(); +} - for (int i = 0; i < depth + 2; i++) out << ' '; - out << "</balance>\n"; - break; +node_t * journal_node_t::children() const +{ + if (! _children) { + account_node_t * master_account = + new account_node_t(document, journal->master, const_cast<journal_node_t *>(this)); - default: - assert(0); - break; - } + parent_node_t * entries = + new parent_node_t(document, const_cast<journal_node_t *>(this)); + entries->set_name("entries"); - for (int i = 0; i < depth; i++) out << ' '; - out << "</value>\n"; + for (entries_list::iterator i = journal->entries.begin(); + i != journal->entries.end(); + i++) + new entry_node_t(document, *i, const_cast<journal_node_t *>(this)); + } + return parent_node_t::children(); } +#endif // defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) + void output_xml_string(std::ostream& out, const std::string& str) { for (const char * s = str.c_str(); *s; s++) { @@ -367,110 +467,5 @@ void output_xml_string(std::ostream& out, const std::string& str) } } -void format_xml_entries::format_last_entry() -{ - output_stream << " <entry>\n" - << " <en:date>" << last_entry->_date.to_string("%Y/%m/%d") - << "</en:date>\n"; - - if (last_entry->_date_eff) - output_stream << " <en:date_eff>" - << last_entry->_date_eff.to_string("%Y/%m/%d") - << "</en:date_eff>\n"; - - if (! last_entry->code.empty()) { - output_stream << " <en:code>"; - output_xml_string(output_stream, last_entry->code); - output_stream << "</en:code>\n"; - } - - if (! last_entry->payee.empty()) { - output_stream << " <en:payee>"; - output_xml_string(output_stream, last_entry->payee); - output_stream << "</en:payee>\n"; - } - - bool first = true; - for (transactions_list::const_iterator i = last_entry->transactions.begin(); - i != last_entry->transactions.end(); - i++) { - if (transaction_has_xdata(**i) && - transaction_xdata_(**i).dflags & TRANSACTION_TO_DISPLAY) { - if (first) { - output_stream << " <en:transactions>\n"; - first = false; - } - - output_stream << " <transaction>\n"; - - if ((*i)->_date) - output_stream << " <tr:date>" - << (*i)->_date.to_string("%Y/%m/%d") - << "</tr:date>\n"; - - if ((*i)->_date_eff) - output_stream << " <tr:date_eff>" - << (*i)->_date_eff.to_string("%Y/%m/%d") - << "</tr:date_eff>\n"; - - if ((*i)->state == transaction_t::CLEARED) - output_stream << " <tr:cleared/>\n"; - else if ((*i)->state == transaction_t::PENDING) - output_stream << " <tr:pending/>\n"; - - if ((*i)->flags & TRANSACTION_VIRTUAL) - output_stream << " <tr:virtual/>\n"; - if ((*i)->flags & TRANSACTION_AUTO) - output_stream << " <tr:generated/>\n"; - - if ((*i)->account) { - std::string name = (*i)->account->fullname(); - if (name == "<Total>") - name = "[TOTAL]"; - else if (name == "<Unknown>") - name = "[UNKNOWN]"; - - output_stream << " <tr:account>"; - output_xml_string(output_stream, name); - output_stream << "</tr:account>\n"; - } - - output_stream << " <tr:amount>\n"; - if (transaction_xdata_(**i).dflags & TRANSACTION_COMPOUND) - xml_write_value(output_stream, - transaction_xdata_(**i).value, 10); - else - xml_write_value(output_stream, value_t((*i)->amount), 10); - output_stream << " </tr:amount>\n"; - - if ((*i)->cost) { - output_stream << " <tr:cost>\n"; - xml_write_value(output_stream, value_t(*(*i)->cost), 10); - output_stream << " </tr:cost>\n"; - } - - if (! (*i)->note.empty()) { - output_stream << " <tr:note>"; - output_xml_string(output_stream, (*i)->note); - output_stream << "</tr:note>\n"; - } - - if (show_totals) { - output_stream << " <total>\n"; - xml_write_value(output_stream, transaction_xdata_(**i).total, 10); - output_stream << " </total>\n"; - } - - output_stream << " </transaction>\n"; - - transaction_xdata_(**i).dflags |= TRANSACTION_DISPLAYED; - } - } - - if (! first) - output_stream << " </en:transactions>\n"; - - output_stream << " </entry>\n"; -} - +} // namespace xml } // namespace ledger @@ -1,46 +1,333 @@ #ifndef _XML_H #define _XML_H -#include "parser.h" -#include "format.h" +#include "value.h" +#include "debug.h" + +extern "C" { +#if defined(HAVE_EXPAT) +#include <expat.h> // expat XML parser +#elif defined(HAVE_XMLPARSE) +#include <xmlparse.h> // expat XML parser +#endif +} namespace ledger { -#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) +class transaction_t; +class entry_t; +class account_t; +class journal_t; -class xml_parser_t : public parser_t +namespace xml { + +class node_t; + +class document_t { + const char ** builtins; + const int builtins_size; + + typedef std::deque<std::string> names_array; + + names_array names; + + typedef std::map<std::string, int> names_map; + typedef std::pair<std::string, int> names_pair; + + names_map names_index; + public: - virtual bool test(std::istream& in) const; + node_t * top; + + // Ids 0-9 are reserved. 10-999 are for "builtin" names. 1000+ are + // for dynamically registered names. + enum special_names_t { + CURRENT, PARENT, ROOT, ALL + }; + + document_t(node_t * _top = NULL, const char ** _builtins = NULL, + const int _builtins_size = 0); - virtual unsigned int parse(std::istream& in, - config_t& config, - journal_t * journal, - account_t * master = NULL, - const std::string * original_file = NULL); + int register_name(const std::string& name); + int lookup_name_id(const std::string& name) const; + const char * lookup_name(int id) const; + + void write(std::ostream& out) const; }; +#define XML_NODE_IS_PARENT 0x1 + +class parent_node_t; + +class node_t +{ +public: + int name_id; +#ifdef THREADSAFE + document_t * document; +#else + static document_t * document; #endif + parent_node_t * parent; + node_t * next; + node_t * prev; + unsigned int flags; + void * info; + + typedef std::map<std::string, std::string> attrs_map; + typedef std::pair<std::string, std::string> attrs_pair; + + attrs_map * attrs; + + node_t(document_t * _document, parent_node_t * _parent = NULL, + unsigned int _flags = 0); + + virtual ~node_t() { + TRACE_DTOR("node_t"); + if (parent) extract(); + if (attrs) delete attrs; + } + + void extract(); // extract this node from its parent's child list + + virtual const char * text() const { + assert(0); + } -class format_xml_entries : public format_entries + const char * name() const { + return document->lookup_name(name_id); + } + int set_name(const char * _name) { + name_id = document->register_name(_name); + return name_id; + } + int set_name(int _name_id) { + name_id = _name_id; + return name_id; + } + + void set_attr(const char * n, const char * v) { + if (! attrs) + attrs = new attrs_map; + std::pair<attrs_map::iterator, bool> result = + attrs->insert(attrs_pair(n, v)); + assert(result.second); + } + const char * get_attr(const char * n) { + if (attrs) { + attrs_map::iterator i = attrs->find(n); + if (i != attrs->end()) + return (*i).second.c_str(); + } + return NULL; + } + + virtual void write(std::ostream& out, int depth = 0) const = 0; + +private: + node_t(const node_t&); + node_t& operator=(const node_t&); +}; + +class parent_node_t : public node_t +{ +public: + mutable node_t * _children; + mutable node_t * _last_child; + + parent_node_t(document_t * _document, parent_node_t * _parent = NULL) + : node_t(_document, _parent, XML_NODE_IS_PARENT), + _children(NULL), _last_child(NULL) + { + TRACE_CTOR("parent_node_t(document_t *, parent_node_t *)"); + } + virtual ~parent_node_t() { + TRACE_DTOR("parent_node_t"); + if (_children) clear(); + } + + virtual void clear(); // clear out all child nodes + virtual node_t * children() const { + return _children; + } + virtual node_t * last_child() { + if (! _children) + children(); + return _last_child; + } + virtual void add_child(node_t * node); + + void write(std::ostream& out, int depth = 0) const; + +private: + parent_node_t(const parent_node_t&); + parent_node_t& operator=(const parent_node_t&); +}; + +class terminal_node_t : public node_t { - bool show_totals; + std::string data; + +public: + terminal_node_t(document_t * _document, parent_node_t * _parent = NULL) + : node_t(_document, _parent) + { + TRACE_CTOR("terminal_node_t(document_t *, parent_node_t *)"); + } + + virtual const char * text() const { + return data.c_str(); + } + virtual void set_text(const char * _data) { + data = _data; + } + virtual void set_text(const std::string& _data) { + data = _data; + } + + void write(std::ostream& out, int depth = 0) const; + +private: + terminal_node_t(const node_t&); + terminal_node_t& operator=(const node_t&); +}; + +#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) + +class parser_t +{ + public: + document_t * document; + XML_Parser parser; + std::string have_error; + const char * pending; + node_t::attrs_map * pending_attrs; + bool handled_data; + + std::list<parent_node_t *> node_stack; + + parser_t() : document(NULL), pending(NULL), pending_attrs(NULL), + handled_data(false) {} + + virtual bool test(std::istream& in) const; + virtual document_t * parse(std::istream& in, + const char ** builtins = NULL, + const int builtins_size = 0); +}; + +class parse_error : public error { public: - format_xml_entries(std::ostream& output_stream, - const bool _show_totals = false) - : format_entries(output_stream, ""), show_totals(_show_totals) { - output_stream << "<?xml version=\"1.0\"?>\n" - << "<ledger version=\"2.5\">\n"; + parse_error(const std::string& reason, error_context * ctxt = NULL) throw() + : error(reason, ctxt) {} + virtual ~parse_error() throw() {} +}; + +#endif + +class transaction_node_t : public parent_node_t +{ + transaction_t * transaction; + +public: + transaction_node_t(document_t * document, transaction_t * _transaction, + parent_node_t * parent = NULL) + : parent_node_t(document, parent), transaction(_transaction) { + TRACE_CTOR("transaction_node_t(document_t *, transaction_t *, parent_node_t *)"); + set_name("transaction"); + } + virtual ~transaction_node_t() { + TRACE_DTOR("transaction_node_t"); } - virtual void flush() { - format_entries::flush(); - output_stream << "</ledger>" << std::endl; + virtual node_t * children() const; +}; + +class entry_node_t : public parent_node_t +{ + entry_t * entry; + +public: + entry_node_t(document_t * document, entry_t * _entry, + parent_node_t * parent = NULL) + : parent_node_t(document, parent), entry(_entry) { + TRACE_CTOR("entry_node_t(document_t *, entry_t *, parent_node_t *)"); + set_name("entry"); + } + virtual ~entry_node_t() { + TRACE_DTOR("entry_node_t"); + } + + virtual node_t * children() const; +}; + +class account_node_t : public parent_node_t +{ + account_t * account; + +public: + account_node_t(document_t * document, account_t * _account, + parent_node_t * parent = NULL) + : parent_node_t(document, parent), account(_account) { + TRACE_CTOR("account_node_t(document_t *, account_t *, parent_node_t *)"); + set_name("account"); + } + virtual ~account_node_t() { + TRACE_DTOR("account_node_t"); } - virtual void format_last_entry(); + virtual node_t * children() const; }; +class journal_node_t : public parent_node_t +{ + journal_t * journal; + +public: + journal_node_t(document_t * document, journal_t * _journal, + parent_node_t * parent = NULL) + : parent_node_t(document, parent), journal(_journal) { + TRACE_CTOR("journal_node_t(document_t *, journal_t *, parent_node_t *)"); + set_name("journal"); + } + virtual ~journal_node_t() { + TRACE_DTOR("journal_node_t"); + } + + virtual node_t * children() const; +}; + +template <typename T> +inline parent_node_t * wrap_node(document_t * doc, T * item, + void * parent_node = NULL) { + assert(0); +} + +template <> +inline parent_node_t * wrap_node(document_t * doc, transaction_t * xact, + void * parent_node) { + return new transaction_node_t(doc, xact, (parent_node_t *)parent_node); +} + +template <> +inline parent_node_t * wrap_node(document_t * doc, entry_t * entry, + void * parent_node) { + return new entry_node_t(doc, entry, (parent_node_t *)parent_node); +} + +template <> +inline parent_node_t * wrap_node(document_t * doc, account_t * account, + void * parent_node) { + return new account_node_t(doc, account, (parent_node_t *)parent_node); +} + +template <> +inline parent_node_t * wrap_node(document_t * doc, journal_t * journal, + void * parent_node) { + return new journal_node_t(doc, journal, (parent_node_t *)parent_node); +} + +} // namespace xml } // namespace ledger #endif // _XML_H diff --git a/xmlparse.cc b/xmlparse.cc new file mode 100644 index 00000000..c036c007 --- /dev/null +++ b/xmlparse.cc @@ -0,0 +1,473 @@ +#include "xmlparse.h" +#include "journal.h" + +#include <cstring> + +extern "C" { +#if defined(HAVE_EXPAT) +#include <expat.h> // expat XML parser +#elif defined(HAVE_XMLPARSE) +#include <xmlparse.h> // expat XML parser +#endif +} + +namespace ledger { + +#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) + +static XML_Parser current_parser; +static unsigned int count; + +static journal_t * curr_journal; +static entry_t * curr_entry; +static commodity_t * curr_comm; +static std::string comm_flags; + +static transaction_t::state_t curr_state; + +static std::string data; +static bool ignore; +static std::string have_error; + +static void startElement(void *userData, const char *name, const char **attrs) +{ + if (ignore) + return; + + if (std::strcmp(name, "entry") == 0) { + assert(! curr_entry); + curr_entry = new entry_t; + curr_state = transaction_t::UNCLEARED; + } + else if (std::strcmp(name, "transaction") == 0) { + assert(curr_entry); + curr_entry->add_transaction(new transaction_t); + if (curr_state != transaction_t::UNCLEARED) + curr_entry->transactions.back()->state = curr_state; + } + else if (std::strcmp(name, "commodity") == 0) { + if (std::string(attrs[0]) == "flags") + comm_flags = attrs[1]; + } + else if (std::strcmp(name, "total") == 0) { + ignore = true; + } +} + +static void endElement(void *userData, const char *name) +{ + if (ignore) { + if (std::strcmp(name, "total") == 0) + ignore = false; + return; + } + + if (std::strcmp(name, "entry") == 0) { + assert(curr_entry); + if (curr_journal->add_entry(curr_entry)) { + count++; + } else { + account_t * acct = curr_journal->find_account("<Unknown>"); + curr_entry->add_transaction(new transaction_t(acct)); + if (curr_journal->add_entry(curr_entry)) { + count++; + } else { + delete curr_entry; + have_error = "Entry cannot be balanced"; + } + } + curr_entry = NULL; + } + else if (std::strcmp(name, "en:date") == 0) { + curr_entry->_date = data; + } + else if (std::strcmp(name, "en:date_eff") == 0) { + curr_entry->_date_eff = data; + } + else if (std::strcmp(name, "en:code") == 0) { + curr_entry->code = data; + } + else if (std::strcmp(name, "en:cleared") == 0) { + curr_state = transaction_t::CLEARED; + } + else if (std::strcmp(name, "en:pending") == 0) { + curr_state = transaction_t::PENDING; + } + else if (std::strcmp(name, "en:payee") == 0) { + curr_entry->payee = data; + } + else if (std::strcmp(name, "tr:account") == 0) { + curr_entry->transactions.back()->account = curr_journal->find_account(data); + } + else if (std::strcmp(name, "tr:cleared") == 0) { + curr_entry->transactions.back()->state = transaction_t::CLEARED; + } + else if (std::strcmp(name, "tr:pending") == 0) { + curr_entry->transactions.back()->state = transaction_t::PENDING; + } + else if (std::strcmp(name, "tr:virtual") == 0) { + curr_entry->transactions.back()->flags |= TRANSACTION_VIRTUAL; + } + else if (std::strcmp(name, "tr:generated") == 0) { + curr_entry->transactions.back()->flags |= TRANSACTION_AUTO; + } + else if (std::strcmp(name, "symbol") == 0) { + assert(! curr_comm); + curr_comm = commodity_t::find_or_create(data); + assert(curr_comm); + curr_comm->add_flags(COMMODITY_STYLE_SUFFIXED); + if (! comm_flags.empty()) { + for (std::string::size_type i = 0, l = comm_flags.length(); i < l; i++) { + switch (comm_flags[i]) { + case 'P': curr_comm->drop_flags(COMMODITY_STYLE_SUFFIXED); break; + case 'S': curr_comm->add_flags(COMMODITY_STYLE_SEPARATED); break; + case 'T': curr_comm->add_flags(COMMODITY_STYLE_THOUSANDS); break; + case 'E': curr_comm->add_flags(COMMODITY_STYLE_EUROPEAN); break; + } + } + } + } +#if 0 + // jww (2006-03-02): !!! + else if (std::strcmp(name, "price") == 0) { + assert(curr_comm); + amount_t * price = new amount_t(data); + std::ostringstream symstr; + symstr << curr_comm->symbol << " {" << *price << "}"; + commodity_t * priced_comm = + commodity_t::find_commodity(symstr.str(), true); + priced_comm->price = price; + priced_comm->base = curr_comm; + curr_comm = priced_comm; + } +#endif + else if (std::strcmp(name, "quantity") == 0) { + curr_entry->transactions.back()->amount.parse(data); + if (curr_comm) { + std::string::size_type i = data.find('.'); + if (i != std::string::npos) { + int precision = data.length() - i - 1; + if (precision > curr_comm->precision()) + curr_comm->set_precision(precision); + } + curr_entry->transactions.back()->amount.set_commodity(*curr_comm); + curr_comm = NULL; + } + } + else if (std::strcmp(name, "tr:amount") == 0) { + curr_comm = NULL; + } +} + +static void dataHandler(void *userData, const char *s, int len) +{ + if (! ignore) + data = std::string(s, len); +} + +bool xml_parser_t::test(std::istream& in) const +{ + char buf[80]; + + in.getline(buf, 79); + if (std::strncmp(buf, "<?xml", 5) != 0) { + in.clear(); + in.seekg(0, std::ios::beg); + return false; + } + + in.getline(buf, 79); + if (! std::strstr(buf, "<ledger")) { + in.clear(); + in.seekg(0, std::ios::beg); + return false; + } + + in.clear(); + in.seekg(0, std::ios::beg); + return true; +} + +unsigned int xml_parser_t::parse(std::istream& in, + journal_t * journal, + account_t * master, + const std::string * original_file) +{ + char buf[BUFSIZ]; + + count = 0; + curr_journal = journal; + curr_entry = NULL; + curr_comm = NULL; + ignore = false; + + unsigned int offset = 2; + XML_Parser parser = XML_ParserCreate(NULL); + current_parser = parser; + + XML_SetElementHandler(parser, startElement, endElement); + XML_SetCharacterDataHandler(parser, dataHandler); + + while (! in.eof()) { + in.getline(buf, BUFSIZ - 1); + std::strcat(buf, "\n"); + bool result; + try { + result = XML_Parse(parser, buf, std::strlen(buf), in.eof()); + } + catch (const std::exception& err) { + unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; + XML_ParserFree(parser); + throw new parse_error(err.what()); + } + + if (! have_error.empty()) { + unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; + parse_error err(have_error); + std::cerr << "Error: " << err.what() << std::endl; + have_error = ""; + } + + if (! result) { + unsigned long line = XML_GetCurrentLineNumber(parser) - offset++; + const char * err = XML_ErrorString(XML_GetErrorCode(parser)); + XML_ParserFree(parser); + throw new parse_error(err); + } + } + + XML_ParserFree(parser); + + return count; +} + +#endif // defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) + +#if 0 +void xml_write_amount(std::ostream& out, const amount_t& amount, + const int depth = 0) +{ + for (int i = 0; i < depth; i++) out << ' '; + out << "<amount>\n"; + + commodity_t& c = amount.commodity(); + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "<commodity flags=\""; + if (! (c.flags() & COMMODITY_STYLE_SUFFIXED)) out << 'P'; + if (c.flags() & COMMODITY_STYLE_SEPARATED) out << 'S'; + if (c.flags() & COMMODITY_STYLE_THOUSANDS) out << 'T'; + if (c.flags() & COMMODITY_STYLE_EUROPEAN) out << 'E'; + out << "\">\n"; + for (int i = 0; i < depth + 4; i++) out << ' '; +#if 0 + // jww (2006-03-02): !!! + if (c.price) { + out << "<symbol>" << c.base->symbol << "</symbol>\n"; + for (int i = 0; i < depth + 4; i++) out << ' '; + out << "<price>\n"; + xml_write_amount(out, *c.price, depth + 6); + for (int i = 0; i < depth + 4; i++) out << ' '; + out << "</price>\n"; + } else { + out << "<symbol>" << c.symbol << "</symbol>\n"; + } +#endif + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "</commodity>\n"; + + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "<quantity>"; + out << amount.quantity_string() << "</quantity>\n"; + + for (int i = 0; i < depth; i++) out << ' '; + out << "</amount>\n"; +} + +void xml_write_value(std::ostream& out, const value_t& value, + const int depth = 0) +{ + balance_t * bal = NULL; + + for (int i = 0; i < depth; i++) out << ' '; + out << "<value type=\""; + switch (value.type) { + case value_t::BOOLEAN: out << "boolean"; break; + case value_t::INTEGER: out << "integer"; break; + case value_t::AMOUNT: out << "amount"; break; + case value_t::BALANCE: + case value_t::BALANCE_PAIR: out << "balance"; break; + } + out << "\">\n"; + + switch (value.type) { + case value_t::BOOLEAN: + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "<boolean>" << *((bool *) value.data) << "</boolean>\n"; + break; + + case value_t::INTEGER: + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "<integer>" << *((long *) value.data) << "</integer>\n"; + break; + + case value_t::AMOUNT: + xml_write_amount(out, *((amount_t *) value.data), depth + 2); + break; + + case value_t::BALANCE: + bal = (balance_t *) value.data; + // fall through... + + case value_t::BALANCE_PAIR: + if (! bal) + bal = &((balance_pair_t *) value.data)->quantity; + + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "<balance>\n"; + + for (amounts_map::const_iterator i = bal->amounts.begin(); + i != bal->amounts.end(); + i++) + xml_write_amount(out, (*i).second, depth + 4); + + for (int i = 0; i < depth + 2; i++) out << ' '; + out << "</balance>\n"; + break; + + default: + assert(0); + break; + } + + for (int i = 0; i < depth; i++) out << ' '; + out << "</value>\n"; +} + +void output_xml_string(std::ostream& out, const std::string& str) +{ + for (const char * s = str.c_str(); *s; s++) { + switch (*s) { + case '<': + out << "<"; + break; + case '>': + out << "&rt;"; + break; + case '&': + out << "&"; + break; + default: + out << *s; + break; + } + } +} + +void format_xml_entries::format_last_entry() +{ + output_stream << " <entry>\n" + << " <en:date>" << last_entry->_date.to_string("%Y/%m/%d") + << "</en:date>\n"; + + if (last_entry->_date_eff) + output_stream << " <en:date_eff>" + << last_entry->_date_eff.to_string("%Y/%m/%d") + << "</en:date_eff>\n"; + + if (! last_entry->code.empty()) { + output_stream << " <en:code>"; + output_xml_string(output_stream, last_entry->code); + output_stream << "</en:code>\n"; + } + + if (! last_entry->payee.empty()) { + output_stream << " <en:payee>"; + output_xml_string(output_stream, last_entry->payee); + output_stream << "</en:payee>\n"; + } + + bool first = true; + for (transactions_list::const_iterator i = last_entry->transactions.begin(); + i != last_entry->transactions.end(); + i++) { + if (transaction_has_xdata(**i) && + transaction_xdata_(**i).dflags & TRANSACTION_TO_DISPLAY) { + if (first) { + output_stream << " <en:transactions>\n"; + first = false; + } + + output_stream << " <transaction>\n"; + + if ((*i)->_date) + output_stream << " <tr:date>" + << (*i)->_date.to_string("%Y/%m/%d") + << "</tr:date>\n"; + + if ((*i)->_date_eff) + output_stream << " <tr:date_eff>" + << (*i)->_date_eff.to_string("%Y/%m/%d") + << "</tr:date_eff>\n"; + + if ((*i)->state == transaction_t::CLEARED) + output_stream << " <tr:cleared/>\n"; + else if ((*i)->state == transaction_t::PENDING) + output_stream << " <tr:pending/>\n"; + + if ((*i)->flags & TRANSACTION_VIRTUAL) + output_stream << " <tr:virtual/>\n"; + if ((*i)->flags & TRANSACTION_AUTO) + output_stream << " <tr:generated/>\n"; + + if ((*i)->account) { + std::string name = (*i)->account->fullname(); + if (name == "<Total>") + name = "[TOTAL]"; + else if (name == "<Unknown>") + name = "[UNKNOWN]"; + + output_stream << " <tr:account>"; + output_xml_string(output_stream, name); + output_stream << "</tr:account>\n"; + } + + output_stream << " <tr:amount>\n"; + if (transaction_xdata_(**i).dflags & TRANSACTION_COMPOUND) + xml_write_value(output_stream, + transaction_xdata_(**i).value, 10); + else + xml_write_value(output_stream, value_t((*i)->amount), 10); + output_stream << " </tr:amount>\n"; + + if ((*i)->cost) { + output_stream << " <tr:cost>\n"; + xml_write_value(output_stream, value_t(*(*i)->cost), 10); + output_stream << " </tr:cost>\n"; + } + + if (! (*i)->note.empty()) { + output_stream << " <tr:note>"; + output_xml_string(output_stream, (*i)->note); + output_stream << "</tr:note>\n"; + } + + if (show_totals) { + output_stream << " <total>\n"; + xml_write_value(output_stream, transaction_xdata_(**i).total, 10); + output_stream << " </total>\n"; + } + + output_stream << " </transaction>\n"; + + transaction_xdata_(**i).dflags |= TRANSACTION_DISPLAYED; + } + } + + if (! first) + output_stream << " </en:transactions>\n"; + + output_stream << " </entry>\n"; +} +#endif + +} // namespace ledger diff --git a/xmlparse.h b/xmlparse.h new file mode 100644 index 00000000..5670bcc2 --- /dev/null +++ b/xmlparse.h @@ -0,0 +1,25 @@ +#ifndef _XMLPARSE_H +#define _XMLPARSE_H + +#include "parser.h" + +namespace ledger { + +#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE) + +class xml_parser_t : public parser_t +{ + public: + virtual bool test(std::istream& in) const; + + virtual unsigned int parse(std::istream& in, + journal_t * journal, + account_t * master = NULL, + const std::string * original_file = NULL); +}; + +#endif + +} // namespace ledger + +#endif // _XMLPARSE_H diff --git a/xpath.cc b/xpath.cc new file mode 100644 index 00000000..3d43fff5 --- /dev/null +++ b/xpath.cc @@ -0,0 +1,2560 @@ +#ifdef USE_PCH +#include "pch.h" +#else +#include "xpath.h" +#include "debug.h" +#include "util.h" +#ifdef USE_BOOST_PYTHON +#include "py_eval.h" +#endif +#include <fstream> +#endif + +namespace ledger { +namespace xml { + +#ifndef THREADSAFE +xpath_t::token_t xpath_t::lookahead; +#endif + +void xpath_t::token_t::parse_ident(std::istream& in) +{ + if (in.eof()) { + kind = TOK_EOF; + return; + } + assert(in.good()); + + char c = peek_next_nonws(in); + + if (in.eof()) { + kind = TOK_EOF; + return; + } + assert(in.good()); + + kind = IDENT; + length = 0; + + char buf[256]; + READ_INTO_(in, buf, 255, c, length, + std::isalnum(c) || c == '_' || c == '.'); + + switch (buf[0]) { + case 'a': + if (std::strcmp(buf, "and") == 0) + kind = KW_AND; + break; + case 'd': + if (std::strcmp(buf, "div") == 0) + kind = KW_DIV; + break; + case 'e': + if (std::strcmp(buf, "eq") == 0) + kind = EQUAL; + break; + case 'f': + if (std::strcmp(buf, "false") == 0) { + kind = VALUE; + value = false; + } + break; + case 'g': + if (std::strcmp(buf, "gt") == 0) + kind = GREATER; + else if (std::strcmp(buf, "ge") == 0) + kind = GREATEREQ; + break; + case 'i': + if (std::strcmp(buf, "is") == 0) + kind = EQUAL; + break; + case 'l': + if (std::strcmp(buf, "lt") == 0) + kind = LESS; + else if (std::strcmp(buf, "le") == 0) + kind = LESSEQ; + break; + case 'm': + if (std::strcmp(buf, "mod") == 0) + kind = KW_MOD; + break; + case 'n': + if (std::strcmp(buf, "ne") == 0) + kind = NEQUAL; + break; + case 'o': + if (std::strcmp(buf, "or") == 0) + kind = KW_OR; + break; + case 't': + if (std::strcmp(buf, "true") == 0) { + kind = VALUE; + value = true; + } + break; + case 'u': + if (std::strcmp(buf, "union") == 0) + kind = KW_UNION; + break; + } + + if (kind == IDENT) + value.set_string(buf); +} + +void xpath_t::token_t::next(std::istream& in, unsigned short flags) +{ + if (in.eof()) { + kind = TOK_EOF; + return; + } + assert(in.good()); + + char c = peek_next_nonws(in); + + if (in.eof()) { + kind = TOK_EOF; + return; + } + assert(in.good()); + + symbol[0] = c; + symbol[1] = '\0'; + + length = 1; + + if (! (flags & XPATH_PARSE_RELAXED) && + (std::isalpha(c) || c == '_')) { + parse_ident(in); + return; + } + + switch (c) { + case '@': + in.get(c); + kind = AT_SYM; + break; +#if 0 + case '$': + in.get(c); + kind = DOLLAR; + break; +#endif + + case '(': + in.get(c); + kind = LPAREN; + break; + case ')': + in.get(c); + kind = RPAREN; + break; + + case '[': { + in.get(c); + if (flags & XPATH_PARSE_ALLOW_DATE) { + char buf[256]; + READ_INTO_(in, buf, 255, c, length, c != ']'); + if (c != ']') + unexpected(c, ']'); + in.get(c); + length++; + interval_t timespan(buf); + kind = VALUE; + value = timespan.first(); + } else { + kind = LBRACKET; + } + break; + } + + case ']': { + in.get(c); + kind = RBRACKET; + break; + } + + case '"': { + in.get(c); + char buf[4096]; + READ_INTO_(in, buf, 4095, c, length, c != '"'); + if (c != '"') + unexpected(c, '"'); + in.get(c); + length++; + kind = VALUE; + value.set_string(buf); + break; + } + + case '{': { + in.get(c); + amount_t temp; + temp.parse(in, AMOUNT_PARSE_NO_MIGRATE); + in.get(c); + if (c != '}') + unexpected(c, '}'); + length++; + kind = VALUE; + value = temp; + break; + } + + case '!': + in.get(c); + c = in.peek(); + if (c == '=') { + in.get(c); + symbol[1] = c; + symbol[2] = '\0'; + kind = NEQUAL; + length = 2; + break; + } +#if 0 + else if (c == '~') { + in.get(c); + symbol[1] = c; + symbol[2] = '\0'; + kind = NMATCH; + length = 2; + break; + } +#endif + kind = EXCLAM; + break; + + case '-': + in.get(c); + kind = MINUS; + break; + case '+': + in.get(c); + kind = PLUS; + break; + + case '*': + in.get(c); + if (in.peek() == '*') { + in.get(c); + symbol[1] = c; + symbol[2] = '\0'; + kind = POWER; + length = 2; + break; + } + kind = STAR; + break; + + case '/': + in.get(c); +#if 0 + if (flags & XPATH_PARSE_REGEXP) { + char buf[1024]; + READ_INTO_(in, buf, 1023, c, length, c != '/'); + in.get(c); + if (c != '/') + unexpected(c, '/'); + kind = REGEXP; + value.set_string(buf); + break; + } +#endif + kind = SLASH; + break; + + case '=': + in.get(c); +#if 0 + if (in.peek() == '~') { + in.get(c); + symbol[1] = c; + symbol[2] = '\0'; + kind = MATCH; + length = 2; + break; + } +#endif + kind = EQUAL; + break; + + case '<': + in.get(c); + if (in.peek() == '=') { + in.get(c); + symbol[1] = c; + symbol[2] = '\0'; + kind = LESSEQ; + length = 2; + break; + } + kind = LESS; + break; + + case '>': + in.get(c); + if (in.peek() == '=') { + in.get(c); + symbol[1] = c; + symbol[2] = '\0'; + kind = GREATEREQ; + length = 2; + break; + } + kind = GREATER; + break; + + case '&': + in.get(c); + kind = AMPER; + break; + case '|': + in.get(c); + kind = PIPE; + break; + case '?': + in.get(c); + kind = QUESTION; + break; + case ':': + in.get(c); + if (in.peek() == '=') { + in.get(c); + symbol[1] = c; + symbol[2] = '\0'; + kind = ASSIGN; + length = 2; + break; + } + kind = COLON; + break; + case ',': + in.get(c); + kind = COMMA; + break; +#if 0 + case '%': + in.get(c); + kind = PERCENT; + break; +#endif + + case '.': + in.get(c); + c = in.peek(); + if (c == '.') { + in.get(c); + length++; + kind = DOTDOT; + break; + } + else if (! std::isdigit(c)) { + kind = DOT; + break; + } + in.unget(); // put the first '.' back + // fall through... + + default: + if (! (flags & XPATH_PARSE_RELAXED)) { + kind = UNKNOWN; + } else { + amount_t temp; + unsigned long pos = 0; + + // When in relaxed parsing mode, we want to migrate commodity + // flags so that any precision specified by the user updates the + // current maximum displayed precision. + try { + pos = (long)in.tellg(); + + unsigned char parse_flags = 0; + if (flags & XPATH_PARSE_NO_MIGRATE) + parse_flags |= AMOUNT_PARSE_NO_MIGRATE; + if (flags & XPATH_PARSE_NO_REDUCE) + parse_flags |= AMOUNT_PARSE_NO_REDUCE; + + temp.parse(in, parse_flags); + + kind = VALUE; + value = temp; + } + catch (amount_error * err) { + // If the amount had no commodity, it must be an unambiguous + // variable reference + if (std::strcmp(err->what(), "No quantity specified for amount") == 0) { + in.clear(); + in.seekg(pos, std::ios::beg); + + c = in.peek(); + assert(! (std::isdigit(c) || c == '.')); + parse_ident(in); + } else { + throw err; + } + } + } + break; + } +} + +void xpath_t::token_t::rewind(std::istream& in) +{ + for (int i = 0; i < length; i++) + in.unget(); +} + + +void xpath_t::token_t::unexpected() +{ + switch (kind) { + case TOK_EOF: + throw new parse_error("Unexpected end of expression"); + case IDENT: + throw new parse_error(std::string("Unexpected symbol '") + + value.to_string() + "'"); + case VALUE: + throw new parse_error(std::string("Unexpected value '") + + value.to_string() + "'"); + default: + throw new parse_error(std::string("Unexpected operator '") + symbol + "'"); + } +} + +void xpath_t::token_t::unexpected(char c, char wanted) +{ + if ((unsigned char) c == 0xff) { + if (wanted) + throw new parse_error(std::string("Missing '") + wanted + "'"); + else + throw new parse_error("Unexpected end"); + } else { + if (wanted) + throw new parse_error(std::string("Invalid char '") + c + + "' (wanted '" + wanted + "')"); + else + throw new parse_error(std::string("Invalid char '") + c + "'"); + } +} + +xpath_t::op_t * xpath_t::wrap_value(const value_t& val) +{ + xpath_t::op_t * temp = new xpath_t::op_t(xpath_t::op_t::VALUE); + temp->valuep = new value_t(val); + return temp; +} + +xpath_t::op_t * xpath_t::wrap_sequence(value_t::sequence_t * val) +{ + if (val->size() == 0) { + return wrap_value(false); + } + else if (val->size() == 1) { + return wrap_value(val->front()); + } + else { + xpath_t::op_t * temp = new xpath_t::op_t(xpath_t::op_t::VALUE); + temp->valuep = new value_t(val); + return temp; + } +} + +xpath_t::op_t * xpath_t::wrap_functor(functor_t * fobj) +{ + xpath_t::op_t * temp = new xpath_t::op_t(xpath_t::op_t::FUNCTOR); + temp->functor = fobj; + return temp; +} + +#if 0 +xpath_t::op_t * xpath_t::wrap_mask(const std::string& pattern) +{ + xpath_t::op_t * temp = new xpath_t::op_t(xpath_t::op_t::MASK); + temp->mask = new mask_t(pattern); + return temp; +} +#endif + +void xpath_t::scope_t::define(const std::string& name, op_t * def) +{ + DEBUG_PRINT("ledger.xpath.syms", "Defining '" << name << "' = " << def); + + std::pair<symbol_map::iterator, bool> result + = symbols.insert(symbol_pair(name, def)); + if (! result.second) { + symbol_map::iterator i = symbols.find(name); + assert(i != symbols.end()); + (*i).second->release(); + symbols.erase(i); + + std::pair<symbol_map::iterator, bool> result + = symbols.insert(symbol_pair(name, def)); + if (! result.second) + throw new compile_error(std::string("Redefinition of '") + + name + "' in same scope"); + } + def->acquire(); +} + +xpath_t::op_t * +xpath_t::scope_t::lookup(const std::string& name) +{ + symbol_map::const_iterator i = symbols.find(name); + if (i != symbols.end()) + return (*i).second; + else if (parent) + return parent->lookup(name); + return NULL; +} + +void xpath_t::scope_t::define(const std::string& name, functor_t * def) { + define(name, wrap_functor(def)); +} + +bool xpath_t::function_scope_t::resolve(const std::string& name, + value_t& result, + scope_t * locals) +{ + switch (name[0]) { + case 'l': + if (name == "last") { + if (sequence) + result = (long)sequence->size(); + else + result = 1L; + return true; + } + break; + + case 'p': + if (name == "position") { + result = (long)index + 1; + return true; + } + break; + + case 't': + if (name == "text") { + if (value->type == value_t::XML_NODE) + result.set_string(value->to_xml_node()->text()); + else + throw new calc_error("Attempt to call text() on a non-node value"); + return true; + } + break; + } + return scope_t::resolve(name, result, locals); +} + +xpath_t::op_t::~op_t() +{ + TRACE_DTOR("xpath_t::op_t"); + + DEBUG_PRINT("ledger.xpath.memory", "Destroying " << this); + assert(refc == 0); + + switch (kind) { + case VALUE: + assert(! left); + assert(valuep); + delete valuep; + break; + + case NODE_NAME: + case FUNC_NAME: + case ATTR_NAME: + case VAR_NAME: + assert(! left); + assert(name); + delete name; + break; + + case ARG_INDEX: + break; + + case FUNCTOR: + assert(! left); + assert(functor); + delete functor; + break; + +#if 0 + case MASK: + assert(! left); + assert(mask); + delete mask; + break; +#endif + + default: + assert(kind < LAST); + if (left) + left->release(); + if (kind > TERMINALS && right) + right->release(); + break; + } +} + +void xpath_t::op_t::get_value(value_t& result) const +{ + switch (kind) { + case VALUE: + result = *valuep; + break; + case ARG_INDEX: + result = (long)arg_index; + break; + default: { + std::ostringstream buf; + write(buf); + throw new calc_error + (std::string("Cannot determine value of expression symbol '") + + buf.str() + "'"); + } + } +} + +xpath_t::op_t * +xpath_t::parse_value_term(std::istream& in, unsigned short flags) const +{ + std::auto_ptr<op_t> node; + + token_t& tok = next_token(in, flags); + + switch (tok.kind) { + case token_t::VALUE: + node.reset(new op_t(op_t::VALUE)); + node->valuep = new value_t(tok.value); + break; + + case token_t::IDENT: { +#ifdef USE_BOOST_PYTHON + if (tok.value->to_string() == "lambda") // special + try { + char c, buf[4096]; + + std::strcpy(buf, "lambda "); + READ_INTO(in, &buf[7], 4000, c, true); + + op_t * eval = new op_t(op_t::O_EVAL); + op_t * lambda = new op_t(op_t::FUNCTOR); + lambda->functor = new python_functor_t(python_eval(buf)); + eval->set_left(lambda); + op_t * sym = new op_t(op_t::SYMBOL); + sym->name = new std::string("__ptr"); + eval->set_right(sym); + + node.reset(eval); + + goto done; + } + catch(const boost::python::error_already_set&) { + throw new parse_error("Error parsing lambda expression"); + } +#endif + + std::string ident = tok.value.to_string(); + if (std::isdigit(ident[0])) { + node.reset(new op_t(op_t::ARG_INDEX)); + node->arg_index = std::atol(ident.c_str()); + } else { + node.reset(new op_t(op_t::NODE_NAME)); + node->name = new std::string(ident); + } + + // An identifier followed by ( represents a function call + tok = next_token(in, flags); + if (tok.kind == token_t::LPAREN) { + node->kind = op_t::FUNC_NAME; + + std::auto_ptr<op_t> call_node; + call_node.reset(new op_t(op_t::O_EVAL)); + call_node->set_left(node.release()); + call_node->set_right(parse_value_expr(in, flags | XPATH_PARSE_PARTIAL)); + + tok = next_token(in, flags); + if (tok.kind != token_t::RPAREN) + tok.unexpected(); // jww (2006-09-09): wanted ) + + node.reset(call_node.release()); + } else { + push_token(tok); + } + break; + } + + case token_t::AT_SYM: + tok = next_token(in, flags); + if (tok.kind != token_t::IDENT) + throw parse_error("@ symbol must be followed by attribute name"); + + node.reset(new op_t(op_t::ATTR_NAME)); + node->name = new std::string(tok.value.to_string()); + break; + +#if 0 + case token_t::DOLLAR: + tok = next_token(in, flags); + if (tok.kind != token_t::IDENT) + throw parse_error("$ symbol must be followed by variable name"); + + node.reset(new op_t(op_t::VAR_NAME)); + node->name = new std::string(tok.value.to_string()); + break; +#endif + + case token_t::DOT: + node.reset(new op_t(op_t::NODE_ID)); + node->name_id = document_t::CURRENT; + break; + case token_t::DOTDOT: + node.reset(new op_t(op_t::NODE_ID)); + node->name_id = document_t::PARENT; + break; + case token_t::SLASH: + node.reset(new op_t(op_t::NODE_ID)); + node->name_id = document_t::ROOT; + break; + case token_t::STAR: + node.reset(new op_t(op_t::NODE_ID)); + node->name_id = document_t::ALL; + break; + + case token_t::LPAREN: + node.reset(parse_value_expr(in, flags | XPATH_PARSE_PARTIAL)); + if (! node.get()) + throw new parse_error(std::string(tok.symbol) + + " operator not followed by argument"); + tok = next_token(in, flags); + if (tok.kind != token_t::RPAREN) + tok.unexpected(); // jww (2006-09-09): wanted ) + break; + +#if 0 + case token_t::REGEXP: + node.reset(wrap_mask(tok.value.to_string())); + break; +#endif + + default: + push_token(tok); + break; + } + + done: + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_predicate_expr(std::istream& in, unsigned short flags) const +{ + std::auto_ptr<op_t> node(parse_value_term(in, flags)); + + if (node.get()) { + token_t& tok = next_token(in, flags); + while (tok.kind == token_t::LBRACKET) { + std::auto_ptr<op_t> prev(node.release()); + node.reset(new op_t(op_t::O_PRED)); + node->set_left(prev.release()); + node->set_right(parse_value_expr(in, flags | XPATH_PARSE_PARTIAL)); + if (! node->right) + throw new parse_error("[ operator not followed by valid expression"); + + tok = next_token(in, flags); + if (tok.kind != token_t::RBRACKET) + tok.unexpected(); // jww (2006-09-09): wanted ] + + tok = next_token(in, flags); + } + + push_token(tok); + } + + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_path_expr(std::istream& in, unsigned short flags) const +{ + std::auto_ptr<op_t> node(parse_predicate_expr(in, flags)); + + if (node.get()) { + // If the beginning of the path was /, just put it back; this + // makes parsing much simpler. + if (node->kind == op_t::NODE_ID && node->name_id == document_t::ROOT) + push_token(); + + token_t& tok = next_token(in, flags); + while (tok.kind == token_t::SLASH) { + std::auto_ptr<op_t> prev(node.release()); + + tok = next_token(in, flags); + node.reset(new op_t(tok.kind == token_t::SLASH ? + op_t::O_RFIND : op_t::O_FIND)); + if (tok.kind != token_t::SLASH) + push_token(tok); + + node->set_left(prev.release()); + node->set_right(parse_predicate_expr(in, flags)); + if (! node->right) + throw new parse_error("/ operator not followed by a valid term"); + + tok = next_token(in, flags); + } + + push_token(tok); + } + + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_unary_expr(std::istream& in, unsigned short flags) const +{ + std::auto_ptr<op_t> node; + + token_t& tok = next_token(in, flags); + + switch (tok.kind) { + case token_t::EXCLAM: { + std::auto_ptr<op_t> expr(parse_path_expr(in, flags)); + if (! expr.get()) + throw new parse_error(std::string(tok.symbol) + + " operator not followed by argument"); + // A very quick optimization + if (expr->kind == op_t::VALUE) { + *expr->valuep = ! *expr->valuep; + node.reset(expr.release()); + } else { + node.reset(new op_t(op_t::O_NOT)); + node->set_left(expr.release()); + } + break; + } + + case token_t::MINUS: { + std::auto_ptr<op_t> expr(parse_path_expr(in, flags)); + if (! expr.get()) + throw new parse_error(std::string(tok.symbol) + + " operator not followed by argument"); + // A very quick optimization + if (expr->kind == op_t::VALUE) { + expr->valuep->negate(); + node.reset(expr.release()); + } else { + node.reset(new op_t(op_t::O_NEG)); + node->set_left(expr.release()); + } + break; + } + +#if 0 + case token_t::PERCENT: { + std::auto_ptr<op_t> expr(parse_path_expr(in, flags)); + if (! expr.get()) + throw new parse_error(std::string(tok.symbol) + + " operator not followed by argument"); + // A very quick optimization + if (expr->kind == op_t::VALUE) { + static value_t perc("100.0%"); + *expr->valuep = perc * *expr->valuep; + node.reset(expr.release()); + } else { + node.reset(new op_t(op_t::O_PERC)); + node->set_left(expr.release()); + } + break; + } +#endif + + default: + push_token(tok); + node.reset(parse_path_expr(in, flags)); + break; + } + + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_union_expr(std::istream& in, unsigned short flags) const +{ + std::auto_ptr<op_t> node(parse_unary_expr(in, flags)); + + if (node.get()) { + token_t& tok = next_token(in, flags); + if (tok.kind == token_t::PIPE || tok.kind == token_t::KW_UNION) { + std::auto_ptr<op_t> prev(node.release()); + node.reset(new op_t(op_t::O_UNION)); + node->set_left(prev.release()); + node->set_right(parse_union_expr(in, flags)); + if (! node->right) + throw new parse_error(std::string(tok.symbol) + + " operator not followed by argument"); + } else { + push_token(tok); + } + } + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_mul_expr(std::istream& in, unsigned short flags) const +{ + std::auto_ptr<op_t> node(parse_union_expr(in, flags)); + + if (node.get()) { + token_t& tok = next_token(in, flags); + if (tok.kind == token_t::STAR || tok.kind == token_t::KW_DIV) { + std::auto_ptr<op_t> prev(node.release()); + node.reset(new op_t(tok.kind == token_t::STAR ? + op_t::O_MUL : op_t::O_DIV)); + node->set_left(prev.release()); + node->set_right(parse_mul_expr(in, flags)); + if (! node->right) + throw new parse_error(std::string(tok.symbol) + + " operator not followed by argument"); + + tok = next_token(in, flags); + } + push_token(tok); + } + + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_add_expr(std::istream& in, unsigned short flags) const +{ + std::auto_ptr<op_t> node(parse_mul_expr(in, flags)); + + if (node.get()) { + token_t& tok = next_token(in, flags); + if (tok.kind == token_t::PLUS || + tok.kind == token_t::MINUS) { + std::auto_ptr<op_t> prev(node.release()); + node.reset(new op_t(tok.kind == token_t::PLUS ? + op_t::O_ADD : op_t::O_SUB)); + node->set_left(prev.release()); + node->set_right(parse_add_expr(in, flags)); + if (! node->right) + throw new parse_error(std::string(tok.symbol) + + " operator not followed by argument"); + + tok = next_token(in, flags); + } + push_token(tok); + } + + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_logic_expr(std::istream& in, unsigned short flags) const +{ + std::auto_ptr<op_t> node(parse_add_expr(in, flags)); + + if (node.get()) { + op_t::kind_t kind = op_t::LAST; + + unsigned short _flags = flags; + + token_t& tok = next_token(in, flags); + switch (tok.kind) { + case token_t::ASSIGN: + kind = op_t::O_DEFINE; + break; + case token_t::EQUAL: + kind = op_t::O_EQ; + break; + case token_t::NEQUAL: + kind = op_t::O_NEQ; + break; +#if 0 + case token_t::MATCH: + kind = op_t::O_MATCH; + _flags |= XPATH_PARSE_REGEXP; + break; + case token_t::NMATCH: + kind = op_t::O_NMATCH; + _flags |= XPATH_PARSE_REGEXP; + break; +#endif + case token_t::LESS: + kind = op_t::O_LT; + break; + case token_t::LESSEQ: + kind = op_t::O_LTE; + break; + case token_t::GREATER: + kind = op_t::O_GT; + break; + case token_t::GREATEREQ: + kind = op_t::O_GTE; + break; + default: + push_token(tok); + break; + } + + if (kind != op_t::LAST) { + std::auto_ptr<op_t> prev(node.release()); + node.reset(new op_t(kind)); + node->set_left(prev.release()); + if (kind == op_t::O_DEFINE) + node->set_right(parse_querycolon_expr(in, flags)); + else + node->set_right(parse_add_expr(in, _flags)); + + if (! node->right) { + if (tok.kind == token_t::PLUS) + throw new parse_error(std::string(tok.symbol) + + " operator not followed by argument"); + else + throw new parse_error(std::string(tok.symbol) + + " operator not followed by argument"); + } + } + } + + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_and_expr(std::istream& in, unsigned short flags) const +{ + std::auto_ptr<op_t> node(parse_logic_expr(in, flags)); + + if (node.get()) { + token_t& tok = next_token(in, flags); + if (tok.kind == token_t::KW_AND) { + std::auto_ptr<op_t> prev(node.release()); + node.reset(new op_t(op_t::O_AND)); + node->set_left(prev.release()); + node->set_right(parse_and_expr(in, flags)); + if (! node->right) + throw new parse_error(std::string(tok.symbol) + + " operator not followed by argument"); + } else { + push_token(tok); + } + } + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_or_expr(std::istream& in, unsigned short flags) const +{ + std::auto_ptr<op_t> node(parse_and_expr(in, flags)); + + if (node.get()) { + token_t& tok = next_token(in, flags); + if (tok.kind == token_t::KW_OR) { + std::auto_ptr<op_t> prev(node.release()); + node.reset(new op_t(op_t::O_OR)); + node->set_left(prev.release()); + node->set_right(parse_or_expr(in, flags)); + if (! node->right) + throw new parse_error(std::string(tok.symbol) + + " operator not followed by argument"); + } else { + push_token(tok); + } + } + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_querycolon_expr(std::istream& in, unsigned short flags) const +{ + std::auto_ptr<op_t> node(parse_or_expr(in, flags)); + + if (node.get()) { + token_t& tok = next_token(in, flags); + if (tok.kind == token_t::QUESTION) { + std::auto_ptr<op_t> prev(node.release()); + node.reset(new op_t(op_t::O_QUES)); + node->set_left(prev.release()); + node->set_right(new op_t(op_t::O_COLON)); + node->right->set_left(parse_querycolon_expr(in, flags)); + if (! node->right) + throw new parse_error(std::string(tok.symbol) + + " operator not followed by argument"); + tok = next_token(in, flags); + if (tok.kind != token_t::COLON) + tok.unexpected(); // jww (2006-09-09): wanted : + node->right->set_right(parse_querycolon_expr(in, flags)); + if (! node->right) + throw new parse_error(std::string(tok.symbol) + + " operator not followed by argument"); + } else { + push_token(tok); + } + } + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_value_expr(std::istream& in, unsigned short flags) const +{ + std::auto_ptr<op_t> node(parse_querycolon_expr(in, flags)); + + if (node.get()) { + token_t& tok = next_token(in, flags); + if (tok.kind == token_t::COMMA) { + std::auto_ptr<op_t> prev(node.release()); + node.reset(new op_t(op_t::O_COMMA)); + node->set_left(prev.release()); + node->set_right(parse_value_expr(in, flags)); + if (! node->right) + throw new parse_error(std::string(tok.symbol) + + " operator not followed by argument"); + tok = next_token(in, flags); + } + + if (tok.kind != token_t::TOK_EOF) { + if (flags & XPATH_PARSE_PARTIAL) + push_token(tok); + else + tok.unexpected(); + } + } + else if (! (flags & XPATH_PARSE_PARTIAL)) { + throw new parse_error(std::string("Failed to parse value expression")); + } + + return node.release(); +} + +xpath_t::op_t * +xpath_t::parse_expr(std::istream& in, unsigned short flags) const +{ + std::auto_ptr<op_t> node(parse_value_expr(in, flags)); + + if (use_lookahead) { + use_lookahead = false; + lookahead.rewind(in); + } + lookahead.clear(); + + return node.release(); +} + +xpath_t::op_t * +xpath_t::op_t::new_node(kind_t kind, op_t * left, op_t * right) +{ + std::auto_ptr<op_t> node(new op_t(kind)); + if (left) + node->set_left(left); + if (right) + node->set_right(right); + return node.release(); +} + +xpath_t::op_t * +xpath_t::op_t::copy(op_t * left, op_t * right) const +{ + std::auto_ptr<op_t> node(new op_t(kind)); + if (left) + node->set_left(left); + if (right) + node->set_right(right); + return node.release(); +} + +void xpath_t::op_t::find_values(value_t * context, scope_t * scope, + value_t::sequence_t& result_seq, + bool recursive) +{ + xpath_t expr(compile(context, scope, true)); + + if (expr->kind == VALUE) + append_value(*expr->valuep, result_seq); + + if (recursive) { + if (context->type == value_t::XML_NODE) { + node_t * ptr = context->to_xml_node(); + if (ptr->flags & XML_NODE_IS_PARENT) { + parent_node_t * parent = static_cast<parent_node_t *>(ptr); + for (node_t * node = parent->children(); + node; + node = node->next) { + value_t temp(node); + find_values(&temp, scope, result_seq, recursive); + } + } + } else { + throw new calc_error("Recursive path selection on a non-node value"); + } + } +} + +bool xpath_t::op_t::test_value(value_t * context, scope_t * scope, + int index) +{ + xpath_t expr(compile(context, scope, true)); + + if (expr->kind != VALUE) + throw new calc_error("Predicate expression does not yield a constant value"); + + switch (expr->valuep->type) { + case value_t::INTEGER: + case value_t::AMOUNT: + return *expr->valuep == (long)index + 1; + + default: + return expr->valuep->to_boolean(); + } +} + +xpath_t::op_t * xpath_t::op_t::defer_sequence(value_t::sequence_t& result_seq) +{ + // If not all of the elements were constants, transform the result + // into an expression sequence using O_COMMA. + + assert(! result_seq.empty()); + + if (result_seq.size() == 1) + return wrap_value(result_seq.front())->acquire(); + + value_t::sequence_t::iterator i = result_seq.begin(); + + std::auto_ptr<op_t> lit_seq(new op_t(O_COMMA)); + + lit_seq->set_left(wrap_value(*i++)); + op_t ** opp = &lit_seq->right; + + for (; i != result_seq.end(); i++) { + if (*opp) { + op_t * val = *opp; + *opp = new op_t(O_COMMA); + (*opp)->set_left(val); + opp = &(*opp)->right; + } + + if ((*i).type != value_t::POINTER) + *opp = wrap_value(*i)->acquire(); + else + *opp = static_cast<op_t *>((*i).to_pointer()); + } + + return lit_seq.release(); +} + +void xpath_t::op_t::append_value(value_t& value, + value_t::sequence_t& result_seq) +{ + if (value.type == value_t::SEQUENCE) { + value_t::sequence_t * subseq = value.to_sequence(); + for (value_t::sequence_t::iterator i = subseq->begin(); + i != subseq->end(); + i++) + result_seq.push_back(*i); + } else { + result_seq.push_back(value); + } +} + +xpath_t::op_t * xpath_t::op_t::compile(value_t * context, scope_t * scope, + bool resolve) +{ + try { + switch (kind) { + case VALUE: + return acquire(); + + case NODE_ID: + switch (name_id) { + case document_t::CURRENT: + return wrap_value(context)->acquire(); + + case document_t::PARENT: + if (context->type != value_t::XML_NODE) + throw new compile_error("Referencing parent node from a non-node value"); + else if (context->to_xml_node()->parent) + return wrap_value(context->to_xml_node()->parent)->acquire(); + else + throw new compile_error("Referencing parent node from the root node"); + + case document_t::ROOT: + if (context->type != value_t::XML_NODE) + throw new compile_error("Referencing root node from a non-node value"); + else + return wrap_value(context->to_xml_node()->document->top)->acquire(); + + case document_t::ALL: { + if (context->type != value_t::XML_NODE) + throw new compile_error("Referencing child nodes from a non-node value"); + + node_t * ptr = context->to_xml_node(); + if (! (ptr->flags & XML_NODE_IS_PARENT)) + throw new compile_error("Request for child nodes of a leaf node"); + + parent_node_t * parent = static_cast<parent_node_t *>(ptr); + + value_t::sequence_t * nodes = new value_t::sequence_t; + for (node_t * node = parent->children(); node; node = node->next) + nodes->push_back(node); + + return wrap_value(nodes)->acquire(); + } + + default: + break; // pass down to the NODE_NAME case + } + // fall through... + + case NODE_NAME: + if (context->type == value_t::XML_NODE) { + node_t * ptr = context->to_xml_node(); + if (resolve) { + // First, look up the symbol as a node name within the current + // context. If any exist, then return the set of names. + + value_t::sequence_t * nodes = new value_t::sequence_t; + + if (ptr->flags & XML_NODE_IS_PARENT) { + parent_node_t * parent = static_cast<parent_node_t *>(ptr); + for (node_t * node = parent->children(); + node; + node = node->next) { + if ((kind == NODE_NAME && + std::strcmp(name->c_str(), node->name()) == 0) || + (kind == NODE_ID && name_id == node->name_id)) + nodes->push_back(node); + } + } + return wrap_value(nodes)->acquire(); + } else { + assert(ptr); + int id = ptr->document->lookup_name_id(*name); + if (id != -1) { + op_t * node = new_node(NODE_ID); + node->name_id = id; + return node->acquire(); + } + } + } + return acquire(); + + case ATTR_NAME: { + // jww (2006-09-29): Attrs should map strings to values, not strings + const char * value = context->to_xml_node()->get_attr(name->c_str()); + return wrap_value(value)->acquire(); + } + + case VAR_NAME: + case FUNC_NAME: + if (scope) { + if (resolve) { + value_t temp; + if (scope->resolve(*name, temp)) + return wrap_value(temp)->acquire(); + } + if (op_t * def = scope->lookup(*name)) + return def->compile(context, scope, resolve); + } + return acquire(); + + case ARG_INDEX: + if (scope && scope->kind == scope_t::ARGUMENT) { + assert(scope->args.type == value_t::SEQUENCE); + if (arg_index < scope->args.to_sequence()->size()) + return wrap_value((*scope->args.to_sequence())[arg_index])->acquire(); + else + throw new compile_error("Reference to non-existing argument"); + } else { + return acquire(); + } + + case FUNCTOR: + if (resolve) { + value_t temp; + (*functor)(temp, scope); + return wrap_value(temp)->acquire(); + } else { + return acquire(); + } + break; + +#if 0 + case MASK: + return acquire(); +#endif + + case O_NOT: { + assert(left); + xpath_t expr(left->compile(context, scope, resolve)); + if (! expr->constant()) { + if (left == expr) + return acquire(); + else + return copy(expr)->acquire(); + } + + if (left == expr) { + if (expr->valuep->strip_annotations()) + return wrap_value(false)->acquire(); + else + return wrap_value(true)->acquire(); + } else { + if (expr->valuep->strip_annotations()) + *expr->valuep = false; + else + *expr->valuep = true; + + return expr->acquire(); + } + } + + case O_NEG: { + assert(left); + xpath_t expr(left->compile(context, scope, resolve)); + if (! expr->constant()) { + if (left == expr) + return acquire(); + else + return copy(expr)->acquire(); + } + + if (left == expr) { + return wrap_value(expr->valuep->negated())->acquire(); + } else { + expr->valuep->negate(); + return expr->acquire(); + } + } + + case O_UNION: { + assert(left); + assert(right); + xpath_t lexpr(left->compile(context, scope, resolve)); + xpath_t rexpr(right->compile(context, scope, resolve)); + if (! lexpr->constant() || ! rexpr->constant()) { + if (left == lexpr && right == rexpr) + return acquire(); + else + return copy(lexpr, rexpr)->acquire(); + } + + std::auto_ptr<value_t::sequence_t> result_seq(new value_t::sequence_t); + + append_value(*lexpr->valuep, *result_seq); + append_value(*rexpr->valuep, *result_seq); + + if (result_seq->size() == 1) + return wrap_value(result_seq->front())->acquire(); + else + return wrap_sequence(result_seq.release())->acquire(); + break; + } + + case O_ADD: + case O_SUB: + case O_MUL: + case O_DIV: { + assert(left); + assert(right); + xpath_t lexpr(left->compile(context, scope, resolve)); + xpath_t rexpr(right->compile(context, scope, resolve)); + if (! lexpr->constant() || ! rexpr->constant()) { + if (left == lexpr && right == rexpr) + return acquire(); + else + return copy(lexpr, rexpr)->acquire(); + } + + if (left == lexpr) { + value_t temp(*lexpr->valuep); + switch (kind) { + case O_ADD: temp += *rexpr->valuep; break; + case O_SUB: temp -= *rexpr->valuep; break; + case O_MUL: temp *= *rexpr->valuep; break; + case O_DIV: temp /= *rexpr->valuep; break; + default: assert(0); break; + } + return wrap_value(temp)->acquire(); + } else { + switch (kind) { + case O_ADD: *lexpr->valuep += *rexpr->valuep; break; + case O_SUB: *lexpr->valuep -= *rexpr->valuep; break; + case O_MUL: *lexpr->valuep *= *rexpr->valuep; break; + case O_DIV: *lexpr->valuep /= *rexpr->valuep; break; + default: assert(0); break; + } + return lexpr->acquire(); + } + } + + case O_NEQ: + case O_EQ: + case O_LT: + case O_LTE: + case O_GT: + case O_GTE: { + assert(left); + assert(right); + xpath_t lexpr(left->compile(context, scope, resolve)); + xpath_t rexpr(right->compile(context, scope, resolve)); + if (! lexpr->constant() || ! rexpr->constant()) { + if (left == lexpr && right == rexpr) + return acquire(); + else + return copy(lexpr, rexpr)->acquire(); + } + + if (left == lexpr) { + switch (kind) { + case O_NEQ: + return wrap_value(*lexpr->valuep != *rexpr->valuep)->acquire(); + break; + case O_EQ: + return wrap_value(*lexpr->valuep == *rexpr->valuep)->acquire(); + break; + case O_LT: + return wrap_value(*lexpr->valuep < *rexpr->valuep)->acquire(); + break; + case O_LTE: + return wrap_value(*lexpr->valuep <= *rexpr->valuep)->acquire(); + break; + case O_GT: + return wrap_value(*lexpr->valuep > *rexpr->valuep)->acquire(); + break; + case O_GTE: + return wrap_value(*lexpr->valuep >= *rexpr->valuep)->acquire(); + break; + default: assert(0); break; + } + } else { + switch (kind) { + case O_NEQ: *lexpr->valuep = *lexpr->valuep != *rexpr->valuep; break; + case O_EQ: *lexpr->valuep = *lexpr->valuep == *rexpr->valuep; break; + case O_LT: *lexpr->valuep = *lexpr->valuep < *rexpr->valuep; break; + case O_LTE: *lexpr->valuep = *lexpr->valuep <= *rexpr->valuep; break; + case O_GT: *lexpr->valuep = *lexpr->valuep > *rexpr->valuep; break; + case O_GTE: *lexpr->valuep = *lexpr->valuep >= *rexpr->valuep; break; + default: assert(0); break; + } + return lexpr->acquire(); + } + } + + case O_AND: { + assert(left); + assert(right); + xpath_t lexpr(left->compile(context, scope, resolve)); + if (lexpr->constant() && ! lexpr->valuep->strip_annotations()) { + *lexpr->valuep = false; + return lexpr->acquire(); + } + + xpath_t rexpr(right->compile(context, scope, resolve)); + if (! lexpr->constant() || ! rexpr->constant()) { + if (left == lexpr && right == rexpr) + return acquire(); + else + return copy(lexpr, rexpr)->acquire(); + } + + if (! rexpr->valuep->strip_annotations()) { + if (left == lexpr) { + return wrap_value(false)->acquire(); + } else { + *lexpr->valuep = false; + return lexpr->acquire(); + } + } else { + return rexpr->acquire(); + } + } + + case O_OR: { + assert(left); + assert(right); + xpath_t lexpr(left->compile(context, scope, resolve)); + if (lexpr->constant() && lexpr->valuep->strip_annotations()) + return lexpr->acquire(); + + xpath_t rexpr(right->compile(context, scope, resolve)); + if (! lexpr->constant() || ! rexpr->constant()) { + if (left == lexpr && right == rexpr) + return acquire(); + else + return copy(lexpr, rexpr)->acquire(); + } + + if (rexpr->valuep->strip_annotations()) { + return rexpr->acquire(); + } else { + if (left == lexpr) { + return wrap_value(false)->acquire(); + } else { + *lexpr->valuep = false; + return lexpr->acquire(); + } + } + } + + case O_QUES: { + assert(left); + assert(right); + assert(right->kind == O_COLON); + xpath_t lexpr(left->compile(context, scope, resolve)); + if (! lexpr->constant()) { + xpath_t rexpr(right->compile(context, scope, resolve)); + if (left == lexpr && right == rexpr) + return acquire(); + else + return copy(lexpr, rexpr)->acquire(); + } + + if (lexpr->valuep->strip_annotations()) + return right->left->compile(context, scope, resolve); + else + return right->right->compile(context, scope, resolve); + } + + case O_COLON: { + xpath_t lexpr(left->compile(context, scope, resolve)); + xpath_t rexpr(right->compile(context, scope, resolve)); + if (left == lexpr && right == rexpr) + return acquire(); + else + return copy(lexpr, rexpr)->acquire(); + } + + case O_COMMA: { + assert(left); + assert(right); + // jww (2006-09-29): This should act just like union + xpath_t lexpr(left->compile(context, scope, resolve)); // for side-effects + return right->compile(context, scope, resolve); + } + +#if 0 + case O_MATCH: + case O_NMATCH: { + assert(left); + assert(right); + xpath_t rexpr(right->compile(context, scope, resolve)); + xpath_t lexpr(left->compile(context, scope, resolve)); + if (! lexpr->constant() || rexpr->kind != MASK) { + if (left == lexpr) + return acquire(); + else + return copy(lexpr, rexpr)->acquire(); + } + + if (lexpr->valuep->type != value_t::STRING) + throw new compile_error("Left operand of mask operator is not a string"); + + assert(rexpr->mask); + + bool result = rexpr->mask->match(lexpr->valuep->to_string()); + if (kind == O_NMATCH) + result = ! result; + + if (left == lexpr) { + return wrap_value(result)->acquire(); + } else { + *lexpr->valuep = result; + return lexpr->acquire(); + } + } +#endif + + case O_DEFINE: + assert(left); + assert(right); + if (left->kind == VAR_NAME || left->kind == FUNC_NAME) { + xpath_t rexpr(right->compile(context, scope, resolve)); + if (scope) + scope->define(*left->name, rexpr); + return rexpr->acquire(); + } else { + assert(left->kind == O_EVAL); + assert(left->left->kind == FUNC_NAME); + + std::auto_ptr<scope_t> arg_scope(new scope_t(scope)); + + int index = 0; + op_t * args = left->right; + while (args) { + op_t * arg = args; + if (args->kind == O_COMMA) { + arg = args->left; + args = args->right; + } else { + args = NULL; + } + + // Define the parameter so that on lookup the parser will find + // an ARG_INDEX value. + std::auto_ptr<op_t> ref(new op_t(ARG_INDEX)); + ref->arg_index = index++; + + assert(arg->kind == NODE_NAME); + arg_scope->define(*arg->name, ref.release()); + } + + // jww (2006-09-16): If I compile the definition of a function, + // I eliminate the possibility of future lookups + //xpath_t rexpr(right->compile(arg_scope.get(), resolve)); + + if (scope) + scope->define(*left->left->name, right); + + return right->acquire(); + } + + case O_EVAL: { + assert(left); + + std::auto_ptr<scope_t> call_args(new scope_t(scope)); + call_args->kind = scope_t::ARGUMENT; + + std::auto_ptr<value_t::sequence_t> call_seq; + + int index = 0; + op_t * args = right; + while (args) { + op_t * arg = args; + if (args->kind == O_COMMA) { + arg = args->left; + args = args->right; + } else { + args = NULL; + } + + if (! call_seq.get()) + call_seq.reset(new value_t::sequence_t); + + // jww (2006-09-15): Need to return a reference to these, if + // there are undetermined arguments! + call_seq->push_back(arg->compile(context, scope, resolve)->value()); + } + + if (call_seq.get()) + call_args->args = call_seq.release(); + + if (left->kind == FUNC_NAME) { + if (resolve) { + value_t temp; + if (scope && scope->resolve(*left->name, temp, call_args.get())) + return wrap_value(temp)->acquire(); + } + + // Don't compile to the left, otherwise the function name may + // get resolved before we have a chance to call it + xpath_t func(left->compile(context, scope, false)); + if (func->kind == FUNCTOR) { + value_t temp; + (*func->functor)(temp, call_args.get()); + return wrap_value(temp)->acquire(); + } + else if (! resolve) { + return func->compile(context, call_args.get(), resolve); + } + else { + throw new calc_error(std::string("Unknown function name '") + + *left->name + "'"); + } + } + else if (left->kind == FUNCTOR) { + value_t temp; + (*left->functor)(temp, call_args.get()); + return wrap_value(temp)->acquire(); + } + else { + assert(0); + } + break; + } + + case O_FIND: + case O_RFIND: + case O_PRED: { + assert(left); + assert(right); + xpath_t lexpr(left->compile(context, scope, resolve)); + xpath_t rexpr(resolve ? right->acquire() : + right->compile(context, scope, false)); + if (! lexpr->constant() || ! resolve) { + if (left == lexpr) + return acquire(); + else + return copy(lexpr, rexpr)->acquire(); + } + + std::auto_ptr<value_t::sequence_t> result_seq(new value_t::sequence_t); + + // jww (2006-09-24): What about when nothing is found? + switch (lexpr->valuep->type) { + case value_t::XML_NODE: { + function_scope_t xpath_fscope(NULL, lexpr->valuep, 0, scope); + if (kind == O_PRED) { + if (rexpr->test_value(lexpr->valuep, &xpath_fscope)) + result_seq->push_back(*lexpr->valuep); + } else { + rexpr->find_values(lexpr->valuep, &xpath_fscope, *result_seq.get(), + kind == O_RFIND); + } + break; + } + + case value_t::SEQUENCE: { + value_t::sequence_t * seq = lexpr->valuep->to_sequence(); + + int index = 0; + for (value_t::sequence_t::iterator i = seq->begin(); + i != seq->end(); + i++, index++) { + assert((*i).type != value_t::SEQUENCE); + if ((*i).type != value_t::XML_NODE) + throw new compile_error("Attempting to apply path selection " + "to non-node(s)"); + + function_scope_t xpath_fscope(seq, &(*i), index, scope); + if (kind == O_PRED) { + if (rexpr->test_value(&(*i), &xpath_fscope, index)) + result_seq->push_back(*i); + } else { + rexpr->find_values(&(*i), &xpath_fscope, *result_seq.get(), + kind == O_RFIND); + } + } + break; + } + + default: + throw new compile_error("Attempting to apply path selection " + "to non-node(s)"); + } + + if (result_seq->size() == 1) + return wrap_value(result_seq->front())->acquire(); + else + return wrap_sequence(result_seq.release())->acquire(); + } + +#if 0 + case O_PERC: { + assert(left); + xpath_t expr(left->compile(context, scope, resolve)); + if (! expr->constant()) { + if (left == expr) + return acquire(); + else + return copy(expr)->acquire(); + } + + static value_t perc("100.0%"); + *expr->valuep = perc * *expr->valuep; + return expr->acquire(); + } +#endif + + case LAST: + default: + assert(0); + break; + } + } + catch (error * err) { +#if 0 + // jww (2006-09-09): I need a reference to the parent xpath_t + if (err->context.empty() || + ! dynamic_cast<context *>(err->context.back())) + err->context.push_back(new context(this)); +#endif + throw err; + } + + assert(0); + return NULL; +} + +void xpath_t::calc(value_t& result, node_t * node, scope_t * scope) const +{ + try { + if (node) { + value_t context_node(node); + xpath_t final(ptr->compile(&context_node, scope, true)); + // jww (2006-09-09): Give a better error here if this is not + // actually a value + final->get_value(result); + } else { + std::auto_ptr<terminal_node_t> fake_node(new terminal_node_t(NULL)); + value_t context_node(fake_node.get()); + xpath_t final(ptr->compile(&context_node, scope, true)); + final->get_value(result); + } + } + catch (error * err) { + if (err->context.empty() || + ! dynamic_cast<context *>(err->context.back())) + err->context.push_back + (new context(*this, ptr, "While calculating value expression:")); +#if 0 + error_context * last = err->context.back(); + if (context * ctxt = dynamic_cast<context *>(last)) { + ctxt->xpath = *this; + ctxt->desc = "While calculating value expression:"; + } +#endif + throw err; + } +} + +xpath_t::context::context(const xpath_t& _xpath, + const op_t * _err_node, + const std::string& desc) throw() + : xpath(_xpath), err_node(_err_node), error_context(desc) +{ + _err_node->acquire(); +} + +xpath_t::context::~context() throw() +{ + if (err_node) err_node->release(); +} + +void xpath_t::context::describe(std::ostream& out) const throw() +{ + if (! xpath) { + out << "xpath_t::context expr not set!" << std::endl; + return; + } + + if (! desc.empty()) + out << desc << std::endl; + + out << " "; + unsigned long start = (long)out.tellp() - 1; + unsigned long begin; + unsigned long end; + bool found = false; + if (xpath) + xpath.write(out, true, err_node, &begin, &end); + out << std::endl; + if (found) { + out << " "; + for (int i = 0; i < end - start; i++) { + if (i >= begin - start) + out << "^"; + else + out << " "; + } + out << std::endl; + } +} + +bool xpath_t::op_t::write(std::ostream& out, + const bool relaxed, + const op_t * op_to_find, + unsigned long * start_pos, + unsigned long * end_pos) const +{ + int arg_index = 0; + bool found = false; + op_t * expr; + + if (start_pos && this == op_to_find) { + *start_pos = (long)out.tellp() - 1; + found = true; + } + + std::string symbol; + + switch (kind) { + case VALUE: + switch (valuep->type) { + case value_t::BOOLEAN: + if (*(valuep)) + out << "1"; + else + out << "0"; + break; + case value_t::INTEGER: + case value_t::AMOUNT: + if (! relaxed) + out << '{'; + out << *(valuep); + if (! relaxed) + out << '}'; + break; + case value_t::BALANCE: + case value_t::BALANCE_PAIR: + assert(0); + break; + case value_t::DATETIME: + out << '[' << *valuep << ']'; + break; + case value_t::STRING: + out << '"' << *valuep << '"'; + break; + } + break; + + case NODE_ID: +#ifdef THREADSAFE + out << '%' << name_id; +#else + out << node_t::document->lookup_name(name_id); +#endif + break; + + case NODE_NAME: + case FUNC_NAME: + out << *name; + break; + + case ATTR_NAME: + out << '@' << *name; + break; + + case VAR_NAME: + out << '$' << *name; + break; + + case FUNCTOR: + out << functor->name(); + break; + +#if 0 + case MASK: + out << '/' << mask->pattern << '/'; + break; +#endif + + case ARG_INDEX: + out << '@' << arg_index; + break; + + case O_NOT: + out << "!"; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; + case O_NEG: + out << "-"; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; + + case O_UNION: + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " | "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; + + case O_ADD: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " + "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + case O_SUB: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " - "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + case O_MUL: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " * "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + case O_DIV: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " / "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + + case O_NEQ: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " != "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + case O_EQ: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " == "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + case O_LT: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " < "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + case O_LTE: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " <= "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + case O_GT: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " > "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + case O_GTE: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " >= "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + + case O_AND: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " & "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + case O_OR: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " | "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + + case O_QUES: + out << "("; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " ? "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + case O_COLON: + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " : "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; + + case O_COMMA: + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ", "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; + +#if 0 + case O_MATCH: + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " =~ "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; + case O_NMATCH: + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << " !~ "; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; +#endif + + case O_DEFINE: + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << '='; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; + case O_EVAL: + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << "("; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << ")"; + break; + + case O_FIND: + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << "/"; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; + case O_RFIND: + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << "//"; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; + case O_PRED: + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << "["; + if (right && right->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + out << "]"; + break; + +#if 0 + case O_PERC: + out << "%"; + if (left && left->write(out, relaxed, op_to_find, start_pos, end_pos)) + found = true; + break; +#endif + + case LAST: + default: + assert(0); + break; + } + + if (! symbol.empty()) { + if (commodity_t::find(symbol)) + out << '@'; + out << symbol; + } + + if (end_pos && this == op_to_find) + *end_pos = (long)out.tellp() - 1; + + return found; +} + +void xpath_t::op_t::dump(std::ostream& out, const int depth) const +{ + out.setf(std::ios::left); + out.width(10); + out << this << " "; + + for (int i = 0; i < depth; i++) + out << " "; + + switch (kind) { + case VALUE: + out << "VALUE - " << *valuep; + break; + + case NODE_NAME: + out << "NODE_NAME - " << *name; + break; + + case NODE_ID: +#ifdef THREADSAFE + out << "NODE_ID - " << name_id; +#else + out << "NODE_ID - " << node_t::document->lookup_name(name_id); +#endif + break; + + case ATTR_NAME: + out << "ATTR_NAME - " << *name; + break; + + case FUNC_NAME: + out << "FUNC_NAME - " << *name; + break; + + case VAR_NAME: + out << "VAR_NAME - " << *name; + break; + + case ARG_INDEX: + out << "ARG_INDEX - " << arg_index; + break; + + case FUNCTOR: + out << "FUNCTOR - " << functor->name(); + break; +#if 0 + case MASK: + out << "MASK - " << mask->pattern; + break; +#endif + + case O_NOT: out << "O_NOT"; break; + case O_NEG: out << "O_NEG"; break; + + case O_UNION: out << "O_UNION"; break; + + case O_ADD: out << "O_ADD"; break; + case O_SUB: out << "O_SUB"; break; + case O_MUL: out << "O_MUL"; break; + case O_DIV: out << "O_DIV"; break; + + case O_NEQ: out << "O_NEQ"; break; + case O_EQ: out << "O_EQ"; break; + case O_LT: out << "O_LT"; break; + case O_LTE: out << "O_LTE"; break; + case O_GT: out << "O_GT"; break; + case O_GTE: out << "O_GTE"; break; + + case O_AND: out << "O_AND"; break; + case O_OR: out << "O_OR"; break; + + case O_QUES: out << "O_QUES"; break; + case O_COLON: out << "O_COLON"; break; + + case O_COMMA: out << "O_COMMA"; break; + +#if 0 + case O_MATCH: out << "O_MATCH"; break; + case O_NMATCH: out << "O_NMATCH"; break; +#endif + + case O_DEFINE: out << "O_DEFINE"; break; + case O_EVAL: out << "O_EVAL"; break; + + case O_FIND: out << "O_FIND"; break; + case O_RFIND: out << "O_RFIND"; break; + case O_PRED: out << "O_PRED"; break; + +#if 0 + case O_PERC: out << "O_PERC"; break; +#endif + + case LAST: + default: + assert(0); + break; + } + + out << " (" << refc << ')' << std::endl; + + if (kind > TERMINALS) { + if (left) { + left->dump(out, depth + 1); + if (right) + right->dump(out, depth + 1); + } else { + assert(! right); + } + } else { + assert(! left); + } +} + +} // namespace xml +} // namespace ledger + +#ifdef USE_BOOST_PYTHON + +#ifndef USE_PCH +#include <boost/python.hpp> +#endif + +using namespace boost::python; +using namespace ledger; + +value_t py_calc_1(xpath_t::op_t& xpath_t, const details_t& item) +{ + value_t result; + xpath_t.calc(result, item); + return result; +} + +template <typename T> +value_t py_calc(xpath_t::op_t& xpath_t, const T& item) +{ + value_t result; + xpath_t.calc(result, details_t(item)); + return result; +} + +xpath_t::op_t * py_parse_xpath_t_1(const std::string& str) +{ + return parse_xpath_t(str); +} + +#define EXC_TRANSLATOR(type) \ + void exc_translate_ ## type(const type& err) { \ + PyErr_SetString(PyExc_RuntimeError, err.what()); \ + } + +EXC_TRANSLATOR(xpath_t_error) +EXC_TRANSLATOR(calc_error) +#if 0 +EXC_TRANSLATOR(mask_error) +#endif + +void export_xpath() +{ + class_< details_t > ("Details", init<const entry_t&>()) + .def(init<const transaction_t&>()) + .def(init<const account_t&>()) + .add_property("entry", + make_getter(&details_t::entry, + return_value_policy<reference_existing_object>())) + .add_property("xact", + make_getter(&details_t::xact, + return_value_policy<reference_existing_object>())) + .add_property("account", + make_getter(&details_t::account, + return_value_policy<reference_existing_object>())) + ; + + class_< xpath_t::op_t > ("ValueExpr", init<xpath_t::op_t::kind_t>()) + .def("calc", py_calc_1) + .def("calc", py_calc<account_t>) + .def("calc", py_calc<entry_t>) + .def("calc", py_calc<transaction_t>) + ; + + def("parse_xpath_t", py_parse_xpath_t_1, + return_value_policy<manage_new_object>()); + + class_< item_predicate<transaction_t> > + ("TransactionPredicate", init<std::string>()) + .def("__call__", &item_predicate<transaction_t>::operator()) + ; + + class_< item_predicate<account_t> > + ("AccountPredicate", init<std::string>()) + .def("__call__", &item_predicate<account_t>::operator()) + ; + +#define EXC_TRANSLATE(type) \ + register_exception_translator<type>(&exc_translate_ ## type); + + EXC_TRANSLATE(xpath_t_error); + EXC_TRANSLATE(calc_error); +#if 0 + EXC_TRANSLATE(mask_error); +#endif +} + +#endif // USE_BOOST_PYTHON + +#ifdef TEST + +#if ! defined(HAVE_EXPAT) && ! defined(HAVE_XMLPARSE) +#error No XML parser library was found during configure +#endif + +#if 0 +#include "session.h" +#include "format.h" +#endif + +int main(int argc, char *argv[]) +{ + using namespace ledger; + using namespace ledger::xml; + + try { + parser_t parser; + std::auto_ptr<document_t> doc; + + std::ifstream input(argv[1]); + if (parser.test(input)) { + doc.reset(parser.parse(input)); + doc->write(std::cout); + } else { + std::cerr << "Could not parse XML file: " << argv[1] << std::endl; + return 1; + } + + xpath_t expr(argv[2]); + if (expr) { + std::cout << "Parsed:" << std::endl; + expr.dump(std::cout); + std::cout << std::endl; + + expr.compile(doc.get()); + std::cout << "Compiled:" << std::endl; + expr.dump(std::cout); + std::cout << std::endl; + + value_t temp; + expr.calc(temp, doc->top); + std::cout << "Calculated value: " << temp << std::endl; + } else { + std::cerr << "Failed to parse value expression!" << std::endl; + } + +#if 0 + { + ledger::session_t session; + std::auto_ptr<xpath_t::scope_t> + locals(new xpath_t::scope_t(&session.globals)); + + ledger::format_t fmt(std::string("%20|%40{") + argv[1] + "}\n"); + fmt.format(std::cout, locals.get()); + } +#endif + } + catch (error * err) { + std::cout.flush(); + if (err->context.empty()) + err->context.push_front(new error_context("")); + err->reveal_context(std::cerr, "Error"); + std::cerr << err->what() << std::endl; + delete err; + return 1; + } + catch (fatal * err) { + std::cout.flush(); + if (err->context.empty()) + err->context.push_front(new error_context("")); + err->reveal_context(std::cerr, "Fatal"); + std::cerr << err->what() << std::endl; + delete err; + return 1; + } + catch (const std::exception& err) { + std::cout.flush(); + std::cerr << "Error: " << err.what() << std::endl; + return 1; + } +} + +#endif // TEST diff --git a/xpath.h b/xpath.h new file mode 100644 index 00000000..5c364b6a --- /dev/null +++ b/xpath.h @@ -0,0 +1,773 @@ +#ifndef _XPATH_H +#define _XPATH_H + +#include "xml.h" +#include "error.h" +#if 0 +#include "mask.h" +#endif + +#include <list> +#include <memory> + +namespace ledger { +namespace xml { + +class xpath_t +{ +public: + struct op_t; + + class parse_error : public error { + public: + parse_error(const std::string& reason, + error_context * ctxt = NULL) throw() + : error(reason, ctxt) {} + virtual ~parse_error() throw() {} + }; + + class compile_error : public error { + public: + compile_error(const std::string& reason, + error_context * ctxt = NULL) throw() + : error(reason, ctxt) {} + virtual ~compile_error() throw() {} + }; + + class calc_error : public error { + public: + calc_error(const std::string& reason, + error_context * ctxt = NULL) throw() + : error(reason, ctxt) {} + virtual ~calc_error() throw() {} + }; + + class context : public error_context { + public: + const xpath_t& xpath; + const op_t * err_node; + + context(const xpath_t& _xpath, + const op_t * _err_node, + const std::string& desc = "") throw(); + virtual ~context() throw(); + + virtual void describe(std::ostream& out) const throw(); + }; + +public: + class scope_t; + + class functor_t { + protected: + std::string fname; + public: + bool wants_args; + + functor_t(const std::string& _fname, bool _wants_args = false) + : fname(_fname), wants_args(_wants_args) {} + virtual ~functor_t() {} + + virtual void operator()(value_t& result, scope_t * locals) = 0; + virtual std::string name() const { return fname; } + }; + + template <typename T, typename U> + class member_functor_t : public functor_t { + public: + T * ptr; + U T::*dptr; + + member_functor_t(const std::string& name, T * _ptr, U T::*_dptr) + : functor_t(name, false), ptr(_ptr), dptr(_dptr) {} + + virtual void operator()(value_t& result, scope_t * locals) { + assert(ptr); + assert(dptr); + result = ptr->*dptr; + } + }; + + template <typename T> + class member_functor_t<T, std::string> : public functor_t { + public: + T * ptr; + std::string T::*dptr; + + member_functor_t(const std::string& name, T * _ptr, std::string T::*_dptr) + : functor_t(name, false), ptr(_ptr), dptr(_dptr) {} + + virtual void operator()(value_t& result, scope_t * locals) { + assert(ptr); + assert(dptr); + result.set_string(ptr->*dptr); + } + }; + + template <typename T> + class memfun_functor_t : public functor_t { + public: + T * ptr; + void (T::*mptr)(value_t& result); + + memfun_functor_t(const std::string& name, T * _ptr, + void (T::*_mptr)(value_t& result)) + : functor_t(name, false), ptr(_ptr), mptr(_mptr) {} + + virtual void operator()(value_t& result, scope_t * locals = NULL) { + assert(ptr); + assert(mptr); + (ptr->*mptr)(result); + } + }; + + template <typename T> + class memfun_args_functor_t : public functor_t { + public: + T * ptr; + void (T::*mptr)(value_t& result, scope_t * locals); + + memfun_args_functor_t(const std::string& name, T * _ptr, + void (T::*_mptr)(value_t& result, scope_t * locals)) + : functor_t(name, true), ptr(_ptr), mptr(_mptr) {} + + virtual void operator()(value_t& result, scope_t * locals) { + assert(ptr); + assert(mptr); + (ptr->*mptr)(result, locals); + } + }; + + static op_t * wrap_value(const value_t& val); + static op_t * wrap_sequence(value_t::sequence_t * val); + static op_t * wrap_functor(functor_t * fobj); +#if 0 + static op_t * wrap_mask(const std::string& pattern); +#endif + + template <typename T, typename U> + static op_t * + make_functor(const std::string& name = "<data>", T * ptr, U T::*mptr) { + return wrap_functor(new member_functor_t<T, U>(name, ptr, mptr)); + } + + template <typename T> + static op_t * + make_functor(const std::string& fname = "<func>", T * ptr, + void (T::*mptr)(value_t& result)) { + return wrap_functor(new memfun_functor_t<T>(fname, ptr, mptr)); + } + + template <typename T> + static op_t * + make_functor(const std::string& fname = "<func>", T * ptr, + void (T::*mptr)(value_t& result, scope_t * locals)) { + return wrap_functor(new memfun_args_functor_t<T>(fname, ptr, mptr)); + } + +#define MAKE_FUNCTOR(cls, name) \ + xml::xpath_t::make_functor(#name, this, &cls::name) + +public: + class scope_t + { + typedef std::map<const std::string, op_t *> symbol_map; + typedef std::pair<const std::string, op_t *> symbol_pair; + + symbol_map symbols; + + scope_t(const scope_t&); + scope_t& operator=(const scope_t&); + + public: + scope_t * parent; + value_t args; + + enum kind_t { NORMAL, STATIC, ARGUMENT } kind; + + scope_t(scope_t * _parent = NULL, kind_t _kind = NORMAL) + : parent(_parent), kind(_kind) { + TRACE_CTOR("xpath_t::scope_t(scope *, kind_t)"); + } + + virtual ~scope_t() { + TRACE_DTOR("xpath_t::scope_t"); + for (symbol_map::iterator i = symbols.begin(); + i != symbols.end(); + i++) + (*i).second->release(); + } + + public: + virtual void define(const std::string& name, op_t * def); + virtual bool resolve(const std::string& name, value_t& result, + scope_t * locals = NULL) { + if (parent) + return parent->resolve(name, result, locals); + return false; + } + virtual op_t * lookup(const std::string& name); + + void define(const std::string& name, functor_t * def); + + friend struct op_t; + }; + + class function_scope_t : public scope_t + { + value_t::sequence_t * sequence; + value_t * value; + int index; + + public: + function_scope_t(value_t::sequence_t * _sequence, value_t * _value, + int _index, scope_t * parent = NULL) + : scope_t(parent, STATIC), + sequence(_sequence), value(_value), index(_index) {} + + virtual bool resolve(const std::string& name, value_t& result, + scope_t * locals = NULL); + }; + +#define XPATH_PARSE_NORMAL 0x00 +#define XPATH_PARSE_PARTIAL 0x01 +#define XPATH_PARSE_RELAXED 0x02 +#define XPATH_PARSE_NO_MIGRATE 0x04 +#define XPATH_PARSE_NO_REDUCE 0x08 +#if 0 +#define XPATH_PARSE_REGEXP 0x10 +#endif +#define XPATH_PARSE_ALLOW_DATE 0x20 + +private: + struct token_t + { + enum kind_t { + IDENT, // [A-Za-z_][-A-Za-z0-9_:]* + VALUE, // any kind of literal value +#if 0 + REGEXP, // /regexp/ jww (2006-09-24): deprecate + // in favor of a "match" function +#endif + AT_SYM, // @ + DOLLAR, // $ + DOT, // . + DOTDOT, // .. + LPAREN, // ( + RPAREN, // ) + LBRACKET, // ( + RBRACKET, // ) + EXCLAM, // ! + NEQUAL, // != + MINUS, // - + PLUS, // + + STAR, // * + POWER, // ** + SLASH, // / + EQUAL, // = + ASSIGN, // := + LESS, // < + LESSEQ, // <= + GREATER, // > + GREATEREQ, // >= + AMPER, // & + PIPE, // | + QUESTION, // ? + COLON, // : + COMMA, // , +#if 0 + MATCH, // =~ + NMATCH, // !~ + PERCENT, // % +#endif + KW_AND, + KW_OR, + KW_DIV, + KW_MOD, + KW_UNION, + TOK_EOF, + UNKNOWN + } kind; + + char symbol[3]; + value_t value; + unsigned int length; + + token_t() : kind(UNKNOWN), length(0) { + TRACE_CTOR("xpath_t::token_t()"); + } + + token_t(const token_t& other) { + assert(0); + TRACE_CTOR("xpath_t::token_t(copy)"); + *this = other; + } + + ~token_t() { + TRACE_DTOR("xpath_t::token_t"); + } + + token_t& operator=(const token_t& other) { + if (&other == this) + return *this; + assert(0); + } + + void clear() { + kind = UNKNOWN; + length = 0; + + symbol[0] = '\0'; + symbol[1] = '\0'; + symbol[2] = '\0'; + } + + void parse_ident(std::istream& in); + void next(std::istream& in, unsigned short flags); + void rewind(std::istream& in); + void unexpected(); + + static void unexpected(char c, char wanted = '\0'); + }; + +public: + struct op_t + { + enum kind_t { + VOID, + VALUE, + + NODE_NAME, + NODE_ID, + FUNC_NAME, + ATTR_NAME, + VAR_NAME, + + ARG_INDEX, + + CONSTANTS, // constants end here + + FUNCTOR, +#if 0 + MASK, +#endif + + TERMINALS, // terminals end here + + O_NOT, + O_NEG, + + O_UNION, + + O_ADD, + O_SUB, + O_MUL, + O_DIV, + + O_NEQ, + O_EQ, + O_LT, + O_LTE, + O_GT, + O_GTE, + + O_AND, + O_OR, + + O_QUES, + O_COLON, + + O_COMMA, + +#if 0 + O_MATCH, + O_NMATCH, +#endif + + O_DEFINE, + O_EVAL, + O_ARG, + +#if 0 + O_PERC, +#endif + + O_FIND, + O_RFIND, + O_PRED, + + LAST // operators end here + }; + + kind_t kind; + mutable short refc; + op_t * left; + + union { + value_t * valuep; // used by constant VALUE + std::string * name; // used by constant SYMBOL + unsigned int arg_index; // used by ARG_INDEX and O_ARG + functor_t * functor; // used by terminal FUNCTOR + unsigned int name_id; // used by NODE_NAME and ATTR_NAME +#if 0 + mask_t * mask; // used by terminal MASK +#endif + op_t * right; // used by all operators + }; + + op_t(const kind_t _kind) + : kind(_kind), refc(0), left(NULL), right(NULL) { + TRACE_CTOR("xpath_t::op_t(const kind_t)"); + } + op_t(const op_t&); + ~op_t(); + + op_t& operator=(const op_t&); + + bool constant() const { + return kind == VALUE; + } + void get_value(value_t& result) const; + value_t value() const { + value_t temp; + get_value(temp); + return temp; + } + + functor_t * functor_obj() const { + if (kind == FUNCTOR) + return functor; + else + return NULL; + } + + void release() const { + DEBUG_PRINT("ledger.xpath.memory", + "Releasing " << this << ", refc now " << refc - 1); + assert(refc > 0); + if (--refc == 0) + delete this; + } + op_t * acquire() { + DEBUG_PRINT("ledger.xpath.memory", + "Acquiring " << this << ", refc now " << refc + 1); + assert(refc >= 0); + refc++; + return this; + } + const op_t * acquire() const { + DEBUG_PRINT("ledger.xpath.memory", + "Acquiring " << this << ", refc now " << refc + 1); + assert(refc >= 0); + refc++; + return this; + } + + void set_left(op_t * expr) { + assert(kind > TERMINALS); + if (left) + left->release(); + left = expr ? expr->acquire() : NULL; + } + + void set_right(op_t * expr) { + assert(kind > TERMINALS); + if (right) + right->release(); + right = expr ? expr->acquire() : NULL; + } + + static op_t * new_node(kind_t kind, op_t * left = NULL, + op_t * right = NULL); + + op_t * copy(op_t * left = NULL, + op_t * right = NULL) const; + op_t * compile(value_t * context, scope_t * scope, + bool resolve = false); + + void find_values(value_t * context, scope_t * scope, + value_t::sequence_t& result_seq, bool recursive); + bool test_value(value_t * context, scope_t * scope, int index = 0); + + void append_value(value_t& value, value_t::sequence_t& result_seq); + + static op_t * defer_sequence(value_t::sequence_t& result_seq); + + bool write(std::ostream& out, + const bool relaxed = true, + const op_t * op_to_find = NULL, + unsigned long * start_pos = NULL, + unsigned long * end_pos = NULL) const; + + void dump(std::ostream& out, const int depth) const; + }; + +public: + op_t * ptr; + + xpath_t& operator=(op_t * _expr) { + expr = ""; + reset(_expr); + return *this; + } + + op_t& operator*() throw() { + return *ptr; + } + const op_t& operator*() const throw() { + return *ptr; + } + op_t * operator->() throw() { + return ptr; + } + const op_t * operator->() const throw() { + return ptr; + } + + op_t * get() throw() { return ptr; } + const op_t * get() const throw() { return ptr; } + + op_t * release() throw() { + op_t * tmp = ptr; + ptr = 0; + return tmp; + } + + void reset(op_t * p = 0) throw() { + if (p != ptr) { + if (ptr) + ptr->release(); + ptr = p; + } + } + +#ifdef THREADSAFE + mutable token_t lookahead; +#else + static token_t lookahead; +#endif + mutable bool use_lookahead; + + token_t& next_token(std::istream& in, unsigned short flags) const { + if (use_lookahead) + use_lookahead = false; + else + lookahead.next(in, flags); + return lookahead; + } + void push_token(const token_t& tok) const { + assert(&tok == &lookahead); + use_lookahead = true; + } + void push_token() const { + use_lookahead = true; + } + + op_t * parse_value_term(std::istream& in, unsigned short flags) const; + op_t * parse_predicate_expr(std::istream& in, unsigned short flags) const; + op_t * parse_path_expr(std::istream& in, unsigned short flags) const; + op_t * parse_unary_expr(std::istream& in, unsigned short flags) const; + op_t * parse_union_expr(std::istream& in, unsigned short flags) const; + op_t * parse_mul_expr(std::istream& in, unsigned short flags) const; + op_t * parse_add_expr(std::istream& in, unsigned short flags) const; + op_t * parse_logic_expr(std::istream& in, unsigned short flags) const; + op_t * parse_and_expr(std::istream& in, unsigned short flags) const; + op_t * parse_or_expr(std::istream& in, unsigned short flags) const; + op_t * parse_querycolon_expr(std::istream& in, unsigned short flags) const; + op_t * parse_value_expr(std::istream& in, unsigned short flags) const; + + op_t * parse_expr(std::istream& in, + unsigned short flags = XPATH_PARSE_RELAXED) const; + + op_t * parse_expr(const std::string& str, + unsigned short flags = XPATH_PARSE_RELAXED) const + { + std::istringstream stream(str); + try { + return parse_expr(stream, flags); + } + catch (error * err) { + err->context.push_back + (new line_context(str, (long)stream.tellg() - 1, + "While parsing value expression:")); + throw err; + } + } + + op_t * parse_expr(const char * p, + unsigned short flags = XPATH_PARSE_RELAXED) const { + return parse_expr(std::string(p), flags); + } + + bool write(std::ostream& out, + const bool relaxed, + const op_t * op_to_find, + unsigned long * start_pos, + unsigned long * end_pos) const { + if (ptr) + ptr->write(out, relaxed, op_to_find, start_pos, end_pos); + } + +public: + std::string expr; + unsigned short flags; // flags used to parse `expr' + + xpath_t() : ptr(NULL), use_lookahead(false), flags(0) { + TRACE_CTOR("xpath_t"); + } + xpath_t(op_t * _ptr) : ptr(_ptr), use_lookahead(false) { + TRACE_CTOR("xpath_t(op_t *)"); + } + + xpath_t(const std::string& _expr, + unsigned short _flags = XPATH_PARSE_RELAXED) + : ptr(NULL), use_lookahead(false), flags(0) { + TRACE_CTOR("xpath_t(const std::string&, unsigned short)"); + if (! _expr.empty()) + parse(_expr, _flags); + } + xpath_t(std::istream& in, unsigned short _flags = XPATH_PARSE_RELAXED) + : ptr(NULL), use_lookahead(false), flags(0) { + TRACE_CTOR("xpath_t(std::istream&, unsigned short)"); + parse(in, _flags); + } + xpath_t(const xpath_t& other) + : ptr(other.ptr ? other.ptr->acquire() : NULL), + use_lookahead(false), expr(other.expr), flags(other.flags) { + TRACE_CTOR("xpath_t(copy)"); + } + virtual ~xpath_t() { + TRACE_DTOR("xpath_t"); + if (ptr) + ptr->release(); + } + + xpath_t& operator=(const std::string& _expr) { + parse(_expr); + return *this; + } + xpath_t& operator=(const xpath_t& _expr); + xpath_t& operator=(xpath_t& _xpath) { + ptr = _xpath.ptr->acquire(); + expr = _xpath.expr; + flags = _xpath.flags; + use_lookahead = false; + return *this; + } + + operator op_t *() throw() { + return ptr; + } + + operator bool() const throw() { + return ptr != NULL; + } + operator std::string() const throw() { + return expr; + } + + void parse(const std::string& _expr, unsigned short _flags = XPATH_PARSE_RELAXED) { + expr = _expr; + flags = _flags; + op_t * tmp = parse_expr(_expr, _flags); + assert(tmp); + reset(tmp ? tmp->acquire() : NULL); + } + void parse(std::istream& in, unsigned short _flags = XPATH_PARSE_RELAXED) { + expr = ""; + flags = _flags; + op_t * tmp = parse_expr(in, _flags); + assert(tmp); + reset(tmp ? tmp->acquire() : NULL); + } + + void compile(const std::string& _expr, scope_t * scope = NULL, + unsigned short _flags = XPATH_PARSE_RELAXED) { + parse(_expr, _flags); + // jww (2006-09-24): fix + compile((node_t *)NULL, scope); + } + void compile(std::istream& in, scope_t * scope = NULL, + unsigned short _flags = XPATH_PARSE_RELAXED) { + parse(in, _flags); + // jww (2006-09-24): fix + compile((node_t *)NULL, scope); + } + + void compile(document_t * document, scope_t * scope = NULL) { + if (! document) + document = new xml::document_t; + compile(document->top, scope); + } + void compile(node_t * top_node, scope_t * scope = NULL) { + if (ptr) { + value_t noderef(top_node); + op_t * compiled = ptr->compile(&noderef, scope); + if (compiled == ptr) + compiled->release(); + else + reset(compiled); + } + } + + virtual void calc(value_t& result, node_t * node, scope_t * scope = NULL) const; + + virtual value_t calc(document_t * document, scope_t * scope = NULL) const { + if (! ptr) + return 0L; + value_t temp; + calc(temp, document ? document->top : NULL, scope); + return temp; + } + virtual value_t calc(node_t * context, scope_t * scope = NULL) const { + if (! ptr) + return 0L; + value_t temp; + calc(temp, context, scope); + return temp; + } + + static value_t eval(const std::string& _expr, document_t * document, + scope_t * scope = NULL) { + xpath_t temp(_expr); + return temp.calc(document, scope); + } + + void write(std::ostream& out) const { + write(out, true, NULL, NULL, NULL); + } + void dump(std::ostream& out) const { + if (ptr) + ptr->dump(out, 0); + } + + friend class scope_t; +}; + +} // namespace xml + +template <typename T> +inline T * get_ptr(xml::xpath_t::scope_t * locals, int idx) { + assert(locals->args.size() > idx); + T * ptr = static_cast<T *>(locals->args[idx].to_pointer()); + assert(ptr); + return ptr; +} + +class xml_command : public xml::xpath_t::functor_t +{ + public: + xml_command() : xml::xpath_t::functor_t("xml") {} + + virtual void operator()(value_t& result, xml::xpath_t::scope_t * locals) { + std::ostream * out = get_ptr<std::ostream>(locals, 0); + xml::document_t * doc = get_ptr<xml::document_t>(locals, 1); + + doc->write(*out); + } + +}; + +} // namespace ledger + +#endif // _XPATH_H |