Seamly2D
Code documentation
qxtcsvmodel.cpp
Go to the documentation of this file.
1 /****************************************************************************
2 ** Copyright (c) 2006 - 2011, the LibQxt project.
3 ** See the Qxt AUTHORS file for a list of authors and copyright holders.
4 ** All rights reserved.
5 **
6 ** Redistribution and use in source and binary forms, with or without
7 ** modification, are permitted provided that the following conditions are met:
8 ** * Redistributions of source code must retain the above copyright
9 ** notice, this list of conditions and the following disclaimer.
10 ** * Redistributions in binary form must reproduce the above copyright
11 ** notice, this list of conditions and the following disclaimer in the
12 ** documentation and/or other materials provided with the distribution.
13 ** * Neither the name of the LibQxt project nor the
14 ** names of its contributors may be used to endorse or promote products
15 ** derived from this software without specific prior written permission.
16 **
17 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18 ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 ** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 ** DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
21 ** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 ** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 ** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 ** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 **
28 ** <http://libqxt.org> <foundation@libqxt.org>
29 *****************************************************************************/
30 
31 /*!
32 \class QxtCsvModel
33 \brief The QxtCsvModel class provides a QAbstractTableModel for CSV Files
34  */
35 
36 #include "qxtcsvmodel.h"
37 
38 #include <QFile>
39 #include <QIODevice>
40 #include <QList>
41 #include <QTextStream>
42 
43 #include "../vmisc/diagnostic.h"
44 
45 class QxtCsvModelPrivate : public QxtPrivate<QxtCsvModel>
46 {
47 public:
49  {}
51  virtual ~QxtCsvModelPrivate() Q_DECL_EQ_DEFAULT;
52 
53  QList<QStringList> csvData;
54  QStringList header;
55  int maxColumn;
56  QxtCsvModel::QuoteMode quoteMode;
57 
58 private:
59  Q_DISABLE_COPY(QxtCsvModelPrivate)
60 };
61 
62 QT_WARNING_PUSH
63 QT_WARNING_DISABLE_GCC("-Weffc++")
64 
65 /*!
66  Creates an empty QxtCsvModel with parent \a parent.
67  */
68 QxtCsvModel::QxtCsvModel(QObject *parent) : QAbstractTableModel(parent)
69 {
71 }
72 
73 /*!
74  Creates a QxtCsvModel with the parent \a parent and content loaded from \a file.
75 
76  See \a setSource for information on the \a withHeader and \a separator properties, or
77  if you need control over the quoting method or codec used to parse the file.
78 
79  \sa setSource
80  */
81 QxtCsvModel::QxtCsvModel(QIODevice *file, QObject *parent, bool withHeader, QChar separator)
82  : QAbstractTableModel(parent)
83 {
85  setSource(file, withHeader, separator);
86 }
87 
88 /*!
89  \overload
90 
91  Creates a QxtCsvModel with the parent \a parent and content loaded from \a file.
92 
93  See \a setSource for information on the \a withHeader and \a separator properties, or
94  if you need control over the quoting method or codec used to parse the file.
95 
96  \sa setSource
97  */
98 QxtCsvModel::QxtCsvModel(const QString &filename, QObject *parent, bool withHeader, QChar separator)
99  : QAbstractTableModel(parent)
100 {
102  QFile src(filename);
103  setSource(&src, withHeader, separator);
104 }
105 
107 
108 /*!
109  \reimp
110  */
111 int QxtCsvModel::rowCount(const QModelIndex& parent) const
112 {
113  if (parent.row() != -1 && parent.column() != -1)
114  {
115  return 0;
116  }
117  return qxt_d().csvData.count();
118 }
119 
120 /*!
121  \reimp
122  */
123 int QxtCsvModel::columnCount(const QModelIndex& parent) const
124 {
125  if (parent.row() != -1 && parent.column() != -1)
126  {
127  return 0;
128  }
129  return qxt_d().maxColumn;
130 }
131 
132 /*!
133  \reimp
134  */
135 QVariant QxtCsvModel::data(const QModelIndex& index, int role) const
136 {
137  if (index.parent() != QModelIndex())
138  {
139  return QVariant();
140  }
141  if (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::UserRole)
142  {
143  if (index.row() < 0 || index.column() < 0 || index.row() >= rowCount())
144  {
145  return QVariant();
146  }
147  const QStringList& row = qxt_d().csvData[index.row()];
148  if (index.column() >= row.length())
149  {
150  return QVariant();
151  }
152  return row[index.column()];
153  }
154  return QVariant();
155 }
156 
157 /*!
158  \reimp
159  */
160 QVariant QxtCsvModel::headerData(int section, Qt::Orientation orientation, int role) const
161 {
162  if (section < qxt_d().header.count() && orientation == Qt::Horizontal && (role == Qt::DisplayRole
163  || role == Qt::EditRole
164  || role == Qt::UserRole))
165  {
166  return qxt_d().header[section];
167  }
168  else
169  {
170  return QAbstractTableModel::headerData(section, orientation, role);
171  }
172 }
173 
174 /*!
175  \overload
176 
177  Reads in a CSV file from the provided \a file using \a codec.
178  */
179 void QxtCsvModel::setSource(const QString &filename, bool withHeader, QChar separator, QTextCodec* codec)
180 {
181  QFile src(filename);
182  setSource(&src, withHeader, separator, codec);
183 }
184 
185 /*!
186  Reads in a CSV file from the provided \a file using \a codec.
187 
188  The value of \a separator will be used to delimit fields, subject to the specified \a quoteMode.
189  If \a withHeader is set to true, the first line of the file will be used to populate the model's
190  horizontal header.
191 
192  \sa quoteMode
193  */
194 void QxtCsvModel::setSource(QIODevice *file, bool withHeader, QChar separator, QTextCodec* codec)
195 {
196  QxtCsvModelPrivate* d_ptr = &qxt_d();
197  bool headerSet = !withHeader;
198  if (not file->isOpen())
199  {
200  file->open(QIODevice::ReadOnly);
201  }
202  if (withHeader)
203  {
204  d_ptr->maxColumn = 0;
205  }
206  else
207  {
208  d_ptr->maxColumn = d_ptr->header.size();
209  }
210  d_ptr->csvData.clear();
211  QStringList row;
212  QString field;
213  QChar quote;
214  QChar ch, buffer(0);
215  bool readCR = false;
216  QTextStream stream(file);
217  if (codec)
218  {
219  stream.setCodec(codec);
220  }
221  else
222  {
223  stream.setAutoDetectUnicode(true);
224  }
225  while (not stream.atEnd())
226  {
227  if (buffer != QChar(0))
228  {
229  ch = buffer;
230  buffer = QChar(0);
231  }
232  else
233  {
234  stream >> ch;
235  }
236  if (ch == '\n' && readCR)
237  {
238  continue;
239  }
240  else if (ch == '\r')
241  {
242  readCR = true;
243  }
244  else
245  {
246  readCR = false;
247  }
248  if (ch != separator && (ch.category() == QChar::Separator_Line || ch.category() == QChar::Separator_Paragraph
249  || ch.category() == QChar::Other_Control))
250  {
251  row << field;
252  field.clear();
253  if (not row.isEmpty())
254  {
255  if (not headerSet)
256  {
257  d_ptr->header = row;
258  headerSet = true;
259  }
260  else
261  {
262  d_ptr->csvData.append(row);
263  }
264  if (row.length() > d_ptr->maxColumn)
265  {
266  d_ptr->maxColumn = row.length();
267  }
268  }
269  row.clear();
270  }
271  else if ((d_ptr->quoteMode & DoubleQuote && ch == '"') || (d_ptr->quoteMode & SingleQuote && ch == '\''))
272  {
273  quote = ch;
274  do
275  {
276  stream >> ch;
277  if (ch == '\\' && d_ptr->quoteMode & BackslashEscape)
278  {
279  stream >> ch;
280  }
281  else if (ch == quote)
282  {
283  if (d_ptr->quoteMode & TwoQuoteEscape)
284  {
285  stream >> buffer;
286  if (buffer == quote)
287  {
288  buffer = QChar(0);
289  field.append(ch);
290  continue;
291  }
292  }
293  break;
294  }
295  field.append(ch);
296  } while (!stream.atEnd());
297  }
298  else if (ch == separator)
299  {
300  row << field;
301  field.clear();
302  }
303  else
304  {
305  field.append(ch);
306  }
307  }
308  if (not field.isEmpty())
309  {
310  row << field;
311  }
312  if (not row.isEmpty())
313  {
314  if (not headerSet)
315  {
316  d_ptr->header = row;
317  }
318  else
319  {
320  d_ptr->csvData.append(row);
321  }
322  }
323  file->close();
324 }
325 
326 /*!
327  Sets the horizontal headers of the model to the values provided in \a data.
328  */
329 void QxtCsvModel::setHeaderData(const QStringList& data)
330 {
331  qxt_d().header = data;
332  emit headerDataChanged(Qt::Horizontal, 0, data.count());
333 }
334 
335 /*!
336  \reimp
337  */
338 bool QxtCsvModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant& value, int role)
339 {
340  if (orientation != Qt::Horizontal)
341  {
342  return false; // We don't support the vertical header
343  }
344 
345  if (role != Qt::DisplayRole && role != Qt::EditRole)
346  {
347  return false; // We don't support any other roles
348  }
349 
350  if (section < 0)
351  {
352  return false; // Bogus input
353  }
354 
355  while (section > qxt_d().header.size())
356  {
357  qxt_d().header << QString();
358  }
359  qxt_d().header[section] = value.toString();
360  emit headerDataChanged(Qt::Horizontal, section, section);
361  return true;
362 }
363 
364 /*!
365  \reimp
366  */
367 bool QxtCsvModel::setData(const QModelIndex& index, const QVariant& data, int role)
368 {
369  if (index.parent() != QModelIndex())
370  {
371  return false;
372  }
373 
374  if (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::UserRole)
375  {
376  if (index.row() >= rowCount() || index.column() >= columnCount() || index.row() < 0 || index.column() < 0)
377  {
378  return false;
379  }
380  QStringList& row = qxt_d().csvData[index.row()];
381  while (row.length() <= index.column())
382  {
383  row << QString();
384  }
385  row[index.column()] = data.toString();
386  emit dataChanged(index, index);
387  return true;
388  }
389  return false;
390 }
391 
392 /*!
393  \reimp
394  */
395 bool QxtCsvModel::insertRow(int row, const QModelIndex& parent)
396 {
397  return insertRows(row, 1, parent);
398 }
399 
400 /*!
401  \reimp
402  */
403 bool QxtCsvModel::insertRows(int row, int count, const QModelIndex& parent)
404 {
405  if (parent != QModelIndex() || row < 0)
406  {
407  return false;
408  }
409  emit beginInsertRows(parent, row, row + count);
410  QxtCsvModelPrivate& d_ptr = qxt_d();
411  if (row >= rowCount())
412  {
413  for(int i = 0; i < count; i++)
414  {
415  d_ptr.csvData << QStringList();
416  }
417  }
418  else
419  {
420  for(int i = 0; i < count; i++)
421  {
422  d_ptr.csvData.insert(row, QStringList());
423  }
424  }
425  emit endInsertRows();
426  return true;
427 }
428 
429 /*!
430  \reimp
431  */
432 bool QxtCsvModel::removeRow(int row, const QModelIndex& parent)
433 {
434  return removeRows(row, 1, parent);
435 }
436 
437 /*!
438  \reimp
439  */
440 bool QxtCsvModel::removeRows(int row, int count, const QModelIndex& parent)
441 {
442  if (parent != QModelIndex() || row < 0)
443  {
444  return false;
445  }
446  if (row >= rowCount())
447  {
448  return false;
449  }
450  if (row + count >= rowCount())
451  {
452  count = rowCount() - row;
453  }
454  emit beginRemoveRows(parent, row, row + count);
455  QxtCsvModelPrivate& d_ptr = qxt_d();
456  for (int i = 0;i < count;i++)
457  {
458  d_ptr.csvData.removeAt(row);
459  }
460  emit endRemoveRows();
461  return true;
462 }
463 
464 /*!
465  \reimp
466  */
467 bool QxtCsvModel::insertColumn(int col, const QModelIndex& parent)
468 {
469  return insertColumns(col, 1, parent);
470 }
471 
472 /*!
473  \reimp
474  */
475 bool QxtCsvModel::insertColumns(int col, int count, const QModelIndex& parent)
476 {
477  if (parent != QModelIndex() || col < 0)
478  {
479  return false;
480  }
481  beginInsertColumns(parent, col, col + count - 1);
482  QxtCsvModelPrivate& d_ptr = qxt_d();
483  for (int i = 0; i < rowCount(); i++)
484  {
485  QStringList& row = d_ptr.csvData[i];
486  while (col >= row.length())
487  {
488  row.append(QString());
489  }
490  for (int j = 0; j < count; j++)
491  {
492  row.insert(col, QString());
493  }
494  }
495  for (int i = 0; i < count ;i++)
496  {
497  d_ptr.header.insert(col, QString());
498  }
499  d_ptr.maxColumn += count;
500  endInsertColumns();
501  return true;
502 }
503 
504 /*!
505  \reimp
506  */
507 bool QxtCsvModel::removeColumn(int col, const QModelIndex& parent)
508 {
509  return removeColumns(col, 1, parent);
510 }
511 
512 /*!
513  \reimp
514  */
515 bool QxtCsvModel::removeColumns(int col, int count, const QModelIndex& parent)
516 {
517  if (parent != QModelIndex() || col < 0)
518  {
519  return false;
520  }
521  if (col >= columnCount())
522  {
523  return false;
524  }
525  if (col + count >= columnCount())
526  {
527  count = columnCount() - col;
528  }
529  emit beginRemoveColumns(parent, col, col + count);
530  QxtCsvModelPrivate& d_ptr = qxt_d();
531  for (int i = 0; i < rowCount(); i++)
532  {
533  for (int j = 0; j < count; j++)
534  {
535  d_ptr.csvData[i].removeAt(col);
536  }
537  }
538  for (int i = 0; i < count; i++)
539  {
540  d_ptr.header.removeAt(col);
541  }
542  emit endRemoveColumns();
543  return true;
544 }
545 
546 static QString qxt_addCsvQuotes(QxtCsvModel::QuoteMode mode, QString field)
547 {
548  bool addDoubleQuotes = ((mode & QxtCsvModel::DoubleQuote) && field.contains('"'));
549  bool addSingleQuotes = ((mode & QxtCsvModel::SingleQuote) && field.contains('\''));
550  bool quoteField = (mode & QxtCsvModel::AlwaysQuoteOutput) || addDoubleQuotes || addSingleQuotes;
551  if (quoteField && !addDoubleQuotes && !addSingleQuotes)
552  {
553  if (mode & QxtCsvModel::DoubleQuote)
554  {
555  addDoubleQuotes = true;
556  }
557  else if(mode & QxtCsvModel::SingleQuote)
558  {
559  addSingleQuotes = true;
560  }
561  }
562  if (mode & QxtCsvModel::BackslashEscape)
563  {
564  if (addDoubleQuotes)
565  {
566  return '"' + field.replace("\\", "\\\\").replace("\"", "\\\"") + '"';
567  }
568  if (addSingleQuotes)
569  {
570  return '\'' + field.replace("\\", "\\\\").replace("'", "\\'") + '\'';
571  }
572  }
573  else
574  {
575  if (addDoubleQuotes)
576  {
577  return '"' + field.replace("\"", "\"\"") + '"';
578  }
579  if (addSingleQuotes)
580  {
581  return '\'' + field.replace("'", "''") + '\'';
582  }
583  }
584  return field;
585 }
586 
587 /*!
588  Outputs the content of the model as a CSV file to the device \a dest using \a codec.
589 
590  Fields in the output file will be separated by \a separator. Set \a withHeader to true
591  to output a row of headers at the top of the file.
592  */
593 // cppcheck-suppress funcArgNamesDifferent
594 void QxtCsvModel::toCSV(QIODevice* dest, bool withHeader, QChar separator, QTextCodec* codec) const
595 {
596  const QxtCsvModelPrivate& d_ptr = qxt_d();
597  int row, col, rows, cols;
598  rows = rowCount();
599  cols = columnCount();
600  QString data;
601  if (not dest->isOpen())
602  {
603  dest->open(QIODevice::WriteOnly | QIODevice::Truncate);
604  }
605  QTextStream stream(dest);
606  if (codec)
607  {
608  stream.setCodec(codec);
609  }
610  if (withHeader)
611  {
612  data = "";
613  for (col = 0; col < cols; ++col)
614  {
615  if (col > 0)
616  {
617  data += separator;
618  }
619  data += qxt_addCsvQuotes(d_ptr.quoteMode, d_ptr.header.at(col));
620  }
621  stream << data << Qt::endl;
622  }
623  for (row = 0; row < rows; ++row)
624  {
625  const QStringList& rowData = d_ptr.csvData[row];
626  data = "";
627  for (col = 0; col < cols; ++col)
628  {
629  if (col > 0)
630  {
631  data += separator;
632  }
633  if (col < rowData.length())
634  {
635  data += qxt_addCsvQuotes(d_ptr.quoteMode, rowData.at(col));
636  }
637  else
638  {
639  data += qxt_addCsvQuotes(d_ptr.quoteMode, QString());
640  }
641  }
642  stream << data << Qt::endl;
643  }
644  stream << Qt::flush;
645  dest->close();
646 }
647 
648 /*!
649  \overload
650 
651  Outputs the content of the model as a CSV file to the file specified by \a filename using \a codec.
652 
653  Fields in the output file will be separated by \a separator. Set \a withHeader to true
654  to output a row of headers at the top of the file.
655  */
656 void QxtCsvModel::toCSV(const QString &filename, bool withHeader, QChar separator, QTextCodec* codec) const
657 {
658  QFile dest(filename);
659  toCSV(&dest, withHeader, separator, codec);
660 }
661 
662 /*!
663  \reimp
664  */
665 Qt::ItemFlags QxtCsvModel::flags(const QModelIndex& index) const
666 {
667  return Qt::ItemIsEditable | QAbstractTableModel::flags(index);
668 }
669 
670 /*!
671  * Returns the current quoting mode.
672  * \sa setQuoteMode
673  */
674 QxtCsvModel::QuoteMode QxtCsvModel::quoteMode() const
675 {
676  return qxt_d().quoteMode;
677 }
678 
679 /*!
680  * Sets the current quoting mode. The default quoting mode is BothQuotes | BackslashEscape.
681  *
682  * The quoting mode determines what kinds of quoting is used for reading and writing CSV files.
683  * \sa quoteMode
684  * \sa QuoteOption
685  */
686 void QxtCsvModel::setQuoteMode(QuoteMode mode)
687 {
688  qxt_d().quoteMode = mode;
689 }
690 
691 /*!
692  Sets the content of the cell at row \a row and column \a column to \a value.
693 
694  \sa text
695  */
696 void QxtCsvModel::setText(int row, int column, const QString& value)
697 {
698 // cppcheck-suppress indexCalled
699  setData(index(row, column), value);
700 }
701 
702 /*!
703  Fetches the content of the cell at row \a row and column \a column.
704 
705  \sa setText
706  */
707 QString QxtCsvModel::text(int row, int column) const
708 {
709 // cppcheck-suppress indexCalled
710  return data(index(row, column)).toString();
711 }
712 
713 /*!
714  Sets the content of the header for column \a column to \a value.
715 
716  \sa headerText
717  */
718 void QxtCsvModel::setHeaderText(int column, const QString& value)
719 {
720  setHeaderData(column, Qt::Horizontal, value);
721 }
722 
723 /*!
724  Fetches the content of the cell at row \a row and column \a column.
725 
726  \sa setText
727  */
728 QString QxtCsvModel::headerText(int column) const
729 {
730  return headerData(column, Qt::Horizontal).toString();
731 }
QxtCsvModel::QuoteMode quoteMode
Definition: qxtcsvmodel.cpp:56
QStringList header
Definition: qxtcsvmodel.cpp:54
QList< QStringList > csvData
Definition: qxtcsvmodel.cpp:53
The QxtCsvModel class provides a QAbstractTableModel for CSV Files.
Definition: qxtcsvmodel.h:54
virtual bool insertRows(int row, int count, const QModelIndex &parent=QModelIndex()) Q_DECL_OVERRIDE
\reimp
void setText(int row, int column, const QString &value)
Sets the content of the cell at row row and column column to value.
bool insertColumn(int col, const QModelIndex &parent=QModelIndex())
\reimp
void setHeaderText(int column, const QString &value)
Sets the content of the header for column column to value.
QString headerText(int column) const
Fetches the content of the cell at row row and column column.
bool removeColumn(int col, const QModelIndex &parent=QModelIndex())
\reimp
virtual bool insertColumns(int col, int count, const QModelIndex &parent=QModelIndex()) Q_DECL_OVERRIDE
\reimp
virtual bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role=Qt::DisplayRole) Q_DECL_OVERRIDE
\reimp
void setSource(QIODevice *file, bool withHeader=false, QChar separator=',', QTextCodec *codec=nullptr)
Reads in a CSV file from the provided file using codec.
bool removeRow(int row, const QModelIndex &parent=QModelIndex())
\reimp
QuoteMode quoteMode() const
Returns the current quoting mode.
virtual Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE
\reimp
bool insertRow(int row, const QModelIndex &parent=QModelIndex())
\reimp
virtual QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const Q_DECL_OVERRIDE
\reimp
void setQuoteMode(QuoteMode mode)
Sets the current quoting mode.
virtual int columnCount(const QModelIndex &parent=QModelIndex()) const Q_DECL_OVERRIDE
\reimp
virtual bool removeColumns(int col, int count, const QModelIndex &parent=QModelIndex()) Q_DECL_OVERRIDE
\reimp
virtual bool setData(const QModelIndex &index, const QVariant &data, int role=Qt::EditRole) Q_DECL_OVERRIDE
\reimp
virtual int rowCount(const QModelIndex &parent=QModelIndex()) const Q_DECL_OVERRIDE
\reimp
QString text(int row, int column) const
Fetches the content of the cell at row row and column column.
virtual QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const Q_DECL_OVERRIDE
\reimp
void toCSV(QIODevice *file, bool withHeader=false, QChar separator=',', QTextCodec *codec=nullptr) const
Outputs the content of the model as a CSV file to the device dest using codec.
QxtCsvModel(QObject *parent=nullptr)
Creates an empty QxtCsvModel with parent parent.
Definition: qxtcsvmodel.cpp:68
virtual bool removeRows(int row, int count, const QModelIndex &parent=QModelIndex()) Q_DECL_OVERRIDE
\reimp
QxtPrivateInterface< QxtCsvModel, QxtCsvModelPrivate > qxt_d
Definition: qxtcsvmodel.h:118
#define QXT_DECLARE_PUBLIC(PUB)
Definition: def.h:542
#define QXT_INIT_PRIVATE(PUB)
Definition: def.h:543
static QString qxt_addCsvQuotes(QxtCsvModel::QuoteMode mode, QString field)