summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2007-04-30 06:26:38 +0000
committerJohn Wiegley <johnw@newartisans.com>2008-04-13 03:38:33 -0400
commitc8899addfd2deed3d84be2de234681db64987722 (patch)
tree07f9a5eb603ff4ec83fe18c83083575d2b7a439a /src
parentaa9cc125796711afcaa459898e95527fdae8e912 (diff)
downloadfork-ledger-c8899addfd2deed3d84be2de234681db64987722.tar.gz
fork-ledger-c8899addfd2deed3d84be2de234681db64987722.tar.bz2
fork-ledger-c8899addfd2deed3d84be2de234681db64987722.zip
Rearranged the sources a bit.
Diffstat (limited to 'src')
-rw-r--r--src/.gitignore1
-rw-r--r--src/COPYRIGHT30
-rw-r--r--src/amount.cc2041
-rw-r--r--src/amount.h760
-rw-r--r--src/balance.cc313
-rw-r--r--src/balance.h969
-rw-r--r--src/binary.cc1013
-rw-r--r--src/binary.h252
-rw-r--r--src/context.h28
-rw-r--r--src/csv.cc0
-rw-r--r--src/csv.h0
-rw-r--r--src/derive.cc178
-rw-r--r--src/derive.h18
-rw-r--r--src/emacs.cc0
-rw-r--r--src/emacs.h0
-rw-r--r--src/error.h151
-rw-r--r--src/fdstream.hpp184
-rw-r--r--src/format.cc235
-rw-r--r--src/format.h108
-rw-r--r--src/gd_qnan.h12
-rw-r--r--src/gnucash.cc366
-rw-r--r--src/gnucash.h75
-rw-r--r--src/journal.cc667
-rw-r--r--src/journal.h471
-rw-r--r--src/ledger.h42
-rw-r--r--src/main.cc484
-rw-r--r--src/mask.cc24
-rw-r--r--src/mask.h26
-rw-r--r--src/ofx.cc218
-rw-r--r--src/ofx.h21
-rw-r--r--src/option.cc219
-rw-r--r--src/option.h22
-rw-r--r--src/parser.h86
-rw-r--r--src/py_amount.cc245
-rw-r--r--src/py_balance.cc202
-rw-r--r--src/py_format.cc11
-rw-r--r--src/py_journal.cc374
-rw-r--r--src/py_option.cc73
-rw-r--r--src/py_parser.cc48
-rw-r--r--src/py_report.cc13
-rw-r--r--src/py_session.cc36
-rw-r--r--src/py_transform.cc8
-rw-r--r--src/py_value.cc337
-rw-r--r--src/py_xpath.cc79
-rw-r--r--src/pyfstream.h138
-rw-r--r--src/pyinterp.cc164
-rw-r--r--src/pyinterp.h83
-rw-r--r--src/pyledger.cc53
-rw-r--r--src/pyledger.h16
-rw-r--r--src/qif.cc243
-rw-r--r--src/qif.h21
-rw-r--r--src/quotes.cc80
-rw-r--r--src/quotes.h30
-rw-r--r--src/reconcile.cc0
-rw-r--r--src/reconcile.h0
-rw-r--r--src/register.cc166
-rw-r--r--src/register.h38
-rw-r--r--src/report.cc189
-rw-r--r--src/report.h141
-rw-r--r--src/session.cc220
-rw-r--r--src/session.h197
-rw-r--r--src/system.hh99
-rw-r--r--src/textual.cc966
-rw-r--r--src/textual.h44
-rw-r--r--src/times.cc53
-rw-r--r--src/times.h102
-rw-r--r--src/transform.cc326
-rw-r--r--src/transform.h136
-rw-r--r--src/utils.cc687
-rw-r--r--src/utils.h422
-rw-r--r--src/value.cc2311
-rw-r--r--src/value.h580
-rw-r--r--src/xml.cc550
-rw-r--r--src/xml.h423
-rw-r--r--src/xmlparse.cc467
-rw-r--r--src/xpath.cc2414
-rw-r--r--src/xpath.h783
77 files changed, 22582 insertions, 0 deletions
diff --git a/src/.gitignore b/src/.gitignore
new file mode 100644
index 00000000..0d20b648
--- /dev/null
+++ b/src/.gitignore
@@ -0,0 +1 @@
+*.pyc
diff --git a/src/COPYRIGHT b/src/COPYRIGHT
new file mode 100644
index 00000000..c9d4bd18
--- /dev/null
+++ b/src/COPYRIGHT
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2003-2007, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
diff --git a/src/amount.cc b/src/amount.cc
new file mode 100644
index 00000000..ac7bbdb7
--- /dev/null
+++ b/src/amount.cc
@@ -0,0 +1,2041 @@
+/**
+ * @file amount.cc
+ * @author John Wiegley
+ * @date Thu Apr 26 15:19:46 2007
+ *
+ * @brief Types for handling commoditized math.
+ *
+ * This file defines member functions for amount_t and the various
+ * flavors of commodity_t.
+ */
+
+/*
+ * Copyright (c) 2003-2007, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "amount.h"
+#include "binary.h"
+
+namespace ledger {
+
+bool do_cleanup = true;
+
+bool amount_t::keep_price = false;
+bool amount_t::keep_date = false;
+bool amount_t::keep_tag = false;
+bool amount_t::keep_base = false;
+bool amount_t::full_strings = false;
+
+#define BIGINT_BULK_ALLOC 0x0001
+#define BIGINT_KEEP_PREC 0x0002
+
+class amount_t::bigint_t
+{
+ public:
+ mpz_t val;
+ unsigned char prec;
+ unsigned char flags;
+ unsigned int ref;
+ 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();
+};
+
+unsigned int sizeof_bigint_t() {
+ return sizeof(amount_t::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 = NULL;
+
+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;
+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
+
+void amount_t::initialize()
+{
+ mpz_init(temp);
+ mpz_init(divisor);
+
+ true_value = new amount_t::bigint_t;
+ mpz_set_ui(true_value->val, 1);
+
+ commodity_base_t::updater = NULL;
+
+ commodity_t::commodities_by_ident = new commodities_array;
+
+ commodity_t::default_commodity = NULL;
+ commodity_t::null_commodity = commodity_t::create("");
+ commodity_t::null_commodity->add_flags(COMMODITY_STYLE_NOMARKET |
+ COMMODITY_STYLE_BUILTIN);
+
+ // Add time commodity conversions, so that timelog's may be parsed
+ // in terms of seconds, but reported as minutes or hours.
+ commodity_t * commodity = commodity_t::create("s");
+ commodity->add_flags(COMMODITY_STYLE_NOMARKET | COMMODITY_STYLE_BUILTIN);
+
+ parse_conversion("1.0m", "60s");
+ parse_conversion("1.0h", "60m");
+}
+
+void amount_t::shutdown()
+{
+ mpz_clear(temp);
+ mpz_clear(divisor);
+
+ if (commodity_base_t::updater) {
+ delete commodity_base_t::updater;
+ commodity_base_t::updater = NULL;
+ }
+
+ for (base_commodities_map::iterator i = commodity_base_t::commodities.begin();
+ i != commodity_base_t::commodities.end();
+ i++)
+ delete (*i).second;
+
+ for (commodities_map::iterator i = commodity_t::commodities.begin();
+ i != commodity_t::commodities.end();
+ i++)
+ delete (*i).second;
+
+ commodity_base_t::commodities.clear();
+ commodity_t::commodities.clear();
+
+ delete commodity_t::commodities_by_ident;
+ commodity_t::commodities_by_ident = NULL;
+
+ commodity_t::null_commodity = NULL;
+ commodity_t::default_commodity = NULL;
+
+ true_value->ref--;
+ assert(true_value->ref == 0);
+ delete true_value;
+ true_value = NULL;
+}
+
+static void mpz_round(mpz_t out, mpz_t value, int value_prec, int round_prec)
+{
+ // Round `value', with an encoding precision of `value_prec', to a
+ // rounded value with precision `round_prec'. Result is stored in
+ // `out'.
+
+ assert(value_prec > round_prec);
+
+ mpz_t quotient;
+ mpz_t remainder;
+
+ mpz_init(quotient);
+ mpz_init(remainder);
+
+ mpz_ui_pow_ui(divisor, 10, value_prec - round_prec);
+ mpz_tdiv_qr(quotient, remainder, value, divisor);
+ mpz_divexact_ui(divisor, divisor, 10);
+ mpz_mul_ui(divisor, divisor, 5);
+
+ if (mpz_sgn(remainder) < 0) {
+ mpz_neg(divisor, divisor);
+ if (mpz_cmp(remainder, divisor) < 0) {
+ mpz_ui_pow_ui(divisor, 10, value_prec - round_prec);
+ mpz_add(remainder, divisor, remainder);
+ mpz_ui_sub(remainder, 0, remainder);
+ mpz_add(out, value, remainder);
+ } else {
+ mpz_sub(out, value, remainder);
+ }
+ } else {
+ if (mpz_cmp(remainder, divisor) >= 0) {
+ mpz_ui_pow_ui(divisor, 10, value_prec - round_prec);
+ mpz_sub(remainder, divisor, remainder);
+ mpz_add(out, value, remainder);
+ } else {
+ mpz_sub(out, value, remainder);
+ }
+ }
+ mpz_clear(quotient);
+ mpz_clear(remainder);
+
+ // chop off the rounded bits
+ mpz_ui_pow_ui(divisor, 10, value_prec - round_prec);
+ mpz_tdiv_q(out, out, divisor);
+}
+
+amount_t::amount_t(const long val)
+{
+ TRACE_CTOR(amount_t, "const long");
+ if (val != 0) {
+ quantity = new bigint_t;
+ mpz_set_si(MPZ(quantity), val);
+ } else {
+ quantity = NULL;
+ }
+ commodity_ = NULL;
+}
+
+amount_t::amount_t(const unsigned long val)
+{
+ TRACE_CTOR(amount_t, "const unsigned long");
+ if (val != 0) {
+ quantity = new bigint_t;
+ mpz_set_ui(MPZ(quantity), val);
+ } else {
+ quantity = NULL;
+ }
+ commodity_ = NULL;
+}
+
+namespace {
+ unsigned char convert_double(mpz_t dest, double val)
+ {
+#ifndef HAVE_GDTOA
+ // This code is far too imprecise to be worthwhile.
+
+ mpf_t temp;
+ mpf_init_set_d(temp, val);
+
+ mp_exp_t exp;
+ char * buf = mpf_get_str(NULL, &exp, 10, 1000, temp);
+
+ int len = std::strlen(buf);
+ if (len > 0 && buf[0] == '-')
+ exp++;
+
+ if (exp <= len) {
+ exp = len - exp;
+ } else {
+ // There were trailing zeros, which we have to put back on in
+ // order to convert this buffer into an integer.
+
+ int zeroes = exp - len;
+
+ char * newbuf = (char *)std::malloc(len + zeroes);
+ std::strcpy(newbuf, buf);
+
+ int i;
+ for (i = 0; i < zeroes; i++)
+ newbuf[len + i] = '0';
+ newbuf[len + i] = '\0';
+
+ free(buf);
+ buf = newbuf;
+
+ exp = (len - exp) + zeroes;
+ }
+
+ mpz_set_str(dest, buf, 10);
+ free(buf);
+
+ return (unsigned char)exp;
+#else
+ int decpt, sign;
+ char * buf = dtoa(val, 0, 0, &decpt, &sign, NULL);
+ char * result;
+ int len = std::strlen(buf);
+
+ if (decpt <= len) {
+ decpt = len - decpt;
+ result = NULL;
+ } else {
+ // There were trailing zeros, which we have to put back on in
+ // order to convert this buffer into an integer.
+
+ int zeroes = decpt - len;
+ result = new char[len + zeroes];
+
+ std::strcpy(result, buf);
+ int i;
+ for (i = 0; i < zeroes; i++)
+ result[len + i] = '0';
+ result[len + i] = '\0';
+
+ decpt = (len - decpt) + zeroes;
+ }
+
+ if (sign) {
+ char * newbuf = new char[std::strlen(result ? result : buf) + 1];
+ newbuf[0] = '-';
+ std::strcpy(&newbuf[1], result ? result : buf);
+ mpz_set_str(dest, newbuf, 10);
+ delete[] newbuf;
+ } else {
+ mpz_set_str(dest, result ? result : buf, 10);
+ }
+
+ if (result)
+ delete[] result;
+ freedtoa(buf);
+
+ return decpt;
+#endif
+ }
+}
+
+amount_t::amount_t(const double val)
+{
+ TRACE_CTOR(amount_t, "const double");
+ quantity = new bigint_t;
+ quantity->prec = convert_double(MPZ(quantity), val);
+ commodity_ = NULL;
+}
+
+void amount_t::_release()
+{
+ DEBUG_("amounts.refs",
+ quantity << " ref--, now " << (quantity->ref - 1));
+ if (--quantity->ref == 0) {
+ if (! (quantity->flags & BIGINT_BULK_ALLOC))
+ delete quantity;
+ else
+ quantity->~bigint_t();
+ }
+}
+
+void amount_t::_init()
+{
+ if (! quantity) {
+ quantity = new bigint_t;
+ }
+ else if (quantity->ref > 1) {
+ _release();
+ quantity = new bigint_t;
+ }
+}
+
+void amount_t::_dup()
+{
+ if (quantity->ref > 1) {
+ bigint_t * q = new bigint_t(*quantity);
+ _release();
+ quantity = q;
+ }
+}
+
+void amount_t::_copy(const amount_t& amt)
+{
+ if (quantity != amt.quantity) {
+ if (quantity)
+ _release();
+
+ // Never maintain a pointer into a bulk allocation pool; such
+ // pointers are not guaranteed to remain.
+ if (amt.quantity->flags & BIGINT_BULK_ALLOC) {
+ quantity = new bigint_t(*amt.quantity);
+ } else {
+ quantity = amt.quantity;
+ DEBUG_("amounts.refs",
+ quantity << " ref++, now " << (quantity->ref + 1));
+ quantity->ref++;
+ }
+ }
+ commodity_ = amt.commodity_;
+}
+
+amount_t& amount_t::operator=(const string& val)
+{
+ std::istringstream str(val);
+ parse(str);
+ return *this;
+}
+
+amount_t& amount_t::operator=(const char * val)
+{
+ string valstr(val);
+ std::istringstream str(valstr);
+ parse(str);
+ return *this;
+}
+
+// assignment operator
+amount_t& amount_t::operator=(const amount_t& amt)
+{
+ if (this != &amt) {
+ if (amt.quantity)
+ _copy(amt);
+ else if (quantity)
+ _clear();
+ }
+ return *this;
+}
+
+amount_t& amount_t::operator=(const long val)
+{
+ if (val == 0) {
+ if (quantity)
+ _clear();
+ } else {
+ commodity_ = NULL;
+ _init();
+ mpz_set_si(MPZ(quantity), val);
+ }
+ return *this;
+}
+
+amount_t& amount_t::operator=(const unsigned long val)
+{
+ if (val == 0) {
+ if (quantity)
+ _clear();
+ } else {
+ commodity_ = NULL;
+ _init();
+ mpz_set_ui(MPZ(quantity), val);
+ }
+ return *this;
+}
+
+amount_t& amount_t::operator=(const double val)
+{
+ commodity_ = NULL;
+ _init();
+ quantity->prec = convert_double(MPZ(quantity), val);
+ return *this;
+}
+
+
+void amount_t::_resize(unsigned int prec)
+{
+ assert(prec < 256);
+
+ if (! quantity || prec == quantity->prec)
+ return;
+
+ _dup();
+
+ if (prec < quantity->prec) {
+ mpz_ui_pow_ui(divisor, 10, quantity->prec - prec);
+ mpz_tdiv_q(MPZ(quantity), MPZ(quantity), divisor);
+ } else {
+ mpz_ui_pow_ui(divisor, 10, prec - quantity->prec);
+ mpz_mul(MPZ(quantity), MPZ(quantity), divisor);
+ }
+
+ quantity->prec = 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 (commodity() != amt.commodity()) {
+ throw amount_exception
+ (string("Adding amounts with different commodities: ") +
+ (has_commodity() ? commodity_->qualified_symbol : "NONE") + " != " +
+ (amt.has_commodity() ? amt.commodity_->qualified_symbol : "NONE"),
+ context());
+ }
+
+ if (! amt.quantity)
+ return *this;
+
+ if (! quantity) {
+ _copy(amt);
+ return *this;
+ }
+
+ _dup();
+
+ if (quantity->prec == amt.quantity->prec) {
+ mpz_add(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
+ }
+ else if (quantity->prec < amt.quantity->prec) {
+ _resize(amt.quantity->prec);
+ mpz_add(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
+ }
+ else {
+ amount_t t = amt;
+ t._resize(quantity->prec);
+ mpz_add(MPZ(quantity), MPZ(quantity), MPZ(t.quantity));
+ }
+
+ return *this;
+}
+
+amount_t& amount_t::operator-=(const amount_t& amt)
+{
+ if (commodity() != amt.commodity())
+ throw amount_exception
+ (string("Subtracting amounts with different commodities: ") +
+ (has_commodity() ? commodity_->qualified_symbol : "NONE") + " != " +
+ (amt.has_commodity() ? amt.commodity_->qualified_symbol : "NONE"),
+ context());
+
+ if (! amt.quantity)
+ return *this;
+
+ if (! quantity) {
+ quantity = new bigint_t(*amt.quantity);
+ commodity_ = amt.commodity_;
+ mpz_neg(MPZ(quantity), MPZ(quantity));
+ return *this;
+ }
+
+ _dup();
+
+ if (quantity->prec == amt.quantity->prec) {
+ mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
+ }
+ else if (quantity->prec < amt.quantity->prec) {
+ _resize(amt.quantity->prec);
+ mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
+ }
+ else {
+ amount_t t = amt;
+ t._resize(quantity->prec);
+ mpz_sub(MPZ(quantity), MPZ(quantity), MPZ(t.quantity));
+ }
+
+ return *this;
+}
+
+amount_t& amount_t::operator*=(const amount_t& amt)
+{
+ if (has_commodity() && amt.has_commodity() &&
+ commodity() != amt.commodity()) {
+ throw amount_exception
+ (string("Multiplying amounts with different commodities: ") +
+ (has_commodity() ? commodity_->qualified_symbol : "NONE") + " != " +
+ (amt.has_commodity() ? amt.commodity_->qualified_symbol : "NONE"),
+ context());
+ }
+
+ if (! amt.quantity) {
+ *this = *this - *this; // preserve our commodity
+ goto finish;
+ }
+ else if (! quantity) {
+ *this = amt;
+ *this = *this - *this; // preserve the foreign commodity
+ goto finish;
+ }
+
+ _dup();
+
+ mpz_mul(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
+ quantity->prec += amt.quantity->prec;
+
+ finish:
+ if (! has_commodity())
+ commodity_ = amt.commodity_;
+
+ if (has_commodity() && ! (quantity->flags & BIGINT_KEEP_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;
+ }
+ }
+
+ return *this;
+}
+
+amount_t& amount_t::operator/=(const amount_t& amt)
+{
+ if (has_commodity() && amt.has_commodity() &&
+ commodity() != amt.commodity()) {
+ throw amount_exception
+ (string("Dividing amounts with different commodities: ") +
+ (has_commodity() ? commodity_->qualified_symbol : "NONE") + " != " +
+ (amt.has_commodity() ? amt.commodity_->qualified_symbol : "NONE"),
+ context());
+ }
+
+ if (! amt.quantity || ! amt) {
+ throw amount_exception("Divide by zero", context());
+ }
+ else if (! quantity) {
+ *this = amt;
+ *this = *this - *this; // preserve the foreign commodity
+ goto finish;
+ }
+
+ _dup();
+
+ // Increase the value's precision, to capture fractional parts after
+ // the divide. Round up in the last position.
+
+ mpz_ui_pow_ui(divisor, 10, (2 * amt.quantity->prec) + quantity->prec + 7U);
+ mpz_mul(MPZ(quantity), MPZ(quantity), divisor);
+ mpz_tdiv_q(MPZ(quantity), MPZ(quantity), MPZ(amt.quantity));
+ quantity->prec += amt.quantity->prec + quantity->prec + 7U;
+
+ mpz_round(MPZ(quantity), MPZ(quantity), quantity->prec, quantity->prec - 1);
+ quantity->prec -= 1;
+
+ finish:
+ if (! has_commodity())
+ commodity_ = amt.commodity_;
+
+ // If this amount has a commodity, and we're not dealing with plain
+ // numbers, or internal numbers (which keep full precision at all
+ // times), then round the number to within the commodity's precision
+ // plus six places.
+
+ if (has_commodity() && ! (quantity->flags & BIGINT_KEEP_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;
+ }
+ }
+
+ return *this;
+}
+
+// unary negation
+void amount_t::in_place_negate()
+{
+ if (quantity) {
+ _dup();
+ mpz_neg(MPZ(quantity), MPZ(quantity));
+ }
+}
+
+int amount_t::sign() const
+{
+ return quantity ? mpz_sgn(MPZ(quantity)) : 0;
+}
+
+int amount_t::compare(const amount_t& amt) const
+{
+ if (! quantity) {
+ if (! amt.quantity)
+ return 0;
+ return - amt.sign();
+ }
+ if (! amt.quantity)
+ return sign();
+
+ if (has_commodity() && amt.commodity() && commodity() != amt.commodity())
+ throw amount_exception
+ (string("Cannot compare amounts with different commodities: ") +
+ commodity().symbol() + " and " + amt.commodity().symbol(),
+ context());
+
+ if (quantity->prec == amt.quantity->prec) {
+ return mpz_cmp(MPZ(quantity), MPZ(amt.quantity));
+ }
+ else if (quantity->prec < amt.quantity->prec) {
+ amount_t t = *this;
+ t._resize(amt.quantity->prec);
+ return mpz_cmp(MPZ(t.quantity), MPZ(amt.quantity));
+ }
+ else {
+ amount_t t = amt;
+ t._resize(quantity->prec);
+ return mpz_cmp(MPZ(quantity), MPZ(t.quantity));
+ }
+}
+
+bool amount_t::operator==(const amount_t& amt) const
+{
+ if (commodity() != amt.commodity())
+ return false;
+ return compare(amt) == 0;
+}
+
+bool amount_t::operator!=(const amount_t& amt) const
+{
+ if (commodity() != amt.commodity())
+ return true;
+ return compare(amt) != 0;
+}
+
+bool amount_t::zero() const
+{
+ if (! quantity)
+ return true;
+
+ if (has_commodity()) {
+ if (quantity->prec <= commodity().precision())
+ return realzero();
+ else
+ return round(commodity().precision()).sign() == 0;
+ }
+ return realzero();
+}
+
+amount_t::operator long() const
+{
+ if (! quantity)
+ return 0;
+
+ mpz_set(temp, MPZ(quantity));
+ mpz_ui_pow_ui(divisor, 10, quantity->prec);
+ mpz_tdiv_q(temp, temp, divisor);
+ return mpz_get_si(temp);
+}
+
+amount_t::operator double() const
+{
+ if (! quantity)
+ return 0.0;
+
+ mpz_t remainder;
+ mpz_init(remainder);
+
+ mpz_set(temp, MPZ(quantity));
+ mpz_ui_pow_ui(divisor, 10, quantity->prec);
+ mpz_tdiv_qr(temp, remainder, temp, divisor);
+
+ char * quotient_s = mpz_get_str(NULL, 10, temp);
+ char * remainder_s = mpz_get_str(NULL, 10, remainder);
+
+ std::ostringstream num;
+ num << quotient_s << '.' << remainder_s;
+
+ std::free(quotient_s);
+ std::free(remainder_s);
+
+ mpz_clear(remainder);
+
+ return std::atof(num.str().c_str());
+}
+
+amount_t amount_t::value(const moment_t& moment) const
+{
+ if (quantity) {
+ amount_t amt(commodity().value(moment));
+ if (! amt.realzero())
+ return (amt * number()).round();
+ }
+ return *this;
+}
+
+amount_t amount_t::round(unsigned int prec) const
+{
+ amount_t t = *this;
+
+ if (! quantity || quantity->prec <= prec) {
+ if (quantity && quantity->flags & BIGINT_KEEP_PREC) {
+ t._dup();
+ t.quantity->flags &= ~BIGINT_KEEP_PREC;
+ }
+ return t;
+ }
+
+ t._dup();
+
+ mpz_round(MPZ(t.quantity), MPZ(t.quantity), t.quantity->prec, prec);
+
+ t.quantity->prec = prec;
+ t.quantity->flags &= ~BIGINT_KEEP_PREC;
+
+ return t;
+}
+
+amount_t amount_t::unround() const
+{
+ if (! quantity) {
+ amount_t t(0L);
+ assert(t.quantity);
+ t.quantity->flags |= BIGINT_KEEP_PREC;
+ return t;
+ }
+ else if (quantity->flags & BIGINT_KEEP_PREC) {
+ return *this;
+ }
+
+ amount_t t = *this;
+ t._dup();
+ t.quantity->flags |= BIGINT_KEEP_PREC;
+
+ return t;
+}
+
+void amount_t::print_quantity(std::ostream& out) const
+{
+ if (! quantity) {
+ out << "0";
+ return;
+ }
+
+ mpz_t quotient;
+ mpz_t rquotient;
+ mpz_t remainder;
+
+ mpz_init(quotient);
+ mpz_init(rquotient);
+ mpz_init(remainder);
+
+ bool negative = false;
+
+ // Ensure the value is rounded to the commodity's precision before
+ // outputting it. NOTE: `rquotient' is used here as a temp variable!
+
+ commodity_t& comm(commodity());
+ unsigned char precision;
+
+ if (! comm || quantity->flags & BIGINT_KEEP_PREC) {
+ mpz_ui_pow_ui(divisor, 10, quantity->prec);
+ mpz_tdiv_qr(quotient, remainder, MPZ(quantity), divisor);
+ precision = quantity->prec;
+ }
+ else if (comm.precision() < quantity->prec) {
+ mpz_round(rquotient, MPZ(quantity), quantity->prec, comm.precision());
+ mpz_ui_pow_ui(divisor, 10, comm.precision());
+ mpz_tdiv_qr(quotient, remainder, rquotient, divisor);
+ precision = comm.precision();
+ }
+ else if (comm.precision() > quantity->prec) {
+ mpz_ui_pow_ui(divisor, 10, comm.precision() - quantity->prec);
+ mpz_mul(rquotient, MPZ(quantity), divisor);
+ mpz_ui_pow_ui(divisor, 10, comm.precision());
+ mpz_tdiv_qr(quotient, remainder, rquotient, divisor);
+ precision = comm.precision();
+ }
+ else if (quantity->prec) {
+ mpz_ui_pow_ui(divisor, 10, quantity->prec);
+ mpz_tdiv_qr(quotient, remainder, MPZ(quantity), divisor);
+ precision = quantity->prec;
+ }
+ else {
+ mpz_set(quotient, MPZ(quantity));
+ mpz_set_ui(remainder, 0);
+ precision = 0;
+ }
+
+ if (mpz_sgn(quotient) < 0 || mpz_sgn(remainder) < 0) {
+ negative = true;
+
+ mpz_abs(quotient, quotient);
+ mpz_abs(remainder, remainder);
+ }
+ mpz_set(rquotient, remainder);
+
+ if (mpz_sgn(quotient) == 0 && mpz_sgn(rquotient) == 0) {
+ out << "0";
+ return;
+ }
+
+ if (negative)
+ out << "-";
+
+ if (mpz_sgn(quotient) == 0) {
+ out << '0';
+ } else {
+ char * p = mpz_get_str(NULL, 10, quotient);
+ out << p;
+ std::free(p);
+ }
+
+ if (precision) {
+ out << '.';
+
+ out.width(precision);
+ out.fill('0');
+
+ char * p = mpz_get_str(NULL, 10, rquotient);
+ out << p;
+ std::free(p);
+ }
+
+ mpz_clear(quotient);
+ mpz_clear(rquotient);
+ mpz_clear(remainder);
+}
+
+void amount_t::print(std::ostream& _out, bool omit_commodity,
+ bool full_precision) const
+{
+ amount_t base(*this);
+ if (! amount_t::keep_base && commodity().larger()) {
+ amount_t last(*this);
+ while (last.commodity().larger()) {
+ last /= last.commodity().larger()->number();
+ last.commodity_ = last.commodity().larger()->commodity_;
+ if (last.abs() < 1)
+ break;
+ base = last.round();
+ }
+ }
+
+ std::ostringstream out;
+
+ mpz_t quotient;
+ mpz_t rquotient;
+ mpz_t remainder;
+
+ mpz_init(quotient);
+ mpz_init(rquotient);
+ mpz_init(remainder);
+
+ bool negative = false;
+
+ // Ensure the value is rounded to the commodity's precision before
+ // outputting it. NOTE: `rquotient' is used here as a temp variable!
+
+ commodity_t& comm(base.commodity());
+ unsigned char precision = 0;
+
+ if (quantity) {
+ if (! comm || full_precision || base.quantity->flags & BIGINT_KEEP_PREC) {
+ mpz_ui_pow_ui(divisor, 10, base.quantity->prec);
+ mpz_tdiv_qr(quotient, remainder, MPZ(base.quantity), divisor);
+ precision = base.quantity->prec;
+ }
+ else if (comm.precision() < base.quantity->prec) {
+ mpz_round(rquotient, MPZ(base.quantity), base.quantity->prec,
+ comm.precision());
+ mpz_ui_pow_ui(divisor, 10, comm.precision());
+ mpz_tdiv_qr(quotient, remainder, rquotient, divisor);
+ precision = comm.precision();
+ }
+ else if (comm.precision() > base.quantity->prec) {
+ mpz_ui_pow_ui(divisor, 10, comm.precision() - base.quantity->prec);
+ mpz_mul(rquotient, MPZ(base.quantity), divisor);
+ mpz_ui_pow_ui(divisor, 10, comm.precision());
+ mpz_tdiv_qr(quotient, remainder, rquotient, divisor);
+ precision = comm.precision();
+ }
+ else if (base.quantity->prec) {
+ mpz_ui_pow_ui(divisor, 10, base.quantity->prec);
+ mpz_tdiv_qr(quotient, remainder, MPZ(base.quantity), divisor);
+ precision = base.quantity->prec;
+ }
+ else {
+ mpz_set(quotient, MPZ(base.quantity));
+ mpz_set_ui(remainder, 0);
+ precision = 0;
+ }
+
+ if (mpz_sgn(quotient) < 0 || mpz_sgn(remainder) < 0) {
+ negative = true;
+
+ mpz_abs(quotient, quotient);
+ mpz_abs(remainder, remainder);
+ }
+ mpz_set(rquotient, remainder);
+ }
+
+ if (! omit_commodity && ! (comm.flags() & COMMODITY_STYLE_SUFFIXED)) {
+ comm.write(out);
+
+ if (comm.flags() & COMMODITY_STYLE_SEPARATED)
+ out << " ";
+ }
+
+ if (negative)
+ out << "-";
+
+ if (! quantity || mpz_sgn(quotient) == 0) {
+ out << '0';
+ }
+ else if (! (comm.flags() & COMMODITY_STYLE_THOUSANDS)) {
+ char * p = mpz_get_str(NULL, 10, quotient);
+ out << p;
+ std::free(p);
+ }
+ else {
+ std::list<string> strs;
+ char buf[4];
+
+ for (int powers = 0; true; powers += 3) {
+ if (powers > 0) {
+ mpz_ui_pow_ui(divisor, 10, powers);
+ mpz_tdiv_q(temp, quotient, divisor);
+ if (mpz_sgn(temp) == 0)
+ break;
+ mpz_tdiv_r_ui(temp, temp, 1000);
+ } else {
+ mpz_tdiv_r_ui(temp, quotient, 1000);
+ }
+ mpz_get_str(buf, 10, temp);
+ strs.push_back(buf);
+ }
+
+ bool printed = false;
+
+ for (std::list<string>::reverse_iterator i = strs.rbegin();
+ i != strs.rend();
+ i++) {
+ if (printed) {
+ out << (comm.flags() & COMMODITY_STYLE_EUROPEAN ? '.' : ',');
+ out.width(3);
+ out.fill('0');
+ }
+ out << *i;
+
+ printed = true;
+ }
+ }
+
+ if (quantity && precision) {
+ std::ostringstream final;
+ final.width(precision);
+ final.fill('0');
+ char * p = mpz_get_str(NULL, 10, rquotient);
+ final << p;
+ std::free(p);
+
+ const string& str(final.str());
+ int i, len = str.length();
+ const char * q = str.c_str();
+ for (i = len; i > 0; i--)
+ if (q[i - 1] != '0')
+ break;
+
+ string ender;
+ if (i == len)
+ ender = str;
+ else if (i < comm.precision())
+ ender = string(str, 0, comm.precision());
+ else
+ ender = string(str, 0, i);
+
+ if (! ender.empty()) {
+ out << ((comm.flags() & COMMODITY_STYLE_EUROPEAN) ? ',' : '.');
+ out << ender;
+ }
+ }
+
+ if (! omit_commodity && comm.flags() & COMMODITY_STYLE_SUFFIXED) {
+ if (comm.flags() & COMMODITY_STYLE_SEPARATED)
+ out << " ";
+
+ comm.write(out);
+ }
+
+ mpz_clear(quotient);
+ mpz_clear(rquotient);
+ mpz_clear(remainder);
+
+ // If there are any annotations associated with this commodity,
+ // output them now.
+
+ if (! omit_commodity && comm.annotated) {
+ annotated_commodity_t& ann(static_cast<annotated_commodity_t&>(comm));
+ assert(&ann.price != this);
+ ann.write_annotations(out);
+ }
+
+ // Things are output to a string first, so that if anyone has
+ // specified a width or fill for _out, it will be applied to the
+ // entire amount string, and not just the first part.
+
+ _out << out.str();
+
+ return;
+}
+
+static void parse_quantity(std::istream& in, string& value)
+{
+ char buf[256];
+ char c = peek_next_nonws(in);
+ READ_INTO(in, buf, 255, c,
+ std::isdigit(c) || c == '-' || c == '.' || c == ',');
+
+ int len = std::strlen(buf);
+ while (len > 0 && ! std::isdigit(buf[len - 1])) {
+ buf[--len] = '\0';
+ in.unget();
+ }
+
+ value = buf;
+}
+
+// Invalid commodity characters:
+// SPACE, TAB, NEWLINE, RETURN
+// 0-9 . , ; - + * / ^ ? : & | ! =
+// < > { } [ ] ( ) @
+
+int invalid_chars[256] = {
+ /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */
+/* 00 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0,
+/* 10 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* 20 */ 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1,
+/* 30 */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+/* 40 */ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* 50 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0,
+/* 60 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* 70 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0,
+/* 80 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* 90 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* a0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* b0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* c0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* d0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* e0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+/* f0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+};
+
+static void parse_commodity(std::istream& in, string& symbol)
+{
+ char buf[256];
+ char c = peek_next_nonws(in);
+ if (c == '"') {
+ in.get(c);
+ READ_INTO(in, buf, 255, c, c != '"');
+ if (c == '"')
+ in.get(c);
+ else
+ throw amount_exception("Quoted commodity symbol lacks closing quote",
+ context());
+ } else {
+ READ_INTO(in, buf, 255, c, ! invalid_chars[(unsigned char)c]);
+ }
+ symbol = buf;
+}
+
+bool parse_annotations(std::istream& in, amount_t& price,
+ moment_t& date, string& tag)
+{
+ bool has_date = false;
+
+ do {
+ char buf[256];
+ char c = peek_next_nonws(in);
+ if (c == '{') {
+ if (price)
+ throw amount_exception("Commodity specifies more than one price",
+ context());
+
+ in.get(c);
+ READ_INTO(in, buf, 255, c, c != '}');
+ if (c == '}')
+ in.get(c);
+ else
+ throw amount_exception("Commodity price lacks closing brace", context());
+
+ price.parse(buf, AMOUNT_PARSE_NO_MIGRATE);
+ price.in_place_reduce();
+
+ // Since this price will maintain its own precision, make sure
+ // it is at least as large as the base commodity, since the user
+ // may have only specified {$1} or something similar.
+
+ if (price.has_commodity() &&
+ price.quantity->prec < price.commodity().precision())
+ price = price.round(); // no need to retain individual precision
+ }
+ else if (c == '[') {
+ if (is_valid_moment(date))
+ throw amount_exception("Commodity specifies more than one date",
+ context());
+
+ in.get(c);
+ READ_INTO(in, buf, 255, c, c != ']');
+ if (c == ']')
+ in.get(c);
+ else
+ throw amount_exception("Commodity date lacks closing bracket",
+ context());
+
+ date = parse_datetime(buf);
+ has_date = true;
+ }
+ else if (c == '(') {
+ if (! tag.empty())
+ throw amount_exception("Commodity specifies more than one tag",
+ context());
+
+ in.get(c);
+ READ_INTO(in, buf, 255, c, c != ')');
+ if (c == ')')
+ in.get(c);
+ else
+ throw amount_exception("Commodity tag lacks closing parenthesis",
+ context());
+
+ tag = buf;
+ }
+ else {
+ break;
+ }
+ } while (true);
+
+ DEBUG_("amounts.commodities",
+ "Parsed commodity annotations: "
+ << " price " << price << " "
+ << " date " << date << " "
+ << " tag " << tag);
+
+ return has_date;
+}
+
+void amount_t::parse(std::istream& in, unsigned char flags)
+{
+ // The possible syntax for an amount is:
+ //
+ // [-]NUM[ ]SYM [@ AMOUNT]
+ // SYM[ ][-]NUM [@ AMOUNT]
+
+ string symbol;
+ string quant;
+ amount_t tprice;
+ moment_t tdate;
+ bool had_date = false;
+ string tag;
+ unsigned int comm_flags = COMMODITY_STYLE_DEFAULTS;
+ bool negative = false;
+
+ char c = peek_next_nonws(in);
+ if (c == '-') {
+ negative = true;
+ in.get(c);
+ c = peek_next_nonws(in);
+ }
+
+ char n;
+ if (std::isdigit(c)) {
+ parse_quantity(in, quant);
+
+ if (! in.eof() && ((n = in.peek()) != '\n')) {
+ if (std::isspace(n))
+ comm_flags |= COMMODITY_STYLE_SEPARATED;
+
+ parse_commodity(in, symbol);
+
+ if (! symbol.empty())
+ comm_flags |= COMMODITY_STYLE_SUFFIXED;
+
+ if (! in.eof() && ((n = in.peek()) != '\n'))
+ had_date = parse_annotations(in, tprice, tdate, tag);
+ }
+ } else {
+ parse_commodity(in, symbol);
+
+ if (! in.eof() && ((n = in.peek()) != '\n')) {
+ if (std::isspace(in.peek()))
+ comm_flags |= COMMODITY_STYLE_SEPARATED;
+
+ parse_quantity(in, quant);
+
+ if (! quant.empty() && ! in.eof() && ((n = in.peek()) != '\n'))
+ had_date = parse_annotations(in, tprice, tdate, tag);
+ }
+ }
+
+ if (quant.empty())
+ throw amount_exception("No quantity specified for amount",
+ context());
+
+ _init();
+
+ // Create the commodity if has not already been seen, and update the
+ // precision if something greater was used for the quantity.
+
+ bool newly_created = false;
+
+ if (symbol.empty()) {
+ commodity_ = NULL;
+ } else {
+ commodity_ = commodity_t::find(symbol);
+ if (! commodity_) {
+ commodity_ = commodity_t::create(symbol);
+ newly_created = true;
+ }
+ assert(commodity_);
+
+ if (! tprice.realzero() || had_date || ! tag.empty())
+ commodity_ =
+ annotated_commodity_t::find_or_create(*commodity_, tprice, tdate, tag);
+ }
+
+ // Determine the precision of the amount, based on the usage of
+ // comma or period.
+
+ string::size_type last_comma = quant.rfind(',');
+ string::size_type last_period = quant.rfind('.');
+
+ if (last_comma != string::npos && last_period != string::npos) {
+ comm_flags |= COMMODITY_STYLE_THOUSANDS;
+ if (last_comma > last_period) {
+ comm_flags |= COMMODITY_STYLE_EUROPEAN;
+ quantity->prec = quant.length() - last_comma - 1;
+ } else {
+ quantity->prec = quant.length() - last_period - 1;
+ }
+ }
+ else if (last_comma != string::npos &&
+ commodity().flags() & COMMODITY_STYLE_EUROPEAN) {
+ quantity->prec = quant.length() - last_comma - 1;
+ }
+ else if (last_period != string::npos &&
+ ! (commodity().flags() & COMMODITY_STYLE_EUROPEAN)) {
+ quantity->prec = quant.length() - last_period - 1;
+ }
+ else {
+ quantity->prec = 0;
+ }
+
+ // Set the commodity's flags and precision accordingly
+
+ if (commodity_ && (newly_created || ! (flags & AMOUNT_PARSE_NO_MIGRATE))) {
+ commodity().add_flags(comm_flags);
+ if (quantity->prec > commodity().precision())
+ commodity().set_precision(quantity->prec);
+ }
+
+ if (flags & AMOUNT_PARSE_NO_MIGRATE)
+ quantity->flags |= BIGINT_KEEP_PREC;
+
+ // Now we have the final number. Remove commas and periods, if
+ // necessary.
+
+ if (last_comma != string::npos || last_period != string::npos) {
+ int len = quant.length();
+ char * buf = new char[len + 1];
+ const char * p = quant.c_str();
+ char * t = buf;
+
+ while (*p) {
+ if (*p == ',' || *p == '.')
+ p++;
+ *t++ = *p++;
+ }
+ *t = '\0';
+
+ mpz_set_str(MPZ(quantity), buf, 10);
+ delete[] buf;
+ } else {
+ mpz_set_str(MPZ(quantity), quant.c_str(), 10);
+ }
+
+ if (negative)
+ in_place_negate();
+
+ if (! (flags & AMOUNT_PARSE_NO_REDUCE))
+ in_place_reduce();
+}
+
+void amount_t::in_place_reduce()
+{
+ while (commodity_ && commodity().smaller()) {
+ *this *= commodity().smaller()->number();
+ commodity_ = commodity().smaller()->commodity_;
+ }
+}
+
+void parse_conversion(const string& larger_str,
+ const string& smaller_str)
+{
+ amount_t larger, smaller;
+
+ larger.parse(larger_str.c_str(), AMOUNT_PARSE_NO_REDUCE);
+ smaller.parse(smaller_str.c_str(), AMOUNT_PARSE_NO_REDUCE);
+
+ larger *= smaller.number();
+
+ if (larger.commodity()) {
+ larger.commodity().set_smaller(smaller);
+ larger.commodity().add_flags(smaller.commodity().flags() |
+ COMMODITY_STYLE_NOMARKET);
+ }
+ if (smaller.commodity())
+ 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);
+}
+
+
+#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)
+{
+ char byte = *data++;;
+
+ if (byte == 0) {
+ quantity = NULL;
+ }
+ else if (byte == 1) {
+ quantity = new((bigint_t *)bigints_next) bigint_t;
+ bigints_next += sizeof(bigint_t);
+
+ unsigned short len = *((unsigned short *) data);
+ data += sizeof(unsigned short);
+ mpz_import(MPZ(quantity), len / sizeof(short), 1, sizeof(short),
+ 0, 0, data);
+ data += len;
+
+ char negative = *data++;
+ if (negative)
+ mpz_neg(MPZ(quantity), MPZ(quantity));
+
+ quantity->prec = *((unsigned char *) data);
+ data += sizeof(unsigned char);
+ quantity->flags = *((unsigned char *) data);
+ data += sizeof(unsigned char);
+ quantity->flags |= BIGINT_BULK_ALLOC;
+ } else {
+ unsigned int index = *((unsigned int *) data);
+ data += sizeof(unsigned int);
+
+ quantity = (bigint_t *) (bigints + (index - 1) * sizeof(bigint_t));
+ DEBUG_("amounts.refs",
+ quantity << " ref++, now " << (quantity->ref + 1));
+ quantity->ref++;
+ }
+}
+
+#ifndef THREADSAFE
+static char buf[4096];
+#endif
+
+void amount_t::read_quantity(std::istream& in)
+{
+ char byte;
+ in.read(&byte, sizeof(byte));
+
+ if (byte == 0) {
+ quantity = NULL;
+ }
+ else if (byte == 1) {
+ quantity = new bigint_t;
+
+ unsigned short len;
+ in.read((char *)&len, sizeof(len));
+ assert(len < 4096);
+ in.read(buf, len);
+ mpz_import(MPZ(quantity), len / sizeof(short), 1, sizeof(short),
+ 0, 0, buf);
+
+ char negative;
+ in.read(&negative, sizeof(negative));
+ if (negative)
+ mpz_neg(MPZ(quantity), MPZ(quantity));
+
+ in.read((char *)&quantity->prec, sizeof(quantity->prec));
+ in.read((char *)&quantity->flags, sizeof(quantity->flags));
+ }
+ else {
+ assert(0);
+ }
+}
+
+void amount_t::write_quantity(std::ostream& out) const
+{
+ char byte;
+
+ if (! quantity) {
+ byte = 0;
+ out.write(&byte, sizeof(byte));
+ return;
+ }
+
+ if (quantity->index == 0) {
+ quantity->index = ++bigints_index;
+ bigints_count++;
+
+ byte = 1;
+ out.write(&byte, sizeof(byte));
+
+ std::size_t size;
+ mpz_export(buf, &size, 1, sizeof(short), 0, 0, MPZ(quantity));
+ unsigned short len = size * sizeof(short);
+ out.write((char *)&len, sizeof(len));
+ if (len) {
+ assert(len < 4096);
+ out.write(buf, len);
+ }
+
+ byte = mpz_sgn(MPZ(quantity)) < 0 ? 1 : 0;
+ out.write(&byte, sizeof(byte));
+
+ out.write((char *)&quantity->prec, sizeof(quantity->prec));
+ unsigned char flags = quantity->flags & ~BIGINT_BULK_ALLOC;
+ assert(sizeof(flags) == sizeof(quantity->flags));
+ out.write((char *)&flags, sizeof(flags));
+ } else {
+ assert(quantity->ref > 1);
+
+ // Since this value has already been written, we simply write
+ // out a reference to which one it was.
+ byte = 2;
+ out.write(&byte, sizeof(byte));
+ out.write((char *)&quantity->index, sizeof(quantity->index));
+ }
+}
+
+bool amount_t::valid() const
+{
+ if (quantity) {
+ if (quantity->ref == 0) {
+ DEBUG_("ledger.validate", "amount_t: quantity->ref == 0");
+ return false;
+ }
+ }
+ else if (commodity_) {
+ DEBUG_("ledger.validate", "amount_t: commodity_ != NULL");
+ return false;
+ }
+ return true;
+}
+
+void amount_t::annotate_commodity(const amount_t& tprice,
+ const moment_t& tdate,
+ const string& tag)
+{
+ const commodity_t * this_base;
+ annotated_commodity_t * this_ann = NULL;
+
+ if (commodity().annotated) {
+ this_ann = &static_cast<annotated_commodity_t&>(commodity());
+ this_base = this_ann->ptr;
+ } else {
+ this_base = &commodity();
+ }
+ assert(this_base);
+
+ DEBUG_("amounts.commodities", "Annotating commodity for amount "
+ << *this << std::endl
+ << " price " << tprice << " "
+ << " date " << tdate << " "
+ << " tag " << tag);
+
+ commodity_t * ann_comm =
+ annotated_commodity_t::find_or_create
+ (*this_base, ! tprice && this_ann ? this_ann->price : tprice,
+ ! is_valid_moment(tdate) && this_ann ? this_ann->date : tdate,
+ tag.empty() && this_ann ? this_ann->tag : tag);
+ if (ann_comm)
+ set_commodity(*ann_comm);
+
+ DEBUG_("amounts.commodities", " Annotated amount is " << *this);
+}
+
+amount_t amount_t::strip_annotations(const bool _keep_price,
+ const bool _keep_date,
+ const bool _keep_tag) const
+{
+ if (! commodity().annotated ||
+ (_keep_price && _keep_date && _keep_tag))
+ return *this;
+
+ DEBUG_("amounts.commodities", "Reducing commodity for amount "
+ << *this << std::endl
+ << " keep price " << _keep_price << " "
+ << " keep date " << _keep_date << " "
+ << " keep tag " << _keep_tag);
+
+ annotated_commodity_t&
+ ann_comm(static_cast<annotated_commodity_t&>(commodity()));
+ assert(ann_comm.base);
+
+ commodity_t * new_comm;
+
+ if ((_keep_price && ann_comm.price) ||
+ (_keep_date && is_valid_moment(ann_comm.date)) ||
+ (_keep_tag && ! ann_comm.tag.empty()))
+ {
+ new_comm = annotated_commodity_t::find_or_create
+ (*ann_comm.ptr, _keep_price ? ann_comm.price : amount_t(),
+ _keep_date ? ann_comm.date : moment_t(),
+ _keep_tag ? ann_comm.tag : "");
+ } else {
+ new_comm = commodity_t::find_or_create(ann_comm.base_symbol());
+ }
+ assert(new_comm);
+
+ amount_t t(*this);
+ t.set_commodity(*new_comm);
+ DEBUG_("amounts.commodities", " Reduced amount is " << t);
+
+ return t;
+}
+
+amount_t amount_t::price() const
+{
+ if (commodity_ && commodity_->annotated) {
+ amount_t t(((annotated_commodity_t *)commodity_)->price);
+ t *= number();
+ DEBUG_("amounts.commodities",
+ "Returning price of " << *this << " = " << t);
+ return t;
+ }
+ return *this;
+}
+
+moment_t amount_t::date() const
+{
+ if (commodity_ && commodity_->annotated) {
+ DEBUG_("amounts.commodities",
+ "Returning date of " << *this << " = "
+ << ((annotated_commodity_t *)commodity_)->date);
+ return ((annotated_commodity_t *)commodity_)->date;
+ }
+ return moment_t();
+}
+
+
+void commodity_base_t::add_price(const moment_t& date,
+ const amount_t& price)
+{
+ if (! history)
+ history = new history_t;
+
+ history_map::iterator i = history->prices.find(date);
+ if (i != history->prices.end()) {
+ (*i).second = price;
+ } else {
+ std::pair<history_map::iterator, bool> result
+ = history->prices.insert(history_pair(date, price));
+ assert(result.second);
+ }
+}
+
+bool commodity_base_t::remove_price(const moment_t& date)
+{
+ if (history) {
+ history_map::size_type n = history->prices.erase(date);
+ if (n > 0) {
+ if (history->prices.empty())
+ history = NULL;
+ return true;
+ }
+ }
+ return false;
+}
+
+commodity_base_t * commodity_base_t::create(const string& symbol)
+{
+ commodity_base_t * commodity = new commodity_base_t(symbol);
+
+ DEBUG_("amounts.commodities", "Creating base commodity " << symbol);
+
+ std::pair<base_commodities_map::iterator, bool> result
+ = commodities.insert(base_commodities_pair(symbol, commodity));
+ assert(result.second);
+
+ return commodity;
+}
+
+bool commodity_t::needs_quotes(const string& symbol)
+{
+ for (const char * p = symbol.c_str(); *p; p++)
+ if (std::isspace(*p) || std::isdigit(*p) || *p == '-' || *p == '.')
+ return true;
+
+ return false;
+}
+
+bool commodity_t::valid() const
+{
+ if (symbol().empty() && this != null_commodity) {
+ DEBUG_("ledger.validate",
+ "commodity_t: symbol().empty() && this != null_commodity");
+ return false;
+ }
+
+ if (annotated && ! base) {
+ DEBUG_("ledger.validate", "commodity_t: annotated && ! base");
+ return false;
+ }
+
+ if (precision() > 16) {
+ DEBUG_("ledger.validate", "commodity_t: precision() > 16");
+ return false;
+ }
+
+ return true;
+}
+
+commodity_t * commodity_t::create(const string& symbol)
+{
+ std::auto_ptr<commodity_t> commodity(new commodity_t);
+
+ commodity->base = commodity_base_t::create(symbol);
+
+ if (needs_quotes(symbol)) {
+ commodity->qualified_symbol = "\"";
+ commodity->qualified_symbol += symbol;
+ commodity->qualified_symbol += "\"";
+ } else {
+ commodity->qualified_symbol = symbol;
+ }
+
+ DEBUG_("amounts.commodities",
+ "Creating commodity " << commodity->qualified_symbol);
+
+ std::pair<commodities_map::iterator, bool> result
+ = commodities.insert(commodities_pair(symbol, commodity.get()));
+ 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)
+ commodity->drop_flags(COMMODITY_STYLE_THOUSANDS |
+ COMMODITY_STYLE_NOMARKET);
+
+ return commodity.release();
+}
+
+commodity_t * commodity_t::find_or_create(const string& symbol)
+{
+ DEBUG_("amounts.commodities", "Find-or-create commodity " << symbol);
+
+ commodity_t * commodity = find(symbol);
+ if (commodity)
+ return commodity;
+ return create(symbol);
+}
+
+commodity_t * commodity_t::find(const string& symbol)
+{
+ DEBUG_("amounts.commodities", "Find commodity " << symbol);
+
+ commodities_map::const_iterator i = commodities.find(symbol);
+ if (i != commodities.end())
+ return (*i).second;
+ return NULL;
+}
+
+amount_t commodity_base_t::value(const moment_t& moment)
+{
+ moment_t age;
+ amount_t price;
+
+ if (history) {
+ assert(history->prices.size() > 0);
+
+ if (! is_valid_moment(moment)) {
+ history_map::reverse_iterator r = history->prices.rbegin();
+ age = (*r).first;
+ price = (*r).second;
+ } else {
+ history_map::iterator i = history->prices.lower_bound(moment);
+ if (i == history->prices.end()) {
+ history_map::reverse_iterator r = history->prices.rbegin();
+ age = (*r).first;
+ price = (*r).second;
+ } else {
+ age = (*i).first;
+ if (moment != age) {
+ if (i != history->prices.begin()) {
+ --i;
+ age = (*i).first;
+ price = (*i).second;
+ } else {
+ age = moment_t();
+ }
+ } else {
+ price = (*i).second;
+ }
+ }
+ }
+ }
+
+ if (updater && ! (flags & COMMODITY_STYLE_NOMARKET))
+ (*updater)(*this, moment, age,
+ (history && history->prices.size() > 0 ?
+ (*history->prices.rbegin()).first : moment_t()), price);
+
+ return price;
+}
+
+bool annotated_commodity_t::operator==(const commodity_t& comm) const
+{
+ // If the base commodities don't match, the game's up.
+ if (base != comm.base)
+ return false;
+
+ if (price &&
+ (! comm.annotated ||
+ price != static_cast<const annotated_commodity_t&>(comm).price))
+ return false;
+
+ if (is_valid_moment(date) &&
+ (! comm.annotated ||
+ date != static_cast<const annotated_commodity_t&>(comm).date))
+ return false;
+
+ if (! tag.empty() &&
+ (! comm.annotated ||
+ tag != static_cast<const annotated_commodity_t&>(comm).tag))
+ return false;
+
+ return true;
+}
+
+void
+annotated_commodity_t::write_annotations(std::ostream& out,
+ const amount_t& price,
+ const moment_t& date,
+ const string& tag)
+{
+ if (price)
+ out << " {" << price << '}';
+
+ if (is_valid_moment(date))
+ out << " [" << date << ']';
+
+ if (! tag.empty())
+ out << " (" << tag << ')';
+}
+
+commodity_t *
+annotated_commodity_t::create(const commodity_t& comm,
+ const amount_t& price,
+ const moment_t& date,
+ const string& tag,
+ const string& mapping_key)
+{
+ std::auto_ptr<annotated_commodity_t> commodity(new annotated_commodity_t);
+
+ // Set the annotated bits
+ commodity->price = price;
+ commodity->date = date;
+ commodity->tag = tag;
+
+ commodity->ptr = &comm;
+ assert(commodity->ptr);
+ commodity->base = comm.base;
+ assert(commodity->base);
+
+ commodity->qualified_symbol = comm.symbol();
+
+ DEBUG_("amounts.commodities", "Creating annotated commodity "
+ << "symbol " << commodity->symbol()
+ << " key " << mapping_key << std::endl
+ << " price " << price << " "
+ << " date " << date << " "
+ << " tag " << tag);
+
+ // Add the fully annotated name to the map, so that this symbol may
+ // quickly be found again.
+ std::pair<commodities_map::iterator, bool> result
+ = commodities.insert(commodities_pair(mapping_key, commodity.get()));
+ if (! result.second)
+ return NULL;
+
+ commodity->ident = commodities_by_ident->size();
+ commodities_by_ident->push_back(commodity.get());
+
+ return commodity.release();
+}
+
+namespace {
+ string make_qualified_name(const commodity_t& comm,
+ const amount_t& price,
+ const moment_t& date,
+ const string& tag)
+ {
+ if (price < 0)
+ throw amount_exception("A commodity's price may not be negative",
+ context());
+
+ std::ostringstream name;
+
+ comm.write(name);
+ annotated_commodity_t::write_annotations(name, price, date, tag);
+
+ DEBUG_("amounts.commodities", "make_qualified_name for "
+ << comm.qualified_symbol << std::endl
+ << " price " << price << " "
+ << " date " << date << " "
+ << " tag " << tag);
+
+ DEBUG_("amounts.commodities", "qualified_name is " << name.str());
+
+ return name.str();
+ }
+}
+
+commodity_t *
+annotated_commodity_t::find_or_create(const commodity_t& comm,
+ const amount_t& price,
+ const moment_t& date,
+ const string& tag)
+{
+ string name = make_qualified_name(comm, price, date, tag);
+
+ commodity_t * ann_comm = commodity_t::find(name);
+ if (ann_comm) {
+ assert(ann_comm->annotated);
+ return ann_comm;
+ }
+ return create(comm, price, date, tag, name);
+}
+
+bool compare_amount_commodities::operator()(const amount_t * left,
+ const amount_t * right) const
+{
+ commodity_t& leftcomm(left->commodity());
+ commodity_t& rightcomm(right->commodity());
+
+ int cmp = leftcomm.base_symbol().compare(rightcomm.base_symbol());
+ if (cmp != 0)
+ return cmp < 0;
+
+ if (! leftcomm.annotated) {
+ assert(rightcomm.annotated);
+ return true;
+ }
+ else if (! rightcomm.annotated) {
+ assert(leftcomm.annotated);
+ return false;
+ }
+ else {
+ annotated_commodity_t& aleftcomm(static_cast<annotated_commodity_t&>(leftcomm));
+ annotated_commodity_t& arightcomm(static_cast<annotated_commodity_t&>(rightcomm));
+
+ if (! aleftcomm.price && arightcomm.price)
+ return true;
+ if (aleftcomm.price && ! arightcomm.price)
+ return false;
+
+ if (aleftcomm.price && arightcomm.price) {
+ amount_t leftprice(aleftcomm.price);
+ leftprice.in_place_reduce();
+ amount_t rightprice(arightcomm.price);
+ rightprice.in_place_reduce();
+
+ if (leftprice.commodity() == rightprice.commodity()) {
+ amount_t val = leftprice - rightprice;
+ if (val)
+ return val < 0;
+ } else {
+ // Since we have two different amounts, there's really no way
+ // to establish a true sorting order; we'll just do it based
+ // on the numerical values.
+ leftprice.clear_commodity();
+ rightprice.clear_commodity();
+
+ amount_t val = leftprice - rightprice;
+ if (val)
+ return val < 0;
+ }
+ }
+
+ if (! is_valid_moment(aleftcomm.date) &&
+ is_valid_moment(arightcomm.date))
+ return true;
+ if (is_valid_moment(aleftcomm.date) &&
+ ! is_valid_moment(arightcomm.date))
+ return false;
+
+ if (is_valid_moment(aleftcomm.date) &&
+ is_valid_moment(arightcomm.date)) {
+ duration_t diff = aleftcomm.date - arightcomm.date;
+ return diff.is_negative();
+ }
+
+ if (aleftcomm.tag.empty() && ! arightcomm.tag.empty())
+ return true;
+ if (! aleftcomm.tag.empty() && arightcomm.tag.empty())
+ return false;
+
+ if (! aleftcomm.tag.empty() && ! arightcomm.tag.empty())
+ return aleftcomm.tag < arightcomm.tag;
+
+ assert(0);
+ return true;
+ }
+}
+
+} // namespace ledger
diff --git a/src/amount.h b/src/amount.h
new file mode 100644
index 00000000..dcd30b8d
--- /dev/null
+++ b/src/amount.h
@@ -0,0 +1,760 @@
+/**
+ * @file amount.h
+ * @author John Wiegley
+ * @date Wed Apr 18 22:05:53 2007
+ *
+ * @brief Types for handling commoditized math.
+ *
+ * This file contains two of the most basic types in Ledger: amount_t
+ * commodity_t, and annotated_commodity_t. Both the commodity types
+ * share a common base class, commodity_base_t. These four class
+ * together allow Ledger to handle mathematical expressions involving
+ * differing commodities, or in some cases math using no commodities
+ * at all (such as increasing a dollar amount by a multiplier).
+ */
+
+/*
+ * Copyright (c) 2003-2007, John Wiegley. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * - Neither the name of New Artisans LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _AMOUNT_H
+#define _AMOUNT_H
+
+#include "utils.h"
+#include "times.h"
+
+namespace ledger {
+
+extern bool do_cleanup;
+
+class commodity_t;
+
+/**
+ * @class amount_t
+ *
+ * @brief Encapsulates infinite-precision commoditized amounts.
+ *
+ * The amount_t class can be used for commoditized infinite-precision
+ * math, and also for uncommoditized math. In the commoditized case,
+ * commodities keep track of how they are used, and will always
+ * display back to the user after the same fashion. For
+ * uncommoditized numbers, no display truncation is ever done.
+ * Internally, precision is always kept to an excessive degree.
+ */
+
+class amount_t
+{
+ public:
+ class bigint_t;
+
+ static void initialize();
+ static void shutdown();
+
+ static bool keep_price;
+ static bool keep_date;
+ static bool keep_tag;
+ static bool keep_base;
+ static bool full_strings;
+
+ protected:
+ void _init();
+ void _copy(const amount_t& amt);
+ void _release();
+ void _dup();
+ void _resize(unsigned int prec);
+ void _clear();
+
+ bigint_t * quantity;
+ commodity_t * commodity_;
+
+ public:
+ // constructors
+ 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 string& val) : quantity(NULL) {
+ TRACE_CTOR(amount_t, "const string&");
+ parse(val);
+ }
+ amount_t(const char * val) : quantity(NULL) {
+ TRACE_CTOR(amount_t, "const char *");
+ parse(val);
+ }
+ amount_t(const long val);
+ amount_t(const unsigned long val);
+ amount_t(const double val);
+
+ // destructor
+ ~amount_t() {
+ TRACE_DTOR(amount_t);
+ if (quantity)
+ _release();
+ }
+
+ amount_t number() const {
+ amount_t temp(*this);
+ temp.clear_commodity();
+ return temp;
+ }
+
+ bool has_commodity() const;
+ commodity_t& commodity() const;
+ void set_commodity(commodity_t& comm) {
+ commodity_ = &comm;
+ }
+ void annotate_commodity(const amount_t& price,
+ const moment_t& date = moment_t(),
+ const string& tag = "");
+ amount_t strip_annotations(const bool _keep_price = keep_price,
+ const bool _keep_date = keep_date,
+ const bool _keep_tag = keep_tag) const;
+ void clear_commodity() {
+ commodity_ = NULL;
+ }
+ amount_t price() const;
+ moment_t date() const;
+
+ bool null() const {
+ return ! quantity && ! has_commodity();
+ }
+
+ // assignment operator
+ amount_t& operator=(const amount_t& amt);
+ amount_t& operator=(const string& val);
+ amount_t& operator=(const char * val);
+ amount_t& operator=(const long val);
+ amount_t& operator=(const unsigned long val);
+ amount_t& operator=(const double val);
+
+ // general methods
+ amount_t round(unsigned int prec) const;
+ amount_t round() const;
+ amount_t unround() const;
+
+ // in-place arithmetic
+ amount_t& operator+=(const amount_t& amt);
+ amount_t& operator-=(const amount_t& amt);
+ amount_t& operator*=(const amount_t& amt);
+ amount_t& operator/=(const amount_t& amt);
+
+ template <typename T>
+ amount_t& operator+=(T val) {
+ return *this += amount_t(val);
+ }
+ template <typename T>
+ amount_t& operator-=(T val) {
+ return *this -= amount_t(val);
+ }
+ template <typename T>
+ amount_t& operator*=(T val) {
+ return *this *= amount_t(val);
+ }
+ template <typename T>
+ amount_t& operator/=(T val) {
+ return *this /= amount_t(val);
+ }
+
+ // simple arithmetic
+ amount_t operator+(const amount_t& amt) const {
+ amount_t temp = *this;
+ temp += amt;
+ return temp;
+ }
+ amount_t operator-(const amount_t& amt) const {
+ amount_t temp = *this;
+ temp -= amt;
+ return temp;
+ }
+ amount_t operator*(const amount_t& amt) const {
+ amount_t temp = *this;
+ temp *= amt;
+ return temp;
+ }
+ amount_t operator/(const amount_t& amt) const {
+ amount_t temp = *this;
+ temp /= amt;
+ return temp;
+ }
+
+ template <typename T>
+ amount_t operator+(T val) const {
+ amount_t temp = *this;
+ temp += val;
+ return temp;
+ }
+ template <typename T>
+ amount_t operator-(T val) const {
+ amount_t temp = *this;
+ temp -= val;
+ return temp;
+ }
+ template <typename T>
+ amount_t operator*(T val) const {
+ amount_t temp = *this;
+ temp *= val;
+ return temp;
+ }
+ template <typename T>
+ amount_t operator/(T val) const {
+ amount_t temp = *this;
+ temp /= val;
+ return temp;
+ }
+
+ // unary negation
+ void in_place_negate();
+ amount_t negate() const {
+ amount_t temp = *this;
+ temp.in_place_negate();
+ return temp;
+ }
+ amount_t operator-() const {
+ return negate();
+ }
+
+ // test for zero and non-zero
+ int sign() const;
+ bool zero() const;
+ bool realzero() const {
+ return sign() == 0;
+ }
+ operator bool() const {
+ return ! zero();
+ }
+ operator string() const {
+ return to_string();
+ }
+
+ operator long() const;
+ operator double() const;
+
+ string to_string() const;
+ string to_fullstring() const;
+ string quantity_string() const;
+
+ // comparisons between amounts
+ int compare(const amount_t& amt) const;
+
+ bool operator<(const amount_t& amt) const {
+ return compare(amt) < 0;
+ }
+ bool operator<=(const amount_t& amt) const {
+ return compare(amt) <= 0;
+ }
+ bool operator>(const amount_t& amt) const {
+ return compare(amt) > 0;
+ }
+ bool operator>=(const amount_t& amt) const {
+ return compare(amt) >= 0;
+ }
+ bool operator==(const amount_t& amt) const;
+ bool operator!=(const amount_t& amt) const;
+
+ template <typename T>
+ void parse_num(T num) {
+ std::ostringstream temp;
+ temp << num;
+ std::istringstream in(temp.str());
+ parse(in);
+ }
+
+ // POD comparisons
+#define AMOUNT_CMP_INT(OP) \
+ template <typename T> \
+ bool operator OP (T num) const { \
+ if (num == 0) { \
+ return sign() OP 0; \
+ } else { \
+ amount_t amt; \
+ amt.parse_num(num); \
+ return *this OP amt; \
+ } \
+ }
+
+ AMOUNT_CMP_INT(<)
+ AMOUNT_CMP_INT(<=)
+ AMOUNT_CMP_INT(>)
+ AMOUNT_CMP_INT(>=)
+ AMOUNT_CMP_INT(==)
+
+ template <typename T>
+ bool operator!=(T num) const {
+ return ! (*this == num);
+ }
+
+ amount_t value(const moment_t& moment) const;
+
+ amount_t abs() const {
+ if (*this < 0)
+ return negate();
+ return *this;
+ }
+
+ void in_place_reduce();
+ amount_t reduce() const {
+ amount_t temp(*this);
+ temp.in_place_reduce();
+ return temp;
+ }
+
+ bool valid() const;
+
+ static amount_t exact(const string& value);
+
+ // 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).
+
+ friend void clean_commodity_history(char * item_pool,
+ char * item_pool_end);
+
+ friend bool parse_annotations(std::istream& in, amount_t& price,
+ moment_t& date, string& tag);
+
+ // Streaming interface
+
+ void dump(std::ostream& out) const {
+ out << "AMOUNT(";
+ print(out);
+ out << ")";
+ }
+
+#define AMOUNT_PARSE_NO_MIGRATE 0x01
+#define AMOUNT_PARSE_NO_REDUCE 0x02
+
+ void print(std::ostream& out, bool omit_commodity = false,
+ bool full_precision = false) const;
+ void parse(std::istream& in, unsigned char flags = 0);
+ void parse(const 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 amount_t::exact(const string& value) {
+ amount_t temp;
+ temp.parse(value, AMOUNT_PARSE_NO_MIGRATE);
+ return temp;
+}
+
+inline string amount_t::to_string() const {
+ std::ostringstream bufstream;
+ print(bufstream);
+ return bufstream.str();
+}
+
+inline string amount_t::to_fullstring() const {
+ std::ostringstream bufstream;
+ print(bufstream, false, true);
+ return bufstream.str();
+}
+
+inline string amount_t::quantity_string() const {
+ std::ostringstream bufstream;
+ print(bufstream, true);
+ return bufstream.str();
+}
+
+#define DEFINE_AMOUNT_OPERATORS(T) \
+inline amount_t operator+(const T val, const amount_t& amt) { \
+ amount_t temp(val); \
+ temp += amt; \
+ return temp; \
+} \
+inline amount_t operator-(const T val, const amount_t& amt) { \
+ amount_t temp(val); \
+ temp -= amt; \
+ return temp; \
+} \
+inline amount_t operator*(const T val, const amount_t& amt) { \
+ amount_t temp(val); \
+ temp *= amt; \
+ return temp; \
+} \
+inline amount_t operator/(const T val, const amount_t& amt) { \
+ amount_t temp(val); \
+ temp /= amt; \
+ return temp; \
+} \
+ \
+inline bool operator<(const T val, const amount_t& amt) { \
+ return amount_t(val) < amt; \
+} \
+inline bool operator<=(const T val, const amount_t& amt) { \
+ return amount_t(val) <= amt; \
+} \
+inline bool operator>(const T val, const amount_t& amt) { \
+ return amount_t(val) > amt; \
+} \
+inline bool operator>=(const T val, const amount_t& amt) { \
+ return amount_t(val) >= amt; \
+} \
+inline bool operator==(const T val, const amount_t& amt) { \
+ return amount_t(val) == amt; \
+} \
+inline bool operator!=(const T val, const amount_t& amt) { \
+ return amount_t(val) != amt; \
+}
+
+DEFINE_AMOUNT_OPERATORS(long)
+DEFINE_AMOUNT_OPERATORS(unsigned long)
+DEFINE_AMOUNT_OPERATORS(double)
+
+inline std::ostream& operator<<(std::ostream& out, const amount_t& amt) {
+ amt.print(out, false, amount_t::full_strings);
+ return out;
+}
+inline std::istream& operator>>(std::istream& in, amount_t& amt) {
+ amt.parse(in);
+ return in;
+}
+
+
+#define COMMODITY_STYLE_DEFAULTS 0x0000
+#define COMMODITY_STYLE_SUFFIXED 0x0001
+#define COMMODITY_STYLE_SEPARATED 0x0002
+#define COMMODITY_STYLE_EUROPEAN 0x0004
+#define COMMODITY_STYLE_THOUSANDS 0x0008
+#define COMMODITY_STYLE_NOMARKET 0x0010
+#define COMMODITY_STYLE_BUILTIN 0x0020
+
+typedef std::map<const moment_t, amount_t> history_map;
+typedef std::pair<const moment_t, amount_t> history_pair;
+
+class commodity_base_t;
+
+typedef std::map<const string, commodity_base_t *> base_commodities_map;
+typedef std::pair<const string, commodity_base_t *> base_commodities_pair;
+
+class commodity_base_t
+{
+ public:
+ friend class commodity_t;
+ friend class annotated_commodity_t;
+
+ typedef unsigned long ident_t;
+
+ ident_t ident;
+ string name;
+ string note;
+ unsigned char precision;
+ unsigned char flags;
+ amount_t * smaller;
+ amount_t * larger;
+
+ commodity_base_t()
+ : precision(0), flags(COMMODITY_STYLE_DEFAULTS),
+ smaller(NULL), larger(NULL), history(NULL) {
+ TRACE_CTOR(commodity_base_t, "");
+ }
+
+ commodity_base_t(const commodity_base_t&) {
+ TRACE_CTOR(commodity_base_t, "copy");
+ assert(0);
+ }
+
+ commodity_base_t(const string& _symbol,
+ unsigned int _precision = 0,
+ unsigned int _flags = COMMODITY_STYLE_DEFAULTS)
+ : precision(_precision), flags(_flags),
+ smaller(NULL), larger(NULL), symbol(_symbol), history(NULL) {
+ TRACE_CTOR(commodity_base_t, "const string&, unsigned int, unsigned int");
+ }
+
+ ~commodity_base_t() {
+ TRACE_DTOR(commodity_base_t);
+ if (history) delete history;
+ if (smaller) delete smaller;
+ if (larger) delete larger;
+ }
+
+ static base_commodities_map commodities;
+ static commodity_base_t * create(const string& symbol);
+
+ string symbol;
+
+ struct history_t {
+ history_map prices;
+ ptime last_lookup;
+ history_t() : last_lookup() {}
+ };
+ history_t * history;
+
+ void add_price(const moment_t& date, const amount_t& price);
+ bool remove_price(const moment_t& date);
+ amount_t value(const moment_t& moment = now);
+
+ class updater_t {
+ public:
+ virtual ~updater_t() {}
+ virtual void operator()(commodity_base_t& commodity,
+ const moment_t& moment,
+ const moment_t& date,
+ const moment_t& last,
+ amount_t& price) = 0;
+ };
+ friend class updater_t;
+
+ static updater_t * updater;
+};
+
+typedef std::map<const string, commodity_t *> commodities_map;
+typedef std::pair<const string, commodity_t *> commodities_pair;
+
+typedef std::vector<commodity_t *> commodities_array;
+
+class commodity_t
+{
+ friend class annotated_commodity_t;
+
+ public:
+ // This map remembers all commodities that have been defined.
+
+ 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 string& symbol);
+ static commodity_t * find(const string& name);
+ static commodity_t * find_or_create(const string& symbol);
+
+ static bool needs_quotes(const string& symbol);
+
+ static void make_alias(const string& symbol,
+ commodity_t * commodity);
+
+ // These are specific to each commodity reference
+
+ typedef unsigned long ident_t;
+
+ ident_t ident;
+ commodity_base_t * base;
+ string qualified_symbol;
+ bool annotated;
+
+ public:
+ explicit commodity_t() : base(NULL), annotated(false) {
+ TRACE_CTOR(commodity_t, "");
+ }
+ commodity_t(const commodity_t& o)
+ : ident(o.ident), base(o.base),
+ qualified_symbol(o.qualified_symbol), annotated(o.annotated) {
+ TRACE_CTOR(commodity_t, "copy");
+ }
+ virtual ~commodity_t() {
+ TRACE_DTOR(commodity_t);
+ }
+
+ operator bool() const {
+ return this != null_commodity;
+ }
+ virtual bool operator==(const commodity_t& comm) const {
+ if (comm.annotated)
+ return comm == *this;
+ return base == comm.base;
+ }
+ bool operator!=(const commodity_t& comm) const {
+ return ! (*this == comm);
+ }
+
+ string base_symbol() const {
+ return base->symbol;
+ }
+ string symbol() const {
+ return qualified_symbol;
+ }
+
+ void write(std::ostream& out) const {
+ out << symbol();
+ }
+
+ string name() const {
+ return base->name;
+ }
+ void set_name(const string& arg) {
+ base->name = arg;
+ }
+
+ string note() const {
+ return base->note;
+ }
+ void set_note(const string& arg) {
+ base->note = arg;
+ }
+
+ unsigned char precision() const {
+ return base->precision;
+ }
+ void set_precision(unsigned char arg) {
+ base->precision = arg;
+ }
+
+ unsigned char flags() const {
+ return base->flags;
+ }
+ void set_flags(unsigned char arg) {
+ base->flags = arg;
+ }
+ void add_flags(unsigned char arg) {
+ base->flags |= arg;
+ }
+ void drop_flags(unsigned char arg) {
+ base->flags &= ~arg;
+ }
+
+ amount_t * smaller() const {
+ return base->smaller;
+ }
+ void set_smaller(const amount_t& arg) {
+ if (base->smaller)
+ delete base->smaller;
+ base->smaller = new amount_t(arg);
+ }
+
+ amount_t * larger() const {
+ return base->larger;
+ }
+ void set_larger(const amount_t& arg) {
+ if (base->larger)
+ delete base->larger;
+ base->larger = new amount_t(arg);
+ }
+
+ commodity_base_t::history_t * history() const {
+ return base->history;
+ }
+
+ void add_price(const moment_t& date, const amount_t& price) {
+ return base->add_price(date, price);
+ }
+ bool remove_price(const moment_t& date) {
+ return base->remove_price(date);
+ }
+ amount_t value(const moment_t& moment = now) const {
+ return base->value(moment);
+ }
+
+ bool valid() const;
+};
+
+class annotated_commodity_t : public commodity_t
+{
+ public:
+ const commodity_t * ptr;
+
+ amount_t price;
+ moment_t date;
+ string tag;
+
+ explicit annotated_commodity_t() {
+ TRACE_CTOR(annotated_commodity_t, "");
+ annotated = true;
+ }
+ virtual ~annotated_commodity_t() {
+ TRACE_DTOR(annotated_commodity_t);
+ }
+
+ virtual bool operator==(const commodity_t& comm) const;
+
+ void write_annotations(std::ostream& out) const {
+ annotated_commodity_t::write_annotations(out, price, date, tag);
+ }
+
+ static void write_annotations(std::ostream& out,
+ const amount_t& price,
+ const moment_t& date,
+ const string& tag);
+
+ private:
+ static commodity_t * create(const commodity_t& comm,
+ const amount_t& price,
+ const moment_t& date,
+ const string& tag,
+ const string& mapping_key);
+
+ static commodity_t * find_or_create(const commodity_t& comm,
+ const amount_t& price,
+ const moment_t& date,
+ const string& tag);
+
+ friend class amount_t;
+};
+
+inline std::ostream& operator<<(std::ostream& out, const commodity_t& comm) {
+ out << comm.symbol();
+ return out;
+}
+
+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;
+ else
+ return *commodity_;
+}
+
+void parse_conversion(const string& larger_str,
+ const string& smaller_str);
+
+DECLARE_EXCEPTION(amount_exception);
+
+struct compare_amount_commodities {
+ bool operator()(const amount_t * left, const amount_t * right) const;
+};
+
+} // namespace ledger
+
+#endif // _AMOUNT_H
diff --git a/src/balance.cc b/src/balance.cc
new file mode 100644
index 00000000..487f749f
--- /dev/null
+++ b/src/balance.cc
@@ -0,0 +1,313 @@
+#include "balance.h"
+
+namespace ledger {
+
+amount_t balance_t::amount(const commodity_t& commodity) const
+{
+ if (! commodity) {
+ if (amounts.size() == 1) {
+ amounts_map::const_iterator i = amounts.begin();
+ return (*i).second;
+ }
+ else if (amounts.size() > 1) {
+ // Try stripping annotations before giving an error.
+ balance_t temp(strip_annotations());
+ if (temp.amounts.size() == 1)
+ return temp.amount(commodity);
+
+ throw_(amount_exception,
+ "Requested amount of a balance with multiple commodities: " << temp);
+ }
+ }
+ else if (amounts.size() > 0) {
+ amounts_map::const_iterator i = amounts.find(&commodity);
+ if (i != amounts.end())
+ return (*i).second;
+ }
+ return amount_t();
+}
+
+balance_t balance_t::value(const moment_t& moment) const
+{
+ balance_t temp;
+
+ for (amounts_map::const_iterator i = amounts.begin();
+ i != amounts.end();
+ i++)
+ temp += (*i).second.value(moment);
+
+ return temp;
+}
+
+balance_t balance_t::price() const
+{
+ balance_t temp;
+
+ for (amounts_map::const_iterator i = amounts.begin();
+ i != amounts.end();
+ i++)
+ temp += (*i).second.price();
+
+ return temp;
+}
+
+moment_t balance_t::date() const
+{
+ moment_t temp;
+
+ for (amounts_map::const_iterator i = amounts.begin();
+ i != amounts.end();
+ i++) {
+ moment_t tdate = (*i).second.date();
+ if (! is_valid_moment(temp) && is_valid_moment(tdate))
+ temp = tdate;
+ else if (temp != tdate)
+ return moment_t();
+ }
+ return temp;
+}
+
+balance_t balance_t::strip_annotations(const bool keep_price,
+ const bool keep_date,
+ const bool keep_tag) const
+{
+ balance_t temp;
+
+ for (amounts_map::const_iterator i = amounts.begin();
+ i != amounts.end();
+ i++)
+ temp += (*i).second.strip_annotations(keep_price, keep_date, keep_tag);
+
+ return temp;
+}
+
+void balance_t::write(std::ostream& out,
+ const int first_width,
+ const int latter_width) const
+{
+ bool first = true;
+ int lwidth = latter_width;
+
+ if (lwidth == -1)
+ lwidth = first_width;
+
+ if (commodity_t::commodities_sorted) {
+ for (amounts_map::const_iterator i = amounts.begin();
+ i != amounts.end();
+ i++) {
+ int width;
+ if (! first) {
+ out << std::endl;
+ width = lwidth;
+ } else {
+ first = false;
+ width = first_width;
+ }
+
+ out.width(width);
+ out.fill(' ');
+ out << std::right << (*i).second;
+ }
+ } else {
+ typedef std::vector<const amount_t *> amounts_array;
+ amounts_array sorted;
+
+ for (amounts_map::const_iterator i = amounts.begin();
+ i != amounts.end();
+ i++)
+ if ((*i).second)
+ sorted.push_back(&(*i).second);
+
+ std::stable_sort(sorted.begin(), sorted.end(),
+ compare_amount_commodities());
+
+ for (amounts_array::const_iterator i = sorted.begin();
+ i != sorted.end();
+ i++) {
+ int width;
+ if (! first) {
+ out << std::endl;
+ width = lwidth;
+ } else {
+ first = false;
+ width = first_width;
+ }
+
+ out.width(width);
+ out.fill(' ');
+ out << std::right << **i;
+ }
+ }
+
+ if (first) {
+ out.width(first_width);
+ out.fill(' ');
+ out << std::right << "0";
+ }
+}
+
+balance_t& balance_t::operator*=(const balance_t& bal)
+{
+ if (realzero() || bal.realzero()) {
+ return *this = 0L;
+ }
+ else if (bal.amounts.size() == 1) {
+ return *this *= (*bal.amounts.begin()).second;
+ }
+ else if (amounts.size() == 1) {
+ return *this = bal * *this;
+ }
+ else {
+ // Since we would fail with an error at this point otherwise, try
+ // stripping annotations to see if we can come up with a
+ // reasonable result. The user will not notice any annotations
+ // missing (since they are viewing a stripped report anyway), only
+ // that some of their value expression may not see any pricing or
+ // date data because of this operation.
+
+ balance_t temp(bal.strip_annotations());
+ if (temp.amounts.size() == 1)
+ return *this *= temp;
+ temp = strip_annotations();
+ if (temp.amounts.size() == 1)
+ return *this = bal * temp;
+
+ std::ostringstream errmsg;
+ errmsg << "Cannot multiply two balances: " << temp << " * " << bal;
+ throw amount_exception(errmsg.str(), context());
+ }
+}
+
+balance_t& balance_t::operator*=(const amount_t& amt)
+{
+ if (realzero() || amt.realzero()) {
+ return *this = 0L;
+ }
+ else if (! amt.commodity()) {
+ // Multiplying by the null commodity causes all amounts to be
+ // increased by the same factor.
+ for (amounts_map::iterator i = amounts.begin();
+ i != amounts.end();
+ i++)
+ (*i).second *= amt;
+ }
+ else if (amounts.size() == 1) {
+ *this = (*amounts.begin()).second * amt;
+ }
+ else {
+ amounts_map::iterator i = amounts.find(&amt.commodity());
+ if (i != amounts.end()) {
+ (*i).second *= amt;
+ } else {
+ // Try stripping annotations before giving an error.
+ balance_t temp(strip_annotations());
+ if (temp.amounts.size() == 1) {
+ return *this = (*temp.amounts.begin()).second * amt;
+ } else {
+ i = temp.amounts.find(&amt.commodity());
+ if (i != temp.amounts.end())
+ return *this = temp * amt;
+ }
+
+ std::ostringstream errmsg;
+ errmsg << "Attempt to multiply balance by a commodity"
+ << " not found in that balance: "
+ << temp << " * " << amt;
+ throw amount_exception(errmsg.str(), context());
+ }
+ }
+ return *this;
+}
+
+balance_t& balance_t::operator/=(const balance_t& bal)
+{
+ if (bal.realzero()) {
+ std::ostringstream errmsg;
+ errmsg << "Attempt to divide by zero: " << *this << " / " << bal;
+ throw amount_exception(errmsg.str(), context());
+ }
+ else if (realzero()) {
+ return *this = 0L;
+ }
+ else if (bal.amounts.size() == 1) {
+ return *this /= (*bal.amounts.begin()).second;
+ }
+ else if (*this == bal) {
+ return *this = 1L;
+ }
+ else {
+ // Try stripping annotations before giving an error.
+ balance_t temp(bal.strip_annotations());
+ if (temp.amounts.size() == 1)
+ return *this /= temp;
+
+ std::ostringstream errmsg;
+ errmsg << "Cannot divide between two balances: " << temp << " / " << bal;
+ throw amount_exception(errmsg.str(), context());
+ }
+}
+
+balance_t& balance_t::operator/=(const amount_t& amt)
+{
+ if (amt.realzero()) {
+ std::ostringstream errmsg;
+ errmsg << "Attempt to divide by zero: " << *this << " / " << amt;
+ throw amount_exception(errmsg.str(), context());
+ }
+ else if (realzero()) {
+ return *this = 0L;
+ }
+ else if (! amt.commodity()) {
+ // Dividing by the null commodity causes all amounts to be
+ // decreased by the same factor.
+ for (amounts_map::iterator i = amounts.begin();
+ i != amounts.end();
+ i++)
+ (*i).second /= amt;
+ }
+ else if (amounts.size() == 1 &&
+ (*amounts.begin()).first == &amt.commodity()) {
+ (*amounts.begin()).second /= amt;
+ }
+ else {
+ amounts_map::iterator i = amounts.find(&amt.commodity());
+ if (i != amounts.end()) {
+ (*i).second /= amt;
+ } else {
+ // Try stripping annotations before giving an error.
+ balance_t temp(strip_annotations());
+ if (temp.amounts.size() == 1 &&
+ (*temp.amounts.begin()).first == &amt.commodity())
+ return *this = temp / amt;
+
+ std::ostringstream errmsg;
+ errmsg << "Attempt to divide balance by a commodity"
+ << " not found in that balance: "
+ << temp << " * " << amt;
+ throw amount_exception(errmsg.str(), context());
+ }
+ }
+ return *this;
+}
+
+balance_t::operator amount_t() const
+{
+ if (amounts.size() == 1) {
+ return (*amounts.begin()).second;
+ }
+ else if (amounts.size() == 0) {
+ return amount_t();
+ }
+ else {
+ // Try stripping annotations before giving an error.
+ balance_t temp(strip_annotations());
+ if (temp.amounts.size() == 1)
+ return (*temp.amounts.begin()).second;
+
+ throw_(amount_exception,
+ "Cannot convert a balance with " <<
+ "multiple commodities to an amount: " << temp);
+ }
+}
+
+} // namespace ledger
diff --git a/src/balance.h b/src/balance.h
new file mode 100644
index 00000000..2a6f3072
--- /dev/null
+++ b/src/balance.h
@@ -0,0 +1,969 @@
+#ifndef _BALANCE_H
+#define _BALANCE_H
+
+#include "amount.h"
+
+namespace ledger {
+
+typedef std::map<const commodity_t *, amount_t> amounts_map;
+typedef std::pair<const commodity_t *, amount_t> amounts_pair;
+
+class balance_t
+{
+ public:
+ amounts_map amounts;
+
+ bool valid() const {
+ for (amounts_map::const_iterator i = amounts.begin();
+ i != amounts.end();
+ i++)
+ if (! (*i).second.valid())
+ return false;
+ return true;
+ }
+
+ // constructors
+ 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 val) {
+ TRACE_CTOR(balance_t, "T");
+ amount_t amt(val);
+ if (! amt.realzero())
+ amounts.insert(amounts_pair(&amt.commodity(), amt));
+ }
+
+ ~balance_t() {
+ TRACE_DTOR(balance_t);
+ }
+
+ // assignment operator
+ balance_t& operator=(const balance_t& bal) {
+ if (this != &bal) {
+ amounts.clear();
+ for (amounts_map::const_iterator i = bal.amounts.begin();
+ i != bal.amounts.end();
+ i++)
+ *this += (*i).second;
+ }
+ return *this;
+ }
+ balance_t& operator=(const amount_t& amt) {
+ amounts.clear();
+ *this += amt;
+ return *this;
+ }
+ template <typename T>
+ balance_t& operator=(T val) {
+ amounts.clear();
+ *this += val;
+ return *this;
+ }
+
+ // in-place arithmetic
+ balance_t& operator+=(const balance_t& bal) {
+ for (amounts_map::const_iterator i = bal.amounts.begin();
+ i != bal.amounts.end();
+ i++)
+ *this += (*i).second;
+ return *this;
+ }
+ balance_t& operator+=(const amount_t& amt) {
+ amounts_map::iterator i = amounts.find(&amt.commodity());
+ if (i != amounts.end())
+ (*i).second += amt;
+ else if (! amt.realzero())
+ amounts.insert(amounts_pair(&amt.commodity(), amt));
+ return *this;
+ }
+ template <typename T>
+ balance_t& operator+=(T val) {
+ return *this += amount_t(val);
+ }
+ balance_t& operator-=(const balance_t& bal) {
+ for (amounts_map::const_iterator i = bal.amounts.begin();
+ i != bal.amounts.end();
+ i++)
+ *this -= (*i).second;
+ return *this;
+ }
+ balance_t& operator-=(const amount_t& amt) {
+ amounts_map::iterator i = amounts.find(&amt.commodity());
+ if (i != amounts.end()) {
+ (*i).second -= amt;
+ if ((*i).second.realzero())
+ amounts.erase(i);
+ }
+ else if (! amt.realzero()) {
+ amounts.insert(amounts_pair(&amt.commodity(), - amt));
+ }
+ return *this;
+ }
+ template <typename T>
+ balance_t& operator-=(T val) {
+ return *this -= amount_t(val);
+ }
+
+ // simple arithmetic
+ balance_t operator+(const balance_t& bal) const {
+ balance_t temp = *this;
+ temp += bal;
+ return temp;
+ }
+ balance_t operator+(const amount_t& amt) const {
+ balance_t temp = *this;
+ temp += amt;
+ return temp;
+ }
+ template <typename T>
+ balance_t operator+(T val) const {
+ balance_t temp = *this;
+ temp += val;
+ return temp;
+ }
+ balance_t operator-(const balance_t& bal) const {
+ balance_t temp = *this;
+ temp -= bal;
+ return temp;
+ }
+ balance_t operator-(const amount_t& amt) const {
+ balance_t temp = *this;
+ temp -= amt;
+ return temp;
+ }
+ template <typename T>
+ balance_t operator-(T val) const {
+ balance_t temp = *this;
+ temp -= val;
+ return temp;
+ }
+
+ // multiplication and divide
+ balance_t& operator*=(const balance_t& bal);
+ balance_t& operator*=(const amount_t& amt);
+ template <typename T>
+ balance_t& operator*=(T val) {
+ return *this *= amount_t(val);
+ }
+
+ balance_t& operator/=(const balance_t& bal);
+ balance_t& operator/=(const amount_t& amt);
+ template <typename T>
+ balance_t& operator/=(T val) {
+ return *this /= amount_t(val);
+ }
+
+ // multiplication and divide
+ balance_t operator*(const balance_t& bal) const {
+ balance_t temp = *this;
+ temp *= bal;
+ return temp;
+ }
+ balance_t operator*(const amount_t& amt) const {
+ balance_t temp = *this;
+ temp *= amt;
+ return temp;
+ }
+ template <typename T>
+ balance_t operator*(T val) const {
+ balance_t temp = *this;
+ temp *= val;
+ return temp;
+ }
+ balance_t operator/(const balance_t& bal) const {
+ balance_t temp = *this;
+ temp /= bal;
+ return temp;
+ }
+ balance_t operator/(const amount_t& amt) const {
+ balance_t temp = *this;
+ temp /= amt;
+ return temp;
+ }
+ template <typename T>
+ balance_t operator/(T val) const {
+ balance_t temp = *this;
+ temp /= val;
+ return temp;
+ }
+
+ // comparison
+ bool operator<(const balance_t& bal) const {
+ for (amounts_map::const_iterator i = bal.amounts.begin();
+ i != bal.amounts.end();
+ i++)
+ if (! (amount(*(*i).first) < (*i).second))
+ return false;
+
+ for (amounts_map::const_iterator i = amounts.begin();
+ i != amounts.end();
+ i++)
+ if (! ((*i).second < bal.amount(*(*i).first)))
+ return false;
+
+ if (bal.amounts.size() == 0 && amounts.size() == 0)
+ return false;
+
+ return true;
+ }
+ bool operator<(const amount_t& amt) const {
+ if (amt.commodity())
+ return amount(amt.commodity()) < amt;
+
+ for (amounts_map::const_iterator i = amounts.begin();
+ i != amounts.end();
+ i++)
+ if ((*i).second < amt)
+ return true;
+ return false;
+ }
+ template <typename T>
+ bool operator<(T val) const {
+ for (amounts_map::const_iterator i = amounts.begin();
+ i != amounts.end();
+ i++)
+ if ((*i).second < val)
+ return true;
+ return false;
+ }
+
+ bool operator<=(const balance_t& bal) const {
+ for (amounts_map::const_iterator i = bal.amounts.begin();
+ i != bal.amounts.end();
+ i++)
+ if (! (amount(*(*i).first) <= (*i).second))
+ return false;
+
+ for (amounts_map::const_iterator i = amounts.begin();
+ i != amounts.end();
+ i++)
+ if (! ((*i).second <= bal.amount(*(*i).first)))
+ return false;
+
+ return true;
+ }
+ bool operator<=(const amount_t& amt) const {
+ if (amt.commodity())
+ return amount(amt.commodity()) <= amt;
+
+ for (amounts_map::const_iterator i = amounts.begin();
+ i != amounts.end();
+ i++)
+ if ((*i).second <= amt)
+ return true;
+ return false;
+ }
+ template <typename T>
+ bool operator<=(T val) const {
+ for (amounts_map::const_iterator i = amounts.begin();
+ i != amounts.end();
+ i++)
+ if ((*i).second <= val)
+ return true;
+ return false;
+ }
+
+ bool operator>(const balance_t& bal) const {
+ for (amounts_map::const_iterator i = bal.amounts.begin();
+ i != bal.amounts.end();
+ i++)
+ if (! (amount(*(*i).first) > (*i).second))
+ return false;
+
+ for (amounts_map::const_iterator i = amounts.begin();
+ i != amounts.end();
+ i++)
+ if (! ((*i).second > bal.amount(*(*i).first)))
+ return false;
+
+ if (bal.amounts.size() == 0 && amounts.size() == 0)
+ return false;
+
+ return true;
+ }
+ bool operator>(const amount_t& amt) const {
+ if (amt.commodity())
+ return amount(amt.commodity()) > amt;
+
+ for (amounts_map::const_iterator i = amounts.begin();
+ i != amounts.end();
+ i++)
+ if ((*i).second > amt)
+ return true;
+ return false;
+ }
+ template <typename T>
+ bool operator>(T val) const {
+ for (amounts_map::const_iterator i = amounts.begin();
+ i != amounts.end();
+ i++)
+ if ((*i).second > val)
+ return true;
+ return false;
+ }
+
+ bool operator>=(const balance_t& bal) const {
+ for (amounts_map::const_iterator i = bal.amounts.begin();
+ i != bal.amounts.end();
+ i++)
+ if (! (amount(*(*i).first) >= (*i).second))
+ return false;
+
+ for (amounts_map::const_iterator i = amounts.begin();
+ i != amounts.end();
+ i++)
+ if (! ((*i).second >= bal.amount(*(*i).first)))
+ return false;
+
+ return true;
+ }
+ bool operator>=(const amount_t& amt) const {
+ if (amt.commodity())
+ return amount(amt.commodity()) >= amt;
+
+ for (amounts_map::const_iterator i = amounts.begin();
+ i != amounts.end();
+ i++)
+ if ((*i).second >= amt)
+ return true;
+ return false;
+ }
+ template <typename T>
+ bool operator>=(T val) const {
+ for (amounts_map::const_iterator i = amounts.begin();
+ i != amounts.end();
+ i++)
+ if ((*i).second >= val)
+ return true;
+ return false;
+ }
+
+ bool operator==(const balance_t& bal) const {
+ amounts_map::const_iterator i, j;
+ for (i = amounts.begin(), j = bal.amounts.begin();
+ i != amounts.end() && j != bal.amounts.end();
+ i++, j++) {
+ if (! ((*i).first == (*j).first &&
+ (*i).second == (*j).second))
+ return false;
+ }
+ return i == amounts.end() && j == bal.amounts.end();
+ }
+ bool operator==(const amount_t& amt) const {
+ if (amt.commodity())
+ return amounts.size() == 1 && (*amounts.begin()).second == amt;
+
+ for (amounts_map::const_iterator i = amounts.begin();
+ i != amounts.end();
+ i++)
+ if ((*i).second == amt)
+ return true;
+ return false;
+ }
+ template <typename T>
+ bool operator==(T val) const {
+ for (amounts_map::const_iterator i = amounts.begin();
+ i != amounts.end();
+ i++)
+ if ((*i).second == val)
+ return true;
+ return false;
+ }
+
+ bool operator!=(const balance_t& bal) const {
+ return ! (*this == bal);
+ }
+ bool operator!=(const amount_t& amt) const {
+ return ! (*this == amt);
+ }
+ template <typename T>
+ bool operator!=(T val) const {
+ return ! (*this == val);
+ }
+
+ // unary negation
+ void in_place_negate() {
+ for (amounts_map::iterator i = amounts.begin();
+ i != amounts.end();
+ i++)
+ (*i).second = (*i).second.negate();
+ }
+ balance_t negate() const {
+ balance_t temp = *this;
+ temp.in_place_negate();
+ return temp;
+ }
+ balance_t operator-() const {
+ return negate();
+ }
+
+ // conversion operators
+ operator amount_t() const;
+ operator bool() const {
+ for (amounts_map::const_iterator i = amounts.begin();
+ i != amounts.end();
+ i++)
+ if ((*i).second)
+ return true;
+ return false;
+ }
+
+ bool realzero() const {
+ if (amounts.size() == 0)
+ return true;
+ for (amounts_map::const_iterator i = amounts.begin();
+ i != amounts.end();
+ i++)
+ if (! (*i).second.realzero())
+ return false;
+ return true;
+ }
+
+ amount_t amount(const commodity_t& commodity =
+ *commodity_t::null_commodity) const;
+ balance_t value(const moment_t& moment = now) const;
+ balance_t price() const;
+ moment_t date() const;
+
+ balance_t
+ strip_annotations(const bool keep_price = amount_t::keep_price,
+ const bool keep_date = amount_t::keep_date,
+ const bool keep_tag = amount_t::keep_tag) const;
+
+ void write(std::ostream& out, const int first_width,
+ const int latter_width = -1) const;
+
+ void in_place_abs() {
+ for (amounts_map::iterator i = amounts.begin();
+ i != amounts.end();
+ i++)
+ (*i).second = (*i).second.abs();
+ }
+ balance_t abs() const {
+ balance_t temp = *this;
+ temp.in_place_abs();
+ return temp;
+ }
+
+ void in_place_reduce() {
+ for (amounts_map::iterator i = amounts.begin();
+ i != amounts.end();
+ i++)
+ (*i).second.in_place_reduce();
+ }
+ balance_t reduce() const {
+ balance_t temp(*this);
+ temp.in_place_reduce();
+ return temp;
+ }
+
+ void in_place_round() {
+ for (amounts_map::iterator i = amounts.begin();
+ i != amounts.end();
+ i++)
+ (*i).second = (*i).second.round();
+ }
+ balance_t round() const {
+ balance_t temp(*this);
+ temp.in_place_round();
+ return temp;
+ }
+
+ balance_t unround() const {
+ balance_t temp;
+ for (amounts_map::const_iterator i = amounts.begin();
+ i != amounts.end();
+ i++)
+ if ((*i).second.commodity())
+ temp += (*i).second.unround();
+ return temp;
+ }
+};
+
+inline std::ostream& operator<<(std::ostream& out, const balance_t& bal) {
+ bal.write(out, 12);
+ return out;
+}
+
+class balance_pair_t
+{
+ public:
+ balance_t quantity;
+ balance_t * cost;
+
+ // constructors
+ 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) {
+ TRACE_CTOR(balance_pair_t, "const balance_t&");
+ }
+ balance_pair_t(const amount_t& _quantity)
+ : quantity(_quantity), cost(NULL) {
+ TRACE_CTOR(balance_pair_t, "const amount_t&");
+ }
+ template <typename T>
+ balance_pair_t(T val) : quantity(val), cost(NULL) {
+ TRACE_CTOR(balance_pair_t, "T");
+ }
+
+ // destructor
+ ~balance_pair_t() {
+ TRACE_DTOR(balance_pair_t);
+ if (cost) delete cost;
+ }
+
+ // assignment operator
+ balance_pair_t& operator=(const balance_pair_t& bal_pair) {
+ if (this != &bal_pair) {
+ if (cost) {
+ delete cost;
+ cost = NULL;
+ }
+ quantity = bal_pair.quantity;
+ if (bal_pair.cost)
+ cost = new balance_t(*bal_pair.cost);
+ }
+ return *this;
+ }
+ balance_pair_t& operator=(const balance_t& bal) {
+ if (cost) {
+ delete cost;
+ cost = NULL;
+ }
+ quantity = bal;
+ return *this;
+ }
+ balance_pair_t& operator=(const amount_t& amt) {
+ if (cost) {
+ delete cost;
+ cost = NULL;
+ }
+ quantity = amt;
+ return *this;
+ }
+ template <typename T>
+ balance_pair_t& operator=(T val) {
+ if (cost) {
+ delete cost;
+ cost = NULL;
+ }
+ quantity = val;
+ return *this;
+ }
+
+ // in-place arithmetic
+ balance_pair_t& operator+=(const balance_pair_t& bal_pair) {
+ if (bal_pair.cost && ! cost)
+ cost = new balance_t(quantity);
+ quantity += bal_pair.quantity;
+ if (cost)
+ *cost += bal_pair.cost ? *bal_pair.cost : bal_pair.quantity;
+ return *this;
+ }
+ balance_pair_t& operator+=(const balance_t& bal) {
+ quantity += bal;
+ if (cost)
+ *cost += bal;
+ return *this;
+ }
+ balance_pair_t& operator+=(const amount_t& amt) {
+ quantity += amt;
+ if (cost)
+ *cost += amt;
+ return *this;
+ }
+ template <typename T>
+ balance_pair_t& operator+=(T val) {
+ return *this += amount_t(val);
+ }
+
+ balance_pair_t& operator-=(const balance_pair_t& bal_pair) {
+ if (bal_pair.cost && ! cost)
+ cost = new balance_t(quantity);
+ quantity -= bal_pair.quantity;
+ if (cost)
+ *cost -= bal_pair.cost ? *bal_pair.cost : bal_pair.quantity;
+ return *this;
+ }
+ balance_pair_t& operator-=(const balance_t& bal) {
+ quantity -= bal;
+ if (cost)
+ *cost -= bal;
+ return *this;
+ }
+ balance_pair_t& operator-=(const amount_t& amt) {
+ quantity -= amt;
+ if (cost)
+ *cost -= amt;
+ return *this;
+ }
+ template <typename T>
+ balance_pair_t& operator-=(T val) {
+ return *this -= amount_t(val);
+ }
+
+ // simple arithmetic
+ balance_pair_t operator+(const balance_pair_t& bal_pair) const {
+ balance_pair_t temp = *this;
+ temp += bal_pair;
+ return temp;
+ }
+ balance_pair_t operator+(const balance_t& bal) const {
+ balance_pair_t temp = *this;
+ temp += bal;
+ return temp;
+ }
+ balance_pair_t operator+(const amount_t& amt) const {
+ balance_pair_t temp = *this;
+ temp += amt;
+ return temp;
+ }
+ template <typename T>
+ balance_pair_t operator+(T val) const {
+ balance_pair_t temp = *this;
+ temp += val;
+ return temp;
+ }
+
+ balance_pair_t operator-(const balance_pair_t& bal_pair) const {
+ balance_pair_t temp = *this;
+ temp -= bal_pair;
+ return temp;
+ }
+ balance_pair_t operator-(const balance_t& bal) const {
+ balance_pair_t temp = *this;
+ temp -= bal;
+ return temp;
+ }
+ balance_pair_t operator-(const amount_t& amt) const {
+ balance_pair_t temp = *this;
+ temp -= amt;
+ return temp;
+ }
+ template <typename T>
+ balance_pair_t operator-(T val) const {
+ balance_pair_t temp = *this;
+ temp -= val;
+ return temp;
+ }
+
+ // multiplication and division
+ balance_pair_t& operator*=(const balance_pair_t& bal_pair) {
+ if (bal_pair.cost && ! cost)
+ cost = new balance_t(quantity);
+ quantity *= bal_pair.quantity;
+ if (cost)
+ *cost *= bal_pair.cost ? *bal_pair.cost : bal_pair.quantity;
+ return *this;
+ }
+ balance_pair_t& operator*=(const balance_t& bal) {
+ quantity *= bal;
+ if (cost)
+ *cost *= bal;
+ return *this;
+ }
+ balance_pair_t& operator*=(const amount_t& amt) {
+ quantity *= amt;
+ if (cost)
+ *cost *= amt;
+ return *this;
+ }
+ template <typename T>
+ balance_pair_t& operator*=(T val) {
+ return *this *= amount_t(val);
+ }
+
+ balance_pair_t& operator/=(const balance_pair_t& bal_pair) {
+ if (bal_pair.cost && ! cost)
+ cost = new balance_t(quantity);
+ quantity /= bal_pair.quantity;
+ if (cost)
+ *cost /= bal_pair.cost ? *bal_pair.cost : bal_pair.quantity;
+ return *this;
+ }
+ balance_pair_t& operator/=(const balance_t& bal) {
+ quantity /= bal;
+ if (cost)
+ *cost /= bal;
+ return *this;
+ }
+ balance_pair_t& operator/=(const amount_t& amt) {
+ quantity /= amt;
+ if (cost)
+ *cost /= amt;
+ return *this;
+ }
+ template <typename T>
+ balance_pair_t& operator/=(T val) {
+ return *this /= amount_t(val);
+ }
+
+ balance_pair_t operator*(const balance_pair_t& bal_pair) const {
+ balance_pair_t temp = *this;
+ temp *= bal_pair;
+ return temp;
+ }
+ balance_pair_t operator*(const balance_t& bal) const {
+ balance_pair_t temp = *this;
+ temp *= bal;
+ return temp;
+ }
+ balance_pair_t operator*(const amount_t& amt) const {
+ balance_pair_t temp = *this;
+ temp *= amt;
+ return temp;
+ }
+ template <typename T>
+ balance_pair_t operator*(T val) const {
+ balance_pair_t temp = *this;
+ temp *= val;
+ return temp;
+ }
+
+ balance_pair_t operator/(const balance_pair_t& bal_pair) const {
+ balance_pair_t temp = *this;
+ temp /= bal_pair;
+ return temp;
+ }
+ balance_pair_t operator/(const balance_t& bal) const {
+ balance_pair_t temp = *this;
+ temp /= bal;
+ return temp;
+ }
+ balance_pair_t operator/(const amount_t& amt) const {
+ balance_pair_t temp = *this;
+ temp /= amt;
+ return temp;
+ }
+ template <typename T>
+ balance_pair_t operator/(T val) const {
+ balance_pair_t temp = *this;
+ temp /= val;
+ return temp;
+ }
+
+ // comparison
+ bool operator<(const balance_pair_t& bal_pair) const {
+ return quantity < bal_pair.quantity;
+ }
+ bool operator<(const balance_t& bal) const {
+ return quantity < bal;
+ }
+ bool operator<(const amount_t& amt) const {
+ return quantity < amt;
+ }
+ template <typename T>
+ bool operator<(T val) const {
+ return quantity < val;
+ }
+
+ bool operator<=(const balance_pair_t& bal_pair) const {
+ return quantity <= bal_pair.quantity;
+ }
+ bool operator<=(const balance_t& bal) const {
+ return quantity <= bal;
+ }
+ bool operator<=(const amount_t& amt) const {
+ return quantity <= amt;
+ }
+ template <typename T>
+ bool operator<=(T val) const {
+ return quantity <= val;
+ }
+
+ bool operator>(const balance_pair_t& bal_pair) const {
+ return quantity > bal_pair.quantity;
+ }
+ bool operator>(const balance_t& bal) const {
+ return quantity > bal;
+ }
+ bool operator>(const amount_t& amt) const {
+ return quantity > amt;
+ }
+ template <typename T>
+ bool operator>(T val) const {
+ return quantity > val;
+ }
+
+ bool operator>=(const balance_pair_t& bal_pair) const {
+ return quantity >= bal_pair.quantity;
+ }
+ bool operator>=(const balance_t& bal) const {
+ return quantity >= bal;
+ }
+ bool operator>=(const amount_t& amt) const {
+ return quantity >= amt;
+ }
+ template <typename T>
+ bool operator>=(T val) const {
+ return quantity >= val;
+ }
+
+ bool operator==(const balance_pair_t& bal_pair) const {
+ return quantity == bal_pair.quantity;
+ }
+ bool operator==(const balance_t& bal) const {
+ return quantity == bal;
+ }
+ bool operator==(const amount_t& amt) const {
+ return quantity == amt;
+ }
+ template <typename T>
+ bool operator==(T val) const {
+ return quantity == val;
+ }
+
+ bool operator!=(const balance_pair_t& bal_pair) const {
+ return ! (*this == bal_pair);
+ }
+ bool operator!=(const balance_t& bal) const {
+ return ! (*this == bal);
+ }
+ bool operator!=(const amount_t& amt) const {
+ return ! (*this == amt);
+ }
+ template <typename T>
+ bool operator!=(T val) const {
+ return ! (*this == val);
+ }
+
+ // unary negation
+ void in_place_negate() {
+ quantity = quantity.negate();
+ if (cost)
+ *cost = cost->negate();
+ }
+ balance_pair_t negate() const {
+ balance_pair_t temp = *this;
+ temp.in_place_negate();
+ return temp;
+ }
+ balance_pair_t operator-() const {
+ return negate();
+ }
+
+ // test for non-zero (use ! for zero)
+ operator balance_t() const {
+ return quantity;
+ }
+ operator amount_t() const {
+ return quantity;
+ }
+ operator bool() const {
+ return quantity;
+ }
+
+ bool realzero() const {
+ return ((! cost || cost->realzero()) && quantity.realzero());
+ }
+
+ void in_place_abs() {
+ quantity = quantity.abs();
+ if (cost)
+ *cost = cost->abs();
+ }
+ balance_pair_t abs() const {
+ balance_pair_t temp = *this;
+ temp.in_place_abs();
+ return temp;
+ }
+
+ amount_t amount(const commodity_t& commodity =
+ *commodity_t::null_commodity) const {
+ return quantity.amount(commodity);
+ }
+ balance_t value(const moment_t& moment = now) const {
+ return quantity.value(moment);
+ }
+ balance_t price() const {
+ return quantity.price();
+ }
+ moment_t date() const {
+ return quantity.date();
+ }
+
+ balance_t
+ strip_annotations(const bool keep_price = amount_t::keep_price,
+ const bool keep_date = amount_t::keep_date,
+ const bool keep_tag = amount_t::keep_tag) const {
+ return quantity.strip_annotations(keep_price, keep_date, keep_tag);
+ }
+
+ void write(std::ostream& out, const int first_width,
+ const int latter_width = -1) const {
+ quantity.write(out, first_width, latter_width);
+ }
+
+ balance_pair_t& add(const amount_t& amt,
+ const amount_t * a_cost = NULL) {
+ if (a_cost && ! cost)
+ cost = new balance_t(quantity);
+ quantity += amt;
+ if (cost)
+ *cost += a_cost ? *a_cost : amt;
+ return *this;
+ }
+
+ bool valid() {
+ return quantity.valid() && (! cost || cost->valid());
+ }
+
+ void in_place_reduce() {
+ quantity.in_place_reduce();
+ if (cost) cost->in_place_reduce();
+ }
+ balance_pair_t reduce() const {
+ balance_pair_t temp(*this);
+ temp.in_place_reduce();
+ return temp;
+ }
+
+ void in_place_round() {
+ quantity = quantity.round();
+ if (cost)
+ *cost = cost->round();
+ }
+ balance_pair_t round() const {
+ balance_pair_t temp(*this);
+ temp.in_place_round();
+ return temp;
+ }
+
+ balance_pair_t unround() {
+ balance_pair_t temp(quantity.unround());
+ if (cost)
+ temp.cost = new balance_t(cost->unround());
+ return temp;
+ }
+};
+
+inline std::ostream& operator<<(std::ostream& out,
+ const balance_pair_t& bal_pair) {
+ bal_pair.quantity.write(out, 12);
+ return out;
+}
+
+} // namespace ledger
+
+#endif // _BALANCE_H
diff --git a/src/binary.cc b/src/binary.cc
new file mode 100644
index 00000000..d3238b5a
--- /dev/null
+++ b/src/binary.cc
@@ -0,0 +1,1013 @@
+#include "binary.h"
+
+namespace ledger {
+
+#if 0
+static unsigned long binary_magic_number = 0xFFEED765;
+#if defined(DEBUG_ON)
+static unsigned long format_version = 0x00030000;
+#else
+static unsigned long format_version = 0x00030000;
+#endif
+
+static account_t ** accounts;
+static account_t ** accounts_next;
+static unsigned int account_index;
+
+static commodity_base_t ** base_commodities;
+static commodity_base_t ** base_commodities_next;
+static unsigned int base_commodity_index;
+
+static commodity_t ** commodities;
+static commodity_t ** commodities_next;
+static unsigned int commodity_index;
+
+extern char * bigints;
+extern char * bigints_next;
+extern unsigned int bigints_index;
+extern unsigned int bigints_count;
+#endif
+
+void read_binary_bool(std::istream& in, bool& num)
+{
+ read_binary_guard(in, 0x2005);
+ unsigned char val;
+ in.read((char *)&val, sizeof(val));
+ num = val == 1;
+ read_binary_guard(in, 0x2006);
+}
+
+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);
+}
+
+void read_binary_string(std::istream& in, string& str)
+{
+ read_binary_guard(in, 0x3001);
+
+ unsigned char len;
+ read_binary_number_nocheck(in, len);
+ if (len == 0xff) {
+ unsigned short slen;
+ read_binary_number_nocheck(in, slen);
+ char * buf = new char[slen + 1];
+ in.read(buf, slen);
+ buf[slen] = '\0';
+ str = buf;
+ delete[] buf;
+ }
+ else if (len) {
+ char buf[256];
+ in.read(buf, len);
+ buf[len] = '\0';
+ str = buf;
+ } else {
+ str = "";
+ }
+
+ read_binary_guard(in, 0x3002);
+}
+
+void read_binary_string(char *& data, string& str)
+{
+ read_binary_guard(data, 0x3001);
+
+ unsigned char len;
+ read_binary_number_nocheck(data, len);
+ if (len == 0xff) {
+ unsigned short slen;
+ read_binary_number_nocheck(data, slen);
+ str = string(data, slen);
+ data += slen;
+ }
+ else if (len) {
+ str = string(data, len);
+ data += len;
+ }
+ else {
+ str = "";
+ }
+
+ read_binary_guard(data, 0x3002);
+}
+
+void read_binary_string(char *& data, string * str)
+{
+ read_binary_guard(data, 0x3001);
+
+ unsigned char len;
+ read_binary_number_nocheck(data, len);
+ if (len == 0xff) {
+ unsigned short slen;
+ read_binary_number_nocheck(data, slen);
+ new(str) string(data, slen);
+ data += slen;
+ }
+ else if (len) {
+ new(str) string(data, len);
+ data += len;
+ }
+ else {
+ new(str) string("");
+ }
+
+ read_binary_guard(data, 0x3002);
+}
+
+#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));
+
+ switch (val.type) {
+ case value_t::BOOLEAN:
+ read_binary_bool(data, *((bool *) val.data));
+ break;
+ case value_t::INTEGER:
+ read_binary_long(data, *((long *) val.data));
+ break;
+ case value_t::DATETIME:
+ read_binary_number(data, *((moment_t *) val.data));
+ break;
+ case value_t::AMOUNT:
+ read_binary_amount(data, *((amount_t *) val.data));
+ break;
+
+ case value_t::BALANCE:
+ case value_t::BALANCE_PAIR:
+ assert(0);
+ break;
+ }
+}
+
+inline void read_binary_mask(char *& data, mask_t *& mask)
+{
+ bool exclude;
+ read_binary_number(data, exclude);
+ string pattern;
+ read_binary_string(data, pattern);
+
+ mask = new mask_t(pattern);
+ mask->exclude = exclude;
+}
+
+inline void read_binary_transaction(char *& data, transaction_t * xact)
+{
+ read_binary_number(data, xact->_date);
+ read_binary_number(data, xact->_date_eff);
+ xact->account = accounts[read_binary_long<account_t::ident_t>(data) - 1];
+
+ unsigned char flag = read_binary_number<unsigned char>(data);
+ if (flag == 0) {
+ read_binary_amount(data, xact->amount);
+ }
+ else if (flag == 1) {
+ 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)) {
+ xact->cost = new amount_t;
+ read_binary_amount(data, *xact->cost);
+ read_binary_string(data, xact->cost_expr);
+ } else {
+ xact->cost = NULL;
+ }
+
+ read_binary_number(data, xact->state);
+ read_binary_number(data, xact->flags);
+ xact->flags |= TRANSACTION_BULK_ALLOC;
+ read_binary_string(data, &xact->note);
+
+ xact->beg_pos = read_binary_long<unsigned long>(data);
+ read_binary_long(data, xact->beg_line);
+ xact->end_pos = read_binary_long<unsigned long>(data);
+ read_binary_long(data, xact->end_line);
+
+ xact->data = NULL;
+}
+
+inline void read_binary_entry_base(char *& data, entry_base_t * entry,
+ transaction_t *& xact_pool, bool& finalize)
+{
+ read_binary_long(data, entry->src_idx);
+ entry->beg_pos = read_binary_long<unsigned long>(data);
+ read_binary_long(data, entry->beg_line);
+ entry->end_pos = read_binary_long<unsigned long>(data);
+ read_binary_long(data, entry->end_line);
+
+ bool ignore_calculated = read_binary_bool(data);
+
+ for (unsigned long i = 0, count = read_binary_long<unsigned long>(data);
+ 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;
+ entry->add_transaction(xact_pool++);
+ }
+}
+
+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);
+ read_binary_string(data, &entry->code);
+ read_binary_string(data, &entry->payee);
+}
+
+inline void read_binary_auto_entry(char *& data, auto_entry_t * entry,
+ transaction_t *& xact_pool)
+{
+ bool ignore;
+ read_binary_entry_base(data, entry, xact_pool, ignore);
+
+ 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,
+ transaction_t *& xact_pool, bool& finalize)
+{
+ read_binary_entry_base(data, entry, xact_pool, finalize);
+ read_binary_string(data, &entry->period_string);
+ std::istringstream stream(entry->period_string);
+ entry->period.parse(stream);
+}
+
+inline commodity_base_t * read_binary_commodity_base(char *& data)
+{
+ commodity_base_t * commodity = new commodity_base_t;
+ *base_commodities_next++ = commodity;
+
+ read_binary_string(data, commodity->symbol);
+ read_binary_string(data, commodity->name);
+ read_binary_string(data, commodity->note);
+ read_binary_number(data, commodity->precision);
+ read_binary_number(data, commodity->flags);
+
+ return commodity;
+}
+
+inline void read_binary_commodity_base_extra(char *& data,
+ commodity_t::ident_t ident)
+{
+ commodity_base_t * commodity = base_commodities[ident];
+
+ bool read_history = false;
+ for (unsigned long i = 0, count = read_binary_long<unsigned long>(data);
+ i < count;
+ i++) {
+ moment_t when;
+ read_binary_number(data, when);
+ amount_t amt;
+ read_binary_amount(data, amt);
+
+ // Upon insertion, amt will be copied, which will cause the amount
+ // to be duplicated (and thus not lost when the journal's
+ // item_pool is deleted).
+ if (! commodity->history)
+ commodity->history = new commodity_base_t::history_t;
+ commodity->history->prices.insert(history_pair(when, amt));
+
+ read_history = true;
+ }
+ if (read_history)
+ read_binary_number(data, commodity->history->last_lookup);
+
+ if (read_binary_bool(data)) {
+ amount_t amt;
+ read_binary_amount(data, amt);
+ commodity->smaller = new amount_t(amt);
+ }
+
+ if (read_binary_bool(data)) {
+ amount_t amt;
+ read_binary_amount(data, amt);
+ commodity->larger = new amount_t(amt);
+ }
+}
+
+inline commodity_t * read_binary_commodity(char *& data)
+{
+ commodity_t * commodity = new commodity_t;
+ *commodities_next++ = commodity;
+
+ commodity->base =
+ base_commodities[read_binary_long<commodity_base_t::ident_t>(data) - 1];
+
+ read_binary_string(data, commodity->qualified_symbol);
+ commodity->annotated = false;
+
+ return commodity;
+}
+
+inline commodity_t * read_binary_commodity_annotated(char *& data)
+{
+ annotated_commodity_t * commodity = new annotated_commodity_t;
+ *commodities_next++ = commodity;
+
+ commodity->base =
+ base_commodities[read_binary_long<commodity_base_t::ident_t>(data) - 1];
+
+ read_binary_string(data, commodity->qualified_symbol);
+ commodity->annotated = true;
+
+ commodity->ptr =
+ commodities[read_binary_long<commodity_t::ident_t>(data) - 1];
+
+ // This read-and-then-assign causes a new amount to be allocated
+ // which does not live within the bulk allocation pool, since that
+ // pool will be deleted *before* the commodities are destroyed.
+ amount_t amt;
+ read_binary_amount(data, amt);
+ commodity->price = amt;
+
+ read_binary_number(data, commodity->date);
+ read_binary_string(data, commodity->tag);
+
+ return commodity;
+}
+
+inline
+account_t * read_binary_account(char *& data, journal_t * journal,
+ account_t * master = NULL)
+{
+ account_t * acct = new account_t(NULL);
+ *accounts_next++ = acct;
+
+ acct->journal = journal;
+
+ account_t::ident_t id;
+ read_binary_long(data, id); // parent id
+ if (id == 0xffffffff)
+ acct->parent = NULL;
+ else
+ acct->parent = accounts[id - 1];
+
+ read_binary_string(data, acct->name);
+ read_binary_string(data, acct->note);
+ read_binary_number(data, acct->depth);
+
+ // If all of the subaccounts will be added to a different master
+ // account, throw away what we've learned about the recorded
+ // journal's own master account.
+
+ if (master && acct != master) {
+ delete acct;
+ acct = master;
+ }
+
+ for (account_t::ident_t i = 0,
+ count = read_binary_long<account_t::ident_t>(data);
+ i < count;
+ i++) {
+ account_t * child = read_binary_account(data, journal);
+ child->parent = acct;
+ assert(acct != child);
+ acct->add_account(child);
+ }
+
+ return acct;
+}
+
+unsigned int read_binary_journal(std::istream& in,
+ journal_t * journal,
+ account_t * master,
+ const string& original_file)
+{
+ 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 (! original_file.empty()) {
+ for (unsigned short i = 0,
+ count = read_binary_number<unsigned short>(in);
+ i < count;
+ i++) {
+ string path = read_binary_string(in);
+ std::time_t old_mtime;
+ read_binary_number(in, old_mtime);
+ struct stat info;
+ stat(path.c_str(), &info);
+ if (std::difftime(info.st_mtime, old_mtime) > 0)
+ return 0;
+
+ journal->sources.push_back(path);
+ }
+
+ // Make sure that the cache uses the same price database,
+ // otherwise it means that LEDGER_PRICE_DB has been changed, and
+ // we should ignore this cache file.
+ if (read_binary_string(in) != journal->price_db)
+ return 0;
+ }
+
+ // Read all of the data in at once, so that we're just dealing with
+ // a big data buffer.
+
+ unsigned long data_size = read_binary_number<unsigned long>(in);
+
+ char * data_pool = new char[data_size];
+ char * data = data_pool;
+ in.read(data, data_size);
+
+ // Read in the accounts
+
+ account_t::ident_t a_count = read_binary_long<account_t::ident_t>(data);
+ accounts = accounts_next = new account_t *[a_count];
+
+ assert(journal->master);
+ delete journal->master;
+ journal->master = read_binary_account(data, journal, master);
+
+ if (read_binary_bool(data))
+ journal->basket = accounts[read_binary_long<account_t::ident_t>(data) - 1];
+
+ // Allocate the memory needed for the entries and transactions in
+ // one large block, which is then chopped up and custom constructed
+ // as necessary.
+
+ unsigned long count = read_binary_long<unsigned long>(data);
+ unsigned long auto_count = read_binary_long<unsigned long>(data);
+ unsigned long period_count = read_binary_long<unsigned long>(data);
+ unsigned long xact_count = read_binary_number<unsigned long>(data);
+ unsigned long bigint_count = read_binary_number<unsigned long>(data);
+
+ std::size_t pool_size = (sizeof(entry_t) * count +
+ sizeof(transaction_t) * xact_count +
+ sizeof_bigint_t() * bigint_count);
+
+ char * item_pool = new char[pool_size];
+
+ journal->item_pool = item_pool;
+ journal->item_pool_end = item_pool + pool_size;
+
+ entry_t * entry_pool = (entry_t *) item_pool;
+ transaction_t * xact_pool = (transaction_t *) (item_pool +
+ sizeof(entry_t) * count);
+ bigints_index = 0;
+ bigints = bigints_next = (item_pool + sizeof(entry_t) * count +
+ sizeof(transaction_t) * xact_count);
+
+ // Read in the base commodities and then derived commodities
+
+ commodity_base_t::ident_t bc_count =
+ read_binary_long<commodity_base_t::ident_t>(data);
+ base_commodities = base_commodities_next = new commodity_base_t *[bc_count];
+
+ for (commodity_base_t::ident_t i = 0; i < bc_count; i++) {
+ commodity_base_t * commodity = read_binary_commodity_base(data);
+
+ std::pair<base_commodities_map::iterator, bool> result =
+ commodity_base_t::commodities.insert
+ (base_commodities_pair(commodity->symbol, commodity));
+ if (! result.second) {
+ base_commodities_map::iterator c =
+ commodity_base_t::commodities.find(commodity->symbol);
+
+ // It's possible the user might have used a commodity in a value
+ // expression passed to an option, we'll just override the
+ // flags, but keep the commodity pointer intact.
+ if (c == commodity_base_t::commodities.end())
+ throw new error(string("Failed to read base commodity from cache: ") +
+ commodity->symbol);
+
+ (*c).second->name = commodity->name;
+ (*c).second->note = commodity->note;
+ (*c).second->precision = commodity->precision;
+ (*c).second->flags = commodity->flags;
+ if ((*c).second->smaller)
+ delete (*c).second->smaller;
+ (*c).second->smaller = commodity->smaller;
+ if ((*c).second->larger)
+ delete (*c).second->larger;
+ (*c).second->larger = commodity->larger;
+
+ *(base_commodities_next - 1) = (*c).second;
+ delete commodity;
+ }
+ }
+
+ commodity_t::ident_t c_count = read_binary_long<commodity_t::ident_t>(data);
+ commodities = commodities_next = new commodity_t *[c_count];
+
+ for (commodity_t::ident_t i = 0; i < c_count; i++) {
+ commodity_t * commodity;
+ string mapping_key;
+
+ if (! read_binary_bool(data)) {
+ commodity = read_binary_commodity(data);
+ mapping_key = commodity->base->symbol;
+ } else {
+ read_binary_string(data, mapping_key);
+ commodity = read_binary_commodity_annotated(data);
+ }
+
+ std::pair<commodities_map::iterator, bool> result =
+ commodity_t::commodities.insert(commodities_pair
+ (mapping_key, commodity));
+ if (! result.second) {
+ commodities_map::iterator c =
+ commodity_t::commodities.find(mapping_key);
+ if (c == commodity_t::commodities.end())
+ throw new error(string("Failed to read commodity from cache: ") +
+ commodity->symbol());
+
+ *(commodities_next - 1) = (*c).second;
+ delete commodity;
+ }
+ }
+
+ for (commodity_base_t::ident_t i = 0; i < bc_count; i++)
+ read_binary_commodity_base_extra(data, i);
+
+ commodity_t::ident_t ident;
+ read_binary_long(data, ident);
+ if (ident == 0xffffffff || ident == 0)
+ commodity_t::default_commodity = NULL;
+ else
+ commodity_t::default_commodity = commodities[ident - 1];
+
+ // Read in the entries and transactions
+
+ for (unsigned long i = 0; i < count; i++) {
+ new(entry_pool) entry_t;
+ bool finalize = false;
+ 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++);
+ }
+
+ for (unsigned long i = 0; i < auto_count; i++) {
+ auto_entry_t * auto_entry = new auto_entry_t;
+ read_binary_auto_entry(data, auto_entry, xact_pool);
+ auto_entry->journal = journal;
+ journal->auto_entries.push_back(auto_entry);
+ }
+
+ for (unsigned long i = 0; i < period_count; i++) {
+ period_entry_t * period_entry = new period_entry_t;
+ bool finalize = false;
+ read_binary_period_entry(data, period_entry, xact_pool, finalize);
+ period_entry->journal = journal;
+ if (finalize && ! period_entry->finalize())
+ continue;
+ journal->period_entries.push_back(period_entry);
+ }
+
+ // Clean up and return the number of entries read
+
+ delete[] accounts;
+ delete[] commodities;
+ delete[] data_pool;
+
+ VALIDATE(journal->valid());
+
+ 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 &&
+ read_binary_number_nocheck<unsigned long>(in) == format_version)
+ return true;
+
+ in.clear();
+ in.seekg(0, std::ios::beg);
+ return false;
+}
+
+unsigned int binary_parser_t::parse(std::istream& in,
+ journal_t * journal,
+ account_t * master,
+ const string * original_file)
+{
+#if 0
+ return read_binary_journal(in, journal, master,
+ original_file ? *original_file : "");
+#endif
+}
+#endif
+
+
+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);
+}
+
+void write_binary_string(std::ostream& out, const string& str)
+{
+ write_binary_guard(out, 0x3001);
+
+ unsigned long len = str.length();
+ if (len > 255) {
+ assert(len < 65536);
+ write_binary_number_nocheck<unsigned char>(out, 0xff);
+ write_binary_number_nocheck<unsigned short>(out, len);
+ } else {
+ write_binary_number_nocheck<unsigned char>(out, len);
+ }
+
+ if (len)
+ out.write(str.c_str(), len);
+
+ write_binary_guard(out, 0x3002);
+}
+
+#if 0
+void write_binary_value(std::ostream& out, const value_t& val)
+{
+ write_binary_long(out, (int)val.type);
+
+ switch (val.type) {
+ case value_t::BOOLEAN:
+ write_binary_bool(out, *((bool *) val.data));
+ break;
+ case value_t::INTEGER:
+ write_binary_long(out, *((long *) val.data));
+ break;
+ case value_t::DATETIME:
+ write_binary_number(out, *((moment_t *) val.data));
+ break;
+ case value_t::AMOUNT:
+ write_binary_amount(out, *((amount_t *) val.data));
+ break;
+
+ case value_t::BALANCE:
+ case value_t::BALANCE_PAIR:
+ throw new error("Cannot write a balance to the binary cache");
+ }
+}
+
+void write_binary_mask(std::ostream& out, mask_t * mask)
+{
+ write_binary_number(out, mask->exclude);
+ write_binary_string(out, mask->pattern);
+}
+
+void write_binary_transaction(std::ostream& out, transaction_t * xact,
+ bool ignore_calculated)
+{
+ write_binary_number(out, xact->_date);
+ write_binary_number(out, xact->_date_eff);
+ write_binary_long(out, xact->account->ident);
+
+ if (ignore_calculated && xact->flags & TRANSACTION_CALCULATED) {
+ write_binary_number<unsigned char>(out, 0);
+ write_binary_amount(out, amount_t());
+ }
+ else if (! xact->amount_expr.empty()) {
+ write_binary_number<unsigned char>(out, 1);
+ write_binary_string(out, xact->amount_expr);
+ }
+ else {
+ write_binary_number<unsigned char>(out, 0);
+ write_binary_amount(out, xact->amount);
+ }
+
+ if (xact->cost &&
+ (! (ignore_calculated && xact->flags & TRANSACTION_CALCULATED))) {
+ write_binary_bool(out, true);
+ write_binary_amount(out, *xact->cost);
+ write_binary_string(out, xact->cost_expr);
+ } else {
+ write_binary_bool(out, false);
+ }
+
+ write_binary_number(out, xact->state);
+ write_binary_number(out, xact->flags);
+ write_binary_string(out, xact->note);
+
+ write_binary_long(out, xact->beg_pos);
+ write_binary_long(out, xact->beg_line);
+ write_binary_long(out, xact->end_pos);
+ write_binary_long(out, xact->end_line);
+}
+
+void write_binary_entry_base(std::ostream& out, entry_base_t * entry)
+{
+ write_binary_long(out, entry->src_idx);
+ write_binary_long(out, entry->beg_pos);
+ write_binary_long(out, entry->beg_line);
+ write_binary_long(out, entry->end_pos);
+ write_binary_long(out, entry->end_line);
+
+ bool ignore_calculated = false;
+ for (transactions_list::const_iterator i = entry->transactions.begin();
+ i != entry->transactions.end();
+ i++)
+ if (! (*i)->amount_expr.empty()) {
+ ignore_calculated = true;
+ break;
+ }
+
+ write_binary_bool(out, ignore_calculated);
+
+ write_binary_long(out, entry->transactions.size());
+ for (transactions_list::const_iterator i = entry->transactions.begin();
+ i != entry->transactions.end();
+ i++)
+ write_binary_transaction(out, *i, ignore_calculated);
+}
+
+void write_binary_entry(std::ostream& out, entry_t * entry)
+{
+ write_binary_entry_base(out, entry);
+ write_binary_number(out, entry->_date);
+ write_binary_number(out, entry->_date_eff);
+ write_binary_string(out, entry->code);
+ write_binary_string(out, entry->payee);
+}
+
+void write_binary_auto_entry(std::ostream& out, auto_entry_t * entry)
+{
+ write_binary_entry_base(out, entry);
+ write_binary_string(out, entry->predicate.expr);
+}
+
+void write_binary_period_entry(std::ostream& out, period_entry_t * entry)
+{
+ write_binary_entry_base(out, entry);
+ write_binary_string(out, entry->period_string);
+}
+
+void write_binary_commodity_base(std::ostream& out, commodity_base_t * commodity)
+{
+ commodity->ident = ++base_commodity_index;
+
+ write_binary_string(out, commodity->symbol);
+ write_binary_string(out, commodity->name);
+ write_binary_string(out, commodity->note);
+ write_binary_number(out, commodity->precision);
+ write_binary_number(out, commodity->flags);
+}
+
+void write_binary_commodity_base_extra(std::ostream& out,
+ commodity_base_t * commodity)
+{
+ if (commodity->history && commodity->history->bogus_time)
+ commodity->remove_price(commodity->history->bogus_time);
+
+ if (! commodity->history) {
+ write_binary_long<unsigned long>(out, 0);
+ } else {
+ write_binary_long<unsigned long>(out, commodity->history->prices.size());
+ for (history_map::const_iterator i = commodity->history->prices.begin();
+ i != commodity->history->prices.end();
+ i++) {
+ write_binary_number(out, (*i).first);
+ write_binary_amount(out, (*i).second);
+ }
+ write_binary_number(out, commodity->history->last_lookup);
+ }
+
+ if (commodity->smaller) {
+ write_binary_bool(out, true);
+ write_binary_amount(out, *commodity->smaller);
+ } else {
+ write_binary_bool(out, false);
+ }
+
+ if (commodity->larger) {
+ write_binary_bool(out, true);
+ write_binary_amount(out, *commodity->larger);
+ } else {
+ write_binary_bool(out, false);
+ }
+}
+
+void write_binary_commodity(std::ostream& out, commodity_t * commodity)
+{
+ commodity->ident = ++commodity_index;
+
+ write_binary_long(out, commodity->base->ident);
+ write_binary_string(out, commodity->qualified_symbol);
+}
+
+void write_binary_commodity_annotated(std::ostream& out,
+ commodity_t * commodity)
+{
+ commodity->ident = ++commodity_index;
+
+ write_binary_long(out, commodity->base->ident);
+ write_binary_string(out, commodity->qualified_symbol);
+
+ annotated_commodity_t * ann_comm =
+ static_cast<annotated_commodity_t *>(commodity);
+
+ write_binary_long(out, ann_comm->base->ident);
+ write_binary_amount(out, ann_comm->price);
+ write_binary_number(out, ann_comm->date);
+ write_binary_string(out, ann_comm->tag);
+}
+
+static inline account_t::ident_t count_accounts(account_t * account)
+{
+ account_t::ident_t count = 1;
+
+ for (accounts_map::iterator i = account->accounts.begin();
+ i != account->accounts.end();
+ i++)
+ count += count_accounts((*i).second);
+
+ return count;
+}
+
+void write_binary_account(std::ostream& out, account_t * account)
+{
+ account->ident = ++account_index;
+
+ if (account->parent)
+ write_binary_long(out, account->parent->ident);
+ else
+ write_binary_long<account_t::ident_t>(out, 0xffffffff);
+
+ write_binary_string(out, account->name);
+ write_binary_string(out, account->note);
+ write_binary_number(out, account->depth);
+
+ write_binary_long<account_t::ident_t>(out, account->accounts.size());
+ for (accounts_map::iterator i = account->accounts.begin();
+ i != account->accounts.end();
+ i++)
+ write_binary_account(out, (*i).second);
+}
+
+void write_binary_journal(std::ostream& out, journal_t * journal)
+{
+ account_index =
+ base_commodity_index =
+ commodity_index = 0;
+
+ write_binary_number_nocheck(out, binary_magic_number);
+ write_binary_number_nocheck(out, format_version);
+
+ // Write out the files that participated in this journal, so that
+ // they can be checked for changes on reading.
+
+ if (journal->sources.empty()) {
+ write_binary_number<unsigned short>(out, 0);
+ } else {
+ write_binary_number<unsigned short>(out, journal->sources.size());
+ for (strings_list::const_iterator i = journal->sources.begin();
+ i != journal->sources.end();
+ i++) {
+ write_binary_string(out, *i);
+ struct stat info;
+ stat((*i).c_str(), &info);
+ write_binary_number(out, std::time_t(info.st_mtime));
+ }
+
+ // Write out the price database that relates to this data file, so
+ // that if it ever changes the cache can be invalidated.
+ write_binary_string(out, journal->price_db);
+ }
+
+ ostream_pos_type data_val = out.tellp();
+ write_binary_number<unsigned long>(out, 0);
+
+ // Write out the accounts
+
+ write_binary_long<account_t::ident_t>(out, count_accounts(journal->master));
+ write_binary_account(out, journal->master);
+
+ if (journal->basket) {
+ write_binary_bool(out, true);
+ write_binary_long(out, journal->basket->ident);
+ } else {
+ write_binary_bool(out, false);
+ }
+
+ // Write out the number of entries, transactions, and amounts
+
+ write_binary_long<unsigned long>(out, journal->entries.size());
+ write_binary_long<unsigned long>(out, journal->auto_entries.size());
+ write_binary_long<unsigned long>(out, journal->period_entries.size());
+
+ ostream_pos_type xacts_val = out.tellp();
+ write_binary_number<unsigned long>(out, 0);
+
+ ostream_pos_type bigints_val = out.tellp();
+ write_binary_number<unsigned long>(out, 0);
+
+ bigints_count = 0;
+
+ // Write out the commodities
+
+ write_binary_long<commodity_t::ident_t>
+ (out, commodity_base_t::commodities.size());
+
+ for (base_commodities_map::const_iterator i =
+ commodity_base_t::commodities.begin();
+ i != commodity_base_t::commodities.end();
+ i++)
+ write_binary_commodity_base(out, (*i).second);
+
+ write_binary_long<commodity_t::ident_t>
+ (out, commodity_t::commodities.size());
+
+ for (commodities_map::const_iterator i = commodity_t::commodities.begin();
+ i != commodity_t::commodities.end();
+ i++) {
+ if (! (*i).second->annotated) {
+ write_binary_bool(out, false);
+ write_binary_commodity(out, (*i).second);
+ }
+ }
+
+ for (commodities_map::const_iterator i = commodity_t::commodities.begin();
+ i != commodity_t::commodities.end();
+ i++) {
+ if ((*i).second->annotated) {
+ write_binary_bool(out, true);
+ write_binary_string(out, (*i).first); // the mapping key
+ write_binary_commodity_annotated(out, (*i).second);
+ }
+ }
+
+ // Write out the history and smaller/larger convertible links after
+ // both the base and the main commodities have been written, since
+ // the amounts in both will refer to the mains.
+
+ for (base_commodities_map::const_iterator i =
+ commodity_base_t::commodities.begin();
+ i != commodity_base_t::commodities.end();
+ i++)
+ write_binary_commodity_base_extra(out, (*i).second);
+
+ if (commodity_t::default_commodity)
+ write_binary_long(out, commodity_t::default_commodity->ident);
+ else
+ write_binary_long<commodity_t::ident_t>(out, 0xffffffff);
+
+ // Write out the entries and transactions
+
+ unsigned long xact_count = 0;
+
+ for (entries_list::const_iterator i = journal->entries.begin();
+ i != journal->entries.end();
+ i++) {
+ write_binary_entry(out, *i);
+ xact_count += (*i)->transactions.size();
+ }
+
+ for (auto_entries_list::const_iterator i = journal->auto_entries.begin();
+ i != journal->auto_entries.end();
+ i++) {
+ write_binary_auto_entry(out, *i);
+ xact_count += (*i)->transactions.size();
+ }
+
+ for (period_entries_list::const_iterator i = journal->period_entries.begin();
+ i != journal->period_entries.end();
+ i++) {
+ write_binary_period_entry(out, *i);
+ xact_count += (*i)->transactions.size();
+ }
+
+ // Back-patch the count for amounts
+
+ unsigned long data_size = (((unsigned long) out.tellp()) -
+ ((unsigned long) data_val) -
+ sizeof(unsigned long));
+ out.seekp(data_val);
+ write_binary_number<unsigned long>(out, data_size);
+ out.seekp(xacts_val);
+ write_binary_number<unsigned long>(out, xact_count);
+ out.seekp(bigints_val);
+ write_binary_number<unsigned long>(out, bigints_count);
+}
+#endif
+
+} // namespace ledger
diff --git a/src/binary.h b/src/binary.h
new file mode 100644
index 00000000..528217fa
--- /dev/null
+++ b/src/binary.h
@@ -0,0 +1,252 @@
+#ifndef _BINARY_H
+#define _BINARY_H
+
+#include "parser.h"
+
+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,
+ journal_t * journal,
+ account_t * master = NULL,
+ const 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, string& str);
+void read_binary_string(char *& data, string& str);
+void read_binary_string(char *& data, string * str);
+
+inline string read_binary_string(std::istream& in) {
+ string temp;
+ read_binary_string(in, temp);
+ return temp;
+}
+
+inline string read_binary_string(char *& data) {
+ 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 string& str);
+
+
+
+#if 0
+void write_binary_journal(std::ostream& out, journal_t * journal);
+#endif
+
+} // namespace ledger
+
+#endif // _BINARY_H
diff --git a/src/context.h b/src/context.h
new file mode 100644
index 00000000..3851d073
--- /dev/null
+++ b/src/context.h
@@ -0,0 +1,28 @@
+#ifndef _CONTEXT_H
+#define _CONTEXT_H
+
+namespace ledger {
+
+class context
+{
+public:
+ string context; // ex: 'While parsing file "%R" at line %L'
+
+ string resource; // ex: ledger.dat
+ long linenum_beg; // ex: 1010
+ long linenum_end; // ex: 1010
+ long colnum_beg; // ex: 8
+ long colnum_end; // ex: 8
+ long position_beg;
+ long position_end;
+
+ string text; // ex: (The multi-line text of an entry)
+ long linenum_beg_off; // ex: 2 / -1 means start at beginning
+ long linenum_end_off; // ex: 2 / -1 means start at beginning
+ long colnum_beg_off; // ex: 8 / -1 means start
+ long colnum_end_off; // ex: 8 / -1 means start
+};
+
+} // namespace ledger
+
+#endif // _CONTEXT_H
diff --git a/src/csv.cc b/src/csv.cc
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/csv.cc
diff --git a/src/csv.h b/src/csv.h
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/csv.h
diff --git a/src/derive.cc b/src/derive.cc
new file mode 100644
index 00000000..6586c1f4
--- /dev/null
+++ b/src/derive.cc
@@ -0,0 +1,178 @@
+#include "derive.h"
+#include "mask.h"
+
+namespace ledger {
+
+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 == args.end())
+ throw new error("Too few arguments to 'entry'");
+
+ mask_t regexp(*i++);
+
+ entries_list::reverse_iterator j;
+ for (j = journal.entries.rbegin();
+ j != journal.entries.rend();
+ j++)
+ if (regexp.match((*j)->payee)) {
+ matching = *j;
+ break;
+ }
+
+ added->payee = matching ? matching->payee : regexp.pattern;
+
+ if (! matching) {
+ account_t * acct;
+ if (i == args.end() || ((*i)[0] == '-' || std::isdigit((*i)[0]))) {
+ acct = journal.find_account("Expenses");
+ }
+ else if (i != args.end()) {
+ acct = journal.find_account_re(*i);
+ if (! acct)
+ acct = journal.find_account(*i);
+ assert(acct);
+ i++;
+ }
+
+ if (i == args.end()) {
+ added->add_transaction(new transaction_t(acct));
+ } else {
+ transaction_t * xact = new transaction_t(acct, amount_t(*i++));
+ added->add_transaction(xact);
+
+ if (! xact->amount.commodity()) {
+ // If the amount has no commodity, we can determine it given
+ // the account by creating a final for the account and then
+ // checking if it contains only a single commodity. An
+ // account to which only dollars are applied would imply that
+ // dollars are wanted now too.
+
+ std::auto_ptr<item_handler<transaction_t> > formatter;
+ formatter.reset(new set_account_value);
+ walk_entries(journal.entries, *formatter.get());
+ formatter->flush();
+
+ sum_accounts(*journal.master);
+
+ value_t total = account_xdata(*acct).total;
+ if (total.type == value_t::AMOUNT)
+ xact->amount.set_commodity(((amount_t *) total.data)->commodity());
+ }
+ }
+
+ if (journal.basket)
+ acct = journal.basket;
+ else
+ acct = journal.find_account("Equity");
+
+ added->add_transaction(new transaction_t(acct));
+ }
+ 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;
+
+ for (transactions_list::iterator k = matching->transactions.begin();
+ k != matching->transactions.end();
+ k++)
+ added->add_transaction(new transaction_t(**k));
+ }
+ else if ((*i)[0] == '-' || std::isdigit((*i)[0])) {
+ transaction_t * m_xact, * xact, * first;
+ m_xact = matching->transactions.front();
+
+ first = xact = new transaction_t(m_xact->account, amount_t(*i++));
+ added->add_transaction(xact);
+
+ if (! xact->amount.commodity())
+ xact->amount.set_commodity(m_xact->amount.commodity());
+
+ m_xact = matching->transactions.back();
+
+ xact = new transaction_t(m_xact->account, - first->amount);
+ added->add_transaction(xact);
+
+ if (i != args.end()) {
+ account_t * acct = journal.find_account_re(*i);
+ if (! acct)
+ acct = journal.find_account(*i);
+ assert(acct);
+ added->transactions.back()->account = acct;
+ }
+ }
+ else {
+ while (i != args.end()) {
+ string& re_pat(*i++);
+ account_t * acct = NULL;
+ amount_t * amt = NULL;
+
+ mask_t acct_regex(re_pat);
+
+ for (; j != journal.entries.rend(); j++)
+ if (regexp.match((*j)->payee)) {
+ entry_t * entry = *j;
+ for (transactions_list::const_iterator x =
+ entry->transactions.begin();
+ x != entry->transactions.end();
+ x++)
+ if (acct_regex.match((*x)->account->fullname())) {
+ acct = (*x)->account;
+ amt = &(*x)->amount;
+ matching = entry;
+ goto found;
+ }
+ }
+
+ found:
+ if (! acct)
+ acct = journal.find_account_re(re_pat);
+ if (! acct)
+ acct = journal.find_account(re_pat);
+
+ transaction_t * xact;
+ if (i == args.end()) {
+ if (amt)
+ xact = new transaction_t(acct, *amt);
+ else
+ xact = new transaction_t(acct);
+ } else {
+ xact = new transaction_t(acct, amount_t(*i++));
+ if (! xact->amount.commodity()) {
+ if (amt)
+ xact->amount.set_commodity(amt->commodity());
+ else if (commodity_t::default_commodity)
+ xact->amount.set_commodity(*commodity_t::default_commodity);
+ }
+ }
+ added->add_transaction(xact);
+ }
+
+ assert(matching->transactions.back()->account);
+ if (account_t * draw_acct = matching->transactions.back()->account)
+ added->add_transaction(new transaction_t(draw_acct));
+ }
+
+ done:
+ if (! run_hooks(journal.entry_finalize_hooks, *added, false) ||
+ ! added->finalize() ||
+ ! run_hooks(journal.entry_finalize_hooks, *added, true))
+ throw new error("Failed to finalize derived entry (check commodities)");
+
+ return added.release();
+#endif
+}
+
+} // namespace ledger
diff --git a/src/derive.h b/src/derive.h
new file mode 100644
index 00000000..c0607fc2
--- /dev/null
+++ b/src/derive.h
@@ -0,0 +1,18 @@
+#ifndef _DERIVE_H
+#define _DERIVE_H
+
+#include "journal.h"
+
+namespace ledger {
+
+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
+
+#endif // _DERIVE_H
diff --git a/src/emacs.cc b/src/emacs.cc
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/emacs.cc
diff --git a/src/emacs.h b/src/emacs.h
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/emacs.h
diff --git a/src/error.h b/src/error.h
new file mode 100644
index 00000000..5cbf54fb
--- /dev/null
+++ b/src/error.h
@@ -0,0 +1,151 @@
+#ifndef _ERROR_H
+#define _ERROR_H
+
+#import "context.h"
+
+namespace ledger {
+
+class exception : public std::exception
+{
+protected:
+ string reason;
+
+public:
+ std::list<context> context_stack;
+
+ exception(const string& _reason,
+ const context& immediate_ctxt) throw()
+ : reason(_reason) {
+ EXCEPTION(reason);
+ push(immediate_ctxt);
+ }
+
+ virtual ~exception() throw() {}
+
+ void push(const context& intermediate_ctxt) throw() {
+ context_stack.push_front(intermediate_ctxt);
+ }
+
+ void write(std::ostream& out) const throw() {
+#if 0
+ for (std::list<context>::const_iterator
+ i = context_stack.begin();
+ i != context_stack.end();
+ i++)
+ (*i).write(out);
+#endif
+ }
+
+ const char * what() const throw() {
+ return reason.c_str();
+ }
+};
+
+#define DECLARE_EXCEPTION(name) \
+ class name : public exception { \
+ public: \
+ name(const string& _reason, \
+ const context& immediate_ctxt) throw() \
+ : exception(_reason, immediate_ctxt) {} \
+ }
+
+#if 0
+
+class error_context
+{
+ public:
+ string desc;
+
+ error_context(const string& _desc) throw() : desc(_desc) {}
+ virtual ~error_context() throw() {}
+ virtual void describe(std::ostream& out) const throw() {
+ if (! desc.empty())
+ out << desc << std::endl;
+ }
+};
+
+class file_context : public error_context
+{
+ protected:
+ string file;
+ unsigned long line;
+ public:
+ file_context(const string& _file, unsigned long _line,
+ const string& _desc = "") throw()
+ : error_context(_desc), file(_file), line(_line) {}
+ virtual ~file_context() throw() {}
+
+ virtual void describe(std::ostream& out) const throw() {
+ if (! desc.empty())
+ out << desc << " ";
+
+ out << "\"" << file << "\", line " << line << ": ";
+ }
+};
+
+class line_context : public error_context {
+ public:
+ string line;
+ long pos;
+
+ line_context(const string& _line, long _pos,
+ const string& _desc = "") throw()
+ : error_context(_desc), line(_line), pos(_pos) {}
+ virtual ~line_context() throw() {}
+
+ virtual void describe(std::ostream& out) const throw() {
+ if (! desc.empty())
+ out << desc << std::endl;
+
+ out << " " << line << std::endl << " ";
+ long idx = pos < 0 ? line.length() - 1 : pos;
+ for (int i = 0; i < idx; i++)
+ out << " ";
+ out << "^" << std::endl;
+ }
+};
+
+class error : public str_exception {
+ public:
+ error(const string& _reason, error_context * _ctxt = NULL) throw()
+ : str_exception(_reason, _ctxt) {}
+ virtual ~error() throw() {}
+};
+
+class fatal : public str_exception {
+ public:
+ fatal(const string& _reason, error_context * _ctxt = NULL) throw()
+ : str_exception(_reason, _ctxt) {}
+ virtual ~fatal() throw() {}
+};
+
+class fatal_assert : public fatal {
+ public:
+ fatal_assert(const string& _reason, error_context * _ctxt = NULL) throw()
+ : fatal(string("assertion failed '") + _reason + "'", _ctxt) {}
+ virtual ~fatal_assert() throw() {}
+};
+
+#endif // 0
+
+inline void unexpected(char c, char wanted)
+{
+#if 0
+ if ((unsigned char) c == 0xff) {
+ if (wanted)
+ throw new error(string("Missing '") + wanted + "'");
+ else
+ throw new error("Unexpected end of input");
+ } else {
+ if (wanted)
+ throw new error(string("Invalid char '") + c +
+ "' (wanted '" + wanted + "')");
+ else
+ throw new error(string("Invalid char '") + c + "'");
+ }
+#endif
+}
+
+} // namespace ledger
+
+#endif // _ERROR_H
diff --git a/src/fdstream.hpp b/src/fdstream.hpp
new file mode 100644
index 00000000..a74a5781
--- /dev/null
+++ b/src/fdstream.hpp
@@ -0,0 +1,184 @@
+/* The following code declares classes to read from and write to
+ * file descriptore or file handles.
+ *
+ * See
+ * http://www.josuttis.com/cppcode
+ * for details and the latest version.
+ *
+ * - open:
+ * - integrating BUFSIZ on some systems?
+ * - optimized reading of multiple characters
+ * - stream for reading AND writing
+ * - i18n
+ *
+ * (C) Copyright Nicolai M. Josuttis 2001.
+ * Permission to copy, use, modify, sell and distribute this software
+ * is granted provided this copyright notice appears in all copies.
+ * This software is provided "as is" without express or implied
+ * warranty, and with no claim as to its suitability for any purpose.
+ *
+ * Version: Jul 28, 2002
+ * History:
+ * Jul 28, 2002: bugfix memcpy() => memmove()
+ * fdinbuf::underflow(): cast for return statements
+ * Aug 05, 2001: first public version
+ */
+#ifndef BOOST_FDSTREAM_HPP
+#define BOOST_FDSTREAM_HPP
+
+#include <istream>
+#include <ostream>
+#include <streambuf>
+// for EOF:
+#include <cstdio>
+// for memmove():
+#include <cstring>
+
+
+// low-level read and write functions
+#ifdef _MSC_VER
+# include <io.h>
+#else
+# include <unistd.h>
+//extern "C" {
+// int write (int fd, const char* buf, int num);
+// int read (int fd, char* buf, int num);
+//}
+#endif
+
+
+// BEGIN namespace BOOST
+namespace boost {
+
+
+/************************************************************
+ * fdostream
+ * - a stream that writes on a file descriptor
+ ************************************************************/
+
+
+class fdoutbuf : public std::streambuf {
+ protected:
+ int fd; // file descriptor
+ public:
+ // constructor
+ fdoutbuf (int _fd) : fd(_fd) {
+ }
+ protected:
+ // write one character
+ virtual int_type overflow (int_type c) {
+ if (c != EOF) {
+ char z = c;
+ if (write (fd, &z, 1) != 1) {
+ return EOF;
+ }
+ }
+ return c;
+ }
+ // write multiple characters
+ virtual
+ std::streamsize xsputn (const char* s,
+ std::streamsize num) {
+ return write(fd,s,num);
+ }
+};
+
+class fdostream : public std::ostream {
+ protected:
+ fdoutbuf buf;
+ public:
+ fdostream (int fd) : std::ostream(0), buf(fd) {
+ rdbuf(&buf);
+ }
+};
+
+
+/************************************************************
+ * fdistream
+ * - a stream that reads on a file descriptor
+ ************************************************************/
+
+class fdinbuf : public std::streambuf {
+ protected:
+ int fd; // file descriptor
+ 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()
+ */
+ fdinbuf (int _fd) : fd(_fd) {
+ 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;
+ num = read (fd, buffer+pbSize, bufSize);
+ if (num <= 0) {
+ // ERROR or EOF
+ return EOF;
+ }
+
+ // 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 fdistream : public std::istream {
+ protected:
+ fdinbuf buf;
+ public:
+ fdistream (int fd) : std::istream(0), buf(fd) {
+ rdbuf(&buf);
+ }
+};
+
+
+} // END namespace boost
+
+#endif /*BOOST_FDSTREAM_HPP*/
diff --git a/src/format.cc b/src/format.cc
new file mode 100644
index 00000000..774af6ca
--- /dev/null
+++ b/src/format.cc
@@ -0,0 +1,235 @@
+#include "format.h"
+#include "pyinterp.h"
+
+namespace ledger {
+
+void format_t::parse(const string& fmt)
+{
+ 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;
+ }
+ 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->kind = element_t::TEXT;
+ current->chars = new string(buf, q);
+ q = buf;
+
+ current = new element_t;
+ elements.push_back(current);
+ }
+
+ ++p;
+ if (*p == '-') {
+ current->align_left = true;
+ ++p;
+ }
+
+ if (*p && std::isdigit(*p)) {
+ int num = *p++ - '0';
+ while (*p && std::isdigit(*p)) {
+ num *= 10;
+ num += *p++ - '0';
+ }
+ current->min_width = num;
+ }
+
+ if (*p == '.') {
+ ++p;
+ int num = 0;
+ while (*p && std::isdigit(*p)) {
+ num *= 10;
+ num += *p++ - '0';
+ }
+
+ current->max_width = num;
+ 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_(format_exception, "Maximum width is less than the minimum width");
+
+ switch (*p) {
+ 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 == close && --depth == 0)
+ break;
+ else if (*p == open)
+ ++depth;
+ p++;
+ }
+ if (*p != close)
+ throw_(format_exception, "Missing '" << close << "'");
+
+ if (open == '{') {
+ assert(! current->xpath);
+ current->kind = element_t::XPATH;
+ current->xpath = new xml::xpath_t(string(b, p));
+ } else {
+ assert(! current->format);
+ current->kind = element_t::GROUP;
+ current->format = new format_t(string(b, p));
+ }
+ break;
+ }
+
+ default:
+ assert(! current->xpath);
+ current->kind = element_t::XPATH;
+ current->xpath = new xml::xpath_t(string(p, p + 1));
+ break;
+ }
+ }
+
+ if (q != buf) {
+ current = new element_t;
+ elements.push_back(current);
+
+ current->kind = element_t::TEXT;
+ current->chars = new string(buf, q);
+ }
+}
+
+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;
+ default:
+ break;
+ }
+}
+
+int format_t::element_formatter_t::operator()
+ (std::ostream& out_str, element_t * elem, xml::node_t * context,
+ int column) const
+{
+ if (elem->kind == element_t::COLUMN) {
+ if (elem->max_width != -1 && elem->max_width < column) {
+ out_str << '\n';
+ column = 0;
+ }
+
+ if (elem->min_width != -1 && elem->min_width > column) {
+ out_str << string(elem->min_width - column, ' ');
+ column = elem->min_width;
+ }
+ return column;
+ }
+
+ std::ostringstream out;
+
+ if (elem->align_left)
+ out << std::left;
+ else
+ out << std::right;
+
+ if (elem->min_width > 0)
+ out.width(elem->min_width);
+
+ int start_column = column;
+
+ 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);
+
+ string temp = out.str();
+ for (string::const_iterator i = temp.begin();
+ i != temp.end();
+ i++)
+ if (*i == '\n' || *i == '\r')
+ column = 0;
+ else
+ column++;
+
+ int virtual_width = column - start_column;
+
+ if (elem->min_width != -1 && virtual_width < elem->min_width) {
+ out_str << temp << string(' ', elem->min_width - virtual_width);
+ }
+ else if (elem->max_width != -1 && virtual_width > elem->max_width) {
+ temp.erase(temp.length() - (virtual_width - elem->max_width));
+ out_str << temp;
+ }
+ else {
+ out_str << temp;
+ }
+
+ return column;
+}
+
+int format_t::format(std::ostream& out, xml::node_t * context,
+ int column, const element_formatter_t& formatter) const
+{
+ for (std::list<element_t *>::const_iterator i = elements.begin();
+ i != elements.end();
+ i++)
+ column = formatter(out, *i, context, column);
+
+ return column;
+}
+
+} // namespace ledger
diff --git a/src/format.h b/src/format.h
new file mode 100644
index 00000000..1ddd8202
--- /dev/null
+++ b/src/format.h
@@ -0,0 +1,108 @@
+#ifndef _FORMAT_H
+#define _FORMAT_H
+
+#include "xpath.h"
+
+namespace ledger {
+
+class format_t
+{
+ public:
+ struct element_t
+ {
+ bool align_left;
+ short min_width;
+ short max_width;
+
+ enum kind_t { UNKNOWN, TEXT, COLUMN, XPATH, GROUP } kind;
+ union {
+ 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() {
+ 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;
+ }
+ }
+
+ private:
+ element_t(const element_t& other);
+ };
+
+ struct element_formatter_t {
+ virtual ~element_formatter_t() {}
+ virtual int operator()(std::ostream& out, element_t * element,
+ xml::node_t * context, int column) const;
+ };
+
+ string format_string;
+ std::list<element_t *> elements;
+
+ private:
+ format_t(const format_t&);
+
+ public:
+ format_t() {
+ TRACE_CTOR(format_t, "");
+ }
+ format_t(const string& fmt) {
+ TRACE_CTOR(format_t, "const string&");
+ parse(fmt);
+ }
+
+ void clear_elements() {
+ for (std::list<element_t *>::iterator i = elements.begin();
+ i != elements.end();
+ i++)
+ delete *i;
+ elements.clear();
+ }
+
+ virtual ~format_t() {
+ TRACE_DTOR(format_t);
+ clear_elements();
+ }
+
+ void parse(const string& fmt);
+
+ void compile(const string& fmt, xml::node_t * context = NULL) {
+ parse(fmt);
+ compile(context);
+ }
+ void compile(xml::node_t * context = NULL);
+
+ int format(std::ostream& out, xml::node_t * context = NULL,
+ int column = 0, const element_formatter_t& formatter =
+ element_formatter_t()) const;
+
+ operator bool() const {
+ return ! format_string.empty();
+ }
+};
+
+DECLARE_EXCEPTION(format_exception);
+
+} // namespace ledger
+
+#endif // _FORMAT_H
diff --git a/src/gd_qnan.h b/src/gd_qnan.h
new file mode 100644
index 00000000..87eba8fb
--- /dev/null
+++ b/src/gd_qnan.h
@@ -0,0 +1,12 @@
+#define f_QNAN 0xffc00000
+#define d_QNAN0 0x0
+#define d_QNAN1 0xfff80000
+#define ld_QNAN0 0x0
+#define ld_QNAN1 0xc0000000
+#define ld_QNAN2 0xffff
+#define ld_QNAN3 0x0
+#define ldus_QNAN0 0x0
+#define ldus_QNAN1 0x0
+#define ldus_QNAN2 0x0
+#define ldus_QNAN3 0xc000
+#define ldus_QNAN4 0xffff
diff --git a/src/gnucash.cc b/src/gnucash.cc
new file mode 100644
index 00000000..abe8c555
--- /dev/null
+++ b/src/gnucash.cc
@@ -0,0 +1,366 @@
+#include "gnucash.h"
+
+namespace ledger {
+
+void startElement(void *userData, const char *name, const char ** /* attrs */)
+{
+ gnucash_parser_t * parser = static_cast<gnucash_parser_t *>(userData);
+
+ if (std::strcmp(name, "gnc:account") == 0) {
+ parser->curr_account = new account_t(parser->master_account);
+ }
+ else if (std::strcmp(name, "act:name") == 0)
+ parser->action = gnucash_parser_t::ACCOUNT_NAME;
+ else if (std::strcmp(name, "act:id") == 0)
+ parser->action = gnucash_parser_t::ACCOUNT_ID;
+ else if (std::strcmp(name, "act:parent") == 0)
+ parser->action = gnucash_parser_t::ACCOUNT_PARENT;
+ else if (std::strcmp(name, "gnc:commodity") == 0)
+ parser->curr_comm = NULL;
+ else if (std::strcmp(name, "cmdty:id") == 0)
+ parser->action = gnucash_parser_t::COMM_SYM;
+ else if (std::strcmp(name, "cmdty:name") == 0)
+ parser->action = gnucash_parser_t::COMM_NAME;
+ else if (std::strcmp(name, "cmdty:fraction") == 0)
+ parser->action = gnucash_parser_t::COMM_PREC;
+ else if (std::strcmp(name, "gnc:transaction") == 0) {
+ assert(! parser->curr_entry);
+ parser->curr_entry = new entry_t;
+ }
+ else if (std::strcmp(name, "trn:num") == 0)
+ parser->action = gnucash_parser_t::ENTRY_NUM;
+ else if (std::strcmp(name, "trn:date-posted") == 0)
+ 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)
+ parser->action = gnucash_parser_t::ENTRY_DESC;
+ else if (std::strcmp(name, "trn:split") == 0) {
+ assert(parser->curr_entry);
+ parser->curr_entry->add_transaction(new transaction_t(parser->curr_account));
+ }
+ else if (std::strcmp(name, "split:reconciled-state") == 0)
+ parser->action = gnucash_parser_t::XACT_STATE;
+ else if (std::strcmp(name, "split:amount") == 0)
+ parser->action = gnucash_parser_t::XACT_AMOUNT;
+ else if (std::strcmp(name, "split:value") == 0)
+ parser->action = gnucash_parser_t::XACT_VALUE;
+ else if (std::strcmp(name, "split:quantity") == 0)
+ parser->action = gnucash_parser_t::XACT_QUANTITY;
+ else if (std::strcmp(name, "split:account") == 0)
+ parser->action = gnucash_parser_t::XACT_ACCOUNT;
+ else if (std::strcmp(name, "split:memo") == 0)
+ parser->action = gnucash_parser_t::XACT_NOTE;
+}
+
+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(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) {
+ parser->curr_comm = NULL;
+ }
+ else if (std::strcmp(name, "gnc:transaction") == 0) {
+ assert(parser->curr_entry);
+
+ // Add the new entry (what gnucash calls a 'transaction') to the
+ // journal
+ 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 {
+ 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
+ parser->curr_entry = NULL;
+ parser->entry_comm = NULL;
+ }
+ else if (std::strcmp(name, "trn:split") == 0) {
+ 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 (parser->entry_comm) {
+ default_commodity = parser->entry_comm;
+ } else {
+ 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) {
+ parser->curr_quant.set_commodity(*default_commodity);
+ value = parser->curr_quant.round();
+
+ if (parser->curr_value.commodity() == *default_commodity)
+ parser->curr_value = value;
+ } else {
+ value = parser->curr_quant;
+ }
+
+ xact->state = parser->curr_state;
+ xact->amount = value;
+ if (value != parser->curr_value)
+ xact->cost = new amount_t(parser->curr_value);
+
+ 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
+ parser->curr_state = transaction_t::UNCLEARED;
+ parser->curr_value = amount_t();
+ parser->curr_quant = amount_t();
+ }
+
+ parser->action = gnucash_parser_t::NO_ACTION;
+}
+
+amount_t gnucash_parser_t::convert_number(const string& number,
+ int * precision)
+{
+ const char * num = number.c_str();
+
+ if (char * p = std::strchr(num, '/')) {
+ string numer_str(num, p - num);
+ string denom_str(p + 1);
+
+ amount_t amt(numer_str);
+ amount_t den(denom_str);
+
+ if (precision)
+ *precision = denom_str.length() - 1;
+
+ if (! den) {
+ have_error = "Denominator in entry is zero!";
+ return amt;
+ } else {
+ return amt / den;
+ }
+ } else {
+ return amount_t(number);
+ }
+}
+
+void dataHandler(void *userData, const char *s, int len)
+{
+ gnucash_parser_t * parser = static_cast<gnucash_parser_t *>(userData);
+
+ switch (parser->action) {
+ case gnucash_parser_t::ACCOUNT_NAME:
+ parser->curr_account->name = string(s, len);
+ break;
+
+ case gnucash_parser_t::ACCOUNT_ID:
+ parser->curr_account_id = string(s, len);
+ break;
+
+ case gnucash_parser_t::ACCOUNT_PARENT: {
+ accounts_map::iterator i = parser->accounts_by_id.find(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 gnucash_parser_t::COMM_SYM: {
+ string symbol(s, len);
+ if (symbol == "USD") symbol = "$";
+
+ parser->curr_comm = commodity_t::find_or_create(symbol);
+ assert(parser->curr_comm);
+
+ if (symbol != "$")
+ parser->curr_comm->add_flags(COMMODITY_STYLE_SEPARATED);
+
+ if (parser->curr_account)
+ parser->account_comms.insert
+ (gnucash_parser_t::account_comm_pair(parser->curr_account,
+ parser->curr_comm));
+ else if (parser->curr_entry)
+ parser->entry_comm = parser->curr_comm;
+ break;
+ }
+
+ case gnucash_parser_t::COMM_NAME:
+ parser->curr_comm->set_name(string(s, len));
+ break;
+
+ case gnucash_parser_t::COMM_PREC:
+ parser->curr_comm->set_precision(len - 1);
+ break;
+
+ case gnucash_parser_t::ENTRY_NUM:
+ parser->curr_entry->code = string(s, len);
+ break;
+
+ case gnucash_parser_t::ENTRY_DATE:
+ parser->curr_entry->_date = parse_datetime(string(s, len));
+ break;
+
+ case gnucash_parser_t::ENTRY_DESC:
+ parser->curr_entry->payee = string(s, len);
+ break;
+
+ case gnucash_parser_t::XACT_STATE:
+ if (*s == 'y')
+ parser->curr_state = transaction_t::CLEARED;
+ else if (*s == 'n')
+ parser->curr_state = transaction_t::UNCLEARED;
+ else
+ parser->curr_state = transaction_t::PENDING;
+ break;
+
+ case gnucash_parser_t::XACT_VALUE: {
+ int precision;
+ assert(parser->entry_comm);
+ parser->curr_value = parser->convert_number(string(s, len), &precision);
+ parser->curr_value.set_commodity(*parser->entry_comm);
+
+ if (precision > parser->entry_comm->precision())
+ parser->entry_comm->set_precision(precision);
+ break;
+ }
+
+ case gnucash_parser_t::XACT_QUANTITY:
+ parser->curr_quant = parser->convert_number(string(s, len));
+ break;
+
+ case gnucash_parser_t::XACT_ACCOUNT: {
+ transaction_t * xact = parser->curr_entry->transactions.back();
+
+ accounts_map::iterator i =
+ parser->accounts_by_id.find(string(s, len));
+ if (i != parser->accounts_by_id.end()) {
+ xact->account = (*i).second;
+ } else {
+ xact->account = parser->curr_journal->find_account("<Unknown>");
+
+ parser->have_error = (string("Could not find account ") +
+ string(s, len));
+ }
+ break;
+ }
+
+ case gnucash_parser_t::XACT_NOTE:
+ parser->curr_entry->transactions.back()->note = string(s, len);
+ break;
+
+ case gnucash_parser_t::NO_ACTION:
+ case gnucash_parser_t::ALMOST_ENTRY_DATE:
+ case gnucash_parser_t::XACT_AMOUNT:
+ break;
+
+ default:
+ assert(0);
+ break;
+ }
+}
+
+bool gnucash_parser_t::test(std::istream& in) const
+{
+ char buf[5];
+ in.read(buf, 5);
+ in.clear();
+ in.seekg(0, std::ios::beg);
+
+ return std::strncmp(buf, "<?xml", 5) == 0;
+}
+
+unsigned int gnucash_parser_t::parse(std::istream& in,
+ journal_t * journal,
+ account_t * master,
+ const string * original_file)
+{
+ char buf[BUFSIZ];
+
+ // 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;
+ action = NO_ACTION;
+ curr_journal = journal;
+ master_account = master ? master : journal->master;
+ curr_account = NULL;
+ curr_entry = NULL;
+ curr_comm = NULL;
+ entry_comm = NULL;
+ curr_state = transaction_t::UNCLEARED;
+
+ instreamp = &in;
+ path = original_file ? *original_file : "<gnucash>";
+ src_idx = journal->sources.size() - 1;
+
+ // GnuCash uses the USD commodity without defining it, which really
+ // means $.
+ commodity_t * usd = commodity_t::find_or_create("$");
+ usd->set_precision(2);
+ usd->add_flags(COMMODITY_STYLE_THOUSANDS);
+
+ offset = 2;
+ 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();
+ beg_line = (XML_GetCurrentLineNumber(parser) - offset) + 1;
+
+ in.getline(buf, BUFSIZ - 1);
+ std::strcat(buf, "\n");
+ if (! XML_Parse(parser, buf, std::strlen(buf), in.eof())) {
+ //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++;
+ const char * msg = XML_ErrorString(XML_GetErrorCode(parser));
+ XML_ParserFree(parser);
+ throw_(parse_exception, msg);
+ }
+
+ if (! have_error.empty()) {
+ //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++;
+#if 0
+ // jww (2007-04-26): What is this doing?
+ parse_error err(have_error);
+ std::cerr << "Error: " << err.what() << std::endl;
+#endif
+ have_error = "";
+ }
+ }
+
+ XML_ParserFree(parser);
+
+ accounts_by_id.clear();
+ curr_account_id.clear();
+
+ return count;
+}
+
+} // namespace ledger
diff --git a/src/gnucash.h b/src/gnucash.h
new file mode 100644
index 00000000..a0d9fb18
--- /dev/null
+++ b/src/gnucash.h
@@ -0,0 +1,75 @@
+#ifndef _GNUCASH_H
+#define _GNUCASH_H
+
+#include "parser.h"
+#include "journal.h"
+
+namespace ledger {
+
+struct gnucash_parser_t : public parser_t
+{
+ typedef std::map<const string, account_t *> accounts_map;
+ typedef std::pair<const 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;
+ 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;
+ string have_error;
+
+ std::istream * instreamp;
+ unsigned int offset;
+ XML_Parser parser;
+ string path;
+ unsigned int src_idx;
+ unsigned long 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,
+ journal_t * journal,
+ account_t * master = NULL,
+ const string * original_file = NULL);
+
+ amount_t convert_number(const string& number, int * precision = NULL);
+};
+
+} // namespace ledger
+
+#endif // _GNUCASH_H
diff --git a/src/journal.cc b/src/journal.cc
new file mode 100644
index 00000000..1f643364
--- /dev/null
+++ b/src/journal.cc
@@ -0,0 +1,667 @@
+#include "journal.h"
+#include "mask.h"
+#if 0
+#ifdef USE_BOOST_PYTHON
+#include "pyinterp.h"
+#endif
+#endif
+
+namespace ledger {
+
+const string version = PACKAGE_VERSION;
+
+bool transaction_t::use_effective_date = false;
+
+transaction_t::~transaction_t()
+{
+ TRACE_DTOR(transaction_t);
+ if (cost) delete cost;
+}
+
+moment_t transaction_t::actual_date() const
+{
+ if (! is_valid_moment(_date) && entry)
+ return entry->actual_date();
+ return _date;
+}
+
+moment_t transaction_t::effective_date() const
+{
+ if (! is_valid_moment(_date_eff) && entry)
+ return entry->effective_date();
+ return _date_eff;
+}
+
+bool transaction_t::valid() const
+{
+ if (! entry) {
+ DEBUG_("ledger.validate", "transaction_t: ! entry");
+ return false;
+ }
+
+ if (state != UNCLEARED && state != CLEARED && state != PENDING) {
+ DEBUG_("ledger.validate", "transaction_t: state is bad");
+ return false;
+ }
+
+ bool found = false;
+ for (transactions_list::const_iterator i = entry->transactions.begin();
+ i != entry->transactions.end();
+ i++)
+ if (*i == this) {
+ found = true;
+ break;
+ }
+ if (! found) {
+ DEBUG_("ledger.validate", "transaction_t: ! found");
+ return false;
+ }
+
+ if (! account) {
+ DEBUG_("ledger.validate", "transaction_t: ! account");
+ return false;
+ }
+
+ if (! amount.valid()) {
+ DEBUG_("ledger.validate", "transaction_t: ! amount.valid()");
+ return false;
+ }
+
+ if (cost && ! cost->valid()) {
+ DEBUG_("ledger.validate", "transaction_t: cost && ! cost->valid()");
+ return false;
+ }
+
+ if (flags & ~0x003f) {
+ DEBUG_("ledger.validate", "transaction_t: flags are bad");
+ return false;
+ }
+
+ return true;
+}
+
+void entry_base_t::add_transaction(transaction_t * xact)
+{
+ transactions.push_back(xact);
+}
+
+bool entry_base_t::remove_transaction(transaction_t * xact)
+{
+ transactions.remove(xact);
+ return true;
+}
+
+bool entry_base_t::finalize()
+{
+ // Scan through and compute the total balance for the entry. This
+ // is used for auto-calculating the value of entries with no cost,
+ // and the per-unit price of unpriced commodities.
+
+ value_t balance;
+
+ bool no_amounts = true;
+ bool saw_null = false;
+ for (transactions_list::const_iterator x = transactions.begin();
+ x != transactions.end();
+ x++)
+ if (! ((*x)->flags & TRANSACTION_VIRTUAL) ||
+ ((*x)->flags & TRANSACTION_BALANCE)) {
+ amount_t * p = (*x)->cost ? (*x)->cost : &(*x)->amount;
+ if (*p) {
+ if (no_amounts) {
+ balance = *p;
+ no_amounts = false;
+ } else {
+ balance += *p;
+ }
+
+ if ((*x)->cost && (*x)->amount.commodity().annotated) {
+ annotated_commodity_t&
+ ann_comm(static_cast<annotated_commodity_t&>
+ ((*x)->amount.commodity()));
+ if (ann_comm.price)
+ balance += ann_comm.price * (*x)->amount.number() - *((*x)->cost);
+ }
+ } else {
+ saw_null = true;
+ }
+ }
+
+ // If it's a null entry, then let the user have their fun
+ if (no_amounts)
+ return true;
+
+ // If there is only one transaction, balance against the basket
+ // account if one has been set.
+
+ if (journal && journal->basket && transactions.size() == 1) {
+ assert(balance.type < value_t::BALANCE);
+ transaction_t * nxact = new transaction_t(journal->basket);
+ // The amount doesn't need to be set because the code below will
+ // balance this transaction against the other.
+ add_transaction(nxact);
+ nxact->flags |= TRANSACTION_CALCULATED;
+ }
+
+ // If the first transaction of a two-transaction entry is of a
+ // different commodity than the other, and it has no per-unit price,
+ // determine its price by dividing the unit count into the value of
+ // the balance. This is done for the last eligible commodity.
+
+ if (! saw_null && balance && balance.type == value_t::BALANCE &&
+ ((balance_t *) balance.data)->amounts.size() == 2) {
+ transactions_list::const_iterator x = transactions.begin();
+ commodity_t& this_comm = (*x)->amount.commodity();
+
+ amounts_map::const_iterator this_bal =
+ ((balance_t *) balance.data)->amounts.find(&this_comm);
+ amounts_map::const_iterator other_bal =
+ ((balance_t *) balance.data)->amounts.begin();
+ if (this_bal == other_bal)
+ other_bal++;
+
+ amount_t per_unit_cost =
+ amount_t((*other_bal).second / (*this_bal).second.number()).unround();
+
+ for (; x != transactions.end(); x++) {
+ if ((*x)->cost || ((*x)->flags & TRANSACTION_VIRTUAL) ||
+ ! (*x)->amount || (*x)->amount.commodity() != this_comm)
+ continue;
+
+ assert((*x)->amount);
+ balance -= (*x)->amount;
+
+ entry_t * entry = dynamic_cast<entry_t *>(this);
+
+ if ((*x)->amount.commodity() &&
+ ! (*x)->amount.commodity().annotated)
+ (*x)->amount.annotate_commodity
+ (per_unit_cost.abs(),
+ entry ? entry->actual_date() : moment_t(),
+ entry ? entry->code : "");
+
+ (*x)->cost = new amount_t(- (per_unit_cost * (*x)->amount.number()));
+ balance += *(*x)->cost;
+ }
+ }
+
+ // Walk through each of the transactions, fixing up any that we
+ // can, and performing any on-the-fly calculations.
+
+ bool empty_allowed = true;
+
+ for (transactions_list::const_iterator x = transactions.begin();
+ x != transactions.end();
+ x++) {
+ if (! (*x)->amount.null() ||
+ (((*x)->flags & TRANSACTION_VIRTUAL) &&
+ ! ((*x)->flags & TRANSACTION_BALANCE)))
+ continue;
+
+ if (! empty_allowed)
+ throw_(exception, "Only one transaction with null amount allowed per entry");
+ empty_allowed = false;
+
+ // If one transaction gives no value at all, its value will become
+ // the inverse of the value of the others. If multiple
+ // commodities are involved, multiple transactions will be
+ // generated to balance them all.
+
+ balance_t * bal = NULL;
+ switch (balance.type) {
+ case value_t::BALANCE_PAIR:
+ bal = &((balance_pair_t *) balance.data)->quantity;
+ // fall through...
+
+ case value_t::BALANCE:
+ if (! bal)
+ bal = (balance_t *) balance.data;
+
+ if (bal->amounts.size() < 2) {
+ balance.cast(value_t::AMOUNT);
+ } else {
+ bool first = true;
+ for (amounts_map::const_iterator i = bal->amounts.begin();
+ i != bal->amounts.end();
+ i++) {
+ amount_t amt = (*i).second.negate();
+
+ if (first) {
+ (*x)->amount = amt;
+ first = false;
+ } else {
+ transaction_t * nxact = new transaction_t((*x)->account);
+ add_transaction(nxact);
+ nxact->flags |= TRANSACTION_CALCULATED;
+ nxact->amount = amt;
+ }
+
+ balance += amt;
+ }
+ break;
+ }
+ // fall through...
+
+ case value_t::AMOUNT:
+ (*x)->amount = ((amount_t *) balance.data)->negate();
+ (*x)->flags |= TRANSACTION_CALCULATED;
+
+ balance += (*x)->amount;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (balance) {
+#if 1
+ throw_(balance_exception, "Entry does not balance");
+#else
+ error * err =
+ new balance_error("Entry does not balance",
+ new entry_context(*this, "While balancing entry:"));
+ err->context.push_front
+ (new value_context(balance, "Unbalanced remainder is:"));
+ throw err;
+#endif
+ }
+
+ return true;
+}
+
+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), data(NULL)
+{
+ TRACE_CTOR(entry_t, "copy");
+ for (transactions_list::const_iterator i = transactions.begin();
+ i != transactions.end();
+ i++)
+ (*i)->entry = this;
+}
+
+bool entry_t::get_state(transaction_t::state_t * state) const
+{
+ bool first = true;
+ bool hetero = false;
+
+ for (transactions_list::const_iterator i = transactions.begin();
+ i != transactions.end();
+ i++) {
+ if (first) {
+ *state = (*i)->state;
+ first = false;
+ }
+ else if (*state != (*i)->state) {
+ hetero = true;
+ break;
+ }
+ }
+
+ return ! hetero;
+}
+
+void entry_t::add_transaction(transaction_t * xact)
+{
+ xact->entry = this;
+ entry_base_t::add_transaction(xact);
+}
+
+bool entry_t::valid() const
+{
+ if (! is_valid_moment(_date) || ! journal) {
+ DEBUG_("ledger.validate", "entry_t: ! _date || ! journal");
+ return false;
+ }
+
+ for (transactions_list::const_iterator i = transactions.begin();
+ i != transactions.end();
+ i++)
+ if ((*i)->entry != this || ! (*i)->valid()) {
+ DEBUG_("ledger.validate", "entry_t: transaction not valid");
+ return false;
+ }
+
+ return true;
+}
+
+void auto_entry_t::extend_entry(entry_base_t& entry, bool post)
+{
+ transactions_list initial_xacts(entry.transactions.begin(),
+ entry.transactions.end());
+
+ for (transactions_list::iterator i = initial_xacts.begin();
+ i != initial_xacts.end();
+ 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++) {
+ amount_t amt;
+ if (! (*t)->amount.commodity()) {
+ if (! post)
+ continue;
+ amt = (*i)->amount * (*t)->amount;
+ } else {
+ if (post)
+ continue;
+ amt = (*t)->amount;
+ }
+
+ account_t * account = (*t)->account;
+ string fullname = account->fullname();
+ assert(! fullname.empty());
+ if (fullname == "$account" || fullname == "@account")
+ account = (*i)->account;
+
+ transaction_t * xact
+ = new transaction_t(account, amt, (*t)->flags | TRANSACTION_AUTO);
+ entry.add_transaction(xact);
+ }
+ }
+ }
+}
+
+account_t::~account_t()
+{
+ TRACE_DTOR(account_t);
+
+ for (accounts_map::iterator i = accounts.begin();
+ i != accounts.end();
+ i++)
+ delete (*i).second;
+}
+
+account_t * account_t::find_account(const string& name,
+ const bool auto_create)
+{
+ accounts_map::const_iterator i = accounts.find(name);
+ if (i != accounts.end())
+ return (*i).second;
+
+ char buf[256];
+
+ string::size_type sep = name.find(':');
+ assert(sep < 256|| sep == string::npos);
+
+ const char * first, * rest;
+ if (sep == string::npos) {
+ first = name.c_str();
+ rest = NULL;
+ } else {
+ std::strncpy(buf, name.c_str(), sep);
+ buf[sep] = '\0';
+
+ first = buf;
+ rest = name.c_str() + sep + 1;
+ }
+
+ account_t * account;
+
+ i = accounts.find(first);
+ if (i == accounts.end()) {
+ if (! auto_create)
+ return NULL;
+
+ account = new account_t(this, first);
+ account->journal = journal;
+
+ std::pair<accounts_map::iterator, bool> result
+ = accounts.insert(accounts_pair(first, account));
+ assert(result.second);
+ } else {
+ account = (*i).second;
+ }
+
+ if (rest)
+ account = account->find_account(rest, auto_create);
+
+ return account;
+}
+
+static inline
+account_t * find_account_re_(account_t * account, const mask_t& regexp)
+{
+ if (regexp.match(account->fullname()))
+ return account;
+
+ for (accounts_map::iterator i = account->accounts.begin();
+ i != account->accounts.end();
+ i++)
+ if (account_t * a = find_account_re_((*i).second, regexp))
+ return a;
+
+ return NULL;
+}
+
+account_t * journal_t::find_account_re(const string& regexp)
+{
+ return find_account_re_(master, mask_t(regexp));
+}
+
+string account_t::fullname() const
+{
+ if (! _fullname.empty()) {
+ return _fullname;
+ } else {
+ const account_t * first = this;
+ string fullname = name;
+
+ while (first->parent) {
+ first = first->parent;
+ if (! first->name.empty())
+ fullname = first->name + ":" + fullname;
+ }
+
+ _fullname = fullname;
+
+ return fullname;
+ }
+}
+
+std::ostream& operator<<(std::ostream& out, const account_t& account)
+{
+ out << account.fullname();
+ return out;
+}
+
+bool account_t::valid() const
+{
+ if (depth > 256 || ! journal) {
+ DEBUG_("ledger.validate", "account_t: depth > 256 || ! journal");
+ return false;
+ }
+
+ for (accounts_map::const_iterator i = accounts.begin();
+ i != accounts.end();
+ i++) {
+ if (this == (*i).second) {
+ DEBUG_("ledger.validate", "account_t: parent refers to itself!");
+ return false;
+ }
+
+ if (! (*i).second->valid()) {
+ DEBUG_("ledger.validate", "account_t: child not valid");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+journal_t::~journal_t()
+{
+ TRACE_DTOR(journal_t);
+
+ assert(master);
+ delete master;
+
+ if (document)
+ delete document;
+
+ // Don't bother unhooking each entry's transactions from the
+ // accounts they refer to, because all accounts are about to
+ // be deleted.
+ for (entries_list::iterator i = entries.begin();
+ i != entries.end();
+ i++)
+ if (! item_pool ||
+ ((char *) *i) < item_pool || ((char *) *i) >= item_pool_end)
+ delete *i;
+ else
+ (*i)->~entry_t();
+
+ for (auto_entries_list::iterator i = auto_entries.begin();
+ i != auto_entries.end();
+ i++)
+ if (! item_pool ||
+ ((char *) *i) < item_pool || ((char *) *i) >= item_pool_end)
+ delete *i;
+ else
+ (*i)->~auto_entry_t();
+
+ for (period_entries_list::iterator i = period_entries.begin();
+ i != period_entries.end();
+ i++)
+ if (! item_pool ||
+ ((char *) *i) < item_pool || ((char *) *i) >= item_pool_end)
+ delete *i;
+ else
+ (*i)->~period_entry_t();
+
+ if (item_pool)
+ delete[] item_pool;
+}
+
+bool journal_t::add_entry(entry_t * entry)
+{
+ entry->journal = this;
+
+ if (! run_hooks(entry_finalize_hooks, *entry, false) ||
+ ! entry->finalize() ||
+ ! run_hooks(entry_finalize_hooks, *entry, true)) {
+ entry->journal = NULL;
+ return false;
+ }
+
+ entries.push_back(entry);
+
+ for (transactions_list::const_iterator i = entry->transactions.begin();
+ i != entry->transactions.end();
+ i++)
+ if ((*i)->cost && (*i)->amount)
+ (*i)->amount.commodity().add_price(entry->date(),
+ *(*i)->cost / (*i)->amount.number());
+
+ return true;
+}
+
+bool journal_t::remove_entry(entry_t * entry)
+{
+ bool found = false;
+ entries_list::iterator i;
+ for (i = entries.begin(); i != entries.end(); i++)
+ if (*i == entry) {
+ found = true;
+ break;
+ }
+ if (! found)
+ return false;
+
+ entries.erase(i);
+ entry->journal = NULL;
+
+ return true;
+}
+
+bool journal_t::valid() const
+{
+ if (! master->valid()) {
+ DEBUG_("ledger.validate", "journal_t: master not valid");
+ return false;
+ }
+
+ for (entries_list::const_iterator i = entries.begin();
+ i != entries.end();
+ i++)
+ if (! (*i)->valid()) {
+ DEBUG_("ledger.validate", "journal_t: entry not valid");
+ return false;
+ }
+
+ for (commodities_map::const_iterator i = commodity_t::commodities.begin();
+ i != commodity_t::commodities.end();
+ i++)
+ if (! (*i).second->valid()) {
+ DEBUG_("ledger.validate", "journal_t: commodity not valid");
+ return false;
+ }
+
+ return true;
+}
+
+void print_entry(std::ostream& out, const entry_base_t& entry_base,
+ const string& prefix)
+{
+ string print_format;
+
+ if (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
+}
+
+#if 0
+void entry_context::describe(std::ostream& out) const throw()
+{
+ if (! desc.empty())
+ out << desc << std::endl;
+
+ print_entry(out, entry, " ");
+}
+
+xact_context::xact_context(const ledger::transaction_t& _xact,
+ const string& desc) throw()
+ : file_context("", 0, desc), xact(_xact)
+{
+ const ledger::strings_list& sources(xact.entry->journal->sources);
+ unsigned int x = 0;
+ for (ledger::strings_list::const_iterator i = sources.begin();
+ i != sources.end();
+ i++, x++)
+ if (x == xact.entry->src_idx) {
+ file = *i;
+ break;
+ }
+ line = xact.beg_line;
+}
+#endif
+
+} // namespace ledger
diff --git a/src/journal.h b/src/journal.h
new file mode 100644
index 00000000..1995e0f3
--- /dev/null
+++ b/src/journal.h
@@ -0,0 +1,471 @@
+#ifndef _JOURNAL_H
+#define _JOURNAL_H
+
+#include "xpath.h"
+
+namespace ledger {
+
+// These flags persist with the object
+#define TRANSACTION_NORMAL 0x0000
+#define TRANSACTION_VIRTUAL 0x0001
+#define TRANSACTION_BALANCE 0x0002
+#define TRANSACTION_AUTO 0x0004
+#define TRANSACTION_BULK_ALLOC 0x0008
+#define TRANSACTION_CALCULATED 0x0010
+
+class entry_t;
+class account_t;
+
+class transaction_t
+{
+ public:
+ enum state_t { UNCLEARED, CLEARED, PENDING };
+
+ entry_t * entry;
+ moment_t _date;
+ moment_t _date_eff;
+ account_t * account;
+ amount_t amount;
+ string amount_expr;
+ amount_t * cost;
+ string cost_expr;
+ state_t state;
+ unsigned short flags;
+ string note;
+ unsigned long beg_pos;
+ unsigned long beg_line;
+ unsigned long end_pos;
+ unsigned long end_line;
+
+ 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) {
+ TRACE_CTOR(transaction_t, "account_t *");
+ }
+ transaction_t(account_t * _account,
+ const amount_t& _amount,
+ unsigned int _flags = TRANSACTION_NORMAL,
+ const string& _note = "")
+ : entry(NULL), account(_account), amount(_amount), cost(NULL),
+ state(UNCLEARED), flags(_flags),
+ note(_note), beg_pos(0), beg_line(0), end_pos(0), end_line(0),
+ data(NULL) {
+ TRACE_CTOR(transaction_t, "account_t *, const amount_t&, unsigned int, const 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) {
+ TRACE_CTOR(transaction_t, "copy");
+ }
+ ~transaction_t();
+
+ moment_t actual_date() const;
+ moment_t effective_date() const;
+ moment_t date() const {
+ if (use_effective_date)
+ return effective_date();
+ else
+ return actual_date();
+ }
+
+ bool operator==(const transaction_t& xact) {
+ return this == &xact;
+ }
+ bool operator!=(const transaction_t& xact) {
+ return ! (*this == xact);
+ }
+
+ bool valid() const;
+};
+
+#if 0
+class xact_context : public file_context {
+ public:
+ const transaction_t& xact;
+
+ xact_context(const transaction_t& _xact,
+ const string& desc = "") throw();
+ virtual ~xact_context() throw() {}
+};
+#endif
+
+class journal_t;
+
+typedef std::list<transaction_t *> transactions_list;
+
+class entry_base_t
+{
+ public:
+ journal_t * journal;
+ unsigned long src_idx;
+ unsigned long beg_pos;
+ unsigned long beg_line;
+ unsigned long end_pos;
+ unsigned long end_line;
+ transactions_list transactions;
+
+ entry_base_t() : journal(NULL),
+ 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)
+ {
+ 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() {
+ TRACE_DTOR(entry_base_t);
+ for (transactions_list::iterator i = transactions.begin();
+ i != transactions.end();
+ i++)
+ if (! ((*i)->flags & TRANSACTION_BULK_ALLOC))
+ delete *i;
+ else
+ (*i)->~transaction_t();
+ }
+
+ bool operator==(const entry_base_t& entry) {
+ return this == &entry;
+ }
+ bool operator!=(const entry_base_t& entry) {
+ return ! (*this == entry);
+ }
+
+ virtual void add_transaction(transaction_t * xact);
+ virtual bool remove_transaction(transaction_t * xact);
+
+ virtual bool finalize();
+ virtual bool valid() const = 0;
+};
+
+class entry_t : public entry_base_t
+{
+ public:
+ moment_t _date;
+ moment_t _date_eff;
+ string code;
+ string payee;
+
+ mutable void * data;
+
+ entry_t() : data(NULL) {
+ TRACE_CTOR(entry_t, "");
+ }
+ entry_t(const entry_t& e);
+
+ virtual ~entry_t() {
+ TRACE_DTOR(entry_t);
+ }
+
+ moment_t actual_date() const {
+ return _date;
+ }
+ moment_t effective_date() const {
+ if (! is_valid_moment(_date_eff))
+ return _date;
+ return _date_eff;
+ }
+ moment_t date() const {
+ if (transaction_t::use_effective_date)
+ return effective_date();
+ else
+ return actual_date();
+ }
+
+ virtual void add_transaction(transaction_t * xact);
+
+ virtual bool valid() const;
+
+ bool get_state(transaction_t::state_t * state) const;
+};
+
+struct entry_finalizer_t {
+ virtual ~entry_finalizer_t() {}
+ virtual bool operator()(entry_t& entry, bool post) = 0;
+};
+
+void print_entry(std::ostream& out, const entry_base_t& entry,
+ const string& prefix = "");
+
+#if 0
+class entry_context : public error_context {
+ public:
+ const entry_base_t& entry;
+
+ entry_context(const entry_base_t& _entry,
+ const string& _desc = "") throw()
+ : error_context(_desc), entry(_entry) {}
+ virtual ~entry_context() throw() {}
+
+ virtual void describe(std::ostream& out) const throw();
+};
+#endif
+
+DECLARE_EXCEPTION(balance_exception);
+
+
+class auto_entry_t : public entry_base_t
+{
+public:
+ xml::xpath_t predicate;
+
+ auto_entry_t() {
+ TRACE_CTOR(auto_entry_t, "");
+ }
+ auto_entry_t(const string& _predicate)
+ : predicate(_predicate) {
+ TRACE_CTOR(auto_entry_t, "const string&");
+ }
+
+ virtual ~auto_entry_t() {
+ TRACE_DTOR(auto_entry_t);
+ }
+
+ virtual void extend_entry(entry_base_t& entry, bool post);
+ virtual bool valid() const {
+ return true;
+ }
+};
+
+struct auto_entry_finalizer_t : public entry_finalizer_t {
+ journal_t * journal;
+ auto_entry_finalizer_t(journal_t * _journal) : journal(_journal) {}
+ virtual bool operator()(entry_t& entry, bool post);
+};
+
+
+class period_entry_t : public entry_base_t
+{
+ public:
+ interval_t period;
+ string period_string;
+
+ period_entry_t() {
+ TRACE_CTOR(period_entry_t, "");
+ }
+ period_entry_t(const string& _period)
+ : period(_period), period_string(_period) {
+ TRACE_CTOR(period_entry_t, "const string&");
+ }
+ period_entry_t(const period_entry_t& e)
+ : entry_base_t(e), period(e.period), period_string(e.period_string) {
+ TRACE_CTOR(period_entry_t, "copy");
+ }
+
+ virtual ~period_entry_t() {
+ TRACE_DTOR(period_entry_t);
+ }
+
+ virtual bool valid() const {
+ return period;
+ }
+};
+
+
+typedef std::map<const string, account_t *> accounts_map;
+typedef std::pair<const string, account_t *> accounts_pair;
+
+class account_t
+{
+ public:
+ typedef unsigned long ident_t;
+
+ journal_t * journal;
+ account_t * parent;
+ string name;
+ string note;
+ unsigned short depth;
+ accounts_map accounts;
+
+ mutable void * data;
+ mutable ident_t ident;
+ mutable string _fullname;
+
+ account_t(account_t * _parent = NULL,
+ const string& _name = "",
+ const string& _note = "")
+ : parent(_parent), name(_name), note(_note),
+ depth(parent ? parent->depth + 1 : 0), data(NULL), ident(0) {
+ TRACE_CTOR(account_t, "account_t *, const string&, const string&");
+ }
+ ~account_t();
+
+ bool operator==(const account_t& account) {
+ return this == &account;
+ }
+ bool operator!=(const account_t& account) {
+ return ! (*this == account);
+ }
+
+ string fullname() const;
+
+ void add_account(account_t * acct) {
+ accounts.insert(accounts_pair(acct->name, acct));
+ acct->journal = journal;
+ }
+ bool remove_account(account_t * acct) {
+ accounts_map::size_type n = accounts.erase(acct->name);
+ acct->journal = NULL;
+ return n > 0;
+ }
+
+ account_t * find_account(const string& name, bool auto_create = true);
+
+ operator string() const {
+ return fullname();
+ }
+
+ bool valid() const;
+
+ friend class journal_t;
+};
+
+std::ostream& operator<<(std::ostream& out, const account_t& account);
+
+
+struct func_finalizer_t : public entry_finalizer_t {
+ typedef bool (*func_t)(entry_t& entry, bool post);
+ func_t func;
+ func_finalizer_t(func_t _func) : func(_func) {}
+ func_finalizer_t(const func_finalizer_t& other) :
+ entry_finalizer_t(), func(other.func) {}
+ virtual bool operator()(entry_t& entry, bool post) {
+ return func(entry, post);
+ }
+};
+
+template <typename T>
+void add_hook(std::list<T>& list, T obj, const bool prepend = false) {
+ if (prepend)
+ list.push_front(obj);
+ else
+ list.push_back(obj);
+}
+
+template <typename T>
+void remove_hook(std::list<T>& list, T obj) {
+ list.remove(obj);
+}
+
+template <typename T, typename Data>
+bool run_hooks(std::list<T>& list, Data& item, bool post) {
+ for (typename std::list<T>::const_iterator i = list.begin();
+ i != list.end();
+ i++)
+ if (! (*(*i))(item, post))
+ return false;
+ return true;
+}
+
+
+typedef std::list<entry_t *> entries_list;
+typedef std::list<auto_entry_t *> auto_entries_list;
+typedef std::list<period_entry_t *> period_entries_list;
+typedef std::list<string> strings_list;
+
+class session_t;
+
+class journal_t
+{
+ public:
+ session_t * session;
+ account_t * master;
+ account_t * basket;
+ entries_list entries;
+ strings_list sources;
+ string price_db;
+ 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 accounts_map accounts_cache;
+
+ std::list<entry_finalizer_t *> entry_finalize_hooks;
+
+ journal_t(session_t * _session)
+ : session(_session), basket(NULL),
+ item_pool(NULL), item_pool_end(NULL), document(NULL) {
+ TRACE_CTOR(journal_t, "");
+ master = new account_t(NULL, "");
+ master->journal = this;
+ }
+ ~journal_t();
+
+ bool operator==(const journal_t& journal) {
+ return this == &journal;
+ }
+ bool operator!=(const journal_t& journal) {
+ return ! (*this == journal);
+ }
+
+ void add_account(account_t * acct) {
+ master->add_account(acct);
+ acct->journal = this;
+ }
+ bool remove_account(account_t * acct) {
+ return master->remove_account(acct);
+ acct->journal = NULL;
+ }
+
+ account_t * find_account(const string& name, bool auto_create = true) {
+ accounts_map::iterator c = accounts_cache.find(name);
+ if (c != accounts_cache.end())
+ return (*c).second;
+
+ account_t * account = master->find_account(name, auto_create);
+ accounts_cache.insert(accounts_pair(name, account));
+ account->journal = this;
+ return account;
+ }
+ account_t * find_account_re(const string& regexp);
+
+ bool add_entry(entry_t * entry);
+ bool remove_entry(entry_t * entry);
+
+ void add_entry_finalizer(entry_finalizer_t * finalizer) {
+ add_hook<entry_finalizer_t *>(entry_finalize_hooks, finalizer);
+ }
+ void remove_entry_finalizer(entry_finalizer_t * finalizer) {
+ remove_hook<entry_finalizer_t *>(entry_finalize_hooks, finalizer);
+ }
+
+ bool valid() const;
+};
+
+inline void extend_entry_base(journal_t * journal, entry_base_t& entry,
+ bool post) {
+ for (auto_entries_list::iterator i = journal->auto_entries.begin();
+ i != journal->auto_entries.end();
+ i++)
+ (*i)->extend_entry(entry, post);
+}
+
+inline bool auto_entry_finalizer_t::operator()(entry_t& entry, bool post) {
+ extend_entry_base(journal, entry, post);
+ return true;
+}
+
+extern const string version;
+
+} // namespace ledger
+
+#endif // _JOURNAL_H
diff --git a/src/ledger.h b/src/ledger.h
new file mode 100644
index 00000000..2122ece3
--- /dev/null
+++ b/src/ledger.h
@@ -0,0 +1,42 @@
+#ifndef _LEDGER_H
+#define _LEDGER_H
+
+//////////////////////////////////////////////////////////////////////
+//
+// Ledger Accounting Tool
+//
+// A command-line tool for general double-entry accounting.
+//
+// Copyright (c) 2003,2004 John Wiegley <johnw@newartisans.com>
+//
+
+#include <amount.h>
+#include <balance.h>
+#include <value.h>
+#include <xml.h>
+#include <xpath.h>
+#include <format.h>
+#include <quotes.h>
+
+#include <session.h>
+#include <journal.h>
+#include <parser.h>
+#include <textual.h>
+#include <binary.h>
+#include <gnucash.h>
+#include <qif.h>
+#include <ofx.h>
+
+#include <report.h>
+#include <transform.h>
+
+#include <register.h>
+
+#if 0
+#include <emacs.h>
+#include <csv.h>
+#include <derive.h>
+#include <reconcile.h>
+#endif
+
+#endif // _LEDGER_H
diff --git a/src/main.cc b/src/main.cc
new file mode 100644
index 00000000..0ca984c3
--- /dev/null
+++ b/src/main.cc
@@ -0,0 +1,484 @@
+#if defined(USE_BOOST_PYTHON)
+#include <pyledger.h>
+#else
+#include <ledger.h>
+#endif
+#include <option.h>
+
+#include "acconf.h"
+
+#ifdef HAVE_UNIX_PIPES
+#include <sys/types.h>
+#include <sys/wait.h>
+#include "fdstream.hpp"
+#endif
+
+using namespace ledger;
+
+#if 0
+class print_addr : public repitem_t::select_callback_t {
+ virtual void operator()(repitem_t * item) {
+ std::cout << item << std::endl;
+ }
+};
+#endif
+
+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<string> args;
+ process_arguments(argc - 1, argv + 1, false, report, args);
+
+ if (args.empty()) {
+#if 0
+ help(std::cerr);
+#endif
+ return 1;
+ }
+ strings_list::iterator arg = args.begin();
+
+ if (session.cache_file == "<none>")
+ session.use_cache = false;
+ else
+ session.use_cache = session.data_file.empty() && session.price_db.empty();
+
+ DEBUG_("ledger.session.cache", "1. use_cache = " << session.use_cache);
+
+ // Process the environment settings
+
+ TRACE_START(environment, 1, "Processed environment variables");
+ process_environment(const_cast<const char **>(envp), "LEDGER_", report);
+ TRACE_FINISH(environment, 1);
+
+ const char * p = std::getenv("HOME");
+ string home = p ? p : "";
+
+ if (session.init_file.empty())
+ session.init_file = home + "/.ledgerrc";
+ if (session.price_db.empty())
+ session.price_db = home + "/.pricedb";
+
+ if (session.cache_file.empty())
+ session.cache_file = home + "/.ledger-cache";
+
+ if (session.data_file == session.cache_file)
+ session.use_cache = false;
+
+ DEBUG_("ledger.session.cache", "2. use_cache = " << session.use_cache);
+
+ INFO("Initialization file is " << session.init_file);
+ INFO("Price database is " << session.price_db);
+ INFO("Binary cache is " << session.cache_file);
+ INFO("Journal file is " << session.data_file);
+
+ if (! session.use_cache)
+ INFO("Binary cache mechanism will not be used");
+
+ // Read the command word and create a command object based on it
+
+ string verb = *arg++;
+
+ std::auto_ptr<xml::xpath_t::functor_t> command;
+
+ if (verb == "register" || verb == "reg" || verb == "r") {
+#if 1
+ command.reset(new register_command);
+#else
+ command = new format_command
+ ("register", either_or(report->format_string,
+ report->session->register_format));
+#endif
+ }
+#if 0
+ 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.reset(new xml_command);
+ else if (verb == "expr")
+ ;
+ else if (verb == "xpath")
+ ;
+ else if (verb == "parse") {
+ xml::xpath_t expr(*arg);
+
+ IF_INFO() {
+ std::cout << "Value expression tree:" << std::endl;
+ expr.dump(std::cout);
+ std::cout << std::endl;
+ std::cout << "Value expression parsed was:" << std::endl;
+ expr.write(std::cout);
+ std::cout << std::endl << std::endl;
+ std::cout << "Result of calculation: ";
+ }
+
+ std::cout << expr.calc((xml::document_t *)NULL, report).
+ strip_annotations() << std::endl;
+
+ return 0;
+ }
+ else {
+ char buf[128];
+ std::strcpy(buf, "command_");
+ std::strcat(buf, verb.c_str());
+
+ // jww (2007-04-19): This is an error, since command is an
+ // auto_ptr!
+ if (xml::xpath_t::op_t * def = report->lookup(buf))
+ command.reset(def->functor_obj());
+
+ if (! command.get())
+ throw_(exception, string("Unrecognized command '") + verb + "'");
+ }
+
+ // Parse the initialization file, which can only be textual; then
+ // parse the journal data.
+
+ session.read_init();
+
+ INFO_START(journal, "Read journal file");
+ journal_t * journal = session.read_data(report->account);
+ INFO_FINISH(journal);
+
+ TRACE_FINISH(entry_text, 1);
+ TRACE_FINISH(entry_date, 1);
+ TRACE_FINISH(entry_details, 1);
+ TRACE_FINISH(entry_xacts, 1);
+ TRACE_FINISH(entries, 1);
+ TRACE_FINISH(parsing_total, 1);
+
+ // Configure the output stream
+
+#ifdef HAVE_UNIX_PIPES
+ int status, pfd[2]; // Pipe file descriptors
+#endif
+ std::ostream * out = &std::cout;
+
+ if (! report->output_file.empty()) {
+ out = new std::ofstream(report->output_file.c_str());
+ }
+#ifdef HAVE_UNIX_PIPES
+ else if (! report->pager.empty()) {
+ status = pipe(pfd);
+ if (status == -1)
+ throw_(exception, "Failed to create pipe");
+
+ status = fork();
+ if (status < 0) {
+ throw_(exception, "Failed to fork child process");
+ }
+ else if (status == 0) { // child
+ const char *arg0;
+
+ // Duplicate pipe's reading end into stdin
+ status = dup2(pfd[0], STDIN_FILENO);
+ if (status == -1)
+ perror("dup2");
+
+ // Close unuseful file descriptors: the pipe's writing and
+ // reading ends (the latter is not needed anymore, after the
+ // duplication).
+ close(pfd[1]);
+ close(pfd[0]);
+
+ // Find command name: its the substring starting right of the
+ // rightmost '/' character in the pager pathname. See manpage
+ // for strrchr.
+ arg0 = std::strrchr(report->pager.c_str(), '/');
+ if (arg0)
+ arg0++;
+ else
+ arg0 = report->pager.c_str(); // No slashes in pager.
+
+ execlp(report->pager.c_str(), arg0, (char *)0);
+ perror("execl");
+ exit(1);
+ }
+ else { // parent
+ close(pfd[0]);
+ out = new boost::fdostream(pfd[1]);
+ }
+ }
+#endif
+
+ // Are we handling the expr commands? Do so now.
+
+ if (verb == "expr") {
+ xml::xpath_t expr(*arg);
+
+ IF_INFO() {
+ *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: ";
+ }
+
+ *out << expr.calc((xml::document_t *)NULL, report).
+ strip_annotations() << std::endl;
+
+ return 0;
+ }
+ 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;
+ }
+
+ // Create the an argument scope containing the report command's
+ // arguments, and then invoke the command.
+
+ std::auto_ptr<xml::xpath_t::scope_t> locals
+ (new xml::xpath_t::scope_t(report, xml::xpath_t::scope_t::ARGUMENT));
+
+ locals->args = new value_t::sequence_t;
+ locals->args.push_back(out);
+ locals->args.push_back(journal->document);
+
+ if (command->wants_args) {
+ for (strings_list::iterator i = args.begin();
+ i != args.end();
+ i++)
+ locals->args.push_back(*i);
+ } else {
+ 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 0
+ // jww (2006-09-21): Escape the \ in these strings!
+
+ if (! regexps[3].empty())
+ report->transforms.push_front
+ (new remove_transform
+ (string("//entry[payee =~ /(") + regexps[3] + ")/]"));
+
+ if (! regexps[2].empty())
+ report->transforms.push_front
+ (new select_transform
+ (string("//entry[payee =~ /(") + regexps[2] + ")/]"));
+
+ if (! regexps[1].empty())
+ report->transforms.push_front
+ (new remove_transform
+ (string("//xact[account =~ /(") + regexps[1] + ")/]"));
+
+ if (! regexps[0].empty())
+ report->transforms.push_front
+ (new select_transform
+ (string("//xact[account =~ /(") + regexps[0] + ")/]"));
+#endif
+ }
+
+ INFO_START(transforms, "Applied transforms");
+ report->apply_transforms(journal->document);
+ INFO_FINISH(transforms);
+
+ INFO_START(command, "Did user command '" << verb << "'");
+ value_t temp;
+ (*command)(temp, locals.get());
+ INFO_FINISH(command);
+
+ // Write out the binary cache, if need be
+
+ if (session.use_cache && session.cache_dirty &&
+ ! session.cache_file.empty()) {
+ TRACE_START(binary_cache, 1, "Wrote binary journal file");
+
+ std::ofstream stream(session.cache_file.c_str());
+#if 0
+ write_binary_journal(stream, journal);
+#endif
+
+ TRACE_FINISH(binary_cache, 1);
+ }
+
+#if defined(FREE_MEMORY)
+ // Cleanup memory -- if this is a beta or development build.
+
+ if (! report->output_file.empty())
+ delete out;
+#endif
+
+ // If the user specified a pager, wait for it to exit now
+
+#ifdef HAVE_UNIX_PIPES
+ if (report->output_file.empty() && ! report->pager.empty()) {
+ delete out;
+ close(pfd[1]);
+
+ // Wait for child to finish
+ wait(&status);
+ if (status & 0xffff != 0)
+ throw_(exception, "Something went wrong in the pager");
+ }
+#endif
+
+ return 0;
+}
+
+int main(int argc, char * argv[], char * envp[])
+{
+ int status = 1;
+
+ for (int i = 1; i < argc; i++)
+ if (argv[i][0] == '-') {
+#if defined(VERIFY_ON)
+ if (std::strcmp(argv[i], "--verify") == 0)
+ ledger::verify_enabled = true;
+#endif
+#if defined(DEBUG_ON)
+ if (i + 1 < argc && std::strcmp(argv[i], "--debug") == 0) {
+ ledger::_log_level = LOG_DEBUG;
+ ledger::_log_category = argv[i + 1];
+ i++;
+ }
+#endif
+#if defined(TRACING_ON)
+ if (i + 1 < argc && std::strcmp(argv[i], "--trace") == 0) {
+ ledger::_log_level = LOG_TRACE;
+ ledger::_trace_level = std::atoi(argv[i + 1]);
+ i++;
+ }
+#endif
+ }
+
+ try {
+ std::ios::sync_with_stdio(false);
+
+ ledger::initialize();
+
+#if ! defined(FULL_DEBUG)
+ ledger::do_cleanup = false;
+#endif
+ INFO("Ledger starting");
+
+ 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::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()));
+
+ status = read_and_report(report.get(), argc, argv, envp);
+
+ if (! ledger::do_cleanup) {
+ report.release();
+ session.release();
+ }
+ }
+#if 0
+ catch (error * err) {
+ std::cout.flush();
+ // Push a null here since there's no file context
+ if (err->context.empty() ||
+ ! dynamic_cast<xact_context *>(err->context.front()))
+ err->context.push_front(new error_context(""));
+ err->reveal_context(std::cerr, "Error");
+ std::cerr << err->what() << std::endl;
+ delete err;
+ }
+ catch (fatal * err) {
+ std::cout.flush();
+ // Push a null here since there's no file context
+ if (err->context.empty() ||
+ ! dynamic_cast<xact_context *>(err->context.front()))
+ err->context.push_front(new error_context(""));
+ err->reveal_context(std::cerr, "Fatal");
+ std::cerr << err->what() << std::endl;
+ delete err;
+ }
+#endif
+ catch (const std::exception& err) {
+ std::cout.flush();
+ std::cerr << "Error: " << err.what() << std::endl;
+ }
+ catch (int _status) {
+ status = _status;
+ }
+
+ if (ledger::do_cleanup)
+ ledger::shutdown();
+
+ return status;
+}
+
+// main.cc ends here.
diff --git a/src/mask.cc b/src/mask.cc
new file mode 100644
index 00000000..02cac880
--- /dev/null
+++ b/src/mask.cc
@@ -0,0 +1,24 @@
+#include "mask.h"
+
+namespace ledger {
+
+mask_t::mask_t(const string& pat) : exclude(false)
+{
+ const char * p = pat.c_str();
+
+ if (*p == '-') {
+ exclude = true;
+ p++;
+ while (std::isspace(*p))
+ p++;
+ }
+ else if (*p == '+') {
+ p++;
+ while (std::isspace(*p))
+ p++;
+ }
+
+ expr.assign(p);
+}
+
+} // namespace ledger
diff --git a/src/mask.h b/src/mask.h
new file mode 100644
index 00000000..82634c19
--- /dev/null
+++ b/src/mask.h
@@ -0,0 +1,26 @@
+#ifndef _MASK_H
+#define _MASK_H
+
+#include "utils.h"
+
+#include <boost/regex.hpp>
+
+namespace ledger {
+
+class mask_t
+{
+ public:
+ bool exclude;
+ boost::regex expr;
+
+ explicit mask_t(const string& pattern);
+ mask_t(const mask_t& m) : exclude(m.exclude), expr(m.expr) {}
+
+ bool match(const string& str) const {
+ return boost::regex_match(str, expr) && ! exclude;
+ }
+};
+
+} // namespace ledger
+
+#endif // _MASK_H
diff --git a/src/ofx.cc b/src/ofx.cc
new file mode 100644
index 00000000..f0bce1f2
--- /dev/null
+++ b/src/ofx.cc
@@ -0,0 +1,218 @@
+#include "ofx.h"
+
+namespace ledger {
+
+typedef std::map<const string, account_t *> accounts_map;
+typedef std::pair<const string, account_t *> accounts_pair;
+
+typedef std::map<const string, commodity_t *> commodities_map;
+typedef std::pair<const string, commodity_t *> commodities_pair;
+
+journal_t * curr_journal;
+accounts_map ofx_accounts;
+commodities_map ofx_account_currencies;
+commodities_map ofx_securities;
+account_t * master_account;
+
+int ofx_proc_statement_cb(struct OfxStatementData data, void * statement_data)
+{
+}
+
+int ofx_proc_account_cb(struct OfxAccountData data, void * account_data)
+{
+ if (! data.account_id_valid)
+ return -1;
+
+ DEBUG_("ledger.ofx.parse", "account " << data.account_name);
+ account_t * account = new account_t(master_account, data.account_name);
+ curr_journal->add_account(account);
+ ofx_accounts.insert(accounts_pair(data.account_id, account));
+
+ if (data.currency_valid) {
+ commodity_t * commodity = commodity_t::find_or_create(data.currency);
+ commodity->add_flags(COMMODITY_STYLE_SUFFIXED | COMMODITY_STYLE_SEPARATED);
+
+ commodities_map::iterator i = ofx_account_currencies.find(data.account_id);
+ if (i == ofx_account_currencies.end())
+ ofx_account_currencies.insert(commodities_pair(data.account_id,
+ commodity));
+ }
+
+ return 0;
+}
+
+int ofx_proc_transaction_cb(struct OfxTransactionData data,
+ void * transaction_data)
+{
+ if (! data.account_id_valid || ! data.units_valid)
+ return -1;
+
+ accounts_map::iterator i = ofx_accounts.find(data.account_id);
+ assert(i != ofx_accounts.end());
+ account_t * account = (*i).second;
+
+ entry_t * entry = new entry_t;
+
+ entry->add_transaction(new transaction_t(account));
+ transaction_t * xact = entry->transactions.back();
+
+ // get the account's default currency
+ commodities_map::iterator ac = ofx_account_currencies.find(data.account_id);
+ assert(ac != ofx_account_currencies.end());
+ commodity_t * default_commodity = (*ac).second;
+
+ std::ostringstream stream;
+ stream << - data.units;
+
+ // jww (2005-02-09): what if the amount contains fees?
+
+ if (data.unique_id_valid) {
+ commodities_map::iterator s = ofx_securities.find(data.unique_id);
+ assert(s != ofx_securities.end());
+ xact->amount = stream.str() + " " + (*s).second->base_symbol();
+ } else {
+ xact->amount = stream.str() + " " + default_commodity->base_symbol();
+ }
+
+ if (data.unitprice_valid && data.unitprice != 1.0) {
+ std::ostringstream cstream;
+ stream << - data.unitprice << " " << default_commodity->base_symbol();
+ xact->cost = new amount_t(stream.str());
+ }
+
+ DEBUG_("ofx.parse", "xact " << xact->amount << " from " << *xact->account);
+
+ if (data.date_initiated_valid)
+ entry->_date = data.date_initiated;
+ else if (data.date_posted_valid)
+ entry->_date = data.date_posted;
+
+ if (data.check_number_valid)
+ entry->code = data.check_number;
+ else if (data.reference_number_valid)
+ entry->code = data.reference_number;
+
+ if (data.name_valid)
+ entry->payee = data.name;
+
+ if (data.memo_valid)
+ xact->note = data.memo;
+
+ // jww (2005-02-09): check for fi_id_corrected? or is this handled
+ // by the library?
+
+ // Balance all entries into <Unknown>, since it is not specified.
+ account = curr_journal->find_account("<Unknown>");
+ entry->add_transaction(new transaction_t(account));
+
+ 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";
+#endif
+ delete entry;
+ return -1;
+ }
+ return 0;
+}
+
+int ofx_proc_security_cb(struct OfxSecurityData data, void * security_data)
+{
+ if (! data.unique_id_valid)
+ return -1;
+
+ string symbol;
+ if (data.ticker_valid)
+ symbol = data.ticker;
+ else if (data.currency_valid)
+ symbol = data.currency;
+ else
+ return -1;
+
+ commodity_t * commodity = commodity_t::find_or_create(symbol);
+ commodity->add_flags(COMMODITY_STYLE_SUFFIXED | COMMODITY_STYLE_SEPARATED);
+
+ if (data.secname_valid)
+ commodity->set_name(data.secname);
+
+ if (data.memo_valid)
+ commodity->set_note(data.memo);
+
+ commodities_map::iterator i = ofx_securities.find(data.unique_id);
+ if (i == ofx_securities.end()) {
+ DEBUG_("ledger.ofx.parse", "security " << symbol);
+ ofx_securities.insert(commodities_pair(data.unique_id, commodity));
+ }
+
+ // jww (2005-02-09): What is the commodity for data.unitprice?
+ if (data.date_unitprice_valid && data.unitprice_valid) {
+ DEBUG_("ledger.ofx.parse", " price " << data.unitprice);
+ commodity->add_price(data.date_unitprice, amount_t(data.unitprice));
+ }
+
+ return 0;
+}
+
+int ofx_proc_status_cb(struct OfxStatusData data, void * status_data)
+{
+}
+
+bool ofx_parser_t::test(std::istream& in) const
+{
+ char buf[80];
+
+ in.getline(buf, 79);
+ if (std::strncmp(buf, "OFXHEADER", 9) == 0) {
+ in.clear();
+ in.seekg(0, std::ios::beg);
+ return true;
+ }
+ else if (std::strncmp(buf, "<?xml", 5) != 0) {
+ in.clear();
+ in.seekg(0, std::ios::beg);
+ return false;
+ }
+
+ in.getline(buf, 79);
+ if (std::strncmp(buf, "<?OFX", 5) != 0 &&
+ std::strncmp(buf, "<?ofx", 5) != 0) {
+ in.clear();
+ in.seekg(0, std::ios::beg);
+ return false;
+ }
+
+ in.clear();
+ in.seekg(0, std::ios::beg);
+ return true;
+}
+
+unsigned int ofx_parser_t::parse(std::istream& in,
+ journal_t * journal,
+ account_t * master,
+ const string * original_file)
+{
+ if (! original_file)
+ return 0;
+
+ curr_journal = journal;
+ master_account = master ? master : journal->master;
+
+ LibofxContextPtr libofx_context = libofx_get_new_context();
+
+ ofx_set_statement_cb (libofx_context, ofx_proc_statement_cb, 0);
+ ofx_set_account_cb (libofx_context, ofx_proc_account_cb, 0);
+ ofx_set_transaction_cb(libofx_context, ofx_proc_transaction_cb, 0);
+ ofx_set_security_cb (libofx_context, ofx_proc_security_cb, 0);
+ ofx_set_status_cb (libofx_context, ofx_proc_status_cb, 0);
+
+ // The processing is done by way of callbacks, which are all defined
+ // above.
+ libofx_proc_file(libofx_context, original_file->c_str(), AUTODETECT);
+
+ libofx_free_context(libofx_context);
+
+ return 1; // jww (2005-02-09): count;
+}
+
+} // namespace ledger
diff --git a/src/ofx.h b/src/ofx.h
new file mode 100644
index 00000000..54713c17
--- /dev/null
+++ b/src/ofx.h
@@ -0,0 +1,21 @@
+#ifndef _OFX_H
+#define _OFX_H
+
+#include "parser.h"
+
+namespace ledger {
+
+class ofx_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 string * original_file = NULL);
+};
+
+} // namespace ledger
+
+#endif // _OFX_H
diff --git a/src/option.cc b/src/option.cc
new file mode 100644
index 00000000..222baf92
--- /dev/null
+++ b/src/option.cc
@@ -0,0 +1,219 @@
+#include "option.h"
+
+#if 0
+#ifdef USE_BOOST_PYTHON
+static ledger::option_t * find_option(const string& name);
+#endif
+#endif
+
+namespace ledger {
+
+namespace {
+ xml::xpath_t::op_t * find_option(xml::xpath_t::scope_t * scope,
+ const 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';
+
+ return scope->lookup(buf);
+ }
+
+ xml::xpath_t::op_t * find_option(xml::xpath_t::scope_t * scope,
+ const char letter)
+ {
+ char buf[9];
+ std::strcpy(buf, "option_");
+ buf[7] = letter;
+ buf[8] = '\0';
+
+ return scope->lookup(buf);
+ }
+
+ void process_option(xml::xpath_t::functor_t * opt, xml::xpath_t::scope_t * scope,
+ const char * arg)
+ {
+#if 0
+ try {
+#endif
+ std::auto_ptr<xml::xpath_t::scope_t> args;
+ if (arg) {
+ args.reset(new xml::xpath_t::scope_t(scope, xml::xpath_t::scope_t::ARGUMENT));
+ args->args.set_string(arg);
+ }
+
+ value_t temp;
+ (*opt)(temp, args.get());
+#if 0
+ }
+ catch (error * err) {
+ err->context.push_back
+ (new error_context
+ (string("While parsing option '--") + opt->long_opt +
+ "'" + (opt->short_opt != '\0' ?
+ (string(" (-") + opt->short_opt + "):") : ":")));
+ throw err;
+ }
+#endif
+ }
+}
+
+bool process_option(const string& name, xml::xpath_t::scope_t * scope,
+ const char * arg)
+{
+ std::auto_ptr<xml::xpath_t::op_t> opt(find_option(scope, name));
+ if (opt.get()) {
+ xml::xpath_t::functor_t * def = opt->functor_obj();
+ if (def) {
+ process_option(def, scope, arg);
+ return true;
+ }
+ }
+ return false;
+}
+
+void process_environment(const char ** envp, const 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 == '=') {
+#if 0
+ try {
+#endif
+ if (! process_option(buf, scope, q + 1))
+#if 0
+ throw new option_error("unknown option")
+#endif
+ ;
+#if 0
+ }
+ catch (error * err) {
+ err->context.push_back
+ (new error_context
+ (string("While parsing environment variable option '") +
+ *p + "':"));
+ throw err;
+ }
+#endif
+ }
+ }
+}
+
+void process_arguments(int argc, char ** argv, const bool anywhere,
+ xml::xpath_t::scope_t * scope,
+ std::list<string>& args)
+{
+ for (char ** i = argv; *i; i++) {
+ if ((*i)[0] != '-') {
+ if (anywhere) {
+ args.push_back(*i);
+ continue;
+ } else {
+ for (; *i; i++)
+ args.push_back(*i);
+ break;
+ }
+ }
+
+ // --long-option or -s
+ again:
+ if ((*i)[1] == '-') {
+ if ((*i)[2] == '\0')
+ break;
+
+ char * name = *i + 2;
+ char * value = NULL;
+ if (char * p = std::strchr(name, '=')) {
+ *p++ = '\0';
+ value = p;
+ }
+
+ std::auto_ptr<xml::xpath_t::op_t> opt(find_option(scope, name));
+ if (! opt.get())
+ throw_(option_exception, "illegal option --" << name);
+
+ xml::xpath_t::functor_t * def = opt->functor_obj();
+ if (! def)
+ throw_(option_exception, "illegal option --" << name);
+
+ if (def->wants_args && value == NULL) {
+ value = *++i;
+ if (value == NULL)
+ throw_(option_exception, "missing option argument for --" << name);
+ }
+ process_option(def, scope, value);
+ }
+ else if ((*i)[1] == '\0') {
+ throw_(option_exception, "illegal option -");
+ }
+ else {
+ std::list<xml::xpath_t::op_t *> option_queue;
+
+ int x = 1;
+ for (char c = (*i)[x]; c != '\0'; x++, c = (*i)[x]) {
+ xml::xpath_t::op_t * opt = find_option(scope, c);
+ if (! opt)
+ throw_(option_exception, "illegal option -" << c);
+
+ xml::xpath_t::functor_t * def = opt->functor_obj();
+ if (! def)
+ throw_(option_exception, "illegal option -" << c);
+
+ option_queue.push_back(opt);
+ }
+
+ for (std::list<xml::xpath_t::op_t *>::iterator
+ o = option_queue.begin();
+ o != option_queue.end();
+ o++) {
+ char * value = NULL;
+
+ xml::xpath_t::functor_t * def = (*o)->functor_obj();
+ assert(def);
+
+ if (def->wants_args) {
+ value = *++i;
+ if (value == NULL)
+ throw_(option_exception, "missing option argument for -" <<
+#if 0
+ def->short_opt
+#else
+ '?'
+#endif
+ );
+ }
+ process_option(def, scope, value);
+
+ delete *o;
+ }
+ }
+
+ next:
+ ;
+ }
+}
+
+} // namespace ledger
diff --git a/src/option.h b/src/option.h
new file mode 100644
index 00000000..c9664ae3
--- /dev/null
+++ b/src/option.h
@@ -0,0 +1,22 @@
+#ifndef _OPTION_H
+#define _OPTION_H
+
+#include "xpath.h"
+
+namespace ledger {
+
+bool process_option(const string& name, xml::xpath_t::scope_t * scope,
+ const char * arg = NULL);
+
+void process_environment(const char ** envp, const 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<string>& args);
+
+DECLARE_EXCEPTION(option_exception);
+
+} // namespace ledger
+
+#endif // _OPTION_H
diff --git a/src/parser.h b/src/parser.h
new file mode 100644
index 00000000..2bdc4622
--- /dev/null
+++ b/src/parser.h
@@ -0,0 +1,86 @@
+#ifndef _PARSER_H
+#define _PARSER_H
+
+#include "utils.h"
+
+namespace ledger {
+
+class account_t;
+class journal_t;
+
+class parser_t
+{
+ public:
+ virtual ~parser_t() {}
+
+ virtual bool test(std::istream& in) const = 0;
+
+ virtual unsigned int parse(std::istream& in,
+ journal_t * journal,
+ account_t * master = NULL,
+ const string * original_file = NULL) = 0;
+};
+
+DECLARE_EXCEPTION(parse_exception);
+
+/************************************************************************
+ *
+ * General utility parsing functions
+ */
+
+inline char * skip_ws(char * ptr) {
+ while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n')
+ ptr++;
+ return ptr;
+}
+
+inline char peek_next_nonws(std::istream& in) {
+ char c = in.peek();
+ while (! in.eof() && std::isspace(c)) {
+ in.get(c);
+ c = in.peek();
+ }
+ return c;
+}
+
+#define READ_INTO(str, targ, size, var, cond) { \
+ char * _p = targ; \
+ var = str.peek(); \
+ while (! str.eof() && var != '\n' && (cond) && _p - targ < size) { \
+ str.get(var); \
+ if (str.eof()) \
+ break; \
+ if (var == '\\') { \
+ str.get(var); \
+ if (in.eof()) \
+ break; \
+ } \
+ *_p++ = var; \
+ var = str.peek(); \
+ } \
+ *_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'; \
+}
+
+} // namespace ledger
+
+#endif // _PARSER_H
diff --git a/src/py_amount.cc b/src/py_amount.cc
new file mode 100644
index 00000000..1d9f9255
--- /dev/null
+++ b/src/py_amount.cc
@@ -0,0 +1,245 @@
+#include "pyinterp.h"
+#include "amount.h"
+
+using namespace boost::python;
+
+namespace ledger {
+
+int py_amount_quantity(amount_t& amount)
+{
+ std::ostringstream quant;
+ amount.print_quantity(quant);
+ return std::atol(quant.str().c_str());
+}
+
+void py_parse_1(amount_t& amount, const string& str,
+ unsigned char flags) {
+ amount.parse(str, flags);
+}
+void py_parse_2(amount_t& amount, const string& str) {
+ amount.parse(str);
+}
+
+amount_t py_round_1(amount_t& amount, unsigned int prec) {
+ return amount.round(prec);
+}
+amount_t py_round_2(amount_t& amount) {
+ return amount.round();
+}
+
+struct commodity_updater_wrap : public commodity_base_t::updater_t
+{
+ PyObject * self;
+ commodity_updater_wrap(PyObject * self_) : self(self_) {}
+
+ virtual void operator()(commodity_base_t& commodity,
+ const moment_t& moment,
+ const moment_t& date,
+ const moment_t& last,
+ amount_t& price) {
+ call_method<void>(self, "__call__", commodity, moment, date, last, price);
+ }
+};
+
+commodity_t * py_find_commodity(const string& symbol)
+{
+ return commodity_t::find(symbol);
+}
+
+#define EXC_TRANSLATOR(type) \
+ void exc_translate_ ## type(const type& err) { \
+ PyErr_SetString(PyExc_ArithmeticError, err.what()); \
+ }
+
+EXC_TRANSLATOR(amount_exception)
+
+void export_amount()
+{
+ scope().attr("AMOUNT_PARSE_NO_MIGRATE") = AMOUNT_PARSE_NO_MIGRATE;
+ scope().attr("AMOUNT_PARSE_NO_REDUCE") = AMOUNT_PARSE_NO_REDUCE;
+
+ class_< amount_t > ("amount")
+ .def(init<amount_t>())
+ .def(init<std::string>())
+ .def(init<char *>())
+ .def(init<long>())
+ .def(init<double>())
+
+ .def(self += self)
+ .def(self += long())
+ .def(self += double())
+
+ .def(self + self)
+ .def(self + long())
+ .def(long() + self)
+ .def(self + double())
+ .def(double() + self)
+
+ .def(self -= self)
+ .def(self -= long())
+ .def(self -= double())
+
+ .def(self - self)
+ .def(self - long())
+ .def(long() - self)
+ .def(self - double())
+ .def(double() - self)
+
+ .def(self *= self)
+ .def(self *= long())
+ .def(self *= double())
+
+ .def(self * self)
+ .def(self * long())
+ .def(long() * self)
+ .def(self * double())
+ .def(double() * self)
+
+ .def(self /= self)
+ .def(self /= long())
+ .def(self /= double())
+
+ .def(self / self)
+ .def(self / long())
+ .def(long() / self)
+ .def(self / double())
+ .def(double() / self)
+
+ .def(- self)
+
+ .def(self < self)
+ .def(self < long())
+ .def(long() < self)
+
+ .def(self <= self)
+ .def(self <= long())
+ .def(long() <= self)
+
+ .def(self > self)
+ .def(self > long())
+ .def(long() > self)
+
+ .def(self >= self)
+ .def(self >= long())
+ .def(long() >= self)
+
+ .def(self == self)
+ .def(self == long())
+ .def(long() == self)
+
+ .def(self != self)
+ .def(self != long())
+ .def(long() != self)
+
+ .def(! self)
+
+ .def(self_ns::int_(self))
+ .def(self_ns::float_(self))
+
+ .def("__str__", &amount_t::to_string)
+ .def("__repr__", &amount_t::to_fullstring)
+
+ .def("has_commodity", &amount_t::has_commodity)
+
+ .add_property("commodity",
+ make_function(&amount_t::commodity,
+ return_value_policy<reference_existing_object>()),
+ make_function(&amount_t::set_commodity,
+ with_custodian_and_ward<1, 2>()))
+
+ .def("annotate_commodity", &amount_t::annotate_commodity)
+ .def("strip_annotations", &amount_t::strip_annotations)
+ .def("clear_commodity", &amount_t::clear_commodity)
+
+ //.add_static_property("full_strings", &amount_t::full_strings)
+
+ .def("to_string", &amount_t::to_string)
+ .def("to_fullstring", &amount_t::to_fullstring)
+ .def("quantity_string", &amount_t::quantity_string)
+
+ .def("exact", &amount_t::exact)
+ .staticmethod("exact")
+
+ .def("__abs__", &amount_t::abs)
+ .def("compare", &amount_t::compare)
+ .def("date", &amount_t::date)
+ .def("negate", &amount_t::negate)
+ .def("null", &amount_t::null)
+ .def("parse", py_parse_1)
+ .def("parse", py_parse_2)
+ .def("price", &amount_t::price)
+ .def("realzero", &amount_t::realzero)
+ .def("reduce", &amount_t::reduce)
+ .def("round", py_round_1)
+ .def("round", py_round_2)
+ .def("sign", &amount_t::sign)
+ .def("unround", &amount_t::unround)
+ .def("value", &amount_t::value)
+ .def("zero", &amount_t::zero)
+
+ .def("valid", &amount_t::valid)
+
+ .def("initialize", &amount_t::initialize)
+ .staticmethod("initialize")
+ .def("shutdown", &amount_t::shutdown)
+ .staticmethod("shutdown")
+ ;
+
+ class_< commodity_base_t::updater_t, commodity_updater_wrap,
+ boost::noncopyable >
+ ("updater")
+ ;
+
+ scope().attr("COMMODITY_STYLE_DEFAULTS") = COMMODITY_STYLE_DEFAULTS;
+ scope().attr("COMMODITY_STYLE_SUFFIXED") = COMMODITY_STYLE_SUFFIXED;
+ scope().attr("COMMODITY_STYLE_SEPARATED") = COMMODITY_STYLE_SEPARATED;
+ scope().attr("COMMODITY_STYLE_EUROPEAN") = COMMODITY_STYLE_EUROPEAN;
+ scope().attr("COMMODITY_STYLE_THOUSANDS") = COMMODITY_STYLE_THOUSANDS;
+ scope().attr("COMMODITY_STYLE_NOMARKET") = COMMODITY_STYLE_NOMARKET;
+ scope().attr("COMMODITY_STYLE_BUILTIN") = COMMODITY_STYLE_BUILTIN;
+
+ class_< commodity_t > ("commodity")
+#if 0
+ .add_property("symbol", &commodity_t::symbol)
+
+ .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,
+ &commodity_t::set_precision)
+ .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)
+ //.add_property("updater", &commodity_t::updater)
+
+ .add_property("smaller",
+ make_getter(&commodity_t::smaller,
+ return_value_policy<reference_existing_object>()),
+ make_setter(&commodity_t::smaller,
+ return_value_policy<reference_existing_object>()))
+ .add_property("larger",
+ make_getter(&commodity_t::larger,
+ return_value_policy<reference_existing_object>()),
+ make_setter(&commodity_t::larger,
+ return_value_policy<reference_existing_object>()))
+
+ .def(self_ns::str(self))
+
+ .def("find", py_find_commodity,
+ return_value_policy<reference_existing_object>())
+ .staticmethod("find")
+
+ .def("add_price", &commodity_t::add_price)
+ .def("remove_price", &commodity_t::remove_price)
+ .def("value", &commodity_t::value)
+
+ .def("valid", &commodity_t::valid)
+#endif
+ ;
+
+#define EXC_TRANSLATE(type) \
+ register_exception_translator<type>(&exc_translate_ ## type);
+
+ EXC_TRANSLATE(amount_exception);
+}
+
+} // namespace ledger
diff --git a/src/py_balance.cc b/src/py_balance.cc
new file mode 100644
index 00000000..372bf1e9
--- /dev/null
+++ b/src/py_balance.cc
@@ -0,0 +1,202 @@
+using namespace boost::python;
+using namespace ledger;
+
+unsigned int balance_len(balance_t& bal)
+{
+ return bal.amounts.size();
+}
+
+amount_t balance_getitem(balance_t& bal, int i)
+{
+ std::size_t len = bal.amounts.size();
+
+ if (abs(i) >= len) {
+ PyErr_SetString(PyExc_IndexError, "Index out of range");
+ throw_error_already_set();
+ }
+
+ int x = i < 0 ? len + i : i;
+ amounts_map::iterator elem = bal.amounts.begin();
+ while (--x >= 0)
+ elem++;
+
+ return (*elem).second;
+}
+
+unsigned int balance_pair_len(balance_pair_t& bal_pair)
+{
+ return balance_len(bal_pair.quantity);
+}
+
+amount_t balance_pair_getitem(balance_pair_t& bal_pair, int i)
+{
+ return balance_getitem(bal_pair.quantity, i);
+}
+
+void export_balance()
+{
+ class_< balance_t > ("Balance")
+ .def(init<balance_t>())
+ .def(init<amount_t>())
+ .def(init<long>())
+ .def(init<unsigned long>())
+ .def(init<double>())
+
+ .def(self += self)
+ .def(self += other<amount_t>())
+ .def(self += long())
+ .def(self + self)
+ .def(self + other<amount_t>())
+ .def(self + long())
+ .def(self -= self)
+ .def(self -= other<amount_t>())
+ .def(self -= long())
+ .def(self - self)
+ .def(self - other<amount_t>())
+ .def(self - long())
+ .def(self *= self)
+ .def(self *= other<amount_t>())
+ .def(self *= long())
+ .def(self * self)
+ .def(self * other<amount_t>())
+ .def(self * long())
+ .def(self /= self)
+ .def(self /= other<amount_t>())
+ .def(self /= long())
+ .def(self / self)
+ .def(self / other<amount_t>())
+ .def(self / long())
+ .def(- self)
+
+ .def(self < self)
+ .def(self < other<amount_t>())
+ .def(self < long())
+ .def(self <= self)
+ .def(self <= other<amount_t>())
+ .def(self <= long())
+ .def(self > self)
+ .def(self > other<amount_t>())
+ .def(self > long())
+ .def(self >= self)
+ .def(self >= other<amount_t>())
+ .def(self >= long())
+ .def(self == self)
+ .def(self == other<amount_t>())
+ .def(self == long())
+ .def(self != self)
+ .def(self != other<amount_t>())
+ .def(self != long())
+ .def(! self)
+
+ .def(self_ns::str(self))
+
+ .def("__abs__", &balance_t::abs)
+ .def("__len__", balance_len)
+ .def("__getitem__", balance_getitem)
+
+ .def("valid", &balance_t::valid)
+
+ .def("realzero", &balance_t::realzero)
+ .def("amount", &balance_t::amount)
+ .def("value", &balance_t::value)
+ .def("price", &balance_t::price)
+ .def("date", &balance_t::date)
+ .def("strip_annotations", &balance_t::strip_annotations)
+ .def("write", &balance_t::write)
+ .def("round", &balance_t::round)
+ .def("negate", &balance_t::negate)
+ .def("negated", &balance_t::negated)
+ ;
+
+ class_< balance_pair_t > ("BalancePair")
+ .def(init<balance_pair_t>())
+ .def(init<balance_t>())
+ .def(init<amount_t>())
+ .def(init<long>())
+ .def(init<unsigned long>())
+ .def(init<double>())
+
+ .def(self += self)
+ .def(self += other<balance_t>())
+ .def(self += other<amount_t>())
+ .def(self += long())
+ .def(self + self)
+ .def(self + other<balance_t>())
+ .def(self + other<amount_t>())
+ .def(self + long())
+ .def(self -= self)
+ .def(self -= other<balance_t>())
+ .def(self -= other<amount_t>())
+ .def(self -= long())
+ .def(self - self)
+ .def(self - other<balance_t>())
+ .def(self - other<amount_t>())
+ .def(self - long())
+ .def(self *= self)
+ .def(self *= other<balance_t>())
+ .def(self *= other<amount_t>())
+ .def(self *= long())
+ .def(self * self)
+ .def(self * other<balance_t>())
+ .def(self * other<amount_t>())
+ .def(self * long())
+ .def(self /= self)
+ .def(self /= other<balance_t>())
+ .def(self /= other<amount_t>())
+ .def(self /= long())
+ .def(self / self)
+ .def(self / other<balance_t>())
+ .def(self / other<amount_t>())
+ .def(self / long())
+ .def(- self)
+
+ .def(self < self)
+ .def(self < other<balance_t>())
+ .def(self < other<amount_t>())
+ .def(self < long())
+ .def(self <= self)
+ .def(self <= other<balance_t>())
+ .def(self <= other<amount_t>())
+ .def(self <= long())
+ .def(self > self)
+ .def(self > other<balance_t>())
+ .def(self > other<amount_t>())
+ .def(self > long())
+ .def(self >= self)
+ .def(self >= other<balance_t>())
+ .def(self >= other<amount_t>())
+ .def(self >= long())
+ .def(self == self)
+ .def(self == other<balance_t>())
+ .def(self == other<amount_t>())
+ .def(self == long())
+ .def(self != self)
+ .def(self != other<balance_t>())
+ .def(self != other<amount_t>())
+ .def(self != long())
+ .def(! self)
+
+ .def(self_ns::str(self))
+
+ .def("__abs__", &balance_pair_t::abs)
+ .def("__len__", balance_pair_len)
+ .def("__getitem__", balance_pair_getitem)
+
+ .def("valid", &balance_pair_t::valid)
+
+ .def("realzero", &balance_pair_t::realzero)
+ .def("amount", &balance_pair_t::amount)
+ .def("value", &balance_pair_t::value)
+ .def("price", &balance_pair_t::price)
+ .def("date", &balance_pair_t::date)
+ .def("strip_annotations", &balance_pair_t::strip_annotations)
+ .def("write", &balance_pair_t::write)
+ .def("round", &balance_pair_t::round)
+ .def("negate", &balance_pair_t::negate)
+ .def("negated", &balance_pair_t::negated)
+
+ .add_property("cost",
+ make_getter(&balance_pair_t::cost,
+ return_value_policy<reference_existing_object>()))
+ ;
+}
diff --git a/src/py_format.cc b/src/py_format.cc
new file mode 100644
index 00000000..e2faf5dc
--- /dev/null
+++ b/src/py_format.cc
@@ -0,0 +1,11 @@
+using namespace boost::python;
+using namespace ledger;
+
+void export_format()
+{
+ class_< format_t > ("Format")
+ .def(init<string>())
+ .def("parse", &format_t::parse)
+ .def("format", &format_t::format)
+ ;
+}
diff --git a/src/py_journal.cc b/src/py_journal.cc
new file mode 100644
index 00000000..d309cf95
--- /dev/null
+++ b/src/py_journal.cc
@@ -0,0 +1,374 @@
+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 string& name)
+{
+ return journal.find_account(name);
+}
+
+account_t * py_find_account_2(journal_t& journal, const 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 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 *, string, 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);
+}
diff --git a/src/py_option.cc b/src/py_option.cc
new file mode 100644
index 00000000..877d92a7
--- /dev/null
+++ b/src/py_option.cc
@@ -0,0 +1,73 @@
+using namespace boost::python;
+using namespace ledger;
+
+struct py_option_t : public option_t
+{
+ PyObject * self;
+
+ py_option_t(PyObject * self_,
+ const string& long_opt,
+ const bool wants_arg)
+ : self(self_), option_t(long_opt, wants_arg) {}
+
+ virtual ~py_option_t() {}
+
+ virtual bool check(option_source_t source) {
+ return call_method<bool>(self, "check", source);
+ }
+
+ 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);
+ }
+};
+
+BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(option_select_overloads,
+ py_option_t::select, 1, 2)
+
+typedef std::map<const string, object> options_map;
+typedef std::pair<const string, object> options_pair;
+
+options_map options;
+
+static option_t * find_option(const string& name)
+{
+ options_map::const_iterator i = options.find(name);
+ if (i != options.end())
+ return extract<py_option_t *>((*i).second.ptr());
+
+ return NULL;
+}
+
+void shutdown_option()
+{
+ options.clear();
+}
+
+void export_option()
+{
+ class_< option_t, py_option_t, boost::noncopyable >
+ ("Option", init<const 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())
+ ;
+
+ 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>())
+ ;
+
+ scope().attr("options") = ptr(&options);
+}
diff --git a/src/py_parser.cc b/src/py_parser.cc
new file mode 100644
index 00000000..f119a0ef
--- /dev/null
+++ b/src/py_parser.cc
@@ -0,0 +1,48 @@
+#include "parser.h"
+
+#if 0
+#ifdef USE_BOOST_PYTHON
+
+using namespace boost::python;
+using namespace ledger;
+
+struct py_parser_t : public parser_t
+{
+ PyObject * self;
+ py_parser_t(PyObject * self_) : self(self_) {}
+
+ virtual bool test(std::istream& in) const {
+ return call_method<bool>(self, "test", in);
+ }
+
+ virtual repitem_t * parse(std::istream& in,
+ journal_t * journal,
+ account_t * master = NULL,
+ const string * original_file = NULL) {
+ return call_method<unsigned int>(self, "parse", in, journal, master,
+ original_file);
+ }
+};
+
+BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(parser_parse_overloads,
+ py_parser_t::parse, 2, 4)
+
+BOOST_PYTHON_FUNCTION_OVERLOADS(parse_journal_overloads, parse_journal, 2, 4)
+BOOST_PYTHON_FUNCTION_OVERLOADS(parse_journal_file_overloads,
+ parse_journal_file, 2, 4)
+
+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())
+ ;
+
+ def("register_parser", register_parser);
+ def("unregister_parser", unregister_parser);
+
+ def("parse_journal", parse_journal, parse_journal_overloads());
+ def("parse_journal_file", parse_journal_file, parse_journal_file_overloads());
+}
+
+#endif // USE_BOOST_PYTHON
+#endif
diff --git a/src/py_report.cc b/src/py_report.cc
new file mode 100644
index 00000000..0bc36857
--- /dev/null
+++ b/src/py_report.cc
@@ -0,0 +1,13 @@
+using namespace boost::python;
+using namespace ledger;
+
+void export_report()
+{
+ class_< report_t > ("Report")
+ .add_property("session",
+ make_getter(&report_t::session,
+ return_value_policy<reference_existing_object>()))
+
+ .def("apply_transforms", &report_t::apply_transforms)
+ ;
+}
diff --git a/src/py_session.cc b/src/py_session.cc
new file mode 100644
index 00000000..f249c54e
--- /dev/null
+++ b/src/py_session.cc
@@ -0,0 +1,36 @@
+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_alloc_mode", &session_t::trace_alloc_mode)
+ .def_readwrite("trace_class_mode", &session_t::trace_class_mode)
+
+ .def_readwrite("journals", &session_t::journals)
+ ;
+}
diff --git a/src/py_transform.cc b/src/py_transform.cc
new file mode 100644
index 00000000..a0ba31d4
--- /dev/null
+++ b/src/py_transform.cc
@@ -0,0 +1,8 @@
+using namespace boost::python;
+using namespace ledger;
+
+void export_transform()
+{
+ class_< repitem_t > ("Transform")
+ ;
+}
diff --git a/src/py_value.cc b/src/py_value.cc
new file mode 100644
index 00000000..1a43ebc1
--- /dev/null
+++ b/src/py_value.cc
@@ -0,0 +1,337 @@
+using namespace boost::python;
+using namespace ledger;
+
+long balance_len(balance_t& bal);
+amount_t balance_getitem(balance_t& bal, int i);
+long balance_pair_len(balance_pair_t& bal_pair);
+amount_t balance_pair_getitem(balance_pair_t& bal_pair, int i);
+
+long value_len(value_t& val)
+{
+ switch (val.type) {
+ case value_t::BOOLEAN:
+ case value_t::INTEGER:
+ case value_t::DATETIME:
+ case value_t::AMOUNT:
+ return 1;
+
+ case value_t::BALANCE:
+ return balance_len(*((balance_t *) val.data));
+
+ case value_t::BALANCE_PAIR:
+ return balance_pair_len(*((balance_pair_t *) val.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 **) val.data)->size();
+
+ default:
+ assert(0);
+ break;
+ }
+ assert(0);
+ return 0;
+}
+
+amount_t value_getitem(value_t& val, int i)
+{
+ std::size_t len = value_len(val);
+
+ if (abs(i) >= len) {
+ PyErr_SetString(PyExc_IndexError, "Index out of range");
+ throw_error_already_set();
+ }
+
+ switch (val.type) {
+ case value_t::BOOLEAN:
+ throw_(value_exception, "Cannot cast a boolean to an amount");
+
+ case value_t::INTEGER:
+ return long(val);
+
+ case value_t::DATETIME:
+ throw_(value_exception, "Cannot cast a date/time to an amount");
+
+ case value_t::AMOUNT:
+ return *((amount_t *) val.data);
+
+ case value_t::BALANCE:
+ return balance_getitem(*((balance_t *) val.data), i);
+
+ case value_t::BALANCE_PAIR:
+ return balance_pair_getitem(*((balance_pair_t *) val.data), i);
+
+ case value_t::STRING:
+ throw_(value_exception, "Cannot cast a string to an amount");
+
+ case value_t::XML_NODE:
+ return (*(xml::node_t **) data)->to_value();
+
+ case value_t::POINTER:
+ throw_(value_exception, "Cannot cast a pointer to an amount");
+
+ case value_t::SEQUENCE:
+ return (*(value_t::sequence_t **) val.data)[i];
+
+ default:
+ assert(0);
+ break;
+ }
+ assert(0);
+ return 0L;
+}
+
+double py_to_float(value_t& val)
+{
+ return double(val);
+}
+
+void export_value()
+{
+ class_< value_t > ("value")
+ .def(init<value_t>())
+ .def(init<balance_pair_t>())
+ .def(init<balance_t>())
+ .def(init<amount_t>())
+ .def(init<string>())
+ .def(init<double>())
+ .def(init<long>())
+ .def(initmoment_t())
+
+ .def(self + self)
+ .def(self + other<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<string>() + self)
+ .def(other<balance_pair_t>() + self)
+ .def(other<balance_t>() + self)
+ .def(other<amount_t>() + self)
+ .def(long() + self)
+ .def(double() + self)
+
+ .def(self - self)
+ .def(self - other<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<string>() - self)
+ .def(other<balance_pair_t>() - self)
+ .def(other<balance_t>() - self)
+ .def(other<amount_t>() - self)
+ .def(long() - self)
+ .def(double() - self)
+
+ .def(self * self)
+ .def(self * other<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<string>() * self)
+ .def(other<balance_pair_t>() * self)
+ .def(other<balance_t>() * self)
+ .def(other<amount_t>() * self)
+ .def(long() * self)
+ .def(double() * self)
+
+ .def(self / self)
+ .def(self / other<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<string>() / self)
+ .def(other<balance_pair_t>() / self)
+ .def(other<balance_t>() / self)
+ .def(other<amount_t>() / self)
+ .def(long() / self)
+ .def(double() / self)
+
+ .def(- self)
+
+ .def(self += self)
+ .def(self += other<string>())
+ .def(self += other<balance_pair_t>())
+ .def(self += other<balance_t>())
+ .def(self += other<amount_t>())
+ .def(self += long())
+ .def(self += double())
+
+ .def(self -= self)
+ .def(self -= other<string>())
+ .def(self -= other<balance_pair_t>())
+ .def(self -= other<balance_t>())
+ .def(self -= other<amount_t>())
+ .def(self -= long())
+ .def(self -= double())
+
+ .def(self *= self)
+ .def(self *= other<string>())
+ .def(self *= other<balance_pair_t>())
+ .def(self *= other<balance_t>())
+ .def(self *= other<amount_t>())
+ .def(self *= long())
+ .def(self *= double())
+
+ .def(self /= self)
+ .def(self /= other<string>())
+ .def(self /= other<balance_pair_t>())
+ .def(self /= other<balance_t>())
+ .def(self /= other<amount_t>())
+ .def(self /= long())
+ .def(self /= double())
+
+ .def(self < self)
+ .def(self < other<string>())
+ .def(self < other<balance_pair_t>())
+ .def(self < other<balance_t>())
+ .def(self < other<amount_t>())
+ .def(self < long())
+ .def(self < othermoment_t())
+ .def(self < double())
+
+ .def(other<string>() < self)
+ .def(other<balance_pair_t>() < self)
+ .def(other<balance_t>() < self)
+ .def(other<amount_t>() < self)
+ .def(long() < self)
+ .def(othermoment_t() < self)
+ .def(double() < self)
+
+ .def(self <= self)
+ .def(self <= other<string>())
+ .def(self <= other<balance_pair_t>())
+ .def(self <= other<balance_t>())
+ .def(self <= other<amount_t>())
+ .def(self <= long())
+ .def(self <= othermoment_t())
+ .def(self <= double())
+
+ .def(other<string>() <= self)
+ .def(other<balance_pair_t>() <= self)
+ .def(other<balance_t>() <= self)
+ .def(other<amount_t>() <= self)
+ .def(long() <= self)
+ .def(othermoment_t() <= self)
+ .def(double() <= self)
+
+ .def(self > self)
+ .def(self > other<string>())
+ .def(self > other<balance_pair_t>())
+ .def(self > other<balance_t>())
+ .def(self > other<amount_t>())
+ .def(self > long())
+ .def(self > othermoment_t())
+ .def(self > double())
+
+ .def(other<string>() > self)
+ .def(other<balance_pair_t>() > self)
+ .def(other<balance_t>() > self)
+ .def(other<amount_t>() > self)
+ .def(long() > self)
+ .def(othermoment_t() > self)
+ .def(double() > self)
+
+ .def(self >= self)
+ .def(self >= other<string>())
+ .def(self >= other<balance_pair_t>())
+ .def(self >= other<balance_t>())
+ .def(self >= other<amount_t>())
+ .def(self >= long())
+ .def(self >= othermoment_t())
+ .def(self >= double())
+
+ .def(other<string>() >= self)
+ .def(other<balance_pair_t>() >= self)
+ .def(other<balance_t>() >= self)
+ .def(other<amount_t>() >= self)
+ .def(long() >= self)
+ .def(othermoment_t() >= self)
+ .def(double() >= self)
+
+ .def(self == self)
+ .def(self == other<string>())
+ .def(self == other<balance_pair_t>())
+ .def(self == other<balance_t>())
+ .def(self == other<amount_t>())
+ .def(self == long())
+ .def(self == othermoment_t())
+ .def(self == double())
+
+ .def(other<string>() == self)
+ .def(other<balance_pair_t>() == self)
+ .def(other<balance_t>() == self)
+ .def(other<amount_t>() == self)
+ .def(long() == self)
+ .def(othermoment_t() == self)
+ .def(double() == self)
+
+ .def(self != self)
+ .def(self != other<string>())
+ .def(self != other<balance_pair_t>())
+ .def(self != other<balance_t>())
+ .def(self != other<amount_t>())
+ .def(self != long())
+ .def(self != othermoment_t())
+ .def(self != double())
+
+ .def(other<string>() != self)
+ .def(other<balance_pair_t>() != self)
+ .def(other<balance_t>() != self)
+ .def(other<amount_t>() != self)
+ .def(long() != self)
+ .def(othermoment_t() != self)
+ .def(double() != self)
+
+ .def(! self)
+
+ .def(self_ns::int_(self))
+ .def(self_ns::float_(self))
+ .def(self_ns::str(self))
+
+ .def_readonly("type", &value_t::type)
+
+ .def("__abs__", &value_t::abs)
+ .def("__len__", value_len)
+ .def("__getitem__", value_getitem)
+
+ .def("cast", &value_t::cast)
+ .def("cost", &value_t::cost)
+ .def("price", &value_t::price)
+ .def("date", &value_t::date)
+ .def("strip_annotations", &value_t::strip_annotations)
+ .def("add", &value_t::add, return_internal_reference<>())
+ .def("value", &value_t::value)
+ .def("round", &value_t::round)
+ .def("negate", &value_t::negate)
+ .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("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/src/py_xpath.cc b/src/py_xpath.cc
new file mode 100644
index 00000000..6569ce7b
--- /dev/null
+++ b/src/py_xpath.cc
@@ -0,0 +1,79 @@
+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 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<string>())
+ .def("__call__", &item_predicate<transaction_t>::operator())
+ ;
+
+ class_< item_predicate<account_t> >
+ ("AccountPredicate", init<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
+}
diff --git a/src/pyfstream.h b/src/pyfstream.h
new file mode 100644
index 00000000..27e5166b
--- /dev/null
+++ b/src/pyfstream.h
@@ -0,0 +1,138 @@
+#ifndef _PYFSTREAM_H
+#define _PYFSTREAM_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/src/pyinterp.cc b/src/pyinterp.cc
new file mode 100644
index 00000000..b0cb166c
--- /dev/null
+++ b/src/pyinterp.cc
@@ -0,0 +1,164 @@
+#include "pyinterp.h"
+
+namespace ledger {
+
+struct python_run
+{
+ object result;
+ python_run(python_interpreter_t * intepreter,
+ const 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;
+ }
+};
+
+extern void initialize_for_python();
+
+python_interpreter_t::python_interpreter_t(xml::xpath_t::scope_t * parent)
+ : xml::xpath_t::scope_t(parent),
+ mmodule(borrowed(PyImport_AddModule("__main__"))),
+ nspace(handle<>(borrowed(PyModule_GetDict(mmodule.get()))))
+{
+ Py_Initialize();
+ detail::init_module("ledger", &initialize_for_python);
+}
+
+object python_interpreter_t::import(const string& str)
+{
+ assert(Py_IsInitialized());
+
+ try {
+ PyObject * mod = PyImport_Import(PyString_FromString(str.c_str()));
+ if (! mod)
+ throw_(exception, "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[string(PyModule_GetName(mod))] = newmod;
+#endif
+ return newmod;
+ }
+ catch (const error_already_set&) {
+ PyErr_Print();
+ throw_(exception, "Importing Python module " << str);
+ }
+}
+
+object python_interpreter_t::eval(std::istream& in, py_eval_mode_t mode)
+{
+ bool first = true;
+ 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_(exception, "Evaluating Python code");
+ }
+}
+
+object python_interpreter_t::eval(const 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_(exception, "Evaluating Python code");
+ }
+}
+
+void python_interpreter_t::functor_t::operator()(value_t& result,
+ xml::xpath_t::scope_t * locals)
+{
+ try {
+ if (! PyCallable_Check(func.ptr())) {
+ result = static_cast<const value_t&>(extract<value_t>(func.ptr()));
+ } else {
+ assert(locals->args.type == value_t::SEQUENCE);
+ if (locals->args.to_sequence()->size() > 0) {
+ list arglist;
+ for (value_t::sequence_t::iterator
+ i = locals->args.to_sequence()->begin();
+ i != locals->args.to_sequence()->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_(xml::xpath_t::calc_exception,
+ "While calling Python function '" << name() << "'");
+ } else {
+ assert(0);
+ }
+ } else {
+ result = call<value_t>(func.ptr());
+ }
+ }
+ }
+ catch (const error_already_set&) {
+ PyErr_Print();
+ throw_(xml::xpath_t::calc_exception,
+ "While calling Python function '" << name() << "'");
+ }
+}
+
+void python_interpreter_t::lambda_t::operator()(value_t& result,
+ xml::xpath_t::scope_t * locals)
+{
+ try {
+ assert(locals->args.type == value_t::SEQUENCE);
+ assert(locals->args.to_sequence()->size() == 1);
+ value_t item = locals->args[0];
+ assert(item.type == value_t::POINTER);
+ result = call<value_t>(func.ptr(), (xml::node_t *)*(void **)item.data);
+ }
+ catch (const error_already_set&) {
+ PyErr_Print();
+ throw_(xml::xpath_t::calc_exception,
+ "While evaluating Python lambda expression");
+ }
+}
+
+} // namespace ledger
diff --git a/src/pyinterp.h b/src/pyinterp.h
new file mode 100644
index 00000000..a75ef78b
--- /dev/null
+++ b/src/pyinterp.h
@@ -0,0 +1,83 @@
+#ifndef _PY_EVAL_H
+#define _PY_EVAL_H
+
+#include "xpath.h"
+
+#if defined(USE_BOOST_PYTHON)
+
+#include <boost/python.hpp>
+#include <boost/python/detail/api_placeholder.hpp>
+#include <boost/python/exception_translator.hpp>
+#include <boost/python/suite/indexing/map_indexing_suite.hpp>
+
+#include <Python.h>
+
+#include "pyfstream.h"
+
+using namespace boost::python;
+
+namespace ledger {
+
+class python_interpreter_t : public xml::xpath_t::scope_t
+{
+ handle<> mmodule;
+
+ public:
+ dict nspace;
+
+ python_interpreter_t(xml::xpath_t::scope_t * parent);
+
+ virtual ~python_interpreter_t() {
+ Py_Finalize();
+ }
+
+ object import(const 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 string& str, py_eval_mode_t mode = PY_EVAL_EXPR);
+ object eval(const char * c_str, py_eval_mode_t mode = PY_EVAL_EXPR) {
+ string str(c_str);
+ return eval(str, mode);
+ }
+
+ class functor_t : public xml::xpath_t::functor_t {
+ protected:
+ object func;
+ public:
+ functor_t(const string& name, object _func)
+ : xml::xpath_t::functor_t(name), func(_func) {}
+
+ virtual void operator()(value_t& result, xml::xpath_t::scope_t * locals);
+ };
+
+ virtual void define(const string& name, xml::xpath_t::op_t * def) {
+ // Pass any definitions up to our parent
+ parent->define(name, def);
+ }
+
+ virtual xml::xpath_t::op_t * lookup(const string& name) {
+ object func = eval(name);
+ if (! func)
+ return parent ? parent->lookup(name) : NULL;
+ return xml::xpath_t::wrap_functor(new functor_t(name, func));
+ }
+
+ class lambda_t : public functor_t {
+ public:
+ lambda_t(object code) : functor_t("<lambda>", code) {}
+
+ virtual void operator()(value_t& result, xml::xpath_t::scope_t * locals);
+ };
+};
+
+} // namespace ledger
+
+#endif // USE_BOOST_PYTHON
+
+#endif // _PY_EVAL_H
diff --git a/src/pyledger.cc b/src/pyledger.cc
new file mode 100644
index 00000000..b7654e5d
--- /dev/null
+++ b/src/pyledger.cc
@@ -0,0 +1,53 @@
+#include <pyledger.h>
+
+using namespace boost::python;
+
+namespace ledger {
+
+void export_amount();
+#if 0
+void export_balance();
+void export_value();
+
+void export_journal();
+void export_parser();
+void export_option();
+void export_walk();
+void export_report();
+void export_format();
+void export_valexpr();
+
+void shutdown_option();
+#endif
+
+void initialize_for_python()
+{
+ export_amount();
+#if 0
+ export_balance();
+ export_value();
+
+ export_journal();
+ export_parser();
+ export_option();
+ export_walk();
+ export_format();
+ export_report();
+ export_valexpr();
+#endif
+}
+
+void shutdown_for_python()
+{
+#if 0
+ shutdown_option();
+#endif
+}
+
+}
+
+BOOST_PYTHON_MODULE(ledger)
+{
+ ledger::initialize();
+ ledger::initialize_for_python();
+}
diff --git a/src/pyledger.h b/src/pyledger.h
new file mode 100644
index 00000000..4056fec0
--- /dev/null
+++ b/src/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 <pyinterp.h>
+
+#endif // _PYLEDGER_H
diff --git a/src/qif.cc b/src/qif.cc
new file mode 100644
index 00000000..5e567ea0
--- /dev/null
+++ b/src/qif.cc
@@ -0,0 +1,243 @@
+#include "qif.h"
+#include "journal.h"
+
+namespace ledger {
+
+#define MAX_LINE 1024
+
+static char line[MAX_LINE + 1];
+static string path;
+static unsigned int src_idx;
+static unsigned int linenum;
+
+static inline char * get_line(std::istream& in) {
+ in.getline(line, MAX_LINE);
+ int len = std::strlen(line);
+ if (line[len - 1] == '\r')
+ line[len - 1] = '\0';
+ linenum++;
+ return line;
+}
+
+bool qif_parser_t::test(std::istream& in) const
+{
+ char magic[sizeof(unsigned int) + 1];
+ in.read(magic, sizeof(unsigned int));
+ magic[sizeof(unsigned int)] = '\0';
+ in.clear();
+ in.seekg(0, std::ios::beg);
+
+ return (std::strcmp(magic, "!Typ") == 0 ||
+ std::strcmp(magic, "\n!Ty") == 0 ||
+ std::strcmp(magic, "\r\n!T") == 0);
+}
+
+unsigned int qif_parser_t::parse(std::istream& in,
+ journal_t * journal,
+ account_t * master,
+ const string *)
+{
+ std::auto_ptr<entry_t> entry;
+ std::auto_ptr<amount_t> amount;
+
+ transaction_t * xact;
+ unsigned int count = 0;
+ account_t * misc = NULL;
+ commodity_t * def_commodity = NULL;
+ bool saw_splits = false;
+ bool saw_category = false;
+ transaction_t * total = NULL;
+
+ entry.reset(new entry_t);
+ xact = new transaction_t(master);
+ entry->add_transaction(xact);
+
+ path = journal->sources.back();
+ src_idx = journal->sources.size() - 1;
+ linenum = 1;
+
+ unsigned long beg_pos = 0;
+ unsigned long beg_line = 0;
+
+#define SET_BEG_POS_AND_LINE() \
+ if (! beg_line) { \
+ beg_pos = in.tellg(); \
+ beg_line = linenum; \
+ }
+
+ while (in.good() && ! in.eof()) {
+ char c;
+ in.get(c);
+ switch (c) {
+ case ' ':
+ case '\t':
+ if (peek_next_nonws(in) != '\n') {
+ get_line(in);
+ throw_(parse_exception, "Line begins with whitespace");
+ }
+ // fall through...
+
+ case '\n':
+ linenum++;
+ case '\r': // skip blank lines
+ break;
+
+ case '!':
+ get_line(in);
+
+ if (std::strcmp(line, "Type:Invst") == 0 ||
+ std::strcmp(line, "Account") == 0 ||
+ std::strcmp(line, "Type:Cat") == 0 ||
+ std::strcmp(line, "Type:Class") == 0 ||
+ std::strcmp(line, "Type:Memorized") == 0)
+ throw_(parse_exception,
+ "QIF files of type " << line << " are not supported.");
+ break;
+
+ case 'D':
+ SET_BEG_POS_AND_LINE();
+ get_line(in);
+ entry->_date = parse_datetime(line);
+ break;
+
+ case 'T':
+ case '$': {
+ SET_BEG_POS_AND_LINE();
+ get_line(in);
+ xact->amount.parse(line);
+
+ unsigned char flags = xact->amount.commodity().flags();
+ unsigned char prec = xact->amount.commodity().precision();
+
+ if (! def_commodity) {
+ def_commodity = commodity_t::find_or_create("$");
+ assert(def_commodity);
+ }
+ xact->amount.set_commodity(*def_commodity);
+
+ def_commodity->add_flags(flags);
+ if (prec > def_commodity->precision())
+ def_commodity->set_precision(prec);
+
+ if (c == '$') {
+ saw_splits = true;
+ xact->amount.in_place_negate();
+ } else {
+ total = xact;
+ }
+ break;
+ }
+
+ case 'C':
+ SET_BEG_POS_AND_LINE();
+ c = in.peek();
+ if (c == '*' || c == 'X') {
+ in.get(c);
+ xact->state = transaction_t::CLEARED;
+ }
+ break;
+
+ case 'N':
+ SET_BEG_POS_AND_LINE();
+ get_line(in);
+ entry->code = line;
+ break;
+
+ case 'P':
+ case 'M':
+ case 'L':
+ case 'S':
+ case 'E': {
+ SET_BEG_POS_AND_LINE();
+ get_line(in);
+
+ switch (c) {
+ case 'P':
+ entry->payee = line;
+ break;
+
+ case 'S':
+ xact = new transaction_t(NULL);
+ entry->add_transaction(xact);
+ // fall through...
+ case 'L': {
+ int len = std::strlen(line);
+ if (line[len - 1] == ']')
+ line[len - 1] = '\0';
+ xact->account = journal->find_account(line[0] == '[' ?
+ line + 1 : line);
+ if (c == 'L')
+ saw_category = true;
+ break;
+ }
+
+ case 'M':
+ case 'E':
+ xact->note = line;
+ break;
+ }
+ break;
+ }
+
+ case 'A':
+ SET_BEG_POS_AND_LINE();
+ // jww (2004-08-19): these are ignored right now
+ get_line(in);
+ break;
+
+ case '^': {
+ account_t * other;
+ if (xact->account == master) {
+ if (! misc)
+ misc = journal->find_account("Miscellaneous");
+ other = misc;
+ } else {
+ other = master;
+ }
+
+ if (total && saw_category) {
+ if (! saw_splits)
+ total->amount.in_place_negate(); // negate, to show correct flow
+ else
+ total->account = other;
+ }
+
+ if (! saw_splits) {
+ transaction_t * nxact = new transaction_t(other);
+ // The amount doesn't need to be set because the code below
+ // will balance this transaction against the other.
+ entry->add_transaction(nxact);
+ }
+
+ if (journal->add_entry(entry.get())) {
+ entry->src_idx = src_idx;
+ entry->beg_pos = beg_pos;
+ entry->beg_line = beg_line;
+ entry->end_pos = in.tellg();
+ entry->end_line = linenum;
+ entry.release();
+ count++;
+ }
+
+ // reset things for the next entry
+ entry.reset(new entry_t);
+ xact = new transaction_t(master);
+ entry->add_transaction(xact);
+
+ saw_splits = false;
+ saw_category = false;
+ total = NULL;
+ beg_line = 0;
+ break;
+ }
+
+ default:
+ get_line(in);
+ break;
+ }
+ }
+
+ return count;
+}
+
+} // namespace ledger
diff --git a/src/qif.h b/src/qif.h
new file mode 100644
index 00000000..6c68a99b
--- /dev/null
+++ b/src/qif.h
@@ -0,0 +1,21 @@
+#ifndef _QIF_H
+#define _QIF_H
+
+#include "parser.h"
+
+namespace ledger {
+
+class qif_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 string * original_file = NULL);
+};
+
+} // namespace ledger
+
+#endif // _QIF_H
diff --git a/src/quotes.cc b/src/quotes.cc
new file mode 100644
index 00000000..1463c9bc
--- /dev/null
+++ b/src/quotes.cc
@@ -0,0 +1,80 @@
+#include "quotes.h"
+
+namespace ledger {
+
+void quotes_by_script::operator()(commodity_base_t& commodity,
+ const ptime& moment,
+ const ptime& date,
+ const ptime& last,
+ amount_t& price)
+{
+ LOGGER("quotes.download");
+
+ DEBUG("commodity: " << commodity.symbol);
+ DEBUG(" now: " << now);
+ DEBUG(" moment: " << moment);
+ DEBUG(" date: " << date);
+ DEBUG(" last: " << last);
+
+ if (SHOW_DEBUG() && commodity.history)
+ DEBUG("last_lookup: " << commodity.history->last_lookup);
+ DEBUG("pricing_leeway is " << pricing_leeway);
+
+ if ((commodity.history &&
+ (time_now - commodity.history->last_lookup) < pricing_leeway) ||
+ (time_now - last) < pricing_leeway ||
+ (price && moment > date && (moment - date) <= pricing_leeway))
+ return;
+
+ DEBUG("downloading quote for symbol " << commodity.symbol);
+
+ char buf[256];
+ buf[0] = '\0';
+
+ bool success = true;
+
+ if (FILE * fp = popen((string("getquote \"") +
+ commodity.symbol + "\"").c_str(), "r")) {
+ if (feof(fp) || ! fgets(buf, 255, fp))
+ success = false;
+ if (pclose(fp) != 0)
+ success = false;
+ } else {
+ success = false;
+ }
+
+ if (success && buf[0]) {
+ char * p = strchr(buf, '\n');
+ if (p) *p = '\0';
+
+ DEBUG("downloaded quote: " << buf);
+
+ price.parse(buf);
+ commodity.add_price(now, price);
+
+ commodity.history->last_lookup = time_now;
+ cache_dirty = true;
+
+ if (price && ! price_db.empty()) {
+#if defined(__GNUG__) && __GNUG__ < 3
+ std::ofstream database(price_db.c_str(), ios::out | ios::app);
+#else
+ std::ofstream database(price_db.c_str(),
+ std::ios_base::out | std::ios_base::app);
+#endif
+#if 0
+ // jww (2007-04-18): Need to convert to local time and print
+ // here, print with UTC timezone specifier
+ database << "P " << now.to_string("%Y/%m/%d %H:%M:%S")
+ << " " << commodity.symbol << " " << price << endl;
+#endif
+ }
+ } else {
+ throw exception(string("Failed to download price for '") +
+ commodity.symbol + "' (command: \"getquote " +
+ commodity.symbol + "\")",
+ context());
+ }
+}
+
+} // namespace ledger
diff --git a/src/quotes.h b/src/quotes.h
new file mode 100644
index 00000000..a1fabd93
--- /dev/null
+++ b/src/quotes.h
@@ -0,0 +1,30 @@
+#ifndef _QUOTES_H
+#define _QUOTES_H
+
+#include "amount.h"
+
+namespace ledger {
+
+class quotes_by_script : public commodity_base_t::updater_t
+{
+ string price_db;
+ time_duration pricing_leeway;
+ bool& cache_dirty;
+
+ public:
+ quotes_by_script(string _price_db,
+ time_duration _pricing_leeway,
+ bool& _cache_dirty)
+ : price_db(_price_db), pricing_leeway(_pricing_leeway),
+ cache_dirty(_cache_dirty) {}
+
+ virtual void operator()(commodity_base_t& commodity,
+ const ptime& moment,
+ const ptime& date,
+ const ptime& last,
+ amount_t& price);
+};
+
+} // namespace ledger
+
+#endif // _QUOTES_H
diff --git a/src/reconcile.cc b/src/reconcile.cc
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/reconcile.cc
diff --git a/src/reconcile.h b/src/reconcile.h
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/reconcile.h
diff --git a/src/register.cc b/src/register.cc
new file mode 100644
index 00000000..9f33bd0c
--- /dev/null
+++ b/src/register.cc
@@ -0,0 +1,166 @@
+#include "register.h"
+#include "journal.h"
+
+namespace ledger {
+
+string abbreviate(const string& str,
+ unsigned int width,
+ elision_style_t elision_style,
+ const bool is_account,
+ int abbrev_length)
+{
+ const unsigned int len = str.length();
+ if (len <= width)
+ return str;
+
+ assert(width < 4095);
+
+ static 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<string> parts;
+ string::size_type beg = 0;
+ for (string::size_type pos = str.find(':');
+ pos != string::npos;
+ beg = pos + 1, pos = str.find(':', beg))
+ parts.push_back(string(str, beg, pos - beg));
+ parts.push_back(string(str, beg));
+
+ string result;
+ unsigned int newlen = len;
+ for (std::list<string>::iterator i = parts.begin();
+ i != parts.end();
+ i++) {
+ // Don't contract the last element
+ std::list<string>::iterator x = i;
+ if (++x == parts.end()) {
+ result += *i;
+ break;
+ }
+
+ if (newlen > width) {
+ result += 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;
+}
+
+static void scan_for_transactions(std::ostream& out, const xml::node_t * node)
+{
+ if (! (node->flags & XML_NODE_IS_PARENT))
+ return;
+
+ const xml::parent_node_t * parent =
+ static_cast<const xml::parent_node_t *>(node);
+
+ for (const xml::node_t * child = parent->children();
+ child;
+ child = child->next)
+ if (child->name_id == xml::document_t::TRANSACTION) {
+ const xml::transaction_node_t * xact_node =
+ dynamic_cast<const xml::transaction_node_t *>(child);
+ assert(xact_node);
+
+ const transaction_t * xact = xact_node->transaction;
+ assert(xact);
+
+ out << xact->entry->date() << ' '
+ << std::setw(21) << std::left
+ << abbreviate(xact->entry->payee, 21) << ' '
+ << std::setw(21) << std::left
+ << abbreviate(xact->account->fullname(), 21,
+ ABBREVIATE, true) << ' '
+ << std::setw(12) << std::right
+ << xact->amount << '\n';
+ } else {
+ scan_for_transactions(out, child);
+ }
+}
+
+void register_command::print_document(std::ostream& out,
+ xml::document_t * doc)
+{
+#if 1
+ scan_for_transactions(out, doc->top);
+ out.flush();
+#else
+ value_t nodelist;
+ xml::xpath_t::eval(nodelist, "//transaction", doc);
+
+ const value_t::sequence_t * xact_list = nodelist.to_sequence();
+ assert(xact_list);
+
+ for (value_t::sequence_t::const_iterator i = xact_list->begin();
+ i != xact_list->end();
+ i++) {
+ const xml::node_t * node = (*i).to_xml_node();
+ assert(node);
+
+ const xml::transaction_node_t * xact_node =
+ dynamic_cast<const xml::transaction_node_t *>(node);
+ assert(xact_node);
+
+ const transaction_t * xact = xact_node->transaction;
+ assert(xact);
+
+ std::cout << xact->entry->date() << ' '
+ << std::setw(21) << std::left
+ << abbreviate(xact->entry->payee, 21) << ' '
+ << std::setw(21) << std::left
+ << abbreviate(xact->account->fullname(), 21,
+ ABBREVIATE, true) << ' '
+ << std::setw(12) << std::right
+ << xact->amount
+ << std::endl;
+ }
+#endif
+}
+
+} // namespace ledger
diff --git a/src/register.h b/src/register.h
new file mode 100644
index 00000000..ba2020ec
--- /dev/null
+++ b/src/register.h
@@ -0,0 +1,38 @@
+#ifndef _REGISTER_H
+#define _REGISTER_H
+
+#include "xpath.h"
+
+namespace ledger {
+
+class register_command : public xml::xpath_t::functor_t
+{
+ public:
+ register_command() : xml::xpath_t::functor_t("register") {}
+
+ virtual void operator()(value_t&, 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);
+
+ print_document(*out, doc);
+ }
+
+ virtual void print_document(std::ostream& out, xml::document_t * doc);
+};
+
+enum elision_style_t {
+ TRUNCATE_TRAILING,
+ TRUNCATE_MIDDLE,
+ TRUNCATE_LEADING,
+ ABBREVIATE
+};
+
+string abbreviate(const string& str,
+ unsigned int width,
+ elision_style_t elision_style = TRUNCATE_TRAILING,
+ const bool is_account = false,
+ int abbrev_length = 2);
+
+} // namespace ledger
+
+#endif // _REGISTER_H
diff --git a/src/report.cc b/src/report.cc
new file mode 100644
index 00000000..116747ef
--- /dev/null
+++ b/src/report.cc
@@ -0,0 +1,189 @@
+#include "report.h"
+
+namespace ledger {
+
+report_t::~report_t()
+{
+ TRACE_DTOR(report_t);
+ for (std::list<transform_t *>::const_iterator i = transforms.begin();
+ i != transforms.end();
+ i++)
+ delete *i;
+}
+
+void report_t::apply_transforms(xml::document_t * document)
+{
+ for (std::list<transform_t *>::const_iterator i = transforms.begin();
+ i != transforms.end();
+ i++)
+ (*i)->execute(document);
+}
+
+void report_t::abbrev(value_t& result, xml::xpath_t::scope_t * locals)
+{
+ if (locals->args.size() < 2)
+ throw_(exception, "usage: abbrev(STRING, WIDTH [, STYLE, ABBREV_LEN])");
+
+ string str = locals->args[0].to_string();
+ long wid = locals->args[1];
+
+ elision_style_t style = session->elision_style;
+ if (locals->args.size() == 3)
+ style = (elision_style_t)locals->args[2].to_integer();
+
+ long abbrev_len = session->abbrev_length;
+ if (locals->args.size() == 4)
+ abbrev_len = locals->args[3].to_integer();
+
+ result.set_string(abbreviate(str, wid, style, true, (int)abbrev_len));
+}
+
+void report_t::ftime(value_t&, xml::xpath_t::scope_t * locals)
+{
+ if (locals->args.size() < 1)
+ throw_(exception, "usage: ftime(DATE [, DATE_FORMAT])");
+
+ moment_t date = locals->args[0].to_datetime();
+
+ string date_format;
+ if (locals->args.size() == 2)
+ date_format = locals->args[1].to_string();
+#if 0
+ // jww (2007-04-18): Need to setup an output facet here
+ else
+ date_format = moment_t::output_format;
+
+ result.set_string(date.to_string(date_format));
+#endif
+}
+
+bool report_t::resolve(const string& name, value_t& result,
+ xml::xpath_t::scope_t * locals)
+{
+ const char * p = name.c_str();
+ switch (*p) {
+ case 'a':
+ if (name == "abbrev") {
+ abbrev(result, locals);
+ return true;
+ }
+ break;
+
+ case 'f':
+ if (name == "ftime") {
+ ftime(result, locals);
+ return true;
+ }
+ break;
+ }
+
+ return xml::xpath_t::scope_t::resolve(name, result, locals);
+}
+
+xml::xpath_t::op_t * report_t::lookup(const 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;
+
+ case 'b':
+ if (std::strcmp(p, "bar") == 0)
+ return MAKE_FUNCTOR(report_t, option_bar);
+ break;
+
+#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;
+
+ 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;
+
+ case 'i':
+#if 0
+ if (std::strcmp(p, "include") == 0)
+ return MAKE_FUNCTOR(report_t, option_select);
+#endif
+ break;
+
+ case 'l':
+#if 0
+ if (! *(p + 1) || std::strcmp(p, "limit") == 0)
+ return MAKE_FUNCTOR(report_t, option_limit);
+#endif
+ break;
+
+#if 0
+ case 'm':
+ if (std::strcmp(p, "merge") == 0)
+ return MAKE_FUNCTOR(report_t, option_merge);
+ break;
+#endif
+
+ case 'r':
+#if 0
+ if (std::strcmp(p, "remove") == 0)
+ return MAKE_FUNCTOR(report_t, option_remove);
+#endif
+ break;
+
+#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
+
+ 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;
+
+ case 'T':
+ if (! *(p + 1))
+ return MAKE_FUNCTOR(report_t, option_total);
+ break;
+ }
+ }
+ break;
+ }
+
+ return xml::xpath_t::scope_t::lookup(name);
+}
+
+} // namespace ledger
diff --git a/src/report.h b/src/report.h
new file mode 100644
index 00000000..11a0b759
--- /dev/null
+++ b/src/report.h
@@ -0,0 +1,141 @@
+#ifndef _REPORT_H
+#define _REPORT_H
+
+#include "session.h"
+#include "transform.h"
+
+namespace ledger {
+
+typedef std::list<string> strings_list;
+
+class report_t : public xml::xpath_t::scope_t
+{
+ public:
+ string output_file;
+ string format_string;
+ string amount_expr;
+ string total_expr;
+ string date_output_format;
+
+ unsigned long budget_flags;
+
+ string account;
+ string pager;
+
+ bool show_totals;
+ 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)
+ {
+ TRACE_CTOR(report_t, "session_t *");
+ 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 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(string("t=") + locals->args[0].to_string());
+ }
+ void option_total(value_t&, xml::xpath_t::scope_t * locals) {
+ eval(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) {
+ string expr = (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 string& name, value_t& result,
+ xml::xpath_t::scope_t * locals);
+ virtual xml::xpath_t::op_t * lookup(const string& name);
+};
+
+string abbrev(const string& str, unsigned int width,
+ const bool is_account);
+
+} // namespace ledger
+
+#endif // _REPORT_H
diff --git a/src/session.cc b/src/session.cc
new file mode 100644
index 00000000..78fc2596
--- /dev/null
+++ b/src/session.cc
@@ -0,0 +1,220 @@
+#include "session.h"
+
+namespace ledger {
+
+unsigned int session_t::read_journal(std::istream& in,
+ journal_t * journal,
+ account_t * master,
+ const string * original_file)
+{
+ if (! master)
+ master = journal->master;
+
+ 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 string& path,
+ journal_t * journal,
+ account_t * master,
+ const string * original_file)
+{
+ journal->sources.push_back(path);
+
+ if (access(path.c_str(), R_OK) == -1)
+ throw_(exception, "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_(exception, "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 string& master_account)
+{
+ TRACE_START(parser, 1, "Parsing journal file");
+
+ journal_t * journal = new_journal();
+ journal->document = new xml::document_t;
+ journal->document->set_top(xml::wrap_node(journal->document, journal));
+
+ unsigned int entry_count = 0;
+
+ DEBUG_("ledger.cache", "3. use_cache = " << use_cache);
+
+ if (use_cache && ! cache_file.empty() &&
+ ! data_file.empty()) {
+ DEBUG_("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());
+
+ 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_(exception, "Entries not allowed in price history file");
+ } else {
+ DEBUG_("ledger.cache", "read price database " << journal->price_db);
+ journal->sources.pop_back();
+ }
+ }
+
+ DEBUG_("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);
+ }
+ }
+
+ VERIFY(journal->valid());
+
+ if (entry_count == 0)
+ throw_(exception, "Failed to locate any journal entries; "
+ "did you specify a valid file with -f?");
+
+ TRACE_STOP(parser, 1);
+
+ return journal;
+}
+
+bool session_t::resolve(const 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") {
+ // jww (2007-04-18): What to do here?
+#if 0
+ result.set_string(moment_t::output_format);
+#endif
+ 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 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 'd':
+ if (std::strcmp(p, "debug") == 0)
+ return MAKE_FUNCTOR(session_t, option_debug);
+ break;
+
+ case 'f':
+ if (! *(p + 1) || std::strcmp(p, "file") == 0)
+ return MAKE_FUNCTOR(session_t, option_file);
+ break;
+
+ case 't':
+ if (std::strcmp(p, "trace") == 0)
+ return MAKE_FUNCTOR(session_t, option_trace);
+ break;
+
+ case 'v':
+ if (! *(p + 1) || std::strcmp(p, "verbose") == 0)
+ return MAKE_FUNCTOR(session_t, option_verbose);
+ else if (std::strcmp(p, "verify") == 0)
+ return MAKE_FUNCTOR(session_t, option_verify);
+ break;
+ }
+ }
+ break;
+ }
+
+ return xml::xpath_t::scope_t::lookup(name);
+}
+
+// jww (2007-04-26): All of Ledger should be accessed through a
+// session_t object
+void initialize()
+{
+ IF_VERIFY()
+ initialize_memory_tracing();
+
+ amount_t::initialize();
+ xml::xpath_t::initialize();
+}
+
+void shutdown()
+{
+ xml::xpath_t::shutdown();
+ amount_t::shutdown();
+
+ IF_VERIFY() {
+ INFO("Ledger shutdown (Boost/libstdc++ may still hold memory)");
+ shutdown_memory_tracing();
+ } else {
+ INFO("Ledger shutdown");
+ }
+}
+
+} // namespace ledger
diff --git a/src/session.h b/src/session.h
new file mode 100644
index 00000000..a1fd304b
--- /dev/null
+++ b/src/session.h
@@ -0,0 +1,197 @@
+#ifndef _SESSION_H
+#define _SESSION_H
+
+#include "journal.h"
+#include "parser.h"
+#include "register.h"
+
+namespace ledger {
+
+class session_t : public xml::xpath_t::scope_t
+{
+ public:
+ string init_file;
+ string data_file;
+ string cache_file;
+ string price_db;
+
+ string register_format;
+ string wide_register_format;
+ string print_format;
+ string balance_format;
+ string equity_format;
+ string plot_amount_format;
+ string plot_total_format;
+ string write_hdr_format;
+ string write_xact_format;
+ string prices_format;
+ string pricesdb_format;
+
+ unsigned long pricing_leeway;
+
+ bool download_quotes;
+ bool use_cache;
+ bool cache_dirty;
+
+ moment_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),
+
+ now(now),
+
+ elision_style(ABBREVIATE),
+ abbrev_length(2),
+
+ ansi_codes(false),
+ ansi_invert(false) {
+ TRACE_CTOR(session_t, "xml::xpath_t::scope_t *");
+ }
+
+ virtual ~session_t() {
+ TRACE_DTOR(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 string * original_file = NULL);
+
+ unsigned int read_journal(const string& path,
+ journal_t * journal,
+ account_t * master = NULL,
+ const string * original_file = NULL);
+
+ void read_init();
+
+ journal_t * read_data(const 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 string& name, value_t& result,
+ xml::xpath_t::scope_t * locals = NULL);
+ virtual xml::xpath_t::op_t * lookup(const string& name);
+
+ //
+ // Debug options
+ //
+
+ void option_verify(value_t&) {}
+ void option_trace(value_t&, xml::xpath_t::scope_t * locals) {}
+ void option_debug(value_t&, xml::xpath_t::scope_t * locals) {}
+
+ void option_verbose(value_t&) {
+ if (_log_level < LOG_INFO)
+ _log_level = LOG_INFO;
+ }
+
+ //
+ // Option handlers
+ //
+
+ void option_file(value_t&, xml::xpath_t::scope_t * locals) {
+ data_file = locals->args.to_string();
+ }
+
+#if 0
+#if defined(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
+#endif
+};
+
+void initialize();
+void shutdown();
+
+} // namespace ledger
+
+#endif // _SESSION_H
diff --git a/src/system.hh b/src/system.hh
new file mode 100644
index 00000000..bf376deb
--- /dev/null
+++ b/src/system.hh
@@ -0,0 +1,99 @@
+#ifndef _SYSTEM_HH
+#define _SYSTEM_HH
+
+/**
+ * @file system.hh
+ * @author John Wiegley
+ * @date Mon Apr 23 03:43:05 2007
+ *
+ * @brief All system headers needed by Ledger.
+ *
+ * These are collected here so that a pre-compiled header can be made.
+ * None of these header files (with the exception of acconf.h, when
+ * configure is re-run) are expected to change.
+ */
+
+#include "acconf.h"
+
+#if defined(__GNUG__) && __GNUG__ < 3
+#define _XOPEN_SOURCE
+#endif
+
+#include <algorithm>
+#include <exception>
+#include <iostream>
+#include <streambuf>
+#include <iomanip>
+#include <fstream>
+#include <sstream>
+#include <iterator>
+#include <list>
+#include <map>
+#include <memory>
+#include <new>
+#include <stack>
+#include <string>
+#include <vector>
+
+#if defined(__GNUG__) && __GNUG__ < 3
+namespace std {
+ inline ostream & right (ostream & i) {
+ i.setf(i.right, i.adjustfield);
+ return i;
+ }
+ inline ostream & left (ostream & i) {
+ i.setf(i.left, i.adjustfield);
+ return i;
+ }
+}
+#endif
+
+#include <cassert>
+#include <cctype>
+#include <cstdarg>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <ctime>
+
+#if defined __FreeBSD__ && __FreeBSD__ <= 4
+// FreeBSD has a broken isspace macro, so don't use it
+#undef isspace(c)
+#endif
+
+#include <sys/stat.h>
+
+#ifdef WIN32
+#include <io.h>
+#else
+#include <unistd.h>
+#endif
+
+#if defined(HAVE_GETPWUID) || defined(HAVE_GETPWNAM)
+#include <pwd.h>
+#endif
+
+#if defined(HAVE_NL_LANGINFO)
+#include <langinfo.h>
+#endif
+
+#include <gmp.h>
+
+#define HAVE_GDTOA 1
+#ifdef HAVE_GDTOA
+#include <gdtoa/gdtoa.h>
+#endif
+
+extern "C" {
+#if defined(HAVE_EXPAT)
+#include <expat.h> // expat XML parser
+#elif defined(HAVE_XMLPARSE)
+#include <xmlparse.h> // expat XML parser
+#endif
+}
+
+#if defined(HAVE_LIBOFX)
+#include <libofx.h>
+#endif
+
+#endif // _SYSTEM_HH
diff --git a/src/textual.cc b/src/textual.cc
new file mode 100644
index 00000000..ab657898
--- /dev/null
+++ b/src/textual.cc
@@ -0,0 +1,966 @@
+#include "textual.h"
+#include "session.h"
+
+#define TIMELOG_SUPPORT 1
+
+namespace ledger {
+
+#define MAX_LINE 1024
+
+static string path;
+static unsigned int linenum;
+static unsigned int src_idx;
+static accounts_map account_aliases;
+
+static std::list<std::pair<string, int> > include_stack;
+
+#ifdef TIMELOG_SUPPORT
+struct time_entry_t {
+ moment_t checkin;
+ account_t * account;
+ string desc;
+};
+std::list<time_entry_t> time_entries;
+#endif
+
+inline char * next_element(char * buf, bool variable = false)
+{
+ for (char * p = buf; *p; p++) {
+ if (! (*p == ' ' || *p == '\t'))
+ continue;
+
+ if (! variable) {
+ *p = '\0';
+ return skip_ws(p + 1);
+ }
+ else if (*p == '\t') {
+ *p = '\0';
+ return skip_ws(p + 1);
+ }
+ else if (*(p + 1) == ' ') {
+ *p = '\0';
+ return skip_ws(p + 2);
+ }
+ }
+ return NULL;
+}
+
+static inline void
+parse_amount_expr(std::istream& in, journal_t *,
+ transaction_t& xact, amount_t& amount,
+ unsigned short flags = 0)
+{
+ xml::xpath_t xpath(in, flags | XPATH_PARSE_RELAXED | XPATH_PARSE_PARTIAL);
+
+ DEBUG_("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed an amount expression");
+
+#if 0
+ IF_DEBUG_("ledger.textual.parse") {
+ if (_debug_stream) {
+ xpath.dump(*_debug_stream);
+ *_debug_stream << std::endl;
+ }
+ }
+#endif
+
+ amount = xpath.calc(static_cast<xml::transaction_node_t *>(xact.data)).to_amount();
+
+ DEBUG_("ledger.textual.parse", "line " << linenum << ": " <<
+ "The transaction amount is " << amount);
+}
+
+transaction_t * parse_transaction(char * line,
+ journal_t * journal,
+ account_t * account,
+ entry_t * entry = NULL)
+{
+ // The account will be determined later...
+ std::auto_ptr<transaction_t> xact(new transaction_t(NULL));
+
+ // First cut up the input line into its various parts.
+
+ char * state = NULL;
+ char * account_path = NULL;
+ char * amount = NULL;
+ char * note = NULL;
+
+ char * p = line;
+
+ if (*p == '*' || *p == '!')
+ state = p++;
+
+ account_path = skip_ws(p);
+
+ amount = next_element(account_path, true);
+ if (amount) {
+ char * p = amount;
+ while (*p && *p != ';')
+ p++;
+
+ if (*p == ';') {
+ *p++ = '\0';
+ note = skip_ws(p);
+ }
+
+ p = amount + (std::strlen(amount) - 1);
+ while (p > amount && std::isspace(*p))
+ p--;
+
+ if (std::isspace(*(p + 1)))
+ *++p = '\0';
+ }
+
+ string err_desc;
+#if 0
+ try {
+#endif
+
+ xact->entry = entry; // this might be NULL
+
+ // Parse the state flag
+
+ if (state)
+ switch (*state) {
+ case '*':
+ xact->state = transaction_t::CLEARED;
+ DEBUG_("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed the CLEARED flag");
+ break;
+ case '!':
+ xact->state = transaction_t::PENDING;
+ DEBUG_("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed the PENDING flag");
+ break;
+ }
+
+ // Parse the account name
+
+ char * b = &account_path[0];
+ char * e = &account_path[std::strlen(account_path) - 1];
+ if ((*b == '[' && *e == ']') ||
+ (*b == '(' && *e == ')')) {
+ xact->flags |= TRANSACTION_VIRTUAL;
+ DEBUG_("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed a virtual account name");
+ if (*b == '[') {
+ xact->flags |= TRANSACTION_BALANCE;
+ DEBUG_("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed a balanced virtual account name");
+ }
+ *account_path++ = '\0';
+ *e = '\0';
+ }
+
+ DEBUG_("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed account name " << account_path);
+ if (account_aliases.size() > 0) {
+ accounts_map::const_iterator i = account_aliases.find(account_path);
+ if (i != account_aliases.end())
+ xact->account = (*i).second;
+ }
+ if (! xact->account)
+ xact->account = account->find_account(account_path);
+
+ // Parse the optional amount
+
+ if (amount && *amount) {
+ std::istringstream in(amount);
+
+ try {
+ // jww (2006-09-15): Make sure it doesn't gobble up the upcoming @ symbol
+
+ unsigned long beg = (long)in.tellg();
+
+ xact->amount.parse(in, AMOUNT_PARSE_NO_REDUCE);
+
+ char c;
+ if (! in.eof() && (c = peek_next_nonws(in)) != '@' &&
+ c != ';' && ! in.eof()) {
+ in.seekg(beg, std::ios::beg);
+
+ if (xact->entry) {
+ // Create a report item for this entry, so the transaction
+ // below may refer to it
+
+ if (! xact->entry->data)
+ xact->entry->data = xml::wrap_node(journal->document, xact->entry,
+ journal->document->top);
+
+ xact->data = xml::wrap_node(journal->document, xact.get(),
+ xact->entry->data);
+ }
+
+ parse_amount_expr(in, journal, *xact, xact->amount,
+ XPATH_PARSE_NO_REDUCE);
+
+ if (xact->entry) {
+ delete static_cast<xml::transaction_node_t *>(xact->data);
+ xact->data = NULL;
+ }
+
+ unsigned long end = (long)in.tellg();
+
+ xact->amount_expr = string(line, beg, end - beg);
+ }
+ }
+ catch (exception& err) {
+ err_desc = "While parsing transaction amount:";
+ throw;
+ }
+
+ // Parse the optional cost (@ PER-UNIT-COST, @@ TOTAL-COST)
+
+ if (in.good() && ! in.eof()) {
+ char c = peek_next_nonws(in);
+ if (c == '@') {
+ DEBUG_("ledger.textual.parse", "line " << linenum << ": " <<
+ "Found a price indicator");
+ bool per_unit = true;
+ in.get(c);
+ if (in.peek() == '@') {
+ in.get(c);
+ per_unit = false;
+ DEBUG_("ledger.textual.parse", "line " << linenum << ": " <<
+ "And it's for a total price");
+ }
+
+ if (in.good() && ! in.eof()) {
+ xact->cost = new amount_t;
+
+ try {
+ unsigned long beg = (long)in.tellg();
+
+ xact->cost->parse(in);
+
+ unsigned long end = (long)in.tellg();
+
+ if (per_unit)
+ xact->cost_expr = (string("@") +
+ string(amount, beg, end - beg));
+ else
+ xact->cost_expr = (string("@@") +
+ string(amount, beg, end - beg));
+ }
+ catch (exception& err) {
+ err_desc = "While parsing transaction cost:";
+ throw;
+ }
+
+ if (*xact->cost < 0)
+ throw_(parse_exception, "A transaction's cost may not be negative");
+
+ amount_t per_unit_cost(*xact->cost);
+ if (per_unit)
+ *xact->cost *= xact->amount.number();
+ else
+ per_unit_cost /= xact->amount.number();
+
+ if (xact->amount.commodity() &&
+ ! xact->amount.commodity().annotated)
+ xact->amount.annotate_commodity(per_unit_cost,
+ xact->entry->actual_date(),
+ xact->entry->code);
+
+ DEBUG_("ledger.textual.parse", "line " << linenum << ": " <<
+ "Total cost is " << *xact->cost);
+ DEBUG_("ledger.textual.parse", "line " << linenum << ": " <<
+ "Per-unit cost is " << per_unit_cost);
+ DEBUG_("ledger.textual.parse", "line " << linenum << ": " <<
+ "Annotated amount is " << xact->amount);
+ DEBUG_("ledger.textual.parse", "line " << linenum << ": " <<
+ "Bare amount is " << xact->amount.number());
+ }
+ }
+ }
+
+ xact->amount.in_place_reduce();
+
+ DEBUG_("ledger.textual.parse", "line " << linenum << ": " <<
+ "Reduced amount is " << xact->amount);
+ }
+
+ // Parse the optional note
+
+ if (note) {
+ xact->note = note;
+ DEBUG_("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed a note '" << xact->note << "'");
+
+ if (char * b = std::strchr(xact->note.c_str(), '['))
+ if (char * e = std::strchr(xact->note.c_str(), ']')) {
+ char buf[256];
+ std::strncpy(buf, b + 1, e - b - 1);
+ buf[e - b - 1] = '\0';
+
+ DEBUG_("ledger.textual.parse", "line " << linenum << ": " <<
+ "Parsed a transaction date " << buf);
+
+ if (char * p = std::strchr(buf, '=')) {
+ *p++ = '\0';
+ xact->_date_eff = parse_datetime(p);
+ }
+ if (buf[0])
+ xact->_date = parse_datetime(buf);
+ }
+ }
+
+ return xact.release();
+
+#if 0
+ }
+ catch (error * err) {
+ err->context.push_back
+ (new line_context(line, -1, ! err_desc.empty() ?
+ err_desc : "While parsing transaction:"));
+ throw err;
+ }
+#endif
+}
+
+bool parse_transactions(std::istream& in,
+ journal_t * journal,
+ account_t * account,
+ entry_base_t& entry,
+ const string& /* kind */,
+ unsigned long beg_pos)
+{
+ static char line[MAX_LINE + 1];
+ bool added = false;
+
+ while (! in.eof() && (in.peek() == ' ' || in.peek() == '\t')) {
+ in.getline(line, MAX_LINE);
+ if (in.eof())
+ break;
+
+ beg_pos += std::strlen(line) + 1;
+ linenum++;
+
+ char * p = skip_ws(line);
+ if (! *p || *p == '\r' || *p == '\n')
+ break;
+
+ if (transaction_t * xact = parse_transaction(p, journal, account)) {
+ entry.add_transaction(xact);
+ added = true;
+ }
+ }
+
+ return added;
+}
+
+entry_t * parse_entry(std::istream& in, char * line, journal_t * journal,
+ account_t * master, textual_parser_t& /* parser */,
+ unsigned long beg_pos)
+{
+ TRACE_START(entry_text, 1, "Time spent preparing entry text:");
+
+ std::auto_ptr<entry_t> curr(new entry_t);
+
+ // First cut up the input line into its various parts.
+
+ char * date = NULL;
+ char * date_eff = NULL;
+ char * statep = NULL;
+ char * code = NULL;
+ char * payee = NULL;
+
+ date = line;
+
+ char * p = line;
+
+ while (*p && (std::isdigit(*p) || *p == '/' || *p == '.' || *p == '-'))
+ p++;
+ assert(*p);
+
+ if (*p == '=') {
+ *p++ = '\0';
+ date_eff = p;
+
+ while (*p && (std::isdigit(*p) || *p == '/' || *p == '.' || *p == '-'))
+ p++;
+ assert(*p);
+ } else {
+ *p++ = '\0';
+ }
+
+ p = skip_ws(p);
+
+ if (*p == '*' || *p == '!') {
+ statep = p;
+ p++; *p++ = '\0';
+
+ p = skip_ws(p);
+ }
+
+ if (*p == '(') {
+ code = ++p;
+ while (*p && *p != ')')
+ p++;
+ assert(*p);
+ *p++ = '\0';
+
+ p = skip_ws(p);
+ }
+
+ payee = p;
+
+ p = payee + (std::strlen(payee) - 1);
+ while (p > payee && std::isspace(*p))
+ p--;
+
+ if (std::isspace(*(p + 1)))
+ *++p = '\0';
+
+ TRACE_STOP(entry_text, 1);
+
+ // Parse the date
+
+ TRACE_START(entry_date, 1, "Time spent parsing entry dates:");
+
+ curr->_date = parse_datetime(date);
+
+ if (date_eff)
+ curr->_date_eff = parse_datetime(date_eff);
+
+ TRACE_STOP(entry_date, 1);
+
+ // Parse the optional cleared flag: *
+
+ TRACE_START(entry_details, 1, "Time spent parsing entry details:");
+
+ transaction_t::state_t state = transaction_t::UNCLEARED;
+ if (statep) {
+ switch (*statep) {
+ case '*':
+ state = transaction_t::CLEARED;
+ break;
+ case '!':
+ state = transaction_t::PENDING;
+ break;
+ }
+ }
+
+ // Parse the optional code: (TEXT)
+
+ if (code)
+ curr->code = code;
+
+ // Parse the payee/description text
+
+ assert(payee);
+ curr->payee = *payee != '\0' ? payee : "<Unspecified payee>";
+
+ TRACE_STOP(entry_details, 1);
+
+ // Parse all of the transactions associated with this entry
+
+ TRACE_START(entry_xacts, 1, "Time spent parsing transactions:");
+
+ unsigned long end_pos;
+ unsigned long beg_line = linenum;
+
+ while (! in.eof() && (in.peek() == ' ' || in.peek() == '\t')) {
+ line[0] = '\0';
+ in.getline(line, MAX_LINE);
+ if (in.eof() || line[0] == '\0')
+ break;
+ end_pos = beg_pos + std::strlen(line) + 1;
+ linenum++;
+
+ char * p = skip_ws(line);
+ if (! *p || *p == '\r' || *p == '\n')
+ break;
+
+ if (transaction_t * xact = parse_transaction(p, journal, master,
+ curr.get())) {
+ if (state != transaction_t::UNCLEARED &&
+ xact->state == transaction_t::UNCLEARED)
+ xact->state = state;
+
+ xact->beg_pos = beg_pos;
+ xact->beg_line = beg_line;
+ xact->end_pos = end_pos;
+ xact->end_line = linenum;
+ beg_pos = end_pos;
+
+ curr->add_transaction(xact);
+ }
+
+ if (in.eof())
+ break;
+ }
+
+ if (curr->data) {
+ delete static_cast<xml::entry_node_t *>(curr->data);
+ curr->data = NULL;
+ }
+
+ TRACE_STOP(entry_xacts, 1);
+
+ return curr.release();
+}
+
+template <typename T>
+struct push_var {
+ T& var;
+ T prev;
+ push_var(T& _var) : var(_var), prev(var) {}
+ ~push_var() { var = prev; }
+};
+
+static inline void parse_symbol(char *& p, string& symbol)
+{
+ if (*p == '"') {
+ char * q = std::strchr(p + 1, '"');
+ if (! q)
+ throw_(parse_exception, "Quoted commodity symbol lacks closing quote");
+ symbol = string(p + 1, 0, q - p - 1);
+ p = q + 2;
+ } else {
+ char * q = next_element(p);
+ symbol = p;
+ if (q)
+ p = q;
+ else
+ p += symbol.length();
+ }
+ if (symbol.empty())
+ throw_(parse_exception, "Failed to parse commodity");
+}
+
+bool textual_parser_t::test(std::istream& in) const
+{
+ char buf[5];
+
+ in.read(buf, 5);
+ if (std::strncmp(buf, "<?xml", 5) == 0) {
+#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE)
+ throw_(parse_exception, "Ledger file contains XML data, but format was not recognized");
+#else
+ throw_(parse_exception, "Ledger file contains XML data, but no XML support present");
+#endif
+ }
+
+ in.clear();
+ in.seekg(0, std::ios::beg);
+ assert(in.good());
+ return true;
+}
+
+static void clock_out_from_timelog(const moment_t& when,
+ account_t * account,
+ const char * desc,
+ journal_t * journal)
+{
+ time_entry_t event;
+
+ if (time_entries.size() == 1) {
+ event = time_entries.back();
+ time_entries.clear();
+ }
+ else if (time_entries.empty()) {
+ throw_(parse_exception, "Timelog check-out event without a check-in");
+ }
+ else if (! account) {
+ throw_(parse_exception,
+ "When multiple check-ins are active, checking out requires an account");
+ }
+ else {
+ bool found = false;
+
+ for (std::list<time_entry_t>::iterator i = time_entries.begin();
+ i != time_entries.end();
+ i++)
+ if (account == (*i).account) {
+ event = *i;
+ found = true;
+ time_entries.erase(i);
+ break;
+ }
+
+ if (! found)
+ throw_(parse_exception,
+ "Timelog check-out event does not match any current check-ins");
+ }
+
+ if (desc && event.desc.empty()) {
+ event.desc = desc;
+ desc = NULL;
+ }
+
+ std::auto_ptr<entry_t> curr(new entry_t);
+ curr->_date = when;
+ curr->code = desc ? desc : "";
+ curr->payee = event.desc;
+
+ if (curr->_date < event.checkin)
+ throw_(parse_exception,
+ "Timelog check-out date less than corresponding check-in");
+
+ char buf[32];
+ std::sprintf(buf, "%lds", (long)(curr->_date - event.checkin).total_seconds());
+ amount_t amt;
+ amt.parse(buf);
+
+ transaction_t * xact
+ = new transaction_t(event.account, amt, TRANSACTION_VIRTUAL);
+ xact->state = transaction_t::CLEARED;
+ curr->add_transaction(xact);
+
+ if (! journal->add_entry(curr.get()))
+ throw_(parse_exception, "Failed to record 'out' timelog entry");
+ else
+ curr.release();
+}
+
+unsigned int textual_parser_t::parse(std::istream& in,
+ journal_t * journal,
+ account_t * master,
+ const string * original_file)
+{
+ static bool added_auto_entry_hook = false;
+ static char line[MAX_LINE + 1];
+ unsigned int count = 0;
+ unsigned int errors = 0;
+
+ TRACE_START(parsing_total, 1, "Total time spent parsing text:");
+
+ std::list<account_t *> account_stack;
+
+ auto_entry_finalizer_t auto_entry_finalizer(journal);
+
+ if (! master && journal)
+ master = journal->master;
+
+ account_stack.push_front(master);
+
+ path = journal ? journal->sources.back() : *original_file;
+ src_idx = journal ? journal->sources.size() - 1 : 0;
+ linenum = 1;
+
+ INFO("Parsing file '" << path << "'");
+
+ unsigned long beg_pos = in.tellg();
+ unsigned long end_pos;
+ unsigned long beg_line = linenum;
+
+ while (in.good() && ! in.eof()) {
+#if 0
+ try {
+#endif
+ in.getline(line, MAX_LINE);
+ if (in.eof())
+ break;
+ end_pos = beg_pos + std::strlen(line) + 1;
+ linenum++;
+
+ switch (line[0]) {
+ case '\0':
+ case '\r':
+ break;
+
+ case ' ':
+ case '\t': {
+ char * p = skip_ws(line);
+ if (*p && *p != '\r')
+ throw_(parse_exception, "Line begins with whitespace");
+ break;
+ }
+
+#ifdef TIMELOG_SUPPORT
+ case 'i':
+ case 'I': {
+ string date(line, 2, 19);
+
+ char * p = skip_ws(line + 22);
+ char * n = next_element(p, true);
+
+ time_entry_t event;
+ event.desc = n ? n : "";
+ event.checkin = parse_datetime(date);
+ event.account = account_stack.front()->find_account(p);
+
+ if (! time_entries.empty())
+ for (std::list<time_entry_t>::iterator i = time_entries.begin();
+ i != time_entries.end();
+ i++)
+ if (event.account == (*i).account)
+ throw_(parse_exception,
+ "Cannot double check-in to the same account");
+
+ time_entries.push_back(event);
+ break;
+ }
+
+ case 'o':
+ case 'O':
+ if (time_entries.empty()) {
+ throw_(parse_exception, "Timelog check-out event without a check-in");
+ } else {
+ string date(line, 2, 19);
+
+ char * p = skip_ws(line + 22);
+ char * n = next_element(p, true);
+
+ clock_out_from_timelog
+ (parse_datetime(date),
+ p ? account_stack.front()->find_account(p) : NULL, n, journal);
+ count++;
+ }
+ break;
+#endif // TIMELOG_SUPPORT
+
+ case 'D': { // a default commodity for "entry"
+ amount_t amt(skip_ws(line + 1));
+ commodity_t::default_commodity = &amt.commodity();
+ break;
+ }
+
+ case 'A': // a default account for unbalanced xacts
+ journal->basket =
+ account_stack.front()->find_account(skip_ws(line + 1));
+ break;
+
+ case 'C': // a set of conversions
+ if (char * p = std::strchr(line + 1, '=')) {
+ *p++ = '\0';
+ parse_conversion(line + 1, p);
+ }
+ break;
+
+ case 'P': { // a pricing entry
+ char * date_field_ptr = skip_ws(line + 1);
+ char * time_field_ptr = next_element(date_field_ptr);
+ if (! time_field_ptr) break;
+ string date_field = date_field_ptr;
+
+ char * symbol_and_price;
+ moment_t datetime;
+
+ if (std::isdigit(time_field_ptr[0])) {
+ symbol_and_price = next_element(time_field_ptr);
+ if (! symbol_and_price) break;
+ datetime = parse_datetime(date_field + " " + time_field_ptr);
+ } else {
+ symbol_and_price = time_field_ptr;
+ datetime = parse_datetime(date_field);
+ }
+
+ string symbol;
+ parse_symbol(symbol_and_price, symbol);
+ amount_t price(symbol_and_price);
+
+ if (commodity_t * commodity = commodity_t::find_or_create(symbol))
+ commodity->add_price(datetime, price);
+ break;
+ }
+
+ case 'N': { // don't download prices
+ char * p = skip_ws(line + 1);
+ string symbol;
+ parse_symbol(p, symbol);
+
+ if (commodity_t * commodity = commodity_t::find_or_create(symbol))
+ commodity->add_flags(COMMODITY_STYLE_NOMARKET);
+ break;
+ }
+
+ case 'Y': // set current year
+#if 0
+ // jww (2007-04-18): Need to set this up again
+ date_t::current_year = std::atoi(skip_ws(line + 1));
+#endif
+ break;
+
+#ifdef TIMELOG_SUPPORT
+ case 'h':
+ case 'b':
+#endif
+ case ';': // comment
+ break;
+
+ case '-': // option setting
+ throw_(parse_exception, "Option settings are not allowed in journal files");
+
+ case '=': { // automated entry
+ if (! added_auto_entry_hook) {
+ journal->add_entry_finalizer(&auto_entry_finalizer);
+ added_auto_entry_hook = true;
+ }
+
+ auto_entry_t * ae = new auto_entry_t(skip_ws(line + 1));
+ if (parse_transactions(in, journal, account_stack.front(), *ae,
+ "automated", end_pos)) {
+ journal->auto_entries.push_back(ae);
+ ae->src_idx = src_idx;
+ ae->beg_pos = beg_pos;
+ ae->beg_line = beg_line;
+ ae->end_pos = end_pos;
+ ae->end_line = linenum;
+ }
+ break;
+ }
+
+ case '~': { // period entry
+ period_entry_t * pe = new period_entry_t(skip_ws(line + 1));
+ if (! pe->period)
+ throw_(parse_exception, string("Parsing time period '") + skip_ws(line + 1) + "'");
+
+ if (parse_transactions(in, journal, account_stack.front(), *pe,
+ "period", end_pos)) {
+ if (pe->finalize()) {
+ extend_entry_base(journal, *pe, true);
+ journal->period_entries.push_back(pe);
+ pe->src_idx = src_idx;
+ pe->beg_pos = beg_pos;
+ pe->beg_line = beg_line;
+ pe->end_pos = end_pos;
+ pe->end_line = linenum;
+ } else {
+ throw_(parse_exception, "Period entry failed to balance");
+ }
+ }
+ break;
+ }
+
+ case '@':
+ case '!': { // directive
+ char * p = next_element(line);
+ string word(line + 1);
+ if (word == "include") {
+ push_var<string> save_path(path);
+ push_var<unsigned int> save_src_idx(src_idx);
+ push_var<unsigned long> save_beg_pos(beg_pos);
+ push_var<unsigned long> save_end_pos(end_pos);
+ push_var<unsigned int> save_linenum(linenum);
+
+ path = p;
+ if (path[0] != '/' && path[0] != '\\' && path[0] != '~') {
+ string::size_type pos = save_path.prev.rfind('/');
+ if (pos == string::npos)
+ pos = save_path.prev.rfind('\\');
+ if (pos != string::npos)
+ path = string(save_path.prev, 0, pos + 1) + path;
+ }
+ path = resolve_path(path);
+
+ DEBUG_("ledger.textual.include", "line " << linenum << ": " <<
+ "Including path '" << path << "'");
+
+ include_stack.push_back(std::pair<string, int>
+ (journal->sources.back(), linenum - 1));
+ count += journal->session->read_journal(path, journal,
+ account_stack.front());
+ include_stack.pop_back();
+ }
+ else if (word == "account") {
+ account_t * acct;
+ acct = account_stack.front()->find_account(p);
+ account_stack.push_front(acct);
+ }
+ else if (word == "end") {
+ account_stack.pop_front();
+ }
+ else if (word == "alias") {
+ char * b = p;
+ if (char * e = std::strchr(b, '=')) {
+ char * z = e - 1;
+ while (std::isspace(*z))
+ *z-- = '\0';
+ *e++ = '\0';
+ e = skip_ws(e);
+
+ // Once we have an alias name (b) and the target account
+ // name (e), add a reference to the account in the
+ // `account_aliases' map, which is used by the transaction
+ // parser to resolve alias references.
+ account_t * acct = account_stack.front()->find_account(e);
+ std::pair<accounts_map::iterator, bool> result
+ = account_aliases.insert(accounts_pair(b, acct));
+ assert(result.second);
+ }
+ }
+ 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;
+ }
+
+ default: {
+ //unsigned int first_line = linenum;
+ unsigned long pos = end_pos;
+
+ TRACE_START(entries, 1, "Time spent handling entries:");
+ 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;
+ entry->beg_line = beg_line;
+ entry->end_pos = end_pos;
+ entry->end_line = linenum;
+ count++;
+ } else {
+ delete entry;
+ throw_(parse_exception, "Entry does not balance");
+ }
+ } else {
+ throw_(parse_exception, "Failed to parse entry");
+ }
+ TRACE_STOP(entries, 1);
+
+ end_pos = pos;
+ break;
+ }
+ }
+#if 0
+ }
+ catch (error * err) {
+ for (std::list<std::pair<string, int> >::reverse_iterator i =
+ include_stack.rbegin();
+ i != include_stack.rend();
+ i++)
+ err->context.push_back(new include_context((*i).first, (*i).second,
+ "In file included from"));
+ err->context.push_front(new file_context(path, linenum - 1));
+
+ std::cout.flush();
+ if (errors > 0 && err->context.size() > 1)
+ std::cerr << std::endl;
+ err->reveal_context(std::cerr, "Error");
+ std::cerr << err->what() << std::endl;
+ delete err;
+ errors++;
+ }
+#endif
+ beg_pos = end_pos;
+ }
+
+ if (! time_entries.empty()) {
+ for (std::list<time_entry_t>::iterator i = time_entries.begin();
+ i != time_entries.end();
+ i++)
+ clock_out_from_timelog(now, (*i).account, NULL, journal);
+ time_entries.clear();
+ }
+
+ if (added_auto_entry_hook)
+ journal->remove_entry_finalizer(&auto_entry_finalizer);
+
+ if (errors > 0)
+ throw (int)errors;
+
+ TRACE_STOP(parsing_total, 1);
+
+ return count;
+}
+
+} // namespace ledger
diff --git a/src/textual.h b/src/textual.h
new file mode 100644
index 00000000..bf05b1fc
--- /dev/null
+++ b/src/textual.h
@@ -0,0 +1,44 @@
+#ifndef _TEXTUAL_H
+#define _TEXTUAL_H
+
+#include "parser.h"
+
+namespace ledger {
+
+class textual_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 string * original_file = NULL);
+};
+
+#if 0
+void write_textual_journal(journal_t& journal, string path,
+ item_handler<transaction_t>& formatter,
+ const string& write_hdr_format,
+ std::ostream& out);
+#endif
+
+#if 0
+class include_context : public file_context {
+ public:
+ include_context(const string& file, unsigned long line,
+ const string& desc = "") throw()
+ : file_context(file, line, desc) {}
+ virtual ~include_context() throw() {}
+
+ virtual void describe(std::ostream& out) const throw() {
+ if (! desc.empty())
+ out << desc << ": ";
+ out << "\"" << file << "\", line " << line << ":" << std::endl;
+ }
+};
+#endif
+
+} // namespace ledger
+
+#endif // _TEXTUAL_H
diff --git a/src/times.cc b/src/times.cc
new file mode 100644
index 00000000..8966d598
--- /dev/null
+++ b/src/times.cc
@@ -0,0 +1,53 @@
+#include "times.h"
+
+namespace ledger {
+
+#ifdef BOOST_DATE_TIME_HAS_HIGH_PRECISION_CLOCK
+const ptime time_now = boost::posix_time::microsec_clock::universal_time();
+#else
+const ptime time_now = boost::posix_time::second_clock::universal_time();
+#endif
+const date date_now = boost::gregorian::day_clock::universal_day();
+
+#ifdef SUPPORT_DATE_AND_TIME
+const moment_t& now(time_now);
+#else
+const moment_t& now(date_now);
+#endif
+
+bool day_before_month = false;
+static bool day_before_month_initialized = false;
+
+moment_t parse_datetime(const char * str)
+{
+ if (! day_before_month_initialized) {
+#ifdef HAVE_NL_LANGINFO
+ const char * d_fmt = nl_langinfo(D_FMT);
+ if (d_fmt && std::strlen(d_fmt) > 1 && d_fmt[1] == 'd')
+ day_before_month = true;
+ day_before_month_initialized = true;
+#endif
+ }
+#if 0
+ return parse_abs_datetime(in);
+#else
+ int year = ((str[0] - '0') * 1000 +
+ (str[1] - '0') * 100 +
+ (str[2] - '0') * 10 +
+ (str[3] - '0'));
+
+ int mon = ((str[5] - '0') * 10 +
+ (str[6] - '0'));
+
+ int day = ((str[8] - '0') * 10 +
+ (str[9] - '0'));
+
+ return moment_t(boost::gregorian::date(year, mon, day));
+#endif
+}
+
+moment_t datetime_range_from_stream(std::istream& in)
+{
+}
+
+} // namespace ledger
diff --git a/src/times.h b/src/times.h
new file mode 100644
index 00000000..2cc0d7e4
--- /dev/null
+++ b/src/times.h
@@ -0,0 +1,102 @@
+#ifndef _TIMES_H
+#define _TIMES_H
+
+#include "utils.h"
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+namespace ledger {
+
+typedef boost::posix_time::ptime ptime;
+typedef ptime::time_duration_type time_duration;
+typedef boost::gregorian::date date;
+typedef boost::gregorian::date_duration date_duration;
+typedef boost::posix_time::seconds seconds;
+
+#define SUPPORT_DATE_AND_TIME 1
+#ifdef SUPPORT_DATE_AND_TIME
+
+typedef boost::posix_time::ptime moment_t;
+typedef moment_t::time_duration_type duration_t;
+
+inline bool is_valid_moment(const moment_t& moment) {
+ return ! moment.is_not_a_date_time();
+}
+
+#else // SUPPORT_DATE_AND_TIME
+
+typedef boost::gregorian::date moment_t;
+typedef boost::gregorian::date_duration duration_t;
+
+inline bool is_valid_moment(const moment_t& moment) {
+ return ! moment.is_not_a_date();
+}
+
+#endif // SUPPORT_DATE_AND_TIME
+
+extern const moment_t& now;
+
+DECLARE_EXCEPTION(datetime_exception);
+
+class interval_t
+{
+public:
+ interval_t() {}
+ interval_t(const string&) {}
+
+ operator bool() const {
+ return false;
+ }
+
+ void start(const moment_t&) {}
+ moment_t next() const { return moment_t(); }
+
+ void parse(std::istream&) {}
+};
+
+#if 0
+inline moment_t ptime_local_to_utc(const moment_t& when) {
+ struct std::tm tm_gmt = to_tm(when);
+ return boost::posix_time::from_time_t(std::mktime(&tm_gmt));
+}
+
+// jww (2007-04-18): I need to make a general parsing function
+// instead, and then make these into private methods.
+inline moment_t ptime_from_local_date_string(const string& date_string) {
+ return ptime_local_to_utc(moment_t(boost::gregorian::from_string(date_string),
+ time_duration()));
+}
+
+inline moment_t ptime_from_local_time_string(const string& time_string) {
+ return ptime_local_to_utc(boost::posix_time::time_from_string(time_string));
+}
+#endif
+
+moment_t parse_datetime(const char * str);
+
+inline moment_t parse_datetime(const string& str) {
+ return parse_datetime(str.c_str());
+}
+
+extern const ptime time_now;
+extern const date date_now;
+extern bool day_before_month;
+
+#if 0
+struct intorchar
+{
+ int ival;
+ string sval;
+
+ intorchar() : ival(-1) {}
+ intorchar(int val) : ival(val) {}
+ intorchar(const string& val) : ival(-1), sval(val) {}
+ intorchar(const intorchar& o) : ival(o.ival), sval(o.sval) {}
+};
+
+ledger::moment_t parse_abs_datetime(std::istream& input);
+#endif
+
+} // namespace ledger
+
+#endif // _TIMES_H
diff --git a/src/transform.cc b/src/transform.cc
new file mode 100644
index 00000000..b6a25cee
--- /dev/null
+++ b/src/transform.cc
@@ -0,0 +1,326 @@
+#include "transform.h"
+
+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
diff --git a/src/transform.h b/src/transform.h
new file mode 100644
index 00000000..1a5286a1
--- /dev/null
+++ b/src/transform.h
@@ -0,0 +1,136 @@
+#ifndef _TRANSFORM_H
+#define _TRANSFORM_H
+
+#include "xpath.h"
+
+namespace ledger {
+
+class transform_t {
+ public:
+ virtual ~transform_t() {}
+ 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 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 string& selection_path)
+ : select_transform(selection_path) {}
+
+ virtual void execute(xml::document_t * document);
+};
+#endif
+
+} // namespace ledger
+
+#endif // _TRANSFORM_H
diff --git a/src/utils.cc b/src/utils.cc
new file mode 100644
index 00000000..63c28c5c
--- /dev/null
+++ b/src/utils.cc
@@ -0,0 +1,687 @@
+#include "utils.h"
+#include "times.h"
+
+/**********************************************************************
+ *
+ * Assertions
+ */
+
+#if defined(ASSERTS_ON)
+
+namespace ledger {
+
+void debug_assert(const string& reason,
+ const string& func,
+ const string& file,
+ unsigned long line)
+{
+ std::ostringstream buf;
+ buf << "Assertion failed in \"" << file << "\", line " << line
+ << ": " << reason;
+ throw exception(buf.str(), context());
+}
+
+} // namespace ledger
+
+#endif
+
+/**********************************************************************
+ *
+ * Verification (basically, very slow asserts)
+ */
+
+#if defined(VERIFY_ON)
+
+namespace ledger {
+
+bool verify_enabled = false;
+
+typedef std::pair<std::string, std::size_t> allocation_pair;
+typedef std::map<void *, allocation_pair> live_memory_map;
+typedef std::pair<void *, allocation_pair> live_memory_pair;
+typedef std::multimap<void *, allocation_pair> live_objects_map;
+typedef std::pair<void *, allocation_pair> live_objects_pair;
+typedef std::pair<unsigned int, std::size_t> count_size_pair;
+typedef std::map<std::string, count_size_pair> object_count_map;
+typedef std::pair<std::string, count_size_pair> object_count_pair;
+
+static live_memory_map * live_memory = NULL;
+static object_count_map * live_memory_count = NULL;
+static object_count_map * total_memory_count = NULL;
+
+static bool memory_tracing_active = false;
+
+static live_objects_map * live_objects = NULL;
+static object_count_map * live_object_count = NULL;
+static object_count_map * total_object_count = NULL;
+static object_count_map * total_ctor_count = NULL;
+
+void initialize_memory_tracing()
+{
+ memory_tracing_active = false;
+
+ live_memory = new live_memory_map;
+ live_memory_count = new object_count_map;
+ total_memory_count = new object_count_map;
+
+ live_objects = new live_objects_map;
+ live_object_count = new object_count_map;
+ total_object_count = new object_count_map;
+ total_ctor_count = new object_count_map;
+
+ memory_tracing_active = true;
+}
+
+void shutdown_memory_tracing()
+{
+ memory_tracing_active = false;
+
+ if (live_objects) {
+ IF_DEBUG_("memory.counts")
+ report_memory(std::cerr, true);
+ else
+ IF_DEBUG_("memory.counts.live")
+ report_memory(std::cerr);
+ else if (live_objects->size() > 0)
+ report_memory(std::cerr);
+ }
+
+ delete live_memory; live_memory = NULL;
+ delete live_memory_count; live_memory_count = NULL;
+ delete total_memory_count; total_memory_count = NULL;
+
+ delete live_objects; live_objects = NULL;
+ delete live_object_count; live_object_count = NULL;
+ delete total_object_count; total_object_count = NULL;
+ delete total_ctor_count; total_ctor_count = NULL;
+}
+
+inline void add_to_count_map(object_count_map& the_map,
+ const char * name, std::size_t size)
+{
+ object_count_map::iterator k = the_map.find(name);
+ if (k != the_map.end()) {
+ (*k).second.first++;
+ (*k).second.second += size;
+ } else {
+ std::pair<object_count_map::iterator, bool> result =
+ the_map.insert(object_count_pair(name, count_size_pair(1, size)));
+ VERIFY(result.second);
+ }
+}
+
+std::size_t current_memory_size()
+{
+ std::size_t memory_size = 0;
+
+ for (object_count_map::const_iterator i = live_memory_count->begin();
+ i != live_memory_count->end();
+ i++)
+ memory_size += (*i).second.second;
+
+ return memory_size;
+}
+
+static void trace_new_func(void * ptr, const char * which, std::size_t size)
+{
+ memory_tracing_active = false;
+
+ if (! live_memory) return;
+
+ live_memory->insert(live_memory_pair(ptr, allocation_pair(which, size)));
+
+ add_to_count_map(*live_memory_count, which, size);
+ add_to_count_map(*total_memory_count, which, size);
+ add_to_count_map(*total_memory_count, "__ALL__", size);
+
+ memory_tracing_active = true;
+}
+
+static void trace_delete_func(void * ptr, const char * which)
+{
+ memory_tracing_active = false;
+
+ if (! live_memory) return;
+
+ // Ignore deletions of memory not tracked, since it's possible that
+ // a user (like boost) allocated a block of memory before memory
+ // tracking began, and then deleted it before memory tracking ended.
+ // If it really is a double-delete, the malloc library on OS/X will
+ // notify me.
+
+ live_memory_map::iterator i = live_memory->find(ptr);
+ if (i == live_memory->end())
+ return;
+
+ std::size_t size = (*i).second.second;
+ VERIFY((*i).second.first == which);
+
+ live_memory->erase(i);
+
+ object_count_map::iterator j = live_memory_count->find(which);
+ VERIFY(j != live_memory_count->end());
+
+ (*j).second.second -= size;
+ if (--(*j).second.first == 0)
+ live_memory_count->erase(j);
+
+ memory_tracing_active = true;
+}
+
+} // namespace ledger
+
+void * operator new(std::size_t size) throw (std::bad_alloc) {
+ void * ptr = std::malloc(size);
+ if (DO_VERIFY() && ledger::memory_tracing_active)
+ ledger::trace_new_func(ptr, "new", size);
+ return ptr;
+}
+void * operator new(std::size_t size, const std::nothrow_t&) throw() {
+ void * ptr = std::malloc(size);
+ if (DO_VERIFY() && ledger::memory_tracing_active)
+ ledger::trace_new_func(ptr, "new", size);
+ return ptr;
+}
+void * operator new[](std::size_t size) throw (std::bad_alloc) {
+ void * ptr = std::malloc(size);
+ if (DO_VERIFY() && ledger::memory_tracing_active)
+ ledger::trace_new_func(ptr, "new[]", size);
+ return ptr;
+}
+void * operator new[](std::size_t size, const std::nothrow_t&) throw() {
+ void * ptr = std::malloc(size);
+ if (DO_VERIFY() && ledger::memory_tracing_active)
+ ledger::trace_new_func(ptr, "new[]", size);
+ return ptr;
+}
+void operator delete(void * ptr) throw() {
+ if (DO_VERIFY() && ledger::memory_tracing_active)
+ ledger::trace_delete_func(ptr, "new");
+ std::free(ptr);
+}
+void operator delete(void * ptr, const std::nothrow_t&) throw() {
+ if (DO_VERIFY() && ledger::memory_tracing_active)
+ ledger::trace_delete_func(ptr, "new");
+ std::free(ptr);
+}
+void operator delete[](void * ptr) throw() {
+ if (DO_VERIFY() && ledger::memory_tracing_active)
+ ledger::trace_delete_func(ptr, "new[]");
+ std::free(ptr);
+}
+void operator delete[](void * ptr, const std::nothrow_t&) throw() {
+ if (DO_VERIFY() && ledger::memory_tracing_active)
+ ledger::trace_delete_func(ptr, "new[]");
+ std::free(ptr);
+}
+
+namespace ledger {
+
+inline void report_count_map(std::ostream& out, object_count_map& the_map)
+{
+ for (object_count_map::iterator i = the_map.begin();
+ i != the_map.end();
+ i++)
+ out << " " << std::right << std::setw(12) << (*i).second.first
+ << " " << std::right << std::setw(12) << (*i).second.second
+ << " " << std::left << (*i).first
+ << std::endl;
+}
+
+std::size_t current_objects_size()
+{
+ std::size_t objects_size = 0;
+
+ for (object_count_map::const_iterator i = live_object_count->begin();
+ i != live_object_count->end();
+ i++)
+ objects_size += (*i).second.second;
+
+ return objects_size;
+}
+
+void trace_ctor_func(void * ptr, const char * cls_name, const char * args,
+ std::size_t cls_size)
+{
+ memory_tracing_active = false;
+
+ if (! live_objects) return;
+
+ static char name[1024];
+ std::strcpy(name, cls_name);
+ std::strcat(name, "(");
+ std::strcat(name, args);
+ std::strcat(name, ")");
+
+ DEBUG_("verify.memory", "TRACE_CTOR " << ptr << " " << name);
+
+ live_objects->insert(live_objects_pair(ptr, allocation_pair(cls_name, cls_size)));
+
+ add_to_count_map(*live_object_count, cls_name, cls_size);
+ add_to_count_map(*total_object_count, cls_name, cls_size);
+ add_to_count_map(*total_object_count, "__ALL__", cls_size);
+ add_to_count_map(*total_ctor_count, name, cls_size);
+
+ memory_tracing_active = true;
+}
+
+void trace_dtor_func(void * ptr, const char * cls_name, std::size_t cls_size)
+{
+ memory_tracing_active = false;
+
+ if (! live_objects) return;
+
+ DEBUG_("ledger.trace.debug", "TRACE_DTOR " << ptr << " " << cls_name);
+
+ live_objects_map::iterator i = live_objects->find(ptr);
+ VERIFY(i != live_objects->end());
+
+ int ptr_count = live_objects->count(ptr);
+ for (int x = 0; x < ptr_count; x++, i++) {
+ if ((*i).second.first == cls_name) {
+ live_objects->erase(i);
+ break;
+ }
+ }
+
+ object_count_map::iterator k = live_object_count->find(cls_name);
+ VERIFY(k != live_object_count->end());
+
+ (*k).second.second -= cls_size;
+ if (--(*k).second.first == 0)
+ live_object_count->erase(k);
+
+ memory_tracing_active = true;
+}
+
+void report_memory(std::ostream& out, bool report_all)
+{
+ if (! live_memory) return;
+
+ if (live_memory_count->size() > 0) {
+ out << "NOTE: There may be memory held by Boost "
+ << "and libstdc++ after ledger::shutdown()" << std::endl;
+ out << "Live memory count:" << std::endl;
+ report_count_map(out, *live_memory_count);
+ }
+
+ if (live_memory->size() > 0) {
+ out << "Live memory:" << std::endl;
+
+ for (live_memory_map::const_iterator i = live_memory->begin();
+ i != live_memory->end();
+ i++)
+ out << " " << std::right << std::setw(7) << (*i).first
+ << " " << std::right << std::setw(7) << (*i).second.second
+ << " " << std::left << (*i).second.first
+ << std::endl;
+ }
+
+ if (report_all && total_memory_count->size() > 0) {
+ out << "Total memory counts:" << std::endl;
+ report_count_map(out, *total_memory_count);
+ }
+
+ if (live_object_count->size() > 0) {
+ out << "Live object count:" << std::endl;
+ report_count_map(out, *live_object_count);
+ }
+
+ if (live_objects->size() > 0) {
+ out << "Live objects:" << std::endl;
+
+ for (live_objects_map::const_iterator i = live_objects->begin();
+ i != live_objects->end();
+ i++)
+ out << " " << std::right << std::setw(7) << (*i).first
+ << " " << std::right << std::setw(7) << (*i).second.second
+ << " " << std::left << (*i).second.first
+ << std::endl;
+ }
+
+ if (report_all) {
+ if (total_object_count->size() > 0) {
+ out << "Total object counts:" << std::endl;
+ report_count_map(out, *total_object_count);
+ }
+
+ if (total_ctor_count->size() > 0) {
+ out << "Total constructor counts:" << std::endl;
+ report_count_map(out, *total_ctor_count);
+ }
+ }
+}
+
+#if ! defined(USE_BOOST_PYTHON)
+
+string::string() : std::string() {
+ TRACE_CTOR(string, "");
+}
+string::string(const string& str) : std::string(str) {
+ TRACE_CTOR(string, "const string&");
+}
+string::string(const std::string& str) : std::string(str) {
+ TRACE_CTOR(string, "const std::string&");
+}
+string::string(const int len, char x) : std::string(len, x) {
+ TRACE_CTOR(string, "const int, char");
+}
+string::string(const char * str) : std::string(str) {
+ TRACE_CTOR(string, "const char *");
+}
+string::string(const char * str, const char * end) : std::string(str, end) {
+ TRACE_CTOR(string, "const char *, const char *");
+}
+string::string(const string& str, int x) : std::string(str, x) {
+ TRACE_CTOR(string, "const string&, int");
+}
+string::string(const string& str, int x, int y) : std::string(str, x, y) {
+ TRACE_CTOR(string, "const string&, int, int");
+}
+string::string(const char * str, int x) : std::string(str, x) {
+ TRACE_CTOR(string, "const char *, int");
+}
+string::string(const char * str, int x, int y) : std::string(str, x, y) {
+ TRACE_CTOR(string, "const char *, int, int");
+}
+string::~string() {
+ TRACE_DTOR(string);
+}
+
+#endif
+
+} // namespace ledger
+
+#endif // VERIFY_ON
+
+/**********************************************************************
+ *
+ * Logging
+ */
+
+#if defined(LOGGING_ON)
+
+namespace ledger {
+
+log_level_t _log_level;
+std::ostream * _log_stream = &std::cerr;
+std::ostringstream _log_buffer;
+
+#if defined(TRACING_ON)
+unsigned int _trace_level;
+#endif
+
+#ifdef BOOST_DATE_TIME_HAS_HIGH_PRECISION_CLOCK
+#define CURRENT_TIME() boost::posix_time::microsec_clock::universal_time()
+#else
+#define CURRENT_TIME() boost::posix_time::second_clock::universal_time()
+#endif
+
+static inline void stream_memory_size(std::ostream& out, std::size_t size)
+{
+ if (size < 1024)
+ out << size << 'b';
+ else if (size < (1024 * 1024))
+ out << (double(size) / 1024.0) << 'K';
+ else if (size < (1024 * 1024 * 1024))
+ out << (double(size) / (1024.0 * 1024.0)) << 'M';
+ else if (size < (1024 * 1024 * 1024 * 1024))
+ out << (double(size) / (1024.0 * 1024.0 * 1024.0)) << 'G';
+ else
+ assert(false);
+}
+
+static bool logger_has_run = false;
+static ptime logger_start;
+
+bool logger_func(log_level_t level)
+{
+ unsigned long appender = 0;
+
+ if (! logger_has_run) {
+ logger_has_run = true;
+ logger_start = CURRENT_TIME();
+
+ IF_VERIFY()
+ *_log_stream << " TIME OBJSZ MEMSZ" << std::endl;
+
+ appender = (logger_start - now).total_milliseconds();
+ }
+
+ *_log_stream << std::right << std::setw(5)
+ << (CURRENT_TIME() - logger_start).total_milliseconds();
+
+ IF_VERIFY() {
+ *_log_stream << std::right << std::setw(6) << std::setprecision(3);
+ stream_memory_size(*_log_stream, current_objects_size());
+ *_log_stream << std::right << std::setw(6) << std::setprecision(3);
+ stream_memory_size(*_log_stream, current_memory_size());
+ }
+
+ *_log_stream << " " << std::left << std::setw(7);
+
+ switch (level) {
+ case LOG_CRIT: *_log_stream << "[CRIT]"; break;
+ case LOG_FATAL: *_log_stream << "[FATAL]"; break;
+ case LOG_ASSERT: *_log_stream << "[ASSRT]"; break;
+ case LOG_ERROR: *_log_stream << "[ERROR]"; break;
+ case LOG_VERIFY: *_log_stream << "[VERFY]"; break;
+ case LOG_WARN: *_log_stream << "[WARN]"; break;
+ case LOG_INFO: *_log_stream << "[INFO]"; break;
+ case LOG_EXCEPT: *_log_stream << "[EXCPT]"; break;
+ case LOG_DEBUG: *_log_stream << "[DEBUG]"; break;
+ case LOG_TRACE: *_log_stream << "[TRACE]"; break;
+
+ case LOG_OFF:
+ case LOG_ALL:
+ assert(false);
+ break;
+ }
+
+ *_log_stream << ' ' << _log_buffer.str();
+
+ if (appender)
+ *_log_stream << " (" << appender << "ms startup)";
+
+ *_log_stream << std::endl;
+
+ _log_buffer.str("");
+
+ return true;
+}
+
+} // namespace ledger
+
+#if defined(DEBUG_ON)
+
+#include <boost/regex.hpp>
+
+namespace ledger {
+
+std::string _log_category;
+
+} // namespace ledger
+
+#endif // DEBUG_ON
+#endif // LOGGING_ON
+
+/**********************************************************************
+ *
+ * Timers (allows log entries to specify cumulative time spent)
+ */
+
+#if defined(LOGGING_ON) && defined(TIMERS_ON)
+
+namespace ledger {
+
+struct timer_t {
+ log_level_t level;
+ ptime begin;
+ time_duration spent;
+ std::string description;
+ bool active;
+
+ timer_t(log_level_t _level, std::string _description)
+ : level(_level), begin(CURRENT_TIME()),
+ spent(time_duration(0, 0, 0, 0)),
+ description(_description), active(true) {}
+};
+
+typedef std::map<std::string, timer_t> timer_map;
+typedef std::pair<std::string, timer_t> timer_pair;
+
+static timer_map timers;
+
+void start_timer(const char * name, log_level_t lvl)
+{
+#if defined(VERIFY_ON)
+ memory_tracing_active = false;
+#endif
+
+ timer_map::iterator i = timers.find(name);
+ if (i == timers.end()) {
+ timers.insert(timer_pair(name, timer_t(lvl, _log_buffer.str())));
+ } else {
+ assert((*i).second.description == _log_buffer.str());
+ (*i).second.begin = CURRENT_TIME();
+ (*i).second.active = true;
+ }
+ _log_buffer.str("");
+
+#if defined(VERIFY_ON)
+ memory_tracing_active = true;
+#endif
+}
+
+void stop_timer(const char * name)
+{
+#if defined(VERIFY_ON)
+ memory_tracing_active = false;
+#endif
+
+ timer_map::iterator i = timers.find(name);
+ assert(i != timers.end());
+
+ (*i).second.spent += CURRENT_TIME() - (*i).second.begin;
+ (*i).second.active = false;
+
+#if defined(VERIFY_ON)
+ memory_tracing_active = true;
+#endif
+}
+
+void finish_timer(const char * name)
+{
+#if defined(VERIFY_ON)
+ memory_tracing_active = false;
+#endif
+
+ timer_map::iterator i = timers.find(name);
+ if (i == timers.end())
+ return;
+
+ time_duration spent = (*i).second.spent;
+ if ((*i).second.active) {
+ spent = CURRENT_TIME() - (*i).second.begin;
+ (*i).second.active = false;
+ }
+
+ _log_buffer << (*i).second.description << ' ';
+
+ bool need_paren =
+ (*i).second.description[(*i).second.description.size() - 1] != ':';
+
+ if (need_paren)
+ _log_buffer << '(';
+
+ _log_buffer << spent.total_milliseconds() << "ms";
+
+ if (need_paren)
+ _log_buffer << ')';
+
+ logger_func((*i).second.level);
+
+ timers.erase(i);
+
+#if defined(VERIFY_ON)
+ memory_tracing_active = true;
+#endif
+}
+
+} // namespace ledger
+
+#endif // LOGGING_ON && TIMERS_ON
+
+/**********************************************************************
+ *
+ * Exception handling
+ */
+
+namespace ledger {
+
+std::ostringstream _exc_buffer;
+
+} // namespace ledger
+
+/**********************************************************************
+ *
+ * General utility functions
+ */
+
+namespace ledger {
+
+string expand_path(const string& path)
+{
+ if (path.length() == 0 || path[0] != '~')
+ return path;
+
+ const char * pfx = NULL;
+ 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 {
+ string user(path, 1, pos == string::npos ?
+ 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;
+
+ string result(pfx);
+
+ if (pos == string::npos)
+ return result;
+
+ if (result.length() == 0 || result[result.length() - 1] != '/')
+ result += '/';
+
+ result += path.substr(pos + 1);
+
+ return result;
+}
+
+string resolve_path(const string& path)
+{
+ if (path[0] == '~')
+ return expand_path(path);
+ return path;
+}
+
+} // namespace ledger
diff --git a/src/utils.h b/src/utils.h
new file mode 100644
index 00000000..79595c71
--- /dev/null
+++ b/src/utils.h
@@ -0,0 +1,422 @@
+#ifndef _UTILS_H
+#define _UTILS_H
+
+#include <system.hh>
+
+/**********************************************************************
+ *
+ * Forward declarations
+ */
+
+namespace ledger {
+#if ! defined(USE_BOOST_PYTHON)
+ class string;
+#else
+ typedef std::string string;
+#endif
+}
+
+/**********************************************************************
+ *
+ * Default values
+ */
+
+#if defined(FULL_DEBUG)
+#define VERIFY_ON 1
+#define TRACING_ON 1
+#define DEBUG_ON 1
+#define TIMERS_ON 1
+#define FREE_MEMORY 1
+#elif defined(NO_DEBUG)
+#define NO_ASSERTS 1
+#define NO_LOGGING 1
+#else
+#define VERIFY_ON 1 // compiled in, use --verify to enable
+#define TRACING_ON 1 // use --trace X to enable
+#define TIMERS_ON 1 // jww (2007-04-25): is this correct?
+#endif
+
+/**********************************************************************
+ *
+ * Assertions
+ */
+
+#ifdef assert
+#undef assert
+#endif
+
+#if ! defined(NO_ASSERTS)
+#define ASSERTS_ON 1
+#endif
+#if defined(ASSERTS_ON)
+
+#include <boost/current_function.hpp>
+
+namespace ledger {
+ void debug_assert(const string& reason, const string& func,
+ const string& file, unsigned long line);
+}
+
+#define assert(x) \
+ ((x) ? ((void)0) : debug_assert(#x, BOOST_CURRENT_FUNCTION, \
+ __FILE__, __LINE__))
+
+#endif // ASSERTS_ON
+
+/**********************************************************************
+ *
+ * Verification (basically, very slow asserts)
+ */
+
+#if defined(VERIFY_ON)
+
+namespace ledger {
+
+extern bool verify_enabled;
+
+#define VERIFY(x) (ledger::verify_enabled ? assert(x) : ((void)0))
+#define DO_VERIFY() ledger::verify_enabled
+#define IF_VERIFY() if (DO_VERIFY())
+
+void initialize_memory_tracing();
+void shutdown_memory_tracing();
+
+std::size_t current_memory_size();
+std::size_t current_objects_size();
+
+void trace_ctor_func(void * ptr, const char * cls_name, const char * args,
+ std::size_t cls_size);
+void trace_dtor_func(void * ptr, const char * cls_name, std::size_t cls_size);
+
+#define TRACE_CTOR(cls, args) \
+ (DO_VERIFY() ? trace_ctor_func(this, #cls, args, sizeof(cls)) : ((void)0))
+#define TRACE_DTOR(cls) \
+ (DO_VERIFY() ? trace_dtor_func(this, #cls, sizeof(cls)) : ((void)0))
+
+void report_memory(std::ostream& out, bool report_all = false);
+
+#if ! defined(USE_BOOST_PYTHON)
+
+class string : public std::string
+{
+public:
+ string();
+ string(const string& str);
+ string(const std::string& str);
+ string(const int len, char x);
+ string(const char * str);
+ string(const char * str, const char * end);
+ string(const string& str, int x);
+ string(const string& str, int x, int y);
+ string(const char * str, int x);
+ string(const char * str, int x, int y);
+ ~string();
+};
+
+inline string operator+(const string& __lhs, const string& __rhs)
+{
+ string __str(__lhs);
+ __str.append(__rhs);
+ return __str;
+}
+
+string operator+(const char* __lhs, const string& __rhs);
+string operator+(char __lhs, const string& __rhs);
+
+inline string operator+(const string& __lhs, const char* __rhs)
+{
+ string __str(__lhs);
+ __str.append(__rhs);
+ return __str;
+}
+
+inline string operator+(const string& __lhs, char __rhs)
+{
+ typedef string __string_type;
+ typedef string::size_type __size_type;
+ __string_type __str(__lhs);
+ __str.append(__size_type(1), __rhs);
+ return __str;
+}
+
+inline bool operator==(const string& __lhs, const string& __rhs)
+{ return __lhs.compare(__rhs) == 0; }
+
+inline bool operator==(const char* __lhs, const string& __rhs)
+{ return __rhs.compare(__lhs) == 0; }
+
+inline bool operator==(const string& __lhs, const char* __rhs)
+{ return __lhs.compare(__rhs) == 0; }
+
+inline bool operator!=(const string& __lhs, const string& __rhs)
+{ return __rhs.compare(__lhs) != 0; }
+
+inline bool operator!=(const char* __lhs, const string& __rhs)
+{ return __rhs.compare(__lhs) != 0; }
+
+inline bool operator!=(const string& __lhs, const char* __rhs)
+{ return __lhs.compare(__rhs) != 0; }
+
+#endif
+
+} // namespace ledger
+
+#else // ! VERIFY_ON
+
+#define VERIFY(x)
+#define TRACE_CTOR(cls, args)
+#define TRACE_DTOR(cls)
+
+#endif // VERIFY_ON
+
+/**********************************************************************
+ *
+ * Logging
+ */
+
+#if ! defined(NO_LOGGING)
+#define LOGGING_ON 1
+#endif
+#if defined(LOGGING_ON)
+
+namespace ledger {
+
+enum log_level_t {
+ LOG_OFF = 0,
+ LOG_CRIT,
+ LOG_FATAL,
+ LOG_ASSERT,
+ LOG_ERROR,
+ LOG_VERIFY,
+ LOG_WARN,
+ LOG_INFO,
+ LOG_EXCEPT,
+ LOG_DEBUG,
+ LOG_TRACE,
+ LOG_ALL
+};
+
+extern log_level_t _log_level;
+extern std::ostream * _log_stream;
+extern std::ostringstream _log_buffer;
+
+bool logger_func(log_level_t level);
+
+#define LOGGER(cat) \
+ static const char * const _this_category = cat
+
+#if defined(TRACING_ON)
+
+extern unsigned int _trace_level;
+
+#define SHOW_TRACE(lvl) \
+ (_log_level >= LOG_TRACE && lvl <= _trace_level)
+#define TRACE(lvl, msg) \
+ (SHOW_TRACE(lvl) ? ((_log_buffer << msg), logger_func(LOG_TRACE)) : false)
+
+#else // TRACING_ON
+
+#define SHOW_TRACE(lvl) false
+#define TRACE(lvl, msg)
+
+#endif // TRACING_ON
+
+#if defined(DEBUG_ON)
+
+extern std::string _log_category;
+
+inline bool category_matches(const char * cat) {
+ return (_log_category == cat ||
+ (std::strlen(cat) > _log_category.size() + 1 &&
+ std::strncmp(cat, _log_category.c_str(),
+ _log_category.size()) == 0 &&
+ cat[_log_category.size()] == '.'));
+}
+
+#define SHOW_DEBUG_(cat) \
+ (_log_level >= LOG_DEBUG && category_matches(cat))
+#define SHOW_DEBUG() SHOW_DEBUG_(_this_category)
+
+#define DEBUG_(cat, msg) \
+ (SHOW_DEBUG_(cat) ? ((_log_buffer << msg), logger_func(LOG_DEBUG)) : false)
+#define DEBUG(msg) DEBUG_(_this_category, msg)
+
+#else // DEBUG_ON
+
+#define SHOW_DEBUG_(cat) false
+#define SHOW_DEBUG() false
+#define DEBUG_(cat, msg)
+#define DEBUG(msg)
+
+#endif // DEBUG_ON
+
+#define LOG_MACRO(level, msg) \
+ (_log_level >= level ? \
+ ((_log_buffer << msg), logger_func(level)) : false)
+
+#define SHOW_INFO() (_log_level >= LOG_INFO)
+#define SHOW_WARN() (_log_level >= LOG_WARN)
+#define SHOW_ERROR() (_log_level >= LOG_ERROR)
+#define SHOW_FATAL() (_log_level >= LOG_FATAL)
+#define SHOW_CRITICAL() (_log_level >= LOG_CRIT)
+
+#define INFO(msg) LOG_MACRO(LOG_INFO, msg)
+#define WARN(msg) LOG_MACRO(LOG_WARN, msg)
+#define ERROR(msg) LOG_MACRO(LOG_ERROR, msg)
+#define FATAL(msg) LOG_MACRO(LOG_FATAL, msg)
+#define CRITICAL(msg) LOG_MACRO(LOG_CRIT, msg)
+#define EXCEPTION(msg) LOG_MACRO(LOG_EXCEPT, msg)
+
+} // namespace ledger
+
+#else // ! LOGGING_ON
+
+#define LOGGER(cat)
+
+#define SHOW_TRACE(lvl) false
+#define SHOW_DEBUG_(cat) false
+#define SHOW_DEBUG() false
+#define SHOW_INFO() false
+#define SHOW_WARN() false
+#define SHOW_ERROR() false
+#define SHOW_FATAL() false
+#define SHOW_CRITICAL() false
+
+#define TRACE(lvl, msg)
+#define DEBUG(msg)
+#define DEBUG_(cat, msg)
+#define INFO(msg)
+#define WARN(msg)
+#define ERROR(msg)
+#define FATAL(msg)
+#define CRITICAL(msg)
+
+#endif // LOGGING_ON
+
+#define IF_TRACE(lvl) if (SHOW_TRACE(lvl))
+#define IF_DEBUG_(cat) if (SHOW_DEBUG_(cat))
+#define IF_DEBUG() if (SHOW_DEBUG())
+#define IF_INFO() if (SHOW_INFO())
+#define IF_WARN() if (SHOW_WARN())
+#define IF_ERROR() if (SHOW_ERROR())
+#define IF_FATAL() if (SHOW_FATAL())
+#define IF_CRITICAL() if (SHOW_CRITICAL())
+
+/**********************************************************************
+ *
+ * Timers (allows log entries to specify cumulative time spent)
+ */
+
+#if defined(LOGGING_ON) && defined(TIMERS_ON)
+
+namespace ledger {
+
+void start_timer(const char * name, log_level_t lvl);
+void stop_timer(const char * name);
+void finish_timer(const char * name);
+
+#if defined(TRACING_ON)
+#define TRACE_START(name, lvl, msg) \
+ (SHOW_TRACE(lvl) ? \
+ ((_log_buffer << msg), start_timer(#name, LOG_TRACE)) : ((void)0))
+#define TRACE_STOP(name, lvl) \
+ (SHOW_TRACE(lvl) ? stop_timer(#name) : ((void)0))
+#define TRACE_FINISH(name, lvl) \
+ (SHOW_TRACE(lvl) ? finish_timer(#name) : ((void)0))
+#else
+#define TRACE_START(name, lvl, msg)
+#define TRACE_STOP(name)
+#define TRACE_FINISH(name)
+#endif
+
+#if defined(DEBUG_ON)
+#define DEBUG_START_(name, cat, msg) \
+ (SHOW_DEBUG_(cat) ? \
+ ((_log_buffer << msg), start_timer(#name, LOG_DEBUG)) : ((void)0))
+#define DEBUG_START(name, msg) \
+ DEBUG_START_(name, _this_category, msg)
+#define DEBUG_STOP_(name, cat) \
+ (SHOW_DEBUG_(cat) ? stop_timer(#name) : ((void)0))
+#define DEBUG_STOP(name) \
+ DEBUG_STOP_(name, _this_category)
+#define DEBUG_FINISH_(name, cat) \
+ (SHOW_DEBUG_(cat) ? finish_timer(#name) : ((void)0))
+#define DEBUG_FINISH(name) \
+ DEBUG_FINISH_(name, _this_category)
+#else
+#define DEBUG_START(name, msg)
+#define DEBUG_START_(name, cat, msg)
+#define DEBUG_STOP(name)
+#define DEBUG_FINISH(name)
+#endif
+
+#define INFO_START(name, msg) \
+ (SHOW_INFO() ? \
+ ((_log_buffer << msg), start_timer(#name, LOG_INFO)) : ((void)0))
+#define INFO_STOP(name) \
+ (SHOW_INFO() ? stop_timer(#name) : ((void)0))
+#define INFO_FINISH(name) \
+ (SHOW_INFO() ? finish_timer(#name) : ((void)0))
+
+} // namespace ledger
+
+#else // ! (LOGGING_ON && TIMERS_ON)
+
+#define TRACE_START(lvl, msg, name)
+#define TRACE_STOP(name)
+#define TRACE_FINISH(name)
+
+#define DEBUG_START(name, msg)
+#define DEBUG_START_(name, cat, msg)
+#define DEBUG_STOP(name)
+#define DEBUG_FINISH(name)
+
+#define INFO_START(name, msg)
+#define INFO_STOP(name)
+#define INFO_FINISH(name)
+
+#endif // TIMERS_ON
+
+/**********************************************************************
+ *
+ * Exception handling
+ */
+
+#include "error.h"
+
+namespace ledger {
+
+extern std::ostringstream _exc_buffer;
+
+template <typename T>
+inline void throw_func(const std::string& message) {
+ _exc_buffer.str("");
+ throw T(message, context());
+}
+
+#define throw_(cls, msg) \
+ ((_exc_buffer << msg), throw_func<cls>(_exc_buffer.str()))
+
+} // namespace ledger
+
+/**********************************************************************
+ *
+ * General utility functions
+ */
+
+namespace ledger {
+
+string resolve_path(const string& path);
+
+#ifdef HAVE_REALPATH
+extern "C" char * realpath(const char *, char resolved_path[]);
+#endif
+
+inline const string& either_or(const string& first,
+ const string& second) {
+ return first.empty() ? second : first;
+}
+
+} // namespace ledger
+
+#endif // _UTILS_H
diff --git a/src/value.cc b/src/value.cc
new file mode 100644
index 00000000..914a49e2
--- /dev/null
+++ b/src/value.cc
@@ -0,0 +1,2311 @@
+#include "value.h"
+#include "xml.h"
+
+namespace ledger {
+
+bool value_t::to_boolean() const
+{
+ if (type == BOOLEAN) {
+ return *(bool *) data;
+ } else {
+ value_t temp(*this);
+ temp.in_place_cast(BOOLEAN);
+ return *(bool *) temp.data;
+ }
+}
+
+long value_t::to_integer() const
+{
+ if (type == INTEGER) {
+ return *(long *) data;
+ } else {
+ value_t temp(*this);
+ temp.in_place_cast(INTEGER);
+ return *(long *) temp.data;
+ }
+}
+
+moment_t value_t::to_datetime() const
+{
+ if (type == DATETIME) {
+ return *(moment_t *) data;
+ } else {
+ value_t temp(*this);
+ temp.in_place_cast(DATETIME);
+ return *(moment_t *) temp.data;
+ }
+}
+
+amount_t value_t::to_amount() const
+{
+ if (type == AMOUNT) {
+ return *(amount_t *) data;
+ } else {
+ value_t temp(*this);
+ temp.in_place_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.in_place_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.in_place_cast(BALANCE_PAIR);
+ return *(balance_pair_t *) temp.data;
+ }
+}
+
+string value_t::to_string() const
+{
+ if (type == STRING) {
+ return **(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_(value_exception, "Value is not an XML node");
+}
+
+void * value_t::to_pointer() const
+{
+ if (type == POINTER)
+ return *(void **) data;
+ else
+ throw_(value_exception, "Value is not a pointer");
+}
+
+value_t::sequence_t * value_t::to_sequence() const
+{
+ if (type == SEQUENCE)
+ return *(sequence_t **) data;
+ else
+ throw_(value_exception, "Value is not a sequence");
+}
+
+void value_t::destroy()
+{
+ switch (type) {
+ case AMOUNT:
+ ((amount_t *)data)->~amount_t();
+ break;
+ case BALANCE:
+ ((balance_t *)data)->~balance_t();
+ break;
+ case BALANCE_PAIR:
+ ((balance_pair_t *)data)->~balance_pair_t();
+ break;
+ case STRING:
+ delete *(string **) data;
+ break;
+ case SEQUENCE:
+ delete *(sequence_t **) data;
+ break;
+ default:
+ break;
+ }
+}
+
+void value_t::simplify()
+{
+ if (realzero()) {
+ DEBUG_("amounts.values.simplify", "Zeroing type " << type);
+ *this = 0L;
+ return;
+ }
+
+ if (type == BALANCE_PAIR &&
+ (! ((balance_pair_t *) data)->cost ||
+ ((balance_pair_t *) data)->cost->realzero())) {
+ DEBUG_("amounts.values.simplify", "Reducing balance pair to balance");
+ in_place_cast(BALANCE);
+ }
+
+ if (type == BALANCE &&
+ ((balance_t *) data)->amounts.size() == 1) {
+ DEBUG_("amounts.values.simplify", "Reducing balance to amount");
+ in_place_cast(AMOUNT);
+ }
+
+ if (type == AMOUNT &&
+ ! ((amount_t *) data)->commodity()) {
+ DEBUG_("amounts.values.simplify", "Reducing amount to integer");
+ in_place_cast(INTEGER);
+ }
+}
+
+value_t& value_t::operator=(const value_t& val)
+{
+ if (this == &val)
+ return *this;
+
+ if (type == BOOLEAN && val.type == BOOLEAN) {
+ *((bool *) data) = *((bool *) val.data);
+ return *this;
+ }
+ else if (type == INTEGER && val.type == INTEGER) {
+ *((long *) data) = *((long *) val.data);
+ return *this;
+ }
+ else if (type == DATETIME && val.type == DATETIME) {
+ *((moment_t *) data) = *((moment_t *) val.data);
+ return *this;
+ }
+ else if (type == AMOUNT && val.type == AMOUNT) {
+ *(amount_t *) data = *(amount_t *) val.data;
+ return *this;
+ }
+ else if (type == BALANCE && val.type == BALANCE) {
+ *(balance_t *) data = *(balance_t *) val.data;
+ return *this;
+ }
+ else if (type == BALANCE_PAIR && val.type == BALANCE_PAIR) {
+ *(balance_pair_t *) data = *(balance_pair_t *) val.data;
+ return *this;
+ }
+ else if (type == STRING && val.type == STRING) {
+ **(string **) data = **(string **) val.data;
+ return *this;
+ }
+ else if (type == SEQUENCE && val.type == SEQUENCE) {
+ **(sequence_t **) data = **(sequence_t **) val.data;
+ return *this;
+ }
+
+ destroy();
+
+ switch (val.type) {
+ case BOOLEAN:
+ *((bool *) data) = *((bool *) val.data);
+ break;
+
+ case INTEGER:
+ *((long *) data) = *((long *) val.data);
+ break;
+
+ case DATETIME:
+ *((moment_t *) data) = *((moment_t *) val.data);
+ break;
+
+ case AMOUNT:
+ new((amount_t *)data) amount_t(*((amount_t *) val.data));
+ break;
+
+ case BALANCE:
+ new((balance_t *)data) balance_t(*((balance_t *) val.data));
+ break;
+
+ case BALANCE_PAIR:
+ new((balance_pair_t *)data) balance_pair_t(*((balance_pair_t *) val.data));
+ break;
+
+ case STRING:
+ *(string **) data = new string(**(string **) val.data);
+ break;
+
+ case XML_NODE:
+ *(xml::node_t **) data = *(xml::node_t **) val.data;
+ break;
+
+ case POINTER:
+ *(void **) data = *(void **) val.data;
+ break;
+
+ case SEQUENCE:
+ *(sequence_t **) data = new sequence_t(**(sequence_t **) val.data);
+ break;
+
+ default:
+ assert(0);
+ break;
+ }
+
+ type = val.type;
+
+ return *this;
+}
+
+value_t& value_t::operator+=(const value_t& val)
+{
+ if (val.type == BOOLEAN)
+ throw_(value_exception, "Cannot add a boolean to a value");
+ else if (val.type == DATETIME)
+ throw_(value_exception, "Cannot add a date/time to a value");
+ else if (val.type == POINTER)
+ throw_(value_exception, "Cannot add a pointer to a value");
+ else if (val.type == SEQUENCE)
+ throw_(value_exception, "Cannot add a sequence to a value");
+ else if (val.type == XML_NODE) // recurse
+ return *this += (*(xml::node_t **) val.data)->to_value();
+
+ switch (type) {
+ case BOOLEAN:
+ throw_(value_exception, "Cannot add a value to a boolean");
+
+ case INTEGER:
+ switch (val.type) {
+ case INTEGER:
+ *((long *) data) += *((long *) val.data);
+ break;
+ case AMOUNT:
+ in_place_cast(AMOUNT);
+ *((amount_t *) data) += *((amount_t *) val.data);
+ break;
+ case BALANCE:
+ in_place_cast(BALANCE);
+ *((balance_t *) data) += *((balance_t *) val.data);
+ break;
+ case BALANCE_PAIR:
+ in_place_cast(BALANCE_PAIR);
+ *((balance_pair_t *) data) += *((balance_pair_t *) val.data);
+ break;
+ case STRING:
+ throw_(value_exception, "Cannot add a string to an integer");
+ default:
+ assert(0);
+ break;
+ }
+ break;
+
+ case DATETIME:
+ switch (val.type) {
+ case INTEGER:
+ *((moment_t *) data) += date_duration(*((long *) val.data));
+ break;
+ case AMOUNT:
+ *((moment_t *) data) += date_duration(long(*((amount_t *) val.data)));
+ break;
+ case BALANCE:
+ *((moment_t *) data) += date_duration(long(*((balance_t *) val.data)));
+ break;
+ case BALANCE_PAIR:
+ *((moment_t *) data) += date_duration(long(*((balance_pair_t *) val.data)));
+ break;
+ case STRING:
+ throw_(value_exception, "Cannot add a string to an date/time");
+ default:
+ assert(0);
+ break;
+ }
+ break;
+
+ case AMOUNT:
+ switch (val.type) {
+ case INTEGER:
+ if (*((long *) val.data) &&
+ ((amount_t *) data)->commodity()) {
+ in_place_cast(BALANCE);
+ return *this += val;
+ }
+ *((amount_t *) data) += *((long *) val.data);
+ break;
+
+ case AMOUNT:
+ if (((amount_t *) data)->commodity() !=
+ ((amount_t *) val.data)->commodity()) {
+ in_place_cast(BALANCE);
+ return *this += val;
+ }
+ *((amount_t *) data) += *((amount_t *) val.data);
+ break;
+
+ case BALANCE:
+ in_place_cast(BALANCE);
+ *((balance_t *) data) += *((balance_t *) val.data);
+ break;
+
+ case BALANCE_PAIR:
+ in_place_cast(BALANCE_PAIR);
+ *((balance_pair_t *) data) += *((balance_pair_t *) val.data);
+ break;
+
+ case STRING:
+ throw_(value_exception, "Cannot add a string to an amount");
+
+ default:
+ assert(0);
+ break;
+ }
+ break;
+
+ case BALANCE:
+ switch (val.type) {
+ case INTEGER:
+ *((balance_t *) data) += *((long *) val.data);
+ break;
+ case AMOUNT:
+ *((balance_t *) data) += *((amount_t *) val.data);
+ break;
+ case BALANCE:
+ *((balance_t *) data) += *((balance_t *) val.data);
+ break;
+ case BALANCE_PAIR:
+ in_place_cast(BALANCE_PAIR);
+ *((balance_pair_t *) data) += *((balance_pair_t *) val.data);
+ break;
+ case STRING:
+ throw_(value_exception, "Cannot add a string to an balance");
+ default:
+ assert(0);
+ break;
+ }
+ break;
+
+ case BALANCE_PAIR:
+ switch (val.type) {
+ case INTEGER:
+ *((balance_pair_t *) data) += *((long *) val.data);
+ break;
+ case AMOUNT:
+ *((balance_pair_t *) data) += *((amount_t *) val.data);
+ break;
+ case BALANCE:
+ *((balance_pair_t *) data) += *((balance_t *) val.data);
+ break;
+ case BALANCE_PAIR:
+ *((balance_pair_t *) data) += *((balance_pair_t *) val.data);
+ break;
+ case STRING:
+ throw_(value_exception, "Cannot add a string to an balance pair");
+ default:
+ assert(0);
+ break;
+ }
+ break;
+
+ case STRING:
+ switch (val.type) {
+ case INTEGER:
+ throw_(value_exception, "Cannot add an integer to a string");
+ case AMOUNT:
+ throw_(value_exception, "Cannot add an amount to a string");
+ case BALANCE:
+ throw_(value_exception, "Cannot add a balance to a string");
+ case BALANCE_PAIR:
+ throw_(value_exception, "Cannot add a balance pair to a string");
+ case STRING:
+ **(string **) data += **(string **) val.data;
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ break;
+
+ case XML_NODE:
+ throw_(value_exception, "Cannot add a value to an XML node");
+
+ case POINTER:
+ throw_(value_exception, "Cannot add a value to a pointer");
+
+ case SEQUENCE:
+ throw_(value_exception, "Cannot add a value to a sequence");
+
+ default:
+ assert(0);
+ break;
+ }
+ return *this;
+}
+
+value_t& value_t::operator-=(const value_t& val)
+{
+ if (val.type == BOOLEAN)
+ throw_(value_exception, "Cannot subtract a boolean from a value");
+ else if (val.type == DATETIME && type != DATETIME)
+ throw_(value_exception, "Cannot subtract a date/time from a value");
+ else if (val.type == STRING)
+ throw_(value_exception, "Cannot subtract a string from a value");
+ else if (val.type == POINTER)
+ throw_(value_exception, "Cannot subtract a pointer from a value");
+ else if (val.type == SEQUENCE)
+ throw_(value_exception, "Cannot subtract a sequence from a value");
+ else if (val.type == XML_NODE) // recurse
+ return *this -= (*(xml::node_t **) val.data)->to_value();
+
+ switch (type) {
+ case BOOLEAN:
+ throw_(value_exception, "Cannot subtract a value from a boolean");
+
+ case INTEGER:
+ switch (val.type) {
+ case INTEGER:
+ *((long *) data) -= *((long *) val.data);
+ break;
+ case AMOUNT:
+ in_place_cast(AMOUNT);
+ *((amount_t *) data) -= *((amount_t *) val.data);
+ break;
+ case BALANCE:
+ in_place_cast(BALANCE);
+ *((balance_t *) data) -= *((balance_t *) val.data);
+ break;
+ case BALANCE_PAIR:
+ in_place_cast(BALANCE_PAIR);
+ *((balance_pair_t *) data) -= *((balance_pair_t *) val.data);
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ break;
+
+ case DATETIME:
+ switch (val.type) {
+ case INTEGER:
+ *((moment_t *) data) -= date_duration(*((long *) val.data));
+ break;
+ case DATETIME: {
+ duration_t tval = ((moment_t *) data)->operator-(*((moment_t *) val.data));
+ in_place_cast(INTEGER);
+ *((long *) data) = tval.total_seconds() / 86400L;
+ break;
+ }
+ case AMOUNT:
+ *((moment_t *) data) -= date_duration(long(*((amount_t *) val.data)));
+ break;
+ case BALANCE:
+ *((moment_t *) data) -= date_duration(long(*((balance_t *) val.data)));
+ break;
+ case BALANCE_PAIR:
+ *((moment_t *) data) -= date_duration(long(*((balance_pair_t *) val.data)));
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ break;
+
+ case AMOUNT:
+ switch (val.type) {
+ case INTEGER:
+ if (*((long *) val.data) &&
+ ((amount_t *) data)->commodity()) {
+ in_place_cast(BALANCE);
+ return *this -= val;
+ }
+ *((amount_t *) data) -= *((long *) val.data);
+ break;
+
+ case AMOUNT:
+ if (((amount_t *) data)->commodity() !=
+ ((amount_t *) val.data)->commodity()) {
+ in_place_cast(BALANCE);
+ return *this -= val;
+ }
+ *((amount_t *) data) -= *((amount_t *) val.data);
+ break;
+
+ case BALANCE:
+ in_place_cast(BALANCE);
+ *((balance_t *) data) -= *((balance_t *) val.data);
+ break;
+
+ case BALANCE_PAIR:
+ in_place_cast(BALANCE_PAIR);
+ *((balance_pair_t *) data) -= *((balance_pair_t *) val.data);
+ break;
+
+ default:
+ assert(0);
+ break;
+ }
+ break;
+
+ case BALANCE:
+ switch (val.type) {
+ case INTEGER:
+ *((balance_t *) data) -= *((long *) val.data);
+ break;
+ case AMOUNT:
+ *((balance_t *) data) -= *((amount_t *) val.data);
+ break;
+ case BALANCE:
+ *((balance_t *) data) -= *((balance_t *) val.data);
+ break;
+ case BALANCE_PAIR:
+ in_place_cast(BALANCE_PAIR);
+ *((balance_pair_t *) data) -= *((balance_pair_t *) val.data);
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ break;
+
+ case BALANCE_PAIR:
+ switch (val.type) {
+ case INTEGER:
+ *((balance_pair_t *) data) -= *((long *) val.data);
+ break;
+ case AMOUNT:
+ *((balance_pair_t *) data) -= *((amount_t *) val.data);
+ break;
+ case BALANCE:
+ *((balance_pair_t *) data) -= *((balance_t *) val.data);
+ break;
+ case BALANCE_PAIR:
+ *((balance_pair_t *) data) -= *((balance_pair_t *) val.data);
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ break;
+
+ case STRING:
+ throw_(value_exception, "Cannot subtract a value from a string");
+ case XML_NODE:
+ throw_(value_exception, "Cannot subtract a value from an XML node");
+ case POINTER:
+ throw_(value_exception, "Cannot subtract a value from a pointer");
+ case SEQUENCE:
+ throw_(value_exception, "Cannot subtract a value from a sequence");
+
+ default:
+ assert(0);
+ break;
+ }
+
+ simplify();
+
+ return *this;
+}
+
+value_t& value_t::operator*=(const value_t& val)
+{
+ if (val.type == BOOLEAN)
+ throw_(value_exception, "Cannot multiply a value by a boolean");
+ else if (val.type == DATETIME)
+ throw_(value_exception, "Cannot multiply a value by a date/time");
+ else if (val.type == STRING)
+ throw_(value_exception, "Cannot multiply a value by a string");
+ else if (val.type == POINTER)
+ throw_(value_exception, "Cannot multiply a value by a pointer");
+ else if (val.type == SEQUENCE)
+ throw_(value_exception, "Cannot multiply a value by a sequence");
+ else if (val.type == XML_NODE) // recurse
+ return *this *= (*(xml::node_t **) val.data)->to_value();
+
+ if (val.realzero() && type != STRING) {
+ *this = 0L;
+ return *this;
+ }
+
+ switch (type) {
+ case BOOLEAN:
+ throw_(value_exception, "Cannot multiply a value by a boolean");
+
+ case INTEGER:
+ switch (val.type) {
+ case INTEGER:
+ *((long *) data) *= *((long *) val.data);
+ break;
+ case AMOUNT:
+ in_place_cast(AMOUNT);
+ *((amount_t *) data) *= *((amount_t *) val.data);
+ break;
+ case BALANCE:
+ in_place_cast(BALANCE);
+ *((balance_t *) data) *= *((balance_t *) val.data);
+ break;
+ case BALANCE_PAIR:
+ in_place_cast(BALANCE_PAIR);
+ *((balance_pair_t *) data) *= *((balance_pair_t *) val.data);
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ break;
+
+ case AMOUNT:
+ switch (val.type) {
+ case INTEGER:
+ *((amount_t *) data) *= *((long *) val.data);
+ break;
+ case AMOUNT:
+ *((amount_t *) data) *= *((amount_t *) val.data);
+ break;
+ case BALANCE:
+ in_place_cast(BALANCE);
+ *((balance_t *) data) *= *((balance_t *) val.data);
+ break;
+ case BALANCE_PAIR:
+ in_place_cast(BALANCE_PAIR);
+ *((balance_pair_t *) data) *= *((balance_pair_t *) val.data);
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ break;
+
+ case BALANCE:
+ switch (val.type) {
+ case INTEGER:
+ *((balance_t *) data) *= *((long *) val.data);
+ break;
+ case AMOUNT:
+ *((balance_t *) data) *= *((amount_t *) val.data);
+ break;
+ case BALANCE:
+ *((balance_t *) data) *= *((balance_t *) val.data);
+ break;
+ case BALANCE_PAIR:
+ in_place_cast(BALANCE_PAIR);
+ *((balance_pair_t *) data) *= *((balance_pair_t *) val.data);
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ break;
+
+ case BALANCE_PAIR:
+ switch (val.type) {
+ case INTEGER:
+ *((balance_pair_t *) data) *= *((long *) val.data);
+ break;
+ case AMOUNT:
+ *((balance_pair_t *) data) *= *((amount_t *) val.data);
+ break;
+ case BALANCE:
+ *((balance_pair_t *) data) *= *((balance_t *) val.data);
+ break;
+ case BALANCE_PAIR:
+ *((balance_pair_t *) data) *= *((balance_pair_t *) val.data);
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ break;
+
+ case STRING:
+ switch (val.type) {
+ case INTEGER: {
+ string temp;
+ for (long i = 0; i < *(long *) val.data; i++)
+ temp += **(string **) data;
+ **(string **) data = temp;
+ break;
+ }
+ case AMOUNT: {
+ string temp;
+ value_t num(val);
+ num.in_place_cast(INTEGER);
+ for (long i = 0; i < *(long *) num.data; i++)
+ temp += **(string **) data;
+ **(string **) data = temp;
+ break;
+ }
+ case BALANCE:
+ throw_(value_exception, "Cannot multiply a string by a balance");
+ case BALANCE_PAIR:
+ throw_(value_exception, "Cannot multiply a string by a balance pair");
+ default:
+ assert(0);
+ break;
+ }
+ break;
+
+ case XML_NODE:
+ throw_(value_exception, "Cannot multiply an XML node by a value");
+ case POINTER:
+ throw_(value_exception, "Cannot multiply a pointer by a value");
+ case SEQUENCE:
+ throw_(value_exception, "Cannot multiply a sequence by a value");
+
+ default:
+ assert(0);
+ break;
+ }
+ return *this;
+}
+
+value_t& value_t::operator/=(const value_t& val)
+{
+ if (val.type == BOOLEAN)
+ throw_(value_exception, "Cannot divide a boolean by a value");
+ else if (val.type == DATETIME)
+ throw_(value_exception, "Cannot divide a date/time by a value");
+ else if (val.type == STRING)
+ throw_(value_exception, "Cannot divide a string by a value");
+ else if (val.type == POINTER)
+ throw_(value_exception, "Cannot divide a pointer by a value");
+ else if (val.type == SEQUENCE)
+ throw_(value_exception, "Cannot divide a value by a sequence");
+ else if (val.type == XML_NODE) // recurse
+ return *this /= (*(xml::node_t **) val.data)->to_value();
+
+ switch (type) {
+ case BOOLEAN:
+ throw_(value_exception, "Cannot divide a value by a boolean");
+
+ case INTEGER:
+ switch (val.type) {
+ case INTEGER:
+ *((long *) data) /= *((long *) val.data);
+ break;
+ case AMOUNT:
+ in_place_cast(AMOUNT);
+ *((amount_t *) data) /= *((amount_t *) val.data);
+ break;
+ case BALANCE:
+ in_place_cast(BALANCE);
+ *((balance_t *) data) /= *((balance_t *) val.data);
+ break;
+ case BALANCE_PAIR:
+ in_place_cast(BALANCE_PAIR);
+ *((balance_pair_t *) data) /= *((balance_pair_t *) val.data);
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ break;
+
+ case AMOUNT:
+ switch (val.type) {
+ case INTEGER:
+ *((amount_t *) data) /= *((long *) val.data);
+ break;
+ case AMOUNT:
+ *((amount_t *) data) /= *((amount_t *) val.data);
+ break;
+ case BALANCE:
+ in_place_cast(BALANCE);
+ *((balance_t *) data) /= *((balance_t *) val.data);
+ break;
+ case BALANCE_PAIR:
+ in_place_cast(BALANCE_PAIR);
+ *((balance_pair_t *) data) /= *((balance_pair_t *) val.data);
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ break;
+
+ case BALANCE:
+ switch (val.type) {
+ case INTEGER:
+ *((balance_t *) data) /= *((long *) val.data);
+ break;
+ case AMOUNT:
+ *((balance_t *) data) /= *((amount_t *) val.data);
+ break;
+ case BALANCE:
+ *((balance_t *) data) /= *((balance_t *) val.data);
+ break;
+ case BALANCE_PAIR:
+ in_place_cast(BALANCE_PAIR);
+ *((balance_pair_t *) data) /= *((balance_pair_t *) val.data);
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ break;
+
+ case BALANCE_PAIR:
+ switch (val.type) {
+ case INTEGER:
+ *((balance_pair_t *) data) /= *((long *) val.data);
+ break;
+ case AMOUNT:
+ *((balance_pair_t *) data) /= *((amount_t *) val.data);
+ break;
+ case BALANCE:
+ *((balance_pair_t *) data) /= *((balance_t *) val.data);
+ break;
+ case BALANCE_PAIR:
+ *((balance_pair_t *) data) /= *((balance_pair_t *) val.data);
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ break;
+
+ case STRING:
+ throw_(value_exception, "Cannot divide a value from a string");
+ case XML_NODE:
+ throw_(value_exception, "Cannot divide a value from an XML node");
+ case POINTER:
+ throw_(value_exception, "Cannot divide a value from a pointer");
+ case SEQUENCE:
+ throw_(value_exception, "Cannot divide a value from a sequence");
+
+ default:
+ assert(0);
+ break;
+ }
+ return *this;
+}
+
+template <>
+value_t::operator bool() const
+{
+ switch (type) {
+ case BOOLEAN:
+ return *(bool *) data;
+ case INTEGER:
+ return *(long *) data;
+ case DATETIME:
+ return is_valid_moment(*((moment_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 ! (**((string **) data)).empty();
+ case XML_NODE:
+ return (*(xml::node_t **) data)->to_value().to_boolean();
+ 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_(value_exception, "Cannot convert a boolean to an integer");
+ case INTEGER:
+ return *((long *) data);
+ case DATETIME:
+ throw_(value_exception, "Cannot convert a date/time to an integer");
+ case AMOUNT:
+ return *((amount_t *) data);
+ case BALANCE:
+ throw_(value_exception, "Cannot convert a balance to an integer");
+ case BALANCE_PAIR:
+ throw_(value_exception, "Cannot convert a balance pair to an integer");
+ case STRING:
+ throw_(value_exception, "Cannot convert a string to an integer");
+ case XML_NODE:
+ return (*(xml::node_t **) data)->to_value().to_integer();
+ case POINTER:
+ throw_(value_exception, "Cannot convert a pointer to an integer");
+ case SEQUENCE:
+ throw_(value_exception, "Cannot convert a sequence to an integer");
+
+ default:
+ assert(0);
+ break;
+ }
+ assert(0);
+ return 0;
+}
+
+template <>
+value_t::operator moment_t() const
+{
+ switch (type) {
+ case BOOLEAN:
+ throw_(value_exception, "Cannot convert a boolean to a date/time");
+ case INTEGER:
+ throw_(value_exception, "Cannot convert an integer to a date/time");
+ case DATETIME:
+ return *((moment_t *) data);
+ case AMOUNT:
+ throw_(value_exception, "Cannot convert an amount to a date/time");
+ case BALANCE:
+ throw_(value_exception, "Cannot convert a balance to a date/time");
+ case BALANCE_PAIR:
+ throw_(value_exception, "Cannot convert a balance pair to a date/time");
+ case STRING:
+ throw_(value_exception, "Cannot convert a string to a date/time");
+ case XML_NODE:
+ return (*(xml::node_t **) data)->to_value().to_datetime();
+ case POINTER:
+ throw_(value_exception, "Cannot convert a pointer to a date/time");
+ case SEQUENCE:
+ throw_(value_exception, "Cannot convert a sequence to a date/time");
+
+ default:
+ assert(0);
+ break;
+ }
+ assert(0);
+ return moment_t();
+}
+
+template <>
+value_t::operator double() const
+{
+ switch (type) {
+ case BOOLEAN:
+ throw_(value_exception, "Cannot convert a boolean to a double");
+ case INTEGER:
+ return *((long *) data);
+ case DATETIME:
+ throw_(value_exception, "Cannot convert a date/time to a double");
+ case AMOUNT:
+ return *((amount_t *) data);
+ case BALANCE:
+ throw_(value_exception, "Cannot convert a balance to a double");
+ case BALANCE_PAIR:
+ throw_(value_exception, "Cannot convert a balance pair to a double");
+ case STRING:
+ throw_(value_exception, "Cannot convert a string to a double");
+ case XML_NODE:
+ return (*(xml::node_t **) data)->to_value().to_amount().number();
+ case POINTER:
+ throw_(value_exception, "Cannot convert a pointer to a double");
+ case SEQUENCE:
+ throw_(value_exception, "Cannot convert a sequence to a double");
+
+ default:
+ assert(0);
+ break;
+ }
+ assert(0);
+ return 0;
+}
+
+template <>
+value_t::operator string() const
+{
+ switch (type) {
+ case BOOLEAN:
+ case INTEGER:
+ case DATETIME:
+ case AMOUNT:
+ case BALANCE:
+ case BALANCE_PAIR: {
+ value_t temp(*this);
+ temp.in_place_cast(STRING);
+ return temp;
+ }
+ case STRING:
+ return **(string **) data;
+ case XML_NODE:
+ return (*(xml::node_t **) data)->to_value().to_string();
+
+ case POINTER:
+ throw_(value_exception, "Cannot convert a pointer to a string");
+ case SEQUENCE:
+ throw_(value_exception, "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& val) \
+{ \
+ switch (type) { \
+ case BOOLEAN: \
+ switch (val.type) { \
+ case BOOLEAN: \
+ return *((bool *) data) OP *((bool *) val.data); \
+ \
+ case INTEGER: \
+ return *((bool *) data) OP bool(*((long *) val.data)); \
+ \
+ case DATETIME: \
+ throw_(value_exception, "Cannot compare a boolean to a date/time"); \
+ \
+ case AMOUNT: \
+ return *((bool *) data) OP bool(*((amount_t *) val.data)); \
+ \
+ case BALANCE: \
+ return *((bool *) data) OP bool(*((balance_t *) val.data)); \
+ \
+ case BALANCE_PAIR: \
+ return *((bool *) data) OP bool(*((balance_pair_t *) val.data)); \
+ \
+ case STRING: \
+ throw_(value_exception, "Cannot compare a boolean to a string"); \
+ \
+ case XML_NODE: \
+ return *this OP (*(xml::node_t **) data)->to_value(); \
+ \
+ case POINTER: \
+ throw_(value_exception, "Cannot compare a boolean to a pointer"); \
+ case SEQUENCE: \
+ throw_(value_exception, "Cannot compare a boolean to a sequence"); \
+ \
+ default: \
+ assert(0); \
+ break; \
+ } \
+ break; \
+ \
+ case INTEGER: \
+ switch (val.type) { \
+ case BOOLEAN: \
+ return (*((long *) data) OP \
+ ((long) *((bool *) val.data))); \
+ \
+ case INTEGER: \
+ return (*((long *) data) OP *((long *) val.data)); \
+ \
+ case DATETIME: \
+ throw_(value_exception, "Cannot compare an integer to a date/time"); \
+ \
+ case AMOUNT: \
+ return (amount_t(*((long *) data)) OP \
+ *((amount_t *) val.data)); \
+ \
+ case BALANCE: \
+ return (balance_t(*((long *) data)) OP \
+ *((balance_t *) val.data)); \
+ \
+ case BALANCE_PAIR: \
+ return (balance_pair_t(*((long *) data)) OP \
+ *((balance_pair_t *) val.data)); \
+ \
+ case STRING: \
+ throw_(value_exception, "Cannot compare an integer to a string"); \
+ \
+ case XML_NODE: \
+ return *this OP (*(xml::node_t **) data)->to_value(); \
+ \
+ case POINTER: \
+ throw_(value_exception, "Cannot compare an integer to a pointer"); \
+ case SEQUENCE: \
+ throw_(value_exception, "Cannot compare an integer to a sequence"); \
+ \
+ default: \
+ assert(0); \
+ break; \
+ } \
+ break; \
+ \
+ case DATETIME: \
+ switch (val.type) { \
+ case BOOLEAN: \
+ throw_(value_exception, "Cannot compare a date/time to a boolean"); \
+ \
+ case INTEGER: \
+ throw_(value_exception, "Cannot compare a date/time to an integer"); \
+ \
+ case DATETIME: \
+ return *((moment_t *) data) OP *((moment_t *) val.data); \
+ \
+ case AMOUNT: \
+ throw_(value_exception, "Cannot compare a date/time to an amount"); \
+ case BALANCE: \
+ throw_(value_exception, "Cannot compare a date/time to a balance"); \
+ case BALANCE_PAIR: \
+ throw_(value_exception, "Cannot compare a date/time to a balance pair"); \
+ case STRING: \
+ throw_(value_exception, "Cannot compare a date/time to a string"); \
+ \
+ case XML_NODE: \
+ return *this OP (*(xml::node_t **) data)->to_value(); \
+ \
+ case POINTER: \
+ throw_(value_exception, "Cannot compare a date/time to a pointer"); \
+ case SEQUENCE: \
+ throw_(value_exception, "Cannot compare a date/time to a sequence"); \
+ \
+ default: \
+ assert(0); \
+ break; \
+ } \
+ break; \
+ \
+ case AMOUNT: \
+ switch (val.type) { \
+ case BOOLEAN: \
+ throw_(value_exception, "Cannot compare an amount to a boolean"); \
+ \
+ case INTEGER: \
+ return (*((amount_t *) data) OP \
+ amount_t(*((long *) val.data))); \
+ \
+ case DATETIME: \
+ throw_(value_exception, "Cannot compare an amount to a date/time"); \
+ \
+ case AMOUNT: \
+ return *((amount_t *) data) OP *((amount_t *) val.data); \
+ \
+ case BALANCE: \
+ return (balance_t(*((amount_t *) data)) OP \
+ *((balance_t *) val.data)); \
+ \
+ case BALANCE_PAIR: \
+ return (balance_t(*((amount_t *) data)) OP \
+ *((balance_pair_t *) val.data)); \
+ \
+ case STRING: \
+ throw_(value_exception, "Cannot compare an amount to a string"); \
+ \
+ case XML_NODE: \
+ return *this OP (*(xml::node_t **) data)->to_value(); \
+ \
+ case POINTER: \
+ throw_(value_exception, "Cannot compare an amount to a pointer"); \
+ case SEQUENCE: \
+ throw_(value_exception, "Cannot compare an amount to a sequence"); \
+ \
+ default: \
+ assert(0); \
+ break; \
+ } \
+ break; \
+ \
+ case BALANCE: \
+ switch (val.type) { \
+ case BOOLEAN: \
+ throw_(value_exception, "Cannot compare a balance to a boolean"); \
+ \
+ case INTEGER: \
+ return *((balance_t *) data) OP *((long *) val.data); \
+ \
+ case DATETIME: \
+ throw_(value_exception, "Cannot compare a balance to a date/time"); \
+ \
+ case AMOUNT: \
+ return *((balance_t *) data) OP *((amount_t *) val.data); \
+ \
+ case BALANCE: \
+ return *((balance_t *) data) OP *((balance_t *) val.data); \
+ \
+ case BALANCE_PAIR: \
+ return (*((balance_t *) data) OP \
+ ((balance_pair_t *) val.data)->quantity); \
+ \
+ case STRING: \
+ throw_(value_exception, "Cannot compare a balance to a string"); \
+ \
+ case XML_NODE: \
+ return *this OP (*(xml::node_t **) data)->to_value(); \
+ \
+ case POINTER: \
+ throw_(value_exception, "Cannot compare a balance to a pointer"); \
+ case SEQUENCE: \
+ throw_(value_exception, "Cannot compare a balance to a sequence"); \
+ \
+ default: \
+ assert(0); \
+ break; \
+ } \
+ break; \
+ \
+ case BALANCE_PAIR: \
+ switch (val.type) { \
+ case BOOLEAN: \
+ throw_(value_exception, "Cannot compare a balance pair to a boolean"); \
+ \
+ case INTEGER: \
+ return (((balance_pair_t *) data)->quantity OP \
+ *((long *) val.data)); \
+ \
+ case DATETIME: \
+ throw_(value_exception, "Cannot compare a balance pair to a date/time"); \
+ \
+ case AMOUNT: \
+ return (((balance_pair_t *) data)->quantity OP \
+ *((amount_t *) val.data)); \
+ \
+ case BALANCE: \
+ return (((balance_pair_t *) data)->quantity OP \
+ *((balance_t *) val.data)); \
+ \
+ case BALANCE_PAIR: \
+ return (*((balance_pair_t *) data) OP \
+ *((balance_pair_t *) val.data)); \
+ \
+ case STRING: \
+ throw_(value_exception, "Cannot compare a balance pair to a string"); \
+ \
+ case XML_NODE: \
+ return *this OP (*(xml::node_t **) data)->to_value(); \
+ \
+ case POINTER: \
+ throw_(value_exception, "Cannot compare a balance pair to a pointer"); \
+ case SEQUENCE: \
+ throw_(value_exception, "Cannot compare a balance pair to a sequence"); \
+ \
+ default: \
+ assert(0); \
+ break; \
+ } \
+ break; \
+ \
+ case STRING: \
+ switch (val.type) { \
+ case BOOLEAN: \
+ throw_(value_exception, "Cannot compare a string to a boolean"); \
+ case INTEGER: \
+ throw_(value_exception, "Cannot compare a string to an integer"); \
+ case DATETIME: \
+ throw_(value_exception, "Cannot compare a string to a date/time"); \
+ case AMOUNT: \
+ throw_(value_exception, "Cannot compare a string to an amount"); \
+ case BALANCE: \
+ throw_(value_exception, "Cannot compare a string to a balance"); \
+ case BALANCE_PAIR: \
+ throw_(value_exception, "Cannot compare a string to a balance pair"); \
+ \
+ case STRING: \
+ return (**((string **) data) OP \
+ **((string **) val.data)); \
+ \
+ case XML_NODE: \
+ return *this OP (*(xml::node_t **) data)->to_value(); \
+ \
+ case POINTER: \
+ throw_(value_exception, "Cannot compare a string to a pointer"); \
+ case SEQUENCE: \
+ throw_(value_exception, "Cannot compare a string to a sequence"); \
+ \
+ default: \
+ assert(0); \
+ break; \
+ } \
+ break; \
+ \
+ case XML_NODE: \
+ switch (val.type) { \
+ case BOOLEAN: \
+ return (*(xml::node_t **) data)->to_value() OP *this; \
+ case INTEGER: \
+ return (*(xml::node_t **) data)->to_value() OP *this; \
+ case DATETIME: \
+ return (*(xml::node_t **) data)->to_value() OP *this; \
+ case AMOUNT: \
+ return (*(xml::node_t **) data)->to_value() OP *this; \
+ case BALANCE: \
+ return (*(xml::node_t **) data)->to_value() OP *this; \
+ case BALANCE_PAIR: \
+ return (*(xml::node_t **) data)->to_value() OP *this; \
+ case STRING: \
+ return (*(xml::node_t **) data)->to_value() OP *this; \
+ \
+ case XML_NODE: \
+ return ((*(xml::node_t **) data)->to_value() OP \
+ (*(xml::node_t **) val.data)->to_value()); \
+ \
+ case POINTER: \
+ throw_(value_exception, "Cannot compare an XML node to a pointer"); \
+ case SEQUENCE: \
+ throw_(value_exception, "Cannot compare an XML node to a sequence"); \
+ \
+ default: \
+ assert(0); \
+ break; \
+ } \
+ break; \
+ \
+ case POINTER: \
+ switch (val.type) { \
+ case BOOLEAN: \
+ throw_(value_exception, "Cannot compare a pointer to a boolean"); \
+ case INTEGER: \
+ throw_(value_exception, "Cannot compare a pointer to an integer"); \
+ case DATETIME: \
+ throw_(value_exception, "Cannot compare a pointer to a date/time"); \
+ case AMOUNT: \
+ throw_(value_exception, "Cannot compare a pointer to an amount"); \
+ case BALANCE: \
+ throw_(value_exception, "Cannot compare a pointer to a balance"); \
+ case BALANCE_PAIR: \
+ throw_(value_exception, "Cannot compare a pointer to a balance pair"); \
+ case STRING: \
+ throw_(value_exception, "Cannot compare a pointer to a string node"); \
+ case XML_NODE: \
+ throw_(value_exception, "Cannot compare a pointer to an XML node"); \
+ case POINTER: \
+ return (*((void **) data) OP *((void **) val.data)); \
+ case SEQUENCE: \
+ throw_(value_exception, "Cannot compare a pointer to a sequence"); \
+ \
+ default: \
+ assert(0); \
+ break; \
+ } \
+ break; \
+ \
+ case SEQUENCE: \
+ throw_(value_exception, "Cannot compare a value to a sequence"); \
+ \
+ default: \
+ assert(0); \
+ break; \
+ } \
+ return *this; \
+}
+
+DEF_VALUE_CMP_OP(==)
+DEF_VALUE_CMP_OP(<)
+DEF_VALUE_CMP_OP(<=)
+DEF_VALUE_CMP_OP(>)
+DEF_VALUE_CMP_OP(>=)
+
+void value_t::in_place_cast(type_t cast_type)
+{
+ switch (type) {
+ case BOOLEAN:
+ switch (cast_type) {
+ case BOOLEAN:
+ break;
+ case INTEGER:
+ throw_(value_exception, "Cannot convert a boolean to an integer");
+ case DATETIME:
+ throw_(value_exception, "Cannot convert a boolean to a date/time");
+ case AMOUNT:
+ throw_(value_exception, "Cannot convert a boolean to an amount");
+ case BALANCE:
+ throw_(value_exception, "Cannot convert a boolean to a balance");
+ case BALANCE_PAIR:
+ throw_(value_exception, "Cannot convert a boolean to a balance pair");
+ case STRING:
+ *(string **) data = new string(*((bool *) data) ? "true" : "false");
+ break;
+ case XML_NODE:
+ throw_(value_exception, "Cannot convert a boolean to an XML node");
+ case POINTER:
+ throw_(value_exception, "Cannot convert a boolean to a pointer");
+ case SEQUENCE:
+ throw_(value_exception, "Cannot convert a boolean to a sequence");
+
+ default:
+ assert(0);
+ break;
+ }
+ break;
+
+ case INTEGER:
+ switch (cast_type) {
+ case BOOLEAN:
+ *((bool *) data) = *((long *) data);
+ break;
+ case INTEGER:
+ break;
+ case DATETIME:
+ throw_(value_exception, "Cannot convert an integer to a date/time");
+
+ case AMOUNT:
+ new((amount_t *)data) amount_t(*((long *) data));
+ break;
+ case BALANCE:
+ new((balance_t *)data) balance_t(*((long *) data));
+ break;
+ 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);
+ *(string **) data = new string(buf);
+ break;
+ }
+ case XML_NODE:
+ throw_(value_exception, "Cannot convert an integer to an XML node");
+ case POINTER:
+ throw_(value_exception, "Cannot convert an integer to a pointer");
+ case SEQUENCE:
+ throw_(value_exception, "Cannot convert an integer to a sequence");
+
+ default:
+ assert(0);
+ break;
+ }
+ break;
+
+ case DATETIME:
+ switch (cast_type) {
+ case BOOLEAN:
+ *((bool *) data) = is_valid_moment(*((moment_t *) data));
+ break;
+ case INTEGER:
+ throw_(value_exception, "Cannot convert a date/time to an integer");
+ case DATETIME:
+ break;
+ case AMOUNT:
+ throw_(value_exception, "Cannot convert a date/time to an amount");
+ case BALANCE:
+ throw_(value_exception, "Cannot convert a date/time to a balance");
+ case BALANCE_PAIR:
+ throw_(value_exception, "Cannot convert a date/time to a balance pair");
+ case STRING:
+ throw_(value_exception, "Cannot convert a date/time to a string");
+ case XML_NODE:
+ throw_(value_exception, "Cannot convert a date/time to an XML node");
+ case POINTER:
+ throw_(value_exception, "Cannot convert a date/time to a pointer");
+ case SEQUENCE:
+ throw_(value_exception, "Cannot convert a date/time to a sequence");
+
+ default:
+ assert(0);
+ break;
+ }
+ break;
+
+ case AMOUNT:
+ switch (cast_type) {
+ case BOOLEAN: {
+ bool temp = *((amount_t *) data);
+ destroy();
+ *((bool *)data) = temp;
+ break;
+ }
+ case INTEGER: {
+ long temp = *((amount_t *) data);
+ destroy();
+ *((long *)data) = temp;
+ break;
+ }
+ case DATETIME:
+ throw_(value_exception, "Cannot convert an amount to a date/time");
+ case AMOUNT:
+ break;
+ case BALANCE: {
+ amount_t temp = *((amount_t *) data);
+ destroy();
+ new((balance_t *)data) balance_t(temp);
+ break;
+ }
+ case BALANCE_PAIR: {
+ amount_t temp = *((amount_t *) data);
+ destroy();
+ new((balance_pair_t *)data) balance_pair_t(temp);
+ break;
+ }
+ case STRING: {
+ std::ostringstream out;
+ out << *(amount_t *) data;
+ destroy();
+ *(string **) data = new string(out.str());
+ break;
+ }
+ case XML_NODE:
+ throw_(value_exception, "Cannot convert an amount to an XML node");
+ case POINTER:
+ throw_(value_exception, "Cannot convert an amount to a pointer");
+ case SEQUENCE:
+ throw_(value_exception, "Cannot convert an amount to a sequence");
+
+ default:
+ assert(0);
+ break;
+ }
+ break;
+
+ case BALANCE:
+ switch (cast_type) {
+ case BOOLEAN: {
+ bool temp = *((balance_t *) data);
+ destroy();
+ *((bool *)data) = temp;
+ break;
+ }
+ case INTEGER:
+ throw_(value_exception, "Cannot convert a balance to an integer");
+ case DATETIME:
+ throw_(value_exception, "Cannot convert a balance to a date/time");
+
+ case AMOUNT: {
+ balance_t * temp = (balance_t *) data;
+ if (temp->amounts.size() == 1) {
+ amount_t amt = (*temp->amounts.begin()).second;
+ destroy();
+ new((amount_t *)data) amount_t(amt);
+ }
+ else if (temp->amounts.size() == 0) {
+ new((amount_t *)data) amount_t();
+ }
+ else {
+ throw_(value_exception, "Cannot convert a balance with "
+ "multiple commodities to an amount");
+ }
+ break;
+ }
+ case BALANCE:
+ break;
+ case BALANCE_PAIR: {
+ balance_t temp = *((balance_t *) data);
+ destroy();
+ new((balance_pair_t *)data) balance_pair_t(temp);
+ break;
+ }
+ case STRING:
+ throw_(value_exception, "Cannot convert a balance to a string");
+ case XML_NODE:
+ throw_(value_exception, "Cannot convert a balance to an XML node");
+ case POINTER:
+ throw_(value_exception, "Cannot convert a balance to a pointer");
+ case SEQUENCE:
+ throw_(value_exception, "Cannot convert a balance to a sequence");
+
+ default:
+ assert(0);
+ break;
+ }
+ break;
+
+ case BALANCE_PAIR:
+ switch (cast_type) {
+ case BOOLEAN: {
+ bool temp = *((balance_pair_t *) data);
+ destroy();
+ *((bool *)data) = temp;
+ break;
+ }
+ case INTEGER:
+ throw_(value_exception, "Cannot convert a balance pair to an integer");
+ case DATETIME:
+ throw_(value_exception, "Cannot convert a balance pair to a date/time");
+
+ case AMOUNT: {
+ balance_t * temp = &((balance_pair_t *) data)->quantity;
+ if (temp->amounts.size() == 1) {
+ amount_t amt = (*temp->amounts.begin()).second;
+ destroy();
+ new((amount_t *)data) amount_t(amt);
+ }
+ else if (temp->amounts.size() == 0) {
+ new((amount_t *)data) amount_t();
+ }
+ else {
+ throw_(value_exception, "Cannot convert a balance pair with "
+ "multiple commodities to an amount");
+ }
+ break;
+ }
+ case BALANCE: {
+ balance_t temp = ((balance_pair_t *) data)->quantity;
+ destroy();
+ new((balance_t *)data) balance_t(temp);
+ break;
+ }
+ case BALANCE_PAIR:
+ break;
+ case STRING:
+ throw_(value_exception, "Cannot convert a balance pair to a string");
+ case XML_NODE:
+ throw_(value_exception, "Cannot convert a balance pair to an XML node");
+ case POINTER:
+ throw_(value_exception, "Cannot convert a balance pair to a pointer");
+ case SEQUENCE:
+ throw_(value_exception, "Cannot convert a balance pair to a sequence");
+
+ default:
+ assert(0);
+ break;
+ }
+ break;
+
+ case STRING:
+ switch (cast_type) {
+ case BOOLEAN: {
+ if (**(string **) data == "true") {
+ destroy();
+ *(bool *) data = true;
+ }
+ else if (**(string **) data == "false") {
+ destroy();
+ *(bool *) data = false;
+ }
+ else {
+ throw_(value_exception, "Cannot convert string to an boolean");
+ }
+ break;
+ }
+ case INTEGER: {
+ int l = (*(string **) data)->length();
+ const char * p = (*(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((*(string **) data)->c_str());
+ destroy();
+ *(long *) data = temp;
+ } else {
+ throw_(value_exception, "Cannot convert string to an integer");
+ }
+ break;
+ }
+
+ case DATETIME:
+ throw_(value_exception, "Cannot convert a string to a date/time");
+
+ case AMOUNT: {
+ amount_t temp = **(string **) data;
+ destroy();
+ new((amount_t *)data) amount_t(temp);
+ break;
+ }
+ case BALANCE:
+ throw_(value_exception, "Cannot convert a string to a balance");
+ case BALANCE_PAIR:
+ throw_(value_exception, "Cannot convert a string to a balance pair");
+ case STRING:
+ break;
+ case XML_NODE:
+ throw_(value_exception, "Cannot convert a string to an XML node");
+ case POINTER:
+ throw_(value_exception, "Cannot convert a string to a pointer");
+ case SEQUENCE:
+ throw_(value_exception, "Cannot convert a string to a sequence");
+
+ default:
+ assert(0);
+ break;
+ }
+ break;
+
+ case XML_NODE:
+ switch (cast_type) {
+ case BOOLEAN:
+ case INTEGER:
+ case DATETIME:
+ case AMOUNT:
+ case BALANCE:
+ case BALANCE_PAIR:
+ case STRING:
+ *this = (*(xml::node_t **) data)->to_value();
+ break;
+ case XML_NODE:
+ break;
+ case POINTER:
+ throw_(value_exception, "Cannot convert an XML node to a pointer");
+ case SEQUENCE:
+ throw_(value_exception, "Cannot convert an XML node to a sequence");
+
+ default:
+ assert(0);
+ break;
+ }
+ break;
+
+ case POINTER:
+ switch (cast_type) {
+ case BOOLEAN:
+ throw_(value_exception, "Cannot convert a pointer to a boolean");
+ case INTEGER:
+ throw_(value_exception, "Cannot convert a pointer to an integer");
+ case DATETIME:
+ throw_(value_exception, "Cannot convert a pointer to a date/time");
+ case AMOUNT:
+ throw_(value_exception, "Cannot convert a pointer to an amount");
+ case BALANCE:
+ throw_(value_exception, "Cannot convert a pointer to a balance");
+ case BALANCE_PAIR:
+ throw_(value_exception, "Cannot convert a pointer to a balance pair");
+ case STRING:
+ throw_(value_exception, "Cannot convert a pointer to a string");
+ case XML_NODE:
+ throw_(value_exception, "Cannot convert a pointer to an XML node");
+ case POINTER:
+ break;
+ case SEQUENCE:
+ throw_(value_exception, "Cannot convert a pointer to a sequence");
+
+ default:
+ assert(0);
+ break;
+ }
+ break;
+
+ case SEQUENCE:
+ switch (cast_type) {
+ case BOOLEAN:
+ throw_(value_exception, "Cannot convert a sequence to a boolean");
+ case INTEGER:
+ throw_(value_exception, "Cannot convert a sequence to an integer");
+ case DATETIME:
+ throw_(value_exception, "Cannot convert a sequence to a date/time");
+ case AMOUNT:
+ throw_(value_exception, "Cannot convert a sequence to an amount");
+ case BALANCE:
+ throw_(value_exception, "Cannot convert a sequence to a balance");
+ case BALANCE_PAIR:
+ throw_(value_exception, "Cannot convert a sequence to a balance pair");
+ case STRING:
+ throw_(value_exception, "Cannot convert a sequence to a string");
+ case XML_NODE:
+ throw_(value_exception, "Cannot compare a sequence to an XML node");
+ case POINTER:
+ throw_(value_exception, "Cannot convert a sequence to a pointer");
+ case SEQUENCE:
+ break;
+
+ default:
+ assert(0);
+ break;
+ }
+ break;
+
+ default:
+ assert(0);
+ break;
+ }
+ type = cast_type;
+}
+
+void value_t::in_place_negate()
+{
+ switch (type) {
+ case BOOLEAN:
+ *((bool *) data) = ! *((bool *) data);
+ break;
+ case INTEGER:
+ *((long *) data) = - *((long *) data);
+ break;
+ case DATETIME:
+ throw_(value_exception, "Cannot negate a date/time");
+ case AMOUNT:
+ ((amount_t *) data)->in_place_negate();
+ break;
+ case BALANCE:
+ ((balance_t *) data)->in_place_negate();
+ break;
+ case BALANCE_PAIR:
+ ((balance_pair_t *) data)->in_place_negate();
+ break;
+ case STRING:
+ throw_(value_exception, "Cannot negate a string");
+ case XML_NODE:
+ *this = (*(xml::node_t **) data)->to_value();
+ in_place_negate();
+ break;
+ case POINTER:
+ throw_(value_exception, "Cannot negate a pointer");
+ case SEQUENCE:
+ throw_(value_exception, "Cannot negate a sequence");
+
+ default:
+ assert(0);
+ break;
+ }
+}
+
+void value_t::in_place_abs()
+{
+ switch (type) {
+ case BOOLEAN:
+ break;
+ case INTEGER:
+ if (*((long *) data) < 0)
+ *((long *) data) = - *((long *) data);
+ break;
+ case DATETIME:
+ break;
+ case AMOUNT:
+ ((amount_t *) data)->abs();
+ break;
+ case BALANCE:
+ ((balance_t *) data)->abs();
+ break;
+ case BALANCE_PAIR:
+ ((balance_pair_t *) data)->abs();
+ break;
+ case STRING:
+ throw_(value_exception, "Cannot take the absolute value of a string");
+ case XML_NODE:
+ *this = (*(xml::node_t **) data)->to_value();
+ in_place_abs();
+ break;
+ case POINTER:
+ throw_(value_exception, "Cannot take the absolute value of a pointer");
+ case SEQUENCE:
+ throw_(value_exception, "Cannot take the absolute value of a sequence");
+
+ default:
+ assert(0);
+ break;
+ }
+}
+
+value_t value_t::value(const moment_t& moment) const
+{
+ switch (type) {
+ case BOOLEAN:
+ throw_(value_exception, "Cannot find the value of a boolean");
+ case DATETIME:
+ throw_(value_exception, "Cannot find the value of a date/time");
+ case INTEGER:
+ return *this;
+ case AMOUNT:
+ return ((amount_t *) data)->value(moment);
+ case BALANCE:
+ return ((balance_t *) data)->value(moment);
+ case BALANCE_PAIR:
+ return ((balance_pair_t *) data)->quantity.value(moment);
+ case STRING:
+ throw_(value_exception, "Cannot find the value of a string");
+ case XML_NODE:
+ return (*(xml::node_t **) data)->to_value().value(moment);
+ case POINTER:
+ throw_(value_exception, "Cannot find the value of a pointer");
+ case SEQUENCE:
+ throw_(value_exception, "Cannot find the value of a sequence");
+ default:
+ assert(0);
+ return value_t();
+ }
+}
+
+void value_t::in_place_reduce()
+{
+ switch (type) {
+ case BOOLEAN:
+ case DATETIME:
+ case INTEGER:
+ break;
+ case AMOUNT:
+ ((amount_t *) data)->in_place_reduce();
+ break;
+ case BALANCE:
+ ((balance_t *) data)->in_place_reduce();
+ break;
+ case BALANCE_PAIR:
+ ((balance_pair_t *) data)->in_place_reduce();
+ break;
+ case STRING:
+ throw_(value_exception, "Cannot reduce a string");
+ case XML_NODE:
+ *this = (*(xml::node_t **) data)->to_value();
+ in_place_reduce(); // recurse
+ break;
+ case POINTER:
+ throw_(value_exception, "Cannot reduce a pointer");
+ case SEQUENCE:
+ throw_(value_exception, "Cannot reduce a sequence");
+ }
+}
+
+value_t value_t::round() const
+{
+ switch (type) {
+ case BOOLEAN:
+ throw_(value_exception, "Cannot round a boolean");
+ case DATETIME:
+ throw_(value_exception, "Cannot round a date/time");
+ case INTEGER:
+ return *this;
+ case AMOUNT:
+ return ((amount_t *) data)->round();
+ case BALANCE:
+ return ((balance_t *) data)->round();
+ case BALANCE_PAIR:
+ return ((balance_pair_t *) data)->round();
+ case STRING:
+ throw_(value_exception, "Cannot round a string");
+ case XML_NODE:
+ return (*(xml::node_t **) data)->to_value().round();
+ case POINTER:
+ throw_(value_exception, "Cannot round a pointer");
+ case SEQUENCE:
+ throw_(value_exception, "Cannot round a sequence");
+ }
+ assert(0);
+ return value_t();
+}
+
+value_t value_t::unround() const
+{
+ switch (type) {
+ case BOOLEAN:
+ throw_(value_exception, "Cannot un-round a boolean");
+ case DATETIME:
+ throw_(value_exception, "Cannot un-round a date/time");
+ case INTEGER:
+ return *this;
+ case AMOUNT:
+ return ((amount_t *) data)->unround();
+ case BALANCE:
+ return ((balance_t *) data)->unround();
+ case BALANCE_PAIR:
+ return ((balance_pair_t *) data)->unround();
+ case STRING:
+ throw_(value_exception, "Cannot un-round a string");
+ case XML_NODE:
+ return (*(xml::node_t **) data)->to_value().unround();
+ case POINTER:
+ throw_(value_exception, "Cannot un-round a pointer");
+ case SEQUENCE:
+ throw_(value_exception, "Cannot un-round a sequence");
+ }
+ assert(0);
+ return value_t();
+}
+
+value_t value_t::price() const
+{
+ switch (type) {
+ case BOOLEAN:
+ throw_(value_exception, "Cannot find the price of a boolean");
+ case INTEGER:
+ return *this;
+ case DATETIME:
+ throw_(value_exception, "Cannot find the price of a date/time");
+
+ case AMOUNT:
+ return ((amount_t *) data)->price();
+ case BALANCE:
+ return ((balance_t *) data)->price();
+ case BALANCE_PAIR:
+ return ((balance_pair_t *) data)->quantity.price();
+
+ case STRING:
+ throw_(value_exception, "Cannot find the price of a string");
+
+ case XML_NODE:
+ return (*(xml::node_t **) data)->to_value().price();
+
+ case POINTER:
+ throw_(value_exception, "Cannot find the price of a pointer");
+ case SEQUENCE:
+ throw_(value_exception, "Cannot find the price of a sequence");
+
+ default:
+ assert(0);
+ break;
+ }
+ assert(0);
+ return value_t();
+}
+
+value_t value_t::date() const
+{
+ switch (type) {
+ case BOOLEAN:
+ throw_(value_exception, "Cannot find the date of a boolean");
+ case INTEGER:
+ throw_(value_exception, "Cannot find the date of an integer");
+
+ case DATETIME:
+ return *this;
+
+ case AMOUNT:
+ return ((amount_t *) data)->date();
+ case BALANCE:
+ return ((balance_t *) data)->date();
+ case BALANCE_PAIR:
+ return ((balance_pair_t *) data)->quantity.date();
+
+ case STRING:
+ throw_(value_exception, "Cannot find the date of a string");
+
+ case XML_NODE:
+ return (*(xml::node_t **) data)->to_value().date();
+
+ case POINTER:
+ throw_(value_exception, "Cannot find the date of a pointer");
+ case SEQUENCE:
+ throw_(value_exception, "Cannot find the date of a sequence");
+
+ default:
+ assert(0);
+ break;
+ }
+ assert(0);
+ return value_t();
+}
+
+value_t value_t::strip_annotations(const bool keep_price,
+ const bool keep_date,
+ const bool keep_tag) const
+{
+ switch (type) {
+ 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);
+ case BALANCE:
+ return ((balance_t *) data)->strip_annotations
+ (keep_price, keep_date, keep_tag);
+ case BALANCE_PAIR:
+ return ((balance_pair_t *) data)->quantity.strip_annotations
+ (keep_price, keep_date, keep_tag);
+
+ default:
+ assert(0);
+ break;
+ }
+ assert(0);
+ return value_t();
+}
+
+value_t value_t::cost() const
+{
+ switch (type) {
+ case BOOLEAN:
+ throw_(value_exception, "Cannot find the cost of a boolean");
+ case INTEGER:
+ case AMOUNT:
+ case BALANCE:
+ return *this;
+ case DATETIME:
+ throw_(value_exception, "Cannot find the cost of a date/time");
+
+ case BALANCE_PAIR:
+ assert(((balance_pair_t *) data)->cost);
+ if (((balance_pair_t *) data)->cost)
+ return *(((balance_pair_t *) data)->cost);
+ else
+ return ((balance_pair_t *) data)->quantity;
+
+ case STRING:
+ throw_(value_exception, "Cannot find the cost of a string");
+ case XML_NODE:
+ return (*(xml::node_t **) data)->to_value().cost();
+ case POINTER:
+ throw_(value_exception, "Cannot find the cost of a pointer");
+ case SEQUENCE:
+ throw_(value_exception, "Cannot find the cost of a sequence");
+
+ default:
+ assert(0);
+ break;
+ }
+ assert(0);
+ return value_t();
+}
+
+value_t& value_t::add(const amount_t& amount, const amount_t * tcost)
+{
+ switch (type) {
+ case BOOLEAN:
+ throw_(value_exception, "Cannot add an amount to a boolean");
+ case DATETIME:
+ throw_(value_exception, "Cannot add an amount to a date/time");
+ case INTEGER:
+ case AMOUNT:
+ if (tcost) {
+ in_place_cast(BALANCE_PAIR);
+ return add(amount, tcost);
+ }
+ else if ((type == AMOUNT &&
+ ((amount_t *) data)->commodity() != amount.commodity()) ||
+ (type != AMOUNT && amount.commodity())) {
+ in_place_cast(BALANCE);
+ return add(amount, tcost);
+ }
+ else if (type != AMOUNT) {
+ in_place_cast(AMOUNT);
+ }
+ *((amount_t *) data) += amount;
+ break;
+
+ case BALANCE:
+ if (tcost) {
+ in_place_cast(BALANCE_PAIR);
+ return add(amount, tcost);
+ }
+ *((balance_t *) data) += amount;
+ break;
+
+ case BALANCE_PAIR:
+ ((balance_pair_t *) data)->add(amount, tcost);
+ break;
+
+ case STRING:
+ throw_(value_exception, "Cannot add an amount to a string");
+ case XML_NODE:
+ throw_(value_exception, "Cannot add an amount to an XML node");
+ case POINTER:
+ throw_(value_exception, "Cannot add an amount to a pointer");
+ case SEQUENCE:
+ throw_(value_exception, "Cannot add an amount to a sequence");
+
+ default:
+ assert(0);
+ break;
+ }
+
+ 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_(value_exception, "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& val)
+{
+ switch (val.type) {
+ case value_t::BOOLEAN:
+ out << (*((bool *) val.data) ? "true" : "false");
+ break;
+ case value_t::INTEGER:
+ out << *(long *) val.data;
+ break;
+ case value_t::DATETIME:
+ out << *(moment_t *) val.data;
+ break;
+ case value_t::AMOUNT:
+ out << *(amount_t *) val.data;
+ break;
+ case value_t::BALANCE:
+ out << *(balance_t *) val.data;
+ break;
+ case value_t::BALANCE_PAIR:
+ out << *(balance_pair_t *) val.data;
+ break;
+ case value_t::STRING:
+ out << **(string **) val.data;
+ break;
+ case value_t::XML_NODE:
+ if ((*(xml::node_t **) val.data)->flags & XML_NODE_IS_PARENT)
+ out << '<' << (*(xml::node_t **) val.data)->name() << '>';
+ else
+ out << (*(xml::node_t **) val.data)->text();
+ break;
+
+ case value_t::POINTER:
+ throw_(value_exception, "Cannot output a pointer value");
+
+ case value_t::SEQUENCE: {
+ out << '(';
+ bool first = true;
+ for (value_t::sequence_t::iterator
+ i = (*(value_t::sequence_t **) val.data)->begin();
+ i != (*(value_t::sequence_t **) val.data)->end();
+ i++) {
+ if (first)
+ first = false;
+ else
+ out << ", ";
+ out << *i;
+ }
+ out << ')';
+ break;
+ }
+
+ default:
+ assert(0);
+ break;
+ }
+ return out;
+}
+
+#if 0
+value_context::value_context(const value_t& _bal,
+ const string& _desc) throw()
+ : error_context(_desc), bal(new value_t(_bal)) {}
+
+value_context::~value_context() throw()
+{
+ delete bal;
+}
+
+void value_context::describe(std::ostream& out) const throw()
+{
+ if (! desc.empty())
+ out << desc << std::endl;
+
+ balance_t * ptr = NULL;
+
+ out << std::right;
+ out.width(20);
+
+ switch (bal->type) {
+ case value_t::BOOLEAN:
+ out << (*((bool *) bal->data) ? "true" : "false");
+ break;
+ case value_t::INTEGER:
+ out << *((long *) bal->data);
+ break;
+ case value_t::DATETIME:
+ out << *((moment_t *) bal->data);
+ break;
+ case value_t::AMOUNT:
+ out << *((amount_t *) bal->data);
+ break;
+ case value_t::BALANCE:
+ ptr = (balance_t *) bal->data;
+ // fall through...
+
+ case value_t::BALANCE_PAIR:
+ if (! ptr)
+ ptr = &((balance_pair_t *) bal->data)->quantity;
+
+ ptr->write(out, 20);
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ out << std::endl;
+}
+#endif
+
+} // namespace ledger
diff --git a/src/value.h b/src/value.h
new file mode 100644
index 00000000..2b0adf2b
--- /dev/null
+++ b/src/value.h
@@ -0,0 +1,580 @@
+#ifndef _VALUE_H
+#define _VALUE_H
+
+#include "amount.h"
+#include "balance.h"
+
+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,
+// balance_t. This was found to be prohibitively expensive, especially
+// when large logic chains were involved, since many temporary
+// allocations would occur for every operator. With value_t, and the
+// fact that logic chains only need boolean values to continue, no
+// memory allocations need to take place at all.
+
+class value_t
+{
+ public:
+ typedef std::vector<value_t> sequence_t;
+
+ char data[sizeof(balance_pair_t)];
+
+ enum type_t {
+ BOOLEAN,
+ INTEGER,
+ DATETIME,
+ AMOUNT,
+ BALANCE,
+ BALANCE_PAIR,
+ STRING,
+ XML_NODE,
+ POINTER,
+ SEQUENCE
+ } type;
+
+ value_t() {
+ TRACE_CTOR(value_t, "");
+ *((long *) data) = 0;
+ type = INTEGER;
+ }
+
+ value_t(const value_t& val) : type(INTEGER) {
+ TRACE_CTOR(value_t, "copy");
+ *this = val;
+ }
+ value_t(const bool val) {
+ TRACE_CTOR(value_t, "const bool");
+ *((bool *) data) = val;
+ type = BOOLEAN;
+ }
+ value_t(const long val) {
+ TRACE_CTOR(value_t, "const long");
+ *((long *) data) = val;
+ type = INTEGER;
+ }
+ value_t(const moment_t val) {
+ TRACE_CTOR(value_t, "const moment_t");
+ *((moment_t *) data) = val;
+ type = DATETIME;
+ }
+ value_t(const unsigned long val) {
+ TRACE_CTOR(value_t, "const unsigned long");
+ new((amount_t *) data) amount_t(val);
+ type = AMOUNT;
+ }
+ value_t(const double val) {
+ TRACE_CTOR(value_t, "const double");
+ new((amount_t *) data) amount_t(val);
+ type = AMOUNT;
+ }
+ value_t(const string& val, bool literal = false) {
+ TRACE_CTOR(value_t, "const string&, bool");
+ if (literal) {
+ type = INTEGER;
+ set_string(val);
+ } else {
+ new((amount_t *) data) amount_t(val);
+ type = AMOUNT;
+ }
+ }
+ value_t(const char * val) {
+ TRACE_CTOR(value_t, "const char *");
+ new((amount_t *) data) amount_t(val);
+ type = AMOUNT;
+ }
+ value_t(const amount_t& val) {
+ TRACE_CTOR(value_t, "const amount_t&");
+ new((amount_t *)data) amount_t(val);
+ type = AMOUNT;
+ }
+ value_t(const balance_t& val) : type(INTEGER) {
+ TRACE_CTOR(value_t, "const balance_t&");
+ *this = val;
+ }
+ value_t(const balance_pair_t& val) : type(INTEGER) {
+ TRACE_CTOR(value_t, "const balance_pair_t&");
+ *this = val;
+ }
+ 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();
+ }
+
+ void destroy();
+ void simplify();
+
+ value_t& operator=(const value_t& val);
+ value_t& operator=(const bool val) {
+ if ((bool *) data != &val) {
+ destroy();
+ *((bool *) data) = val;
+ type = BOOLEAN;
+ }
+ return *this;
+ }
+ value_t& operator=(const long val) {
+ if ((long *) data != &val) {
+ destroy();
+ *((long *) data) = val;
+ type = INTEGER;
+ }
+ return *this;
+ }
+ value_t& operator=(const moment_t val) {
+ if ((moment_t *) data != &val) {
+ destroy();
+ *((moment_t *) data) = val;
+ type = DATETIME;
+ }
+ return *this;
+ }
+ value_t& operator=(const unsigned long val) {
+ return *this = amount_t(val);
+ }
+ value_t& operator=(const double val) {
+ return *this = amount_t(val);
+ }
+ value_t& operator=(const string& val) {
+ return *this = amount_t(val);
+ }
+ value_t& operator=(const char * val) {
+ return *this = amount_t(val);
+ }
+ value_t& operator=(const amount_t& val) {
+ if (type == AMOUNT &&
+ (amount_t *) data == &val)
+ return *this;
+
+ if (val.realzero()) {
+ return *this = 0L;
+ } else {
+ destroy();
+ new((amount_t *)data) amount_t(val);
+ type = AMOUNT;
+ }
+ return *this;
+ }
+ value_t& operator=(const balance_t& val) {
+ if (type == BALANCE &&
+ (balance_t *) data == &val)
+ return *this;
+
+ if (val.realzero()) {
+ return *this = 0L;
+ }
+ else if (val.amounts.size() == 1) {
+ return *this = (*val.amounts.begin()).second;
+ }
+ else {
+ destroy();
+ new((balance_t *)data) balance_t(val);
+ type = BALANCE;
+ return *this;
+ }
+ }
+ value_t& operator=(const balance_pair_t& val) {
+ if (type == BALANCE_PAIR &&
+ (balance_pair_t *) data == &val)
+ return *this;
+
+ if (val.realzero()) {
+ return *this = 0L;
+ }
+ else if (! val.cost) {
+ return *this = val.quantity;
+ }
+ else {
+ destroy();
+ new((balance_pair_t *)data) balance_pair_t(val);
+ type = BALANCE_PAIR;
+ 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 string& str = "") {
+ if (type != STRING) {
+ destroy();
+ *(string **) data = new string(str);
+ type = STRING;
+ } else {
+ **(string **) data = str;
+ }
+ return *this;
+ }
+
+ bool to_boolean() const;
+ long to_integer() const;
+ moment_t to_datetime() const;
+ amount_t to_amount() const;
+ balance_t to_balance() const;
+ balance_pair_t to_balance_pair() const;
+ 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& val) {
+ sequence_t * seq = to_sequence();
+ assert(seq);
+ return seq->push_back(val);
+ }
+
+ std::size_t size() const {
+ sequence_t * seq = to_sequence();
+ assert(seq);
+ return seq->size();
+ }
+
+ value_t& operator+=(const value_t& val);
+ value_t& operator-=(const value_t& val);
+ value_t& operator*=(const value_t& val);
+ value_t& operator/=(const value_t& val);
+
+ template <typename T>
+ value_t& operator+=(const T& val) {
+ return *this += value_t(val);
+ }
+ template <typename T>
+ value_t& operator-=(const T& val) {
+ return *this -= value_t(val);
+ }
+ template <typename T>
+ value_t& operator*=(const T& val) {
+ return *this *= value_t(val);
+ }
+ template <typename T>
+ value_t& operator/=(const T& val) {
+ return *this /= value_t(val);
+ }
+
+ value_t operator+(const value_t& val) {
+ value_t temp(*this);
+ temp += val;
+ return temp;
+ }
+ value_t operator-(const value_t& val) {
+ value_t temp(*this);
+ temp -= val;
+ return temp;
+ }
+ value_t operator*(const value_t& val) {
+ value_t temp(*this);
+ temp *= val;
+ return temp;
+ }
+ value_t operator/(const value_t& val) {
+ value_t temp(*this);
+ temp /= val;
+ return temp;
+ }
+
+ template <typename T>
+ value_t operator+(const T& val) {
+ return *this + value_t(val);
+ }
+ template <typename T>
+ value_t operator-(const T& val) {
+ return *this - value_t(val);
+ }
+ template <typename T>
+ value_t operator*(const T& val) {
+ return *this * value_t(val);
+ }
+ template <typename T>
+ value_t operator/(const T& val) {
+ return *this / value_t(val);
+ }
+
+ bool operator<(const value_t& val);
+ bool operator<=(const value_t& val);
+ bool operator>(const value_t& val);
+ bool operator>=(const value_t& val);
+ bool operator==(const value_t& val);
+ bool operator!=(const value_t& val) {
+ return ! (*this == val);
+ }
+
+ template <typename T>
+ bool operator<(const T& val) {
+ return *this < value_t(val);
+ }
+ template <typename T>
+ bool operator<=(const T& val) {
+ return *this <= value_t(val);
+ }
+ template <typename T>
+ bool operator>(const T& val) {
+ return *this > value_t(val);
+ }
+ template <typename T>
+ bool operator>=(const T& val) {
+ return *this >= value_t(val);
+ }
+ template <typename T>
+ bool operator==(const T& val) {
+ return *this == value_t(val);
+ }
+ template <typename T>
+ bool operator!=(const T& val) {
+ return ! (*this == val);
+ }
+
+ template <typename T>
+ operator T() const;
+
+ void in_place_negate();
+ value_t negate() const {
+ value_t temp = *this;
+ temp.in_place_negate();
+ return temp;
+ }
+ value_t operator-() const {
+ return negate();
+ }
+
+ bool realzero() const {
+ switch (type) {
+ case BOOLEAN:
+ return ! *((bool *) data);
+ case INTEGER:
+ return *((long *) data) == 0;
+ case DATETIME:
+ return ! is_valid_moment(*((moment_t *) data));
+ case AMOUNT:
+ return ((amount_t *) data)->realzero();
+ case BALANCE:
+ return ((balance_t *) data)->realzero();
+ case BALANCE_PAIR:
+ return ((balance_pair_t *) data)->realzero();
+ case STRING:
+ return ((string *) data)->empty();
+ case XML_NODE:
+ case POINTER:
+ case SEQUENCE:
+ return *(void **) data == NULL;
+
+ default:
+ assert(0);
+ break;
+ }
+ assert(0);
+ return 0;
+ }
+
+ void in_place_abs();
+ value_t abs() const;
+ void in_place_cast(type_t cast_type);
+ value_t cost() const;
+ value_t price() const;
+ value_t date() const;
+
+ value_t cast(type_t cast_type) const {
+ value_t temp(*this);
+ temp.in_place_cast(cast_type);
+ return temp;
+ }
+
+ value_t strip_annotations(const bool keep_price = amount_t::keep_price,
+ const bool keep_date = amount_t::keep_date,
+ const bool keep_tag = amount_t::keep_tag) const;
+
+ value_t& add(const amount_t& amount, const amount_t * cost = NULL);
+ value_t value(const moment_t& moment) const;
+ void in_place_reduce();
+
+ value_t reduce() const {
+ value_t temp(*this);
+ temp.in_place_reduce();
+ return temp;
+ }
+
+ value_t round() const;
+ value_t unround() const;
+
+ void write(std::ostream& out, const int first_width,
+ const int latter_width = -1) const;
+};
+
+#define DEFINE_VALUE_OPERATORS(T, OP) \
+inline value_t operator OP(const T& val, const value_t& obj) { \
+ return value_t(val) OP obj; \
+}
+
+DEFINE_VALUE_OPERATORS(bool, ==)
+DEFINE_VALUE_OPERATORS(bool, !=)
+
+DEFINE_VALUE_OPERATORS(long, +)
+DEFINE_VALUE_OPERATORS(long, -)
+DEFINE_VALUE_OPERATORS(long, *)
+DEFINE_VALUE_OPERATORS(long, /)
+DEFINE_VALUE_OPERATORS(long, <)
+DEFINE_VALUE_OPERATORS(long, <=)
+DEFINE_VALUE_OPERATORS(long, >)
+DEFINE_VALUE_OPERATORS(long, >=)
+DEFINE_VALUE_OPERATORS(long, ==)
+DEFINE_VALUE_OPERATORS(long, !=)
+
+DEFINE_VALUE_OPERATORS(amount_t, +)
+DEFINE_VALUE_OPERATORS(amount_t, -)
+DEFINE_VALUE_OPERATORS(amount_t, *)
+DEFINE_VALUE_OPERATORS(amount_t, /)
+DEFINE_VALUE_OPERATORS(amount_t, <)
+DEFINE_VALUE_OPERATORS(amount_t, <=)
+DEFINE_VALUE_OPERATORS(amount_t, >)
+DEFINE_VALUE_OPERATORS(amount_t, >=)
+DEFINE_VALUE_OPERATORS(amount_t, ==)
+DEFINE_VALUE_OPERATORS(amount_t, !=)
+
+DEFINE_VALUE_OPERATORS(balance_t, +)
+DEFINE_VALUE_OPERATORS(balance_t, -)
+DEFINE_VALUE_OPERATORS(balance_t, *)
+DEFINE_VALUE_OPERATORS(balance_t, /)
+DEFINE_VALUE_OPERATORS(balance_t, <)
+DEFINE_VALUE_OPERATORS(balance_t, <=)
+DEFINE_VALUE_OPERATORS(balance_t, >)
+DEFINE_VALUE_OPERATORS(balance_t, >=)
+DEFINE_VALUE_OPERATORS(balance_t, ==)
+DEFINE_VALUE_OPERATORS(balance_t, !=)
+
+DEFINE_VALUE_OPERATORS(balance_pair_t, +)
+DEFINE_VALUE_OPERATORS(balance_pair_t, -)
+DEFINE_VALUE_OPERATORS(balance_pair_t, *)
+DEFINE_VALUE_OPERATORS(balance_pair_t, /)
+DEFINE_VALUE_OPERATORS(balance_pair_t, <)
+DEFINE_VALUE_OPERATORS(balance_pair_t, <=)
+DEFINE_VALUE_OPERATORS(balance_pair_t, >)
+DEFINE_VALUE_OPERATORS(balance_pair_t, >=)
+DEFINE_VALUE_OPERATORS(balance_pair_t, ==)
+DEFINE_VALUE_OPERATORS(balance_pair_t, !=)
+
+template <typename T>
+value_t::operator T() const
+{
+ switch (type) {
+ case BOOLEAN:
+ return *(bool *) data;
+ case INTEGER:
+ return *(long *) data;
+ case DATETIME:
+ return *(moment_t *) data;
+ case AMOUNT:
+ return *(amount_t *) data;
+ case BALANCE:
+ return *(balance_t *) data;
+ case STRING:
+ return **(string **) data;
+ case XML_NODE:
+ return *(xml::node_t **) data;
+ case POINTER:
+ return *(void **) data;
+ case SEQUENCE:
+ return *(sequence_t **) data;
+
+ default:
+ assert(0);
+ break;
+ }
+ assert(0);
+ return 0;
+}
+
+template <> value_t::operator bool() const;
+template <> value_t::operator long() const;
+template <> value_t::operator moment_t() const;
+template <> value_t::operator double() const;
+template <> value_t::operator string() const;
+
+std::ostream& operator<<(std::ostream& out, const value_t& val);
+
+#if 0
+class value_context : public error_context
+{
+ value_t * bal;
+ public:
+ value_context(const value_t& _bal,
+ const string& desc = "") throw();
+ virtual ~value_context() throw();
+
+ virtual void describe(std::ostream& out) const throw();
+};
+#endif
+
+DECLARE_EXCEPTION(value_exception);
+
+} // namespace ledger
+
+#endif // _VALUE_H
diff --git a/src/xml.cc b/src/xml.cc
new file mode 100644
index 00000000..448b62b7
--- /dev/null
+++ b/src/xml.cc
@@ -0,0 +1,550 @@
+#include "xml.h"
+#include "journal.h"
+
+namespace ledger {
+namespace xml {
+
+const std::size_t document_t::ledger_builtins_size = 12;
+const char * document_t::ledger_builtins[] = {
+ "account",
+ "account-path",
+ "amount",
+ "code",
+ "commodity",
+ "entries",
+ "entry",
+ "journal",
+ "name",
+ "note",
+ "payee",
+ "transaction"
+};
+
+document_t::document_t(node_t * _top)
+ : stub(this), top(_top ? _top : &stub) {
+ TRACE_CTOR(xml::document_t, "node_t *, const char **, const int");
+}
+
+document_t::~document_t()
+{
+ TRACE_DTOR(xml::document_t);
+ if (top && top != &stub)
+ delete top;
+}
+
+void document_t::set_top(node_t * _top)
+{
+ if (top && top != &stub)
+ delete top;
+ top = _top;
+}
+
+int document_t::register_name(const string& name)
+{
+ int index = lookup_name_id(name);
+ if (index != -1)
+ return index;
+
+ names.push_back(name);
+ index = names.size() - 1;
+
+ DEBUG_("xml.lookup", this << " Inserting name: " << names.back());
+
+ std::pair<names_map::iterator, bool> result =
+ names_index.insert(names_pair(names.back(), index));
+ assert(result.second);
+
+ return index + 1000;
+}
+
+int document_t::lookup_name_id(const string& name) const
+{
+ int id;
+ if ((id = lookup_builtin_id(name)) != -1)
+ return id;
+
+ DEBUG_("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;
+}
+
+int document_t::lookup_builtin_id(const string& name)
+{
+ int first = 0;
+ int last = (int)ledger_builtins_size;
+
+ while (first <= last) {
+ int mid = (first + last) / 2; // compute mid point.
+
+ int result;
+ if ((result = (int)name[0] - (int)ledger_builtins[mid][0]) == 0)
+ result = std::strcmp(name.c_str(), ledger_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 + 10;
+ }
+
+ 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);
+ return ledger_builtins[id - 10];
+ }
+ } else {
+ return names[id - 1000].c_str();
+ }
+}
+
+void document_t::write(std::ostream& out) const
+{
+ if (top) {
+ out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+ top->write(out);
+ }
+}
+
+#ifndef THREADSAFE
+document_t * node_t::document;
+#endif
+
+node_t::node_t(document_t * _document, parent_node_t * _parent,
+ unsigned int _flags)
+ : name_id(0), parent(_parent), next(NULL), prev(NULL),
+ flags(_flags), attrs(NULL)
+{
+ TRACE_CTOR(node_t, "document_t *, node_t *");
+ document = _document;
+ if (document && ! document->top)
+ document->set_top(this);
+ 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;
+ }
+
+ if (next)
+ next->prev = prev;
+
+ next = NULL;
+ prev = NULL;
+}
+
+const char * node_t::name() const
+{
+ return document->lookup_name(name_id);
+}
+
+int node_t::set_name(const char * _name)
+{
+ name_id = document->register_name(_name);
+ return name_id;
+}
+
+node_t * node_t::lookup_child(const char * _name) const
+{
+ int id = document->lookup_name_id(_name);
+ return lookup_child(id);
+}
+
+node_t * node_t::lookup_child(const string& _name) const
+{
+ int id = document->lookup_name_id(_name);
+ return lookup_child(id);
+}
+
+void parent_node_t::clear()
+{
+ node_t * child = _children;
+ while (child) {
+ node_t * tnext = child->next;
+ delete child;
+ child = tnext;
+ }
+}
+
+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;
+ }
+
+ node->parent = this;
+
+ while (node->next) {
+ node_t * next_node = node->next;
+ assert(next_node->prev == node);
+ next_node->parent = this;
+ node = next_node;
+ }
+
+ _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";
+ }
+}
+
+#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE)
+
+template <typename T>
+inline T * create_node(document_t::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)
+{
+ document_t::parser_t * parser = static_cast<document_t::parser_t *>(userData);
+
+ DEBUG_("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);
+ }
+
+ 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);
+ }
+ }
+}
+
+static void endElement(void *userData, const char *name)
+{
+ document_t::parser_t * parser = static_cast<document_t::parser_t *>(userData);
+
+ DEBUG_("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 (! 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)
+{
+ document_t::parser_t * parser = static_cast<document_t::parser_t *>(userData);
+
+ DEBUG_("xml.parse", "dataHandler(" << 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(string(s, len));
+ parser->handled_data = true;
+
+ if (parser->node_stack.empty()) {
+ parser->document->top = node;
+ return;
+ }
+ }
+}
+
+bool document_t::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.clear();
+ in.seekg(0, std::ios::beg);
+ return true;
+}
+
+document_t * document_t::parser_t::parse(std::istream& in)
+{
+ std::auto_ptr<document_t> doc(new document_t);
+
+ document = doc.get();
+
+ 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");
+ 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_(parse_exception, err.what());
+ }
+
+ if (! have_error.empty()) {
+ //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++;
+#if 0
+ // jww (2007-04-26): What is this doing??
+ parse_exception err(have_error);
+ std::cerr << "Error: " << err.what() << std::endl;
+#endif
+ have_error = "";
+ }
+
+ if (! result) {
+ //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++;
+ const char * err = XML_ErrorString(XML_GetErrorCode(parser));
+ XML_ParserFree(parser);
+ throw_(parse_exception, err);
+ }
+ }
+
+ XML_ParserFree(parser);
+
+ document = NULL;
+ return doc.release();
+}
+
+node_t * commodity_node_t::children() const
+{
+ // jww (2007-04-19): Need to report the commodity and its details
+ return NULL;
+}
+
+node_t * amount_node_t::children() const
+{
+ // jww (2007-04-19): Need to report the quantity and commodity
+ return NULL;
+}
+
+node_t * transaction_node_t::children() const
+{
+ return parent_node_t::children();
+}
+
+node_t * transaction_node_t::lookup_child(int _name_id) const
+{
+ switch (_name_id) {
+ case document_t::PAYEE:
+ payee_virtual_node = new terminal_node_t(document);
+ payee_virtual_node->set_text(transaction->entry->payee);
+ return payee_virtual_node;
+
+ case document_t::ACCOUNT:
+ return new account_node_t(document, transaction->account,
+ const_cast<transaction_node_t *>(this));
+ }
+ return NULL;
+}
+
+value_t transaction_node_t::to_value() const
+{
+ return transaction->amount;
+}
+
+node_t * entry_node_t::children() const
+{
+ if (! _children)
+ 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();
+}
+
+node_t * entry_node_t::lookup_child(int _name_id) const
+{
+ switch (_name_id) {
+ case document_t::CODE:
+ // jww (2007-04-20): I have to save this and then delete it later
+ terminal_node_t * code_node =
+ new terminal_node_t(document, const_cast<entry_node_t *>(this));
+ code_node->set_name(document_t::CODE);
+ code_node->set_text(entry->code);
+ return code_node;
+
+ case document_t::PAYEE:
+ // jww (2007-04-20): I have to save this and then delete it later
+ terminal_node_t * payee_node =
+ new terminal_node_t(document, const_cast<entry_node_t *>(this));
+ payee_node->set_name(document_t::PAYEE);
+ payee_node->set_text(entry->payee);
+ return payee_node;
+ }
+ return NULL;
+}
+
+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(document_t::NAME);
+ name_node->set_text(account->name);
+ }
+
+ if (! account->note.empty()) {
+ terminal_node_t * note_node =
+ new terminal_node_t(document, const_cast<account_node_t *>(this));
+ note_node->set_name(document_t::NOTE);
+ note_node->set_text(account->note);
+ }
+
+ for (accounts_map::iterator i = account->accounts.begin();
+ i != account->accounts.end();
+ i++)
+ new account_node_t(document, (*i).second, const_cast<account_node_t *>(this));
+ }
+ return parent_node_t::children();
+}
+
+node_t * journal_node_t::children() const
+{
+ if (! _children) {
+#if 0
+ account_node_t * master_account =
+ new account_node_t(document, journal->master, const_cast<journal_node_t *>(this));
+#endif
+
+ parent_node_t * entries =
+ new parent_node_t(document, const_cast<journal_node_t *>(this));
+ entries->set_name(document_t::ENTRIES);
+
+ 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 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;
+ }
+ }
+}
+
+} // namespace xml
+} // namespace ledger
diff --git a/src/xml.h b/src/xml.h
new file mode 100644
index 00000000..6a600786
--- /dev/null
+++ b/src/xml.h
@@ -0,0 +1,423 @@
+#ifndef _XML_H
+#define _XML_H
+
+#include "value.h"
+#include "parser.h"
+
+namespace ledger {
+
+class transaction_t;
+class entry_t;
+class account_t;
+class journal_t;
+
+namespace xml {
+
+#define XML_NODE_IS_PARENT 0x1
+
+DECLARE_EXCEPTION(conversion_exception);
+
+class parent_node_t;
+class document_t;
+
+class node_t
+{
+public:
+ unsigned 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;
+
+ typedef std::map<string, string> attrs_map;
+ typedef std::pair<string, 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);
+ return NULL;
+ }
+
+ const char * name() const;
+ int set_name(const char * _name);
+ 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;
+ }
+
+ node_t * lookup_child(const char * _name) const;
+ node_t * lookup_child(const string& _name) const;
+ virtual node_t * lookup_child(int /* _name_id */) const {
+ return NULL;
+ }
+
+ virtual value_t to_value() const {
+ throw_(conversion_exception, "Cannot convert node to a value");
+ }
+
+ 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
+{
+ 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 ~terminal_node_t() {
+ TRACE_DTOR(terminal_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 string& _data) {
+ data = _data;
+ }
+
+ virtual value_t to_value() const {
+ return text();
+ }
+
+ void write(std::ostream& out, int depth = 0) const;
+
+private:
+ terminal_node_t(const node_t&);
+ terminal_node_t& operator=(const node_t&);
+};
+
+class document_t
+{
+ static const char * ledger_builtins[];
+ static const std::size_t ledger_builtins_size;
+
+public:
+ enum ledger_builtins_t {
+ ACCOUNT = 10,
+ ACCOUNT_PATH,
+ AMOUNT,
+ CODE,
+ COMMODITY,
+ ENTRIES,
+ ENTRY,
+ JOURNAL,
+ NAME,
+ NOTE,
+ PAYEE,
+ TRANSACTION
+ };
+
+private:
+ typedef std::vector<string> names_array;
+
+ names_array names;
+
+ typedef std::map<string, int> names_map;
+ typedef std::pair<string, int> names_pair;
+
+ names_map names_index;
+
+ terminal_node_t stub;
+
+ public:
+ 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);
+ ~document_t();
+
+ void set_top(node_t * _top);
+
+ int register_name(const string& name);
+ int lookup_name_id(const string& name) const;
+ static int lookup_builtin_id(const string& name);
+ const char * lookup_name(int id) const;
+
+ void write(std::ostream& out) const;
+
+#if defined(HAVE_EXPAT) || defined(HAVE_XMLPARSE)
+ class parser_t
+ {
+ public:
+ document_t * document;
+ XML_Parser parser;
+ 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 ~parser_t() {}
+
+ virtual bool test(std::istream& in) const;
+ virtual document_t * parse(std::istream& in);
+ };
+#endif
+};
+
+#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 string * original_file = NULL);
+};
+
+DECLARE_EXCEPTION(parse_exception);
+
+#endif
+
+class commodity_node_t : public parent_node_t
+{
+public:
+ commodity_t * commodity;
+
+ commodity_node_t(document_t * _document,
+ commodity_t * _commodity,
+ parent_node_t * _parent = NULL)
+ : parent_node_t(_document, _parent), commodity(_commodity) {
+ TRACE_CTOR(commodity_node_t, "document_t *, commodity_t *, parent_node_t *");
+ set_name(document_t::COMMODITY);
+ }
+ virtual ~commodity_node_t() {
+ TRACE_DTOR(commodity_node_t);
+ }
+
+ virtual node_t * children() const;
+};
+
+class amount_node_t : public parent_node_t
+{
+public:
+ amount_t * amount;
+
+ amount_node_t(document_t * _document,
+ amount_t * _amount,
+ parent_node_t * _parent = NULL)
+ : parent_node_t(_document, _parent), amount(_amount) {
+ TRACE_CTOR(amount_node_t, "document_t *, amount_t *, parent_node_t *");
+ set_name(document_t::AMOUNT);
+ }
+ virtual ~amount_node_t() {
+ TRACE_DTOR(amount_node_t);
+ }
+
+ virtual node_t * children() const;
+
+ virtual value_t to_value() const {
+ return *amount;
+ }
+};
+
+class transaction_node_t : public parent_node_t
+{
+ mutable terminal_node_t * payee_virtual_node;
+
+public:
+ transaction_t * transaction;
+
+ transaction_node_t(document_t * _document,
+ transaction_t * _transaction,
+ parent_node_t * _parent = NULL)
+ : parent_node_t(_document, _parent), payee_virtual_node(NULL),
+ transaction(_transaction) {
+ TRACE_CTOR(transaction_node_t, "document_t *, transaction_t *, parent_node_t *");
+ set_name(document_t::TRANSACTION);
+ }
+ virtual ~transaction_node_t() {
+ TRACE_DTOR(transaction_node_t);
+ if (payee_virtual_node)
+ delete payee_virtual_node;
+ }
+
+ virtual node_t * children() const;
+ virtual node_t * lookup_child(int _name_id) const;
+ virtual value_t to_value() 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(document_t::ENTRY);
+ }
+ virtual ~entry_node_t() {
+ TRACE_DTOR(entry_node_t);
+ }
+
+ virtual node_t * children() const;
+ virtual node_t * lookup_child(int _name_id) const;
+
+ friend class transaction_node_t;
+};
+
+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(document_t::ACCOUNT);
+ }
+ virtual ~account_node_t() {
+ TRACE_DTOR(account_node_t);
+ }
+
+ 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(document_t::JOURNAL);
+ }
+ virtual ~journal_node_t() {
+ TRACE_DTOR(journal_node_t);
+ }
+
+ virtual node_t * children() const;
+
+ friend class transaction_node_t;
+};
+
+template <typename T>
+inline parent_node_t * wrap_node(document_t * doc, T * item,
+ void * parent_node = NULL) {
+ assert(0);
+ return NULL;
+}
+
+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/src/xmlparse.cc b/src/xmlparse.cc
new file mode 100644
index 00000000..5dfcdbaa
--- /dev/null
+++ b/src/xmlparse.cc
@@ -0,0 +1,467 @@
+#include "xml.h"
+#include "journal.h"
+
+namespace ledger {
+namespace xml {
+
+#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 string comm_flags;
+
+static transaction_t::state_t curr_state;
+
+static string data;
+static bool ignore;
+static 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 (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 = parse_datetime(data);
+ }
+ else if (std::strcmp(name, "en:date_eff") == 0) {
+ curr_entry->_date_eff = parse_datetime(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 (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) {
+ string::size_type i = data.find('.');
+ if (i != 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 = 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 string * original_file)
+{
+ char buf[BUFSIZ];
+
+ count = 0;
+ curr_journal = journal;
+ curr_entry = NULL;
+ curr_comm = NULL;
+ ignore = false;
+
+ 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_(parse_exception, err.what());
+ }
+
+ if (! have_error.empty()) {
+ //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++;
+#if 0
+ // jww (2007-04-26): What is this code doing?
+ parse_error err(have_error);
+ std::cerr << "Error: " << err.what() << std::endl;
+#endif
+ have_error = "";
+ }
+
+ if (! result) {
+ //unsigned long line = XML_GetCurrentLineNumber(parser) - offset++;
+ const char * err = XML_ErrorString(XML_GetErrorCode(parser));
+ XML_ParserFree(parser);
+ throw_(parse_exception, 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 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) {
+ 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 xml
+} // namespace ledger
diff --git a/src/xpath.cc b/src/xpath.cc
new file mode 100644
index 00000000..6eb30d48
--- /dev/null
+++ b/src/xpath.cc
@@ -0,0 +1,2414 @@
+#include "xpath.h"
+#include "parser.h"
+
+namespace ledger {
+namespace xml {
+
+#ifndef THREADSAFE
+xpath_t::token_t * xpath_t::lookahead = NULL;
+#endif
+
+void xpath_t::initialize()
+{
+ lookahead = new xpath_t::token_t;
+}
+
+void xpath_t::shutdown()
+{
+ delete lookahead;
+ lookahead = NULL;
+}
+
+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.next();
+ } 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_exception& err) {
+ // If the amount had no commodity, it must be an unambiguous
+ // variable reference
+
+ // jww (2007-04-19): There must be a more efficient way to do this!
+ 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;
+ }
+ }
+ }
+ break;
+ }
+}
+
+void xpath_t::token_t::rewind(std::istream& in)
+{
+ for (unsigned int i = 0; i < length; i++)
+ in.unget();
+}
+
+
+void xpath_t::token_t::unexpected()
+{
+ switch (kind) {
+ case TOK_EOF:
+ throw_(parse_exception, "Unexpected end of expression");
+ case IDENT:
+ throw_(parse_exception, "Unexpected symbol '" << value << "'");
+ case VALUE:
+ throw_(parse_exception, "Unexpected value '" << value << "'");
+ default:
+ throw_(parse_exception, "Unexpected operator '" << symbol << "'");
+ }
+}
+
+void xpath_t::token_t::unexpected(char c, char wanted)
+{
+ if ((unsigned char) c == 0xff) {
+ if (wanted)
+ throw_(parse_exception, "Missing '" << wanted << "'");
+ else
+ throw_(parse_exception, "Unexpected end");
+ } else {
+ if (wanted)
+ throw_(parse_exception, "Invalid char '" << c <<
+ "' (wanted '" << wanted << "')");
+ else
+ throw_(parse_exception, "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
+ return wrap_value(val);
+}
+
+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 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 string& name, op_t * def)
+{
+ DEBUG_("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> result2
+ = symbols.insert(symbol_pair(name, def));
+ if (! result2.second)
+ throw_(compile_exception,
+ "Redefinition of '" << name << "' in same scope");
+ }
+ def->acquire();
+}
+
+xpath_t::op_t *
+xpath_t::scope_t::lookup(const 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 string& name, functor_t * def) {
+ define(name, wrap_functor(def));
+}
+
+bool xpath_t::function_scope_t::resolve(const 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_(calc_exception, "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_("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:
+ throw_(calc_exception,
+ "Cannot determine value of expression symbol '" << *this << "'");
+ }
+}
+
+xpath_t::op_t *
+xpath_t::parse_value_term(std::istream& in, unsigned short tflags) const
+{
+ std::auto_ptr<op_t> node;
+
+ token_t& tok = next_token(in, tflags);
+
+ 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: {
+#if 0
+#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 string("__ptr");
+ eval->set_right(sym);
+
+ node.reset(eval);
+
+ goto done;
+ }
+ catch(const boost::python::error_already_set&) {
+ throw_(parse_exception, "Error parsing lambda expression");
+ }
+#endif /* USE_BOOST_PYTHON */
+#endif
+
+ string ident = tok.value.to_string();
+ int id = -1;
+ if (std::isdigit(ident[0])) {
+ node.reset(new op_t(op_t::ARG_INDEX));
+ node->arg_index = std::atol(ident.c_str());
+ }
+ else if ((id = document_t::lookup_builtin_id(ident)) != -1) {
+ node.reset(new op_t(op_t::NODE_ID));
+ node->name_id = id;
+ }
+ else {
+ node.reset(new op_t(op_t::NODE_NAME));
+ node->name = new string(ident);
+ }
+
+ // An identifier followed by ( represents a function call
+ tok = next_token(in, tflags);
+ 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, tflags | XPATH_PARSE_PARTIAL));
+
+ tok = next_token(in, tflags);
+ 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, tflags);
+ if (tok.kind != token_t::IDENT)
+ throw_(parse_exception, "@ symbol must be followed by attribute name");
+
+ node.reset(new op_t(op_t::ATTR_NAME));
+ node->name = new string(tok.value.to_string());
+ break;
+
+#if 0
+ case token_t::DOLLAR:
+ tok = next_token(in, tflags);
+ 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 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;
+ push_token();
+ 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, tflags | XPATH_PARSE_PARTIAL));
+ if (! node.get())
+ throw_(parse_exception,
+ tok.symbol << " operator not followed by argument");
+ tok = next_token(in, tflags);
+ 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;
+ }
+
+#if 0
+#ifdef USE_BOOST_PYTHON
+ done:
+#endif
+#endif
+ return node.release();
+}
+
+xpath_t::op_t *
+xpath_t::parse_predicate_expr(std::istream& in, unsigned short tflags) const
+{
+ std::auto_ptr<op_t> node(parse_value_term(in, tflags));
+
+ if (node.get()) {
+ token_t& tok = next_token(in, tflags);
+ 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, tflags | XPATH_PARSE_PARTIAL));
+ if (! node->right)
+ throw_(parse_exception, "[ operator not followed by valid expression");
+
+ tok = next_token(in, tflags);
+ if (tok.kind != token_t::RBRACKET)
+ tok.unexpected(); // jww (2006-09-09): wanted ]
+
+ tok = next_token(in, tflags);
+ }
+
+ push_token(tok);
+ }
+
+ return node.release();
+}
+
+xpath_t::op_t *
+xpath_t::parse_path_expr(std::istream& in, unsigned short tflags) const
+{
+ std::auto_ptr<op_t> node(parse_predicate_expr(in, tflags));
+
+ if (node.get()) {
+ token_t& tok = next_token(in, tflags);
+ while (tok.kind == token_t::SLASH) {
+ std::auto_ptr<op_t> prev(node.release());
+
+ tok = next_token(in, tflags);
+ 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, tflags));
+ if (! node->right)
+ throw_(parse_exception, "/ operator not followed by a valid term");
+
+ tok = next_token(in, tflags);
+ }
+
+ push_token(tok);
+ }
+
+ return node.release();
+}
+
+xpath_t::op_t *
+xpath_t::parse_unary_expr(std::istream& in, unsigned short tflags) const
+{
+ std::auto_ptr<op_t> node;
+
+ token_t& tok = next_token(in, tflags);
+
+ switch (tok.kind) {
+ case token_t::EXCLAM: {
+ std::auto_ptr<op_t> texpr(parse_path_expr(in, tflags));
+ if (! texpr.get())
+ throw_(parse_exception,
+ tok.symbol << " operator not followed by argument");
+ // A very quick optimization
+ if (texpr->kind == op_t::VALUE) {
+ *texpr->valuep = ! *texpr->valuep;
+ node.reset(texpr.release());
+ } else {
+ node.reset(new op_t(op_t::O_NOT));
+ node->set_left(texpr.release());
+ }
+ break;
+ }
+
+ case token_t::MINUS: {
+ std::auto_ptr<op_t> texpr(parse_path_expr(in, tflags));
+ if (! texpr.get())
+ throw_(parse_exception,
+ tok.symbol << " operator not followed by argument");
+ // A very quick optimization
+ if (texpr->kind == op_t::VALUE) {
+ texpr->valuep->in_place_negate();
+ node.reset(texpr.release());
+ } else {
+ node.reset(new op_t(op_t::O_NEG));
+ node->set_left(texpr.release());
+ }
+ break;
+ }
+
+#if 0
+ case token_t::PERCENT: {
+ std::auto_ptr<op_t> texpr(parse_path_expr(in, tflags));
+ if (! texpr.get())
+ throw_(parse_exception,
+ tok.symbol << " operator not followed by argument");
+ // A very quick optimization
+ if (texpr->kind == op_t::VALUE) {
+ static value_t perc("100.0%");
+ *texpr->valuep = perc * *texpr->valuep;
+ node.reset(texpr.release());
+ } else {
+ node.reset(new op_t(op_t::O_PERC));
+ node->set_left(texpr.release());
+ }
+ break;
+ }
+#endif
+
+ default:
+ push_token(tok);
+ node.reset(parse_path_expr(in, tflags));
+ break;
+ }
+
+ return node.release();
+}
+
+xpath_t::op_t *
+xpath_t::parse_union_expr(std::istream& in, unsigned short tflags) const
+{
+ std::auto_ptr<op_t> node(parse_unary_expr(in, tflags));
+
+ if (node.get()) {
+ token_t& tok = next_token(in, tflags);
+ 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, tflags));
+ if (! node->right)
+ throw_(parse_exception,
+ 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 tflags) const
+{
+ std::auto_ptr<op_t> node(parse_union_expr(in, tflags));
+
+ if (node.get()) {
+ token_t& tok = next_token(in, tflags);
+ 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, tflags));
+ if (! node->right)
+ throw_(parse_exception,
+ tok.symbol << " operator not followed by argument");
+
+ tok = next_token(in, tflags);
+ }
+ push_token(tok);
+ }
+
+ return node.release();
+}
+
+xpath_t::op_t *
+xpath_t::parse_add_expr(std::istream& in, unsigned short tflags) const
+{
+ std::auto_ptr<op_t> node(parse_mul_expr(in, tflags));
+
+ if (node.get()) {
+ token_t& tok = next_token(in, tflags);
+ 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, tflags));
+ if (! node->right)
+ throw_(parse_exception,
+ tok.symbol << " operator not followed by argument");
+
+ tok = next_token(in, tflags);
+ }
+ push_token(tok);
+ }
+
+ return node.release();
+}
+
+xpath_t::op_t *
+xpath_t::parse_logic_expr(std::istream& in, unsigned short tflags) const
+{
+ std::auto_ptr<op_t> node(parse_add_expr(in, tflags));
+
+ if (node.get()) {
+ op_t::kind_t kind = op_t::LAST;
+
+ unsigned short _flags = tflags;
+
+ token_t& tok = next_token(in, tflags);
+ 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, tflags));
+ else
+ node->set_right(parse_add_expr(in, _flags));
+
+ if (! node->right) {
+ if (tok.kind == token_t::PLUS)
+ throw_(parse_exception,
+ tok.symbol << " operator not followed by argument");
+ else
+ throw_(parse_exception,
+ tok.symbol << " operator not followed by argument");
+ }
+ }
+ }
+
+ return node.release();
+}
+
+xpath_t::op_t *
+xpath_t::parse_and_expr(std::istream& in, unsigned short tflags) const
+{
+ std::auto_ptr<op_t> node(parse_logic_expr(in, tflags));
+
+ if (node.get()) {
+ token_t& tok = next_token(in, tflags);
+ 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, tflags));
+ if (! node->right)
+ throw_(parse_exception,
+ 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 tflags) const
+{
+ std::auto_ptr<op_t> node(parse_and_expr(in, tflags));
+
+ if (node.get()) {
+ token_t& tok = next_token(in, tflags);
+ 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, tflags));
+ if (! node->right)
+ throw_(parse_exception,
+ 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 tflags) const
+{
+ std::auto_ptr<op_t> node(parse_or_expr(in, tflags));
+
+ if (node.get()) {
+ token_t& tok = next_token(in, tflags);
+ 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, tflags));
+ if (! node->right)
+ throw_(parse_exception,
+ tok.symbol << " operator not followed by argument");
+ tok = next_token(in, tflags);
+ if (tok.kind != token_t::COLON)
+ tok.unexpected(); // jww (2006-09-09): wanted :
+ node->right->set_right(parse_querycolon_expr(in, tflags));
+ if (! node->right)
+ throw_(parse_exception,
+ 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 tflags) const
+{
+ std::auto_ptr<op_t> node(parse_querycolon_expr(in, tflags));
+
+ if (node.get()) {
+ token_t& tok = next_token(in, tflags);
+ 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, tflags));
+ if (! node->right)
+ throw_(parse_exception,
+ tok.symbol << " operator not followed by argument");
+ tok = next_token(in, tflags);
+ }
+
+ if (tok.kind != token_t::TOK_EOF) {
+ if (tflags & XPATH_PARSE_PARTIAL)
+ push_token(tok);
+ else
+ tok.unexpected();
+ }
+ }
+ else if (! (tflags & XPATH_PARSE_PARTIAL)) {
+ throw_(parse_exception, "Failed to parse value expression");
+ }
+
+ return node.release();
+}
+
+xpath_t::op_t *
+xpath_t::parse_expr(std::istream& in, unsigned short tflags) const
+{
+ std::auto_ptr<op_t> node(parse_value_expr(in, tflags));
+
+ if (use_lookahead) {
+ use_lookahead = false;
+#ifdef THREADSAFE
+ lookahead.rewind(in);
+#else
+ lookahead->rewind(in);
+#endif
+ }
+#ifdef THREADSAFE
+ lookahead.clear();
+#else
+ lookahead->clear();
+#endif
+
+ 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 * tleft, op_t * tright) const
+{
+ std::auto_ptr<op_t> node(new op_t(kind));
+ if (tleft)
+ node->set_left(tleft);
+ if (tright)
+ node->set_right(tright);
+ 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_(calc_exception, "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_(calc_exception, "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& val,
+ value_t::sequence_t& result_seq)
+{
+ if (val.type == value_t::SEQUENCE) {
+ value_t::sequence_t * subseq = val.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(val);
+ }
+}
+
+xpath_t::op_t * xpath_t::op_t::compile(value_t * context, scope_t * scope,
+ bool resolve)
+{
+#if 0
+ try {
+#endif
+ 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_(compile_exception, "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_(compile_exception, "Referencing parent node from the root node");
+
+ case document_t::ROOT:
+ if (context->type != value_t::XML_NODE)
+ throw_(compile_exception, "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_(compile_exception, "Referencing child nodes from a non-node value");
+
+ node_t * ptr = context->to_xml_node();
+ if (! (ptr->flags & XML_NODE_IS_PARENT))
+ throw_(compile_exception, "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.
+
+ std::auto_ptr<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.release())->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_(compile_exception, "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->negate())->acquire();
+ } else {
+ expr->valuep->in_place_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_(compile_exception, "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;
+
+ 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_(calc_exception, "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_(compile_exception, "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_(compile_exception, "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;
+ }
+#if 0
+ }
+ 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;
+ }
+#endif
+
+ assert(0);
+ return NULL;
+}
+
+void xpath_t::calc(value_t& result, node_t * node, scope_t * scope) const
+{
+#if 0
+ try {
+#endif
+ 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);
+ }
+#if 0
+ }
+ 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;
+ }
+#endif
+}
+
+#if 0
+xpath_t::context::context(const xpath_t& _xpath,
+ const op_t * _err_node,
+ const string& desc) throw()
+ : error_context(desc), xpath(_xpath), err_node(_err_node)
+{
+ _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 (unsigned int i = 0; i < end - start; i++) {
+ if (i >= begin - start)
+ out << "^";
+ else
+ out << " ";
+ }
+ out << std::endl;
+ }
+}
+#endif
+
+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;
+
+ if (start_pos && this == op_to_find) {
+ *start_pos = (long)out.tellp() - 1;
+ found = true;
+ }
+
+ 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;
+
+ case value_t::XML_NODE:
+ out << '<' << valuep << '>';
+ break;
+ case value_t::POINTER:
+ out << '&' << valuep;
+ break;
+ case value_t::SEQUENCE:
+ 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
diff --git a/src/xpath.h b/src/xpath.h
new file mode 100644
index 00000000..13966ffc
--- /dev/null
+++ b/src/xpath.h
@@ -0,0 +1,783 @@
+#ifndef _XPATH_H
+#define _XPATH_H
+
+#include "xml.h"
+
+namespace ledger {
+namespace xml {
+
+class xpath_t
+{
+public:
+ struct op_t;
+
+ static void initialize();
+ static void shutdown();
+
+ DECLARE_EXCEPTION(parse_exception);
+ DECLARE_EXCEPTION(compile_exception);
+ DECLARE_EXCEPTION(calc_exception);
+
+#if 0
+ 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 string& desc = "") throw();
+ virtual ~context() throw();
+
+ virtual void describe(std::ostream& out) const throw();
+ };
+#endif
+
+public:
+ class scope_t;
+
+ class functor_t {
+ protected:
+ string fname;
+ public:
+ bool wants_args;
+
+ functor_t(const 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 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 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, string> : public functor_t {
+ public:
+ T * ptr;
+ string T::*dptr;
+
+ member_functor_t(const string& _name, T * _ptr, 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 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);
+ assert(locals || locals == NULL);
+ (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 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 string& pattern);
+#endif
+
+ template <typename T, typename U>
+ static op_t *
+ make_functor(const string& name, 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 string& fname, 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 string& fname, 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 string, op_t *> symbol_map;
+ typedef std::pair<const 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 string& name, op_t * def);
+ virtual bool resolve(const string& name, value_t& result,
+ scope_t * locals = NULL) {
+ if (parent)
+ return parent->resolve(name, result, locals);
+ return false;
+ }
+ virtual op_t * lookup(const string& name);
+
+ void define(const 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 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);
+ return *this;
+ }
+
+ void clear() {
+ kind = UNKNOWN;
+ length = 0;
+ value = 0L;
+
+ 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
+ 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_("ledger.xpath.memory",
+ "Releasing " << this << ", refc now " << refc - 1);
+ assert(refc > 0);
+ if (--refc == 0)
+ delete this;
+ }
+ op_t * acquire() {
+ DEBUG_("ledger.xpath.memory",
+ "Acquiring " << this << ", refc now " << refc + 1);
+ assert(refc >= 0);
+ refc++;
+ return this;
+ }
+ const op_t * acquire() const {
+ DEBUG_("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 tflags) const {
+ if (use_lookahead)
+ use_lookahead = false;
+ else
+#ifdef THREADSAFE
+ lookahead.next(in, tflags);
+#else
+ lookahead->next(in, tflags);
+#endif
+#ifdef THREADSAFE
+ return lookahead;
+#else
+ return *lookahead;
+#endif
+ }
+ void push_token(const token_t& tok) const {
+#ifdef THREADSAFE
+ assert(&tok == &lookahead);
+#else
+ assert(&tok == lookahead);
+#endif
+ 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 string& str,
+ unsigned short tflags = XPATH_PARSE_RELAXED) const
+ {
+ std::istringstream stream(str);
+#if 0
+ try {
+#endif
+ return parse_expr(stream, tflags);
+#if 0
+ }
+ catch (error * err) {
+ err->context.push_back
+ (new line_context(str, (long)stream.tellg() - 1,
+ "While parsing value expression:"));
+ throw err;
+ }
+#endif
+ }
+
+ op_t * parse_expr(const char * p,
+ unsigned short tflags = XPATH_PARSE_RELAXED) const {
+ return parse_expr(string(p), tflags);
+ }
+
+ 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);
+ return true;
+ }
+
+public:
+ 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 string& _expr,
+ unsigned short _flags = XPATH_PARSE_RELAXED)
+ : ptr(NULL), use_lookahead(false), flags(0) {
+ TRACE_CTOR(xpath_t, "const 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);
+ reset(NULL);
+ }
+
+ xpath_t& operator=(const 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 string() const throw() {
+ return expr;
+ }
+
+ void parse(const 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 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_t tdoc;
+ compile(tdoc.top, scope);
+ } else {
+ 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 * tcontext, scope_t * scope = NULL) const {
+ if (! ptr)
+ return 0L;
+ value_t temp;
+ calc(temp, tcontext, scope);
+ return temp;
+ }
+
+ static void eval(value_t& result, const string& _expr,
+ document_t * document, scope_t * scope = NULL) {
+ xpath_t temp(_expr);
+ temp.calc(result, document->top, scope);
+ }
+ static value_t eval(const 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;
+};
+
+inline std::ostream& operator<<(std::ostream& out, const xpath_t::op_t& op) {
+ op.write(out);
+ return out;
+};
+
+} // namespace xml
+
+template <typename T>
+inline T * get_ptr(xml::xpath_t::scope_t * locals, unsigned 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&, 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