diff --git a/src/lib/ebus/data.cpp b/src/lib/ebus/data.cpp index adbc296d..b18916a3 100644 --- a/src/lib/ebus/data.cpp +++ b/src/lib/ebus/data.cpp @@ -105,13 +105,14 @@ unsigned int parseInt(const char* str, int base, const unsigned int minValue, co return ret; } -void printErrorPos(vector::iterator begin, const vector::iterator end, vector::iterator pos) +void printErrorPos(vector::iterator begin, const vector::iterator end, vector::iterator pos, string filename, size_t lineNo, result_t result) { + if (pos > begin) + pos--; + cout << "Error reading \"" << filename << "\" line " << static_cast(pos.base()-begin.base()) << " value \"" << *pos << "\": " << getResultCode(result) << endl; cout << "Erroneous item is here:" << endl; bool first = true; int cnt = 0; - if (pos > begin) - pos--; while (begin != end) { if (first == true) first = false; @@ -122,9 +123,12 @@ void printErrorPos(vector::iterator begin, const vector::iterato } } if (begin < pos) { - cnt += (*begin).length(); + cnt += 1+(*begin).length()+1; + } else if (begin == pos) { + cnt++; } - cout << (*begin++); + string item = *begin++; + cout << TEXT_SEPARATOR << item << TEXT_SEPARATOR; } cout << endl; cout << setw(cnt) << " " << setw(0) << "^" << endl; @@ -1221,7 +1225,7 @@ result_t DataFieldTemplates::add(DataField* field, bool replace) return RESULT_OK; } -result_t DataFieldTemplates::addFromFile(vector& row, void* arg, vector< vector >* defaults) +result_t DataFieldTemplates::addFromFile(vector& row, void* arg, vector< vector >* defaults, const string& filename, unsigned int lineNo) { DataField* field = NULL; vector::iterator it = row.begin(); diff --git a/src/lib/ebus/data.h b/src/lib/ebus/data.h index 985d2e2d..078efad7 100644 --- a/src/lib/ebus/data.h +++ b/src/lib/ebus/data.h @@ -22,6 +22,7 @@ #include "symbol.h" #include "result.h" +#include "filereader.h" #include #include #include @@ -31,9 +32,6 @@ using namespace std; -/** the separator character used between fields (in CSV only). */ -#define FIELD_SEPARATOR ',' - /** the separator character used between multiple values (in CSV only). */ #define VALUE_SEPARATOR ';' @@ -103,7 +101,7 @@ unsigned int parseInt(const char* str, int base, const unsigned int minValue, co * @param pos the iterator with the erroneous position. * @param separator the character to place between items. */ -void printErrorPos(vector::iterator begin, const vector::iterator end, vector::iterator pos); +void printErrorPos(vector::iterator begin, const vector::iterator end, vector::iterator pos, string filename, size_t lineNo, result_t result); class DataFieldTemplates; @@ -585,81 +583,6 @@ private: }; -/** - * @brief An abstract class that support reading definitions from a file. - */ -template -class FileReader -{ -public: - - /** - * @brief Constructs a new instance. - */ - FileReader(bool supportsDefaults) - : m_supportsDefaults(supportsDefaults) {} - /** - * @brief Destructor. - */ - virtual ~FileReader() {} - /** - * @brief Reads the definitions from a file. - * @param filename the name (and path) of the file to read. - * @return @a RESULT_OK on success, or an error code. - */ - virtual result_t readFromFile(string filename, T arg=NULL) - { - ifstream ifs; - ifs.open(filename.c_str(), ifstream::in); - if (ifs.is_open() == false) - return RESULT_ERR_NOTFOUND; - - string line; - unsigned int lineNo = 0; - vector row; - string token; - vector< vector > defaults; - while (getline(ifs, line) != 0) { - lineNo++; - // skip empty lines and comments - if (line.length() == 0 || line.substr(0, 1) == "#" || line.substr(0, 2) == "//") - continue; - istringstream isstr(line); - row.clear(); - while (getline(isstr, token, FIELD_SEPARATOR) != 0) - row.push_back(token); - - if (m_supportsDefaults == true && line.substr(0, 1) == "*") { - row[0] = row[0].substr(1); - defaults.push_back(row); - continue; - } - result_t result = addFromFile(row, arg, m_supportsDefaults == true ? &defaults : NULL); - if (result != RESULT_OK) { - cerr << "error reading \"" << filename << "\" line " << static_cast(lineNo) << ": " << getResultCode(result) << endl; - ifs.close(); - return result; - } - } - - ifs.close(); - return RESULT_OK; - } - /** - * @brief Adds a definition that was read from a file. - * @param row the definition row read from the file. - * @param defaults all previously read default rows (initial star char removed), or NULL if not supported. - * @return @a RESULT_OK on success, or an error code. - */ - virtual result_t addFromFile(vector& row, T arg, vector< vector >* defaults) = 0; - -private: - /** whether this instance supports rows with defaults (starting with a star). */ - bool m_supportsDefaults; - -}; - - /** * @brief A map of template @a DataField instances. */ @@ -688,7 +611,7 @@ public: */ result_t add(DataField* message, bool replace=false); // @copydoc - virtual result_t addFromFile(vector& row, void* arg, vector< vector >* defaults); + virtual result_t addFromFile(vector& row, void* arg, vector< vector >* defaults, const string& filename, unsigned int lineNo); /** * @brief Gets the template @a DataField instance with the specified name. * @return the template @a DataField instance, or NULL. diff --git a/src/lib/ebus/filereader.h b/src/lib/ebus/filereader.h new file mode 100644 index 00000000..62a9b738 --- /dev/null +++ b/src/lib/ebus/filereader.h @@ -0,0 +1,163 @@ +/* + * Copyright (C) John Baier 2014 + * + * This file is part of ebusd. + * + * ebusd is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ebusd is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ebusd. If not, see http://www.gnu.org/licenses/. + */ + +#ifndef LIBEBUS_FILEREADER_H_ +#define LIBEBUS_FILEREADER_H_ + +#include "symbol.h" +#include "result.h" +#include +#include +#include +#include +#include +#include + +using namespace std; + + +/** the separator character used between fields. */ +#define FIELD_SEPARATOR ',' + +/** the separator character used to quote text having the @a FIELD_SEPARATOR in it. */ +#define TEXT_SEPARATOR '"' + + +/** + * @brief An abstract class that support reading definitions from a file. + */ +template +class FileReader +{ +public: + + /** + * @brief Constructs a new instance. + */ + FileReader(bool supportsDefaults) + : m_supportsDefaults(supportsDefaults) {} + + /** + * @brief Destructor. + */ + virtual ~FileReader() {} + + /** + * @brief Reads the definitions from a file. + * @param filename the name (and path) of the file to read. + * @return @a RESULT_OK on success, or an error code. + */ + virtual result_t readFromFile(const string filename, T arg=NULL) + { + ifstream ifs; + ifs.open(filename.c_str(), ifstream::in); + if (ifs.is_open() == false) + return RESULT_ERR_NOTFOUND; + + string line; + unsigned int lineNo = 0; + vector row; + string token; + vector< vector > defaults; + while (getline(ifs, line) != 0) { + lineNo++; + // skip empty lines and comments + size_t length = line.length(); + if (length == 0 || line[0] == '#' || (line.length() > 1 && line[0] == '/' && line[1] == '/')) + continue; + + row.clear(); + bool quotedText = false; + ostringstream field; + char prev = FIELD_SEPARATOR; + for (size_t pos = 0; pos < length; pos++) { + char ch = line[pos]; + switch (ch) + { + case FIELD_SEPARATOR: + if (quotedText == true) + field << ch; + else { + row.push_back(field.str()); + field.str(""); + } + break; + case TEXT_SEPARATOR: + if (quotedText == true) { + quotedText = false; + } + else if (prev == TEXT_SEPARATOR) { // double dquote + quotedText = true; + field << ch; + } + else if (prev == FIELD_SEPARATOR) { + quotedText = true; + } + else + field << ch; + break; + case '\r': + break; + default: + field << ch; + break; + } + prev = ch; + } + row.push_back(field.str()); + + result_t result; + if (m_supportsDefaults == true) { + if (line[0] == '*') { + row[0] = row[0].substr(1); + defaults.push_back(row); + continue; + } + result = addFromFile(row, arg, &defaults, filename, lineNo); + } + else + result = addFromFile(row, arg, NULL, filename, lineNo); + + if (result != RESULT_OK) { + cerr << "error reading \"" << filename << "\" line " << static_cast(lineNo) << ": " << getResultCode(result) << endl; + ifs.close(); + return result; + } + } + + ifs.close(); + return RESULT_OK; + } + + /** + * @brief Adds a definition that was read from a file. + * @param row the definition row read from the file. + * @param defaults all previously read default rows (initial star char removed), or NULL if not supported. + * @return @a RESULT_OK on success, or an error code. + */ + virtual result_t addFromFile(vector& row, T arg, vector< vector >* defaults, const string& filename, unsigned int lineNo) = 0; + +private: + + /** whether this instance supports rows with defaults (starting with a star). */ + bool m_supportsDefaults; + +}; + +#endif // LIBEBUS_FILEREADER_H_ diff --git a/src/lib/ebus/message.cpp b/src/lib/ebus/message.cpp index 27dfa633..01d421e9 100644 --- a/src/lib/ebus/message.cpp +++ b/src/lib/ebus/message.cpp @@ -369,7 +369,7 @@ result_t MessageMap::add(Message* message) return RESULT_OK; } -result_t MessageMap::addFromFile(vector& row, DataFieldTemplates* arg, vector< vector >* defaults) +result_t MessageMap::addFromFile(vector& row, DataFieldTemplates* arg, vector< vector >* defaults, const string& filename, unsigned int lineNo) { Message* message = NULL; string types = row[0]; @@ -384,8 +384,8 @@ result_t MessageMap::addFromFile(vector& row, DataFieldTemplates* arg, v vector::iterator it = row.begin(); result = Message::create(it, row.end(), defaults, arg, message); if (result != RESULT_OK) { - printErrorPos(row.begin(), row.end(), it); - return result; + printErrorPos(row.begin(), row.end(), it, filename, lineNo, result); + continue; } result = add(message); if (result != RESULT_OK) { diff --git a/src/lib/ebus/message.h b/src/lib/ebus/message.h index 9dea47d9..27430019 100644 --- a/src/lib/ebus/message.h +++ b/src/lib/ebus/message.h @@ -264,7 +264,7 @@ public: */ result_t add(Message* message); // @copydoc - virtual result_t addFromFile(vector& row, DataFieldTemplates* arg, vector< vector >* defaults); + virtual result_t addFromFile(vector& row, DataFieldTemplates* arg, vector< vector >* defaults, const string& filename, unsigned int lineNo); /** * @brief Find the @a Message instance for the specified class and name. * @param class the optional device class. diff --git a/src/lib/ebus/test/test_message.cpp b/src/lib/ebus/test/test_message.cpp index 8e79957d..eac50086 100644 --- a/src/lib/ebus/test/test_message.cpp +++ b/src/lib/ebus/test/test_message.cpp @@ -113,7 +113,7 @@ int main() if (result != RESULT_OK) { cout << "\"" << check[0] << "\": create error: " << getResultCode(result) << endl; - printErrorPos(entries.begin(), entries.end(), it); + printErrorPos(entries.begin(), entries.end(), it, "", 0, result); continue; } if (deleteMessage == NULL) {