File indexing completed on 2024-12-22 03:35:43

0001 /*
0002     File                 : AsciiFilter.cpp
0003     Project              : LabPlot
0004     Description          : ASCII I/O-filter
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2009-2022 Stefan Gerlach <stefan.gerlach@uni.kn>
0007     SPDX-FileCopyrightText: 2009-2023 Alexander Semke <alexander.semke@web.de>
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 #include "backend/datasources/filters/AsciiFilter.h"
0012 #include "backend/core/Project.h"
0013 #include "backend/core/column/Column.h"
0014 #include "backend/datasources/LiveDataSource.h"
0015 #include "backend/datasources/filters/AsciiFilterPrivate.h"
0016 #include "backend/lib/XmlStreamReader.h"
0017 #include "backend/lib/macros.h"
0018 #include "backend/lib/trace.h"
0019 #include "backend/matrix/Matrix.h"
0020 #include "backend/worksheet/plots/cartesian/CartesianPlot.h"
0021 #include "backend/worksheet/plots/cartesian/XYCurve.h"
0022 
0023 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
0024 #include "3rdparty/stringtokenizer/qstringtokenizer.h"
0025 #endif
0026 
0027 #ifdef HAVE_MQTT
0028 #include "backend/datasources/MQTTClient.h"
0029 #include "backend/datasources/MQTTTopic.h"
0030 #endif
0031 
0032 #include <KCompressionDevice>
0033 #include <KLocalizedString>
0034 #include <QDateTime>
0035 
0036 #if defined(Q_OS_LINUX) || defined(Q_OS_BSD4)
0037 #include <QProcess>
0038 #include <QStandardPaths>
0039 #endif
0040 
0041 #include <QRegularExpression>
0042 
0043 /*!
0044 \class AsciiFilter
0045 \brief Manages the import/export of data organized as columns (vectors) from/to an ASCII-file.
0046 
0047 \ingroup datasources
0048 */
0049 AsciiFilter::AsciiFilter()
0050     : AbstractFileFilter(FileType::Ascii)
0051     , d(new AsciiFilterPrivate(this)) {
0052 }
0053 
0054 AsciiFilter::~AsciiFilter() = default;
0055 
0056 /*!
0057   reads the content of the device \c device.
0058 */
0059 void AsciiFilter::readDataFromDevice(QIODevice& device, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) {
0060     d->readDataFromDevice(device, dataSource, importMode, lines);
0061 }
0062 
0063 void AsciiFilter::readFromLiveDeviceNotFile(QIODevice& device, AbstractDataSource* dataSource) {
0064     d->readFromLiveDevice(device, dataSource);
0065 }
0066 
0067 qint64 AsciiFilter::readFromLiveDevice(QIODevice& device, AbstractDataSource* dataSource, qint64 from) {
0068     return d->readFromLiveDevice(device, dataSource, from);
0069 }
0070 
0071 #ifdef HAVE_MQTT
0072 QVector<QStringList> AsciiFilter::preview(const QString& message) {
0073     return d->preview(message);
0074 }
0075 
0076 /*!
0077   reads the content of a message received by the topic.
0078 */
0079 void AsciiFilter::readMQTTTopic(const QString& message, AbstractDataSource* dataSource) {
0080     d->readMQTTTopic(message, dataSource);
0081 }
0082 
0083 /*!
0084   After the MQTTTopic is loaded, prepares the filter for reading.
0085 */
0086 void AsciiFilter::setPreparedForMQTT(bool prepared, MQTTTopic* topic, const QString& separator) {
0087     d->setPreparedForMQTT(prepared, topic, separator);
0088 }
0089 #endif
0090 
0091 /*!
0092   returns the separator used by the filter.
0093 */
0094 QString AsciiFilter::separator() const {
0095     return d->separator();
0096 }
0097 
0098 /*!
0099   returns the separator used by the filter.
0100 */
0101 int AsciiFilter::isPrepared() {
0102     return d->isPrepared();
0103 }
0104 
0105 /*!
0106   reads the content of the file \c fileName.
0107 */
0108 void AsciiFilter::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) {
0109     d->readDataFromFile(fileName, dataSource, importMode);
0110 }
0111 
0112 QVector<QStringList> AsciiFilter::preview(const QString& fileName, int lines) {
0113     return d->preview(fileName, lines);
0114 }
0115 
0116 QVector<QStringList> AsciiFilter::preview(QIODevice& device) {
0117     return d->preview(device);
0118 }
0119 
0120 /*!
0121 writes the content of the data source \c dataSource to the file \c fileName.
0122 */
0123 void AsciiFilter::write(const QString& fileName, AbstractDataSource* dataSource) {
0124     d->write(fileName, dataSource);
0125 }
0126 
0127 /*!
0128   returns the list with the names of all saved
0129   (system wide or user defined) filter settings.
0130 */
0131 QStringList AsciiFilter::predefinedFilters() {
0132     // TODO?
0133     return {};
0134 }
0135 
0136 /*!
0137   returns the list of all predefined separator characters.
0138 */
0139 QStringList AsciiFilter::separatorCharacters() {
0140     return (QStringList() << QStringLiteral("auto") << QStringLiteral("TAB") << QStringLiteral("SPACE") << QStringLiteral(",") << QStringLiteral(";")
0141                           << QStringLiteral(":") << QStringLiteral(",TAB") << QStringLiteral(";TAB") << QStringLiteral(":TAB") << QStringLiteral(",SPACE")
0142                           << QStringLiteral(";SPACE") << QStringLiteral(":SPACE") << QStringLiteral("2xSPACE") << QStringLiteral("3xSPACE")
0143                           << QStringLiteral("4xSPACE") << QStringLiteral("2xTAB"));
0144 }
0145 
0146 /*!
0147 returns the list of all predefined comment characters.
0148 */
0149 QStringList AsciiFilter::commentCharacters() {
0150     return (QStringList() << QStringLiteral("#") << QStringLiteral("!") << QStringLiteral("//") << QStringLiteral("+") << QStringLiteral("c")
0151                           << QStringLiteral(":") << QStringLiteral(";"));
0152 }
0153 
0154 /*!
0155 returns the list of all predefined data types.
0156 */
0157 QStringList AsciiFilter::dataTypes() {
0158     const QMetaObject& mo = AbstractColumn::staticMetaObject;
0159     const QMetaEnum& me = mo.enumerator(mo.indexOfEnumerator("ColumnMode"));
0160     QStringList list;
0161     for (int i = 0; i <= 100; ++i) // me.keyCount() does not work because we have holes in enum
0162         if (me.valueToKey(i))
0163             list << QLatin1String(me.valueToKey(i));
0164     return list;
0165 }
0166 
0167 QString AsciiFilter::fileInfoString(const QString& fileName) {
0168     QString info(i18n("Number of columns: %1", AsciiFilter::columnNumber(fileName)));
0169     info += QStringLiteral("<br>");
0170     info += i18n("Number of lines: %1", AsciiFilter::lineNumber(fileName));
0171     return info;
0172 }
0173 
0174 /*!
0175     returns the number of columns in the file \c fileName.
0176 */
0177 int AsciiFilter::columnNumber(const QString& fileName, const QString& separator) {
0178     KCompressionDevice device(fileName);
0179     if (!device.open(QIODevice::ReadOnly)) {
0180         DEBUG(Q_FUNC_INFO << ", Could not open file " << STDSTRING(fileName) << " for determining number of columns");
0181         return -1;
0182     }
0183 
0184     QString line = QLatin1String(device.readLine());
0185     line.remove(QRegularExpression(QStringLiteral("[\\n\\r]")));
0186 
0187     QStringList lineStringList;
0188     if (separator.length() > 0)
0189         lineStringList = line.split(separator);
0190     else
0191         lineStringList = line.split(QRegularExpression(QStringLiteral("\\s+")));
0192     DEBUG(Q_FUNC_INFO << ", number of columns : " << lineStringList.size());
0193 
0194     return lineStringList.size();
0195 }
0196 
0197 size_t AsciiFilter::lineNumber(const QString& fileName, const size_t maxLines) {
0198     KCompressionDevice device(fileName);
0199 
0200     if (!device.open(QIODevice::ReadOnly)) {
0201         DEBUG(Q_FUNC_INFO << ", Could not open file " << STDSTRING(fileName) << " to determine number of lines");
0202 
0203         return 0;
0204     }
0205     //  if (!device.canReadLine())
0206     //      return -1;
0207 
0208     size_t lineCount = 0;
0209 #if defined(Q_OS_LINUX) || defined(Q_OS_BSD4)
0210     if (maxLines == std::numeric_limits<std::size_t>::max()) { // only when reading all lines
0211         // on Linux and BSD use wc, if available, which is much faster than counting lines in the file
0212         DEBUG(Q_FUNC_INFO << ", using wc to count lines")
0213         const QString wcFullPath = QStandardPaths::findExecutable(QStringLiteral("wc"));
0214         if (device.compressionType() == KCompressionDevice::None && !wcFullPath.isEmpty()) {
0215             QProcess wc;
0216             wc.start(wcFullPath, QStringList() << QStringLiteral("-l") << fileName);
0217             size_t lineCount = 0;
0218             while (wc.waitForReadyRead()) {
0219                 QString line = QLatin1String(wc.readLine());
0220                 // wc on macOS has leading spaces: use SkipEmptyParts
0221 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
0222                 lineCount = line.split(QLatin1Char(' '), Qt::SkipEmptyParts).at(0).toInt();
0223 #else
0224                 lineCount = line.split(QLatin1Char(' '), QString::SkipEmptyParts).at(0).toInt();
0225 #endif
0226             }
0227             return lineCount;
0228         }
0229     }
0230 #endif
0231 
0232     while (!device.atEnd()) {
0233         if (lineCount >= maxLines) // stop when maxLines available
0234             return lineCount;
0235         device.readLine();
0236         lineCount++;
0237     }
0238 
0239     return lineCount;
0240 }
0241 
0242 /*!
0243   returns the number of lines in the device \c device (limited by maxLines) and 0 if sequential.
0244   resets the position to 0!
0245 */
0246 size_t AsciiFilter::lineNumber(QIODevice& device, const size_t maxLines) const {
0247     if (device.isSequential())
0248         return 0;
0249     //  if (!device.canReadLine())
0250     //      DEBUG("WARNING in AsciiFilter::lineNumber(): device cannot 'readLine()' but using it anyway.");
0251 
0252     size_t lineCount = 0;
0253     device.seek(0);
0254     if (d->readingFile)
0255         lineCount = lineNumber(d->readingFileName, maxLines);
0256     else {
0257         while (!device.atEnd()) {
0258             if (lineCount >= maxLines) // stop when maxLines available
0259                 break;
0260             device.readLine();
0261             lineCount++;
0262         }
0263     }
0264     device.seek(0);
0265 
0266     return lineCount;
0267 }
0268 
0269 void AsciiFilter::setCommentCharacter(const QString& s) {
0270     d->commentCharacter = s;
0271 }
0272 QString AsciiFilter::commentCharacter() const {
0273     return d->commentCharacter;
0274 }
0275 
0276 void AsciiFilter::setSeparatingCharacter(const QString& s) {
0277     d->separatingCharacter = s;
0278 }
0279 QString AsciiFilter::separatingCharacter() const {
0280     return d->separatingCharacter;
0281 }
0282 
0283 void AsciiFilter::setDateTimeFormat(const QString& f) {
0284     d->dateTimeFormat = f;
0285 }
0286 QString AsciiFilter::dateTimeFormat() const {
0287     return d->dateTimeFormat;
0288 }
0289 
0290 void AsciiFilter::setNumberFormat(QLocale::Language lang) {
0291     d->numberFormat = lang;
0292     d->locale = QLocale(lang);
0293 }
0294 QLocale::Language AsciiFilter::numberFormat() const {
0295     return d->numberFormat;
0296 }
0297 
0298 void AsciiFilter::setAutoModeEnabled(const bool b) {
0299     d->autoModeEnabled = b;
0300 }
0301 bool AsciiFilter::isAutoModeEnabled() const {
0302     return d->autoModeEnabled;
0303 }
0304 
0305 void AsciiFilter::setHeaderEnabled(const bool b) {
0306     d->headerEnabled = b;
0307 }
0308 bool AsciiFilter::isHeaderEnabled() const {
0309     return d->headerEnabled;
0310 }
0311 
0312 // TODO: this setter modifies also startRow which is not clear for external consumers,
0313 // the default value of headerLine is 1, same for startRow. if we don't call setHeaderLine(1),
0314 // assuming the default value is already set to 1 anyway, startRow is kept at 1 and not set to 2
0315 // and the file is read wrongly. This forces us to always call this function, like in
0316 // DatasetHandler::configureFilter() or to call it after setStartRow() was called like in
0317 // ImportFileWidget::currentFileFilter().
0318 // We shouldn't be dependent on the order of these calls and there shouldn't be any reason
0319 // to call this function to set the default value again.
0320 // -> redesign the APIs.
0321 void AsciiFilter::setHeaderLine(int line) {
0322     d->headerLine = line;
0323     DEBUG(Q_FUNC_INFO << ", line = " << line << ", startRow = " << d->startRow)
0324     d->startRow = line + 1;
0325     DEBUG(Q_FUNC_INFO << ", new startRow = " << d->startRow)
0326 }
0327 
0328 void AsciiFilter::setSkipEmptyParts(const bool b) {
0329     d->skipEmptyParts = b;
0330 }
0331 bool AsciiFilter::skipEmptyParts() const {
0332     return d->skipEmptyParts;
0333 }
0334 
0335 void AsciiFilter::setCreateIndexEnabled(bool b) {
0336     d->createIndexEnabled = b;
0337 }
0338 
0339 bool AsciiFilter::createIndexEnabled() const {
0340     return d->createIndexEnabled;
0341 }
0342 
0343 void AsciiFilter::setCreateTimestampEnabled(bool b) {
0344     d->createTimestampEnabled = b;
0345 }
0346 
0347 bool AsciiFilter::createTimestampEnabled() const {
0348     return d->createTimestampEnabled;
0349 }
0350 
0351 void AsciiFilter::setSimplifyWhitespacesEnabled(bool b) {
0352     d->simplifyWhitespacesEnabled = b;
0353 }
0354 bool AsciiFilter::simplifyWhitespacesEnabled() const {
0355     return d->simplifyWhitespacesEnabled;
0356 }
0357 
0358 void AsciiFilter::setNaNValueToZero(bool b) {
0359     if (b)
0360         d->nanValue = 0.;
0361     else
0362         d->nanValue = std::numeric_limits<double>::quiet_NaN();
0363 }
0364 bool AsciiFilter::NaNValueToZeroEnabled() const {
0365     return (d->nanValue == 0.);
0366 }
0367 
0368 void AsciiFilter::setRemoveQuotesEnabled(bool b) {
0369     d->removeQuotesEnabled = b;
0370 }
0371 bool AsciiFilter::removeQuotesEnabled() const {
0372     return d->removeQuotesEnabled;
0373 }
0374 
0375 void AsciiFilter::setVectorNames(const QString& s) {
0376     d->columnNames.clear();
0377     if (!s.simplified().isEmpty())
0378         d->columnNames = s.simplified().split(QLatin1Char(' '));
0379 }
0380 void AsciiFilter::setVectorNames(const QStringList& list) {
0381     d->columnNames = list;
0382 }
0383 QStringList AsciiFilter::vectorNames() const {
0384     return d->columnNames;
0385 }
0386 
0387 QVector<AbstractColumn::ColumnMode> AsciiFilter::columnModes() {
0388     return d->columnModes;
0389 }
0390 
0391 void AsciiFilter::setStartRow(const int r) {
0392     DEBUG(Q_FUNC_INFO << " row:" << r)
0393     d->startRow = r;
0394 }
0395 int AsciiFilter::startRow() const {
0396     return d->startRow;
0397 }
0398 
0399 void AsciiFilter::setEndRow(const int r) {
0400     d->endRow = r;
0401 }
0402 int AsciiFilter::endRow() const {
0403     return d->endRow;
0404 }
0405 
0406 void AsciiFilter::setStartColumn(const int c) {
0407     d->startColumn = c;
0408 }
0409 int AsciiFilter::startColumn() const {
0410     return d->startColumn;
0411 }
0412 
0413 void AsciiFilter::setEndColumn(const int c) {
0414     d->endColumn = c;
0415 }
0416 int AsciiFilter::endColumn() const {
0417     return d->endColumn;
0418 }
0419 
0420 // #####################################################################
0421 // ################### Private implementation ##########################
0422 // #####################################################################
0423 AsciiFilterPrivate::AsciiFilterPrivate(AsciiFilter* owner)
0424     : q(owner) {
0425 }
0426 
0427 int AsciiFilterPrivate::isPrepared() {
0428     return m_prepared;
0429 }
0430 
0431 /*!
0432  * \brief Returns the separator used by the filter
0433  * \return
0434  */
0435 QString AsciiFilterPrivate::separator() const {
0436     return m_separator;
0437 }
0438 
0439 // #####################################################################
0440 // ############################# Read ##################################
0441 // #####################################################################
0442 /*!
0443  * returns -1 if the device couldn't be opened, 1 if the current read position in the device is at the end and 0 otherwise.
0444  */
0445 int AsciiFilterPrivate::prepareDeviceToRead(QIODevice& device, const size_t maxLines) {
0446     DEBUG(Q_FUNC_INFO << ", is sequential = " << device.isSequential() << ", can readLine = " << device.canReadLine());
0447 
0448     if (!device.open(QIODevice::ReadOnly))
0449         return -1;
0450 
0451     if (device.atEnd() && !device.isSequential()) // empty file
0452         return 1;
0453 
0454     // NEW method
0455     // 1. First read header (vector names) at given headerLine (counting comment lines)
0456     // 2. Go further to first data line (at given startRow)
0457     // startRow==1: first row after header, etc.
0458 
0459     /////////////////////////////////////////////////////////////////
0460     DEBUG(Q_FUNC_INFO << ", headerEnabled = " << headerEnabled << ", header line: " << headerLine << ", start row: " << startRow)
0461 
0462     QString firstLine;
0463     if (headerEnabled && headerLine) {
0464         // go to header line (counting comment lines)
0465         for (int l = 0; l < headerLine; l++) {
0466             firstLine = getLine(device);
0467             DEBUG(Q_FUNC_INFO << ", first line (header) = \"" << STDSTRING(firstLine) << "\"");
0468         }
0469     } else { // read first data line (skipping comments)
0470         if (!commentCharacter.isEmpty()) {
0471             do {
0472                 firstLine = getLine(device);
0473             } while (firstLine.startsWith(commentCharacter) || firstLine.simplified().isEmpty());
0474         } else
0475             firstLine = QLatin1String(device.readLine());
0476     }
0477 
0478     firstLine.remove(QRegularExpression(QStringLiteral("[\\n\\r]"))); // remove any newline
0479     if (removeQuotesEnabled)
0480         firstLine.remove(QLatin1Char('"'));
0481     QDEBUG(Q_FUNC_INFO << ", first line = " << firstLine);
0482 
0483     // determine separator and split first line
0484     QStringList firstLineStringList;
0485     if (separatingCharacter == QLatin1String("auto")) {
0486         DEBUG(Q_FUNC_INFO << ", using AUTOMATIC separator");
0487         if (firstLine.indexOf(QLatin1Char('\t')) != -1) {
0488             // in case we have a mix of tabs and spaces in the header, give the tab character preference
0489             m_separator = QLatin1Char('\t');
0490             firstLineStringList = split(firstLine, false);
0491         } else {
0492             firstLineStringList = split(firstLine);
0493 
0494             if (!firstLineStringList.isEmpty()) {
0495                 int length1 = firstLineStringList.at(0).length();
0496                 if (firstLineStringList.size() > 1)
0497                     m_separator = firstLine.mid(length1, 1);
0498                 else { // no spaces, use comma as default (CSV) and split
0499                     m_separator = QLatin1Char(',');
0500                     firstLineStringList = split(firstLine, false);
0501                 }
0502             }
0503         }
0504     } else { // use given separator
0505         DEBUG(Q_FUNC_INFO << ", using GIVEN separator: " << STDSTRING(m_separator))
0506         // replace symbolic "TAB" with '\t'
0507         m_separator = separatingCharacter.replace(QLatin1String("2xTAB"), QLatin1String("\t\t"), Qt::CaseInsensitive);
0508         m_separator = separatingCharacter.replace(QLatin1String("TAB"), QLatin1String("\t"), Qt::CaseInsensitive);
0509         // replace symbolic "SPACE" with ' '
0510         m_separator = m_separator.replace(QLatin1String("2xSPACE"), QLatin1String("  "), Qt::CaseInsensitive);
0511         m_separator = m_separator.replace(QLatin1String("3xSPACE"), QLatin1String("   "), Qt::CaseInsensitive);
0512         m_separator = m_separator.replace(QLatin1String("4xSPACE"), QLatin1String("    "), Qt::CaseInsensitive);
0513         m_separator = m_separator.replace(QLatin1String("SPACE"), QLatin1String(" "), Qt::CaseInsensitive);
0514         firstLineStringList = split(firstLine, false);
0515     }
0516     QDEBUG(Q_FUNC_INFO << ", separator: \'" << m_separator << '\'');
0517     DEBUG(Q_FUNC_INFO << ", number of columns: " << firstLineStringList.size());
0518     QDEBUG(Q_FUNC_INFO << ", first line split: " << firstLineStringList);
0519 
0520     // remove whitespaces at start and end
0521     if (simplifyWhitespacesEnabled) {
0522         for (auto& s : firstLineStringList)
0523             s = s.simplified();
0524     }
0525 
0526     if (headerEnabled && headerLine) { // use first line to name vectors (starting from startColumn)
0527         columnNames = firstLineStringList.mid(startColumn - 1);
0528         QDEBUG(Q_FUNC_INFO << ", COLUMN NAMES: " << columnNames);
0529     }
0530 
0531     // set range to read
0532     if (endColumn == -1) {
0533         if ((headerEnabled && headerLine) || columnNames.size() == 0)
0534             endColumn = firstLineStringList.size(); // last column
0535         else // number of column names provided in the import dialog (not more than the maximum number of columns in the file)
0536             endColumn = std::min(columnNames.size(), firstLineStringList.size());
0537     }
0538 
0539     if (endColumn < startColumn)
0540         m_actualCols = 0;
0541     else
0542         m_actualCols = endColumn - startColumn + 1;
0543 
0544     // add time stamp and index column
0545     if (createTimestampEnabled) {
0546         columnNames.prepend(i18n("Timestamp"));
0547         m_actualCols++;
0548     }
0549     if (createIndexEnabled) {
0550         columnNames.prepend(i18n("Index"));
0551         m_actualCols++;
0552     }
0553     QDEBUG(Q_FUNC_INFO << ", ALL COLUMN NAMES: " << columnNames);
0554 
0555     /////////////////////////////////////////////////////////////////
0556 
0557     // navigate to the line where we asked to start reading from
0558     DEBUG(Q_FUNC_INFO << ", Skipping " << startRow - 1 << " line(s)");
0559     for (int i = 0; i < startRow - 1; ++i) {
0560         DEBUG(Q_FUNC_INFO << ", skipping line: " << STDSTRING(firstLine));
0561         firstLine = getLine(device);
0562     }
0563 
0564     // DEBUG(Q_FUNC_INFO << ", device position after first line and comments = " << device.pos());
0565     // valgrind: Conditional jump or move depends on uninitialised value(s)
0566     firstLine.remove(QRegularExpression(QStringLiteral("[\\n\\r]"))); // remove any newline
0567 
0568     if (removeQuotesEnabled)
0569         firstLine = firstLine.remove(QLatin1Char('"'));
0570     DEBUG(Q_FUNC_INFO << ", Actual first line: \'" << STDSTRING(firstLine) << '\'');
0571 
0572     m_actualStartRow = startRow;
0573 
0574     // TEST: readline-seek-readline fails
0575     /*  qint64 testpos = device.pos();
0576         DEBUG("read data line @ pos " << testpos << " : " << STDSTRING(device.readLine()));
0577         device.seek(testpos);
0578         testpos = device.pos();
0579         DEBUG("read data line again @ pos " << testpos << "  : " << STDSTRING(device.readLine()));
0580     */
0581     /////////////////////////////////////////////////////////////////
0582 
0583     // parse first data line to determine data type for each column
0584     firstLineStringList = split(firstLine, false);
0585 
0586     DEBUG("actual start row = " << m_actualStartRow)
0587     QDEBUG("firstLineStringList = " << firstLineStringList)
0588 
0589     columnModes.resize(m_actualCols);
0590     int col = 0;
0591     if (createIndexEnabled) {
0592         columnModes[0] = AbstractColumn::ColumnMode::Integer;
0593         ++col;
0594     }
0595 
0596     if (createTimestampEnabled) {
0597         columnModes[col] = AbstractColumn::ColumnMode::DateTime;
0598         ++col;
0599     }
0600 
0601     for (auto& valueString : firstLineStringList) { // parse columns available in first data line
0602         const int index = col - startColumn + 1;
0603         if (index < (int)createIndexEnabled + (int)createTimestampEnabled) {
0604             col++;
0605             continue;
0606         }
0607         if (index == m_actualCols)
0608             break;
0609 
0610         if (simplifyWhitespacesEnabled)
0611             valueString = valueString.simplified();
0612         if (removeQuotesEnabled)
0613             valueString.remove(QLatin1Char('"'));
0614         QDEBUG("value string = " << valueString)
0615         columnModes[index] = AbstractFileFilter::columnMode(valueString, dateTimeFormat, numberFormat);
0616         col++;
0617     }
0618 #ifndef NDEBUG
0619     for (const auto mode : columnModes)
0620         DEBUG(Q_FUNC_INFO << ", column mode = " << ENUM_TO_STRING(AbstractColumn, ColumnMode, mode));
0621 #endif
0622 
0623     // parsing more lines to better determine data types
0624     DEBUG(Q_FUNC_INFO << ", Parsing more lines to determine data types")
0625     for (unsigned int i = 0; i < m_dataTypeLines; ++i) {
0626         if (device.atEnd()) // EOF reached
0627             break;
0628         firstLineStringList = getLineString(device);
0629 
0630         col = (int)createIndexEnabled + (int)createTimestampEnabled;
0631 
0632         for (auto& valueString : firstLineStringList) {
0633             const int index{col - startColumn + 1};
0634             if (index < (int)createIndexEnabled + (int)createTimestampEnabled) {
0635                 col++;
0636                 continue;
0637             }
0638             if (index == m_actualCols)
0639                 break;
0640 
0641             if (simplifyWhitespacesEnabled)
0642                 valueString = valueString.simplified();
0643             if (removeQuotesEnabled)
0644                 valueString.remove(QLatin1Char('"'));
0645             auto mode = AbstractFileFilter::columnMode(valueString, dateTimeFormat, numberFormat);
0646 
0647             // numeric: integer -> numeric
0648             if (mode == AbstractColumn::ColumnMode::Double && columnModes[index] == AbstractColumn::ColumnMode::Integer)
0649                 columnModes[index] = mode;
0650             // text: non text -> text
0651             if (mode == AbstractColumn::ColumnMode::Text && columnModes[index] != AbstractColumn::ColumnMode::Text)
0652                 columnModes[index] = mode;
0653             // numeric: text -> numeric/integer
0654             if (mode != AbstractColumn::ColumnMode::Text && columnModes[index] == AbstractColumn::ColumnMode::Text)
0655                 columnModes[index] = mode;
0656             col++;
0657         }
0658     }
0659 #ifndef NDEBUG
0660     for (const auto mode : columnModes)
0661         DEBUG(Q_FUNC_INFO << ", column mode (after checking more lines) = " << ENUM_TO_STRING(AbstractColumn, ColumnMode, mode));
0662 #endif
0663 
0664     // ATTENTION: This resets the position in the device to 0
0665     m_actualRows = (int)q->lineNumber(device, maxLines);
0666     DEBUG(Q_FUNC_INFO << ", m_actualRows: " << m_actualRows << ", startRow: " << startRow << ", endRow: " << endRow)
0667 
0668     DEBUG(Q_FUNC_INFO << ", headerEnabled = " << headerEnabled << ", headerLine = " << headerLine << ", m_actualStartRow = " << m_actualStartRow)
0669     if ((!headerEnabled || headerLine < 1) && startRow <= 2 && m_actualStartRow > 1) // take header line
0670         m_actualStartRow--;
0671 
0672     const int actualEndRow = (endRow == -1 || endRow > m_actualRows) ? m_actualRows : endRow;
0673     if (actualEndRow >= m_actualStartRow)
0674         m_actualRows = actualEndRow - m_actualStartRow + 1;
0675     else
0676         m_actualRows = 0;
0677 
0678     DEBUG("start/end column: " << startColumn << ' ' << endColumn);
0679     DEBUG("start/end row: " << m_actualStartRow << ' ' << actualEndRow);
0680     DEBUG("actual cols/rows (w/o header): " << m_actualCols << ' ' << m_actualRows);
0681 
0682     if (m_actualRows == 0 && !device.isSequential())
0683         return 1;
0684 
0685     return 0;
0686 }
0687 
0688 /*!
0689     reads the content of the file \c fileName to the data source \c dataSource. Uses the settings defined in the data source.
0690 */
0691 void AsciiFilterPrivate::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) {
0692     //  DEBUG(Q_FUNC_INFO << ", fileName = \'" << STDSTRING(fileName) << "\', dataSource = "
0693     //        << dataSource << ", mode = " << ENUM_TO_STRING(AbstractFileFilter, ImportMode, importMode));
0694 
0695     // dirty hack: set readingFile and readingFileName in order to know in lineNumber(QIODevice)
0696     // that we're reading from a file and to benefit from much faster wc on linux
0697     // TODO: redesign the APIs and remove this later
0698     readingFile = true;
0699     readingFileName = fileName;
0700     KCompressionDevice device(fileName);
0701     readDataFromDevice(device, dataSource, importMode);
0702     readingFile = false;
0703 }
0704 
0705 qint64 AsciiFilterPrivate::readFromLiveDevice(QIODevice& device, AbstractDataSource* dataSource, qint64 from) {
0706     DEBUG(Q_FUNC_INFO << ", bytes available = " << device.bytesAvailable() << ", from = " << from);
0707     if (device.bytesAvailable() <= 0) {
0708         DEBUG(" No new data available");
0709         return 0;
0710     }
0711 
0712     // TODO: may be also a matrix?
0713     auto* spreadsheet = dynamic_cast<LiveDataSource*>(dataSource);
0714     if (!spreadsheet)
0715         return 0;
0716 
0717     if (spreadsheet->sourceType() != LiveDataSource::SourceType::FileOrPipe)
0718         if (device.isSequential() && device.bytesAvailable() < (int)sizeof(quint16))
0719             return 0;
0720 
0721     if (!m_prepared) {
0722         DEBUG(Q_FUNC_INFO << ", Preparing ..");
0723 
0724         switch (spreadsheet->sourceType()) {
0725         case LiveDataSource::SourceType::FileOrPipe: {
0726             const int deviceError = prepareDeviceToRead(device);
0727             if (deviceError != 0) {
0728                 DEBUG(Q_FUNC_INFO << ", Device ERROR: " << deviceError);
0729                 return 0;
0730             }
0731             break;
0732         }
0733         case LiveDataSource::SourceType::NetworkTCPSocket:
0734         case LiveDataSource::SourceType::NetworkUDPSocket:
0735         case LiveDataSource::SourceType::LocalSocket:
0736         case LiveDataSource::SourceType::SerialPort:
0737             m_actualRows = 1;
0738             m_actualCols = 1;
0739             columnModes.clear();
0740             if (createIndexEnabled) {
0741                 columnModes << AbstractColumn::ColumnMode::Integer;
0742                 columnNames << i18n("Index");
0743                 m_actualCols++;
0744             }
0745 
0746             if (createTimestampEnabled) {
0747                 columnModes << AbstractColumn::ColumnMode::DateTime;
0748                 columnNames << i18n("Timestamp");
0749                 m_actualCols++;
0750             }
0751 
0752             // add column for the actual value
0753             columnModes << AbstractColumn::ColumnMode::Double;
0754             columnNames << i18n("Value");
0755 
0756             QDEBUG(Q_FUNC_INFO << ", column names = " << columnNames);
0757             break;
0758         case LiveDataSource::SourceType::MQTT:
0759             break;
0760         }
0761 
0762         // prepare import for spreadsheet
0763         spreadsheet->setUndoAware(false);
0764         spreadsheet->resize(AbstractFileFilter::ImportMode::Replace, columnNames, m_actualCols);
0765 
0766         // set the plot designation for columns Index, Timestamp and Values, if available
0767         // index column
0768         if (createIndexEnabled)
0769             spreadsheet->column(0)->setPlotDesignation(AbstractColumn::PlotDesignation::X);
0770 
0771         // timestamp column
0772         if (createTimestampEnabled) {
0773             const int index = (int)createIndexEnabled;
0774             spreadsheet->column(index)->setPlotDesignation(AbstractColumn::PlotDesignation::X);
0775         }
0776 
0777         // value column, available only when reading from sockets and serial port
0778         auto type = spreadsheet->sourceType();
0779         if (type == LiveDataSource::SourceType::NetworkTCPSocket || type == LiveDataSource::SourceType::NetworkUDPSocket
0780             || type == LiveDataSource::SourceType::LocalSocket || type == LiveDataSource::SourceType::SerialPort) {
0781             const int index = (int)createIndexEnabled + (int)createTimestampEnabled;
0782             spreadsheet->column(index)->setPlotDesignation(AbstractColumn::PlotDesignation::Y);
0783         }
0784 
0785         // columns in a file data source don't have any manual changes.
0786         // make the available columns undo unaware and suppress the "data changed" signal.
0787         // data changes will be propagated via an explicit Column::setChanged() call once new data was read.
0788         for (auto* c : spreadsheet->children<Column>()) {
0789             c->setUndoAware(false);
0790             c->setSuppressDataChangedSignal(true);
0791         }
0792 
0793         int keepNValues = spreadsheet->keepNValues();
0794         if (keepNValues == 0)
0795             spreadsheet->setRowCount(m_actualRows > 1 ? m_actualRows : 1);
0796         else {
0797             spreadsheet->setRowCount(keepNValues);
0798             m_actualRows = keepNValues;
0799         }
0800 
0801         m_dataContainer.resize(m_actualCols);
0802         initDataContainer(spreadsheet);
0803 
0804         DEBUG(Q_FUNC_INFO << ", data source resized to col: " << m_actualCols);
0805         DEBUG(Q_FUNC_INFO << ", data source rowCount: " << spreadsheet->rowCount());
0806         DEBUG(Q_FUNC_INFO << ", Prepared!");
0807     }
0808 
0809     qint64 bytesread = 0;
0810 
0811 #ifdef PERFTRACE_LIVE_IMPORT
0812     PERFTRACE(QStringLiteral("AsciiLiveDataImportTotal: "));
0813 #endif
0814     LiveDataSource::ReadingType readingType;
0815     if (!m_prepared) {
0816         readingType = LiveDataSource::ReadingType::TillEnd;
0817     } else {
0818         // we have to read all the data when reading from end
0819         // so we set readingType to TillEnd
0820         if (spreadsheet->readingType() == LiveDataSource::ReadingType::FromEnd)
0821             readingType = LiveDataSource::ReadingType::TillEnd;
0822         // if we read the whole file we just start from the beginning of it
0823         // and read till end
0824         else if (spreadsheet->readingType() == LiveDataSource::ReadingType::WholeFile)
0825             readingType = LiveDataSource::ReadingType::TillEnd;
0826         else
0827             readingType = spreadsheet->readingType();
0828     }
0829     DEBUG(" Reading type = " << ENUM_TO_STRING(LiveDataSource, ReadingType, readingType));
0830 
0831     // move to the last read position, from == total bytes read
0832     // since the other source types are sequential we cannot seek on them
0833     if (spreadsheet->sourceType() == LiveDataSource::SourceType::FileOrPipe)
0834         device.seek(from);
0835 
0836     // count the new lines, increase actualrows on each
0837     // now we read all the new lines, if we want to use sample rate
0838     // then here we can do it, if we have actually sample rate number of lines :-?
0839     int newLinesForSampleSizeNotTillEnd = 0;
0840     int newLinesTillEnd = 0;
0841     QVector<QString> newData;
0842     if (readingType != LiveDataSource::ReadingType::TillEnd)
0843         newData.resize(spreadsheet->sampleSize());
0844 
0845     int newDataIdx = 0;
0846     {
0847 #ifdef PERFTRACE_LIVE_IMPORT
0848         PERFTRACE(QStringLiteral("AsciiLiveDataImportReadingFromFile: "));
0849 #endif
0850         DEBUG(" source type = " << ENUM_TO_STRING(LiveDataSource, SourceType, spreadsheet->sourceType()));
0851         while (!device.atEnd()) {
0852             if (readingType != LiveDataSource::ReadingType::TillEnd) {
0853                 switch (spreadsheet->sourceType()) { // different sources need different read methods
0854                 case LiveDataSource::SourceType::LocalSocket:
0855                     newData[newDataIdx++] = QString::fromUtf8(device.readAll());
0856                     break;
0857                 case LiveDataSource::SourceType::NetworkUDPSocket:
0858                     newData[newDataIdx++] = QString::fromUtf8(device.read(device.bytesAvailable()));
0859                     break;
0860                 case LiveDataSource::SourceType::FileOrPipe:
0861                     newData.push_back(QString::fromUtf8(device.readLine()));
0862                     break;
0863                 case LiveDataSource::SourceType::NetworkTCPSocket:
0864                 // TODO: check serial port
0865                 case LiveDataSource::SourceType::SerialPort:
0866                     newData[newDataIdx++] = QString::fromUtf8(device.read(device.bytesAvailable()));
0867                     break;
0868                 case LiveDataSource::SourceType::MQTT:
0869                     break;
0870                 }
0871             } else { // ReadingType::TillEnd
0872                 switch (spreadsheet->sourceType()) { // different sources need different read methods
0873                 case LiveDataSource::SourceType::LocalSocket:
0874                     newData.push_back(QString::fromUtf8(device.readAll()));
0875                     break;
0876                 case LiveDataSource::SourceType::NetworkUDPSocket:
0877                     newData.push_back(QString::fromUtf8(device.read(device.bytesAvailable())));
0878                     break;
0879                 case LiveDataSource::SourceType::FileOrPipe:
0880                     newData.push_back(QString::fromUtf8(device.readLine()));
0881                     break;
0882                 case LiveDataSource::SourceType::NetworkTCPSocket:
0883                 // TODO: check serial port
0884                 case LiveDataSource::SourceType::SerialPort:
0885                     newData.push_back(QString::fromUtf8(device.read(device.bytesAvailable())));
0886                     break;
0887                 case LiveDataSource::SourceType::MQTT:
0888                     break;
0889                 }
0890             }
0891             newLinesTillEnd++;
0892 
0893             if (readingType != LiveDataSource::ReadingType::TillEnd) {
0894                 newLinesForSampleSizeNotTillEnd++;
0895                 // for Continuous reading and FromEnd we read sample rate number of lines if possible
0896                 // here TillEnd and Whole file behave the same
0897                 if (newLinesForSampleSizeNotTillEnd == spreadsheet->sampleSize())
0898                     break;
0899             }
0900         }
0901         QDEBUG(Q_FUNC_INFO << ", data read:" << newData);
0902     }
0903 
0904     // now we reset the readingType
0905     if (spreadsheet->readingType() == LiveDataSource::ReadingType::FromEnd)
0906         readingType = spreadsheet->readingType();
0907 
0908     // we have less new lines than the sample size specified
0909     if (readingType != LiveDataSource::ReadingType::TillEnd)
0910         QDEBUG("    Removed empty lines: " << newData.removeAll(QString()));
0911 
0912     // back to the last read position before counting when reading from files
0913     if (spreadsheet->sourceType() == LiveDataSource::SourceType::FileOrPipe)
0914         device.seek(from);
0915 
0916     // split newData to get data columns (only TCP atm)
0917     if (!m_prepared && spreadsheet->sourceType() == LiveDataSource::SourceType::NetworkTCPSocket) {
0918         DEBUG("TCP: COLUMN count = " << m_actualCols)
0919         QString firstRowData = newData.at(0);
0920         QStringList dataStringList;
0921         QDEBUG("TCP: sep char = " << separatingCharacter)
0922         if (separatingCharacter == QLatin1String("auto")) {
0923             DEBUG(Q_FUNC_INFO << ", using AUTOMATIC separator");
0924             if (firstRowData.indexOf(QLatin1Char('\t')) != -1) {
0925                 // in case we have a mix of tabs and spaces in the header, give the tab character preference
0926                 m_separator = QLatin1Char('\t');
0927                 dataStringList = split(firstRowData, false);
0928             } else {
0929                 dataStringList = split(firstRowData);
0930             }
0931         } else {
0932             DEBUG(Q_FUNC_INFO << ", using GIVEN separator: " << STDSTRING(m_separator))
0933             // replace symbolic "TAB" with '\t'
0934             m_separator = separatingCharacter.replace(QLatin1String("2xTAB"), QLatin1String("\t\t"), Qt::CaseInsensitive);
0935             m_separator = separatingCharacter.replace(QLatin1String("TAB"), QLatin1String("\t"), Qt::CaseInsensitive);
0936             // replace symbolic "SPACE" with ' '
0937             m_separator = m_separator.replace(QLatin1String("2xSPACE"), QLatin1String("  "), Qt::CaseInsensitive);
0938             m_separator = m_separator.replace(QLatin1String("3xSPACE"), QLatin1String("   "), Qt::CaseInsensitive);
0939             m_separator = m_separator.replace(QLatin1String("4xSPACE"), QLatin1String("    "), Qt::CaseInsensitive);
0940             m_separator = m_separator.replace(QLatin1String("SPACE"), QLatin1String(" "), Qt::CaseInsensitive);
0941             dataStringList = split(firstRowData, false);
0942         }
0943         QDEBUG(Q_FUNC_INFO << ", separator: \'" << m_separator << '\'');
0944         DEBUG(Q_FUNC_INFO << ", number of data columns: " << dataStringList.size());
0945         QDEBUG(Q_FUNC_INFO << ", first data row split: " << dataStringList);
0946         int defaultCols = (int)createIndexEnabled + (int)createTimestampEnabled; // automatic columns
0947         m_actualCols += dataStringList.size() - 1; // one data column already counted
0948         columnModes.resize(m_actualCols);
0949 
0950         // column header
0951         columnNames.clear();
0952         if (createIndexEnabled)
0953             columnNames << i18n("Index");
0954         if (createTimestampEnabled)
0955             columnNames << i18n("Timestamp");
0956 
0957         for (int i = 0; i < m_actualCols - defaultCols; i++) {
0958             columnModes[i + defaultCols] = AbstractFileFilter::columnMode(dataStringList.at(i), dateTimeFormat, numberFormat);
0959             if (dataStringList.size() == 1)
0960                 columnNames << i18n("Value");
0961             else
0962                 columnNames << i18n("Value %1", i + 1);
0963         }
0964         QDEBUG("COLUMN names: " << columnNames)
0965 
0966         for (int i = 0; i < columnModes.size(); i++)
0967             QDEBUG("Data column mode " << i << " : " << ENUM_TO_STRING(AbstractColumn, ColumnMode, columnModes.at(i)))
0968 
0969         spreadsheet->setUndoAware(false);
0970         spreadsheet->resize(AbstractFileFilter::ImportMode::Replace, columnNames, m_actualCols);
0971     }
0972 
0973     const int spreadsheetRowCountBeforeResize = spreadsheet->rowCount();
0974 
0975     int currentRow = 0; // indexes the position in the vector(column)
0976     int linesToRead = 0;
0977     int keepNValues = spreadsheet->keepNValues();
0978 
0979     DEBUG(Q_FUNC_INFO << ", Increase row count. keepNValues = " << keepNValues);
0980     if (m_prepared) {
0981         // increase row count if we don't have a fixed size
0982         // but only after the preparation step
0983         if (keepNValues == 0) {
0984             DEBUG(" keep All values");
0985             if (readingType != LiveDataSource::ReadingType::TillEnd)
0986                 m_actualRows += std::min(static_cast<qsizetype>(newData.size()), static_cast<qsizetype>(spreadsheet->sampleSize()));
0987             else {
0988                 // we don't increase it if we reread the whole file, we reset it
0989                 if (!(spreadsheet->readingType() == LiveDataSource::ReadingType::WholeFile))
0990                     m_actualRows += newData.size();
0991                 else
0992                     m_actualRows = newData.size();
0993             }
0994 
0995             // appending
0996             if (spreadsheet->readingType() == LiveDataSource::ReadingType::WholeFile)
0997                 linesToRead = m_actualRows;
0998             else
0999                 linesToRead = m_actualRows - spreadsheetRowCountBeforeResize;
1000         } else { // fixed size
1001             DEBUG(" keep " << keepNValues << " values");
1002             if (readingType == LiveDataSource::ReadingType::TillEnd) {
1003                 // we had more lines than the fixed size, so we read m_actualRows number of lines
1004                 if (newLinesTillEnd > m_actualRows) {
1005                     linesToRead = m_actualRows;
1006                     // TODO after reading we should skip the next data lines
1007                     // because it's TillEnd actually
1008                 } else
1009                     linesToRead = newLinesTillEnd;
1010             } else {
1011                 // we read max sample size number of lines when the reading mode
1012                 // is ContinuouslyFixed or FromEnd, WholeFile is disabled
1013                 linesToRead = std::min(spreadsheet->sampleSize(), newLinesTillEnd);
1014             }
1015         }
1016 
1017         if (linesToRead == 0)
1018             return 0;
1019     } else // not prepared
1020         linesToRead = newLinesTillEnd;
1021 
1022     DEBUG(Q_FUNC_INFO << ", lines to read = " << linesToRead);
1023     DEBUG(Q_FUNC_INFO << ", actual rows (w/o header) = " << m_actualRows);
1024 
1025     // TODO
1026     //  if (spreadsheet->sourceType() == LiveDataSource::SourceType::FileOrPipe || spreadsheet->sourceType() == LiveDataSource::SourceType::NetworkUdpSocket) {
1027     //      if (m_actualRows < linesToRead) {
1028     //          DEBUG(" SET lines to read to " << m_actualRows);
1029     //          linesToRead = m_actualRows;
1030     //      }
1031     //  }
1032 
1033     // new rows/resize columns if we don't have a fixed size
1034     // TODO if the user changes this value..m_resizedToFixedSize..setResizedToFixedSize
1035     if (keepNValues == 0) {
1036 #ifdef PERFTRACE_LIVE_IMPORT
1037         PERFTRACE(QLatin1String("AsciiLiveDataImportResizing: "));
1038 #endif
1039         if (spreadsheet->rowCount() < m_actualRows)
1040             spreadsheet->setRowCount(m_actualRows);
1041 
1042         if (!m_prepared)
1043             currentRow = 0;
1044         else {
1045             // indexes the position in the vector(column)
1046             if (spreadsheet->readingType() == LiveDataSource::ReadingType::WholeFile)
1047                 currentRow = 0;
1048             else
1049                 currentRow = spreadsheetRowCountBeforeResize;
1050         }
1051 
1052         // if we have fixed size, we do this only once in preparation, here we can use
1053         // m_prepared and we need something to decide whether it has a fixed size or increasing
1054         initDataContainer(spreadsheet);
1055     } else { // fixed size
1056         // when we have a fixed size we have to pop sampleSize number of lines if specified
1057         // here popping, setting currentRow
1058         if (!m_prepared) {
1059             if (spreadsheet->readingType() == LiveDataSource::ReadingType::WholeFile)
1060                 currentRow = 0;
1061             else
1062                 currentRow = m_actualRows - std::min(newLinesTillEnd, m_actualRows);
1063         } else {
1064             if (readingType == LiveDataSource::ReadingType::TillEnd) {
1065                 if (newLinesTillEnd > m_actualRows) {
1066                     currentRow = 0;
1067                 } else {
1068                     if (spreadsheet->readingType() == LiveDataSource::ReadingType::WholeFile)
1069                         currentRow = 0;
1070                     else
1071                         currentRow = m_actualRows - newLinesTillEnd;
1072                 }
1073             } else {
1074                 // we read max sample size number of lines when the reading mode
1075                 // is ContinuouslyFixed or FromEnd
1076                 currentRow = m_actualRows - std::min(spreadsheet->sampleSize(), newLinesTillEnd);
1077             }
1078         }
1079 
1080         if (m_prepared) {
1081 #ifdef PERFTRACE_LIVE_IMPORT
1082             PERFTRACE(QLatin1String("AsciiLiveDataImportPopping: "));
1083 #endif
1084             // enable data change signal
1085             const auto& columns = spreadsheet->children<Column>();
1086             for (int col = 0; col < m_actualCols; ++col)
1087                 columns.at(col)->setSuppressDataChangedSignal(false);
1088 
1089             for (int row = 0; row < linesToRead; ++row) {
1090                 for (int col = 0; col < m_actualCols; ++col) {
1091                     switch (columnModes.at(col)) {
1092                     case AbstractColumn::ColumnMode::Double: {
1093                         auto* vector = static_cast<QVector<double>*>(columns.at(col)->data());
1094                         vector->pop_front();
1095                         vector->resize(m_actualRows);
1096                         m_dataContainer[col] = static_cast<void*>(vector);
1097                         break;
1098                     }
1099                     case AbstractColumn::ColumnMode::Integer: {
1100                         auto* vector = static_cast<QVector<int>*>(columns.at(col)->data());
1101                         vector->pop_front();
1102                         vector->resize(m_actualRows);
1103                         m_dataContainer[col] = static_cast<void*>(vector);
1104                         break;
1105                     }
1106                     case AbstractColumn::ColumnMode::BigInt: {
1107                         auto* vector = static_cast<QVector<qint64>*>(columns.at(col)->data());
1108                         vector->pop_front();
1109                         vector->resize(m_actualRows);
1110                         m_dataContainer[col] = static_cast<void*>(vector);
1111                         break;
1112                     }
1113                     case AbstractColumn::ColumnMode::Text: {
1114                         auto* vector = static_cast<QVector<QString>*>(columns.at(col)->data());
1115                         vector->pop_front();
1116                         vector->resize(m_actualRows);
1117                         m_dataContainer[col] = static_cast<void*>(vector);
1118                         break;
1119                     }
1120                     case AbstractColumn::ColumnMode::DateTime: {
1121                         auto* vector = static_cast<QVector<QDateTime>*>(columns.at(col)->data());
1122                         vector->pop_front();
1123                         vector->resize(m_actualRows);
1124                         m_dataContainer[col] = static_cast<void*>(vector);
1125                         break;
1126                     }
1127                     // TODO
1128                     case AbstractColumn::ColumnMode::Month:
1129                     case AbstractColumn::ColumnMode::Day:
1130                         break;
1131                     }
1132                 }
1133             }
1134         }
1135     }
1136 
1137     // from the last row we read the new data in the spreadsheet
1138     DEBUG(Q_FUNC_INFO << ", reading from line " << currentRow << " till end line " << newLinesTillEnd);
1139     DEBUG(Q_FUNC_INFO << ", lines to read:" << linesToRead << ", actual rows:" << m_actualRows << ", actual cols:" << m_actualCols);
1140     newDataIdx = 0;
1141     if (readingType == LiveDataSource::ReadingType::FromEnd) {
1142         if (m_prepared) {
1143             if (newData.size() > spreadsheet->sampleSize())
1144                 newDataIdx = newData.size() - spreadsheet->sampleSize();
1145             // since we skip a couple of lines, we need to count those bytes too
1146             for (int i = 0; i < newDataIdx; ++i)
1147                 bytesread += newData.at(i).size();
1148         }
1149     }
1150     DEBUG(" newDataIdx: " << newDataIdx);
1151 
1152     static int indexColumnIdx = 1;
1153     {
1154 #ifdef PERFTRACE_LIVE_IMPORT
1155         PERFTRACE(QLatin1String("AsciiLiveDataImportFillingContainers: "));
1156 #endif
1157         int row = 0;
1158 
1159         if (readingType == LiveDataSource::ReadingType::TillEnd || (readingType == LiveDataSource::ReadingType::ContinuousFixed)) {
1160             if (headerEnabled) {
1161                 if (!m_prepared) {
1162                     row = 1;
1163                     bytesread += newData.at(0).size();
1164                 }
1165             }
1166         }
1167         if (spreadsheet->sourceType() == LiveDataSource::SourceType::FileOrPipe) {
1168             if (readingType == LiveDataSource::ReadingType::WholeFile) {
1169                 if (headerEnabled) {
1170                     row = 1;
1171                     bytesread += newData.at(0).size();
1172                 }
1173             }
1174         }
1175 
1176         for (; row < linesToRead; ++row) {
1177             //          DEBUG("\n   Reading row " << row + 1 << " of " << linesToRead);
1178             QString line;
1179             if (readingType == LiveDataSource::ReadingType::FromEnd)
1180                 line = newData.at(newDataIdx++);
1181             else
1182                 line = newData.at(row);
1183             // when we read the whole file we don't care about the previous position
1184             // so we don't have to count those bytes
1185             if (readingType != LiveDataSource::ReadingType::WholeFile) {
1186                 if (spreadsheet->sourceType() == LiveDataSource::SourceType::FileOrPipe) {
1187                     bytesread += line.size();
1188                 }
1189             }
1190 
1191             if (removeQuotesEnabled)
1192                 line.remove(QLatin1Char('"'));
1193 
1194             if (line.isEmpty() || (!commentCharacter.isEmpty() && line.startsWith(commentCharacter))) // skip empty or commented lines
1195                 continue;
1196 
1197             QStringList lineStringList;
1198             // only FileOrPipe and TCPSocket support multiple columns
1199             if (spreadsheet->sourceType() == LiveDataSource::SourceType::FileOrPipe
1200                 || spreadsheet->sourceType() == LiveDataSource::SourceType::NetworkTCPSocket) {
1201                 QDEBUG("separator = " << m_separator << " , size = " << m_separator.size())
1202                 if (m_separator.size() > 0)
1203                     lineStringList = split(line, false);
1204                 else
1205                     lineStringList = split(line);
1206             } else
1207                 lineStringList << line;
1208             //          QDEBUG("    line = " << lineStringList << ", separator = \'" << m_separator << "\'");
1209 
1210             //          DEBUG(" Line bytes: " << line.size() << " line: " << STDSTRING(line));
1211             if (simplifyWhitespacesEnabled) {
1212                 for (int i = 0; i < lineStringList.size(); ++i)
1213                     lineStringList[i] = lineStringList.at(i).simplified();
1214             }
1215 
1216             // add index if required
1217             int offset = 0;
1218             if (createIndexEnabled) {
1219                 int index = (spreadsheet->keepNValues() == 0) ? currentRow + 1 : indexColumnIdx++;
1220                 (*static_cast<QVector<int>*>(m_dataContainer[0]))[currentRow] = index;
1221                 ++offset;
1222             }
1223 
1224             // add current timestamp if required
1225             if (createTimestampEnabled) {
1226                 DEBUG("current row = " << currentRow << ", container size = " << static_cast<QVector<QDateTime>*>(m_dataContainer[offset])->size())
1227                 (*static_cast<QVector<QDateTime>*>(m_dataContainer[offset]))[currentRow] = QDateTime::currentDateTime();
1228                 ++offset;
1229             }
1230 
1231             // parse columns
1232             QDEBUG("lineStringList = " << lineStringList)
1233             for (int n = offset; n < m_actualCols; ++n) {
1234                 QString valueString;
1235                 if (n - offset < lineStringList.size())
1236                     valueString = lineStringList.at(n - offset);
1237 
1238                 setValue(n, currentRow, valueString);
1239             }
1240             currentRow++;
1241         }
1242     }
1243 
1244     if (m_prepared) {
1245         // notify all affected columns and plots about the changes
1246         PERFTRACE(QLatin1String("AsciiLiveDataImport, notify affected columns and plots"));
1247 
1248         // determine the dependent plots
1249         QVector<CartesianPlot*> plots;
1250         for (int n = 0; n < m_actualCols; ++n)
1251             spreadsheet->column(n)->addUsedInPlots(plots);
1252 
1253         // suppress retransform in the dependent plots
1254         for (auto* plot : plots)
1255             plot->setSuppressRetransform(true);
1256 
1257         for (int n = 0; n < m_actualCols; ++n)
1258             spreadsheet->column(n)->setChanged();
1259 
1260         // retransform the dependent plots
1261         for (auto* plot : plots) {
1262             plot->setSuppressRetransform(false);
1263             plot->dataChanged(-1, -1); // TODO: check if all ranges must be updated!
1264         }
1265     } else
1266         m_prepared = true;
1267 
1268     DEBUG(Q_FUNC_INFO << ", DONE");
1269     return bytesread;
1270 }
1271 
1272 /*!
1273     reads the content of device \c device to the data source \c dataSource. Uses the settings defined in the data source.
1274 */
1275 void AsciiFilterPrivate::readDataFromDevice(QIODevice& device, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) {
1276     DEBUG(Q_FUNC_INFO << ", dataSource = " << dataSource << ", mode = " << ENUM_TO_STRING(AbstractFileFilter, ImportMode, importMode) << ", lines = " << lines);
1277     DEBUG(Q_FUNC_INFO << ", start row: " << startRow)
1278 
1279     if (!m_prepared) {
1280         const int deviceError = prepareDeviceToRead(device);
1281         if (deviceError) {
1282             DEBUG(Q_FUNC_INFO << ", DEVICE ERROR = " << deviceError);
1283             return;
1284         }
1285 
1286         // matrix data has only one column mode
1287         if (dynamic_cast<Matrix*>(dataSource)) {
1288             auto mode = columnModes[0];
1289             // TODO: remove this when Matrix supports text type
1290             if (mode == AbstractColumn::ColumnMode::Text)
1291                 mode = AbstractColumn::ColumnMode::Double;
1292             for (auto& c : columnModes)
1293                 if (c != mode)
1294                     c = mode;
1295         }
1296 
1297         Q_ASSERT(dataSource);
1298         m_columnOffset = dataSource->prepareImport(m_dataContainer, importMode, m_actualRows, m_actualCols, columnNames, columnModes);
1299         m_prepared = true;
1300     }
1301 
1302     DEBUG(Q_FUNC_INFO << ", locale = " << STDSTRING(QLocale::languageToString(numberFormat)));
1303 
1304     // Read the data
1305     int currentRow = 0; // indexes the position in the vector(column)
1306     if (lines == -1)
1307         lines = m_actualRows;
1308     DEBUG(Q_FUNC_INFO << ", lines = " << lines << ", m_actualRows = " << m_actualRows)
1309 
1310     // skip data lines, if required
1311     int skipLines = m_actualStartRow - 1;
1312     if ((!headerEnabled || headerLine < 1) && skipLines <= 1) { // read header as normal line
1313         skipLines--;
1314         m_actualRows++;
1315     }
1316     DEBUG(Q_FUNC_INFO << ", skipping " << skipLines << " line(s)");
1317     for (int i = 0; i < skipLines; ++i)
1318         device.readLine();
1319 
1320     lines = std::min(lines, m_actualRows);
1321     DEBUG(Q_FUNC_INFO << ", reading " << lines << " lines, " << m_actualCols << " columns");
1322 
1323     if (lines == 0 || m_actualCols == 0)
1324         return;
1325 
1326     QString line;
1327     // Don't put the definition QStringList lineStringList outside of the for-loop,
1328     // the compiler doesn't seem to optimize the destructor of QList well enough in this case.
1329 
1330     int progressIndex = 0;
1331     const qreal progressInterval = 0.01 * lines; // update on every 1% only
1332 
1333 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
1334     // when reading numerical data the options removeQuotesEnabled, simplifyWhitespacesEnabled and skipEmptyParts
1335     // are not relevant and we can provide a more faster version that avoids many of string allocations, etc.
1336     if (!removeQuotesEnabled && !simplifyWhitespacesEnabled && !skipEmptyParts) {
1337         for (int i = 0; i < lines; ++i) {
1338             line = QString::fromUtf8(device.readLine());
1339 
1340             // remove any newline
1341             line.remove(QLatin1Char('\n'));
1342             line.remove(QLatin1Char('\r'));
1343 
1344             // DEBUG("1 Line bytes: " << line.size() << " line: " << STDSTRING(line));
1345 
1346             if (line.isEmpty() || (!commentCharacter.isEmpty() && line.startsWith(commentCharacter))) // skip empty or commented lines
1347                 continue;
1348 
1349             int readColumns = 0;
1350 
1351             // index column if required
1352             if (createIndexEnabled) {
1353                 (*static_cast<QVector<int>*>(m_dataContainer[0]))[currentRow] = i + 1;
1354                 readColumns = 1;
1355             }
1356 
1357             // parse the columns in the current line
1358             int currentColumn = 0;
1359             for (const auto& valueString : qTokenize(line, m_separator, (Qt::SplitBehavior)skipEmptyParts)) {
1360                 // skip the first columns up to the start column
1361                 if (currentColumn < startColumn - 1) {
1362                     ++currentColumn;
1363                     continue;
1364                 }
1365 
1366                 // leave the loop if all required columns were read
1367                 if (readColumns == m_actualCols)
1368                     break;
1369 
1370                 // set the column value
1371                 setValue(readColumns, currentRow, valueString);
1372 
1373                 ++readColumns;
1374                 ++currentColumn;
1375             }
1376             // set not available values
1377             for (int c = readColumns; c < m_actualCols; c++)
1378                 setValue(c, currentRow, QString());
1379 
1380             currentRow++;
1381 
1382             // ask to update the progress bar only if we have more than 1000 lines
1383             // only in 1% steps
1384             progressIndex++;
1385             if (lines > 1000 && progressIndex > progressInterval) {
1386                 double value = 100. * currentRow / lines;
1387                 Q_EMIT q->completed(static_cast<int>(value));
1388                 progressIndex = 0;
1389                 QApplication::processEvents(QEventLoop::AllEvents, 0);
1390             }
1391         }
1392     } else {
1393 #endif
1394         QString valueString;
1395         for (int i = 0; i < lines; ++i) {
1396             line = QString::fromUtf8(device.readLine());
1397 
1398             // remove any newline
1399             line.remove(QLatin1Char('\n'));
1400             line.remove(QLatin1Char('\r'));
1401 
1402             if (line.isEmpty() || (!commentCharacter.isEmpty() && line.startsWith(commentCharacter))) // skip empty or commented lines
1403                 continue;
1404 
1405             auto lineStringList = split(line, false);
1406             // DEBUG("2 Line bytes: " << line.size() << " line: " << STDSTRING(line));
1407 
1408             if (removeQuotesEnabled) {
1409                 for (int i = 0; i < lineStringList.size(); ++i)
1410                     lineStringList[i] = lineStringList[i].remove(QLatin1Char('"'));
1411             }
1412 
1413             if (simplifyWhitespacesEnabled) {
1414                 for (int i = 0; i < lineStringList.size(); ++i)
1415                     lineStringList[i] = lineStringList[i].simplified();
1416             }
1417 
1418             // remove left white spaces
1419             if (skipEmptyParts) {
1420                 for (int n = 0; n < lineStringList.size(); ++n) {
1421                     valueString = lineStringList.at(n);
1422                     if (!QString::compare(valueString, QLatin1String(" "))) {
1423                         lineStringList.removeAt(n);
1424                         n--;
1425                     }
1426                 }
1427             }
1428 
1429             // parse columns
1430             //      DEBUG(Q_FUNC_INFO << ", actual cols = " << m_actualCols)
1431             for (int n = 0; n < m_actualCols; ++n) {
1432                 // index column if required
1433                 if (n == 0 && createIndexEnabled) {
1434                     (*static_cast<QVector<int>*>(m_dataContainer[0]))[currentRow] = i + 1;
1435                     continue;
1436                 }
1437 
1438                 // column counting starts with 1, subtract 1 as well as another 1 for the index column if required
1439                 int col = createIndexEnabled ? n + startColumn - 2 : n + startColumn - 1;
1440                 if (col < lineStringList.size())
1441                     setValue(n, currentRow, lineStringList.at(col));
1442                 else
1443                     setValue(n, currentRow, QStringView());
1444             }
1445 
1446             currentRow++;
1447 
1448             // ask to update the progress bar only if we have more than 1000 lines
1449             // only in 1% steps
1450             progressIndex++;
1451             if (lines > 1000 && progressIndex > progressInterval) {
1452                 double value = 100. * currentRow / lines;
1453                 Q_EMIT q->completed(static_cast<int>(value));
1454                 progressIndex = 0;
1455                 QApplication::processEvents(QEventLoop::AllEvents, 0);
1456             }
1457         }
1458 
1459 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
1460     }
1461 #endif
1462 
1463     DEBUG(Q_FUNC_INFO << ", Read " << currentRow << " lines");
1464 
1465     // we might have skipped empty lines above. shrink the spreadsheet if the number of read lines (=currentRow)
1466     // is smaller than the initial size of the spreadsheet (=m_actualRows).
1467     // TODO: should also be relevant for Matrix
1468     auto* s = dynamic_cast<Spreadsheet*>(dataSource);
1469     if (s && currentRow != m_actualRows && importMode == AbstractFileFilter::ImportMode::Replace)
1470         s->setRowCount(currentRow);
1471 
1472     Q_ASSERT(dataSource);
1473     dataSource->finalizeImport(m_columnOffset, startColumn, startColumn + m_actualCols - 1, dateTimeFormat, importMode);
1474 }
1475 
1476 // #####################################################################
1477 // ############################ Preview ################################
1478 // #####################################################################
1479 
1480 /*!
1481  * preview for special devices (local/UDP/TCP socket or serial port)
1482  */
1483 QVector<QStringList> AsciiFilterPrivate::preview(QIODevice& device) {
1484     DEBUG(Q_FUNC_INFO << ", bytesAvailable = " << device.bytesAvailable() << ", isSequential = " << device.isSequential());
1485     QVector<QStringList> dataStrings;
1486 
1487     if (!(device.bytesAvailable() > 0)) {
1488         DEBUG("No new data available");
1489         return dataStrings;
1490     }
1491 
1492     if (device.isSequential() && device.bytesAvailable() < (int)sizeof(quint16))
1493         return dataStrings;
1494 
1495     int linesToRead = 0;
1496     QVector<QString> newData;
1497 
1498     // TODO: serial port "read(nBytes)"?
1499     while (!device.atEnd()) {
1500         if (device.canReadLine())
1501             newData.push_back(QString::fromUtf8(device.readLine()));
1502         else // UDP fails otherwise
1503             newData.push_back(QString::fromUtf8(device.readAll()));
1504         linesToRead++;
1505     }
1506     QDEBUG("    data = " << newData);
1507 
1508     if (linesToRead == 0)
1509         return dataStrings;
1510 
1511     columnNames.clear();
1512     columnModes.clear();
1513 
1514     if (createIndexEnabled) {
1515         columnModes << AbstractColumn::ColumnMode::Integer;
1516         columnNames << i18n("Index");
1517     }
1518 
1519     if (createTimestampEnabled) {
1520         columnModes << AbstractColumn::ColumnMode::DateTime;
1521         columnNames << i18n("Timestamp");
1522     }
1523 
1524     // parse the first data line to determine data type for each column
1525 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
1526     QStringList firstLineStringList = newData.at(0).split(separatingCharacter, Qt::SkipEmptyParts);
1527 #else
1528     QStringList firstLineStringList = newData.at(0).split(separatingCharacter, QString::SkipEmptyParts);
1529 #endif
1530     int i = 1;
1531     for (auto& valueString : firstLineStringList) {
1532         if (simplifyWhitespacesEnabled)
1533             valueString = valueString.simplified();
1534         if (removeQuotesEnabled)
1535             valueString.remove(QLatin1Char('"'));
1536         if (skipEmptyParts && !QString::compare(valueString, QLatin1String(" "))) // handle left white spaces
1537             continue;
1538 
1539         if (firstLineStringList.size() == 1)
1540             columnNames << i18n("Value");
1541         else
1542             columnNames << i18n("Value %1", i++);
1543         columnModes << AbstractFileFilter::columnMode(valueString, dateTimeFormat, numberFormat);
1544     }
1545 
1546     int offset = int(createIndexEnabled) + int(createTimestampEnabled);
1547     QString line;
1548 
1549     // loop over all lines in the new data in the device and parse the available columns
1550     for (int i = 0; i < linesToRead; ++i) {
1551         line = newData.at(i);
1552 
1553         // remove any newline
1554         line = line.remove(QLatin1Char('\n'));
1555         line = line.remove(QLatin1Char('\r'));
1556 
1557         if (simplifyWhitespacesEnabled)
1558             line = line.simplified();
1559 
1560         if (line.isEmpty() || (!commentCharacter.isEmpty() && line.startsWith(commentCharacter))) // skip empty or commented lines
1561             continue;
1562 
1563         QStringList lineString;
1564 
1565         // index column if required
1566         if (createIndexEnabled)
1567             lineString += QString::number(i + 1);
1568 
1569         // timestamp column if required
1570         if (createTimestampEnabled)
1571             lineString += QDateTime::currentDateTime().toString();
1572 
1573             // TODO: use separator
1574 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
1575         QStringList lineStringList = line.split(separatingCharacter, Qt::SkipEmptyParts);
1576 #else
1577         QStringList lineStringList = line.split(separatingCharacter, QString::SkipEmptyParts);
1578 #endif
1579         QDEBUG(" line = " << lineStringList);
1580 
1581         // parse columns
1582         DEBUG(Q_FUNC_INFO << ", number of columns = " << lineStringList.size())
1583         for (int n = 0; n < lineStringList.size(); ++n) {
1584             if (n < lineStringList.size()) {
1585                 QString valueString = lineStringList.at(n);
1586                 if (removeQuotesEnabled)
1587                     valueString.remove(QLatin1Char('"'));
1588 
1589                 if (skipEmptyParts && !QString::compare(valueString, QLatin1String(" "))) // handle left white spaces
1590                     continue;
1591 
1592                 lineString += previewValue(valueString, columnModes[n + offset]);
1593             } else // missing columns in this line
1594                 lineString += QString();
1595         }
1596 
1597         dataStrings << lineString;
1598     }
1599 
1600     return dataStrings;
1601 }
1602 
1603 /*!
1604  * generates the preview for the file \c fileName reading the provided number of \c lines.
1605  */
1606 QVector<QStringList> AsciiFilterPrivate::preview(const QString& fileName, int lines) {
1607     DEBUG(Q_FUNC_INFO)
1608     QVector<QStringList> dataStrings;
1609 
1610     KCompressionDevice device(fileName);
1611     const int deviceError = prepareDeviceToRead(device, lines);
1612 
1613     if (deviceError != 0) {
1614         DEBUG("Device error = " << deviceError);
1615         return dataStrings;
1616     }
1617 
1618     // number formatting
1619     DEBUG(Q_FUNC_INFO << ", locale = " << STDSTRING(QLocale::languageToString(numberFormat)));
1620 
1621     // Read the data
1622     if (lines == -1)
1623         lines = m_actualRows;
1624 
1625     // set column names for preview
1626     if (!headerEnabled || headerLine < 1) {
1627         int start = 0;
1628         if (createIndexEnabled)
1629             start = 1;
1630         for (int i = start; i < m_actualCols; i++)
1631             columnNames << QStringLiteral("Column ") + QString::number(i + 1);
1632     }
1633     QDEBUG(Q_FUNC_INFO << ", column names = " << columnNames);
1634 
1635     // skip data lines, if required
1636     DEBUG("m_actualStartRow = " << m_actualStartRow)
1637     DEBUG("m_actualRows = " << m_actualRows)
1638     int skipLines = m_actualStartRow - 1;
1639     if (!headerEnabled || headerLine < 1) { // read header as normal line
1640         skipLines--;
1641         m_actualRows++;
1642     }
1643     DEBUG(Q_FUNC_INFO << ", skipping " << skipLines << " line(s)");
1644     for (int i = 0; i < skipLines; ++i)
1645         device.readLine();
1646 
1647     lines = std::min(lines, m_actualRows);
1648     DEBUG(Q_FUNC_INFO << ", preview " << lines << " line(s)");
1649     QString line;
1650 
1651     // loop over the preview lines in the file and parse the available columns
1652     for (int i = 0; i < lines; ++i) {
1653         line = QString::fromUtf8(device.readLine());
1654 
1655         // remove any newline
1656         line = line.remove(QLatin1Char('\n'));
1657         line = line.remove(QLatin1Char('\r'));
1658 
1659         if (line.isEmpty() || (!commentCharacter.isEmpty() && line.startsWith(commentCharacter))) // skip empty or commented lines
1660             continue;
1661 
1662         auto lineStringList = split(line, false);
1663         // QDEBUG(" line = " << lineStringList);
1664         // DEBUG("  Line bytes: " << line.size() << " line: " << STDSTRING(line));
1665 
1666         if (simplifyWhitespacesEnabled) {
1667             for (int i = 0; i < lineStringList.size(); ++i)
1668                 lineStringList[i] = lineStringList[i].simplified();
1669         }
1670 
1671         QStringList lineString;
1672 
1673         // parse columns
1674         for (int n = 0; n < m_actualCols; ++n) {
1675             // index column if required
1676             if (n == 0 && createIndexEnabled) {
1677                 lineString += QString::number(i + 1);
1678                 continue;
1679             }
1680 
1681             // column counting starts with 1, subtract 1 as well as another 1 for the index column if required
1682             int col = createIndexEnabled ? n + startColumn - 2 : n + startColumn - 1;
1683 
1684             if (col < lineStringList.size()) {
1685                 QString valueString = lineStringList.at(col);
1686                 if (removeQuotesEnabled)
1687                     valueString.remove(QLatin1Char('"'));
1688 
1689                 if (skipEmptyParts && !QString::compare(valueString, QLatin1String(" "))) // handle left white spaces
1690                     continue;
1691 
1692                 lineString += previewValue(valueString, columnModes[n]);
1693             } else // missing columns in this line
1694                 lineString += QString();
1695         }
1696 
1697         dataStrings << lineString;
1698     }
1699 
1700     return dataStrings;
1701 }
1702 
1703 // #####################################################################
1704 // ####################### Helper functions ############################
1705 // #####################################################################
1706 /*!
1707  * converts \c valueString to the date type according to \c mode and \c locale
1708  * and returns its string representation.
1709  */
1710 QString AsciiFilterPrivate::previewValue(const QString& valueString, AbstractColumn::ColumnMode mode) {
1711     //  DEBUG(Q_FUNC_INFO << ", valueString = " << STDSTRING(valueString) << ", mode = " << (int)mode)
1712 
1713     QString result;
1714     switch (mode) {
1715     case AbstractColumn::ColumnMode::Double: {
1716         bool isNumber;
1717         const double value = locale.toDouble(valueString, &isNumber);
1718         result = QString::number(isNumber ? value : nanValue, 'g', 15);
1719         break;
1720     }
1721     case AbstractColumn::ColumnMode::Integer: {
1722         bool isNumber;
1723         const int value = locale.toInt(valueString, &isNumber);
1724         result = QString::number(isNumber ? value : 0);
1725         break;
1726     }
1727     case AbstractColumn::ColumnMode::BigInt: {
1728         bool isNumber;
1729         const qint64 value = locale.toLongLong(valueString, &isNumber);
1730         result = QString::number(isNumber ? value : 0);
1731         break;
1732     }
1733     case AbstractColumn::ColumnMode::DateTime: {
1734         QDateTime valueDateTime = parseDateTime(valueString, dateTimeFormat);
1735         result = valueDateTime.isValid() ? valueDateTime.toString(dateTimeFormat) : QLatin1String(" ");
1736         break;
1737     }
1738     case AbstractColumn::ColumnMode::Text:
1739         result = valueString;
1740         break;
1741     case AbstractColumn::ColumnMode::Month: // never happens
1742     case AbstractColumn::ColumnMode::Day:
1743         break;
1744     }
1745     return result;
1746 }
1747 
1748 // set value depending on data type
1749 void AsciiFilterPrivate::setValue(int col, int row, QStringView valueString) {
1750     //  QDEBUG(Q_FUNC_INFO << ", string = " << valueString)
1751     if (!valueString.isEmpty()) {
1752         switch (columnModes.at(col)) {
1753         case AbstractColumn::ColumnMode::Double: {
1754             bool isNumber;
1755             const double value = locale.toDouble(valueString, &isNumber);
1756             (*static_cast<QVector<double>*>(m_dataContainer[col]))[row] = (isNumber ? value : nanValue);
1757             break;
1758         }
1759         case AbstractColumn::ColumnMode::Integer: {
1760             bool isNumber;
1761             const int value = locale.toInt(valueString, &isNumber);
1762             (*static_cast<QVector<int>*>(m_dataContainer[col]))[row] = (isNumber ? value : 0);
1763             break;
1764         }
1765         case AbstractColumn::ColumnMode::BigInt: {
1766             bool isNumber;
1767             const qint64 value = locale.toLongLong(valueString, &isNumber);
1768             (*static_cast<QVector<qint64>*>(m_dataContainer[col]))[row] = (isNumber ? value : 0);
1769             break;
1770         }
1771         case AbstractColumn::ColumnMode::DateTime: {
1772             QDateTime valueDateTime = parseDateTime(valueString.toString(), dateTimeFormat);
1773             (*static_cast<QVector<QDateTime>*>(m_dataContainer[col]))[row] = valueDateTime.isValid() ? valueDateTime : QDateTime();
1774             break;
1775         }
1776         case AbstractColumn::ColumnMode::Text: {
1777             auto* colData = static_cast<QVector<QString>*>(m_dataContainer[col]);
1778             (*colData)[row] = valueString.toString();
1779             break;
1780         }
1781         case AbstractColumn::ColumnMode::Month: // never happens
1782         case AbstractColumn::ColumnMode::Day:
1783             break;
1784         }
1785     } else { // missing columns in this line
1786         switch (columnModes.at(col)) {
1787         case AbstractColumn::ColumnMode::Double:
1788             (*static_cast<QVector<double>*>(m_dataContainer[col]))[row] = nanValue;
1789             break;
1790         case AbstractColumn::ColumnMode::Integer:
1791             (*static_cast<QVector<int>*>(m_dataContainer[col]))[row] = 0;
1792             break;
1793         case AbstractColumn::ColumnMode::BigInt:
1794             (*static_cast<QVector<qint64>*>(m_dataContainer[col]))[row] = 0;
1795             break;
1796         case AbstractColumn::ColumnMode::DateTime:
1797             (*static_cast<QVector<QDateTime>*>(m_dataContainer[col]))[row] = QDateTime();
1798             break;
1799         case AbstractColumn::ColumnMode::Text:
1800             (*static_cast<QVector<QString>*>(m_dataContainer[col]))[row].clear();
1801             break;
1802         case AbstractColumn::ColumnMode::Month: // never happens
1803         case AbstractColumn::ColumnMode::Day:
1804             break;
1805         }
1806     }
1807 }
1808 
1809 void AsciiFilterPrivate::initDataContainer(Spreadsheet* spreadsheet) {
1810     DEBUG(Q_FUNC_INFO);
1811     const auto& columns = spreadsheet->children<Column>();
1812     for (int n = 0; n < m_actualCols; ++n) {
1813         // data() returns a void* which is a pointer to any data type (see ColumnPrivate.cpp)
1814         columns.at(n)->setColumnMode(columnModes.at(n));
1815         switch (columnModes.at(n)) {
1816         case AbstractColumn::ColumnMode::Double: {
1817             auto* vector = static_cast<QVector<double>*>(columns.at(n)->data());
1818             vector->reserve(m_actualRows);
1819             vector->resize(m_actualRows);
1820             m_dataContainer[n] = static_cast<void*>(vector);
1821             break;
1822         }
1823         case AbstractColumn::ColumnMode::Integer: {
1824             auto* vector = static_cast<QVector<int>*>(columns.at(n)->data());
1825             vector->resize(m_actualRows);
1826             m_dataContainer[n] = static_cast<void*>(vector);
1827             break;
1828         }
1829         case AbstractColumn::ColumnMode::BigInt: {
1830             auto* vector = static_cast<QVector<qint64>*>(columns.at(n)->data());
1831             vector->resize(m_actualRows);
1832             m_dataContainer[n] = static_cast<void*>(vector);
1833             break;
1834         }
1835         case AbstractColumn::ColumnMode::Text: {
1836             auto* vector = static_cast<QVector<QString>*>(columns.at(n)->data());
1837             vector->resize(m_actualRows);
1838             m_dataContainer[n] = static_cast<void*>(vector);
1839             break;
1840         }
1841         case AbstractColumn::ColumnMode::DateTime: {
1842             auto* vector = static_cast<QVector<QDateTime>*>(columns.at(n)->data());
1843             vector->resize(m_actualRows);
1844             m_dataContainer[n] = static_cast<void*>(vector);
1845             break;
1846         }
1847         // TODO
1848         case AbstractColumn::ColumnMode::Month:
1849         case AbstractColumn::ColumnMode::Day:
1850             break;
1851         }
1852     }
1853 }
1854 
1855 /*!
1856  * get a single line from device
1857  */
1858 QString AsciiFilterPrivate::getLine(QIODevice& device) {
1859     if (!device.canReadLine())
1860         DEBUG(Q_FUNC_INFO << ", WARNING: device cannot 'readLine()' but using it anyway.");
1861 
1862     if (device.atEnd()) {
1863         DEBUG(Q_FUNC_INFO << ", device at end! Giving up.");
1864         return {};
1865     }
1866 
1867     QString line = QString::fromUtf8(device.readLine());
1868     // DEBUG(Q_FUNC_INFO << ", line = " << STDSTRING(line));
1869 
1870     return line;
1871 }
1872 
1873 /*!
1874  * get a single line from device
1875  */
1876 QStringList AsciiFilterPrivate::getLineString(QIODevice& device) {
1877     QString line;
1878     do { // skip comment lines in data lines
1879         if (!device.canReadLine())
1880             DEBUG("WARNING in AsciiFilterPrivate::getLineString(): device cannot 'readLine()' but using it anyway.");
1881         //          line = device.readAll();
1882         line = QString::fromUtf8(device.readLine());
1883         QDEBUG("LINE:" << line)
1884     } while (!commentCharacter.isEmpty() && line.startsWith(commentCharacter));
1885 
1886     line.remove(QRegularExpression(QStringLiteral("[\\n\\r]"))); // remove any newline
1887     DEBUG("data line : \'" << STDSTRING(line) << '\'');
1888     auto lineStringList = split(line, false);
1889 
1890     // TODO: remove quotes here?
1891     if (simplifyWhitespacesEnabled) {
1892         for (int i = 0; i < lineStringList.size(); ++i)
1893             lineStringList[i] = lineStringList[i].simplified();
1894     }
1895 
1896     QDEBUG("data line, parsed: " << lineStringList);
1897 
1898     return lineStringList;
1899 }
1900 
1901 QStringList AsciiFilterPrivate::split(const QString& line, bool autoSeparator) {
1902     QStringList lineStringList;
1903     if (autoSeparator) {
1904         static const QRegularExpression regExp(QStringLiteral("[,;:]?\\s+"));
1905 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
1906         lineStringList = line.split(regExp, (Qt::SplitBehavior)skipEmptyParts);
1907 #else
1908         lineStringList = line.split(regExp, (QString::SplitBehavior)skipEmptyParts);
1909 #endif
1910         // TODO: determine the separator here and perform the merge of columns as in the else-case, if needed
1911     } else {
1912 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
1913         lineStringList = line.split(m_separator, (Qt::SplitBehavior)skipEmptyParts);
1914 #else
1915         lineStringList = line.split(m_separator, (QString::SplitBehavior)skipEmptyParts);
1916 #endif
1917 
1918         // merge the columns if they were splitted because of the separators inside the quotes
1919         for (int i = 0; i < lineStringList.size(); ++i) {
1920             if (lineStringList.at(i).simplified().startsWith(QLatin1Char('"')) && !lineStringList.at(i).simplified().endsWith(QLatin1Char('"'))) {
1921                 int mergeStart = i;
1922                 int mergeEnd = i;
1923                 for (; mergeEnd < lineStringList.size(); ++mergeEnd) {
1924                     if (lineStringList.at(mergeEnd).simplified().endsWith(QLatin1Char('"')))
1925                         break;
1926                 }
1927 
1928                 if (mergeStart != mergeEnd) {
1929                     for (int i = 0; i < (mergeEnd - mergeStart); ++i) {
1930                         if (mergeStart + 1 < lineStringList.count())
1931                             lineStringList[mergeStart] += m_separator + lineStringList.takeAt(mergeStart + 1);
1932                     }
1933                 }
1934             }
1935         }
1936     }
1937 
1938     return lineStringList;
1939 }
1940 
1941 /*!
1942     writes the content of \c dataSource to the file \c fileName.
1943 */
1944 void AsciiFilterPrivate::write(const QString& /*fileName*/, AbstractDataSource* /*dataSource*/) {
1945     // TODO: save data to ascii file
1946 }
1947 
1948 /*!
1949  * create datetime from \c string using \c format considering corner cases
1950  */
1951 QDateTime AsciiFilterPrivate::parseDateTime(const QString& string, const QString& format) {
1952     // DEBUG(Q_FUNC_INFO << ", string = " << STDSTRING(string) << ", format = " << STDSTRING(format))
1953     QString fixedString(string);
1954     QString fixedFormat(format);
1955     if (!format.contains(QLatin1String("yy"))) { // no year given: set temporary to 2000 (must be a leap year to parse "Feb 29")
1956         fixedString.append(QLatin1String(" 2000"));
1957         fixedFormat.append(QLatin1String(" yyyy"));
1958     }
1959 
1960     QDateTime dateTime = QDateTime::fromString(fixedString, fixedFormat);
1961     dateTime.setTimeSpec(Qt::UTC);
1962     // QDEBUG("fromString() =" << dateTime)
1963     //  interpret 2-digit year smaller than 50 as 20XX
1964     if (dateTime.date().year() < 1950 && !format.contains(QLatin1String("yyyy")))
1965         dateTime = dateTime.addYears(100);
1966     // QDEBUG("dateTime fixed =" << dateTime)
1967     // DEBUG(Q_FUNC_INFO << ", dateTime.toString = " << STDSTRING(dateTime.toString(format)))
1968 
1969     return dateTime;
1970 }
1971 
1972 // ##############################################################################
1973 // ##################  Serialization/Deserialization  ###########################
1974 // ##############################################################################
1975 /*!
1976   Saves as XML.
1977  */
1978 void AsciiFilter::save(QXmlStreamWriter* writer) const {
1979     writer->writeStartElement(QStringLiteral("asciiFilter"));
1980     writer->writeAttribute(QStringLiteral("commentCharacter"), d->commentCharacter);
1981     writer->writeAttribute(QStringLiteral("separatingCharacter"), d->separatingCharacter);
1982     writer->writeAttribute(QStringLiteral("autoMode"), QString::number(d->autoModeEnabled));
1983     writer->writeAttribute(QStringLiteral("createIndex"), QString::number(d->createIndexEnabled));
1984     writer->writeAttribute(QStringLiteral("createTimestamp"), QString::number(d->createTimestampEnabled));
1985     writer->writeAttribute(QStringLiteral("header"), QString::number(d->headerEnabled));
1986     writer->writeAttribute(QStringLiteral("vectorNames"), d->columnNames.join(QLatin1Char(' ')));
1987     writer->writeAttribute(QStringLiteral("skipEmptyParts"), QString::number(d->skipEmptyParts));
1988     writer->writeAttribute(QStringLiteral("simplifyWhitespaces"), QString::number(d->simplifyWhitespacesEnabled));
1989     writer->writeAttribute(QStringLiteral("nanValue"), QString::number(d->nanValue));
1990     writer->writeAttribute(QStringLiteral("removeQuotes"), QString::number(d->removeQuotesEnabled));
1991     writer->writeAttribute(QStringLiteral("startRow"), QString::number(d->startRow));
1992     writer->writeAttribute(QStringLiteral("endRow"), QString::number(d->endRow));
1993     writer->writeAttribute(QStringLiteral("startColumn"), QString::number(d->startColumn));
1994     writer->writeAttribute(QStringLiteral("endColumn"), QString::number(d->endColumn));
1995     writer->writeEndElement();
1996 }
1997 
1998 /*!
1999   Loads from XML.
2000 */
2001 bool AsciiFilter::load(XmlStreamReader* reader) {
2002     QXmlStreamAttributes attribs = reader->attributes();
2003     QString str;
2004 
2005     READ_STRING_VALUE("commentCharacter", commentCharacter);
2006     READ_STRING_VALUE("separatingCharacter", separatingCharacter);
2007 
2008     READ_INT_VALUE("createIndex", createIndexEnabled, bool);
2009     READ_INT_VALUE("createTimestamp", createTimestampEnabled, bool);
2010     READ_INT_VALUE("autoMode", autoModeEnabled, bool);
2011     READ_INT_VALUE("header", headerEnabled, bool);
2012 
2013     str = attribs.value(QStringLiteral("vectorNames")).toString();
2014     d->columnNames = str.split(QLatin1Char(' ')); // may be empty
2015 
2016     READ_INT_VALUE("simplifyWhitespaces", simplifyWhitespacesEnabled, bool);
2017     READ_DOUBLE_VALUE("nanValue", nanValue);
2018     READ_INT_VALUE("removeQuotes", removeQuotesEnabled, bool);
2019     READ_INT_VALUE("skipEmptyParts", skipEmptyParts, bool);
2020     READ_INT_VALUE("startRow", startRow, int);
2021     READ_INT_VALUE("endRow", endRow, int);
2022     READ_INT_VALUE("startColumn", startColumn, int);
2023     READ_INT_VALUE("endColumn", endColumn, int);
2024     return true;
2025 }
2026 
2027 // ##############################################################################
2028 // ########################## MQTT releated code  ###############################
2029 // ##############################################################################
2030 #ifdef HAVE_MQTT
2031 int AsciiFilterPrivate::prepareToRead(const QString& message) {
2032     QStringList lines = message.split(QLatin1Char('\n'));
2033     if (lines.isEmpty())
2034         return 1;
2035 
2036     // Parse the first line:
2037     // Determine the number of columns, create the columns and use (if selected) the first row to name them
2038     QString firstLine = lines.at(0);
2039     if (simplifyWhitespacesEnabled)
2040         firstLine = firstLine.simplified();
2041     DEBUG("First line: \'" << STDSTRING(firstLine) << '\'');
2042 
2043     // determine separator and split first line
2044     QStringList firstLineStringList;
2045     if (separatingCharacter == QLatin1String("auto")) {
2046         DEBUG("automatic separator");
2047         firstLineStringList = split(firstLine);
2048     } else { // use given separator
2049         // replace symbolic "TAB" with '\t'
2050         m_separator = separatingCharacter.replace(QLatin1String("2xTAB"), QLatin1String("\t\t"), Qt::CaseInsensitive);
2051         m_separator = separatingCharacter.replace(QLatin1String("TAB"), QLatin1String("\t"), Qt::CaseInsensitive);
2052         // replace symbolic "SPACE" with ' '
2053         m_separator = m_separator.replace(QLatin1String("2xSPACE"), QLatin1String("  "), Qt::CaseInsensitive);
2054         m_separator = m_separator.replace(QLatin1String("3xSPACE"), QLatin1String("   "), Qt::CaseInsensitive);
2055         m_separator = m_separator.replace(QLatin1String("4xSPACE"), QLatin1String("    "), Qt::CaseInsensitive);
2056         m_separator = m_separator.replace(QLatin1String("SPACE"), QLatin1String(" "), Qt::CaseInsensitive);
2057         firstLineStringList = split(firstLine, false);
2058     }
2059     DEBUG("separator: \'" << STDSTRING(m_separator) << '\'');
2060     DEBUG("number of columns: " << firstLineStringList.size());
2061     QDEBUG("first line: " << firstLineStringList);
2062 
2063     // all columns are read plus the optional column for the index and for the timestamp
2064     m_actualCols = firstLineStringList.size() + int(createIndexEnabled) + int(createTimestampEnabled);
2065 
2066     // column names:
2067     // when reading the message strings for different topics, it's not possible to specify vector names
2068     // since the different topics can have different content and different number of columns/vectors
2069     //->we always set the vector names here to fixed values
2070     columnNames.clear();
2071     columnModes.clear();
2072 
2073     // add index column
2074     if (createIndexEnabled) {
2075         columnNames << i18n("Index");
2076         columnModes << AbstractColumn::ColumnMode::Integer;
2077     }
2078 
2079     // add timestamp column
2080     if (createTimestampEnabled) {
2081         columnNames << i18n("Timestamp");
2082         columnModes << AbstractColumn::ColumnMode::DateTime;
2083     }
2084 
2085     // parse the first data line to determine data type for each column
2086     int i = 1;
2087     for (auto& valueString : firstLineStringList) {
2088         if (simplifyWhitespacesEnabled)
2089             valueString = valueString.simplified();
2090         if (removeQuotesEnabled)
2091             valueString.remove(QLatin1Char('"'));
2092 
2093         if (firstLineStringList.size() == 1)
2094             columnNames << i18n("Value");
2095         else
2096             columnNames << i18n("Value %1", i++);
2097         columnModes << AbstractFileFilter::columnMode(valueString, dateTimeFormat, numberFormat);
2098     }
2099 
2100     m_actualStartRow = startRow;
2101     m_actualRows = lines.size();
2102 
2103     //  QDEBUG("column modes = " << columnModes);
2104     DEBUG("actual cols/rows (w/o header): " << m_actualCols << ' ' << m_actualRows);
2105 
2106     return 0;
2107 }
2108 
2109 /*!
2110  * generates the preview for the string \s message.
2111  */
2112 QVector<QStringList> AsciiFilterPrivate::preview(const QString& message) {
2113     QVector<QStringList> dataStrings;
2114     prepareToRead(message);
2115 
2116     // number formatting
2117     DEBUG("locale = " << STDSTRING(QLocale::languageToString(numberFormat)));
2118 
2119     // Read the data
2120     QStringList lines = message.split(QLatin1Char('\n'));
2121 
2122     // loop over all lines in the message and parse the available columns
2123     int i = 0;
2124     for (auto line : lines) {
2125         if (simplifyWhitespacesEnabled)
2126             line = line.simplified();
2127 
2128         if (line.isEmpty() || (!commentCharacter.isEmpty() && line.startsWith(commentCharacter))) // skip empty or commented lines
2129             continue;
2130 
2131         auto lineStringList = split(line, false);
2132         QDEBUG(" line = " << lineStringList);
2133 
2134         QStringList lineString;
2135 
2136         // index column if required
2137         if (createIndexEnabled)
2138             lineString += QString::number(i + 1);
2139 
2140         // timestamp column if required
2141         if (createTimestampEnabled)
2142             lineString += QDateTime::currentDateTime().toString();
2143 
2144         int offset = int(createIndexEnabled) + int(createTimestampEnabled);
2145 
2146         // parse columns
2147         for (int n = 0; n < m_actualCols - offset; ++n) {
2148             if (n < lineStringList.size()) {
2149                 QString valueString = lineStringList.at(n);
2150                 if (removeQuotesEnabled)
2151                     valueString.remove(QLatin1Char('"'));
2152                 if (skipEmptyParts && !QString::compare(valueString, QLatin1String(" "))) // handle left white spaces
2153                     continue;
2154 
2155                 lineString += previewValue(valueString, columnModes[n + offset]);
2156             } else // missing columns in this line
2157                 lineString += QString();
2158         }
2159 
2160         ++i;
2161         dataStrings << lineString;
2162     }
2163 
2164     return dataStrings;
2165 }
2166 
2167 /*!
2168  * \brief reads the content of a message received by the topic.
2169  * Uses the settings defined in the MQTTTopic's MQTTClient
2170  * \param message
2171  * \param topic
2172  * \param dataSource
2173  */
2174 void AsciiFilterPrivate::readMQTTTopic(const QString& message, AbstractDataSource* dataSource) {
2175     // If the message is empty, there is nothing to do
2176     if (message.isEmpty()) {
2177         DEBUG("No new data available");
2178         return;
2179     }
2180 
2181     auto* spreadsheet = dynamic_cast<MQTTTopic*>(dataSource);
2182     if (!spreadsheet)
2183         return;
2184 
2185     const int keepNValues = spreadsheet->mqttClient()->keepNValues();
2186 
2187     if (!m_prepared) {
2188         DEBUG("Start preparing filter for: " << STDSTRING(spreadsheet->topicName()));
2189 
2190         // Prepare the filter
2191         const int mqttPrepareError = prepareToRead(message);
2192         if (mqttPrepareError != 0) {
2193             DEBUG("Mqtt Prepare Error = " << mqttPrepareError);
2194             return;
2195         }
2196 
2197         // prepare import for spreadsheet
2198         spreadsheet->setUndoAware(false);
2199         spreadsheet->resize(AbstractFileFilter::ImportMode::Replace, columnNames, m_actualCols);
2200 
2201         // columns in a MQTTTopic don't have any manual changes.
2202         // make the available columns undo unaware and suppress the "data changed" signal.
2203         // data changes will be propagated via an explicit Column::setChanged() call once new data was read.
2204         const auto& columns = spreadsheet->children<Column>();
2205         for (auto* column : columns) {
2206             column->setUndoAware(false);
2207             column->setSuppressDataChangedSignal(true);
2208         }
2209 
2210         if (keepNValues == 0)
2211             spreadsheet->setRowCount(m_actualRows > 1 ? m_actualRows : 1);
2212         else {
2213             spreadsheet->setRowCount(keepNValues);
2214             m_actualRows = keepNValues;
2215         }
2216 
2217         m_dataContainer.resize(m_actualCols);
2218         initDataContainer(spreadsheet);
2219     }
2220 
2221     MQTTClient::ReadingType readingType;
2222     if (!m_prepared) {
2223         // if filter is not prepared we read till the end
2224         readingType = MQTTClient::ReadingType::TillEnd;
2225     } else {
2226         // we have to read all the data when reading from end
2227         // so we set readingType to TillEnd
2228         if (spreadsheet->mqttClient()->readingType() == MQTTClient::ReadingType::FromEnd)
2229             readingType = MQTTClient::ReadingType::TillEnd;
2230         else
2231             readingType = spreadsheet->mqttClient()->readingType();
2232     }
2233 
2234     // count the new lines, increase actualrows on each
2235     // now we read all the new lines, if we want to use sample rate
2236     // then here we can do it, if we have actually sample rate number of lines :-?
2237     int newLinesForSampleSizeNotTillEnd = 0;
2238     int newLinesTillEnd = 0;
2239     QVector<QString> newData;
2240     if (readingType != MQTTClient::ReadingType::TillEnd) {
2241         newData.reserve(spreadsheet->mqttClient()->sampleSize());
2242         newData.resize(spreadsheet->mqttClient()->sampleSize());
2243     }
2244 
2245     // TODO: bool sampleSizeReached = false;
2246 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
2247     const QStringList newDataList = message.split(QRegularExpression(QStringLiteral("\n|\r\n|\r")), Qt::SkipEmptyParts);
2248 #else
2249     const QStringList newDataList = message.split(QRegularExpression(QStringLiteral("\n|\r\n|\r")), QString::SkipEmptyParts);
2250 #endif
2251     for (auto& line : newDataList) {
2252         newData.push_back(line);
2253         newLinesTillEnd++;
2254 
2255         if (readingType != MQTTClient::ReadingType::TillEnd) {
2256             newLinesForSampleSizeNotTillEnd++;
2257             // for Continuous reading and FromEnd we read sample rate number of lines if possible
2258             if (newLinesForSampleSizeNotTillEnd == spreadsheet->mqttClient()->sampleSize()) {
2259                 // TODO: sampleSizeReached = true;
2260                 break;
2261             }
2262         }
2263     }
2264 
2265     qDebug() << "Processing message done";
2266     // now we reset the readingType
2267     if (spreadsheet->mqttClient()->readingType() == MQTTClient::ReadingType::FromEnd)
2268         readingType = static_cast<MQTTClient::ReadingType>(spreadsheet->mqttClient()->readingType());
2269 
2270     // we had less new lines than the sample rate specified
2271     if (readingType != MQTTClient::ReadingType::TillEnd)
2272         qDebug() << "Removed empty lines: " << newData.removeAll(QString());
2273 
2274     const int spreadsheetRowCountBeforeResize = spreadsheet->rowCount();
2275 
2276     if (m_prepared) {
2277         if (keepNValues == 0)
2278             m_actualRows = spreadsheetRowCountBeforeResize;
2279         else {
2280             // if the keepNValues changed since the last read we have to manage the columns accordingly
2281             if (m_actualRows != keepNValues) {
2282                 if (m_actualRows < keepNValues) {
2283                     spreadsheet->setRowCount(keepNValues);
2284                     qDebug() << "rowcount set to: " << keepNValues;
2285                 }
2286 
2287                 // Calculate the difference between the old and new keepNValues
2288                 int rowDiff = 0;
2289                 if (m_actualRows > keepNValues)
2290                     rowDiff = m_actualRows - keepNValues;
2291 
2292                 if (m_actualRows < keepNValues)
2293                     rowDiff = keepNValues - m_actualRows;
2294 
2295                 const auto& columns = spreadsheet->children<Column>();
2296 
2297                 for (int n = 0; n < columnModes.size(); ++n) {
2298                     // data() returns a void* which is a pointer to any data type (see ColumnPrivate.cpp)
2299                     switch (columnModes.at(n)) {
2300                     case AbstractColumn::ColumnMode::Double: {
2301                         auto* vector = static_cast<QVector<double>*>(columns.at(n)->data());
2302                         m_dataContainer[n] = static_cast<void*>(vector);
2303 
2304                         // if the keepNValues got smaller then we move the last keepNValues count of data
2305                         // in the first keepNValues places
2306                         if (m_actualRows > keepNValues) {
2307                             for (int i = 0; i < keepNValues; i++) {
2308                                 static_cast<QVector<double>*>(m_dataContainer[n])->operator[](i) =
2309                                     static_cast<QVector<double>*>(m_dataContainer[n])->operator[](m_actualRows - keepNValues + i);
2310                             }
2311                         }
2312 
2313                         // if the keepNValues got bigger we move the existing values to the last m_actualRows positions
2314                         // then fill the remaining lines with NaN
2315                         if (m_actualRows < keepNValues) {
2316                             vector->reserve(keepNValues);
2317                             vector->resize(keepNValues);
2318 
2319                             for (int i = 1; i <= m_actualRows; i++) {
2320                                 static_cast<QVector<double>*>(m_dataContainer[n])->operator[](keepNValues - i) =
2321                                     static_cast<QVector<double>*>(m_dataContainer[n])->operator[](keepNValues - i - rowDiff);
2322                             }
2323                             for (int i = 0; i < rowDiff; i++)
2324                                 static_cast<QVector<double>*>(m_dataContainer[n])->operator[](i) = nanValue;
2325                         }
2326                         break;
2327                     }
2328                     case AbstractColumn::ColumnMode::Integer: {
2329                         auto* vector = static_cast<QVector<int>*>(columns.at(n)->data());
2330                         m_dataContainer[n] = static_cast<void*>(vector);
2331 
2332                         // if the keepNValues got smaller then we move the last keepNValues count of data
2333                         // in the first keepNValues places
2334                         if (m_actualRows > keepNValues) {
2335                             for (int i = 0; i < keepNValues; i++) {
2336                                 static_cast<QVector<int>*>(m_dataContainer[n])->operator[](i) =
2337                                     static_cast<QVector<int>*>(m_dataContainer[n])->operator[](m_actualRows - keepNValues + i);
2338                             }
2339                         }
2340 
2341                         // if the keepNValues got bigger we move the existing values to the last m_actualRows positions
2342                         // then fill the remaining lines with 0
2343                         if (m_actualRows < keepNValues) {
2344                             vector->reserve(keepNValues);
2345                             vector->resize(keepNValues);
2346                             for (int i = 1; i <= m_actualRows; i++) {
2347                                 static_cast<QVector<int>*>(m_dataContainer[n])->operator[](keepNValues - i) =
2348                                     static_cast<QVector<int>*>(m_dataContainer[n])->operator[](keepNValues - i - rowDiff);
2349                             }
2350                             for (int i = 0; i < rowDiff; i++)
2351                                 static_cast<QVector<int>*>(m_dataContainer[n])->operator[](i) = 0;
2352                         }
2353                         break;
2354                     }
2355                     case AbstractColumn::ColumnMode::BigInt: {
2356                         auto* vector = static_cast<QVector<qint64>*>(columns.at(n)->data());
2357                         m_dataContainer[n] = static_cast<void*>(vector);
2358 
2359                         // if the keepNValues got smaller then we move the last keepNValues count of data
2360                         // in the first keepNValues places
2361                         if (m_actualRows > keepNValues) {
2362                             for (int i = 0; i < keepNValues; i++) {
2363                                 static_cast<QVector<qint64>*>(m_dataContainer[n])->operator[](i) =
2364                                     static_cast<QVector<qint64>*>(m_dataContainer[n])->operator[](m_actualRows - keepNValues + i);
2365                             }
2366                         }
2367 
2368                         // if the keepNValues got bigger we move the existing values to the last m_actualRows positions
2369                         // then fill the remaining lines with 0
2370                         if (m_actualRows < keepNValues) {
2371                             vector->reserve(keepNValues);
2372                             vector->resize(keepNValues);
2373                             for (int i = 1; i <= m_actualRows; i++) {
2374                                 static_cast<QVector<qint64>*>(m_dataContainer[n])->operator[](keepNValues - i) =
2375                                     static_cast<QVector<qint64>*>(m_dataContainer[n])->operator[](keepNValues - i - rowDiff);
2376                             }
2377                             for (int i = 0; i < rowDiff; i++)
2378                                 static_cast<QVector<qint64>*>(m_dataContainer[n])->operator[](i) = 0;
2379                         }
2380                         break;
2381                     }
2382                     case AbstractColumn::ColumnMode::Text: {
2383                         auto* vector = static_cast<QVector<QString>*>(columns.at(n)->data());
2384                         m_dataContainer[n] = static_cast<void*>(vector);
2385 
2386                         // if the keepNValues got smaller then we move the last keepNValues count of data
2387                         // in the first keepNValues places
2388                         if (m_actualRows > keepNValues) {
2389                             for (int i = 0; i < keepNValues; i++) {
2390                                 static_cast<QVector<QString>*>(m_dataContainer[n])->operator[](i) =
2391                                     static_cast<QVector<QString>*>(m_dataContainer[n])->operator[](m_actualRows - keepNValues + i);
2392                             }
2393                         }
2394 
2395                         // if the keepNValues got bigger we move the existing values to the last m_actualRows positions
2396                         // then fill the remaining lines with empty lines
2397                         if (m_actualRows < keepNValues) {
2398                             vector->reserve(keepNValues);
2399                             vector->resize(keepNValues);
2400                             for (int i = 1; i <= m_actualRows; i++) {
2401                                 static_cast<QVector<QString>*>(m_dataContainer[n])->operator[](keepNValues - i) =
2402                                     static_cast<QVector<QString>*>(m_dataContainer[n])->operator[](keepNValues - i - rowDiff);
2403                             }
2404                             for (int i = 0; i < rowDiff; i++)
2405                                 static_cast<QVector<QString>*>(m_dataContainer[n])->operator[](i).clear();
2406                         }
2407                         break;
2408                     }
2409                     case AbstractColumn::ColumnMode::DateTime: {
2410                         auto* vector = static_cast<QVector<QDateTime>*>(columns.at(n)->data());
2411                         m_dataContainer[n] = static_cast<void*>(vector);
2412 
2413                         // if the keepNValues got smaller then we move the last keepNValues count of data
2414                         // in the first keepNValues places
2415                         if (m_actualRows > keepNValues) {
2416                             for (int i = 0; i < keepNValues; i++) {
2417                                 static_cast<QVector<QDateTime>*>(m_dataContainer[n])->operator[](i) =
2418                                     static_cast<QVector<QDateTime>*>(m_dataContainer[n])->operator[](m_actualRows - keepNValues + i);
2419                             }
2420                         }
2421 
2422                         // if the keepNValues got bigger we move the existing values to the last m_actualRows positions
2423                         // then fill the remaining lines with null datetime
2424                         if (m_actualRows < keepNValues) {
2425                             vector->reserve(keepNValues);
2426                             vector->resize(keepNValues);
2427                             for (int i = 1; i <= m_actualRows; i++) {
2428                                 static_cast<QVector<QDateTime>*>(m_dataContainer[n])->operator[](keepNValues - i) =
2429                                     static_cast<QVector<QDateTime>*>(m_dataContainer[n])->operator[](keepNValues - i - rowDiff);
2430                             }
2431                             for (int i = 0; i < rowDiff; i++)
2432                                 static_cast<QVector<QDateTime>*>(m_dataContainer[n])->operator[](i) = QDateTime();
2433                         }
2434                         break;
2435                     }
2436                     // TODO
2437                     case AbstractColumn::ColumnMode::Month:
2438                     case AbstractColumn::ColumnMode::Day:
2439                         break;
2440                     }
2441                 }
2442                 // if the keepNValues got smaller resize the spreadsheet
2443                 if (m_actualRows > keepNValues)
2444                     spreadsheet->setRowCount(keepNValues);
2445 
2446                 // set the new row count
2447                 m_actualRows = keepNValues;
2448                 qDebug() << "actual rows: " << m_actualRows;
2449             }
2450         }
2451     }
2452 
2453     qDebug() << "starting m_actual rows calculated: " << m_actualRows << ", new data size: " << newData.size();
2454 
2455     int currentRow = 0; // indexes the position in the vector(column)
2456     int linesToRead = 0;
2457 
2458     if (m_prepared) {
2459         // increase row count if we don't have a fixed size
2460         // but only after the preparation step
2461         if (keepNValues == 0) {
2462             if (readingType != MQTTClient::ReadingType::TillEnd)
2463 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
2464                 m_actualRows += std::min(newData.size(), static_cast<qsizetype>(spreadsheet->mqttClient()->sampleSize()));
2465 #else
2466                 m_actualRows += std::min(newData.size(), spreadsheet->mqttClient()->sampleSize());
2467 #endif
2468             else {
2469                 m_actualRows += newData.size();
2470             }
2471         }
2472 
2473         // fixed size
2474         if (keepNValues != 0) {
2475             if (readingType == MQTTClient::ReadingType::TillEnd) {
2476                 // we had more lines than the fixed size, so we read m_actualRows number of lines
2477                 if (newLinesTillEnd > m_actualRows) {
2478                     linesToRead = m_actualRows;
2479                 } else
2480                     linesToRead = newLinesTillEnd;
2481             } else {
2482                 // we read max sample size number of lines when the reading mode
2483                 // is ContinuouslyFixed or FromEnd
2484                 if (spreadsheet->mqttClient()->sampleSize() <= keepNValues)
2485                     linesToRead = std::min(spreadsheet->mqttClient()->sampleSize(), newLinesTillEnd);
2486                 else
2487                     linesToRead = std::min(keepNValues, newLinesTillEnd);
2488             }
2489         } else
2490             linesToRead = m_actualRows - spreadsheetRowCountBeforeResize;
2491 
2492         if (linesToRead == 0)
2493             return;
2494     } else {
2495         if (keepNValues != 0)
2496             linesToRead = newLinesTillEnd > m_actualRows ? m_actualRows : newLinesTillEnd;
2497         else
2498             linesToRead = newLinesTillEnd;
2499     }
2500     qDebug() << "linestoread = " << linesToRead;
2501 
2502     // new rows/resize columns if we don't have a fixed size
2503     if (keepNValues == 0) {
2504 #ifdef PERFTRACE_LIVE_IMPORT
2505         PERFTRACE(QStringLiteral("AsciiLiveDataImportResizing: "));
2506 #endif
2507         if (spreadsheet->rowCount() < m_actualRows)
2508             spreadsheet->setRowCount(m_actualRows);
2509 
2510         if (!m_prepared)
2511             currentRow = 0;
2512         else {
2513             // indexes the position in the vector(column)
2514             currentRow = spreadsheetRowCountBeforeResize;
2515         }
2516 
2517         // if we have fixed size, we do this only once in preparation, here we can use
2518         // m_prepared and we need something to decide whether it has a fixed size or increasing
2519 
2520         initDataContainer(spreadsheet);
2521     } else {
2522         // when we have a fixed size we have to pop sampleSize number of lines if specified
2523         // here popping, setting currentRow
2524         if (!m_prepared)
2525             currentRow = m_actualRows - std::min(newLinesTillEnd, m_actualRows);
2526         else {
2527             if (readingType == MQTTClient::ReadingType::TillEnd) {
2528                 if (newLinesTillEnd > m_actualRows)
2529                     currentRow = 0;
2530                 else
2531                     currentRow = m_actualRows - newLinesTillEnd;
2532             } else {
2533                 // we read max sample rate number of lines when the reading mode
2534                 // is ContinuouslyFixed or FromEnd
2535                 currentRow = m_actualRows - linesToRead;
2536             }
2537         }
2538 
2539         if (m_prepared) {
2540 #ifdef PERFTRACE_LIVE_IMPORT
2541             PERFTRACE(QStringLiteral("AsciiLiveDataImportPopping: "));
2542 #endif
2543             const auto& columns = spreadsheet->children<Column>();
2544             for (int row = 0; row < linesToRead; ++row) {
2545                 for (int col = 0; col < m_actualCols; ++col) {
2546                     switch (columnModes[col]) {
2547                     case AbstractColumn::ColumnMode::Double: {
2548                         auto* vector = static_cast<QVector<double>*>(columns.at(col)->data());
2549                         vector->pop_front();
2550                         vector->reserve(m_actualRows);
2551                         vector->resize(m_actualRows);
2552                         m_dataContainer[col] = static_cast<void*>(vector);
2553                         break;
2554                     }
2555                     case AbstractColumn::ColumnMode::Integer: {
2556                         auto* vector = static_cast<QVector<int>*>(columns.at(col)->data());
2557                         vector->pop_front();
2558                         vector->reserve(m_actualRows);
2559                         vector->resize(m_actualRows);
2560                         m_dataContainer[col] = static_cast<void*>(vector);
2561                         break;
2562                     }
2563                     case AbstractColumn::ColumnMode::BigInt: {
2564                         auto* vector = static_cast<QVector<qint64>*>(columns.at(col)->data());
2565                         vector->pop_front();
2566                         vector->reserve(m_actualRows);
2567                         vector->resize(m_actualRows);
2568                         m_dataContainer[col] = static_cast<void*>(vector);
2569                         break;
2570                     }
2571                     case AbstractColumn::ColumnMode::Text: {
2572                         auto* vector = static_cast<QVector<QString>*>(columns.at(col)->data());
2573                         vector->pop_front();
2574                         vector->reserve(m_actualRows);
2575                         vector->resize(m_actualRows);
2576                         m_dataContainer[col] = static_cast<void*>(vector);
2577                         break;
2578                     }
2579                     case AbstractColumn::ColumnMode::DateTime: {
2580                         auto* vector = static_cast<QVector<QDateTime>*>(columns.at(col)->data());
2581                         vector->pop_front();
2582                         vector->reserve(m_actualRows);
2583                         vector->resize(m_actualRows);
2584                         m_dataContainer[col] = static_cast<void*>(vector);
2585                         break;
2586                     }
2587                     // TODO
2588                     case AbstractColumn::ColumnMode::Month:
2589                     case AbstractColumn::ColumnMode::Day:
2590                         break;
2591                     }
2592                 }
2593             }
2594         }
2595     }
2596 
2597     // from the last row we read the new data in the spreadsheet
2598     qDebug() << "reading from line: " << currentRow << " lines till end: " << newLinesTillEnd;
2599     qDebug() << "Lines to read: " << linesToRead << " actual rows: " << m_actualRows;
2600     int newDataIdx = 0;
2601     // From end means that we read the last sample size amount of data
2602     if (readingType == MQTTClient::ReadingType::FromEnd) {
2603         if (m_prepared) {
2604             if (newData.size() > spreadsheet->mqttClient()->sampleSize())
2605                 newDataIdx = newData.size() - spreadsheet->mqttClient()->sampleSize();
2606         }
2607     }
2608 
2609     qDebug() << "newDataIdx: " << newDataIdx;
2610 
2611     // read the data
2612     static int indexColumnIdx = 0;
2613     {
2614 #ifdef PERFTRACE_LIVE_IMPORT
2615         PERFTRACE(QStringLiteral("AsciiLiveDataImportFillingContainers: "));
2616 #endif
2617         int row = 0;
2618         for (; row < linesToRead; ++row) {
2619             QString line;
2620             if (readingType == MQTTClient::ReadingType::FromEnd)
2621                 line = newData.at(newDataIdx++);
2622             else
2623                 line = newData.at(row);
2624 
2625             if (removeQuotesEnabled)
2626                 line.remove(QLatin1Char('"'));
2627 
2628             if (line.isEmpty() || (!commentCharacter.isEmpty() && line.startsWith(commentCharacter)))
2629                 continue;
2630 
2631             auto lineStringList = split(line, false);
2632 
2633             if (simplifyWhitespacesEnabled) {
2634                 for (int i = 0; i < lineStringList.size(); ++i)
2635                     lineStringList[i] = lineStringList[i].simplified();
2636             }
2637 
2638             // add index if required
2639             int offset = 0;
2640             if (createIndexEnabled) {
2641                 int index = (keepNValues == 0) ? currentRow + 1 : indexColumnIdx++;
2642                 static_cast<QVector<int>*>(m_dataContainer[0])->operator[](currentRow) = index;
2643                 ++offset;
2644             }
2645 
2646             // add current timestamp if required
2647             if (createTimestampEnabled) {
2648                 static_cast<QVector<QDateTime>*>(m_dataContainer[offset])->operator[](currentRow) = QDateTime::currentDateTime();
2649                 ++offset;
2650             }
2651 
2652             // parse the columns
2653             for (int n = 0; n < m_actualCols - offset; ++n) {
2654                 int col = n + offset;
2655                 QString valueString;
2656                 if (n < lineStringList.size())
2657                     valueString = lineStringList.at(n);
2658 
2659                 setValue(col, currentRow, valueString);
2660             }
2661             currentRow++;
2662         }
2663     }
2664 
2665     if (m_prepared) {
2666         // notify all affected columns and plots about the changes
2667         PERFTRACE(QStringLiteral("AsciiLiveDataImport, notify affected columns and plots"));
2668 
2669         const Project* project = spreadsheet->project();
2670         QVector<const XYCurve*> curves = project->children<const XYCurve>(AbstractAspect::ChildIndexFlag::Recursive);
2671         QVector<CartesianPlot*> plots;
2672 
2673         for (int n = 0; n < m_actualCols; ++n) {
2674             Column* column = spreadsheet->column(n);
2675 
2676             // determine the plots where the column is consumed
2677             for (const auto* curve : curves) {
2678                 if (curve->xColumn() == column || curve->yColumn() == column) {
2679                     auto* plot = static_cast<CartesianPlot*>(curve->parentAspect());
2680                     if (plots.indexOf(plot) == -1) {
2681                         plots << plot;
2682                         plot->setSuppressRetransform(true);
2683                     }
2684                 }
2685             }
2686 
2687             column->setChanged();
2688         }
2689 
2690         // loop over all affected plots and retransform them
2691         for (auto* const plot : plots) {
2692             // TODO setting this back to true triggers again a lot of retransforms in the plot (one for each curve).
2693             //                  plot->setSuppressDataChangedSignal(false);
2694             plot->dataChanged(-1, -1); // TODO: check if all ranges must be updated!
2695         }
2696     } else
2697         m_prepared = true;
2698 
2699     DEBUG(Q_FUNC_INFO << ", DONE");
2700 }
2701 
2702 /*!
2703  * \brief After the MQTTTopic was loaded, the filter is prepared for reading
2704  * \param prepared
2705  * \param topic
2706  * \param separator
2707  */
2708 void AsciiFilterPrivate::setPreparedForMQTT(bool prepared, MQTTTopic* topic, const QString& separator) {
2709     m_prepared = prepared;
2710     // If originally it was prepared we have to restore the settings
2711     if (prepared) {
2712         m_separator = separator;
2713         m_actualCols = endColumn - startColumn + 1;
2714         m_actualRows = topic->rowCount();
2715         // set the column modes
2716         columnModes.resize(topic->columnCount());
2717         for (int i = 0; i < topic->columnCount(); ++i)
2718             columnModes[i] = topic->column(i)->columnMode();
2719 
2720         // set the data containers
2721         m_dataContainer.resize(m_actualCols);
2722         initDataContainer(topic);
2723     }
2724 }
2725 #endif