QgsDelimitedTextProvider::QgsDelimitedTextProvider( QString uri ) : QgsVectorDataProvider( uri ) , mDelimiter( "," ) , mDelimiterType( "plain" ) , mFieldCount( 0 ) , mXFieldIndex( -1 ) , mYFieldIndex( -1 ) , mWktFieldIndex( -1 ) , mWktHasZM( false ) , mWktZMRegexp( "\\s+(?:z|m|zm)(?=\\s*\\()", Qt::CaseInsensitive ) , mWktCrdRegexp( "(\\-?\\d+(?:\\.\\d*)?\\s+\\-?\\d+(?:\\.\\d*)?)\\s[\\s\\d\\.\\-]+" ) , mFile( 0 ) , mStream( 0 ) , mSkipLines( 0 ) , mFirstDataLine( 0 ) , mShowInvalidLines( false ) , mCrs() , mWkbType( QGis::WKBUnknown ) { QUrl url = QUrl::fromEncoded( uri.toAscii() ); // Extract the provider definition from the url mFileName = url.toLocalFile(); QString wktField; QString xField; QString yField; if ( url.hasQueryItem( "delimiter" ) ) mDelimiter = url.queryItemValue( "delimiter" ); if ( url.hasQueryItem( "delimiterType" ) ) mDelimiterType = url.queryItemValue( "delimiterType" ); if ( url.hasQueryItem( "wktField" ) ) wktField = url.queryItemValue( "wktField" ); if ( url.hasQueryItem( "xField" ) ) xField = url.queryItemValue( "xField" ); if ( url.hasQueryItem( "yField" ) ) yField = url.queryItemValue( "yField" ); if ( url.hasQueryItem( "skipLines" ) ) mSkipLines = url.queryItemValue( "skipLines" ).toInt(); if ( url.hasQueryItem( "crs" ) ) mCrs.createFromString( url.queryItemValue( "crs" ) ); if ( url.hasQueryItem( "decimalPoint" ) ) mDecimalPoint = url.queryItemValue( "decimalPoint" ); QgsDebugMsg( "Data source uri is " + uri ); QgsDebugMsg( "Delimited text file is: " + mFileName ); QgsDebugMsg( "Delimiter is: " + mDelimiter ); QgsDebugMsg( "Delimiter type is: " + mDelimiterType ); QgsDebugMsg( "wktField is: " + wktField ); QgsDebugMsg( "xField is: " + xField ); QgsDebugMsg( "yField is: " + yField ); QgsDebugMsg( "skipLines is: " + QString::number( mSkipLines ) ); // if delimiter contains some special characters, convert them if ( mDelimiterType != "regexp" ) mDelimiter.replace( "\\t", "\t" ); // replace "\t" with a real tabulator // Set the selection rectangle to null mSelectionRectangle = QgsRectangle(); // assume the layer is invalid until proven otherwise mValid = false; if ( mFileName.isEmpty() || mDelimiter.isEmpty() ) { // uri is invalid so the layer must be too... QgsDebugMsg( "Data source is invalid" ); return; } // check to see that the file exists and perform some sanity checks if ( !QFile::exists( mFileName ) ) { QgsDebugMsg( "Data source " + dataSourceUri() + " doesn't exist" ); return; } // Open the file and get number of rows, etc. We assume that the // file has a header row and process accordingly. Caller should make // sure that the delimited file is properly formed. mFile = new QFile( mFileName ); if ( !mFile->open( QIODevice::ReadOnly ) ) { QgsDebugMsg( "Data source " + dataSourceUri() + " could not be opened" ); delete mFile; mFile = 0; return; } // now we have the file opened and ready for parsing // set the initial extent mExtent = QgsRectangle(); QMap<int, bool> couldBeInt; QMap<int, bool> couldBeDouble; mStream = new QTextStream( mFile ); QString line; mNumberFeatures = 0; int lineNumber = 0; bool hasFields = false; while ( !mStream->atEnd() ) { lineNumber++; line = readLine( mStream ); // line of text excluding '\n', default local 8 bit encoding. if ( lineNumber < mSkipLines + 1 ) continue; if ( line.isEmpty() ) continue; if ( !hasFields ) { // Get the fields from the header row and store them in the // fields vector QStringList fieldList = splitLine( line ); mFieldCount = fieldList.count(); // We don't know anything about a text based field other // than its name. All fields are assumed to be text int fieldPos = 0; for ( int column = 0; column < mFieldCount; column++ ) { QString field = fieldList[column]; if (( field.left( 1 ) == "'" || field.left( 1 ) == "\"" ) && field.left( 1 ) == field.right( 1 ) ) // eat quotes field = field.mid( 1, field.length() - 2 ); if ( field.length() == 0 ) // skip empty field names continue; // check to see if this field matches either the x or y field if ( !wktField.isEmpty() && wktField == field ) { QgsDebugMsg( "Found wkt field: " + ( field ) ); mWktFieldIndex = column; } else if ( !xField.isEmpty() && xField == field ) { QgsDebugMsg( "Found x field: " + ( field ) ); mXFieldIndex = column; } else if ( !yField.isEmpty() && yField == field ) { QgsDebugMsg( "Found y field: " + ( field ) ); mYFieldIndex = column; } // WKT geometry field won't be displayed in attribute tables if ( column == mWktFieldIndex ) continue; QgsDebugMsg( "Adding field: " + ( field ) ); // assume that the field could be integer or double // for now, let's set field type as text attributeColumns.append( column ); attributeFields[fieldPos] = QgsField( field, QVariant::String, "Text" ); couldBeInt.insert( fieldPos, true ); couldBeDouble.insert( fieldPos, true ); fieldPos++; } if ( mWktFieldIndex >= 0 ) { mXFieldIndex = -1; mYFieldIndex = -1; } QgsDebugMsg( "wktfield index: " + QString::number( mWktFieldIndex ) ); QgsDebugMsg( "xfield index: " + QString::number( mXFieldIndex ) ); QgsDebugMsg( "yfield index: " + QString::number( mYFieldIndex ) ); QgsDebugMsg( "Field count for the delimited text file is " + QString::number( attributeFields.size() ) ); hasFields = true; } else // hasFields == true - field names already read { if ( mFirstDataLine == 0 ) mFirstDataLine = lineNumber; // split the line on the delimiter QStringList parts = splitLine( line ); // Ensure that the input has at least the required number of fields (mainly to tolerate // missed blank strings at end of row) while ( parts.size() < mFieldCount ) parts.append( QString::null ); if ( mWktFieldIndex >= 0 ) { // Get the wkt - confirm it is valid, get the type, and // if compatible with the rest of file, add to the extents QString sWkt = parts[mWktFieldIndex]; QgsGeometry *geom = 0; try { if ( !mWktHasZM && sWkt.indexOf( mWktZMRegexp ) >= 0 ) mWktHasZM = true; if ( mWktHasZM ) { sWkt.remove( mWktZMRegexp ).replace( mWktCrdRegexp, "\\1" ); } geom = QgsGeometry::fromWkt( sWkt ); } catch ( ... ) { mInvalidLines << line; geom = 0; } if ( geom ) { QGis::WkbType type = geom->wkbType(); if ( type != QGis::WKBNoGeometry ) { if ( mNumberFeatures == 0 ) { mNumberFeatures++; mWkbType = type; mExtent = geom->boundingBox(); } else if ( type == mWkbType ) { mNumberFeatures++; QgsRectangle bbox( geom->boundingBox() ); mExtent.combineExtentWith( &bbox ); } } delete geom; } } else if ( mWktFieldIndex == -1 && mXFieldIndex >= 0 && mYFieldIndex >= 0 ) { // Get the x and y values, first checking to make sure they // aren't null. QString sX = parts[mXFieldIndex]; QString sY = parts[mYFieldIndex]; if ( !mDecimalPoint.isEmpty() ) { sX.replace( mDecimalPoint, "." ); sY.replace( mDecimalPoint, "." ); } bool xOk = false; bool yOk = false; double x = sX.toDouble( &xOk ); double y = sY.toDouble( &yOk ); if ( xOk && yOk ) { if ( mNumberFeatures > 0 ) { mExtent.combineExtentWith( x, y ); } else { // Extent for the first point is just the first point mExtent.set( x, y, x, y ); mWkbType = QGis::WKBPoint; } mNumberFeatures++; } else { mInvalidLines << line; } } else { mWkbType = QGis::WKBNoGeometry; mNumberFeatures++; } for ( int i = 0; i < attributeFields.size(); i++ ) { QString &value = parts[attributeColumns[i]]; if ( value.isEmpty() ) continue; // try to convert attribute values to integer and double if ( couldBeInt[i] ) { value.toInt( &couldBeInt[i] ); } if ( couldBeDouble[i] ) { value.toDouble( &couldBeDouble[i] ); } } } } QgsDebugMsg( "geometry type is: " + QString::number( mWkbType ) ); QgsDebugMsg( "feature count is: " + QString::number( mNumberFeatures ) ); // now it's time to decide the types for the fields for ( QgsFieldMap::iterator it = attributeFields.begin(); it != attributeFields.end(); ++it ) { if ( couldBeInt[it.key()] ) { it->setType( QVariant::Int ); it->setTypeName( "integer" ); } else if ( couldBeDouble[it.key()] ) { it->setType( QVariant::Double ); it->setTypeName( "double" ); } } mValid = mWkbType != QGis::WKBUnknown; }
QgsDelimitedTextProvider::QgsDelimitedTextProvider( QString uri ) : QgsVectorDataProvider( uri ), mXFieldIndex( -1 ), mYFieldIndex( -1 ), mShowInvalidLines( true ) { // Get the file name and mDelimiter out of the uri mFileName = uri.left( uri.indexOf( "?" ) ); // split the string up on & to get the individual parameters QStringList parameters = uri.mid( uri.indexOf( "?" ) ).split( "&", QString::SkipEmptyParts ); QgsDebugMsg( "Parameter count after split on &" + QString::number( parameters.size() ) ); // get the individual parameters and assign values QStringList temp = parameters.filter( "delimiter=" ); mDelimiter = temp.size() ? temp[0].mid( temp[0].indexOf( "=" ) + 1 ) : ""; temp = parameters.filter( "delimiterType=" ); mDelimiterType = temp.size() ? temp[0].mid( temp[0].indexOf( "=" ) + 1 ) : ""; temp = parameters.filter( "xField=" ); QString xField = temp.size() ? temp[0].mid( temp[0].indexOf( "=" ) + 1 ) : ""; temp = parameters.filter( "yField=" ); QString yField = temp.size() ? temp[0].mid( temp[0].indexOf( "=" ) + 1 ) : ""; // Decode the parts of the uri. Good if someone entered '=' as a delimiter, for instance. mFileName = QUrl::fromPercentEncoding( mFileName.toUtf8() ); mDelimiter = QUrl::fromPercentEncoding( mDelimiter.toUtf8() ); mDelimiterType = QUrl::fromPercentEncoding( mDelimiterType.toUtf8() ); xField = QUrl::fromPercentEncoding( xField.toUtf8() ); yField = QUrl::fromPercentEncoding( yField.toUtf8() ); QgsDebugMsg( "Data source uri is " + uri ); QgsDebugMsg( "Delimited text file is: " + mFileName ); QgsDebugMsg( "Delimiter is: " + mDelimiter ); QgsDebugMsg( "Delimiter type is: " + mDelimiterType ); QgsDebugMsg( "xField is: " + xField ); QgsDebugMsg( "yField is: " + yField ); // if delimiter contains some special characters, convert them if ( mDelimiterType == "regexp" ) mDelimiterRegexp = QRegExp( mDelimiter ); else mDelimiter.replace( "\\t", "\t" ); // replace "\t" with a real tabulator // Set the selection rectangle to null mSelectionRectangle = QgsRectangle(); // assume the layer is invalid until proven otherwise mValid = false; if ( mFileName.isEmpty() || mDelimiter.isEmpty() || xField.isEmpty() || yField.isEmpty() ) { // uri is invalid so the layer must be too... QString( "Data source is invalid" ); return; } // check to see that the file exists and perform some sanity checks if ( !QFile::exists( mFileName ) ) { QgsDebugMsg( "Data source " + dataSourceUri() + " doesn't exist" ); return; } // Open the file and get number of rows, etc. We assume that the // file has a header row and process accordingly. Caller should make // sure the the delimited file is properly formed. mFile = new QFile( mFileName ); if ( !mFile->open( QIODevice::ReadOnly ) ) { QgsDebugMsg( "Data source " + dataSourceUri() + " could not be opened" ); delete mFile; return; } // now we have the file opened and ready for parsing // set the initial extent mExtent = QgsRectangle(); QMap<int, bool> couldBeInt; QMap<int, bool> couldBeDouble; mStream = new QTextStream( mFile ); QString line; mNumberFeatures = 0; int lineNumber = 0; bool firstPoint = true; bool hasFields = false; while ( !mStream->atEnd() ) { lineNumber++; line = mStream->readLine(); // line of text excluding '\n', default local 8 bit encoding. if ( !hasFields ) { // Get the fields from the header row and store them in the // fields vector QgsDebugMsg( "Attempting to split the input line: " + line + " using delimiter " + mDelimiter ); QStringList fieldList; if ( mDelimiterType == "regexp" ) fieldList = line.split( mDelimiterRegexp ); else fieldList = line.split( mDelimiter ); QgsDebugMsg( "Split line into " + QString::number( fieldList.size() ) + " parts" ); // We don't know anything about a text based field other // than its name. All fields are assumed to be text int fieldPos = 0; for ( QStringList::Iterator it = fieldList.begin(); it != fieldList.end(); ++it ) { QString field = *it; if ( field.length() > 0 ) { // for now, let's set field type as text attributeFields[fieldPos] = QgsField( *it, QVariant::String, "Text" ); // check to see if this field matches either the x or y field if ( xField == *it ) { QgsDebugMsg( "Found x field: " + ( *it ) ); mXFieldIndex = fieldPos; } else if ( yField == *it ) { QgsDebugMsg( "Found y field: " + ( *it ) ); mYFieldIndex = fieldPos; } QgsDebugMsg( "Adding field: " + ( *it ) ); // assume that the field could be integer or double couldBeInt.insert( fieldPos, true ); couldBeDouble.insert( fieldPos, true ); fieldPos++; } } QgsDebugMsg( "Field count for the delimited text file is " + QString::number( attributeFields.size() ) ); hasFields = true; } else if ( mXFieldIndex != -1 && mYFieldIndex != -1 ) { mNumberFeatures++; // split the line on the delimiter QStringList parts; if ( mDelimiterType == "regexp" ) parts = line.split( mDelimiterRegexp ); else parts = line.split( mDelimiter ); // Skip malformed lines silently. Report line number with nextFeature() if ( attributeFields.size() != parts.size() ) { continue; } // Get the x and y values, first checking to make sure they // aren't null. QString sX = parts[mXFieldIndex]; QString sY = parts[mYFieldIndex]; bool xOk = true; bool yOk = true; double x = sX.toDouble( &xOk ); double y = sY.toDouble( &yOk ); if ( xOk && yOk ) { if ( !firstPoint ) { mExtent.combineExtentWith( x, y ); } else { // Extent for the first point is just the first point mExtent.set( x, y, x, y ); firstPoint = false; } } int i = 0; for ( QStringList::iterator it = parts.begin(); it != parts.end(); ++it, ++i ) { // try to convert attribute values to integer and double if ( couldBeInt[i] ) { it->toInt( &couldBeInt[i] ); } if ( couldBeDouble[i] ) { it->toDouble( &couldBeDouble[i] ); } } } } // now it's time to decide the types for the fields for ( QgsFieldMap::iterator it = attributeFields.begin(); it != attributeFields.end(); ++it ) { if ( couldBeInt[it.key()] ) { it->setType( QVariant::Int ); it->setTypeName( "integer" ); } else if ( couldBeDouble[it.key()] ) { it->setType( QVariant::Double ); it->setTypeName( "double" ); } } if ( mXFieldIndex != -1 && mYFieldIndex != -1 ) { QgsDebugMsg( "Data store is valid" ); QgsDebugMsg( "Number of features " + QString::number( mNumberFeatures ) ); QgsDebugMsg( "Extents " + mExtent.toString() ); mValid = true; } else { QgsDebugMsg( "Data store is invalid. Specified x,y fields do not match those in the database" ); } QgsDebugMsg( "Done checking validity" ); }