#include "AmdaResultParser.h" #include #include #include #include #include #include #include Q_LOGGING_CATEGORY(LOG_AmdaResultParser, "AmdaResultParser") namespace { /// Message in result file when the file was not found on server const auto FILE_NOT_FOUND_MESSAGE = QStringLiteral("Not Found"); /// Separator between values in a result line const auto RESULT_LINE_SEPARATOR = QRegularExpression{QStringLiteral("\\s+")}; // AMDA V2 // /// Regex to find the header of the data in the file. This header indicates the end of comments // in // /// the file // const auto DATA_HEADER_REGEX = QRegularExpression{QStringLiteral("#\\s*DATA\\s*:")}; /// Format for dates in result files const auto DATE_FORMAT = QStringLiteral("yyyy-MM-ddThh:mm:ss.zzz"); // AMDA V2 // /// Regex to find unit in a line. Examples of valid lines: // /// ... PARAMETER_UNITS : nT ... // /// ... PARAMETER_UNITS:nT ... // /// ... PARAMETER_UNITS: m² ... // /// ... PARAMETER_UNITS : m/s ... // const auto UNIT_REGEX = QRegularExpression{QStringLiteral("\\s*PARAMETER_UNITS\\s*:\\s*(.+)")}; /// ... - Units : nT - ... /// ... -Units:nT- ... /// ... -Units: m²- ... /// ... - Units : m/s - ... const auto UNIT_REGEX = QRegularExpression{QStringLiteral("-\\s*Units\\s*:\\s*(.+?)\\s*-")}; QDateTime dateTimeFromString(const QString &stringDate) noexcept { #if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0) return QDateTime::fromString(stringDate, Qt::ISODateWithMs); #else return QDateTime::fromString(stringDate, DATE_FORMAT); #endif } /// Converts a string date to a double date /// @return a double that represents the date in seconds, NaN if the string date can't be converted double doubleDate(const QString &stringDate) noexcept { // Format: yyyy-MM-ddThh:mm:ss.zzz auto dateTime = dateTimeFromString(stringDate); dateTime.setTimeSpec(Qt::UTC); return dateTime.isValid() ? DateUtils::secondsSinceEpoch(dateTime) : std::numeric_limits::quiet_NaN(); } /// Checks if a line is a comment line bool isCommentLine(const QString &line) { return line.startsWith("#"); } /// @return the number of lines to be read depending on the type of value passed in parameter int nbValues(AmdaResultParser::ValueType valueType) noexcept { switch (valueType) { case AmdaResultParser::ValueType::SCALAR: return 1; case AmdaResultParser::ValueType::VECTOR: return 3; case AmdaResultParser::ValueType::UNKNOWN: // Invalid case break; } // Invalid cases qCCritical(LOG_AmdaResultParser()) << QObject::tr("Can't get the number of values to read: unsupported type"); return 0; } /** * Reads stream to retrieve x-axis unit * @param stream the stream to read * @return the unit that has been read in the stream, a default unit (time unit with no label) if an * error occured during reading */ Unit readXAxisUnit(QTextStream &stream) { QString line{}; // Searches unit in the comment lines (as long as the reading has not reached the data header) // AMDA V2: while (stream.readLineInto(&line) && !line.contains(DATA_HEADER_REGEX)) { while (stream.readLineInto(&line) && isCommentLine(line)) { auto match = UNIT_REGEX.match(line); if (match.hasMatch()) { return Unit{match.captured(1), true}; } } qCWarning(LOG_AmdaResultParser()) << QObject::tr("The unit could not be found in the file"); // Error cases return Unit{{}, true}; } /** * Reads stream to retrieve results * @param stream the stream to read * @return the pair of vectors x-axis data/values data that has been read in the stream */ std::pair, std::vector > readResults(QTextStream &stream, AmdaResultParser::ValueType valueType) { auto expectedNbValues = nbValues(valueType) + 1; auto xData = std::vector{}; auto valuesData = std::vector{}; QString line{}; // Skip comment lines while (stream.readLineInto(&line) && isCommentLine(line)) { } if (!stream.atEnd()) { do { auto lineData = line.split(RESULT_LINE_SEPARATOR, QString::SkipEmptyParts); if (lineData.size() == expectedNbValues) { // X : the data is converted from date to double (in secs) auto x = doubleDate(lineData.at(0)); // Adds result only if x is valid. Then, if value is invalid, it is set to NaN if (!std::isnan(x)) { xData.push_back(x); // Values for (auto valueIndex = 1; valueIndex < expectedNbValues; ++valueIndex) { auto column = valueIndex; bool valueOk; auto value = lineData.at(column).toDouble(&valueOk); if (!valueOk) { qCWarning(LOG_AmdaResultParser()) << QObject::tr( "Value from (line %1, column %2) is invalid and will be " "converted to NaN") .arg(line, column); value = std::numeric_limits::quiet_NaN(); } valuesData.push_back(value); } } else { qCWarning(LOG_AmdaResultParser()) << QObject::tr("Can't retrieve results from line %1: x is invalid") .arg(line); } } else { qCWarning(LOG_AmdaResultParser()) << QObject::tr("Can't retrieve results from line %1: invalid line").arg(line); } } while (stream.readLineInto(&line)); } return std::make_pair(std::move(xData), std::move(valuesData)); } } // namespace std::shared_ptr AmdaResultParser::readTxt(const QString &filePath, ValueType valueType) noexcept { if (valueType == ValueType::UNKNOWN) { qCCritical(LOG_AmdaResultParser()) << QObject::tr("Can't retrieve AMDA data: the type of values to be read is unknown"); return nullptr; } QFile file{filePath}; if (!file.open(QFile::ReadOnly | QIODevice::Text)) { qCCritical(LOG_AmdaResultParser()) << QObject::tr("Can't retrieve AMDA data from file %1: %2") .arg(filePath, file.errorString()); return nullptr; } QTextStream stream{&file}; // Checks if the file was found on the server auto firstLine = stream.readLine(); if (firstLine.compare(FILE_NOT_FOUND_MESSAGE) == 0) { qCCritical(LOG_AmdaResultParser()) << QObject::tr("Can't retrieve AMDA data from file %1: file was not found on server") .arg(filePath); return nullptr; } // Reads x-axis unit stream.seek(0); // returns to the beginning of the file auto xAxisUnit = readXAxisUnit(stream); if (xAxisUnit.m_Name.isEmpty()) { return nullptr; } // Reads results // AMDA V2: remove line stream.seek(0); // returns to the beginning of the file auto results = readResults(stream, valueType); // Creates data series switch (valueType) { case ValueType::SCALAR: return std::make_shared(std::move(results.first), std::move(results.second), xAxisUnit, Unit{}); case ValueType::VECTOR: return std::make_shared(std::move(results.first), std::move(results.second), xAxisUnit, Unit{}); case ValueType::UNKNOWN: // Invalid case break; } // Invalid cases qCCritical(LOG_AmdaResultParser()) << QObject::tr("Can't create data series: unsupported value type"); return nullptr; }