keepassxc

Форк
0
/
CsvParser.cpp 
396 строк · 7.8 Кб
1
/*
2
 *  Copyright (C) 2016 Enrico Mariotti <enricomariotti@yahoo.it>
3
 *  Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
4
 *
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.
9
 *
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.
14
 *
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/>.
17
 */
18

19
#include "CsvParser.h"
20

21
#include <QFile>
22
#include <QTextCodec>
23

24
#include "core/Tools.h"
25

26
CsvParser::CsvParser()
27
    : m_comment('#')
28
    , m_isBackslashSyntax(false)
29
    , m_isFileLoaded(false)
30
    , m_qualifier('"')
31
    , m_separator(',')
32
{
33
    reset();
34
    m_csv.setBuffer(&m_array);
35
    m_ts.setDevice(&m_csv);
36
    m_csv.open(QIODevice::ReadOnly);
37
    m_ts.setCodec("UTF-8");
38
}
39

40
CsvParser::~CsvParser()
41
{
42
    m_csv.close();
43
}
44

45
bool CsvParser::isFileLoaded()
46
{
47
    return m_isFileLoaded;
48
}
49

50
bool CsvParser::reparse()
51
{
52
    reset();
53
    return parseFile();
54
}
55

56
bool CsvParser::parse(QFile* device)
57
{
58
    clear();
59
    if (!device) {
60
        appendStatusMsg(QObject::tr("NULL device"), true);
61
        return false;
62
    }
63
    if (!readFile(device)) {
64
        return false;
65
    }
66
    return parseFile();
67
}
68

69
bool CsvParser::readFile(QFile* device)
70
{
71
    if (device->isOpen()) {
72
        device->close();
73
    }
74

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;
79
    } else {
80
        device->close();
81

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"));
86
        }
87
        m_isFileLoaded = true;
88
    }
89
    return m_isFileLoaded;
90
}
91

92
void CsvParser::reset()
93
{
94
    m_ch = 0;
95
    m_currCol = 1;
96
    m_currRow = 1;
97
    m_isEof = false;
98
    m_isGood = true;
99
    m_lastPos = -1;
100
    m_maxCols = 0;
101
    m_statusMsg.clear();
102
    m_ts.seek(0);
103
    m_table.clear();
104
    // the following can be overridden by the user
105
    // m_comment = '#';
106
    // m_backslashSyntax = false;
107
    // m_comment = '#';
108
    // m_qualifier = '"';
109
    // m_separator = ',';
110
}
111

112
void CsvParser::clear()
113
{
114
    reset();
115
    m_isFileLoaded = false;
116
    m_array.clear();
117
}
118

119
bool CsvParser::parseFile()
120
{
121
    parseRecord();
122
    while (!m_isEof) {
123
        if (!skipEndline()) {
124
            appendStatusMsg(QObject::tr("malformed string"), true);
125
        }
126
        m_currRow++;
127
        m_currCol = 1;
128
        parseRecord();
129
    }
130
    fillColumns();
131
    return m_isGood;
132
}
133

134
void CsvParser::parseRecord()
135
{
136
    CsvRow row;
137
    if (isComment()) {
138
        skipLine();
139
        return;
140
    }
141
    do {
142
        parseField(row);
143
        getChar(m_ch);
144
    } while (m_ch == m_separator && !m_isEof);
145

146
    if (!m_isEof) {
147
        ungetChar();
148
    }
149
    if (isEmptyRow(row)) {
150
        row.clear();
151
        return;
152
    }
153
    m_table.push_back(row);
154
    if (m_maxCols < row.size()) {
155
        m_maxCols = row.size();
156
    }
157
    m_currCol++;
158
}
159

160
void CsvParser::parseField(CsvRow& row)
161
{
162
    QString field;
163
    peek(m_ch);
164
    if (m_ch != m_separator && m_ch != '\n' && m_ch != '\r') {
165
        if (isQualifier(m_ch)) {
166
            parseQuoted(field);
167
        } else {
168
            parseSimple(field);
169
        }
170
    }
171
    row.push_back(field);
172
}
173

174
void CsvParser::parseSimple(QString& s)
175
{
176
    QChar c;
177
    getChar(c);
178
    while (c != '\n' && c != m_separator && !m_isEof) {
179
        s.append(c);
180
        getChar(c);
181
    }
182
    if (!m_isEof) {
183
        ungetChar();
184
    }
185
}
186

187
void CsvParser::parseQuoted(QString& s)
188
{
189
    // read and discard initial qualifier (e.g. quote)
190
    getChar(m_ch);
191
    parseEscaped(s);
192
    if (!isQualifier(m_ch)) {
193
        appendStatusMsg(QObject::tr("missing closing quote"), true);
194
    }
195
}
196

197
void CsvParser::parseEscaped(QString& s)
198
{
199
    parseEscapedText(s);
200
    while (processEscapeMark(s, m_ch)) {
201
        parseEscapedText(s);
202
    }
203
    if (!m_isEof) {
204
        ungetChar();
205
    }
206
}
207

208
void CsvParser::parseEscapedText(QString& s)
209
{
210
    getChar(m_ch);
211
    while (!isQualifier(m_ch) && !m_isEof) {
212
        s.append(m_ch);
213
        getChar(m_ch);
214
    }
215
}
216

217
bool CsvParser::processEscapeMark(QString& s, QChar c)
218
{
219
    QChar c2;
220
    peek(c2);
221
    if (m_isBackslashSyntax) {
222
        // escape-character syntax, e.g. \"
223
        if (c != '\\') {
224
            return false;
225
        }
226
        // consume (and append) second qualifier
227
        getChar(c2);
228
        if (m_isEof) {
229
            c2 = '\\';
230
            s.append('\\');
231
            return false;
232
        }
233
        s.append(c2);
234
        return true;
235
    }
236

237
    // double quote syntax, e.g. ""
238
    if (!isQualifier(c)) {
239
        return false;
240
    }
241
    peek(c2);
242
    if (!m_isEof) { // not EOF, can read one char
243
        if (isQualifier(c2)) {
244
            s.append(c2);
245
            getChar(c2);
246
            return true;
247
        }
248
    }
249
    return false;
250
}
251

252
void CsvParser::fillColumns()
253
{
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();
257
        if (gap > 0) {
258
            CsvRow r = m_table.at(i);
259
            for (int j = 0; j < gap; ++j) {
260
                r.append(QString(""));
261
            }
262
            m_table.replace(i, r);
263
        }
264
    }
265
}
266

267
void CsvParser::skipLine()
268
{
269
    m_ts.readLine();
270
    m_ts.seek(m_ts.pos() - 1);
271
}
272

273
bool CsvParser::skipEndline()
274
{
275
    getChar(m_ch);
276
    return m_ch == '\n';
277
}
278

279
void CsvParser::getChar(QChar& c)
280
{
281
    m_isEof = m_ts.atEnd();
282
    if (!m_isEof) {
283
        m_lastPos = m_ts.pos();
284
        m_ts >> c;
285
    }
286
}
287

288
void CsvParser::ungetChar()
289
{
290
    if (!m_ts.seek(m_lastPos)) {
291
        qWarning("CSV Parser: unget lower bound exceeded");
292
        m_isGood = false;
293
    }
294
}
295

296
void CsvParser::peek(QChar& c)
297
{
298
    getChar(c);
299
    if (!m_isEof) {
300
        ungetChar();
301
    }
302
}
303

304
bool CsvParser::isQualifier(const QChar& c) const
305
{
306
    if (m_isBackslashSyntax && c != m_qualifier) {
307
        return c == '\\';
308
    }
309
    return c == m_qualifier;
310
}
311

312
bool CsvParser::isComment()
313
{
314
    bool result = false;
315
    QChar c2;
316
    qint64 pos = m_ts.pos();
317

318
    do {
319
        getChar(c2);
320
    } while ((c2 == ' ' || c2 == '\t') && !m_isEof);
321

322
    if (c2 == m_comment) {
323
        result = true;
324
    }
325
    m_ts.seek(pos);
326
    return result;
327
}
328

329
bool CsvParser::isEmptyRow(const CsvRow& row) const
330
{
331
    for (auto it = row.constBegin(); it != row.constEnd(); ++it) {
332
        if (*it != "\n" && *it != "") {
333
            return false;
334
        }
335
    }
336
    return true;
337
}
338

339
void CsvParser::setBackslashSyntax(bool set)
340
{
341
    m_isBackslashSyntax = set;
342
}
343

344
void CsvParser::setComment(const QChar& c)
345
{
346
    m_comment = c.unicode();
347
}
348

349
void CsvParser::setCodec(const QString& s)
350
{
351
    m_ts.setCodec(QTextCodec::codecForName(s.toLocal8Bit()));
352
}
353

354
void CsvParser::setFieldSeparator(const QChar& c)
355
{
356
    m_separator = c.unicode();
357
}
358

359
void CsvParser::setTextQualifier(const QChar& c)
360
{
361
    m_qualifier = c.unicode();
362
}
363

364
int CsvParser::getFileSize() const
365
{
366
    return m_csv.size();
367
}
368

369
CsvTable CsvParser::getCsvTable() const
370
{
371
    return m_table;
372
}
373

374
QString CsvParser::getStatus() const
375
{
376
    return m_statusMsg;
377
}
378

379
int CsvParser::getCsvCols() const
380
{
381
    if (!m_table.isEmpty() && !m_table.at(0).isEmpty()) {
382
        return m_table.at(0).size();
383
    }
384
    return 0;
385
}
386

387
int CsvParser::getCsvRows() const
388
{
389
    return m_table.size();
390
}
391

392
void CsvParser::appendStatusMsg(const QString& s, bool isCritical)
393
{
394
    m_statusMsg += QObject::tr("%1: (row, col) %2,%3").arg(s, m_currRow, m_currCol).append("\n");
395
    m_isGood = !isCritical;
396
}
397

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.