mirror of
https://github.com/OPM/ResInsight.git
synced 2025-01-08 07:03:25 -06:00
974 lines
34 KiB
C++
974 lines
34 KiB
C++
/////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright (C) 2017- Statoil ASA
|
|
//
|
|
// ResInsight is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
// FITNESS FOR A PARTICULAR PURPOSE.
|
|
//
|
|
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
|
|
// for more details.
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "RifCsvUserDataParser.h"
|
|
|
|
#include "RifEclipseSummaryAddress.h"
|
|
#include "RifEclipseUserDataKeywordTools.h"
|
|
#include "RifEclipseUserDataParserTools.h"
|
|
#include "RifFileParseTools.h"
|
|
|
|
#include "RiaDateStringParser.h"
|
|
#include "RiaLogging.h"
|
|
#include "RiaQDateTimeTools.h"
|
|
#include "RiaStdStringTools.h"
|
|
#include "RiaTextStringTools.h"
|
|
|
|
#include "SummaryPlotCommands/RicPasteAsciiDataToSummaryPlotFeatureUi.h"
|
|
|
|
#include "caf.h"
|
|
#include "cvfAssert.h"
|
|
|
|
#include <QFile>
|
|
#include <QString>
|
|
#include <QTextStream>
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <limits>
|
|
#include <utility>
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
/// Internal constants
|
|
//--------------------------------------------------------------------------------------------------
|
|
#define DOUBLE_INF std::numeric_limits<double>::infinity()
|
|
|
|
#define ISO_DATE_FORMAT "yyyy-MM-dd"
|
|
#define TIME_FORMAT "hh:mm:ss"
|
|
|
|
using Sample = std::pair<time_t, double>;
|
|
using SampleList = std::vector<Sample>;
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
enum class CsvLineBasedColumnType
|
|
{
|
|
DATE,
|
|
VECTOR,
|
|
VALUE,
|
|
ERROR_VALUE,
|
|
COMMENTS
|
|
};
|
|
const std::vector<QString> CSV_LINE_BASED_COL_NAMES = { "DATE", "VECTOR", "VALUE", "ERROR", "COMMENTS" };
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
RifCsvUserDataParser::RifCsvUserDataParser( QString* errorText )
|
|
: m_errorText( errorText )
|
|
{
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
RifCsvUserDataParser::~RifCsvUserDataParser()
|
|
{
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifCsvUserDataParser::parse( const AsciiDataParseOptions& parseOptions, const std::map<QString, std::pair<QString, double>>& unitMapping )
|
|
{
|
|
if ( determineCsvLayout() == LineBased ) return parseLineBasedData();
|
|
return parseColumnBasedData( parseOptions, unitMapping );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
const TableData& RifCsvUserDataParser::tableData() const
|
|
{
|
|
return m_tableData;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
const Column* RifCsvUserDataParser::columnInfo( size_t columnIndex ) const
|
|
{
|
|
if ( columnIndex >= m_tableData.columnInfos().size() ) return nullptr;
|
|
|
|
return &( m_tableData.columnInfos()[columnIndex] );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
const Column* RifCsvUserDataParser::dateTimeColumn() const
|
|
{
|
|
for ( const Column& col : m_tableData.columnInfos() )
|
|
{
|
|
if ( col.dataType == Column::DATETIME )
|
|
{
|
|
return &col;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
std::vector<int> RifCsvUserDataParser::parseLineBasedHeader( QStringList headerCols )
|
|
{
|
|
std::vector<int> colIndexes;
|
|
|
|
for ( int i = 0; i < (int)CSV_LINE_BASED_COL_NAMES.size(); i++ )
|
|
{
|
|
for ( int j = 0; j < (int)headerCols.size(); j++ )
|
|
{
|
|
if ( headerCols[j] == CSV_LINE_BASED_COL_NAMES[i] )
|
|
{
|
|
colIndexes.push_back( j );
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( i < 3 && (int)colIndexes.size() < i + 1 ) return {};
|
|
}
|
|
return colIndexes;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifCsvUserDataParser::parseColumnInfo( const AsciiDataParseOptions& parseOptions )
|
|
{
|
|
QTextStream* dataStream = openDataStream();
|
|
std::vector<Column> columnInfoList;
|
|
bool result = parseColumnInfo( dataStream, parseOptions, &columnInfoList );
|
|
|
|
if ( result )
|
|
{
|
|
m_tableData = TableData( "", "", columnInfoList );
|
|
}
|
|
closeDataStream();
|
|
return result;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
QString RifCsvUserDataParser::previewText( int lineCount, const AsciiDataParseOptions& parseOptions )
|
|
{
|
|
QTextStream* stream = openDataStream();
|
|
|
|
if ( !stream ) return "";
|
|
|
|
QString preview;
|
|
QTextStream outStream( &preview );
|
|
int iLine = 0;
|
|
bool header = true;
|
|
int timeColumnIndex = -1;
|
|
|
|
outStream << "<Table>";
|
|
outStream << "<Style> th, td {padding-right: 15px;} </Style>";
|
|
while ( iLine < lineCount && !stream->atEnd() )
|
|
{
|
|
QString line = stream->readLine();
|
|
|
|
if ( line.isEmpty() ) continue;
|
|
|
|
outStream << "<tr>";
|
|
int iCol = 0;
|
|
QStringList cols = RifFileParseTools::splitLineAndTrim( line, parseOptions.cellSeparator );
|
|
for ( const QString& cellData : cols )
|
|
{
|
|
if ( cellData == parseOptions.timeSeriesColumnName && header )
|
|
{
|
|
timeColumnIndex = iCol;
|
|
}
|
|
|
|
outStream << ( header ? "<th" : "<td" );
|
|
|
|
if ( iCol == timeColumnIndex )
|
|
{
|
|
outStream << " style=\"background-color: #FFFFD0;\"";
|
|
}
|
|
outStream << ">";
|
|
outStream << cellData;
|
|
if ( iCol < cols.size() - 1 && ( parseOptions.cellSeparator == ";" || parseOptions.cellSeparator == "," ) )
|
|
{
|
|
outStream << parseOptions.cellSeparator;
|
|
}
|
|
outStream << ( header ? "</th>" : "</td>" );
|
|
|
|
iCol++;
|
|
}
|
|
outStream << "</tr>";
|
|
|
|
header = false;
|
|
iLine++;
|
|
}
|
|
|
|
outStream << "</Table>";
|
|
|
|
closeDataStream();
|
|
return preview;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
QStringList RifCsvUserDataParser::timeColumnPreviewData( int lineCount, const AsciiDataParseOptions& parseOptions )
|
|
{
|
|
QStringList timeStrings;
|
|
|
|
QTextStream* stream = openDataStream();
|
|
|
|
if ( stream )
|
|
{
|
|
int timeColumnIndex = -1;
|
|
int iLine = 0;
|
|
|
|
while ( iLine < lineCount && !stream->atEnd() )
|
|
{
|
|
QString line = stream->readLine();
|
|
|
|
if ( line.isEmpty() ) continue;
|
|
|
|
int iCol = 0;
|
|
QStringList cols = RifFileParseTools::splitLineAndTrim( line, parseOptions.cellSeparator );
|
|
for ( const QString& cellData : cols )
|
|
{
|
|
if ( cellData == parseOptions.timeSeriesColumnName && iLine == 0 )
|
|
{
|
|
timeColumnIndex = iCol;
|
|
}
|
|
|
|
if ( iLine > 0 && timeColumnIndex != -1 && timeColumnIndex == iCol )
|
|
{
|
|
timeStrings.push_back( cellData );
|
|
}
|
|
|
|
iCol++;
|
|
}
|
|
|
|
iLine++;
|
|
}
|
|
}
|
|
|
|
closeDataStream();
|
|
|
|
return timeStrings;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
RifCsvUserDataParser::CsvLayout RifCsvUserDataParser::determineCsvLayout()
|
|
{
|
|
QTextStream* dataStream = openDataStream();
|
|
if ( !dataStream ) return LineBased;
|
|
|
|
QString firstLine;
|
|
|
|
QStringList headers;
|
|
while ( !dataStream->atEnd() )
|
|
{
|
|
firstLine = dataStream->readLine();
|
|
if ( firstLine.isEmpty() ) continue;
|
|
headers = firstLine.split( ';' );
|
|
if ( headers.size() < 3 || headers.size() > 5 ) continue;
|
|
break;
|
|
}
|
|
closeDataStream();
|
|
|
|
if ( headers.contains( CSV_LINE_BASED_COL_NAMES[(size_t)CsvLineBasedColumnType::DATE] ) &&
|
|
headers.contains( CSV_LINE_BASED_COL_NAMES[(size_t)CsvLineBasedColumnType::VECTOR] ) &&
|
|
headers.contains( CSV_LINE_BASED_COL_NAMES[(size_t)CsvLineBasedColumnType::VALUE] ) )
|
|
return LineBased;
|
|
return ColumnBased;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifCsvUserDataParser::parseColumnInfo( QTextStream* dataStream,
|
|
const AsciiDataParseOptions& parseOptions,
|
|
std::vector<Column>* columnInfoList,
|
|
const std::map<QString, std::pair<QString, double>>& unitMapping )
|
|
{
|
|
bool headerFound = false;
|
|
|
|
if ( !columnInfoList ) return false;
|
|
|
|
columnInfoList->clear();
|
|
while ( !headerFound )
|
|
{
|
|
QString line = dataStream->readLine();
|
|
if ( line.trimmed().isEmpty() )
|
|
{
|
|
if ( !headerFound && dataStream->atEnd() )
|
|
{
|
|
// Handle empty stream
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// Empty lines are skipped.
|
|
continue;
|
|
}
|
|
}
|
|
|
|
QStringList columnHeaders = RifFileParseTools::splitLineAndTrim( line, parseOptions.cellSeparator );
|
|
|
|
// Optional support for unit text (second header line) and names (third header line)
|
|
QStringList unitTexts;
|
|
QStringList names;
|
|
|
|
auto startOfLineWithDataValues = dataStream->pos();
|
|
bool hasDataValues = false;
|
|
QString nameFromData;
|
|
while ( !hasDataValues )
|
|
{
|
|
QString candidateLine = dataStream->readLine();
|
|
|
|
QStringList candidateColumnHeaders = RifFileParseTools::splitLineAndTrim( candidateLine, parseOptions.cellSeparator );
|
|
for ( const auto& text : candidateColumnHeaders )
|
|
{
|
|
if ( RiaStdStringTools::isNumber( text.toStdString(), parseOptions.locale.decimalPoint().toLatin1() ) )
|
|
{
|
|
hasDataValues = true;
|
|
}
|
|
else if ( nameFromData.isEmpty() )
|
|
{
|
|
// Keep the first non-number data field as a possible name.
|
|
nameFromData = text;
|
|
}
|
|
}
|
|
|
|
if ( !hasDataValues && candidateColumnHeaders.size() == columnHeaders.size() )
|
|
{
|
|
if ( unitTexts.empty() )
|
|
{
|
|
unitTexts = candidateColumnHeaders;
|
|
}
|
|
else if ( names.empty() )
|
|
{
|
|
names = candidateColumnHeaders;
|
|
}
|
|
|
|
startOfLineWithDataValues = dataStream->pos();
|
|
}
|
|
}
|
|
|
|
dataStream->seek( startOfLineWithDataValues );
|
|
|
|
int colCount = columnHeaders.size();
|
|
|
|
for ( int iCol = 0; iCol < colCount; iCol++ )
|
|
{
|
|
QString colName = RiaTextStringTools::trimAndRemoveDoubleSpaces( columnHeaders[iCol] );
|
|
|
|
QString unit;
|
|
|
|
// Check if unit is part of the column name in parentheses, e.g. "VECTOR (unit)".
|
|
QRegExp exp( "\\((.*)\\)" );
|
|
if ( exp.indexIn( colName ) >= 0 )
|
|
{
|
|
// "VECTOR (unit)" ==> "(unit)"
|
|
QString fullCapture = exp.cap( 0 );
|
|
// "VECTOR (unit)" ==> "unit"
|
|
QString unitCapture = exp.cap( 1 );
|
|
|
|
unit = unitCapture;
|
|
|
|
// Remove unit from name
|
|
colName = RiaTextStringTools::trimAndRemoveDoubleSpaces( colName.remove( fullCapture ) );
|
|
}
|
|
|
|
if ( iCol < names.size() )
|
|
{
|
|
QString name = RiaTextStringTools::trimAndRemoveDoubleSpaces( names[iCol] );
|
|
if ( !name.isEmpty() )
|
|
{
|
|
// Create summary address in the form "WBHP:A-1", <vector name>:<well name>
|
|
colName += ":" + name;
|
|
}
|
|
}
|
|
|
|
if ( iCol < unitTexts.size() ) unit = unitTexts[iCol];
|
|
|
|
RifEclipseSummaryAddress addr = RifEclipseSummaryAddress::fromEclipseTextAddressParseErrorTokens( colName.toStdString() );
|
|
|
|
// Create address of a give category if provided
|
|
if ( parseOptions.defaultCategory == RifEclipseSummaryAddress::SummaryVarCategory::SUMMARY_WELL )
|
|
addr = RifEclipseSummaryAddress::wellAddress( colName.toStdString(), nameFromData.toStdString() );
|
|
else if ( parseOptions.defaultCategory == RifEclipseSummaryAddress::SummaryVarCategory::SUMMARY_FIELD )
|
|
addr = RifEclipseSummaryAddress::fieldAddress( colName.toStdString() );
|
|
|
|
double scaleFactor = 1.0;
|
|
if ( !unit.isEmpty() )
|
|
{
|
|
if ( auto it = unitMapping.find( unit ); it != unitMapping.end() )
|
|
{
|
|
std::tie( unit, scaleFactor ) = it->second;
|
|
}
|
|
}
|
|
|
|
Column col = Column::createColumnInfoFromCsvData( addr, unit.toStdString() );
|
|
col.scaleFactor = scaleFactor;
|
|
columnInfoList->push_back( col );
|
|
}
|
|
|
|
headerFound = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifCsvUserDataParser::parseColumnBasedData( const AsciiDataParseOptions& parseOptions,
|
|
const std::map<QString, std::pair<QString, double>>& unitMapping )
|
|
{
|
|
bool errors = false;
|
|
enum
|
|
{
|
|
FIRST_DATA_ROW,
|
|
DATA_ROW
|
|
} parseState = FIRST_DATA_ROW;
|
|
int colCount;
|
|
std::vector<Column> columnInfoList;
|
|
|
|
QTextStream* dataStream = openDataStream();
|
|
|
|
// Parse header
|
|
if ( !parseColumnInfo( dataStream, parseOptions, &columnInfoList, unitMapping ) )
|
|
{
|
|
if ( m_errorText ) m_errorText->append( "CSV import: Failed to parse header columns" );
|
|
return false;
|
|
}
|
|
|
|
colCount = (int)columnInfoList.size();
|
|
while ( !dataStream->atEnd() && !errors )
|
|
{
|
|
QString line = dataStream->readLine();
|
|
if ( line.trimmed().isEmpty() ) continue;
|
|
|
|
QStringList lineColumns = RifFileParseTools::splitLineAndTrim( line, parseOptions.cellSeparator );
|
|
|
|
if ( lineColumns.size() != colCount )
|
|
{
|
|
if ( m_errorText ) m_errorText->append( "CSV import: Varying number of columns" );
|
|
errors = true;
|
|
break;
|
|
}
|
|
else if ( parseState == FIRST_DATA_ROW )
|
|
{
|
|
for ( int iCol = 0; iCol < colCount; iCol++ )
|
|
{
|
|
std::string colData = lineColumns[iCol].toStdString();
|
|
Column& col = columnInfoList[iCol];
|
|
|
|
// Determine column data type
|
|
if ( col.dataType == Column::NONE )
|
|
{
|
|
if ( QString::fromStdString( col.summaryAddress.vectorName() ) == parseOptions.timeSeriesColumnName )
|
|
{
|
|
col.dataType = Column::DATETIME;
|
|
}
|
|
else
|
|
{
|
|
if ( parseOptions.assumeNumericDataColumns ||
|
|
RiaStdStringTools::isNumber( colData, parseOptions.locale.decimalPoint().toLatin1() ) )
|
|
{
|
|
col.dataType = Column::NUMERIC;
|
|
}
|
|
else
|
|
{
|
|
col.dataType = Column::TEXT;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
parseState = DATA_ROW;
|
|
}
|
|
|
|
if ( parseState == DATA_ROW )
|
|
{
|
|
for ( int iCol = 0; iCol < colCount; iCol++ )
|
|
{
|
|
QString& colData = lineColumns[iCol];
|
|
Column& col = columnInfoList[iCol];
|
|
|
|
try
|
|
{
|
|
if ( col.dataType == Column::NUMERIC )
|
|
{
|
|
bool parseOk = true;
|
|
double value = parseOptions.locale.toDouble( colData, &parseOk );
|
|
|
|
if ( !parseOk )
|
|
{
|
|
// Find the error reason, wrong decimal sign or something else
|
|
if ( RiaStdStringTools::isNumber( colData.toStdString(), '.' ) ||
|
|
RiaStdStringTools::isNumber( colData.toStdString(), ',' ) )
|
|
{
|
|
if ( m_errorText )
|
|
m_errorText->append(
|
|
QString( "CSV import: Failed to parse numeric value in column %1\n" ).arg( QString::number( iCol + 1 ) ) );
|
|
throw 0;
|
|
}
|
|
|
|
// Add nullptr value
|
|
value = HUGE_VAL;
|
|
}
|
|
col.values.push_back( value * col.scaleFactor );
|
|
}
|
|
else if ( col.dataType == Column::TEXT )
|
|
{
|
|
col.textValues.push_back( colData.toStdString() );
|
|
}
|
|
else if ( col.dataType == Column::DATETIME )
|
|
{
|
|
QDateTime dt = tryParseDateTime( colData.toStdString(), parseOptions.dateTimeFormat );
|
|
|
|
// Try to match date format only
|
|
if ( !dt.isValid() && parseOptions.dateFormat != parseOptions.dateTimeFormat )
|
|
{
|
|
dt = tryParseDateTime( colData.toStdString(), parseOptions.dateFormat );
|
|
}
|
|
if ( !dt.isValid() && !parseOptions.fallbackDateTimeFormat.isEmpty() )
|
|
{
|
|
dt = tryParseDateTime( colData.toStdString(), parseOptions.fallbackDateTimeFormat );
|
|
}
|
|
|
|
if ( !dt.isValid() && parseOptions.startDateTime.isValid() )
|
|
{
|
|
double minutes = colData.toDouble();
|
|
dt = parseOptions.startDateTime.addSecs( minutes * 60 );
|
|
}
|
|
|
|
if ( !dt.isValid() )
|
|
{
|
|
if ( m_errorText ) m_errorText->append( "CSV import: Failed to parse date time value" );
|
|
throw 0;
|
|
}
|
|
col.dateTimeValues.push_back( dt.toSecsSinceEpoch() );
|
|
}
|
|
}
|
|
catch ( ... )
|
|
{
|
|
errors = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
closeDataStream();
|
|
|
|
if ( !errors )
|
|
{
|
|
TableData td( "", "", columnInfoList );
|
|
m_tableData = td;
|
|
}
|
|
return !errors;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifCsvUserDataParser::parseLineBasedData()
|
|
{
|
|
QTextStream* dataStream = openDataStream();
|
|
if ( !dataStream )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
std::map<RifEclipseSummaryAddress, std::vector<std::pair<time_t, double>>> addressesAndData;
|
|
std::vector<int> colIndexes;
|
|
|
|
// Parse header
|
|
int lineCount = 0;
|
|
bool headerFound = false;
|
|
bool expectErrorValue = false;
|
|
|
|
while ( !dataStream->atEnd() )
|
|
{
|
|
lineCount++;
|
|
|
|
QString line = dataStream->readLine();
|
|
if ( line.trimmed().isEmpty() ) continue;
|
|
|
|
QStringList dataItems = RifFileParseTools::splitLineAndTrim( line, ";" );
|
|
if ( dataItems.size() < 3 || dataItems.size() > 5 ) continue;
|
|
|
|
if ( !headerFound )
|
|
{
|
|
colIndexes = parseLineBasedHeader( dataItems );
|
|
if ( !colIndexes.empty() )
|
|
{
|
|
headerFound = true;
|
|
expectErrorValue = colIndexes.size() > (size_t)CsvLineBasedColumnType::ERROR_VALUE &&
|
|
colIndexes[(size_t)CsvLineBasedColumnType::ERROR_VALUE] >= 0;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if ( dataItems.size() != (int)colIndexes.size() ) continue;
|
|
|
|
{
|
|
auto textAddr = dataItems[colIndexes[(size_t)CsvLineBasedColumnType::VECTOR]];
|
|
auto addr = RifEclipseSummaryAddress::fromEclipseTextAddressParseErrorTokens( textAddr.toStdString() );
|
|
auto errAddr = addr;
|
|
errAddr.setAsErrorResult();
|
|
|
|
if ( !addr.isValid() ) continue;
|
|
|
|
// VECTOR
|
|
{
|
|
if ( addressesAndData.find( addr ) == addressesAndData.end() )
|
|
{
|
|
addressesAndData.insert( std::make_pair( addr, std::vector<Sample>() ) );
|
|
}
|
|
|
|
// Create error address if error value is expected
|
|
if ( expectErrorValue )
|
|
{
|
|
if ( addressesAndData.find( errAddr ) == addressesAndData.end() )
|
|
{
|
|
addressesAndData.insert( std::make_pair( errAddr, std::vector<Sample>() ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
// DATE
|
|
QDateTime dateTime;
|
|
{
|
|
auto dateText = dataItems[colIndexes[(size_t)CsvLineBasedColumnType::DATE]].toStdString();
|
|
|
|
dateTime = tryParseDateTime( dateText, ISO_DATE_FORMAT );
|
|
if ( !dateTime.isValid() )
|
|
{
|
|
// Try to match date and time
|
|
dateTime = tryParseDateTime( dateText, QString( ISO_DATE_FORMAT ) + " " + TIME_FORMAT );
|
|
}
|
|
|
|
if ( !dateTime.isValid() )
|
|
{
|
|
if ( m_errorText )
|
|
m_errorText->append(
|
|
QString( "CSV import: Failed to parse date time value in line %1" ).arg( QString::number( lineCount ) ) );
|
|
throw 0;
|
|
}
|
|
}
|
|
|
|
// VALUE
|
|
{
|
|
bool parseOk = true;
|
|
double value = QLocale::c().toDouble( dataItems[colIndexes[(size_t)CsvLineBasedColumnType::VALUE]], &parseOk );
|
|
|
|
if ( !parseOk )
|
|
{
|
|
if ( m_errorText )
|
|
m_errorText->append(
|
|
QString( "CSV import: Failed to parse numeric value in line %1\n" ).arg( QString::number( lineCount ) ) );
|
|
throw 0;
|
|
}
|
|
|
|
auto& samples = addressesAndData[addr];
|
|
samples.push_back( std::make_pair( dateTime.toSecsSinceEpoch(), value ) );
|
|
}
|
|
|
|
// ERROR VALUE
|
|
if ( expectErrorValue )
|
|
{
|
|
bool parseOk = true;
|
|
double value = QLocale::c().toDouble( dataItems[colIndexes[(size_t)CsvLineBasedColumnType::ERROR_VALUE]], &parseOk );
|
|
|
|
if ( !parseOk ) value = DOUBLE_INF;
|
|
|
|
auto& samples = addressesAndData[errAddr];
|
|
samples.push_back( std::make_pair( dateTime.toSecsSinceEpoch(), value ) );
|
|
}
|
|
}
|
|
}
|
|
closeDataStream();
|
|
|
|
{
|
|
std::vector<Column> columnInfoList;
|
|
for ( const auto& item : addressesAndData )
|
|
{
|
|
auto samples = item.second;
|
|
|
|
// Sort samples by time
|
|
std::sort( samples.begin(), samples.end(), []( const Sample& s1, const Sample& s2 ) { return s1.first < s2.first; } );
|
|
|
|
// Copy
|
|
Column c = Column::createColumnInfoFromCsvData( item.first, "" );
|
|
c.dataType = Column::NUMERIC;
|
|
|
|
for ( const auto& sample : samples )
|
|
{
|
|
c.dateTimeValues.push_back( sample.first );
|
|
c.values.push_back( sample.second );
|
|
}
|
|
columnInfoList.push_back( c );
|
|
}
|
|
|
|
TableData td( "", "", columnInfoList );
|
|
m_tableData = td;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
QDateTime RifCsvUserDataParser::tryParseDateTime( const std::string& colData, const QString& format )
|
|
{
|
|
return RiaQDateTimeTools::fromString( QString::fromStdString( colData ), format );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
QString RifCsvUserDataParser::tryDetermineCellSeparator()
|
|
{
|
|
QTextStream* dataStream = openDataStream();
|
|
if ( !dataStream )
|
|
{
|
|
return "";
|
|
}
|
|
|
|
std::vector<QString> lines;
|
|
int iLine = 0;
|
|
|
|
while ( iLine < 10 && !dataStream->atEnd() )
|
|
{
|
|
QString line = dataStream->readLine();
|
|
if ( line.isEmpty() ) continue;
|
|
|
|
lines.push_back( line );
|
|
iLine++;
|
|
}
|
|
closeDataStream();
|
|
|
|
// Try different cell separators
|
|
int totColumnCountTab = 0;
|
|
int totColumnCountSemicolon = 0;
|
|
int totColumnCountComma = 0;
|
|
|
|
for ( const QString& line : lines )
|
|
{
|
|
totColumnCountTab += RifFileParseTools::splitLineAndTrim( line, "\t" ).size();
|
|
totColumnCountSemicolon += RifFileParseTools::splitLineAndTrim( line, ";" ).size();
|
|
totColumnCountComma += RifFileParseTools::splitLineAndTrim( line, "," ).size();
|
|
}
|
|
|
|
double avgColumnCountTab = (double)totColumnCountTab / lines.size();
|
|
double avgColumnCountSemicolon = (double)totColumnCountSemicolon / lines.size();
|
|
double avgColumnCountComma = (double)totColumnCountComma / lines.size();
|
|
|
|
// Select the one having highest average
|
|
double maxAvg = std::max( std::max( avgColumnCountTab, avgColumnCountSemicolon ), avgColumnCountComma );
|
|
|
|
if ( maxAvg == avgColumnCountTab ) return "\t";
|
|
if ( maxAvg == avgColumnCountSemicolon ) return ";";
|
|
if ( maxAvg == avgColumnCountComma ) return ",";
|
|
return "";
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
QString RifCsvUserDataParser::tryDetermineDecimalSeparator( const QString& cellSeparator )
|
|
{
|
|
QTextStream* dataStream = openDataStream();
|
|
int iLine = 0;
|
|
|
|
int successfulParsesDot = 0;
|
|
int successfulParsesComma = 0;
|
|
|
|
while ( iLine < 10 && !dataStream->atEnd() )
|
|
{
|
|
QString line = dataStream->readLine();
|
|
if ( line.isEmpty() ) continue;
|
|
|
|
for ( const QString& cellData : RifFileParseTools::splitLineAndTrim( line, cellSeparator ) )
|
|
{
|
|
bool parseOk;
|
|
QLocale locale;
|
|
|
|
locale = localeFromDecimalSeparator( "." );
|
|
locale.toDouble( cellData, &parseOk );
|
|
if ( parseOk ) successfulParsesDot++;
|
|
|
|
locale = localeFromDecimalSeparator( "," );
|
|
locale.toDouble( cellData, &parseOk );
|
|
if ( parseOk ) successfulParsesComma++;
|
|
}
|
|
|
|
iLine++;
|
|
}
|
|
closeDataStream();
|
|
|
|
if ( successfulParsesComma > successfulParsesDot )
|
|
return ",";
|
|
else
|
|
return ".";
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
QLocale RifCsvUserDataParser::localeFromDecimalSeparator( const QString& decimalSeparator )
|
|
{
|
|
if ( decimalSeparator == "," )
|
|
{
|
|
return caf::norwegianLocale();
|
|
}
|
|
return QLocale::c();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
RifCsvUserDataFileParser::RifCsvUserDataFileParser( const QString& fileName, QString* errorText )
|
|
: RifCsvUserDataParser( errorText )
|
|
{
|
|
m_fileName = fileName;
|
|
m_file = nullptr;
|
|
m_textStream = nullptr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
RifCsvUserDataFileParser::~RifCsvUserDataFileParser()
|
|
{
|
|
if ( m_textStream )
|
|
{
|
|
delete m_textStream;
|
|
}
|
|
closeFile();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
QTextStream* RifCsvUserDataFileParser::openDataStream()
|
|
{
|
|
if ( !openFile() ) return nullptr;
|
|
|
|
m_textStream = new QTextStream( m_file );
|
|
return m_textStream;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RifCsvUserDataFileParser::closeDataStream()
|
|
{
|
|
if ( m_textStream )
|
|
{
|
|
delete m_textStream;
|
|
m_textStream = nullptr;
|
|
}
|
|
closeFile();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RifCsvUserDataFileParser::openFile()
|
|
{
|
|
if ( !m_file )
|
|
{
|
|
m_file = new QFile( m_fileName );
|
|
if ( !m_file->open( QIODevice::ReadOnly | QIODevice::Text ) )
|
|
{
|
|
RiaLogging::error( QString( "Failed to open %1" ).arg( m_fileName ) );
|
|
|
|
delete m_file;
|
|
m_file = nullptr;
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RifCsvUserDataFileParser::closeFile()
|
|
{
|
|
if ( m_file )
|
|
{
|
|
m_file->close();
|
|
delete m_file;
|
|
m_file = nullptr;
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
RifCsvUserDataPastedTextParser::RifCsvUserDataPastedTextParser( const QString& text, QString* errorText )
|
|
: RifCsvUserDataParser( errorText )
|
|
{
|
|
m_text = text;
|
|
m_textStream = nullptr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
RifCsvUserDataPastedTextParser::~RifCsvUserDataPastedTextParser()
|
|
{
|
|
if ( m_textStream )
|
|
{
|
|
delete m_textStream;
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
QTextStream* RifCsvUserDataPastedTextParser::openDataStream()
|
|
{
|
|
if ( m_textStream )
|
|
{
|
|
delete m_textStream;
|
|
}
|
|
m_textStream = new QTextStream( &m_text );
|
|
return m_textStream;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RifCsvUserDataPastedTextParser::closeDataStream()
|
|
{
|
|
if ( m_textStream )
|
|
{
|
|
delete m_textStream;
|
|
m_textStream = nullptr;
|
|
}
|
|
}
|