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