GnuCash c935c2f+
Loading...
Searching...
No Matches
gnc-tokenizer-csv.cpp
1/********************************************************************\
2 * gnc-tokenizer-csv.cpp - takes a csv file and converts it into a *
3 * two-dimensional vector of strings (table)*
4 * *
5 * Copyright (C) 2015 Geert Janssens <geert@kobaltwit.be> *
6 * *
7 * This program is free software; you can redistribute it and/or *
8 * modify it under the terms of the GNU General Public License as *
9 * published by the Free Software Foundation; either version 2 of *
10 * the License, or (at your option) any later version. *
11 * *
12 * This program is distributed in the hope that it will be useful, *
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15 * GNU General Public License for more details. *
16 * *
17 * You should have received a copy of the GNU General Public License*
18 * along with this program; if not, contact: *
19 * *
20 * Free Software Foundation Voice: +1-617-542-5942 *
21 * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
22 * Boston, MA 02110-1301, USA gnu@gnu.org *
23\********************************************************************/
24
25#include "gnc-tokenizer-csv.hpp"
26
27#include <iostream>
28#include <fstream> // fstream
29#include <vector>
30#include <string>
31#include <algorithm> // copy
32#include <iterator> // ostream_operator
33
34#include <boost/tokenizer.hpp>
35#include <boost/locale.hpp>
36#include <boost/algorithm/string.hpp>
37
38#include <glib/gi18n.h>
39
40void
41GncCsvTokenizer::set_separators(const std::string& separators)
42{
43 m_sep_str = separators;
44}
45
46void
47GncCsvTokenizer::set_enable_escape(const bool enable)
48{
49 m_enable_escape = enable;
50}
51
52
53int GncCsvTokenizer::tokenize()
54{
55 using Tokenizer = boost::tokenizer< boost::escaped_list_separator<char>>;
56
57 boost::escaped_list_separator<char> sep("\\", m_sep_str, "\"");
58
59 StrVec vec;
60 std::string line;
61 std::string buffer;
62
63 bool inside_quotes(false);
64 size_t last_quote(0);
65
66 m_tokenized_contents.clear();
67 std::istringstream in_stream(m_utf8_contents);
68
69 try
70 {
71 while (std::getline (in_stream, buffer))
72 {
73 // --- deal with line breaks in quoted strings
74 buffer = boost::trim_copy (buffer); // Removes trailing newline and spaces
75 last_quote = buffer.find_first_of('"');
76 while (last_quote != std::string::npos)
77 {
78 if (last_quote == 0) // Test separately because last_quote - 1 would be out of range
79 inside_quotes = !inside_quotes;
80 else if (!m_enable_escape || buffer[ last_quote - 1 ] != '\\')
81 inside_quotes = !inside_quotes;
82
83 last_quote = buffer.find_first_of('"',last_quote+1);
84 }
85
86 line.append(buffer);
87 if (inside_quotes)
88 {
89 line.append(" ");
90 continue;
91 }
92 // ---
93
94 // Deal with backslashes that are not meant to be escapes
95 // The boost::tokenizer with escaped_list_separator as we use
96 // it would choke on this.
97 auto bs_pos = line.find ('\\');
98 while (bs_pos != std::string::npos)
99 {
100 if (! m_enable_escape)
101 {
102 // Put an escape before this escape and look for the next
103 line.insert (bs_pos, 1, '\\');
104 bs_pos += 2;
105 }
106 else
107 {
108 // Process as before
109 if ((bs_pos == line.size()) || // got trailing single backslash
110 (line.find_first_of ("\"\\n", bs_pos + 1) != bs_pos + 1)) // backslash is not part of known escapes \\, \" or \n
111 line = line.substr(0, bs_pos) + "\\\\" + line.substr(bs_pos + 1);
112 bs_pos += 2;
113 }
114
115 bs_pos = line.find ('\\', bs_pos);
116 }
117
118 // Deal with repeated " ("") in strings.
119 // This is commonly used as escape mechanism for double quotes in csv files.
120 // However boost just eats them.
121 bs_pos = line.find ("\"\"");
122 while (bs_pos != std::string::npos)
123 {
124 // Only make changes in case the double quotes are part of a larger field
125 // In other words a field which only contains two double quotes represent an
126 // empty field. We don't need to touch those.
127 // The way to determine whether the double quotes represent an empty string
128 // is by checking whether the character in front or after are either
129 // a field separator or the beginning or end of of the string.
130 if (!(((bs_pos == 0) || // quotes are at start of line
131 (m_sep_str.find (line[bs_pos-1]) != std::string::npos)) // quotes preceded by field separator
132 &&
133 ((bs_pos + 2 >= line.length()) || // quotes are at end of line
134 (m_sep_str.find (line[bs_pos+2]) != std::string::npos)))) // quotes followed by field separator
135 // Only make changes in case the double quotes are not an empty field
136 line.replace (bs_pos, 2, "\\\"");
137 bs_pos = line.find ("\"\"", bs_pos + 2);
138 }
139
140 Tokenizer tok(line, sep);
141 vec.assign(tok.begin(),tok.end());
142 m_tokenized_contents.push_back(vec);
143 line.clear();
144 }
145 }
146 catch (boost::escaped_list_error &e)
147 {
148 throw (std::range_error N_("There was an error parsing the file."));
149 }
150
151 return 0;
152}
Class to convert a csv file into vector of string vectors.