/* * Copyright (c) 2003-2023, 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 "csv.h" #include "xact.h" #include "post.h" #include "account.h" #include "journal.h" #include "pool.h" namespace ledger { string csv_reader::read_field(std::istream& in) { string field; char c; if (in.peek() == '"' || in.peek() == '|') { in.get(c); char x; while (in.good() && ! in.eof()) { in.get(x); if (x == '\\') { in.get(x); } else if (x == '"' && in.peek() == '"') { in.get(x); } else if (x == c) { if (x == '|') in.unget(); else if (in.peek() == ',') in.get(c); break; } if (x != '\0') field += x; } } else { while (in.good() && ! in.eof()) { in.get(c); if (in.good()) { if (c == ',') break; if (c != '\0') field += c; } } } trim(field); return field; } char * csv_reader::next_line(std::istream& in) { while (in.good() && ! in.eof() && in.peek() == '#') in.getline(context.linebuf, parse_context_t::MAX_LINE); if (! in.good() || in.eof() || in.peek() == -1) return NULL; in.getline(context.linebuf, parse_context_t::MAX_LINE); return context.linebuf; } void csv_reader::read_index(std::istream& in) { char * line = next_line(in); if (! line) return; std::istringstream instr(line); while (instr.good() && ! instr.eof()) { string field = read_field(instr); names.push_back(field); DEBUG("csv.parse", "Header field: " << field); for (auto& mask : masks) { if (mask.first.match(field)) { index.push_back(mask.second); break; } } } } xact_t * csv_reader::read_xact(bool rich_data) { char * line = next_line(*context.stream.get()); if (! line || index.empty()) return NULL; context.linenum++; std::istringstream instr(line); unique_ptr xact(new xact_t); unique_ptr post(new post_t); xact->set_state(item_t::CLEARED); xact->pos = position_t(); xact->pos->pathname = context.pathname; xact->pos->beg_pos = context.stream->tellg(); xact->pos->beg_line = context.linenum; xact->pos->sequence = context.sequence++; post->xact = xact.get(); post->pos = position_t(); post->pos->pathname = context.pathname; post->pos->beg_pos = context.stream->tellg(); post->pos->beg_line = context.linenum; post->pos->sequence = context.sequence++; post->set_state(item_t::CLEARED); post->account = NULL; std::vector::size_type n = 0; amount_t amt; string total; string field; while (instr.good() && ! instr.eof() && n < index.size()) { field = read_field(instr); switch (index[n]) { case FIELD_DATE: xact->_date = parse_date(field); break; case FIELD_DATE_AUX: if (! field.empty()) xact->_date_aux = parse_date(field); break; case FIELD_CODE: if (! field.empty()) xact->code = field; break; case FIELD_PAYEE: { bool found = false; foreach (payee_alias_mapping_t& value, context.journal->payee_alias_mappings) { DEBUG("csv.mappings", "Looking for payee mapping: " << value.first); if (value.first.match(field)) { xact->payee = value.second; found = true; break; } } if (! found) xact->payee = field; break; } case FIELD_DEBIT: case FIELD_CREDIT: { if (field.length() == 0) break; std::istringstream amount_str(field); amt.parse(amount_str, PARSE_NO_REDUCE); if (! amt.has_commodity() && commodity_pool_t::current_pool->default_commodity) amt.set_commodity(*commodity_pool_t::current_pool->default_commodity); if (index[n] == FIELD_DEBIT) amt = -amt; if (!post->amount.is_null()) throw_(csv_error, _("Cannot have two values for a single transaction")); post->amount = amt; break; } case FIELD_COST: { std::istringstream amount_str(field); amt.parse(amount_str, PARSE_NO_REDUCE); if (! amt.has_commodity() && commodity_pool_t::current_pool->default_commodity) amt.set_commodity(*commodity_pool_t::current_pool->default_commodity); post->cost = amt; break; } case FIELD_TOTAL: total = field; break; case FIELD_NOTE: if (! field.empty()) xact->note = field; break; case FIELD_UNKNOWN: if (! names[n].empty() && ! field.empty()) xact->set_tag(names[n], string_value(field)); break; } n++; } if (rich_data) { xact->set_tag(_("Imported"), string_value(format_date(CURRENT_DATE(), FMT_WRITTEN))); xact->set_tag(_("CSV"), string_value(line)); } // Translate the account name, if we have enough information to do so foreach (account_mapping_t& value, context.journal->payees_for_unknown_accounts) { if (value.first.match(xact->payee)) { post->account = value.second; break; } } xact->add_post(post.release()); // Create the "balancing post", which refers to the account for this data post.reset(new post_t); post->xact = xact.get(); post->pos = position_t(); post->pos->pathname = context.pathname; post->pos->beg_pos = context.stream->tellg(); post->pos->beg_line = context.linenum; post->pos->sequence = context.sequence++; post->set_state(item_t::CLEARED); post->account = context.master; if (! amt.is_null()) post->amount = - amt; if (! total.empty()) { std::istringstream assigned_amount_str(total); amt.parse(assigned_amount_str, PARSE_NO_REDUCE); if (! amt.has_commodity() && commodity_pool_t::current_pool->default_commodity) amt.set_commodity(*commodity_pool_t::current_pool->default_commodity); post->assigned_amount = amt; } xact->add_post(post.release()); return xact.release(); } } // namespace ledger