From 7380da43ab403dacb41d2010093d11942bb7cec1 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Mon, 21 May 2007 20:42:05 +0000 Subject: Many changes. --- src/data/builder.h | 256 +++++++++++++++++++ src/data/compile.cc | 244 ++++++++++++++++++ src/data/compile.h | 206 +++++++++++++++ src/data/document.cc | 180 +++++++++++++ src/data/document.h | 152 +++++++++++ src/data/jbuilder.cc | 67 +++++ src/data/jbuilder.h | 81 ++++++ src/data/journal.cc | 697 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/data/journal.h | 439 ++++++++++++++++++++++++++++++++ src/data/node.cc | 130 ++++++++++ src/data/node.h | 352 ++++++++++++++++++++++++++ src/data/parser.h | 138 ++++++++++ src/data/textual.cc | 480 +++++++++++++++++++++++++++++++++++ src/data/textual.h | 51 ++++ 14 files changed, 3473 insertions(+) create mode 100644 src/data/builder.h create mode 100644 src/data/compile.cc create mode 100644 src/data/compile.h create mode 100644 src/data/document.cc create mode 100644 src/data/document.h create mode 100644 src/data/jbuilder.cc create mode 100644 src/data/jbuilder.h create mode 100644 src/data/journal.cc create mode 100644 src/data/journal.h create mode 100644 src/data/node.cc create mode 100644 src/data/node.h create mode 100644 src/data/parser.h create mode 100644 src/data/textual.cc create mode 100644 src/data/textual.h (limited to 'src/data') 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& end_pos = none) = 0; + virtual void push_node(const node_t::nameid_t name_id, + const optional& 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& end_pos = none) = 0; + virtual node_t * end_node(const node_t::nameid_t name_id, + const optional& 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 > 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(name_id); + else + current = current->as_parent_node().create_child(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& 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& 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(current)->set_text(text); + } + + virtual node_t * end_node(const string& name, + const optional& end_pos = none) { + return current = &*current->parent(); + } + virtual node_t * end_node(const node_t::nameid_t name_id, + const optional& 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 > attrs_list; + + attrs_list current_attrs; + std::ostream& outs; + +public: + xml_writer_t(std::ostream& _outs) : outs(_outs) { + outs << ""; + 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& 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& 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& end_pos = none) { + outs << "'; + return NULL; + } + virtual node_t * end_node(const node_t::nameid_t name_id, + const optional& 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(node).compile(scope); + break; + case ENTRY_NODE: + downcast(node).compile(scope); + break; + case TRANSACTION_NODE: + downcast(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(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(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; + + journal_node_t(nameid_t _name_id, + document_t& _document, + const optional& _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; + + entry_node_t(nameid_t _name_id, + document_t& _document, + const optional& _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; + + transaction_node_t(nameid_t _name_id, + document_t& _document, + const optional& _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 +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 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 document_t::lookup_name_id(const string& name) const +{ + if (optional 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 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 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 << "\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 name_pair; + + typedef multi_index_container< + name_pair, + multi_index::indexed_by< + multi_index::random_access<>, + multi_index::hashed_unique< + multi_index::member > + > + > names_t; + + optional 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 lookup_name_id(const string& name) const; + static optional lookup_builtin_id(const string& name); + optional 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 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(name_id); + break; + case ENTRY_NODE: + current = current->as_parent_node().create_child(name_id); + break; + case TRANSACTION_NODE: + current = current->as_parent_node().create_child(name_id); + break; + + default: + if (terminal) + current = current->as_parent_node().create_child(name_id); + else + current = current->as_parent_node().create_child(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 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 + * 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 + ((*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(this); + + if ((*x)->amount.commodity() && + ! (*x)->amount.commodity().annotated) + (*x)->amount.annotate_commodity + (annotation_t(per_unit_cost.abs(), + entry ? entry->actual_date() : optional(), + entry ? entry->code : optional())); + + (*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 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(&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(&entry_base)) { + out << "= " << entry->predicate->expr << '\n'; + print_format = prefix + " %-34A %12o\n"; + } + else if (const period_entry_t * entry = + dynamic_cast(&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(entry_base.transactions), + formatter); + formatter.flush(); + + clear_transaction_xdata cleaner; + walk_transactions(const_cast(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 _date; + optional _date_eff; + amount_t amount; + optional cost; + optional 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 _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 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 _date_eff; + optional 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 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 accounts_map; + +class account_t +{ + public: + typedef unsigned long ident_t; + + journal_t * journal; + account_t * parent; + string name; + optional 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 _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 +void add_hook(std::list& list, T obj, const bool prepend = false) { + if (prepend) + list.push_front(obj); + else + list.push_back(obj); +} + +template +void remove_hook(std::list& list, T obj) { + list.remove(obj); +} + +template +bool run_hooks(std::list& list, Data& item, bool post) { + for (typename std::list::const_iterator i = list.begin(); + i != list.end(); + i++) + if (! (*(*i))(item, post)) + return false; + return true; +} + + +typedef std::list entries_list; +typedef std::list auto_entries_list; +typedef std::list period_entries_list; +typedef std::list path_list; +typedef std::list strings_list; + +class journal_t +{ + public: + account_t * master; + account_t * basket; + entries_list entries; + path_list sources; + optional 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_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_finalize_hooks, finalizer); + } + void remove_entry_finalizer(entry_finalizer_t * finalizer) { + remove_hook(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 node_t::get_attr(const string& _name) +{ + optional 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 << "<"; + break; + case '>': + out << ">"; + break; + case '&': + out << "&"; + 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 << "'; +} + +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 << "'; + } +} + +} // 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_; + +#if 1 + typedef std::map attributes_t; + typedef std::pair attr_pair; +#else + typedef std::pair attr_pair; + + typedef multi_index_container< + attr_pair, + multi_index::indexed_by< + multi_index::sequenced<>, + multi_index::hashed_unique< + multi_index::member > + > + > attributes_t; + + typedef attributes_t::nth_index<0>::type attributes_by_order; + typedef attributes_t::nth_index<1>::type attributes_hashed; +#endif + + optional attributes; + +public: + bool compiled; // so that compile_node() can access it + + node_t(nameid_t _name_id, document_t& _document, + const optional& _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(*this); + } + const parent_node_t& as_parent_node() const { + return const_cast(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(*this); + } + const terminal_node_t& as_terminal_node() const { + return const_cast(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() 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 result = + attributes->insert(attr_pair(_name_id, value)); + assert(result.second); + return (*result.first).second; + } else { + i->second = value; + return i->second; + } + } + + optional get_attr(const string& _name); + optional 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 get_attr(const string& _name) const { + if (optional value = + const_cast(this)->get_attr(_name)) + return *value; + else + return none; + } + optional get_attr(const nameid_t _name_id) const { + if (optional value = + const_cast(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 >, + multi_index::hashed_unique > + > + > 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 = 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 + 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 = 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 : ""); + 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, "