summaryrefslogtreecommitdiff
path: root/src/data
diff options
context:
space:
mode:
Diffstat (limited to 'src/data')
-rw-r--r--src/data/builder.h256
-rw-r--r--src/data/compile.cc244
-rw-r--r--src/data/compile.h206
-rw-r--r--src/data/document.cc180
-rw-r--r--src/data/document.h152
-rw-r--r--src/data/jbuilder.cc67
-rw-r--r--src/data/jbuilder.h81
-rw-r--r--src/data/journal.cc697
-rw-r--r--src/data/journal.h439
-rw-r--r--src/data/node.cc130
-rw-r--r--src/data/node.h352
-rw-r--r--src/data/parser.h138
-rw-r--r--src/data/textual.cc480
-rw-r--r--src/data/textual.h51
14 files changed, 3473 insertions, 0 deletions
diff --git a/src/data/builder.h b/src/data/builder.h
new file mode 100644
index 00000000..a193879e
--- /dev/null
+++ b/src/data/builder.h
@@ -0,0 +1,256 @@
+/*
+ * 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 _BUILDER_H
+#define _BUILDER_H
+
+#include "document.h"
+
+namespace ledger {
+namespace xml {
+
+/**
+ * @class builder_t
+ *
+ * @brief Represents an interface for building a data hierarchy.
+ *
+ * This interface is much like .NET's XmlWriter facility. It
+ * abstracts the kind of hierarchy we're building, instead focusing
+ * only on the relationships.
+ */
+class builder_t
+{
+public:
+ struct position_t
+ {
+ typedef uint_least32_t file_pos_t;
+ typedef uint_least32_t file_line_t;
+
+ file_pos_t offset;
+ file_line_t linenum;
+
+ position_t() : offset(0), linenum(0) {}
+
+ explicit position_t(file_pos_t _offset,
+ file_line_t _linenum)
+ : offset(_offset), linenum(_linenum) {}
+ };
+
+protected:
+ position_t current_position;
+
+public:
+ virtual ~builder_t() {}
+
+ virtual void set_start_position(std::istream& in) {}
+ virtual void set_position(const position_t& position) {}
+ virtual position_t& position() { return current_position; }
+
+ virtual void push_attr(const string& name,
+ const string& value) = 0;
+ virtual void push_attr(const node_t::nameid_t name_id,
+ const string& value) = 0;
+
+ virtual void begin_node(const string& name, bool terminal = false) = 0;
+ virtual void begin_node(const node_t::nameid_t name_id, bool terminal = false) = 0;
+
+ virtual void push_node(const string& name,
+ const optional<position_t>& end_pos = none) = 0;
+ virtual void push_node(const node_t::nameid_t name_id,
+ const optional<position_t>& end_pos = none) = 0;
+
+ virtual node_t * current_node() = 0;
+
+ virtual void append_text(const string& text) = 0;
+
+ virtual node_t * end_node(const string& name,
+ const optional<position_t>& end_pos = none) = 0;
+ virtual node_t * end_node(const node_t::nameid_t name_id,
+ const optional<position_t>& end_pos = none) = 0;
+};
+
+/**
+ * @class xml_builder_t
+ *
+ * @brief Build a generic node_t hierarchy.
+ *
+ * This builder can be used to parse ordinary XML into a document
+ * object structure which can then be traversed in memory.
+ */
+class document_builder_t : public builder_t
+{
+protected:
+ typedef std::list<std::pair<node_t::nameid_t, string> > attrs_list;
+
+ document_t& document_;
+ attrs_list current_attrs;
+ node_t * current;
+ string current_text;
+
+public:
+ document_builder_t(document_t& _document)
+ : document_(_document), current(&document_) {}
+
+ virtual void push_attr(const string& name,
+ const string& value) {
+ push_attr(document().register_name(name), value);
+ }
+ virtual void push_attr(const node_t::nameid_t name_id,
+ const string& value) {
+ current_attrs.push_back(attrs_list::value_type(name_id, value.c_str()));
+ }
+
+ virtual void begin_node(const string& name, bool terminal = false) {
+ begin_node(document().register_name(name), terminal);
+ }
+ virtual void begin_node(const node_t::nameid_t name_id,
+ bool terminal = false) {
+ if (terminal)
+ current = current->as_parent_node().create_child<terminal_node_t>(name_id);
+ else
+ current = current->as_parent_node().create_child<parent_node_t>(name_id);
+
+ foreach (const attrs_list::value_type& pair, current_attrs)
+ current->set_attr(pair.first, pair.second.c_str());
+ current_attrs.clear();
+ }
+
+ virtual void push_node(const string& name,
+ const optional<position_t>& end_pos = none) {
+ begin_node(name, true);
+ end_node(name, end_pos);
+ }
+ virtual void push_node(const node_t::nameid_t name_id,
+ const optional<position_t>& end_pos = none) {
+ begin_node(name_id, true);
+ end_node(name_id, end_pos);
+ }
+
+ virtual document_t& document() {
+ return document_;
+ }
+ virtual node_t * current_node() {
+ return current;
+ }
+
+ virtual void append_text(const string& text) {
+ assert(! current->is_parent_node());
+ polymorphic_downcast<terminal_node_t *>(current)->set_text(text);
+ }
+
+ virtual node_t * end_node(const string& name,
+ const optional<position_t>& end_pos = none) {
+ return current = &*current->parent();
+ }
+ virtual node_t * end_node(const node_t::nameid_t name_id,
+ const optional<position_t>& end_pos = none) {
+ return current = &*current->parent();
+ }
+};
+
+/**
+ * @class xml_writer_t
+ *
+ * @brief Create textual XML on the given output stream.
+ *
+ * This builder, rather than manipulating data structures in memory,
+ * simply streams its contents on the fly to the given output stream.
+ * It uses only enough memory to remember the currently push
+ * attributes and text.
+ */
+class xml_writer_t : public builder_t
+{
+ typedef std::list<std::pair<string, string> > attrs_list;
+
+ attrs_list current_attrs;
+ std::ostream& outs;
+
+public:
+ xml_writer_t(std::ostream& _outs) : outs(_outs) {
+ outs << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
+ begin_node("ledger");
+ }
+ ~xml_writer_t() {
+ end_node("ledger");
+ }
+
+ virtual void push_attr(const string& name,
+ const string& value) {
+ current_attrs.push_back(attrs_list::value_type(name, value));
+ }
+ virtual void push_attr(const node_t::nameid_t name_id,
+ const string& value) {
+ push_attr("hello", value);
+ }
+
+ virtual void begin_node(const string& name, bool terminal = false) {
+ outs << '<' << name;
+ foreach (const attrs_list::value_type& attr, current_attrs)
+ outs << ' ' << attr.first << "=\"" << attr.second << "\"";
+ current_attrs.clear();
+ outs << '>';
+ }
+ virtual void begin_node(const node_t::nameid_t name_id, bool terminal = false) {
+ begin_node("hello");
+ }
+
+ virtual void push_node(const string& name,
+ const optional<position_t>& end_pos = none) {
+ begin_node(name, true);
+ end_node(name, end_pos);
+ }
+ virtual void push_node(const node_t::nameid_t name_id,
+ const optional<position_t>& end_pos = none) {
+ push_node("hello", end_pos);
+ }
+
+ virtual node_t * current_node() { return NULL; }
+
+ virtual void append_text(const string& text) {
+ outs << text;
+ }
+
+ virtual node_t * end_node(const string& name,
+ const optional<position_t>& end_pos = none) {
+ outs << "</" << name << '>';
+ return NULL;
+ }
+ virtual node_t * end_node(const node_t::nameid_t name_id,
+ const optional<position_t>& end_pos = none) {
+ end_node("hello", end_pos);
+ return NULL;
+ }
+};
+
+} // namespace xml
+} // namespace ledger
+
+#endif // _BUILDER_H
diff --git a/src/data/compile.cc b/src/data/compile.cc
new file mode 100644
index 00000000..0b924418
--- /dev/null
+++ b/src/data/compile.cc
@@ -0,0 +1,244 @@
+/*
+ * 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 "compile.h"
+#include "parser.h"
+
+namespace ledger {
+namespace xml {
+
+void compile_node(node_t& node, xpath_t::scope_t& scope)
+{
+ switch (node.name_id()) {
+ case JOURNAL_NODE:
+ downcast<journal_node_t>(node).compile(scope);
+ break;
+ case ENTRY_NODE:
+ downcast<entry_node_t>(node).compile(scope);
+ break;
+ case TRANSACTION_NODE:
+ downcast<transaction_node_t>(node).compile(scope);
+ break;
+
+ default:
+ break;
+ }
+
+ node.compiled = true;
+
+ if (node.is_parent_node())
+ foreach (node_t * child, node.as_parent_node())
+ compile_node(*child, scope);
+}
+
+void journal_node_t::compile(xpath_t::scope_t& scope)
+{
+ if (! journal.get())
+ journal.reset(new journal_t);
+}
+
+void entry_node_t::compile(xpath_t::scope_t& scope)
+{
+ parent_node_t& parent_node(*parent());
+
+ assert(parent_node.name_id() == JOURNAL_NODE);
+ assert(parent_node.is_compiled());
+
+ journal_t * journal = downcast<journal_node_t>(parent_node).journal.get();
+
+ if (! entry.get()) {
+ entry.reset(new entry_t);
+#if 0
+ journal->add_entry(entry.get());
+#endif
+ }
+ entry->journal = journal;
+
+ foreach (attr_pair& attr, *attributes) {
+ if (attr.first == DATE_ATTR && attr.second.is_string())
+ entry->_date = parse_datetime(attr.second.as_string().c_str());
+ else if (attr.first == EFF_DATE_ATTR && attr.second.is_string())
+ entry->_date_eff = parse_datetime(attr.second.as_string().c_str());
+ else if (attr.first == CODE_ATTR)
+ entry->code = attr.second.as_string();
+ }
+}
+
+void transaction_node_t::parse_amount_expr(xpath_t::scope_t& scope,
+ const char * amount_expr)
+{
+ value_t * amount;
+
+ std::istringstream in(amount_expr);
+
+ PUSH_CONTEXT();
+
+ // jww (2006-09-15): Make sure it doesn't gobble up the upcoming @ symbol
+
+ unsigned long beg = (long)in.tellg();
+
+ amount_t temp;
+ temp.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);
+
+ xpath_t xpath(in, (XPATH_PARSE_NO_REDUCE | XPATH_PARSE_RELAXED |
+ XPATH_PARSE_PARTIAL));
+
+ xpath_t::context_scope_t node_scope(scope, this);
+ amount = &set_attr(AMOUNT_ATTR, xpath.calc(node_scope));
+
+ //unsigned long end = (long)in.tellg();
+ } else {
+ amount = &set_attr(AMOUNT_ATTR, temp);
+ }
+
+ // jww (2007-04-30): This should be a string context, or perhaps a
+ // file context
+ POP_CONTEXT(context("While parsing transaction amount"));
+
+ // Parse the optional cost (@ PER-UNIT-COST, @@ TOTAL-COST)
+
+ unsigned int linenum = -1;
+
+ 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()) {
+ amount_t temp;
+
+ PUSH_CONTEXT();
+
+ //unsigned long beg = (long)in.tellg();
+
+ temp.parse(in);
+
+ if (temp.sign() < 0)
+ throw_(parse_error, "A transaction's cost may not be negative");
+
+ //unsigned long end = (long)in.tellg();
+
+ POP_CONTEXT(context("While parsing transaction cost"));
+
+ amount_t per_unit_cost(temp);
+ amount_t& base_amount(amount->as_amount_lval());
+ if (per_unit)
+ temp *= base_amount.number();
+ else
+ per_unit_cost /= base_amount.number();
+
+ value_t& cost = set_attr(COST_ATTR, temp);
+
+ if (base_amount.commodity() && ! base_amount.commodity().annotated) {
+ assert(transaction);
+ assert(transaction->entry);
+ base_amount.annotate_commodity
+ (annotation_t(per_unit_cost, transaction->entry->actual_date(),
+ transaction->entry->code));
+ }
+
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Total cost is " << cost);
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Per-unit cost is " << per_unit_cost);
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Annotated amount is " << base_amount);
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Bare amount is " << base_amount.number());
+ }
+ }
+ }
+
+ amount->in_place_reduce();
+
+ DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
+ "Reduced amount is " << *amount);
+}
+
+void transaction_node_t::compile(xpath_t::scope_t& scope)
+{
+ parent_node_t& parent_node(*parent());
+
+ assert(parent_node.name_id() == ENTRY_NODE);
+ assert(parent_node.is_compiled());
+
+ entry_t * entry = downcast<entry_node_t>(parent_node).entry.get();
+
+ if (! transaction.get()) {
+ transaction.reset(new transaction_t);
+#if 0
+ entry->add_transaction(transaction.get());
+#endif
+ }
+ transaction->entry = entry;
+
+ foreach (node_t * child, *this) {
+ switch (child->name_id()) {
+ case AMOUNT_EXPR_NODE:
+ parse_amount_expr(scope, child->as_terminal_node().text());
+ break;
+
+ case ACCOUNT_PATH_NODE: {
+ assert(entry);
+
+ journal_t * journal = entry->journal;
+ assert(journal);
+
+ transaction->account =
+ journal->find_account(child->as_terminal_node().text());
+
+ // jww (2007-05-18): Need to set an attribute that refers to the
+ // unique id of the account
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+}
+
+} // namespace xml
+} // namespace ledger
diff --git a/src/data/compile.h b/src/data/compile.h
new file mode 100644
index 00000000..d8b9f536
--- /dev/null
+++ b/src/data/compile.h
@@ -0,0 +1,206 @@
+/*
+ * 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 _COMPILE_H
+#define _COMPILE_H
+
+#include "node.h"
+#include "journal.h"
+
+namespace ledger {
+namespace xml {
+
+void compile_node(node_t& node, xml::xpath_t::scope_t& scope);
+
+#if 0
+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;
+ }
+};
+#endif
+
+class journal_node_t : public parent_node_t
+{
+public:
+ shared_ptr<journal_t> journal;
+
+ journal_node_t(nameid_t _name_id,
+ document_t& _document,
+ const optional<parent_node_t&>& _parent = none,
+ journal_t * _journal = NULL)
+ : parent_node_t(_name_id, _document, _parent), journal(_journal) {
+ TRACE_CTOR(journal_node_t, "document_t *, journal_t *, parent_node_t *");
+ }
+ virtual ~journal_node_t() {
+ TRACE_DTOR(journal_node_t);
+ }
+
+ void compile(xpath_t::scope_t& scope);
+};
+
+class entry_node_t : public parent_node_t
+{
+public:
+ shared_ptr<entry_t> entry;
+
+ entry_node_t(nameid_t _name_id,
+ document_t& _document,
+ const optional<parent_node_t&>& _parent = none,
+ entry_t * _entry = NULL)
+ : parent_node_t(_name_id, _document, _parent), entry(_entry) {
+ TRACE_CTOR(entry_node_t, "document_t&, parent_node_t, entry_t *");
+ assert(_name_id == ENTRY_NODE);
+ }
+ virtual ~entry_node_t() {
+ TRACE_DTOR(entry_node_t);
+ }
+
+ void compile(xpath_t::scope_t& scope);
+};
+
+class transaction_node_t : public parent_node_t
+{
+public:
+ shared_ptr<transaction_t> transaction;
+
+ transaction_node_t(nameid_t _name_id,
+ document_t& _document,
+ const optional<parent_node_t&>& _parent = none,
+ transaction_t * _transaction = NULL)
+ : parent_node_t(_name_id, _document, _parent),
+ transaction(_transaction) {
+ TRACE_CTOR(transaction_node_t,
+ "document_t&, parent_node_t, transaction_t *");
+ assert(_name_id == TRANSACTION_NODE);
+ }
+ virtual ~transaction_node_t() {
+ TRACE_DTOR(transaction_node_t);
+ }
+
+ void compile(xpath_t::scope_t& scope);
+
+private:
+ void parse_amount_expr(xpath_t::scope_t& scope,
+ const char * amount_expr);
+};
+
+#if 0
+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;
+};
+
+template <typename T>
+inline typename T::node_type *
+wrap_node(document_t * doc, T * item, void * parent_node = NULL) {
+ assert(false);
+ return NULL;
+}
+
+template <>
+inline transaction_t::node_type *
+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 entry_t::node_type *
+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 account_t::node_type *
+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 journal_t::node_type *
+wrap_node(document_t * doc, journal_t * journal, void * parent_node) {
+ return new journal_node_t(doc, journal, (parent_node_t *)parent_node);
+}
+#endif
+
+} // namespace xml
+} // namespace ledger
+
+#endif // _COMPILE_H
diff --git a/src/data/document.cc b/src/data/document.cc
new file mode 100644
index 00000000..120440b0
--- /dev/null
+++ b/src/data/document.cc
@@ -0,0 +1,180 @@
+/*
+ * 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 "document.h"
+
+namespace ledger {
+namespace xml {
+
+namespace {
+ const std::size_t ledger_builtins_size = 39;
+ const char * ledger_builtins[] = {
+ "account",
+ "account-path",
+ "amount",
+ "amount",
+ "amount-expr",
+ "arg",
+ "auto-entry",
+ "balance",
+ "checkin",
+ "cleared",
+ "code",
+ "commodity",
+ "commodity-conversion",
+ "commodity-nomarket",
+ "commodity-template",
+ "cost",
+ "current-year",
+ "date",
+ "default-account",
+ "directive",
+ "effective",
+ "entries",
+ "entry",
+ "from",
+ "journal",
+ "ledger",
+ "name",
+ "note",
+ "payee",
+ "pending",
+ "period",
+ "period-entry",
+ "price",
+ "price-history",
+ "rule",
+ "symbol",
+ "template",
+ "time",
+ "to",
+ "transaction",
+ "virtual",
+ "year"
+ };
+}
+
+node_t::nameid_t document_t::register_name(const string& name)
+{
+ optional<nameid_t> index = lookup_name_id(name);
+ if (index)
+ return *index;
+
+ if (! names)
+ names = names_t();
+
+ nameid_t real_index = names->size() + 1000;
+ names->push_back(name_pair(name, real_index));
+ DEBUG("xml.lookup", this << " Inserted name: " << name);
+
+ return real_index;
+}
+
+optional<node_t::nameid_t> document_t::lookup_name_id(const string& name) const
+{
+ if (optional<node_t::nameid_t> id = lookup_builtin_id(name))
+ return id;
+
+ if (! names)
+ return none;
+
+ DEBUG("xml.lookup", this << " Finding name: " << name);
+
+ typedef names_t::nth_index<1>::type names_by_name;
+
+ const names_by_name& name_index = names->get<1>();
+ names_by_name::const_iterator i = name_index.find(name);
+ if (i != name_index.end())
+ return (*i).second;
+
+ return none;
+}
+
+optional<node_t::nameid_t> 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 nameid_t(mid + 10);
+ }
+
+ return none;
+}
+
+optional<const char *> document_t::lookup_name(nameid_t id) const
+{
+ if (id < 1000) {
+ switch (id) {
+ case CURRENT:
+ return ".";
+ case PARENT:
+ return "..";
+ case ROOT:
+ return "";
+ case ALL:
+ return "*";
+
+ default:
+ assert(id >= 10);
+ return ledger_builtins[id - 10];
+ }
+ }
+ else if (names) {
+ assert(id >= 1000);
+ std::size_t index = id - 1000;
+ typedef names_t::nth_index<0>::type names_by_random_access;
+ const names_by_random_access& random_access = names->get<0>();
+ if (index < random_access.size())
+ return random_access[index].first.c_str();
+ }
+ return none;
+}
+
+void document_t::print(std::ostream& out) const
+{
+ out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+ parent_node_t::print(out);
+}
+
+} // namespace xml
+} // namespace ledger
diff --git a/src/data/document.h b/src/data/document.h
new file mode 100644
index 00000000..c1dcf88e
--- /dev/null
+++ b/src/data/document.h
@@ -0,0 +1,152 @@
+/*
+ * 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 _DOCUMENT_H
+#define _DOCUMENT_H
+
+#include "node.h"
+#include "value.h"
+
+namespace ledger {
+namespace xml {
+
+enum ledger_builtins_t {
+ ACCOUNT_ATTR = 10,
+ ACCOUNT_PATH_NODE,
+ AMOUNT_ATTR,
+ AMOUNT_NODE,
+ AMOUNT_EXPR_NODE,
+ ARG_ATTR,
+ AUTO_ENTRY_NODE,
+ BALANCE_ATTR,
+ CHECKIN_NODE,
+ CLEARED_ATTR,
+ CODE_ATTR,
+ COMMODITY_NODE,
+ COMMODITY_CONVERSION_NODE,
+ COMMODITY_NOMARKET_NODE,
+ COMMODITY_TEMPLATE_NODE,
+ COST_ATTR,
+ CURRENT_YEAR_NODE,
+ DATE_ATTR,
+ DEFAULT_ACCOUNT_NODE,
+ DIRECTIVE_NODE,
+ EFF_DATE_ATTR,
+ ENTRIES_NODE,
+ ENTRY_NODE,
+ FROM_ATTR,
+ JOURNAL_NODE,
+ LEDGER_NODE,
+ NAME_ATTR,
+ NOTE_NODE,
+ PAYEE_NODE,
+ PENDING_ATTR,
+ PERIOD_NODE,
+ PERIOD_ENTRY_NODE,
+ PRICE_ATTR,
+ PRICE_HISTORY_NODE,
+ RULE_NODE,
+ SYMBOL_ATTR,
+ TEMPLATE_ATTR,
+ TIME_ATTR,
+ TO_ATTR,
+ TRANSACTION_NODE,
+ VIRTUAL_ATTR,
+ YEAR_ATTR
+};
+
+class document_t : public parent_node_t
+{
+ typedef std::pair<string, nameid_t> name_pair;
+
+ typedef multi_index_container<
+ name_pair,
+ multi_index::indexed_by<
+ multi_index::random_access<>,
+ multi_index::hashed_unique<
+ multi_index::member<name_pair, string, &name_pair::first> >
+ >
+ > names_t;
+
+ optional<names_t> names;
+
+public:
+ // 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, LAST_BUILTIN = 10
+ };
+
+ document_t(node_t::nameid_t _name_id)
+ : parent_node_t(_name_id, *this) {
+ TRACE_CTOR(xml::document_t, "node_t::nameid_t");
+ }
+ ~document_t() {
+ TRACE_DTOR(xml::document_t);
+ }
+
+ nameid_t register_name(const string& name);
+
+ optional<nameid_t> lookup_name_id(const string& name) const;
+ static optional<nameid_t> lookup_builtin_id(const string& name);
+ optional<const char *> lookup_name(nameid_t id) const;
+
+ void print(std::ostream& out) const;
+
+#if 0
+#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
+#endif
+};
+
+} // namespace xml
+} // namespace ledger
+
+#endif // _DOCUMENT_H
diff --git a/src/data/jbuilder.cc b/src/data/jbuilder.cc
new file mode 100644
index 00000000..f37abca5
--- /dev/null
+++ b/src/data/jbuilder.cc
@@ -0,0 +1,67 @@
+/*
+ * 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 "jbuilder.h"
+#include "compile.h"
+
+namespace ledger {
+namespace xml {
+
+void journal_builder_t::begin_node(const node_t::nameid_t name_id,
+ bool terminal)
+{
+ switch (name_id) {
+ case JOURNAL_NODE:
+ current = current->as_parent_node().create_child<journal_node_t>(name_id);
+ break;
+ case ENTRY_NODE:
+ current = current->as_parent_node().create_child<entry_node_t>(name_id);
+ break;
+ case TRANSACTION_NODE:
+ current = current->as_parent_node().create_child<transaction_node_t>(name_id);
+ break;
+
+ default:
+ if (terminal)
+ current = current->as_parent_node().create_child<terminal_node_t>(name_id);
+ else
+ current = current->as_parent_node().create_child<parent_node_t>(name_id);
+ break;
+ }
+
+ foreach (const attrs_list::value_type& pair, current_attrs)
+ current->set_attr(pair.first, pair.second.c_str());
+
+ current_attrs.clear();
+}
+
+} // namespace xml
+} // namespace ledger
diff --git a/src/data/jbuilder.h b/src/data/jbuilder.h
new file mode 100644
index 00000000..4e109a35
--- /dev/null
+++ b/src/data/jbuilder.h
@@ -0,0 +1,81 @@
+/*
+ * 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 _JBUILDER_H
+#define _JBUILDER_H
+
+#include "builder.h"
+#include "journal.h"
+
+namespace ledger {
+namespace xml {
+
+/**
+ * @class journal_builder_t
+ *
+ * @brief This custom builder creates an XML-mirrored Ledger journal.
+ *
+ * Rather than simply creating a node_t hierarchy, as xml_builder_t
+ * does, this code creates the associated journal elements referred to
+ * by those nodes, and then refers to those elements via minimalist
+ * "shadow nodes".
+ *
+ * Thus, after building a <transaction> element, the element itself
+ * will have no children, but instead will point to a transaction_t
+ * object. If later an XPath expression desires to traverse the
+ * <transaction> element, all of the appropriate child nodes will be
+ * constructed on the fly, as if they'd been created in the first
+ * place by a regular xml_builder_t.
+ */
+class journal_builder_t : public document_builder_t
+{
+public:
+ journal_t * journal;
+
+ journal_builder_t(document_t& _document, journal_t * _journal)
+ : document_builder_t(_document), journal(_journal) {}
+
+ virtual void set_start_position(std::istream& in) {
+ set_position(position_t(in.tellg(), 1));
+ }
+
+ virtual void set_position(const position_t& position) {
+ current_position = position;
+ }
+
+ virtual void begin_node(const node_t::nameid_t name_id,
+ bool terminal = false);
+};
+
+} // namespace xml
+} // namespace ledger
+
+#endif // _JBUILDER_H
diff --git a/src/data/journal.cc b/src/data/journal.cc
new file mode 100644
index 00000000..32e45697
--- /dev/null
+++ b/src/data/journal.cc
@@ -0,0 +1,697 @@
+/*
+ * 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 "journal.h"
+#include "xpath.h"
+#include "mask.h"
+
+namespace ledger {
+
+const string version = PACKAGE_VERSION;
+
+bool transaction_t::use_effective_date = false;
+
+transaction_t::~transaction_t()
+{
+ TRACE_DTOR(transaction_t);
+}
+
+moment_t transaction_t::actual_date() const
+{
+ if (! _date && entry)
+ return entry->actual_date();
+ return *_date;
+}
+
+moment_t transaction_t::effective_date() const
+{
+ if (! _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;
+ }
+
+ transactions_list::const_iterator i =
+ std::find(entry->transactions.begin(),
+ entry->transactions.end(), this);
+ if (i == entry->transactions.end()) {
+ 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;
+ }
+
+ 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)->has_flags(TRANSACTION_VIRTUAL) ||
+ (*x)->has_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;
+ }
+
+ assert((*x)->amount);
+ if ((*x)->cost && (*x)->amount.commodity().annotated) {
+ annotated_commodity_t&
+ ann_comm(static_cast<annotated_commodity_t&>
+ ((*x)->amount.commodity()));
+ if (ann_comm.details.price)
+ balance += (*ann_comm.details.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.is_amount());
+ 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->add_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.is_balance() &&
+ balance.as_balance().amounts.size() == 2) {
+ transactions_list::const_iterator x = transactions.begin();
+ assert((*x)->amount);
+ commodity_t& this_comm = (*x)->amount.commodity();
+
+ balance_t::amounts_map::const_iterator this_bal =
+ balance.as_balance().amounts.find(&this_comm);
+ balance_t::amounts_map::const_iterator other_bal =
+ balance.as_balance().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)->has_flags(TRANSACTION_VIRTUAL) ||
+ (*x)->amount.commodity() != this_comm)
+ continue;
+
+ balance -= (*x)->amount;
+
+ entry_t * entry = dynamic_cast<entry_t *>(this);
+
+ if ((*x)->amount.commodity() &&
+ ! (*x)->amount.commodity().annotated)
+ (*x)->amount.annotate_commodity
+ (annotation_t(per_unit_cost.abs(),
+ entry ? entry->actual_date() : optional<moment_t>(),
+ entry ? entry->code : optional<string>()));
+
+ (*x)->cost = - (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 ||
+ ((*x)->has_flags(TRANSACTION_VIRTUAL) &&
+ ! (*x)->has_flags(TRANSACTION_BALANCE)))
+ continue;
+
+ if (! empty_allowed)
+ throw_(std::logic_error,
+ "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.
+
+ const balance_t * bal = NULL;
+ switch (balance.type()) {
+ case value_t::BALANCE_PAIR:
+ bal = &balance.as_balance_pair().quantity;
+ // fall through...
+
+ case value_t::BALANCE:
+ if (! bal)
+ bal = &balance.as_balance();
+
+ if (bal->amounts.size() < 2) {
+ balance.cast(value_t::AMOUNT);
+ } else {
+ bool first = true;
+ for (balance_t::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->add_flags(TRANSACTION_CALCULATED);
+ nxact->amount = amt;
+ }
+
+ balance += amt;
+ }
+ break;
+ }
+ // fall through...
+
+ case value_t::AMOUNT:
+ (*x)->amount = balance.as_amount().negate();
+ (*x)->add_flags(TRANSACTION_CALCULATED);
+
+ balance += (*x)->amount;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (balance) {
+#if 1
+ throw_(balance_error, "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)
+{
+ 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;
+}
+
+auto_entry_t::auto_entry_t()
+{
+ TRACE_CTOR(auto_entry_t, "");
+}
+
+auto_entry_t::auto_entry_t(const string& _predicate)
+ : predicate(new xml::xpath_t(_predicate))
+{
+ TRACE_CTOR(auto_entry_t, "const string&");
+}
+
+auto_entry_t::~auto_entry_t() {
+ TRACE_DTOR(auto_entry_t);
+}
+
+void auto_entry_t::extend_entry(entry_base_t& entry, bool post)
+{
+#if 0
+ 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;
+ assert((*t)->amount);
+ if (! (*t)->amount.commodity()) {
+ if (! post)
+ continue;
+ assert((*i)->amount);
+ 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);
+ }
+ }
+ }
+#endif
+}
+
+account_t::~account_t()
+{
+ TRACE_DTOR(account_t);
+
+ for (accounts_map::iterator i = accounts.begin();
+ i != accounts.end();
+ i++)
+ checked_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_map::value_type(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);
+ checked_delete(master);
+
+ // 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)
+ checked_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)
+ checked_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)
+ checked_delete(*i);
+ else
+ (*i)->~period_entry_t();
+
+ if (item_pool)
+ checked_array_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) {
+ assert((*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;
+ }
+
+ 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(false);
+ }
+
+#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/data/journal.h b/src/data/journal.h
new file mode 100644
index 00000000..ed92bffb
--- /dev/null
+++ b/src/data/journal.h
@@ -0,0 +1,439 @@
+/*
+ * 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 _JOURNAL_H
+#define _JOURNAL_H
+
+#include "amount.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 supports_flags<>
+{
+ public:
+ enum state_t { UNCLEARED, CLEARED, PENDING };
+
+ entry_t * entry;
+ state_t state;
+ account_t * account;
+ optional<moment_t> _date;
+ optional<moment_t> _date_eff;
+ amount_t amount;
+ optional<amount_t> cost;
+ optional<string> note;
+
+ static bool use_effective_date;
+
+ explicit transaction_t(account_t * _account = NULL)
+ : supports_flags<>(TRANSACTION_NORMAL), entry(NULL),
+ state(UNCLEARED), account(_account) {
+ TRACE_CTOR(transaction_t, "account_t *");
+ }
+ explicit transaction_t(account_t * _account,
+ const amount_t& _amount,
+ unsigned int _flags = TRANSACTION_NORMAL,
+ const optional<string> _note = none)
+ : supports_flags<>(_flags), entry(NULL), state(UNCLEARED),
+ account(_account), amount(_amount), note(_note) {
+ TRACE_CTOR(transaction_t,
+ "account_t *, const amount_t&, unsigned int, const string&");
+ }
+ explicit transaction_t(const transaction_t& xact)
+ : supports_flags<>(xact),
+ entry(xact.entry),
+ state(xact.state),
+ account(xact.account),
+ _date(xact._date),
+ _date_eff(xact._date_eff),
+ amount(xact.amount),
+ cost(xact.cost),
+ note(xact.note) {
+ 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 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;
+ transactions_list transactions;
+
+ entry_base_t() : journal(NULL) {
+ TRACE_CTOR(entry_base_t, "");
+ }
+ entry_base_t(const entry_base_t& e) : journal(NULL)
+ {
+ 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)->has_flags(TRANSACTION_BULK_ALLOC))
+ checked_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;
+ optional<moment_t> _date_eff;
+ optional<string> code;
+ string payee;
+
+ entry_t() {
+ 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 {
+ return _date_eff ? *_date_eff : _date;
+ }
+ 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
+
+class auto_entry_t : public entry_base_t
+{
+public:
+ scoped_ptr<xml::xpath_t> predicate;
+
+ auto_entry_t();
+ auto_entry_t(const string& _predicate);
+ virtual ~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;
+
+class account_t
+{
+ public:
+ typedef unsigned long ident_t;
+
+ journal_t * journal;
+ account_t * parent;
+ string name;
+ optional<string> note;
+ unsigned short depth;
+ accounts_map accounts;
+
+ mutable ident_t ident;
+ mutable string _fullname;
+
+ account_t(account_t * _parent = NULL,
+ const string& _name = "",
+ const optional<string> _note = none)
+ : parent(_parent), name(_name), note(_note),
+ depth(parent ? parent->depth + 1 : 0), ident(0) {
+ TRACE_CTOR(account_t, "account_t *, const string&, const string&");
+ }
+ ~account_t();
+
+ operator string() const {
+ return fullname();
+ }
+ string fullname() const;
+
+ void add_account(account_t * acct) {
+ accounts.insert(accounts_map::value_type(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);
+
+ 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<path> path_list;
+typedef std::list<string> strings_list;
+
+class journal_t
+{
+ public:
+ account_t * master;
+ account_t * basket;
+ entries_list entries;
+ path_list sources;
+ optional<path> price_db;
+ char * item_pool;
+ char * item_pool_end;
+
+ 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() : basket(NULL), item_pool(NULL), item_pool_end(NULL) {
+ TRACE_CTOR(journal_t, "");
+ master = new account_t(NULL, "");
+ master->journal = this;
+ }
+ ~journal_t();
+
+ 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_map::value_type(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/data/node.cc b/src/data/node.cc
new file mode 100644
index 00000000..0ca0a04c
--- /dev/null
+++ b/src/data/node.cc
@@ -0,0 +1,130 @@
+/*
+ * 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 "node.h"
+#include "document.h"
+
+namespace ledger {
+namespace xml {
+
+const char * node_t::name() const
+{
+ return *document().lookup_name(name_id());
+}
+
+value_t& node_t::set_attr(const string& _name, const char * value)
+{
+ nameid_t name_id = document().register_name(_name);
+ return set_attr(name_id, value);
+}
+
+value_t& node_t::set_attr(const string& _name, const value_t& value)
+{
+ nameid_t name_id = document().register_name(_name);
+ return set_attr(name_id, value);
+}
+
+optional<value_t&> node_t::get_attr(const string& _name)
+{
+ optional<nameid_t> name_id = document().lookup_name_id(_name);
+ if (name_id)
+ return get_attr(*name_id);
+ else
+ return none;
+}
+
+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 << "&gt;";
+ break;
+ case '&':
+ out << "&amp;";
+ break;
+ default:
+ out << *s;
+ break;
+ }
+ }
+}
+
+void node_t::print_attributes(std::ostream& out) const
+{
+ if (attributes) {
+#if 1
+ foreach (const attr_pair& attr, *attributes)
+ out << ' ' << *document().lookup_name(attr.first)
+ << "=\"" << attr.second << "\"";
+#else
+ foreach (const attr_pair& attr, attributes->get<0>())
+ out << ' ' << *document().lookup_name(attr.first)
+ << "=\"" << attr.second << "\"";
+#endif
+ }
+
+ IF_VERIFY()
+ out << " type=\"parent_node_t\"";
+}
+
+void parent_node_t::print(std::ostream& out) const
+{
+ out << '<' << name();
+ print_attributes(out);
+ out << '>';
+
+ foreach (node_t * child, *this)
+ child->print(out);
+
+ out << "</" << name() << '>';
+}
+
+void terminal_node_t::print(std::ostream& out) const
+{
+ if (data.empty()) {
+ out << '<' << name();
+ print_attributes(out);
+ out << " />";
+ } else {
+ out << '<' << name();
+ print_attributes(out);
+ out << '>';
+ output_xml_string(out, text());
+ out << "</" << name() << '>';
+ }
+}
+
+} // namespace xml
+} // namespace ledger
diff --git a/src/data/node.h b/src/data/node.h
new file mode 100644
index 00000000..b0324ca0
--- /dev/null
+++ b/src/data/node.h
@@ -0,0 +1,352 @@
+/*
+ * 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 _NODE_H
+#define _NODE_H
+
+#include "value.h"
+
+namespace ledger {
+namespace xml {
+
+#define XML_NODE_IS_PARENT 0x1
+
+DECLARE_EXCEPTION(conversion_error);
+
+class document_t;
+class parent_node_t;
+class terminal_node_t;
+
+class node_t : public supports_flags<>, public noncopyable
+{
+public:
+ typedef uint_fast16_t nameid_t;
+
+ // This has to be public so that multi_index_container can reference
+ // it in parent_node_t.
+ nameid_t name_id_;
+
+protected:
+ document_t& document_;
+ optional<parent_node_t&> parent_;
+
+#if 1
+ typedef std::map<const nameid_t, value_t> attributes_t;
+ typedef std::pair<const nameid_t, value_t> attr_pair;
+#else
+ typedef std::pair<nameid_t, value_t> attr_pair;
+
+ typedef multi_index_container<
+ attr_pair,
+ multi_index::indexed_by<
+ multi_index::sequenced<>,
+ multi_index::hashed_unique<
+ multi_index::member<attr_pair, nameid_t, &attr_pair::first> >
+ >
+ > attributes_t;
+
+ typedef attributes_t::nth_index<0>::type attributes_by_order;
+ typedef attributes_t::nth_index<1>::type attributes_hashed;
+#endif
+
+ optional<attributes_t> attributes;
+
+public:
+ bool compiled; // so that compile_node() can access it
+
+ node_t(nameid_t _name_id, document_t& _document,
+ const optional<parent_node_t&>& _parent = none, flags_t _flags = 0)
+ : supports_flags<>(_flags), name_id_(_name_id),
+ document_(_document), parent_(_parent) {
+ TRACE_CTOR(node_t, "document_t&, parent_node_t&, nameid_t, flags_t");
+ }
+
+ virtual ~node_t() {
+ TRACE_DTOR(node_t);
+ }
+
+ void extract();
+
+ bool is_compiled() const {
+ return compiled;
+ }
+
+ bool is_parent_node() const {
+ return has_flags(XML_NODE_IS_PARENT);
+ }
+ parent_node_t& as_parent_node() {
+ if (! is_parent_node())
+ throw_(std::logic_error, "Request to cast leaf node to a parent node");
+ return downcast<parent_node_t>(*this);
+ }
+ const parent_node_t& as_parent_node() const {
+ return const_cast<node_t *>(this)->as_parent_node();
+ }
+
+ bool is_terminal_node() const {
+ return ! has_flags(XML_NODE_IS_PARENT);
+ }
+ terminal_node_t& as_terminal_node() {
+ if (! is_terminal_node())
+ throw_(std::logic_error, "Request to cast parent node to a leaf node");
+ return downcast<terminal_node_t>(*this);
+ }
+ const terminal_node_t& as_terminal_node() const {
+ return const_cast<node_t *>(this)->as_terminal_node();
+ }
+
+ virtual value_t to_value() const = 0;
+ virtual void print(std::ostream& out) const = 0;
+ virtual void print_attributes(std::ostream& out) const;
+
+ const char * name() const;
+ nameid_t name_id() const {
+ return name_id_;
+ }
+
+ document_t& document() const {
+ return document_;
+ }
+ optional<parent_node_t&> parent() const {
+ return parent_;
+ }
+
+ value_t& set_attr(const string& _name, const char * value);
+ value_t& set_attr(const string& _name, const value_t& value);
+
+ value_t& set_attr(const nameid_t _name_id, const char * value) {
+ return set_attr(_name_id, string_value(value));
+ }
+ value_t& set_attr(const nameid_t _name_id, const value_t& value) {
+ if (! attributes)
+ attributes = attributes_t();
+
+ attributes_t::iterator i = attributes->find(_name_id);
+ if (i == attributes->end()) {
+ std::pair<attributes_t::iterator, bool> result =
+ attributes->insert(attr_pair(_name_id, value));
+ assert(result.second);
+ return (*result.first).second;
+ } else {
+ i->second = value;
+ return i->second;
+ }
+ }
+
+ optional<value_t&> get_attr(const string& _name);
+ optional<value_t&> get_attr(const nameid_t _name_id) {
+ if (attributes) {
+#if 1
+ attributes_t::iterator i = attributes->find(_name_id);
+ if (i != attributes->end())
+ return (*i).second;
+#else
+ typedef attributes_t::nth_index<1>::type attributes_by_name;
+
+ const attributes_by_name& name_index = attributes->get<1>();
+ attributes_by_name::iterator i = name_index.find(_name_id);
+ if (i != name_index.end())
+ return (*i).second;
+#endif
+ }
+ return none;
+ }
+
+ optional<const value_t&> get_attr(const string& _name) const {
+ if (optional<value_t&> value =
+ const_cast<node_t *>(this)->get_attr(_name))
+ return *value;
+ else
+ return none;
+ }
+ optional<const value_t&> get_attr(const nameid_t _name_id) const {
+ if (optional<value_t&> value =
+ const_cast<node_t *>(this)->get_attr(_name_id))
+ return *value;
+ else
+ return none;
+ }
+};
+
+class parent_node_t : public node_t
+{
+ typedef multi_index_container<
+ node_t *,
+ multi_index::indexed_by<
+ multi_index::sequenced<>,
+ multi_index::hashed_non_unique<
+ multi_index::member<node_t, nameid_t, &node_t::name_id_> >,
+ multi_index::hashed_unique<multi_index::identity<node_t *> >
+ >
+ > children_t;
+
+ children_t children;
+
+public:
+ typedef children_t::nth_index<0>::type children_by_order;
+ typedef children_t::nth_index<1>::type children_by_nameid;
+ typedef children_t::nth_index<2>::type children_by_ptr;
+
+ parent_node_t(nameid_t _name_id, document_t& _document,
+ const optional<parent_node_t&>& _parent = none)
+ : node_t(_name_id, _document, _parent, XML_NODE_IS_PARENT) {
+ TRACE_CTOR(parent_node_t, "document_t *, parent_node_t *");
+ }
+ virtual ~parent_node_t() {
+ TRACE_DTOR(parent_node_t);
+ clear_children();
+ }
+
+ template <typename T>
+ T * create_child(nameid_t _name_id) {
+ T * child = new T(_name_id, document(), *this);
+ children.push_back(child);
+ return child;
+ }
+
+ void remove_child(node_t * child) {
+ children_by_ptr& ptr_index = children.get<2>();
+ children_by_ptr::iterator i = ptr_index.find(child);
+ if (i == ptr_index.end())
+ throw_(std::logic_error, "Request to delete node which is not a child");
+ ptr_index.erase(i);
+ }
+
+ void delete_child(node_t * child) {
+ remove_child(child);
+ checked_delete(child);
+ }
+
+ struct match_nameid {
+ nameid_t nameid;
+ match_nameid(nameid_t _nameid) : nameid(_nameid) {}
+ bool operator()(const node_t * node) const {
+ return node->name_id() == nameid;
+ }
+ };
+
+ typedef children_by_order::iterator iterator;
+ typedef children_by_order::iterator const_iterator;
+
+ children_by_order::iterator begin() {
+ return children.get<0>().begin();
+ }
+ children_by_order::const_iterator begin() const {
+ return children.get<0>().begin();
+ }
+ children_by_order::iterator end() {
+ return children.get<0>().end();
+ }
+ children_by_order::const_iterator end() const {
+ return children.get<0>().end();
+ }
+
+ std::size_t size() const {
+ return children.get<0>().size();
+ }
+
+ children_by_nameid::iterator begin(nameid_t _name_id) {
+ return std::find_if(children.get<1>().begin(),
+ children.get<1>().end(), match_nameid(_name_id));
+ }
+ children_by_nameid::const_iterator begin(nameid_t _name_id) const {
+ return std::find_if(children.get<1>().begin(),
+ children.get<1>().end(), match_nameid(_name_id));
+ }
+ children_by_nameid::iterator end(nameid_t) {
+ return children.get<1>().end();
+ }
+ children_by_nameid::const_iterator end(nameid_t) const {
+ return children.get<1>().end();
+ }
+
+ void clear_children() {
+ typedef children_t::nth_index<0>::type children_by_index;
+
+ children_by_index& child_index = children.get<0>();
+ for (children_by_index::iterator i = child_index.begin();
+ i != child_index.end();
+ i++)
+ checked_delete(*i);
+
+ children.clear();
+ }
+
+ virtual value_t to_value() const {
+ throw_(std::logic_error, "Cannot convert parent node to a value");
+ return NULL_VALUE;
+ }
+
+ void print(std::ostream& out) const;
+};
+
+inline void node_t::extract()
+{
+ if (parent_)
+ parent_->remove_child(this);
+}
+
+class terminal_node_t : public node_t
+{
+ string data;
+
+public:
+ terminal_node_t(nameid_t _name_id, document_t& _document,
+ const optional<parent_node_t&>& _parent = none)
+ : node_t(_name_id, _document, _parent)
+ {
+ TRACE_CTOR(terminal_node_t, "document_t *, parent_node_t *");
+ }
+ virtual ~terminal_node_t() {
+ TRACE_DTOR(terminal_node_t);
+ }
+
+ const char * text() const {
+ return data.c_str();
+ }
+ void set_text(const string& _data) {
+ data = _data;
+ }
+ void set_text(const char * _data) {
+ data = _data;
+ }
+
+ virtual value_t to_value() const {
+ return value_t(text(), true);
+ }
+
+ void print(std::ostream& out) const;
+};
+
+} // namespace xml
+} // namespace ledger
+
+#endif // _NODE_H
diff --git a/src/data/parser.h b/src/data/parser.h
new file mode 100644
index 00000000..ecc73a6f
--- /dev/null
+++ b/src/data/parser.h
@@ -0,0 +1,138 @@
+/*
+ * 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 _PARSER_H
+#define _PARSER_H
+
+#include "utils.h"
+#include "builder.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 std::size_t parse(std::istream& in,
+ const path& pathname,
+ xml::builder_t& builder) = 0;
+};
+
+DECLARE_EXCEPTION(parse_error);
+
+/************************************************************************
+ *
+ * General utility parsing functions
+ */
+
+inline char * skip_ws(char * ptr) {
+ while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n')
+ ptr++;
+ return ptr;
+}
+
+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;
+}
+
+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/data/textual.cc b/src/data/textual.cc
new file mode 100644
index 00000000..8fdd4db1
--- /dev/null
+++ b/src/data/textual.cc
@@ -0,0 +1,480 @@
+/*
+ * 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 "textual.h"
+
+namespace ledger {
+
+using namespace xml;
+
+#define MAX_LINE 1024
+
+typedef builder_t::position_t position_t;
+
+void parse_transaction(builder_t& builder,
+ char * line,
+ position_t& end_of_line)
+{
+ // 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';
+ }
+
+ // Setup the details for this node
+
+ if (state) {
+ switch (*state) {
+ case '*':
+ builder.push_attr(CLEARED_ATTR, "yes");
+ break;
+ case '!':
+ builder.push_attr(PENDING_ATTR, "yes");
+ break;
+ }
+ }
+
+ builder.begin_node(TRANSACTION_NODE);
+
+ // Parse the account name
+
+ char * b = &account_path[0];
+ char * e = &account_path[std::strlen(account_path) - 1];
+ if ((*b == '[' && *e == ']') ||
+ (*b == '(' && *e == ')')) {
+ builder.push_attr(VIRTUAL_ATTR, "yes");
+ if (*b == '[')
+ builder.push_attr(BALANCE_ATTR, "yes");
+ *account_path++ = '\0';
+ *e = '\0';
+ }
+
+ builder.begin_node(ACCOUNT_PATH_NODE, true);
+ builder.append_text(account_path);
+ builder.end_node(ACCOUNT_PATH_NODE);
+
+ // Parse the optional amount
+
+ if (amount) {
+ builder.begin_node(AMOUNT_EXPR_NODE, true);
+ builder.append_text(amount);
+ builder.end_node(AMOUNT_EXPR_NODE);
+ }
+
+ // Parse the optional note
+
+ if (note) {
+ builder.begin_node(NOTE_NODE, true);
+ builder.append_text(note);
+ builder.end_node(NOTE_NODE);
+ }
+
+ builder.end_node(TRANSACTION_NODE, end_of_line);
+}
+
+bool parse_transactions(std::istream& in, builder_t& builder)
+{
+ TRACE_START(entry_xacts, 1, "Time spent parsing transactions:");
+
+ bool added = false;
+
+ while (! in.eof() && (in.peek() == ' ' || in.peek() == '\t')) {
+ static char line[MAX_LINE + 1];
+ line[0] = '\0';
+ in.getline(line, MAX_LINE);
+ if (in.eof() || line[0] == '\0')
+ break;
+
+ position_t end_of_line(builder.position());
+ end_of_line.offset += std::strlen(line) + 1;
+ end_of_line.linenum++;
+
+ char * p = skip_ws(line);
+ if (! *p || *p == '\r' || *p == '\n')
+ break;
+
+ parse_transaction(builder, line, end_of_line);
+ added = true;
+ }
+
+ TRACE_STOP(entry_xacts, 1);
+
+ return added;
+}
+
+void parse_entry(std::istream& in,
+ builder_t& builder,
+ char * line,
+ position_t& end_of_line)
+{
+ TRACE_START(entry_text, 1, "Time spent preparing entry text:");
+
+ // 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);
+
+ // Setup the details for this node
+
+ TRACE_START(entry_details, 1, "Time spent parsing entry details:");
+
+ builder.push_attr(DATE_ATTR, date);
+
+ if (date_eff)
+ builder.push_attr(EFF_DATE_ATTR, date_eff);
+
+ if (statep) {
+ switch (*statep) {
+ case '*':
+ builder.push_attr(CLEARED_ATTR, "yes");
+ break;
+ case '!':
+ builder.push_attr(PENDING_ATTR, "yes");
+ break;
+ }
+ }
+
+ if (code)
+ builder.push_attr(CODE_ATTR, code);
+
+ builder.begin_node(ENTRY_NODE);
+
+ builder.begin_node(PAYEE_NODE, true);
+ assert(payee);
+ builder.append_text(*payee != '\0' ? payee : "<Unspecified payee>");
+ builder.end_node(PAYEE_NODE, end_of_line);
+
+ TRACE_STOP(entry_details, 1);
+
+ // Parse all the transactions associated with this entry
+
+ if (! parse_transactions(in, builder))
+ throw_(parse_error, "Entry has no transactions");
+
+ builder.end_node(ENTRY_NODE);
+}
+
+bool textual_parser_t::test(std::istream& in) const
+{
+ char buf[5];
+
+ in.read(buf, 5);
+ if (std::strncmp(buf, "<?xml", 5) == 0)
+ throw_(parse_error, "Ledger file contains XML data, but format was not recognized");
+
+ in.clear();
+ in.seekg(0, std::ios::beg);
+ assert(in.good());
+ return true;
+}
+
+std::size_t textual_parser_t::parse(std::istream& in,
+ const path& pathname,
+ builder_t& builder)
+{
+ TRACE_START(parsing_total, 1, "Total time spent parsing text:");
+
+ INFO("Parsing file '" << pathname.string() << "'");
+
+ builder.begin_node(JOURNAL_NODE);
+
+ std::size_t count = 0;
+
+ while (in.good() && ! in.eof()) {
+ static char line[MAX_LINE + 1];
+ in.getline(line, MAX_LINE);
+ if (in.eof())
+ break;
+
+ position_t end_of_line(builder.position());
+ end_of_line.offset += std::strlen(line) + 1;
+ end_of_line.linenum++;
+
+ //PUSH_CONTEXT();
+
+ switch (line[0]) {
+ case '\0':
+ case '\r':
+ break;
+
+ case ' ':
+ case '\t': {
+ char * p = skip_ws(line);
+ if (*p && *p != '\r')
+ throw_(parse_error, "Line begins with whitespace");
+ break;
+ }
+
+ case 'i':
+ case 'I': {
+ string date(line, 2, 19);
+
+ char * p = skip_ws(line + 22);
+ char * n = next_element(p, true);
+
+ builder.push_attr(TIME_ATTR, date);
+ builder.push_attr(ACCOUNT_ATTR, p);
+ builder.begin_node(CHECKIN_NODE, true);
+ builder.append_text(n);
+ builder.end_node(CHECKIN_NODE, end_of_line);
+ break;
+ }
+
+ case 'o':
+ case 'O': {
+ string date(line, 2, 19);
+
+ char * p = skip_ws(line + 22);
+ char * n = next_element(p, true);
+
+ builder.push_attr(TIME_ATTR, date);
+ builder.push_attr(ACCOUNT_ATTR, p);
+ builder.begin_node(CHECKIN_NODE, true);
+ builder.append_text(n);
+ builder.end_node(CHECKIN_NODE, end_of_line);
+ break;
+ }
+
+ case 'D': { // specifies default commodity flags
+ builder.push_attr(TEMPLATE_ATTR, skip_ws(line + 1));
+ builder.push_node(COMMODITY_TEMPLATE_NODE, end_of_line);
+ break;
+ }
+
+ case 'A': // a default account for unbalanced xacts
+ builder.push_attr(NAME_ATTR, skip_ws(line + 1));
+ builder.push_node(DEFAULT_ACCOUNT_NODE, end_of_line);
+ break;
+
+ case 'C': // a set of conversions
+ if (char * p = std::strchr(line + 1, '=')) {
+ *p++ = '\0';
+ builder.push_attr(FROM_ATTR, skip_ws(line + 1));
+ builder.push_attr(TO_ATTR, p);
+ builder.push_node(COMMODITY_CONVERSION_NODE, end_of_line);
+ } else {
+ throw_(parse_error, "Conversion entry (code C) must follow the format X=Y");
+ }
+ 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)
+ throw_(parse_error, "Pricing entry (code P) is missing arguments");
+ 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)
+ throw_(parse_error, "Pricing entry (code P) is missing a symbol name");
+ } else {
+ symbol_and_price = time_field_ptr;
+ }
+
+ builder.push_attr(DATE_ATTR, date_field_ptr);
+ builder.push_attr(TIME_ATTR, time_field_ptr);
+
+ string symbol;
+ commodity_t::parse_symbol(symbol_and_price, symbol);
+
+ builder.push_attr(SYMBOL_ATTR, symbol);
+ builder.push_attr(PRICE_ATTR, skip_ws(symbol_and_price));
+ builder.push_node(PRICE_HISTORY_NODE, end_of_line);
+ break;
+ }
+
+ case 'N': { // don't download prices
+ char * p = skip_ws(line + 1);
+
+ string symbol;
+ commodity_t::parse_symbol(p, symbol);
+
+ builder.push_attr(SYMBOL_ATTR, symbol);
+ builder.push_node(COMMODITY_NOMARKET_NODE, end_of_line);
+ break;
+ }
+
+ case 'Y': // set current year
+ builder.push_attr(YEAR_ATTR, skip_ws(line + 1));
+ builder.push_node(CURRENT_YEAR_NODE, end_of_line);
+ break;
+
+ case 'h':
+ case 'b':
+ case ';': // comment
+ // jww (2007-05-12): Read in the comment and save it
+ break;
+
+ case '@':
+ case '!': { // directive
+ char * p = next_element(line);
+ string word(line + 1);
+
+ builder.push_attr(NAME_ATTR, word);
+ builder.push_attr(ARG_ATTR, p);
+ builder.push_node(DIRECTIVE_NODE, end_of_line);
+ break;
+ }
+
+ case '-': // option setting
+ throw_(parse_error, "Option settings are not allowed in journal files");
+
+ case '=': { // automated entry
+ builder.begin_node(AUTO_ENTRY_NODE);
+ builder.begin_node(RULE_NODE, true);
+ builder.append_text(skip_ws(line + 1));
+ builder.end_node(RULE_NODE);
+
+ builder.set_position(end_of_line);
+
+ if (! parse_transactions(in, builder))
+ throw_(parse_error, "Automated entry has no transactions");
+
+ builder.end_node(AUTO_ENTRY_NODE);
+ break;
+ }
+
+ case '~': // period entry
+ builder.begin_node(PERIOD_ENTRY_NODE);
+ builder.begin_node(PERIOD_NODE, true);
+ builder.append_text(skip_ws(line + 1));
+ builder.end_node(PERIOD_NODE);
+
+ builder.set_position(end_of_line);
+
+ if (! parse_transactions(in, builder))
+ throw_(parse_error, "Repeating entry has no transactions");
+
+ builder.end_node(PERIOD_ENTRY_NODE);
+ break;
+
+ default:
+ TRACE_START(entries, 1, "Time spent handling entries:");
+ parse_entry(in, builder, line, end_of_line);
+ count++;
+ TRACE_STOP(entries, 1);
+ break;
+ }
+
+ //POP_CONTEXT(builder_context(builder));
+ }
+
+ builder.end_node(JOURNAL_NODE);
+
+ TRACE_STOP(parsing_total, 1);
+
+ return count;
+}
+
+} // namespace ledger
diff --git a/src/data/textual.h b/src/data/textual.h
new file mode 100644
index 00000000..f4d81f19
--- /dev/null
+++ b/src/data/textual.h
@@ -0,0 +1,51 @@
+/*
+ * 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 _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 std::size_t parse(std::istream& in,
+ const path& pathname,
+ xml::builder_t& builder);
+};
+
+} // namespace ledger
+
+#endif // _TEXTUAL_H