1 /*
2 * Copyright (C) 2018 Rafael Ostertag
3 *
4 * This file is part of YAPET.
5 *
6 * YAPET is free software: you can redistribute it and/or modify it under the
7 * terms of the GNU General Public License as published by the Free Software
8 * Foundation, either version 3 of the License, or (at your option) any later
9 * version.
10 *
11 * YAPET is distributed in the hope that it will be useful, but WITHOUT ANY
12 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14 * details.
15 *
16 * You should have received a copy of the GNU General Public License along with
17 * YAPET. If not, see <http://www.gnu.org/licenses/>.
18 *
19 * Additional permission under GNU GPL version 3 section 7
20 *
21 * If you modify this program, or any covered work, by linking or combining it
22 * with the OpenSSL project's OpenSSL library (or a modified version of that
23 * library), containing parts covered by the terms of the OpenSSL or SSLeay
24 * licenses, Rafael Ostertag grants you additional permission to convey the
25 * resulting work. Corresponding Source for a non-source form of such a
26 * combination shall include the source code for the parts of OpenSSL used as
27 * well as that of the covered work.
28 */
29
30 #ifdef HAVE_CONFIG_H
31 #include "config.h"
32 #endif
33
34 #include <cstdio>
35 #include <stdexcept>
36
37 #include "consts.h"
38 #include "csvline.hh"
39 #include "intl.h"
40
41 using namespace yapet;
42
43 constexpr std::string::size_type START_OF_LINE{0};
44
validIndexOrThrow(field_index_type index) const45 void CSVLine::validIndexOrThrow(field_index_type index) const {
46 if (index > _numberOfFields) {
47 char msg[YAPET::Consts::EXCEPTION_MESSAGE_BUFFER_SIZE];
48 std::snprintf(msg, YAPET::Consts::EXCEPTION_MESSAGE_BUFFER_SIZE,
49 _("Cannot access field %d. Maximum field index %d"),
50 index, _numberOfFields - 1);
51 throw std::out_of_range(msg);
52 }
53 }
54
CSVLine(field_index_type numberOfFields,char separator)55 CSVLine::CSVLine(field_index_type numberOfFields, char separator)
56 : _numberOfFields{numberOfFields},
57 _line{_numberOfFields},
58 _separator{separator} {}
59
CSVLine(const CSVLine & other)60 CSVLine::CSVLine(const CSVLine& other)
61 : _numberOfFields{other._numberOfFields},
62 _line{other._line},
63 _separator{other._separator} {}
CSVLine(CSVLine && other)64 CSVLine::CSVLine(CSVLine&& other)
65 : _numberOfFields{std::move(other._numberOfFields)},
66 _line{std::move(other._line)},
67 _separator{other._separator} {}
68
operator =(const CSVLine & other)69 CSVLine& CSVLine::operator=(const CSVLine& other) {
70 if (this == &other) {
71 return *this;
72 }
73
74 _numberOfFields = other._numberOfFields;
75 _line = other._line;
76 _separator = other._separator;
77
78 return *this;
79 }
80
operator =(CSVLine && other)81 CSVLine& CSVLine::operator=(CSVLine&& other) {
82 if (this == &other) {
83 return *this;
84 }
85
86 _numberOfFields = std::move(other._numberOfFields);
87 _line = std::move(other._line);
88 _separator = other._separator;
89
90 return *this;
91 }
92
addField(field_index_type index,const std::string & value)93 void CSVLine::addField(field_index_type index, const std::string& value) {
94 validIndexOrThrow(index);
95
96 CSVStringField field{value, _separator};
97 _line[index] = field;
98 }
99
operator [](field_index_type index)100 std::string CSVLine::operator[](field_index_type index) {
101 validIndexOrThrow(index);
102
103 return _line[index].unescape();
104 }
105
addStringAsCsvFieldToLine(const std::string & field,field_index_type atIndex)106 void CSVLine::addStringAsCsvFieldToLine(const std::string& field,
107 field_index_type atIndex) {
108 if (atIndex == _numberOfFields) {
109 char msg[YAPET::Consts::EXCEPTION_MESSAGE_BUFFER_SIZE];
110 std::snprintf(msg, YAPET::Consts::EXCEPTION_MESSAGE_BUFFER_SIZE,
111 _("Expected %d fields in line, read %d"), _numberOfFields,
112 atIndex + 1);
113 throw std::invalid_argument(msg);
114 }
115
116 CSVStringField csvField{field, _separator};
117 _line[atIndex] = csvField;
118 }
119
parseLine(const std::string & line)120 void CSVLine::parseLine(const std::string& line) {
121 bool inEscapedField{false};
122 field_index_type currentIndex{0};
123
124 std::string fieldBuffer;
125 for (std::string::size_type column = 0; column < line.size(); column++) {
126 char currentChar{line[column]};
127 char lookAhead{column + 1 < line.size() ? line[column + 1]
128 : _separator};
129
130 if (currentChar == CSVLine::ESCAPE_CHAR && column == START_OF_LINE) {
131 fieldBuffer = CSVLine::ESCAPE_CHAR;
132 inEscapedField = true;
133 continue;
134 }
135
136 if (currentChar == CSVLine::ESCAPE_CHAR &&
137 lookAhead == CSVLine::ESCAPE_CHAR && inEscapedField) {
138 fieldBuffer += CSVLine::ESCAPE_CHAR;
139 fieldBuffer += CSVLine::ESCAPE_CHAR;
140 column++;
141 continue;
142 }
143
144 if (currentChar == CSVLine::ESCAPE_CHAR && lookAhead == _separator) {
145 fieldBuffer += CSVLine::ESCAPE_CHAR;
146 inEscapedField = false;
147 continue;
148 }
149
150 if (currentChar == _separator && !inEscapedField) {
151 addStringAsCsvFieldToLine(fieldBuffer, currentIndex);
152 currentIndex++;
153 fieldBuffer = std::string{};
154
155 if (lookAhead == CSVLine::ESCAPE_CHAR) {
156 fieldBuffer = CSVLine::ESCAPE_CHAR;
157 inEscapedField = true;
158 column++;
159 }
160
161 continue;
162 }
163
164 fieldBuffer += currentChar;
165 }
166
167 if (inEscapedField) {
168 throw std::invalid_argument(_("'\"' mismatch"));
169 }
170
171 if (currentIndex < (_numberOfFields - 1)) {
172 char msg[YAPET::Consts::EXCEPTION_MESSAGE_BUFFER_SIZE];
173 std::snprintf(msg, YAPET::Consts::EXCEPTION_MESSAGE_BUFFER_SIZE,
174 _("Expected %d fields in line, read only %d"),
175 _numberOfFields, currentIndex + 1);
176 throw std::invalid_argument(msg);
177 }
178
179 // Process the remaining string in stringstream
180 addStringAsCsvFieldToLine(fieldBuffer, currentIndex);
181 }
182
getLine()183 std::string CSVLine::getLine() {
184 std::string lineAsString;
185 for (field_index_type column = 0; column < _numberOfFields; column++) {
186 lineAsString += _line[column].escape();
187 if (column < (_numberOfFields - 1)) {
188 lineAsString += _separator;
189 }
190 }
191
192 return lineAsString;
193 }
194