GnuCash  5.6-150-g038405b370+
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 
40 void
41 GncCsvTokenizer::set_separators(const std::string& separators)
42 {
43  m_sep_str = separators;
44 }
45 
46 void
47 GncCsvTokenizer::set_enable_escape(const bool enable)
48 {
49  m_enable_escape = enable;
50 }
51 
52 
53 int 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.