comparison src/libqxt/qxtcsvmodel.cpp @ 1:7a2637c3eb83

Add csv parser from libqxt
author Andre Heinecke <andre.heinecke@intevation.de>
date Mon, 23 Mar 2015 16:33:26 +0100
parents
children 73efe717b944
comparison
equal deleted inserted replaced
0:147b08bc7d64 1:7a2637c3eb83
1
2 /****************************************************************************
3 ** Copyright (c) 2006 - 2011, the LibQxt project.
4 ** See the Qxt AUTHORS file for a list of authors and copyright holders.
5 ** All rights reserved.
6 **
7 ** Redistribution and use in source and binary forms, with or without
8 ** modification, are permitted provided that the following conditions are met:
9 ** * Redistributions of source code must retain the above copyright
10 ** notice, this list of conditions and the following disclaimer.
11 ** * Redistributions in binary form must reproduce the above copyright
12 ** notice, this list of conditions and the following disclaimer in the
13 ** documentation and/or other materials provided with the distribution.
14 ** * Neither the name of the LibQxt project nor the
15 ** names of its contributors may be used to endorse or promote products
16 ** derived from this software without specific prior written permission.
17 **
18 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 ** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 ** DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
22 ** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 ** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 ** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 ** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 **
29 ** <http://libqxt.org> <foundation@libqxt.org>
30 *****************************************************************************/
31
32 /*!
33 \class QxtCsvModel
34 \inmodule QxtCore
35 \brief The QxtCsvModel class provides a QAbstractTableModel for CSV Files
36 */
37
38
39
40 #include "qxtcsvmodel.h"
41 #include <QFile>
42 #include <QTextStream>
43 #include <QDebug>
44
45 class QxtCsvModelPrivate : public QxtPrivate<QxtCsvModel>
46 {
47 public:
48 QxtCsvModelPrivate() : csvData(), header(), maxColumn(0), quoteMode(QxtCsvModel::DefaultQuoteMode)
49 {}
50 QXT_DECLARE_PUBLIC(QxtCsvModel)
51
52 QList<QStringList> csvData;
53 QStringList header;
54 int maxColumn;
55 QxtCsvModel::QuoteMode quoteMode;
56 };
57
58 /*!
59 Creates an empty QxtCsvModel with parent \a parent.
60 */
61 QxtCsvModel::QxtCsvModel(QObject *parent) : QAbstractTableModel(parent)
62 {
63 QXT_INIT_PRIVATE(QxtCsvModel);
64 }
65
66 /*!
67 Creates a QxtCsvModel with the parent \a parent and content loaded from \a file.
68
69 See \a setSource for information on the \a withHeader and \a separator properties, or
70 if you need control over the quoting method or codec used to parse the file.
71
72 \sa setSource
73 */
74 QxtCsvModel::QxtCsvModel(QIODevice *file, QObject *parent, bool withHeader, QChar separator) : QAbstractTableModel(parent)
75 {
76 QXT_INIT_PRIVATE(QxtCsvModel);
77 setSource(file, withHeader, separator);
78 }
79
80 /*!
81 \overload
82
83 Creates a QxtCsvModel with the parent \a parent and content loaded from \a file.
84
85 See \a setSource for information on the \a withHeader and \a separator properties, or
86 if you need control over the quoting method or codec used to parse the file.
87
88 \sa setSource
89 */
90 QxtCsvModel::QxtCsvModel(const QString filename, QObject *parent, bool withHeader, QChar separator) : QAbstractTableModel(parent)
91 {
92 QXT_INIT_PRIVATE(QxtCsvModel);
93 QFile src(filename);
94 setSource(&src, withHeader, separator);
95 }
96
97 QxtCsvModel::~QxtCsvModel()
98 {}
99
100 /*!
101 \reimp
102 */
103 int QxtCsvModel::rowCount(const QModelIndex& parent) const
104 {
105 if (parent.row() != -1 && parent.column() != -1) return 0;
106 return qxt_d().csvData.count();
107 }
108
109 /*!
110 \reimp
111 */
112 int QxtCsvModel::columnCount(const QModelIndex& parent) const
113 {
114 if (parent.row() != -1 && parent.column() != -1) return 0;
115 return qxt_d().maxColumn;
116 }
117
118 /*!
119 \reimp
120 */
121 QVariant QxtCsvModel::data(const QModelIndex& index, int role) const
122 {
123 if(index.parent() != QModelIndex()) return QVariant();
124 if(role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::UserRole) {
125 if(index.row() < 0 || index.column() < 0 || index.row() >= rowCount())
126 return QVariant();
127 const QStringList& row = qxt_d().csvData[index.row()];
128 if(index.column() >= row.length())
129 return QVariant();
130 return row[index.column()];
131 }
132 return QVariant();
133 }
134
135 /*!
136 \reimp
137 */
138 QVariant QxtCsvModel::headerData(int section, Qt::Orientation orientation, int role) const
139 {
140 if(section < qxt_d().header.count() && orientation == Qt::Horizontal && (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::UserRole))
141 return qxt_d().header[section];
142 else
143 return QAbstractTableModel::headerData(section, orientation, role);
144 }
145
146 /*!
147 \overload
148
149 Reads in a CSV file from the provided \a file using \a codec.
150 */
151 void QxtCsvModel::setSource(const QString filename, bool withHeader, QChar separator, QTextCodec* codec)
152 {
153 QFile src(filename);
154 setSource(&src, withHeader, separator, codec);
155 }
156
157 /*!
158 Reads in a CSV file from the provided \a file using \a codec.
159
160 The value of \a separator will be used to delimit fields, subject to the specified \a quoteMode.
161 If \a withHeader is set to true, the first line of the file will be used to populate the model's
162 horizontal header.
163
164 \sa quoteMode
165 */
166 void QxtCsvModel::setSource(QIODevice *file, bool withHeader, QChar separator, QTextCodec* codec)
167 {
168 QxtCsvModelPrivate* d_ptr = &qxt_d();
169 bool headerSet = !withHeader;
170 if(!file->isOpen())
171 file->open(QIODevice::ReadOnly);
172 if(withHeader)
173 d_ptr->maxColumn = 0;
174 else
175 d_ptr->maxColumn = d_ptr->header.size();
176 d_ptr->csvData.clear();
177 QStringList row;
178 QString field;
179 QChar quote;
180 QChar ch, buffer(0);
181 bool readCR = false;
182 QTextStream stream(file);
183 if(codec) {
184 stream.setCodec(codec);
185 } else {
186 stream.setAutoDetectUnicode(true);
187 }
188 while(!stream.atEnd()) {
189 if(buffer != QChar(0)) {
190 ch = buffer;
191 buffer = QChar(0);
192 } else {
193 stream >> ch;
194 }
195 if(ch == '\n' && readCR)
196 continue;
197 else if(ch == '\r')
198 readCR = true;
199 else
200 readCR = false;
201 if(ch != separator && (ch.category() == QChar::Separator_Line || ch.category() == QChar::Separator_Paragraph || ch.category() == QChar::Other_Control)) {
202 row << field;
203 field.clear();
204 if(!row.isEmpty()) {
205 if(!headerSet) {
206 d_ptr->header = row;
207 headerSet = true;
208 } else {
209 d_ptr->csvData.append(row);
210 }
211 if(row.length() > d_ptr->maxColumn) {
212 d_ptr->maxColumn = row.length();
213 }
214 }
215 row.clear();
216 } else if((d_ptr->quoteMode & DoubleQuote && ch == '"') || (d_ptr->quoteMode & SingleQuote && ch == '\'')) {
217 quote = ch;
218 do {
219 stream >> ch;
220 if(ch == '\\' && d_ptr->quoteMode & BackslashEscape) {
221 stream >> ch;
222 } else if(ch == quote) {
223 if(d_ptr->quoteMode & TwoQuoteEscape) {
224 stream >> buffer;
225 if(buffer == quote) {
226 buffer = QChar(0);
227 field.append(ch);
228 continue;
229 }
230 }
231 break;
232 }
233 field.append(ch);
234 } while(!stream.atEnd());
235 } else if(ch == separator) {
236 row << field;
237 field.clear();
238 } else {
239 field.append(ch);
240 }
241 }
242 if(!field.isEmpty())
243 row << field;
244 if(!row.isEmpty()) {
245 if(!headerSet)
246 d_ptr->header = row;
247 else
248 d_ptr->csvData.append(row);
249 }
250 file->close();
251 }
252
253 /*!
254 Sets the horizontal headers of the model to the values provided in \a data.
255 */
256 void QxtCsvModel::setHeaderData(const QStringList& data)
257 {
258 qxt_d().header = data;
259 emit headerDataChanged(Qt::Horizontal, 0, data.count());
260 }
261
262 /*!
263 \reimp
264 */
265 bool QxtCsvModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant& value, int role)
266 {
267 if(orientation != Qt::Horizontal) return false; // We don't support the vertical header
268 if(role != Qt::DisplayRole || role != Qt::EditRole) return false; // We don't support any other roles
269 if(section < 0) return false; // Bogus input
270 while(section > qxt_d().header.size()) {
271 qxt_d().header << QString();
272 }
273 qxt_d().header[section] = value.toString();
274 emit headerDataChanged(Qt::Horizontal, section, section);
275 return true;
276 }
277
278 /*!
279 \reimp
280 */
281 bool QxtCsvModel::setData(const QModelIndex& index, const QVariant& data, int role)
282 {
283 if (index.parent() != QModelIndex()) return false;
284
285 if(role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::UserRole) {
286 if(index.row() >= rowCount() || index.column() >= columnCount() || index.row() < 0 || index.column() < 0) return false;
287 QStringList& row = qxt_d().csvData[index.row()];
288 while(row.length() <= index.column())
289 row << QString();
290 row[index.column()] = data.toString();
291 emit dataChanged(index, index);
292 return true;
293 }
294 return false;
295 }
296
297 /*!
298 \reimp
299 */
300 bool QxtCsvModel::insertRow(int row, const QModelIndex& parent)
301 {
302 return insertRows(row, 1, parent);
303 }
304
305 /*!
306 \reimp
307 */
308 bool QxtCsvModel::insertRows(int row, int count, const QModelIndex& parent)
309 {
310 if (parent != QModelIndex() || row < 0) return false;
311 emit beginInsertRows(parent, row, row + count);
312 QxtCsvModelPrivate& d_ptr = qxt_d();
313 if(row >= rowCount()) {
314 for(int i = 0; i < count; i++) d_ptr.csvData << QStringList();
315 } else {
316 for(int i = 0; i < count; i++) d_ptr.csvData.insert(row, QStringList());
317 }
318 emit endInsertRows();
319 return true;
320 }
321
322 /*!
323 \reimp
324 */
325 bool QxtCsvModel::removeRow(int row, const QModelIndex& parent)
326 {
327 return removeRows(row, 1, parent);
328 }
329
330 /*!
331 \reimp
332 */
333 bool QxtCsvModel::removeRows(int row, int count, const QModelIndex& parent)
334 {
335 if (parent != QModelIndex() || row < 0) return false;
336 if (row >= rowCount()) return false;
337 if (row + count >= rowCount()) count = rowCount() - row;
338 emit beginRemoveRows(parent, row, row + count);
339 QxtCsvModelPrivate& d_ptr = qxt_d();
340 for (int i = 0;i < count;i++)
341 d_ptr.csvData.removeAt(row);
342 emit endRemoveRows();
343 return true;
344 }
345
346 /*!
347 \reimp
348 */
349 bool QxtCsvModel::insertColumn(int col, const QModelIndex& parent)
350 {
351 return insertColumns(col, 1, parent);
352 }
353
354 /*!
355 \reimp
356 */
357 bool QxtCsvModel::insertColumns(int col, int count, const QModelIndex& parent)
358 {
359 if (parent != QModelIndex() || col < 0) return false;
360 beginInsertColumns(parent, col, col + count - 1);
361 QxtCsvModelPrivate& d_ptr = qxt_d();
362 for(int i = 0; i < rowCount(); i++) {
363 QStringList& row = d_ptr.csvData[i];
364 while(col >= row.length()) row.append(QString());
365 for(int j = 0; j < count; j++) {
366 row.insert(col, QString());
367 }
368 }
369 for(int i = 0; i < count ;i++)
370 d_ptr.header.insert(col, QString());
371 d_ptr.maxColumn += count;
372 endInsertColumns();
373 return true;
374 }
375
376 /*!
377 \reimp
378 */
379 bool QxtCsvModel::removeColumn(int col, const QModelIndex& parent)
380 {
381 return removeColumns(col, 1, parent);
382 }
383
384 /*!
385 \reimp
386 */
387 bool QxtCsvModel::removeColumns(int col, int count, const QModelIndex& parent)
388 {
389 if (parent != QModelIndex() || col < 0) return false;
390 if (col >= columnCount()) return false;
391 if (col + count >= columnCount()) count = columnCount() - col;
392 emit beginRemoveColumns(parent, col, col + count);
393 QxtCsvModelPrivate& d_ptr = qxt_d();
394 QString before, after;
395 for(int i = 0; i < rowCount(); i++) {
396 for(int j = 0; j < count; j++) {
397 d_ptr.csvData[i].removeAt(col);
398 }
399 }
400 for(int i = 0; i < count; i++)
401 d_ptr.header.removeAt(col);
402 emit endRemoveColumns();
403 return true;
404 }
405
406 static QString qxt_addCsvQuotes(QxtCsvModel::QuoteMode mode, QString field)
407 {
408 bool addDoubleQuotes = ((mode & QxtCsvModel::DoubleQuote) && field.contains('"'));
409 bool addSingleQuotes = ((mode & QxtCsvModel::SingleQuote) && field.contains('\''));
410 bool quoteField = (mode & QxtCsvModel::AlwaysQuoteOutput) || addDoubleQuotes || addSingleQuotes;
411 if(quoteField && !addDoubleQuotes && !addSingleQuotes) {
412 if(mode & QxtCsvModel::DoubleQuote)
413 addDoubleQuotes = true;
414 else if(mode & QxtCsvModel::SingleQuote)
415 addSingleQuotes = true;
416 }
417 if(mode & QxtCsvModel::BackslashEscape) {
418 if(addDoubleQuotes)
419 return '"' + field.replace("\\", "\\\\").replace("\"", "\\\"") + '"';
420 if(addSingleQuotes)
421 return '\'' + field.replace("\\", "\\\\").replace("'", "\\'") + '\'';
422 } else {
423 if(addDoubleQuotes)
424 return '"' + field.replace("\"", "\"\"") + '"';
425 if(addSingleQuotes)
426 return '\'' + field.replace("'", "''") + '\'';
427 }
428 return field;
429 }
430
431 /*!
432 Outputs the content of the model as a CSV file to the device \a dest using \a codec.
433
434 Fields in the output file will be separated by \a separator. Set \a withHeader to true
435 to output a row of headers at the top of the file.
436 */
437 void QxtCsvModel::toCSV(QIODevice* dest, bool withHeader, QChar separator, QTextCodec* codec) const
438 {
439 const QxtCsvModelPrivate& d_ptr = qxt_d();
440 int row, col, rows, cols;
441 rows = rowCount();
442 cols = columnCount();
443 QString data;
444 if(!dest->isOpen()) dest->open(QIODevice::WriteOnly | QIODevice::Truncate);
445 QTextStream stream(dest);
446 if(codec) stream.setCodec(codec);
447 if(withHeader) {
448 data = "";
449 for(col = 0; col < cols; ++col) {
450 if(col > 0) data += separator;
451 data += qxt_addCsvQuotes(d_ptr.quoteMode, d_ptr.header.at(col));
452 }
453 stream << data << endl;
454 }
455 for(row = 0; row < rows; ++row)
456 {
457 const QStringList& rowData = d_ptr.csvData[row];
458 data = "";
459 for(col = 0; col < cols; ++col) {
460 if(col > 0) data += separator;
461 if(col < rowData.length())
462 data += qxt_addCsvQuotes(d_ptr.quoteMode, rowData.at(col));
463 else
464 data += qxt_addCsvQuotes(d_ptr.quoteMode, QString());;
465 }
466 stream << data << endl;
467 }
468 stream << flush;
469 dest->close();
470 }
471
472 /*!
473 \overload
474
475 Outputs the content of the model as a CSV file to the file specified by \a filename using \a codec.
476
477 Fields in the output file will be separated by \a separator. Set \a withHeader to true
478 to output a row of headers at the top of the file.
479 */
480 void QxtCsvModel::toCSV(const QString filename, bool withHeader, QChar separator, QTextCodec* codec) const
481 {
482 QFile dest(filename);
483 toCSV(&dest, withHeader, separator, codec);
484 }
485
486 /*!
487 \reimp
488 */
489 Qt::ItemFlags QxtCsvModel::flags(const QModelIndex& index) const
490 {
491 return Qt::ItemIsEditable | QAbstractTableModel::flags(index);
492 }
493
494 /*!
495 * Returns the current quoting mode.
496 * \sa setQuoteMode
497 */
498 QxtCsvModel::QuoteMode QxtCsvModel::quoteMode() const
499 {
500 return qxt_d().quoteMode;
501 }
502
503 /*!
504 * Sets the current quoting mode. The default quoting mode is BothQuotes | BackslashEscape.
505 *
506 * The quoting mode determines what kinds of quoting is used for reading and writing CSV files.
507 * \sa quoteMode
508 * \sa QuoteOption
509 */
510 void QxtCsvModel::setQuoteMode(QuoteMode mode)
511 {
512 qxt_d().quoteMode = mode;
513 }
514
515 /*!
516 Sets the content of the cell at row \a row and column \a column to \a value.
517
518 \sa text
519 */
520 void QxtCsvModel::setText(int row, int column, const QString& value)
521 {
522 setData(index(row, column), value);
523 }
524
525 /*!
526 Fetches the content of the cell at row \a row and column \a column.
527
528 \sa setText
529 */
530 QString QxtCsvModel::text(int row, int column) const
531 {
532 return data(index(row, column)).toString();
533 }
534
535 /*!
536 Sets the content of the header for column \a column to \a value.
537
538 \sa headerText
539 */
540 void QxtCsvModel::setHeaderText(int column, const QString& value)
541 {
542 setHeaderData(column, Qt::Horizontal, value);
543 }
544
545 /*!
546 Fetches the content of the cell at row \a row and column \a column.
547
548 \sa setText
549 */
550 QString QxtCsvModel::headerText(int column) const
551 {
552 return headerData(column, Qt::Horizontal).toString();
553 }
This site is hosted by Intevation GmbH (Datenschutzerklärung und Impressum | Privacy Policy and Imprint)