Mercurial > retraceit
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 } |