From c3535d06c89732a0ba4c13274702b0f48198ae79 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sun, 8 Nov 2009 23:40:42 -0500 Subject: Redesigned the expr_t, predicate_t, query_t classes --- tools/Makefile.am | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'tools/Makefile.am') diff --git a/tools/Makefile.am b/tools/Makefile.am index 5804008e..96776abf 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -42,7 +42,7 @@ libledger_math_la_LDFLAGS = -release $(VERSION).0 libledger_expr_la_SOURCES = \ src/option.cc \ src/format.cc \ - src/predicate.cc \ + src/query.cc \ src/scope.cc \ src/interactive.cc \ src/expr.cc \ @@ -107,10 +107,12 @@ pkginclude_HEADERS = \ src/token.h \ src/parser.h \ src/op.h \ + src/exprbase.h \ src/expr.h \ src/scope.h \ src/interactive.h \ src/predicate.h \ + src/query.h \ src/format.h \ src/option.h \ \ -- cgit v1.2.3 From 7411c74d6d5bea42cb9fa5b6b0ed90480c954a03 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Mon, 9 Nov 2009 01:36:26 -0500 Subject: Redesigned the draft_t class --- src/derive.cc | 559 ------------------------------------------------------ src/derive.h | 62 ------ src/draft.cc | 525 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/draft.h | 113 +++++++++++ src/format.h | 3 +- src/report.cc | 2 +- src/stats.cc | 2 +- tools/Makefile.am | 24 +-- 8 files changed, 653 insertions(+), 637 deletions(-) delete mode 100644 src/derive.cc delete mode 100644 src/derive.h create mode 100644 src/draft.cc create mode 100644 src/draft.h (limited to 'tools/Makefile.am') diff --git a/src/derive.cc b/src/derive.cc deleted file mode 100644 index 6f489003..00000000 --- a/src/derive.cc +++ /dev/null @@ -1,559 +0,0 @@ -/* - * Copyright (c) 2003-2009, 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 - -#include "derive.h" -#include "xact.h" -#include "post.h" -#include "account.h" -#include "journal.h" -#include "session.h" -#include "report.h" -#include "output.h" - -namespace ledger { - -namespace { - struct xact_template_t - { - optional date; - optional code; - optional note; - mask_t payee_mask; - - struct post_template_t { - bool from; - optional account_mask; - optional amount; - optional cost_operator; - optional cost; - - post_template_t() : from(false) {} - }; - - std::list posts; - - xact_template_t() {} - - void dump(std::ostream& out) const - { - if (date) - out << _("Date: ") << *date << std::endl; - else - out << _("Date: ") << std::endl; - - if (code) - out << _("Code: ") << *code << std::endl; - if (note) - out << _("Note: ") << *note << std::endl; - - if (payee_mask.empty()) - out << _("Payee mask: INVALID (template expression will cause an error)") - << std::endl; - else - out << _("Payee mask: ") << payee_mask << std::endl; - - if (posts.empty()) { - out << std::endl - << _("") - << std::endl; - } else { - bool has_only_from = true; - bool has_only_to = true; - - foreach (const post_template_t& post, posts) { - if (post.from) - has_only_to = false; - else - has_only_from = false; - } - - foreach (const post_template_t& post, posts) { - straccstream accum; - out << std::endl - << ACCUM(accum << _("[Posting \"%1\"]") - << (post.from ? _("from") : _("to"))) - << std::endl; - - if (post.account_mask) - out << _(" Account mask: ") << *post.account_mask << std::endl; - else if (post.from) - out << _(" Account mask: ") << std::endl; - else - out << _(" Account mask: ") << std::endl; - - if (post.amount) - out << _(" Amount: ") << *post.amount << std::endl; - - if (post.cost) - out << _(" Cost: ") << *post.cost_operator - << " " << *post.cost << std::endl; - } - } - } - }; - - xact_template_t - args_to_xact_template(value_t::sequence_t::const_iterator begin, - value_t::sequence_t::const_iterator end) - { - regex date_mask(_("([0-9]+(?:[-/.][0-9]+)?(?:[-/.][0-9]+))?")); - smatch what; - - xact_template_t tmpl; - bool check_for_date = true; - - optional weekday; - xact_template_t::post_template_t * post = NULL; - - for (; begin != end; begin++) { - if (check_for_date && - regex_match((*begin).to_string(), what, date_mask)) { - tmpl.date = parse_date(what[0]); - check_for_date = false; - } - else if (check_for_date && - bool(weekday = string_to_day_of_week(what[0]))) { - short dow = static_cast(*weekday); - date_t date = CURRENT_DATE() - date_duration(1); - while (date.day_of_week() != dow) - date -= date_duration(1); - tmpl.date = date; - check_for_date = false; - } - else { - string arg = (*begin).to_string(); - - if (arg == "at") { - if (begin == end) - throw std::runtime_error(_("Invalid xact command arguments")); - tmpl.payee_mask = (*++begin).to_string(); - } - else if (arg == "to" || arg == "from") { - if (! post || post->account_mask) { - tmpl.posts.push_back(xact_template_t::post_template_t()); - post = &tmpl.posts.back(); - } - if (begin == end) - throw std::runtime_error(_("Invalid xact command arguments")); - post->account_mask = mask_t((*++begin).to_string()); - post->from = arg == "from"; - } - else if (arg == "on") { - if (begin == end) - throw std::runtime_error(_("Invalid xact command arguments")); - tmpl.date = parse_date((*++begin).to_string()); - check_for_date = false; - } - else if (arg == "code") { - if (begin == end) - throw std::runtime_error(_("Invalid xact command arguments")); - tmpl.code = (*++begin).to_string(); - } - else if (arg == "note") { - if (begin == end) - throw std::runtime_error(_("Invalid xact command arguments")); - tmpl.note = (*++begin).to_string(); - } - else if (arg == "rest") { - ; // just ignore this argument - } - else if (arg == "@" || arg == "@@") { - amount_t cost; - post->cost_operator = arg; - if (begin == end) - throw std::runtime_error(_("Invalid xact command arguments")); - arg = (*++begin).to_string(); - if (! cost.parse(arg, PARSE_SOFT_FAIL | PARSE_NO_MIGRATE)) - throw std::runtime_error(_("Invalid xact command arguments")); - post->cost = cost; - } - else { - // Without a preposition, it is either: - // - // A payee, if we have not seen one - // An account or an amount, if we have - // An account if an amount has just been seen - // An amount if an account has just been seen - - if (tmpl.payee_mask.empty()) { - tmpl.payee_mask = arg; - } - else { - amount_t amt; - optional account; - - if (! amt.parse(arg, PARSE_SOFT_FAIL | PARSE_NO_MIGRATE)) - account = mask_t(arg); - - if (! post || - (account && post->account_mask) || - (! account && post->amount)) { - tmpl.posts.push_back(xact_template_t::post_template_t()); - post = &tmpl.posts.back(); - } - - if (account) { - post->from = false; - post->account_mask = account; - } else { - post->amount = amt; - } - } - } - } - } - - if (! tmpl.posts.empty()) { - bool has_only_from = true; - bool has_only_to = true; - - // A single account at the end of the line is the "from" account - if (tmpl.posts.size() > 1 && - tmpl.posts.back().account_mask && ! tmpl.posts.back().amount) - tmpl.posts.back().from = true; - - foreach (xact_template_t::post_template_t& post, tmpl.posts) { - if (post.from) - has_only_to = false; - else - has_only_from = false; - } - - if (has_only_from) { - tmpl.posts.push_front(xact_template_t::post_template_t()); - } - else if (has_only_to) { - tmpl.posts.push_back(xact_template_t::post_template_t()); - tmpl.posts.back().from = true; - } - } - - return tmpl; - } - - xact_t * derive_xact_from_template(xact_template_t& tmpl, - report_t& report) - { - if (tmpl.payee_mask.empty()) - throw std::runtime_error(_("xact' command requires at least a payee")); - - xact_t * matching = NULL; - journal_t& journal(*report.session.journal.get()); - std::auto_ptr added(new xact_t); - - for (xacts_list::reverse_iterator j = journal.xacts.rbegin(); - j != journal.xacts.rend(); - j++) { - if (tmpl.payee_mask.match((*j)->payee)) { - matching = *j; - DEBUG("derive.xact", - "Found payee match: transaction on line " << (*j)->pos->beg_line); - break; - } - } - - if (! tmpl.date) { - added->_date = CURRENT_DATE(); - DEBUG("derive.xact", "Setting date to current date"); - } else { - added->_date = tmpl.date; - DEBUG("derive.xact", "Setting date to template date: " << *tmpl.date); - } - - added->set_state(item_t::UNCLEARED); - - if (matching) { - added->payee = matching->payee; - added->code = matching->code; - added->note = matching->note; - -#if defined(DEBUG_ON) - DEBUG("derive.xact", "Setting payee from match: " << added->payee); - if (added->code) - DEBUG("derive.xact", "Setting code from match: " << *added->code); - if (added->note) - DEBUG("derive.xact", "Setting note from match: " << *added->note); -#endif - } else { - added->payee = tmpl.payee_mask.str(); - DEBUG("derive.xact", "Setting payee from template: " << added->payee); - } - - if (tmpl.code) { - added->code = tmpl.code; - DEBUG("derive.xact", "Now setting code from template: " << *added->code); - } - if (tmpl.note) { - added->note = tmpl.note; - DEBUG("derive.xact", "Now setting note from template: " << *added->note); - } - - if (tmpl.posts.empty()) { - if (matching) { - DEBUG("derive.xact", "Template had no postings, copying from match"); - - foreach (post_t * post, matching->posts) { - added->add_post(new post_t(*post)); - added->posts.back()->set_state(item_t::UNCLEARED); - } - } else { - throw_(std::runtime_error, - _("No accounts, and no past transaction matching '%1'") - << tmpl.payee_mask); - } - } else { - DEBUG("derive.xact", "Template had postings"); - - bool any_post_has_amount = false; - foreach (xact_template_t::post_template_t& post, tmpl.posts) { - if (post.amount) { - DEBUG("derive.xact", " and at least one has an amount specified"); - any_post_has_amount = true; - break; - } - } - - foreach (xact_template_t::post_template_t& post, tmpl.posts) { - std::auto_ptr new_post; - - commodity_t * found_commodity = NULL; - - if (matching) { - if (post.account_mask) { - DEBUG("derive.xact", - "Looking for matching posting based on account mask"); - - foreach (post_t * x, matching->posts) { - if (post.account_mask->match(x->account->fullname())) { - new_post.reset(new post_t(*x)); - DEBUG("derive.xact", - "Founding posting from line " << x->pos->beg_line); - break; - } - } - } else { - if (post.from) { - for (posts_list::reverse_iterator j = matching->posts.rbegin(); - j != matching->posts.rend(); - j++) { - if ((*j)->must_balance()) { - new_post.reset(new post_t(**j)); - DEBUG("derive.xact", - "Copied last real posting from matching"); - break; - } - } - } else { - for (posts_list::iterator j = matching->posts.begin(); - j != matching->posts.end(); - j++) { - if ((*j)->must_balance()) { - new_post.reset(new post_t(**j)); - DEBUG("derive.xact", - "Copied first real posting from matching"); - break; - } - } - } - } - } - - if (! new_post.get()) { - new_post.reset(new post_t); - DEBUG("derive.xact", "New posting was NULL, creating a blank one"); - } - - if (! new_post->account) { - DEBUG("derive.xact", "New posting still needs an account"); - - if (post.account_mask) { - DEBUG("derive.xact", "The template has an account mask"); - - account_t * acct = NULL; - if (! acct) { - acct = journal.find_account_re(post.account_mask->str()); -#if defined(DEBUG_ON) - if (acct) - DEBUG("derive.xact", "Found account as a regular expression"); -#endif - } - if (! acct) { - acct = journal.find_account(post.account_mask->str()); -#if defined(DEBUG_ON) - if (acct) - DEBUG("derive.xact", "Found (or created) account by name"); -#endif - } - - // Find out the default commodity to use by looking at the last - // commodity used in that account - for (xacts_list::reverse_iterator j = journal.xacts.rbegin(); - j != journal.xacts.rend(); - j++) { - foreach (post_t * x, (*j)->posts) { - if (x->account == acct && ! x->amount.is_null()) { - new_post.reset(new post_t(*x)); - DEBUG("derive.xact", - "Found account in journal postings, setting new posting"); - break; - } - } - } - - new_post->account = acct; - DEBUG("derive.xact", - "Set new posting's account to: " << acct->fullname()); - } else { - if (post.from) { - new_post->account = journal.find_account(_("Liabilities:Unknown")); - DEBUG("derive.xact", - "Set new posting's account to: Liabilities:Unknown"); - } else { - new_post->account = journal.find_account(_("Expenses:Unknown")); - DEBUG("derive.xact", - "Set new posting's account to: Expenses:Unknown"); - } - } - } - - if (new_post.get() && ! new_post->amount.is_null()) { - found_commodity = &new_post->amount.commodity(); - - if (any_post_has_amount) { - new_post->amount = amount_t(); - DEBUG("derive.xact", "New posting has an amount, but we cleared it"); - } else { - any_post_has_amount = true; - DEBUG("derive.xact", "New posting has an amount, and we're using it"); - } - } - - if (post.amount) { - new_post->amount = *post.amount; - DEBUG("derive.xact", "Copied over posting amount"); - - if (post.from) { - new_post->amount.in_place_negate(); - DEBUG("derive.xact", "Negated new posting amount"); - } - } - - if (post.cost) { - if (post.cost->sign() < 0) - throw parse_error(_("A posting's cost may not be negative")); - - post.cost->in_place_unround(); - - if (*post.cost_operator == "@") { - // For the sole case where the cost might be uncommoditized, - // guarantee that the commodity of the cost after multiplication - // is the same as it was before. - commodity_t& cost_commodity(post.cost->commodity()); - *post.cost *= new_post->amount; - post.cost->set_commodity(cost_commodity); - } - - new_post->cost = *post.cost; - DEBUG("derive.xact", "Copied over posting cost"); - } - - if (found_commodity && - ! new_post->amount.is_null() && - ! new_post->amount.has_commodity()) { - new_post->amount.set_commodity(*found_commodity); - DEBUG("derive.xact", "Set posting amount commodity to: " - << new_post->amount.commodity()); - - new_post->amount = new_post->amount.rounded(); - DEBUG("derive.xact", - "Rounded posting amount to: " << new_post->amount); - } - - added->add_post(new_post.release()); - added->posts.back()->set_state(item_t::UNCLEARED); - - DEBUG("derive.xact", "Added new posting to derived entry"); - } - } - - if (! journal.xact_finalize_hooks.run_hooks(*added.get(), false) || - ! added->finalize() || - ! journal.xact_finalize_hooks.run_hooks(*added.get(), true)) { - throw_(std::runtime_error, - _("Failed to finalize derived transaction (check commodities)")); - } - - return added.release(); - } -} - -value_t template_command(call_scope_t& args) -{ - report_t& report(find_scope(args)); - std::ostream& out(report.output_stream); - - value_t::sequence_t::const_iterator begin = args.value().begin(); - value_t::sequence_t::const_iterator end = args.value().end(); - - out << _("--- Input arguments ---") << std::endl; - args.value().dump(out); - out << std::endl << std::endl; - - xact_template_t tmpl = args_to_xact_template(begin, end); - - out << _("--- Transaction template ---") << std::endl; - tmpl.dump(out); - - return true; -} - -value_t xact_command(call_scope_t& args) -{ - value_t::sequence_t::const_iterator begin = args.value().begin(); - value_t::sequence_t::const_iterator end = args.value().end(); - - report_t& report(find_scope(args)); - xact_template_t tmpl = args_to_xact_template(begin, end); - std::auto_ptr new_xact(derive_xact_from_template(tmpl, report)); - - // Only consider actual postings for the "xact" command - report.HANDLER(limit_).on(string("#xact"), "actual"); - - report.xact_report(post_handler_ptr - (new format_posts(report, - report.HANDLER(print_format_).str())), - *new_xact.get()); - return true; -} - -} // namespace ledger diff --git a/src/derive.h b/src/derive.h deleted file mode 100644 index 0fb071a3..00000000 --- a/src/derive.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2003-2009, 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. - */ - -/** - * @addtogroup derive - */ - -/** - * @file derive.h - * @author John Wiegley - * - * @ingroup report - */ -#ifndef _DERIVE_H -#define _DERIVE_H - -#include "value.h" - -namespace ledger { - -class call_scope_t; - -value_t xact_command(call_scope_t& args); -value_t template_command(call_scope_t& args); - -class xact_t; -class report_t; -xact_t * derive_new_xact(report_t& report, - value_t::sequence_t::const_iterator i, - value_t::sequence_t::const_iterator end); - -} // namespace ledger - -#endif // _DERIVE_H diff --git a/src/draft.cc b/src/draft.cc new file mode 100644 index 00000000..b4e23322 --- /dev/null +++ b/src/draft.cc @@ -0,0 +1,525 @@ +/* + * Copyright (c) 2003-2009, 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 + +#include "draft.h" +#include "xact.h" +#include "post.h" +#include "account.h" +#include "journal.h" +#include "session.h" +#include "report.h" +#include "output.h" + +namespace ledger { + +void draft_t::xact_template_t::dump(std::ostream& out) const +{ + if (date) + out << _("Date: ") << *date << std::endl; + else + out << _("Date: ") << std::endl; + + if (code) + out << _("Code: ") << *code << std::endl; + if (note) + out << _("Note: ") << *note << std::endl; + + if (payee_mask.empty()) + out << _("Payee mask: INVALID (template expression will cause an error)") + << std::endl; + else + out << _("Payee mask: ") << payee_mask << std::endl; + + if (posts.empty()) { + out << std::endl + << _("") + << std::endl; + } else { + bool has_only_from = true; + bool has_only_to = true; + + foreach (const post_template_t& post, posts) { + if (post.from) + has_only_to = false; + else + has_only_from = false; + } + + foreach (const post_template_t& post, posts) { + straccstream accum; + out << std::endl + << ACCUM(accum << _("[Posting \"%1\"]") + << (post.from ? _("from") : _("to"))) + << std::endl; + + if (post.account_mask) + out << _(" Account mask: ") << *post.account_mask << std::endl; + else if (post.from) + out << _(" Account mask: ") << std::endl; + else + out << _(" Account mask: ") << std::endl; + + if (post.amount) + out << _(" Amount: ") << *post.amount << std::endl; + + if (post.cost) + out << _(" Cost: ") << *post.cost_operator + << " " << *post.cost << std::endl; + } + } +} + +void draft_t::parse_args(const value_t& args) +{ + regex date_mask(_("([0-9]+(?:[-/.][0-9]+)?(?:[-/.][0-9]+))?")); + smatch what; + bool check_for_date = true; + + tmpl = xact_template_t(); + + optional weekday; + xact_template_t::post_template_t * post = NULL; + + value_t::sequence_t::const_iterator begin = args.begin(); + value_t::sequence_t::const_iterator end = args.end(); + + for (; begin != end; begin++) { + if (check_for_date && + regex_match((*begin).to_string(), what, date_mask)) { + tmpl->date = parse_date(what[0]); + check_for_date = false; + } + else if (check_for_date && + bool(weekday = string_to_day_of_week(what[0]))) { + short dow = static_cast(*weekday); + date_t date = CURRENT_DATE() - date_duration(1); + while (date.day_of_week() != dow) + date -= date_duration(1); + tmpl->date = date; + check_for_date = false; + } + else { + string arg = (*begin).to_string(); + + if (arg == "at") { + if (begin == end) + throw std::runtime_error(_("Invalid xact command arguments")); + tmpl->payee_mask = (*++begin).to_string(); + } + else if (arg == "to" || arg == "from") { + if (! post || post->account_mask) { + tmpl->posts.push_back(xact_template_t::post_template_t()); + post = &tmpl->posts.back(); + } + if (begin == end) + throw std::runtime_error(_("Invalid xact command arguments")); + post->account_mask = mask_t((*++begin).to_string()); + post->from = arg == "from"; + } + else if (arg == "on") { + if (begin == end) + throw std::runtime_error(_("Invalid xact command arguments")); + tmpl->date = parse_date((*++begin).to_string()); + check_for_date = false; + } + else if (arg == "code") { + if (begin == end) + throw std::runtime_error(_("Invalid xact command arguments")); + tmpl->code = (*++begin).to_string(); + } + else if (arg == "note") { + if (begin == end) + throw std::runtime_error(_("Invalid xact command arguments")); + tmpl->note = (*++begin).to_string(); + } + else if (arg == "rest") { + ; // just ignore this argument + } + else if (arg == "@" || arg == "@@") { + amount_t cost; + post->cost_operator = arg; + if (begin == end) + throw std::runtime_error(_("Invalid xact command arguments")); + arg = (*++begin).to_string(); + if (! cost.parse(arg, PARSE_SOFT_FAIL | PARSE_NO_MIGRATE)) + throw std::runtime_error(_("Invalid xact command arguments")); + post->cost = cost; + } + else { + // Without a preposition, it is either: + // + // A payee, if we have not seen one + // An account or an amount, if we have + // An account if an amount has just been seen + // An amount if an account has just been seen + + if (tmpl->payee_mask.empty()) { + tmpl->payee_mask = arg; + } + else { + amount_t amt; + optional account; + + if (! amt.parse(arg, PARSE_SOFT_FAIL | PARSE_NO_MIGRATE)) + account = mask_t(arg); + + if (! post || + (account && post->account_mask) || + (! account && post->amount)) { + tmpl->posts.push_back(xact_template_t::post_template_t()); + post = &tmpl->posts.back(); + } + + if (account) { + post->from = false; + post->account_mask = account; + } else { + post->amount = amt; + } + } + } + } + } + + if (! tmpl->posts.empty()) { + bool has_only_from = true; + bool has_only_to = true; + + // A single account at the end of the line is the "from" account + if (tmpl->posts.size() > 1 && + tmpl->posts.back().account_mask && ! tmpl->posts.back().amount) + tmpl->posts.back().from = true; + + foreach (xact_template_t::post_template_t& post, tmpl->posts) { + if (post.from) + has_only_to = false; + else + has_only_from = false; + } + + if (has_only_from) { + tmpl->posts.push_front(xact_template_t::post_template_t()); + } + else if (has_only_to) { + tmpl->posts.push_back(xact_template_t::post_template_t()); + tmpl->posts.back().from = true; + } + } +} + +xact_t * draft_t::insert(journal_t& journal) +{ + if (tmpl->payee_mask.empty()) + throw std::runtime_error(_("xact' command requires at least a payee")); + + xact_t * matching = NULL; + + std::auto_ptr added(new xact_t); + + for (xacts_list::reverse_iterator j = journal.xacts.rbegin(); + j != journal.xacts.rend(); + j++) { + if (tmpl->payee_mask.match((*j)->payee)) { + matching = *j; + DEBUG("derive.xact", + "Found payee match: transaction on line " << (*j)->pos->beg_line); + break; + } + } + + if (! tmpl->date) { + added->_date = CURRENT_DATE(); + DEBUG("derive.xact", "Setting date to current date"); + } else { + added->_date = tmpl->date; + DEBUG("derive.xact", "Setting date to template date: " << *tmpl->date); + } + + added->set_state(item_t::UNCLEARED); + + if (matching) { + added->payee = matching->payee; + added->code = matching->code; + added->note = matching->note; + +#if defined(DEBUG_ON) + DEBUG("derive.xact", "Setting payee from match: " << added->payee); + if (added->code) + DEBUG("derive.xact", "Setting code from match: " << *added->code); + if (added->note) + DEBUG("derive.xact", "Setting note from match: " << *added->note); +#endif + } else { + added->payee = tmpl->payee_mask.str(); + DEBUG("derive.xact", "Setting payee from template: " << added->payee); + } + + if (tmpl->code) { + added->code = tmpl->code; + DEBUG("derive.xact", "Now setting code from template: " << *added->code); + } + if (tmpl->note) { + added->note = tmpl->note; + DEBUG("derive.xact", "Now setting note from template: " << *added->note); + } + + if (tmpl->posts.empty()) { + if (matching) { + DEBUG("derive.xact", "Template had no postings, copying from match"); + + foreach (post_t * post, matching->posts) { + added->add_post(new post_t(*post)); + added->posts.back()->set_state(item_t::UNCLEARED); + } + } else { + throw_(std::runtime_error, + _("No accounts, and no past transaction matching '%1'") + << tmpl->payee_mask); + } + } else { + DEBUG("derive.xact", "Template had postings"); + + bool any_post_has_amount = false; + foreach (xact_template_t::post_template_t& post, tmpl->posts) { + if (post.amount) { + DEBUG("derive.xact", " and at least one has an amount specified"); + any_post_has_amount = true; + break; + } + } + + foreach (xact_template_t::post_template_t& post, tmpl->posts) { + std::auto_ptr new_post; + + commodity_t * found_commodity = NULL; + + if (matching) { + if (post.account_mask) { + DEBUG("derive.xact", + "Looking for matching posting based on account mask"); + + foreach (post_t * x, matching->posts) { + if (post.account_mask->match(x->account->fullname())) { + new_post.reset(new post_t(*x)); + DEBUG("derive.xact", + "Founding posting from line " << x->pos->beg_line); + break; + } + } + } else { + if (post.from) { + for (posts_list::reverse_iterator j = matching->posts.rbegin(); + j != matching->posts.rend(); + j++) { + if ((*j)->must_balance()) { + new_post.reset(new post_t(**j)); + DEBUG("derive.xact", + "Copied last real posting from matching"); + break; + } + } + } else { + for (posts_list::iterator j = matching->posts.begin(); + j != matching->posts.end(); + j++) { + if ((*j)->must_balance()) { + new_post.reset(new post_t(**j)); + DEBUG("derive.xact", + "Copied first real posting from matching"); + break; + } + } + } + } + } + + if (! new_post.get()) { + new_post.reset(new post_t); + DEBUG("derive.xact", "New posting was NULL, creating a blank one"); + } + + if (! new_post->account) { + DEBUG("derive.xact", "New posting still needs an account"); + + if (post.account_mask) { + DEBUG("derive.xact", "The template has an account mask"); + + account_t * acct = NULL; + if (! acct) { + acct = journal.find_account_re(post.account_mask->str()); +#if defined(DEBUG_ON) + if (acct) + DEBUG("derive.xact", "Found account as a regular expression"); +#endif + } + if (! acct) { + acct = journal.find_account(post.account_mask->str()); +#if defined(DEBUG_ON) + if (acct) + DEBUG("derive.xact", "Found (or created) account by name"); +#endif + } + + // Find out the default commodity to use by looking at the last + // commodity used in that account + for (xacts_list::reverse_iterator j = journal.xacts.rbegin(); + j != journal.xacts.rend(); + j++) { + foreach (post_t * x, (*j)->posts) { + if (x->account == acct && ! x->amount.is_null()) { + new_post.reset(new post_t(*x)); + DEBUG("derive.xact", + "Found account in journal postings, setting new posting"); + break; + } + } + } + + new_post->account = acct; + DEBUG("derive.xact", + "Set new posting's account to: " << acct->fullname()); + } else { + if (post.from) { + new_post->account = journal.find_account(_("Liabilities:Unknown")); + DEBUG("derive.xact", + "Set new posting's account to: Liabilities:Unknown"); + } else { + new_post->account = journal.find_account(_("Expenses:Unknown")); + DEBUG("derive.xact", + "Set new posting's account to: Expenses:Unknown"); + } + } + } + + if (new_post.get() && ! new_post->amount.is_null()) { + found_commodity = &new_post->amount.commodity(); + + if (any_post_has_amount) { + new_post->amount = amount_t(); + DEBUG("derive.xact", "New posting has an amount, but we cleared it"); + } else { + any_post_has_amount = true; + DEBUG("derive.xact", "New posting has an amount, and we're using it"); + } + } + + if (post.amount) { + new_post->amount = *post.amount; + DEBUG("derive.xact", "Copied over posting amount"); + + if (post.from) { + new_post->amount.in_place_negate(); + DEBUG("derive.xact", "Negated new posting amount"); + } + } + + if (post.cost) { + if (post.cost->sign() < 0) + throw parse_error(_("A posting's cost may not be negative")); + + post.cost->in_place_unround(); + + if (*post.cost_operator == "@") { + // For the sole case where the cost might be uncommoditized, + // guarantee that the commodity of the cost after multiplication + // is the same as it was before. + commodity_t& cost_commodity(post.cost->commodity()); + *post.cost *= new_post->amount; + post.cost->set_commodity(cost_commodity); + } + + new_post->cost = *post.cost; + DEBUG("derive.xact", "Copied over posting cost"); + } + + if (found_commodity && + ! new_post->amount.is_null() && + ! new_post->amount.has_commodity()) { + new_post->amount.set_commodity(*found_commodity); + DEBUG("derive.xact", "Set posting amount commodity to: " + << new_post->amount.commodity()); + + new_post->amount = new_post->amount.rounded(); + DEBUG("derive.xact", + "Rounded posting amount to: " << new_post->amount); + } + + added->add_post(new_post.release()); + added->posts.back()->set_state(item_t::UNCLEARED); + + DEBUG("derive.xact", "Added new posting to derived entry"); + } + } + + if (! journal.add_xact(added.get())) + throw_(std::runtime_error, + _("Failed to finalize derived transaction (check commodities)")); + + return added.release(); +} + +value_t template_command(call_scope_t& args) +{ + report_t& report(find_scope(args)); + std::ostream& out(report.output_stream); + + out << _("--- Input arguments ---") << std::endl; + args.value().dump(out); + out << std::endl << std::endl; + + draft_t draft(args.value()); + + out << _("--- Transaction template ---") << std::endl; + draft.dump(out); + + return true; +} + +value_t xact_command(call_scope_t& args) +{ + report_t& report(find_scope(args)); + draft_t draft(args.value()); + + xact_t * new_xact = draft.insert(*report.session.journal.get()); + + // Only consider actual postings for the "xact" command + report.HANDLER(limit_).on(string("#xact"), "actual"); + + report.xact_report(post_handler_ptr + (new format_posts(report, + report.HANDLER(print_format_).str())), + *new_xact); + return true; +} + +} // namespace ledger diff --git a/src/draft.h b/src/draft.h new file mode 100644 index 00000000..faefa67b --- /dev/null +++ b/src/draft.h @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2003-2009, 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. + */ + +/** + * @addtogroup derive + */ + +/** + * @file derive.h + * @author John Wiegley + * + * @ingroup report + */ +#ifndef _DERIVE_H +#define _DERIVE_H + +#include "exprbase.h" +#include "value.h" + +namespace ledger { + +class journal_t; +class xact_t; + +class draft_t : public expr_base_t +{ + typedef expr_base_t base_type; + + struct xact_template_t + { + optional date; + optional code; + optional note; + mask_t payee_mask; + + struct post_template_t { + bool from; + optional account_mask; + optional amount; + optional cost_operator; + optional cost; + + post_template_t() : from(false) {} + }; + + std::list posts; + + xact_template_t() {} + + void dump(std::ostream& out) const; + }; + + optional tmpl; + +public: + draft_t(const value_t& args) : base_type() { + TRACE_CTOR(draft_t, "value_t"); + if (! args.empty()) + parse_args(args); + } + ~draft_t() { + TRACE_DTOR(draft_t); + } + + void parse_args(const value_t& args); + + virtual result_type real_calc(scope_t&) { + assert(0); + return true; + } + + xact_t * insert(journal_t& journal); + + virtual void dump(std::ostream& out) const { + if (tmpl) + tmpl->dump(out); + } +}; + +value_t xact_command(call_scope_t& args); +value_t template_command(call_scope_t& args); + +} // namespace ledger + +#endif // _DERIVE_H diff --git a/src/format.h b/src/format.h index 516b6d7e..b3ae464f 100644 --- a/src/format.h +++ b/src/format.h @@ -105,8 +105,7 @@ class format_t : public expr_base_t void dump(std::ostream& out) const; }; - string format_string; - scoped_ptr elements; + scoped_ptr elements; public: static enum elision_style_t { diff --git a/src/report.cc b/src/report.cc index c0b5d745..3da71616 100644 --- a/src/report.cc +++ b/src/report.cc @@ -42,7 +42,7 @@ #include "precmd.h" #include "stats.h" #include "generate.h" -#include "derive.h" +#include "draft.h" #include "emacs.h" namespace ledger { diff --git a/src/stats.cc b/src/stats.cc index e2db9d8b..b89a5949 100644 --- a/src/stats.cc +++ b/src/stats.cc @@ -31,7 +31,7 @@ #include -#include "derive.h" +#include "draft.h" #include "xact.h" #include "post.h" #include "account.h" diff --git a/tools/Makefile.am b/tools/Makefile.am index 96776abf..a41c47ce 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -1,4 +1,4 @@ -VERSION = 3.0 +VERSION = 3.0.0 ACLOCAL_AMFLAGS = -I m4 dist_man_MANS = doc/ledger.1 SUBDIRS = po intl @@ -25,9 +25,9 @@ libledger_util_la_SOURCES = \ lib/sha1.cpp libledger_util_la_CPPFLAGS = $(lib_cppflags) -libledger_util_la_LDFLAGS = -release $(VERSION).0 +libledger_util_la_LDFLAGS = -release $(VERSION) -libledger_math_la_SOURCES = \ +libledger_math_la_SOURCES = \ src/value.cc \ src/balance.cc \ src/quotes.cc \ @@ -37,9 +37,9 @@ libledger_math_la_SOURCES = \ src/amount.cc libledger_math_la_CPPFLAGS = $(lib_cppflags) -libledger_math_la_LDFLAGS = -release $(VERSION).0 +libledger_math_la_LDFLAGS = -release $(VERSION) -libledger_expr_la_SOURCES = \ +libledger_expr_la_SOURCES = \ src/option.cc \ src/format.cc \ src/query.cc \ @@ -51,9 +51,9 @@ libledger_expr_la_SOURCES = \ src/token.cc libledger_expr_la_CPPFLAGS = $(lib_cppflags) -libledger_expr_la_LDFLAGS = -release $(VERSION).0 +libledger_expr_la_LDFLAGS = -release $(VERSION) -libledger_data_la_SOURCES = \ +libledger_data_la_SOURCES = \ src/compare.cc \ src/iterators.cc \ src/timelog.cc \ @@ -65,13 +65,13 @@ libledger_data_la_SOURCES = \ src/post.cc \ src/item.cc -libledger_data_la_CPPFLAGS = $(lib_cppflags) -libledger_data_la_LDFLAGS = -release $(VERSION).0 +libledger_data_la_CPPFLAGS = $(lib_cppflags) +libledger_data_la_LDFLAGS = -release $(VERSION) libledger_report_la_SOURCES = \ src/stats.cc \ src/generate.cc \ - src/derive.cc \ + src/draft.cc \ src/emacs.cc \ src/output.cc \ src/precmd.cc \ @@ -82,7 +82,7 @@ libledger_report_la_SOURCES = \ src/session.cc libledger_report_la_CPPFLAGS = $(lib_cppflags) -libledger_report_la_LDFLAGS = -release $(VERSION).0 +libledger_report_la_LDFLAGS = -release $(VERSION) pkginclude_HEADERS = \ src/utils.h \ @@ -132,7 +132,7 @@ pkginclude_HEADERS = \ src/temps.h \ src/chain.h \ src/precmd.h \ - src/derive.h \ + src/draft.h \ src/generate.h \ src/stats.h \ src/output.h \ -- cgit v1.2.3 From 2c80227339538154ad0869e746f52db805325589 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Sun, 25 Oct 2009 05:13:21 -0400 Subject: Added basic foundation for XML reporting --- src/amount.cc | 13 +++++ src/amount.h | 2 + src/annotate.h | 23 +++++++++ src/balance.cc | 8 +++ src/balance.h | 2 + src/commodity.cc | 11 ++++ src/commodity.h | 2 + src/mask.h | 6 +++ src/report.cc | 1 + src/times.h | 12 +++++ src/utils.h | 34 +++++++++++++ src/value.cc | 54 ++++++++++++++++++++ src/value.h | 2 + src/xml.cc | 150 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/xml.h | 90 ++++++++++++++++++++++++++++++++ tools/Makefile.am | 2 + 16 files changed, 412 insertions(+) create mode 100644 src/xml.cc create mode 100644 src/xml.h (limited to 'tools/Makefile.am') diff --git a/src/amount.cc b/src/amount.cc index 8c78a86c..456ef8cf 100644 --- a/src/amount.cc +++ b/src/amount.cc @@ -1125,6 +1125,19 @@ bool amount_t::valid() const return true; } +void to_xml(std::ostream& out, const amount_t& amt) +{ + push_xml x(out, "amount"); + + if (amt.has_commodity()) + to_xml(out, amt.commodity()); + + { + push_xml y(out, "number"); + out << amt.number(); + } +} + #if defined(HAVE_BOOST_SERIALIZATION) template diff --git a/src/amount.h b/src/amount.h index add32b03..d8a19f91 100644 --- a/src/amount.h +++ b/src/amount.h @@ -745,6 +745,8 @@ inline std::istream& operator>>(std::istream& in, amount_t& amt) { return in; } +void to_xml(std::ostream& out, const amount_t& amt); + } // namespace ledger #endif // _AMOUNT_H diff --git a/src/annotate.h b/src/annotate.h index 9ca994c9..37810fb9 100644 --- a/src/annotate.h +++ b/src/annotate.h @@ -110,6 +110,29 @@ private: #endif // HAVE_BOOST_SERIALIZATION }; +inline void to_xml(std::ostream& out, const annotation_t& details) +{ + push_xml x(out, "annotation"); + + if (details.price) + { + push_xml y(out, "ann-price"); + to_xml(out, *details.price); + } + + if (details.date) + { + push_xml y(out, "ann-date"); + to_xml(out, *details.date); + } + + if (details.tag) + { + push_xml y(out, "ann-tag"); + out << y.guard(*details.tag); + } +} + struct keep_details_t { bool keep_price; diff --git a/src/balance.cc b/src/balance.cc index 1c096e01..59eb4d92 100644 --- a/src/balance.cc +++ b/src/balance.cc @@ -299,4 +299,12 @@ void balance_t::print(std::ostream& out, } } +void to_xml(std::ostream& out, const balance_t& bal) +{ + push_xml x(out, "balance"); + + foreach (const balance_t::amounts_map::value_type& pair, bal.amounts) + to_xml(out, pair.second); +} + } // namespace ledger diff --git a/src/balance.h b/src/balance.h index a4f922a8..81a7ff13 100644 --- a/src/balance.h +++ b/src/balance.h @@ -574,6 +574,8 @@ inline std::ostream& operator<<(std::ostream& out, const balance_t& bal) { return out; } +void to_xml(std::ostream& out, const balance_t& amt); + } // namespace ledger #endif // _BALANCE_H diff --git a/src/commodity.cc b/src/commodity.cc index 0d6c11c6..7669b3db 100644 --- a/src/commodity.cc +++ b/src/commodity.cc @@ -654,4 +654,15 @@ bool compare_amount_commodities::operator()(const amount_t * left, } } +void to_xml(std::ostream& out, const commodity_t& comm) +{ + push_xml x(out, "commodity"); + { + push_xml y(out, "symbol"); + out << y.guard(comm.symbol()); + } + if (comm.is_annotated()) + to_xml(out, as_annotated_commodity(comm).details); +} + } // namespace ledger diff --git a/src/commodity.h b/src/commodity.h index 0d31b2bd..ef9ef5c4 100644 --- a/src/commodity.h +++ b/src/commodity.h @@ -418,6 +418,8 @@ struct compare_amount_commodities { bool operator()(const amount_t * left, const amount_t * right) const; }; +void to_xml(std::ostream& out, const commodity_t& comm); + } // namespace ledger #endif // _COMMODITY_H diff --git a/src/mask.h b/src/mask.h index a7dea7cf..18f1893d 100644 --- a/src/mask.h +++ b/src/mask.h @@ -146,6 +146,12 @@ inline std::ostream& operator<<(std::ostream& out, const mask_t& mask) { return out; } +inline void to_xml(std::ostream& out, const mask_t& mask) +{ + push_xml x(out, "mask"); + out << x.guard(mask.expr.str()); +} + } // namespace ledger #endif // _MASK_H diff --git a/src/report.cc b/src/report.cc index 3da71616..1443dd91 100644 --- a/src/report.cc +++ b/src/report.cc @@ -43,6 +43,7 @@ #include "stats.h" #include "generate.h" #include "draft.h" +#include "xml.h" #include "emacs.h" namespace ledger { diff --git a/src/times.h b/src/times.h index 9387320e..c2c2530e 100644 --- a/src/times.h +++ b/src/times.h @@ -116,6 +116,18 @@ std::string format_date(const date_t& when, void set_date_format(const char * format); void set_input_date_format(const char * format); +inline void to_xml(std::ostream& out, const datetime_t& when) +{ + push_xml x(out, "datetime"); + out << format_datetime(when, FMT_WRITTEN); +} + +inline void to_xml(std::ostream& out, const date_t& when) +{ + push_xml x(out, "date"); + out << format_date(when, FMT_WRITTEN); +} + class date_interval_t : public equality_comparable { public: diff --git a/src/utils.h b/src/utils.h index 48435844..6bd67146 100644 --- a/src/utils.h +++ b/src/utils.h @@ -646,6 +646,40 @@ inline string to_hex(uint_least32_t * message_digest, const int len = 1) return buf.str(); } +class push_xml +{ + std::ostream& out; + string tag; +public: + push_xml(std::ostream& _out, const string& _tag) : out(_out), tag(_tag) { + out << '<' << tag << '>'; + } + ~push_xml() { + out << "'; + } + + string guard(const string& str) { + std::ostringstream buf; + foreach (const char& ch, str) { + switch (ch) { + case '<': + buf << "<"; + break; + case '>': + buf << ">"; + break; + case '&': + buf << "&"; + break; + default: + buf << ch; + break; + } + } + return buf.str(); + } +}; + extern const string version; } // namespace ledger diff --git a/src/value.cc b/src/value.cc index e2c9dc8b..2029b9c5 100644 --- a/src/value.cc +++ b/src/value.cc @@ -1746,4 +1746,58 @@ bool sort_value_is_less_than(const std::list& left_values, return false; } +void to_xml(std::ostream& out, const value_t& value) +{ + push_xml x(out, "value"); + + switch (value.type()) { + case value_t::VOID: + out << ""; + break; + case value_t::BOOLEAN: { + push_xml y(out, "boolean"); + out << (value.as_boolean() ? "true" : "false"); + break; + } + case value_t::INTEGER: { + push_xml y(out, "integer"); + out << value.as_long(); + break; + } + case value_t::DATETIME: + to_xml(out, value.as_datetime()); + break; + case value_t::DATE: + to_xml(out, value.as_date()); + break; + case value_t::STRING: { + push_xml y(out, "string"); + out << y.guard(value.as_string()); + break; + } + case value_t::MASK: + to_xml(out, value.as_mask()); + break; + + case value_t::SEQUENCE: { + push_xml y(out, "sequence"); + foreach (const value_t& member, value.as_sequence()) + to_xml(out, member); + break; + } + + case value_t::AMOUNT: + to_xml(out, value.as_amount()); + break; + case value_t::BALANCE: + to_xml(out, value.as_balance()); + break; + + case value_t::SCOPE: + default: + assert(false); + break; + } +} + } // namespace ledger diff --git a/src/value.h b/src/value.h index db45e593..94002bef 100644 --- a/src/value.h +++ b/src/value.h @@ -982,6 +982,8 @@ struct sort_value_t bool sort_value_is_less_than(const std::list& left_values, const std::list& right_values); +void to_xml(std::ostream& out, const value_t& value); + } // namespace ledger #endif // _VALUE_H diff --git a/src/xml.cc b/src/xml.cc new file mode 100644 index 00000000..02fa7137 --- /dev/null +++ b/src/xml.cc @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2003-2009, 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 + +#include "xml.h" +#include "xact.h" +#include "post.h" +#include "account.h" +#include "session.h" +#include "report.h" + +namespace ledger { + +namespace { + void xml_account(std::ostream& out, const account_t * acct) { + if ((acct->has_xdata() && + acct->xdata().has_flags(ACCOUNT_EXT_VISITED)) || + acct->children_with_flags(ACCOUNT_EXT_VISITED)) { + out << "(acct); + out << "\">\n"; + + out << "" << acct->name << "\n"; + value_t total = acct->amount(); + if (! total.is_null()) { + out << "\n"; + to_xml(out, total); + out << "\n"; + } + total = acct->total(); + if (! total.is_null()) { + out << "\n"; + to_xml(out, total); + out << "\n"; + } + out << "\n"; + } + + foreach (const accounts_map::value_type& pair, acct->accounts) + xml_account(out, pair.second); + } + + void xml_transaction(std::ostream& out, const xact_t * xact) { + out << "\n"; + out << "" << xact->payee << "\n"; + + foreach (const post_t * post, xact->posts) { + if (post->has_xdata() && + post->xdata().has_flags(POST_EXT_VISITED)) { + out << "\n"; + out << "(post->account); + out << "\">" << post->account->fullname() << "\n"; + + out << "\n"; + to_xml(out, post->amount); + out << "\n"; + + out << "\n"; + if (post->cost) + to_xml(out, *post->cost); + else + to_xml(out, post->amount); + out << "\n"; + + if (post->assigned_amount) { + out << "\n"; + to_xml(out, *post->assigned_amount); + out << "\n"; + } + + out << "\n"; + } + } + + out << "\n"; + } +} + +void format_xml::flush() +{ + std::ostream& out(report.output_stream); + + out << "\n\n\n"; + + out << "\n"; + foreach (const commodities_pair& pair, commodities) { + to_xml(out, *pair.second); + out << '\n'; + } + out << "\n"; + + out << "\n"; + xml_account(out, report.session.journal->master); + out << "\n"; + + out << "\n"; + foreach (const xact_t * xact, transactions) + xml_transaction(out, xact); + out << "\n"; + + out << "\n\n"; + out.flush(); +} + +void format_xml::operator()(post_t& post) +{ + assert(post.xdata().has_flags(POST_EXT_VISITED)); + + commodities.insert(commodities_pair(post.amount.commodity().symbol(), + &post.amount.commodity())); + + if (transactions_set.find(post.xact) == transactions_set.end()) + transactions.push_back(post.xact); +} + +} // namespace ledger diff --git a/src/xml.h b/src/xml.h new file mode 100644 index 00000000..30a7b1b1 --- /dev/null +++ b/src/xml.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2003-2009, 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. + */ + +/** + * @addtogroup report + */ + +/** + * @file xml.h + * @author John Wiegley + * + * @ingroup report + * + * @brief Brief + * + * Long. + */ +#ifndef _XML_H +#define _XML_H + +#include "chain.h" + +namespace ledger { + +class xact_t; +class account_t; +class commodity_t; +class post_t; +class report_t; + +/** + * @brief Brief + * + * Long. + */ +class format_xml : public item_handler +{ +protected: + report_t& report; + + typedef std::map commodities_map; + typedef std::pair commodities_pair; + + commodities_map commodities; + std::set transactions_set; + std::deque transactions; + +public: + format_xml(report_t& _report) : report(_report) { + TRACE_CTOR(format_xml, "report&"); + } + virtual ~format_xml() { + TRACE_DTOR(format_xml); + } + + virtual void flush(); + virtual void operator()(post_t& post); +}; + +} // namespace ledger + +#endif // _XML_H diff --git a/tools/Makefile.am b/tools/Makefile.am index a41c47ce..c0404606 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -73,6 +73,7 @@ libledger_report_la_SOURCES = \ src/generate.cc \ src/draft.cc \ src/emacs.cc \ + src/xml.cc \ src/output.cc \ src/precmd.cc \ src/chain.cc \ @@ -136,6 +137,7 @@ pkginclude_HEADERS = \ src/generate.h \ src/stats.h \ src/output.h \ + src/xml.h \ src/emacs.h \ \ src/global.h \ -- cgit v1.2.3