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