summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2008-04-13 03:35:00 -0400
committerJohn Wiegley <johnw@newartisans.com>2008-04-13 03:35:00 -0400
commit42f43b7686038e4cbca16d8d2118b139544e6de3 (patch)
tree52c5473401c57282242d66b8dd75f4c07bf41d07
parentc7b4370ff9c8ab5c96f15b1e712e6db6bdab6324 (diff)
downloadledger-42f43b7686038e4cbca16d8d2118b139544e6de3.tar.gz
ledger-42f43b7686038e4cbca16d8d2118b139544e6de3.tar.bz2
ledger-42f43b7686038e4cbca16d8d2118b139544e6de3.zip
Check in all changes made so far toward 3.0.
-rw-r--r--Makefile.am191
-rw-r--r--NEWS25
-rwxr-xr-xacprep66
-rw-r--r--amount.cc237
-rw-r--r--amount.h140
-rw-r--r--balance.cc9
-rw-r--r--balance.h28
-rw-r--r--binary.cc448
-rw-r--r--binary.h238
-rw-r--r--configure.in16
-rw-r--r--csv.cc105
-rw-r--r--csv.h24
-rw-r--r--datetime.cc315
-rw-r--r--datetime.h21
-rw-r--r--debug.cc8
-rw-r--r--debug.h4
-rw-r--r--derive.cc30
-rw-r--r--derive.h10
-rw-r--r--docs/ledger.179
-rw-r--r--emacs.cc80
-rw-r--r--emacs.h30
-rw-r--r--error.h16
-rw-r--r--format.cc1045
-rw-r--r--format.h259
-rw-r--r--gnucash.cc341
-rw-r--r--gnucash.h72
-rw-r--r--journal.cc457
-rw-r--r--journal.h98
-rw-r--r--ledger.el507
-rw-r--r--ledger.h37
-rw-r--r--main.cc584
-rw-r--r--mask.cc12
-rw-r--r--mask.h4
-rw-r--r--ofx.cc9
-rw-r--r--ofx.h1
-rw-r--r--option.cc1126
-rw-r--r--option.h45
-rw-r--r--parser.cc212
-rw-r--r--parser.h32
-rw-r--r--py_eval.cc205
-rw-r--r--py_eval.h77
-rw-r--r--pyfstream.h146
-rw-r--r--pyledger.cc10
-rw-r--r--pyledger.h16
-rw-r--r--qif.cc5
-rw-r--r--qif.h1
-rw-r--r--quotes.cc4
-rw-r--r--reconcile.cc88
-rw-r--r--reconcile.h33
-rw-r--r--report.cc536
-rw-r--r--report.h172
-rw-r--r--sample.dat6
-rw-r--r--session.cc239
-rw-r--r--session.h185
-rwxr-xr-xsetup.py24
-rw-r--r--tests/UnitTests.cc111
-rw-r--r--tests/UnitTests.h14
-rw-r--r--tests/corelib/numerics/#BasicAmountTest.cc#425
-rw-r--r--tests/corelib/numerics/.#BasicAmountTest.cc1
-rw-r--r--tests/corelib/numerics/BasicAmountTest.cc345
-rw-r--r--tests/corelib/numerics/BasicAmountTest.h50
-rw-r--r--textual.cc297
-rw-r--r--textual.h8
-rw-r--r--trace.cc187
-rw-r--r--trace.h51
-rw-r--r--transform.cc349
-rw-r--r--transform.h138
-rw-r--r--util.cc161
-rw-r--r--util.h37
-rw-r--r--value.cc1165
-rw-r--r--value.h206
-rw-r--r--xml.cc679
-rw-r--r--xml.h329
-rw-r--r--xmlparse.cc473
-rw-r--r--xmlparse.h25
-rw-r--r--xpath.cc2560
-rw-r--r--xpath.h773
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
diff --git a/NEWS b/NEWS
index cd180dab..f9ff8246 100644
--- a/NEWS
+++ b/NEWS
@@ -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.
diff --git a/acprep b/acprep
index a684012e..823ccd49 100755
--- a/acprep
+++ b/acprep
@@ -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
diff --git a/amount.cc b/amount.cc
index 948c1738..bd690144 100644
--- a/amount.cc
+++ b/amount.cc
@@ -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)
diff --git a/amount.h b/amount.h
index dfe8d2df..7ab84177 100644
--- a/amount.h
+++ b/amount.h
@@ -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) {}
diff --git a/balance.cc b/balance.cc
index 9e516736..c949075e 100644
--- a/balance.cc
+++ b/balance.cc
@@ -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;
diff --git a/balance.h b/balance.h
index ba76d459..80f1058a 100644
--- a/balance.h
+++ b/balance.h
@@ -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;
}
diff --git a/binary.cc b/binary.cc
index 9185f766..a94b6690 100644
--- a/binary.cc
+++ b/binary.cc
@@ -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
diff --git a/binary.h b/binary.h
index ca3254c9..deb68559 100644
--- a/binary.h
+++ b/binary.h
@@ -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
diff --git a/csv.cc b/csv.cc
index 4a8c1157..e69de29b 100644
--- a/csv.cc
+++ b/csv.cc
@@ -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
diff --git a/csv.h b/csv.h
index 3b370631..e69de29b 100644
--- a/csv.h
+++ b/csv.h
@@ -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
diff --git a/datetime.h b/datetime.h
index 9463296f..a2c4c71b 100644
--- a/datetime.h
+++ b/datetime.h
@@ -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
diff --git a/debug.cc b/debug.cc
index b3b140bc..9b98b1f4 100644
--- a/debug.cc
+++ b/debug.cc
@@ -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,
diff --git a/debug.h b/debug.h
index 81083ad3..9980c5b6 100644
--- a/debug.h
+++ b/debug.h
@@ -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
diff --git a/derive.cc b/derive.cc
index 16452df2..3d35522e 100644
--- a/derive.cc
+++ b/derive.cc
@@ -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
diff --git a/derive.h b/derive.h
index 0df7f231..c0607fc2 100644
--- a/derive.h
+++ b/derive.h
@@ -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
diff --git a/emacs.cc b/emacs.cc
index 823e0367..e69de29b 100644
--- a/emacs.cc
+++ b/emacs.cc
@@ -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
diff --git a/emacs.h b/emacs.h
index ea58bad8..e69de29b 100644
--- a/emacs.h
+++ b/emacs.h
@@ -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
diff --git a/error.h b/error.h
index 7f77db0f..76839157 100644
--- a/error.h
+++ b/error.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
diff --git a/format.cc b/format.cc
index cb890926..3f11fec0 100644
--- a/format.cc
+++ b/format.cc
@@ -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
diff --git a/format.h b/format.h
index 778ec53f..d1a87afa 100644
--- a/format.h
+++ b/format.h
@@ -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 {
diff --git a/gnucash.cc b/gnucash.cc
index 970fba3b..b988e0d7 100644
--- a/gnucash.cc
+++ b/gnucash.cc
@@ -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();
diff --git a/gnucash.h b/gnucash.h
index 6945e55f..4958f6df 100644
--- a/gnucash.h
+++ b/gnucash.h
@@ -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
diff --git a/journal.cc b/journal.cc
index 065825f2..673b281c 100644
--- a/journal.cc
+++ b/journal.cc
@@ -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
diff --git a/journal.h b/journal.h
index 212590cf..5b4c5ea3 100644
--- a/journal.h
+++ b/journal.h
@@ -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();
diff --git a/ledger.el b/ledger.el
index 450f0e8e..f0f05792 100644
--- a/ledger.el
+++ b/ledger.el
@@ -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
diff --git a/ledger.h b/ledger.h
index a59d562d..abb3c540 100644
--- a/ledger.h
+++ b/ledger.h
@@ -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
diff --git a/main.cc b/main.cc
index 30eb2794..0609c96d 100644
--- a/main.cc
+++ b/main.cc
@@ -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;
}
}
diff --git a/mask.cc b/mask.cc
index 972b24ce..7a7a1fc3 100644
--- a/mask.cc
+++ b/mask.cc
@@ -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
diff --git a/mask.h b/mask.h
index 6d4790a3..cf0c893f 100644
--- a/mask.h
+++ b/mask.h
@@ -1,11 +1,11 @@
#ifndef _MASK_H
#define _MASK_H
+#include "error.h"
+
#include <string>
#include <exception>
-#include "error.h"
-
class mask_t
{
public:
diff --git a/ofx.cc b/ofx.cc
index 8b53b284..9a74c750 100644
--- a/ofx.cc
+++ b/ofx.cc
@@ -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)
diff --git a/ofx.h b/ofx.h
index 232daecb..9b017bfa 100644
--- a/ofx.h
+++ b/ofx.h
@@ -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);
diff --git a/option.cc b/option.cc
index a9ef802d..3f84d21a 100644
--- a/option.cc
+++ b/option.cc
@@ -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
diff --git a/option.h b/option.h
index 91838b99..778b16ba 100644
--- a/option.h
+++ b/option.h
@@ -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
diff --git a/parser.cc b/parser.cc
index 7cb65519..78076683 100644
--- a/parser.cc
+++ b/parser.cc
@@ -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
diff --git a/parser.h b/parser.h
index 6178d293..7fac7ca8 100644
--- a/parser.h
+++ b/parser.h
@@ -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
diff --git a/qif.cc b/qif.cc
index faad30ca..c0dbc939 100644
--- a/qif.cc
+++ b/qif.cc
@@ -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)
diff --git a/qif.h b/qif.h
index d8c52576..89663724 100644
--- a/qif.h
+++ b/qif.h
@@ -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);
diff --git a/quotes.cc b/quotes.cc
index a8fbfbc5..b097d97d 100644
--- a/quotes.cc
+++ b/quotes.cc
@@ -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
diff --git a/report.cc b/report.cc
index 90259440..2ebe32e8 100644
--- a/report.cc
+++ b/report.cc
@@ -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
diff --git a/report.h b/report.h
index 377b9c57..92155673 100644
--- a/report.h
+++ b/report.h
@@ -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
diff --git a/sample.dat b/sample.dat
index 4a271d6f..7010b2e5 100644
--- a/sample.dat
+++ b/sample.dat
@@ -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
diff --git a/setup.py b/setup.py
index 5f79efa3..2034f2b7 100755
--- a/setup.py
+++ b/setup.py
@@ -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 &copy );
+ void operator =( const UnitTests &copy );
+};
+
+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 &copy);
+ void operator=(const BasicAmountTest &copy);
+};
+
+#endif /* _BASICAMOUNTTEST_H */
diff --git a/textual.cc b/textual.cc
index 37cf911e..ecd87f33 100644
--- a/textual.cc
+++ b/textual.cc
@@ -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
diff --git a/textual.h b/textual.h
index 8ad653c5..60375830 100644
--- a/textual.h
+++ b/textual.h
@@ -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;
+}
diff --git a/util.h b/util.h
index 21008a22..842879e5 100644
--- a/util.h
+++ b/util.h
@@ -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
diff --git a/value.cc b/value.cc
index dba1798b..8da7681b 100644
--- a/value.cc
+++ b/value.cc
@@ -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)
;
}
diff --git a/value.h b/value.h
index fe01786b..10d669be 100644
--- a/value.h
+++ b/value.h
@@ -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
{
diff --git a/xml.cc b/xml.cc
index 418c6bf7..e75130c7 100644
--- a/xml.cc
+++ b/xml.cc
@@ -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
diff --git a/xml.h b/xml.h
index 13bf317c..2ddb247a 100644
--- a/xml.h
+++ b/xml.h
@@ -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 << "&lt;";
+ break;
+ case '>':
+ out << "&rt;";
+ break;
+ case '&':
+ out << "&amp;";
+ 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