1 /*
2  * Copyright (c) 2003-2018, John Wiegley.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  * - Redistributions of source code must retain the above copyright
9  *   notice, this list of conditions and the following disclaimer.
10  *
11  * - Redistributions in binary form must reproduce the above copyright
12  *   notice, this list of conditions and the following disclaimer in the
13  *   documentation and/or other materials provided with the distribution.
14  *
15  * - Neither the name of New Artisans LLC nor the names of its
16  *   contributors may be used to endorse or promote products derived from
17  *   this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 #include <system.hh>
33 
34 #include "account.h"
35 #include "post.h"
36 #include "xact.h"
37 
38 namespace ledger {
39 
~account_t()40 account_t::~account_t()
41 {
42   TRACE_DTOR(account_t);
43 
44   foreach (accounts_map::value_type& pair, accounts) {
45     if (! pair.second->has_flags(ACCOUNT_TEMP) ||
46         has_flags(ACCOUNT_TEMP)) {
47       checked_delete(pair.second);
48     }
49   }
50 }
51 
find_account(const string & acct_name,const bool auto_create)52 account_t * account_t::find_account(const string& acct_name,
53                                     const bool    auto_create)
54 {
55   accounts_map::const_iterator i = accounts.find(acct_name);
56   if (i != accounts.end())
57     return (*i).second;
58 
59   char buf[8192];
60 
61   string::size_type sep = acct_name.find(':');
62   assert(sep < 256|| sep == string::npos);
63 
64   const char * first, * rest;
65   if (sep == string::npos) {
66     first = acct_name.c_str();
67     rest  = NULL;
68   } else {
69     std::strncpy(buf, acct_name.c_str(), sep);
70     buf[sep] = '\0';
71 
72     first = buf;
73     rest  = acct_name.c_str() + sep + 1;
74   }
75 
76   account_t * account;
77 
78   i = accounts.find(first);
79   if (i == accounts.end()) {
80     if (! auto_create)
81       return NULL;
82 
83     account = new account_t(this, first);
84 
85     // An account created within a temporary or generated account is itself
86     // temporary or generated, so that the whole tree has the same status.
87     if (has_flags(ACCOUNT_TEMP))
88       account->add_flags(ACCOUNT_TEMP);
89     if (has_flags(ACCOUNT_GENERATED))
90       account->add_flags(ACCOUNT_GENERATED);
91 
92 #if DEBUG_ON
93     std::pair<accounts_map::iterator, bool> result =
94 #endif
95       accounts.insert(accounts_map::value_type(first, account));
96 #if DEBUG_ON
97     assert(result.second);
98 #endif
99   } else {
100     account = (*i).second;
101   }
102 
103   if (rest)
104     account = account->find_account(rest, auto_create);
105 
106   return account;
107 }
108 
109 namespace {
find_account_re_(account_t * account,const mask_t & regexp)110   account_t * find_account_re_(account_t * account, const mask_t& regexp)
111   {
112     if (regexp.match(account->fullname()))
113       return account;
114 
115     foreach (accounts_map::value_type& pair, account->accounts)
116       if (account_t * a = find_account_re_(pair.second, regexp))
117         return a;
118 
119     return NULL;
120   }
121 }
122 
find_account_re(const string & regexp)123 account_t * account_t::find_account_re(const string& regexp)
124 {
125   return find_account_re_(this, mask_t(regexp));
126 }
127 
add_post(post_t * post)128 void account_t::add_post(post_t * post)
129 {
130   posts.push_back(post);
131 
132   // Adding a new post changes the possible totals that may have been
133   // computed before.
134   if (xdata_) {
135     xdata_->self_details.gathered     = false;
136     xdata_->self_details.calculated   = false;
137     xdata_->family_details.gathered   = false;
138     xdata_->family_details.calculated = false;
139     if (! xdata_->family_details.total.is_null()) {
140       xdata_->family_details.total = ledger::value_t();
141     }
142     account_t *ancestor = this;
143     while (ancestor->parent) {
144       ancestor = ancestor->parent;
145       if (ancestor->has_xdata()) {
146         xdata_t &xdata = ancestor->xdata();
147         xdata.family_details.gathered   = false;
148         xdata.family_details.calculated = false;
149         xdata.family_details.total = ledger::value_t();
150       }
151     }
152   }
153 }
154 
add_deferred_post(const string & uuid,post_t * post)155 void account_t::add_deferred_post(const string& uuid, post_t * post)
156 {
157   if (! deferred_posts)
158     deferred_posts = deferred_posts_map_t();
159 
160   deferred_posts_map_t::iterator i = deferred_posts->find(uuid);
161   if (i == deferred_posts->end()) {
162     posts_list lst;
163     lst.push_back(post);
164     deferred_posts->insert(deferred_posts_map_t::value_type(uuid, lst));
165   } else {
166     (*i).second.push_back(post);
167   }
168 }
169 
apply_deferred_posts()170 void account_t::apply_deferred_posts()
171 {
172   if (deferred_posts) {
173     foreach (deferred_posts_map_t::value_type& pair, *deferred_posts) {
174       foreach (post_t * post, pair.second)
175         post->account->add_post(post);
176     }
177     deferred_posts = none;
178   }
179 
180   // Also apply in child accounts
181   foreach (const accounts_map::value_type& pair, accounts)
182     pair.second->apply_deferred_posts();
183 }
184 
remove_post(post_t * post)185 bool account_t::remove_post(post_t * post)
186 {
187   // It's possible that 'post' wasn't yet in this account, but try to
188   // remove it anyway.  This can happen if there is an error during
189   // parsing, when the posting knows what it's account is, but
190   // xact_t::finalize has not yet added that posting to the account.
191   posts.remove(post);
192   post->account = NULL;
193   return true;
194 }
195 
fullname() const196 string account_t::fullname() const
197 {
198   if (! _fullname.empty()) {
199     return _fullname;
200   } else {
201     const account_t *   first    = this;
202     string              fullname = name;
203 
204     while (first->parent) {
205       first = first->parent;
206       if (! first->name.empty())
207         fullname = first->name + ":" + fullname;
208     }
209 
210     _fullname = fullname;
211 
212     return fullname;
213   }
214 }
215 
partial_name(bool flat) const216 string account_t::partial_name(bool flat) const
217 {
218   string pname = name;
219 
220   for (const account_t * acct = parent;
221        acct && acct->parent;
222        acct = acct->parent) {
223     if (! flat) {
224       std::size_t count = acct->children_with_flags(ACCOUNT_EXT_TO_DISPLAY);
225       assert(count > 0);
226       if (count > 1 || acct->has_xflags(ACCOUNT_EXT_TO_DISPLAY))
227         break;
228     }
229     pname = acct->name + ":" + pname;
230   }
231   return pname;
232 }
233 
operator <<(std::ostream & out,const account_t & account)234 std::ostream& operator<<(std::ostream& out, const account_t& account)
235 {
236   out << account.fullname();
237   return out;
238 }
239 
240 namespace {
get_partial_name(call_scope_t & args)241   value_t get_partial_name(call_scope_t& args)
242   {
243     return string_value(args.context<account_t>()
244                         .partial_name(args.has<bool>(0) &&
245                                       args.get<bool>(0)));
246   }
247 
get_account(call_scope_t & args)248   value_t get_account(call_scope_t& args) { // this gets the name
249     account_t& account(args.context<account_t>());
250     if (args.has<string>(0)) {
251       account_t * acct = account.parent;
252       for (; acct && acct->parent; acct = acct->parent) ;
253       if (args[0].is_string())
254         return scope_value(acct->find_account(args.get<string>(0), false));
255       else if (args[0].is_mask())
256         return scope_value(acct->find_account_re(args.get<mask_t>(0).str()));
257       else
258         return NULL_VALUE;
259     }
260     else if (args.type_context() == value_t::SCOPE) {
261       return scope_value(&account);
262     }
263     else {
264       return string_value(account.fullname());
265     }
266   }
267 
get_account_base(account_t & account)268   value_t get_account_base(account_t& account) {
269     return string_value(account.name);
270   }
271 
get_amount(account_t & account)272   value_t get_amount(account_t& account) {
273     return SIMPLIFIED_VALUE_OR_ZERO(account.amount());
274   }
275 
get_total(account_t & account)276   value_t get_total(account_t& account) {
277     return SIMPLIFIED_VALUE_OR_ZERO(account.total());
278   }
279 
get_subcount(account_t & account)280   value_t get_subcount(account_t& account) {
281     return long(account.self_details().posts_count);
282   }
283 
get_count(account_t & account)284   value_t get_count(account_t& account) {
285     return long(account.family_details().posts_count);
286   }
get_cost(account_t &)287   value_t get_cost(account_t&) {
288     throw_(calc_error, _("An account does not have a 'cost' value"));
289     return false;
290   }
291 
get_depth(account_t & account)292   value_t get_depth(account_t& account) {
293     return long(account.depth);
294   }
295 
get_note(account_t & account)296   value_t get_note(account_t& account) {
297     return account.note ? string_value(*account.note) : NULL_VALUE;
298   }
299 
ignore(account_t &)300   value_t ignore(account_t&) {
301     return false;
302   }
303 
get_true(account_t &)304   value_t get_true(account_t&) {
305     return true;
306   }
307 
get_addr(account_t & account)308   value_t get_addr(account_t& account) {
309     return long(&account);
310   }
311 
get_depth_parent(account_t & account)312   value_t get_depth_parent(account_t& account)
313   {
314     std::size_t depth = 0;
315     for (const account_t * acct = account.parent;
316          acct && acct->parent;
317          acct = acct->parent) {
318       std::size_t count = acct->children_with_flags(ACCOUNT_EXT_TO_DISPLAY);
319       assert(count > 0);
320       if (count > 1 || acct->has_xflags(ACCOUNT_EXT_TO_DISPLAY))
321         depth++;
322     }
323     return long(depth);
324   }
325 
get_depth_spacer(account_t & account)326   value_t get_depth_spacer(account_t& account)
327   {
328     std::size_t depth = 0;
329     for (const account_t * acct = account.parent;
330          acct && acct->parent;
331          acct = acct->parent) {
332       std::size_t count = acct->children_with_flags(ACCOUNT_EXT_TO_DISPLAY);
333       assert(count > 0);
334       if (count > 1 || acct->has_xflags(ACCOUNT_EXT_TO_DISPLAY))
335         depth++;
336     }
337 
338     std::ostringstream out;
339     for (std::size_t i = 0; i < depth; i++)
340       out << "  ";
341 
342     return string_value(out.str());
343   }
344 
get_latest_cleared(account_t & account)345   value_t get_latest_cleared(account_t& account)
346   {
347     return account.self_details().latest_cleared_post;
348   }
349 
get_earliest(account_t & account)350   value_t get_earliest(account_t& account)
351   {
352     return account.self_details().earliest_post;
353   }
get_earliest_checkin(account_t & account)354   value_t get_earliest_checkin(account_t& account)
355   {
356     return (! account.self_details().earliest_checkin.is_not_a_date_time() ?
357             value_t(account.self_details().earliest_checkin) : NULL_VALUE);
358   }
359 
get_latest(account_t & account)360   value_t get_latest(account_t& account)
361   {
362     return account.self_details().latest_post;
363   }
get_latest_checkout(account_t & account)364   value_t get_latest_checkout(account_t& account)
365   {
366     return (! account.self_details().latest_checkout.is_not_a_date_time() ?
367             value_t(account.self_details().latest_checkout) : NULL_VALUE);
368   }
get_latest_checkout_cleared(account_t & account)369   value_t get_latest_checkout_cleared(account_t& account)
370   {
371     return account.self_details().latest_checkout_cleared;
372   }
373 
374   template <value_t (*Func)(account_t&)>
get_wrapper(call_scope_t & args)375   value_t get_wrapper(call_scope_t& args) {
376     return (*Func)(args.context<account_t>());
377   }
378 
get_parent(account_t & account)379   value_t get_parent(account_t& account) {
380     return scope_value(account.parent);
381   }
382 
fn_any(call_scope_t & args)383   value_t fn_any(call_scope_t& args)
384   {
385     account_t& account(args.context<account_t>());
386     expr_t::ptr_op_t expr(args.get<expr_t::ptr_op_t>(0));
387 
388     foreach (post_t * p, account.posts) {
389       bind_scope_t bound_scope(args, *p);
390       if (expr->calc(bound_scope, args.locus, args.depth).to_boolean())
391         return true;
392     }
393     return false;
394   }
395 
fn_all(call_scope_t & args)396   value_t fn_all(call_scope_t& args)
397   {
398     account_t& account(args.context<account_t>());
399     expr_t::ptr_op_t expr(args.get<expr_t::ptr_op_t>(0));
400 
401     foreach (post_t * p, account.posts) {
402       bind_scope_t bound_scope(args, *p);
403       if (! expr->calc(bound_scope, args.locus, args.depth).to_boolean())
404         return false;
405     }
406     return true;
407   }
408 }
409 
lookup(const symbol_t::kind_t kind,const string & fn_name)410 expr_t::ptr_op_t account_t::lookup(const symbol_t::kind_t kind,
411                                    const string& fn_name)
412 {
413   if (kind != symbol_t::FUNCTION)
414     return NULL;
415 
416   switch (fn_name[0]) {
417   case 'a':
418     if (fn_name[1] == '\0' || fn_name == "amount")
419       return WRAP_FUNCTOR(get_wrapper<&get_amount>);
420     else if (fn_name == "account")
421       return WRAP_FUNCTOR(&get_account);
422     else if (fn_name == "account_base")
423       return WRAP_FUNCTOR(get_wrapper<&get_account_base>);
424     else if (fn_name == "addr")
425       return WRAP_FUNCTOR(get_wrapper<&get_addr>);
426     else if (fn_name == "any")
427       return WRAP_FUNCTOR(&fn_any);
428     else if (fn_name == "all")
429       return WRAP_FUNCTOR(&fn_all);
430     break;
431 
432   case 'c':
433     if (fn_name == "count")
434       return WRAP_FUNCTOR(get_wrapper<&get_count>);
435     else if (fn_name == "cost")
436       return WRAP_FUNCTOR(get_wrapper<&get_cost>);
437     break;
438 
439   case 'd':
440     if (fn_name == "depth")
441       return WRAP_FUNCTOR(get_wrapper<&get_depth>);
442     else if (fn_name == "depth_parent")
443       return WRAP_FUNCTOR(get_wrapper<&get_depth_parent>);
444     else if (fn_name == "depth_spacer")
445       return WRAP_FUNCTOR(get_wrapper<&get_depth_spacer>);
446     break;
447 
448   case 'e':
449     if (fn_name == "earliest")
450       return WRAP_FUNCTOR(get_wrapper<&get_earliest>);
451     else if (fn_name == "earliest_checkin")
452       return WRAP_FUNCTOR(get_wrapper<&get_earliest_checkin>);
453     break;
454 
455   case 'i':
456     if (fn_name == "is_account")
457       return WRAP_FUNCTOR(get_wrapper<&get_true>);
458     else if (fn_name == "is_index")
459       return WRAP_FUNCTOR(get_wrapper<&get_subcount>);
460     break;
461 
462   case 'l':
463     if (fn_name[1] == '\0')
464       return WRAP_FUNCTOR(get_wrapper<&get_depth>);
465     else if (fn_name == "latest_cleared")
466       return WRAP_FUNCTOR(get_wrapper<&get_latest_cleared>);
467     else if (fn_name == "latest")
468       return WRAP_FUNCTOR(get_wrapper<&get_latest>);
469     else if (fn_name == "latest_checkout")
470       return WRAP_FUNCTOR(get_wrapper<&get_latest_checkout>);
471     else if (fn_name == "latest_checkout_cleared")
472       return WRAP_FUNCTOR(get_wrapper<&get_latest_checkout_cleared>);
473     break;
474 
475   case 'n':
476     if (fn_name[1] == '\0')
477       return WRAP_FUNCTOR(get_wrapper<&get_subcount>);
478     else if (fn_name == "note")
479       return WRAP_FUNCTOR(get_wrapper<&get_note>);
480     break;
481 
482   case 'p':
483     if (fn_name == "partial_account")
484       return WRAP_FUNCTOR(get_partial_name);
485     else if (fn_name == "parent")
486       return WRAP_FUNCTOR(get_wrapper<&get_parent>);
487     break;
488 
489   case 's':
490     if (fn_name == "subcount")
491       return WRAP_FUNCTOR(get_wrapper<&get_subcount>);
492     break;
493 
494   case 't':
495     if (fn_name == "total")
496       return WRAP_FUNCTOR(get_wrapper<&get_total>);
497     break;
498 
499   case 'u':
500     if (fn_name == "use_direct_amount")
501       return WRAP_FUNCTOR(get_wrapper<&ignore>);
502     break;
503 
504   case 'N':
505     if (fn_name[1] == '\0')
506       return WRAP_FUNCTOR(get_wrapper<&get_count>);
507     break;
508 
509   case 'O':
510     if (fn_name[1] == '\0')
511       return WRAP_FUNCTOR(get_wrapper<&get_total>);
512     break;
513   }
514 
515   return NULL;
516 }
517 
valid() const518 bool account_t::valid() const
519 {
520   if (depth > 256) {
521     DEBUG("ledger.validate", "account_t: depth > 256");
522     return false;
523   }
524 
525   foreach (const accounts_map::value_type& pair, accounts) {
526     if (this == pair.second) {
527       DEBUG("ledger.validate", "account_t: parent refers to itself!");
528       return false;
529     }
530 
531     if (! pair.second->valid()) {
532       DEBUG("ledger.validate", "account_t: child not valid");
533       return false;
534     }
535   }
536 
537   return true;
538 }
539 
children_with_xdata() const540 bool account_t::children_with_xdata() const
541 {
542   foreach (const accounts_map::value_type& pair, accounts)
543     if (pair.second->has_xdata() ||
544         pair.second->children_with_xdata())
545       return true;
546 
547   return false;
548 }
549 
children_with_flags(xdata_t::flags_t flags) const550 std::size_t account_t::children_with_flags(xdata_t::flags_t flags) const
551 {
552   std::size_t count = 0;
553   bool        grandchildren_visited = false;
554 
555   foreach (const accounts_map::value_type& pair, accounts)
556     if (pair.second->has_xflags(flags) ||
557         pair.second->children_with_flags(flags))
558       count++;
559 
560   // Although no immediately children were visited, if any progeny at all were
561   // visited, it counts as one.
562   if (count == 0 && grandchildren_visited)
563     count = 1;
564 
565   return count;
566 }
567 
568 account_t::xdata_t::details_t&
operator +=(const details_t & other)569 account_t::xdata_t::details_t::operator+=(const details_t& other)
570 {
571   posts_count            += other.posts_count;
572   posts_virtuals_count   += other.posts_virtuals_count;
573   posts_cleared_count    += other.posts_cleared_count;
574   posts_last_7_count     += other.posts_last_7_count;
575   posts_last_30_count    += other.posts_last_30_count;
576   posts_this_month_count += other.posts_this_month_count;
577 
578   if (! is_valid(earliest_post) ||
579       (is_valid(other.earliest_post) &&
580        other.earliest_post < earliest_post))
581     earliest_post = other.earliest_post;
582   if (! is_valid(earliest_cleared_post) ||
583       (is_valid(other.earliest_cleared_post) &&
584        other.earliest_cleared_post < earliest_cleared_post))
585     earliest_cleared_post = other.earliest_cleared_post;
586 
587   if (! is_valid(latest_post) ||
588       (is_valid(other.latest_post) &&
589        other.latest_post > latest_post))
590     latest_post = other.latest_post;
591   if (! is_valid(latest_cleared_post) ||
592       (is_valid(other.latest_cleared_post) &&
593        other.latest_cleared_post > latest_cleared_post))
594     latest_cleared_post = other.latest_cleared_post;
595 
596   filenames.insert(other.filenames.begin(), other.filenames.end());
597   accounts_referenced.insert(other.accounts_referenced.begin(),
598                              other.accounts_referenced.end());
599   payees_referenced.insert(other.payees_referenced.begin(),
600                            other.payees_referenced.end());
601   return *this;
602 }
603 
clear_xdata()604 void account_t::clear_xdata()
605 {
606   xdata_ = none;
607 
608   foreach (accounts_map::value_type& pair, accounts)
609     if (! pair.second->has_flags(ACCOUNT_TEMP))
610       pair.second->clear_xdata();
611 }
612 
amount(const optional<bool> real_only,const optional<expr_t &> & expr) const613 value_t account_t::amount(const optional<bool> real_only, const optional<expr_t&>& expr) const
614 {
615   DEBUG("account.amount", "real only: " << real_only);
616 
617   if (xdata_ && xdata_->has_flags(ACCOUNT_EXT_VISITED)) {
618     posts_list::const_iterator i;
619     if (xdata_->self_details.last_post)
620       i = *xdata_->self_details.last_post;
621     else
622       i = posts.begin();
623 
624     for (; i != posts.end(); i++) {
625       if ((*i)->xdata().has_flags(POST_EXT_VISITED)) {
626         if (! (*i)->xdata().has_flags(POST_EXT_CONSIDERED)) {
627           if (! (*i)->has_flags(POST_VIRTUAL)) {
628             (*i)->add_to_value(xdata_->self_details.real_total, expr);
629           }
630 
631           (*i)->add_to_value(xdata_->self_details.total, expr);
632           (*i)->xdata().add_flags(POST_EXT_CONSIDERED);
633         }
634       }
635       xdata_->self_details.last_post = i;
636     }
637 
638     if (xdata_->self_details.last_reported_post)
639       i = *xdata_->self_details.last_reported_post;
640     else
641       i = xdata_->reported_posts.begin();
642 
643     for (; i != xdata_->reported_posts.end(); i++) {
644       if ((*i)->xdata().has_flags(POST_EXT_VISITED)) {
645         if (! (*i)->xdata().has_flags(POST_EXT_CONSIDERED)) {
646           if (! (*i)->has_flags(POST_VIRTUAL)) {
647             (*i)->add_to_value(xdata_->self_details.real_total, expr);
648           }
649 
650           (*i)->add_to_value(xdata_->self_details.total, expr);
651           (*i)->xdata().add_flags(POST_EXT_CONSIDERED);
652         }
653       }
654       xdata_->self_details.last_reported_post = i;
655     }
656 
657     if (real_only == true) {
658         return xdata_->self_details.real_total;
659     } else {
660         return xdata_->self_details.total;
661     }
662   } else {
663     return NULL_VALUE;
664   }
665 }
666 
total(const optional<expr_t &> & expr) const667 value_t account_t::total(const optional<expr_t&>& expr) const
668 {
669   if (! (xdata_ && xdata_->family_details.calculated)) {
670     const_cast<account_t&>(*this).xdata().family_details.calculated = true;
671 
672     value_t temp;
673     foreach (const accounts_map::value_type& pair, accounts) {
674       temp = pair.second->total(expr);
675       if (! temp.is_null())
676         add_or_set_value(xdata_->family_details.total, temp);
677     }
678 
679     temp = amount(false, expr);
680     if (! temp.is_null())
681       add_or_set_value(xdata_->family_details.total, temp);
682   }
683   return xdata_->family_details.total;
684 }
685 
686 const account_t::xdata_t::details_t&
self_details(bool gather_all) const687 account_t::self_details(bool gather_all) const
688 {
689   if (! (xdata_ && xdata_->self_details.gathered)) {
690     const_cast<account_t&>(*this).xdata().self_details.gathered = true;
691 
692     foreach (const post_t * post, posts)
693       xdata_->self_details.update(const_cast<post_t&>(*post), gather_all);
694   }
695   return xdata_->self_details;
696 }
697 
698 const account_t::xdata_t::details_t&
family_details(bool gather_all) const699 account_t::family_details(bool gather_all) const
700 {
701   if (! (xdata_ && xdata_->family_details.gathered)) {
702     const_cast<account_t&>(*this).xdata().family_details.gathered = true;
703 
704     foreach (const accounts_map::value_type& pair, accounts)
705       xdata_->family_details += pair.second->family_details(gather_all);
706 
707     xdata_->family_details += self_details(gather_all);
708   }
709   return xdata_->family_details;
710 }
711 
update(post_t & post,bool gather_all)712 void account_t::xdata_t::details_t::update(post_t& post,
713                                            bool    gather_all)
714 {
715   posts_count++;
716 
717   if (post.has_flags(POST_VIRTUAL))
718     posts_virtuals_count++;
719 
720   if (gather_all && post.pos)
721     filenames.insert(post.pos->pathname);
722 
723   date_t date = post.date();
724 
725   if (date.year() == CURRENT_DATE().year() &&
726       date.month() == CURRENT_DATE().month())
727     posts_this_month_count++;
728 
729   if ((CURRENT_DATE() - date).days() <= 30)
730     posts_last_30_count++;
731   if ((CURRENT_DATE() - date).days() <= 7)
732     posts_last_7_count++;
733 
734   if (! is_valid(earliest_post) || post.date() < earliest_post)
735     earliest_post = post.date();
736   if (! is_valid(latest_post) || post.date() > latest_post)
737     latest_post = post.date();
738 
739   if (post.checkin && (earliest_checkin.is_not_a_date_time() ||
740                        *post.checkin < earliest_checkin))
741     earliest_checkin = *post.checkin;
742 
743   if (post.checkout && (latest_checkout.is_not_a_date_time() ||
744                         *post.checkout > latest_checkout)) {
745     latest_checkout = *post.checkout;
746     latest_checkout_cleared = post.state() == item_t::CLEARED;
747   }
748 
749   if (post.state() == item_t::CLEARED) {
750     posts_cleared_count++;
751 
752     if (! is_valid(earliest_cleared_post) ||
753         post.date() < earliest_cleared_post)
754       earliest_cleared_post = post.date();
755     if (! is_valid(latest_cleared_post) ||
756         post.date() > latest_cleared_post)
757       latest_cleared_post = post.date();
758   }
759 
760   if (gather_all) {
761     accounts_referenced.insert(post.account->fullname());
762     payees_referenced.insert(post.payee());
763   }
764 }
765 
put_account(property_tree::ptree & st,const account_t & acct,function<bool (const account_t &)> pred)766 void put_account(property_tree::ptree& st, const account_t& acct,
767                  function<bool(const account_t&)> pred)
768 {
769   if (pred(acct)) {
770     std::ostringstream buf;
771     buf.width(sizeof(unsigned long) * 2);
772     buf.fill('0');
773     buf << std::hex << reinterpret_cast<unsigned long>(&acct);
774 
775     st.put("<xmlattr>.id", buf.str());
776 
777     st.put("name", acct.name);
778     st.put("fullname", acct.fullname());
779 
780     value_t total = acct.amount();
781     if (! total.is_null())
782       put_value(st.put("account-amount", ""), total);
783 
784     total = acct.total();
785     if (! total.is_null())
786       put_value(st.put("account-total", ""), total);
787 
788     foreach (const accounts_map::value_type& pair, acct.accounts)
789       put_account(st.add("account", ""), *pair.second, pred);
790   }
791 }
792 
793 } // namespace ledger
794