-
-
Notifications
You must be signed in to change notification settings - Fork 345
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
Basic_Templates
sample with CSV reader class (#2403)
This PR aims to provide a clearer example of how to use templates. It also updates the Formatter classes: - JSON string escaping needs to escape double-quotes, \" - Standard format `quote` method needs to first escape any embedded quotes. Uses "" rather than \" as it's CSV-compatible. - Add XML formatter with basic escaping [scan:coverity]
- Loading branch information
Showing
29 changed files
with
2,873 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
/**** | ||
* Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. | ||
* Created 2015 by Skurydin Alexey | ||
* http://github.com/SmingHub/Sming | ||
* All files of the Sming Core are provided under the LGPL v3 license. | ||
* | ||
* CsvReader.cpp | ||
* | ||
* @author: 2021 - Mikee47 <[email protected]> | ||
* | ||
****/ | ||
|
||
#include "CsvReader.h" | ||
|
||
void CsvReader::reset() | ||
{ | ||
source->seekFrom(0, SeekOrigin::Start); | ||
if(!userHeadingsProvided) { | ||
readRow(); | ||
headings = row; | ||
} | ||
row = nullptr; | ||
} | ||
|
||
bool CsvReader::readRow() | ||
{ | ||
constexpr size_t blockSize{512}; | ||
|
||
String buffer(std::move(reinterpret_cast<String&>(row))); | ||
constexpr char quoteChar{'"'}; | ||
enum class FieldKind { | ||
unknown, | ||
quoted, | ||
unquoted, | ||
}; | ||
FieldKind fieldKind{}; | ||
bool escape{false}; | ||
bool quote{false}; | ||
char lc{'\0'}; | ||
unsigned writepos{0}; | ||
|
||
while(true) { | ||
if(buffer.length() == maxLineLength) { | ||
debug_w("[CSV] Line buffer limit reached %u", maxLineLength); | ||
return false; | ||
} | ||
size_t buflen = std::min(writepos + blockSize, maxLineLength); | ||
if(!buffer.setLength(buflen)) { | ||
debug_e("[CSV] Out of memory %u", buflen); | ||
return false; | ||
} | ||
auto len = source->readBytes(buffer.begin() + writepos, buflen - writepos); | ||
if(len == 0) { | ||
if(writepos == 0) { | ||
return false; | ||
} | ||
buffer.setLength(writepos); | ||
row = std::move(buffer); | ||
return true; | ||
} | ||
buflen = writepos + len; | ||
unsigned readpos = writepos; | ||
|
||
for(; readpos < buflen; ++readpos) { | ||
char c = buffer[readpos]; | ||
if(escape) { | ||
switch(c) { | ||
case 'n': | ||
c = '\n'; | ||
break; | ||
case 'r': | ||
c = '\r'; | ||
break; | ||
case 't': | ||
c = '\t'; | ||
break; | ||
default:; | ||
// Just accept character | ||
} | ||
escape = false; | ||
} else { | ||
if(fieldKind == FieldKind::unknown) { | ||
if(c == quoteChar) { | ||
fieldKind = FieldKind::quoted; | ||
quote = true; | ||
lc = '\0'; | ||
continue; | ||
} | ||
fieldKind = FieldKind::unquoted; | ||
} | ||
if(c == quoteChar) { | ||
quote = !quote; | ||
if(fieldKind == FieldKind::quoted) { | ||
if(lc == quoteChar) { | ||
buffer[writepos++] = c; | ||
lc = '\0'; | ||
} else { | ||
lc = c; | ||
} | ||
continue; | ||
} | ||
} else if(c == '\\') { | ||
escape = true; | ||
continue; | ||
} else if(!quote) { | ||
if(c == fieldSeparator) { | ||
c = '\0'; | ||
fieldKind = FieldKind::unknown; | ||
} else if(c == '\r') { | ||
continue; | ||
} else if(c == '\n') { | ||
source->seekFrom(readpos + 1 - buflen, SeekOrigin::Current); | ||
buffer.setLength(writepos); | ||
row = std::move(buffer); | ||
return true; | ||
} | ||
} | ||
} | ||
buffer[writepos++] = c; | ||
lc = c; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
/**** | ||
* Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. | ||
* Created 2015 by Skurydin Alexey | ||
* http://github.com/SmingHub/Sming | ||
* All files of the Sming Core are provided under the LGPL v3 license. | ||
* | ||
* CsvReader.h | ||
* | ||
* @author: 2021 - Mikee47 <[email protected]> | ||
* | ||
****/ | ||
|
||
#pragma once | ||
|
||
#include "Stream/DataSourceStream.h" | ||
#include "CStringArray.h" | ||
#include <memory> | ||
|
||
/** | ||
* @brief Class to parse a CSV file | ||
* | ||
* Spec: https://www.ietf.org/rfc/rfc4180.txt | ||
* | ||
* 1. Each record is located on a separate line | ||
* 2. Line ending for last record in the file is optional | ||
* 3. Field headings are provided either in the source data or in constructor (but not both) | ||
* 4. Fields separated with ',' and whitespace considered part of field content | ||
* 5. Fields may or may not be quoted - if present, will be removed during parsing | ||
* 6. Fields may contain line breaks, quotes or commas | ||
* 7. Quotes may be escaped thus "" if field itself is quoted | ||
* | ||
* Additional features: | ||
* | ||
* - Line breaks can be \n or \r\n | ||
* - Escapes codes within fields will be converted: \n \r \t \", \\ | ||
* - Field separator can be changed in constructor | ||
*/ | ||
class CsvReader | ||
{ | ||
public: | ||
/** | ||
* @brief Construct a CSV reader | ||
* @param source Stream to read CSV text from | ||
* @param fieldSeparator | ||
* @param headings Required if source data does not contain field headings as first row | ||
* @param maxLineLength Limit size of buffer to guard against malformed data | ||
*/ | ||
CsvReader(IDataSourceStream* source, char fieldSeparator = ',', const CStringArray& headings = nullptr, | ||
size_t maxLineLength = 2048) | ||
: fieldSeparator(fieldSeparator), userHeadingsProvided(headings), maxLineLength(maxLineLength), | ||
headings(headings) | ||
{ | ||
this->source.reset(source); | ||
reset(); | ||
} | ||
|
||
/** | ||
* @brief Reset reader to start of CSV file | ||
* | ||
* Cursor is set to 'before start'. | ||
* Call 'next()' to fetch first record. | ||
*/ | ||
void reset(); | ||
|
||
/** | ||
* @brief Seek to next record | ||
*/ | ||
bool next() | ||
{ | ||
return readRow(); | ||
} | ||
|
||
/** | ||
* @brief Get number of columns | ||
*/ | ||
unsigned count() const | ||
{ | ||
return headings.count(); | ||
} | ||
|
||
/** | ||
* @brief Get a value from the current row | ||
* @param index Column index, starts at 0 | ||
* @retval const char* nullptr if index is not valid | ||
*/ | ||
const char* getValue(unsigned index) | ||
{ | ||
return row[index]; | ||
} | ||
|
||
/** | ||
* @brief Get a value from the current row | ||
* @param index Column name | ||
* @retval const char* nullptr if name is not found | ||
*/ | ||
const char* getValue(const char* name) | ||
{ | ||
return getValue(getColumn(name)); | ||
} | ||
|
||
/** | ||
* @brief Get index of columnn given its name | ||
* @param name Column name to find | ||
* @retval int -1 if name is not found | ||
*/ | ||
int getColumn(const char* name) | ||
{ | ||
return headings.indexOf(name); | ||
} | ||
|
||
/** | ||
* @brief Determine if row is valid | ||
*/ | ||
explicit operator bool() const | ||
{ | ||
return bool(row); | ||
} | ||
|
||
/** | ||
* @brief Get headings | ||
*/ | ||
const CStringArray& getHeadings() const | ||
{ | ||
return headings; | ||
} | ||
|
||
/** | ||
* @brief Get current row | ||
*/ | ||
const CStringArray& getRow() const | ||
{ | ||
return row; | ||
} | ||
|
||
private: | ||
bool readRow(); | ||
|
||
std::unique_ptr<IDataSourceStream> source; | ||
char fieldSeparator; | ||
bool userHeadingsProvided; | ||
size_t maxLineLength; | ||
CStringArray headings; | ||
CStringArray row; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -76,6 +76,8 @@ void Json::escape(String& value) const | |
c = '_'; | ||
} | ||
} | ||
|
||
value.replace("\"", "\\\""); | ||
} | ||
|
||
} // namespace Format |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.