2
* Copyright (C) 2016 Enrico Mariotti <enricomariotti@yahoo.it>
3
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
5
* This program is free software: you can redistribute it and/or modify
6
* it under the terms of the GNU General Public License as published by
7
* the Free Software Foundation, either version 2 or (at your option)
8
* version 3 of the License.
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
15
* You should have received a copy of the GNU General Public License
16
* along with this program. If not, see <http://www.gnu.org/licenses/>.
24
#include "core/Tools.h"
28
, m_isBackslashSyntax(false)
29
, m_isFileLoaded(false)
34
m_csv.setBuffer(&m_array);
35
m_ts.setDevice(&m_csv);
36
m_csv.open(QIODevice::ReadOnly);
37
m_ts.setCodec("UTF-8");
40
CsvParser::~CsvParser()
45
bool CsvParser::isFileLoaded()
47
return m_isFileLoaded;
50
bool CsvParser::reparse()
56
bool CsvParser::parse(QFile* device)
60
appendStatusMsg(QObject::tr("NULL device"), true);
63
if (!readFile(device)) {
69
bool CsvParser::readFile(QFile* device)
71
if (device->isOpen()) {
75
device->open(QIODevice::ReadOnly);
76
if (!Tools::readAllFromDevice(device, m_array)) {
77
appendStatusMsg(QObject::tr("error reading from device"), true);
78
m_isFileLoaded = false;
82
m_array.replace("\r\n", "\n");
83
m_array.replace("\r", "\n");
84
if (m_array.isEmpty()) {
85
appendStatusMsg(QObject::tr("file empty").append("\n"));
87
m_isFileLoaded = true;
89
return m_isFileLoaded;
92
void CsvParser::reset()
104
// the following can be overridden by the user
106
// m_backslashSyntax = false;
108
// m_qualifier = '"';
109
// m_separator = ',';
112
void CsvParser::clear()
115
m_isFileLoaded = false;
119
bool CsvParser::parseFile()
123
if (!skipEndline()) {
124
appendStatusMsg(QObject::tr("malformed string"), true);
134
void CsvParser::parseRecord()
144
} while (m_ch == m_separator && !m_isEof);
149
if (isEmptyRow(row)) {
153
m_table.push_back(row);
154
if (m_maxCols < row.size()) {
155
m_maxCols = row.size();
160
void CsvParser::parseField(CsvRow& row)
164
if (m_ch != m_separator && m_ch != '\n' && m_ch != '\r') {
165
if (isQualifier(m_ch)) {
171
row.push_back(field);
174
void CsvParser::parseSimple(QString& s)
178
while (c != '\n' && c != m_separator && !m_isEof) {
187
void CsvParser::parseQuoted(QString& s)
189
// read and discard initial qualifier (e.g. quote)
192
if (!isQualifier(m_ch)) {
193
appendStatusMsg(QObject::tr("missing closing quote"), true);
197
void CsvParser::parseEscaped(QString& s)
200
while (processEscapeMark(s, m_ch)) {
208
void CsvParser::parseEscapedText(QString& s)
211
while (!isQualifier(m_ch) && !m_isEof) {
217
bool CsvParser::processEscapeMark(QString& s, QChar c)
221
if (m_isBackslashSyntax) {
222
// escape-character syntax, e.g. \"
226
// consume (and append) second qualifier
237
// double quote syntax, e.g. ""
238
if (!isQualifier(c)) {
242
if (!m_isEof) { // not EOF, can read one char
243
if (isQualifier(c2)) {
252
void CsvParser::fillColumns()
254
// fill shorter rows with empty placeholder columns
255
for (int i = 0; i < m_table.size(); ++i) {
256
int gap = m_maxCols - m_table.at(i).size();
258
CsvRow r = m_table.at(i);
259
for (int j = 0; j < gap; ++j) {
260
r.append(QString(""));
262
m_table.replace(i, r);
267
void CsvParser::skipLine()
270
m_ts.seek(m_ts.pos() - 1);
273
bool CsvParser::skipEndline()
279
void CsvParser::getChar(QChar& c)
281
m_isEof = m_ts.atEnd();
283
m_lastPos = m_ts.pos();
288
void CsvParser::ungetChar()
290
if (!m_ts.seek(m_lastPos)) {
291
qWarning("CSV Parser: unget lower bound exceeded");
296
void CsvParser::peek(QChar& c)
304
bool CsvParser::isQualifier(const QChar& c) const
306
if (m_isBackslashSyntax && c != m_qualifier) {
309
return c == m_qualifier;
312
bool CsvParser::isComment()
316
qint64 pos = m_ts.pos();
320
} while ((c2 == ' ' || c2 == '\t') && !m_isEof);
322
if (c2 == m_comment) {
329
bool CsvParser::isEmptyRow(const CsvRow& row) const
331
for (auto it = row.constBegin(); it != row.constEnd(); ++it) {
332
if (*it != "\n" && *it != "") {
339
void CsvParser::setBackslashSyntax(bool set)
341
m_isBackslashSyntax = set;
344
void CsvParser::setComment(const QChar& c)
346
m_comment = c.unicode();
349
void CsvParser::setCodec(const QString& s)
351
m_ts.setCodec(QTextCodec::codecForName(s.toLocal8Bit()));
354
void CsvParser::setFieldSeparator(const QChar& c)
356
m_separator = c.unicode();
359
void CsvParser::setTextQualifier(const QChar& c)
361
m_qualifier = c.unicode();
364
int CsvParser::getFileSize() const
369
CsvTable CsvParser::getCsvTable() const
374
QString CsvParser::getStatus() const
379
int CsvParser::getCsvCols() const
381
if (!m_table.isEmpty() && !m_table.at(0).isEmpty()) {
382
return m_table.at(0).size();
387
int CsvParser::getCsvRows() const
389
return m_table.size();
392
void CsvParser::appendStatusMsg(const QString& s, bool isCritical)
394
m_statusMsg += QObject::tr("%1: (row, col) %2,%3").arg(s, m_currRow, m_currCol).append("\n");
395
m_isGood = !isCritical;