diff --git a/python/core/mesh/qgsmeshlayer.sip.in b/python/core/mesh/qgsmeshlayer.sip.in index 2560646250e..7e3519be066 100644 --- a/python/core/mesh/qgsmeshlayer.sip.in +++ b/python/core/mesh/qgsmeshlayer.sip.in @@ -100,6 +100,14 @@ QgsMeshLayer cannot be copied. virtual bool writeSymbology( QDomNode &node, QDomDocument &doc, QString &errorMessage, const QgsReadWriteContext &context ) const; + virtual QString encodedSource( const QString &source, const QgsReadWriteContext &context ) const; + + virtual QString decodedSource( const QString &source, const QString &provider, const QgsReadWriteContext &context ) const; + + virtual bool readXml( const QDomNode &layer_node, QgsReadWriteContext &context ); + + virtual bool writeXml( QDomNode &layer_node, QDomDocument &doc, const QgsReadWriteContext &context ) const; + QString providerType() const; %Docstring diff --git a/python/core/qgsmaplayer.sip.in b/python/core/qgsmaplayer.sip.in index ecd1fdde649..e3350ba64eb 100644 --- a/python/core/qgsmaplayer.sip.in +++ b/python/core/qgsmaplayer.sip.in @@ -1376,6 +1376,35 @@ project files. %Docstring Called by writeLayerXML(), used by children to write state specific to them to project files. +%End + + virtual QString encodedSource( const QString &source, const QgsReadWriteContext &context ) const; +%Docstring +Called by writeLayerXML(), used by derived classes to encode provider's specific data +source to project files. Typically resolving absolute or relative paths, usernames and +passwords or drivers prefixes ("HDF5:") + +:param source: data source to encode, typically :py:func:`QgsMapLayer.source()` +:param context: writing context (e.g. for conversion between relative and absolute paths) + +:return: encoded source, typically to be written in the dom element "datasource" + +.. versionadded:: 3.2 +%End + + virtual QString decodedSource( const QString &source, const QString &dataProvider, const QgsReadWriteContext &context ) const; +%Docstring +Called by readLayerXML(), used by derived classes to decode provider's specific data +source from project files. Typically resolving absolute or relative paths, usernames and +passwords or drivers prefixes ("HDF5:") + +:param source: data source to decode, typically read from layer's dom element "datasource" +:param dataProvider: string identification of data provider (e.g. "ogr"), typically read from layer's dom element +:param context: reading context (e.g. for conversion between relative and absolute paths) + +:return: decoded source, typically to be used as the layer's datasource + +.. versionadded:: 3.2 %End void readCustomProperties( const QDomNode &layerNode, const QString &keyStartsWith = QString() ); diff --git a/python/core/qgsvectorlayer.sip.in b/python/core/qgsvectorlayer.sip.in index a4e7680918b..0159e43cad6 100644 --- a/python/core/qgsvectorlayer.sip.in +++ b/python/core/qgsvectorlayer.sip.in @@ -748,6 +748,11 @@ Write vector layer specific state to project file Dom node. Called by :py:func:`QgsMapLayer.writeXml()` %End + virtual QString encodedSource( const QString &source, const QgsReadWriteContext &context ) const; + + virtual QString decodedSource( const QString &source, const QString &provider, const QgsReadWriteContext &context ) const; + + virtual void resolveReferences( QgsProject *project ); %Docstring diff --git a/python/core/raster/qgsrasterlayer.sip.in b/python/core/raster/qgsrasterlayer.sip.in index 456a2d90ab6..589bd5f3be2 100644 --- a/python/core/raster/qgsrasterlayer.sip.in +++ b/python/core/raster/qgsrasterlayer.sip.in @@ -347,6 +347,9 @@ In a world file, this is normally the first row (without the sign). virtual bool writeXml( QDomNode &layer_node, QDomDocument &doc, const QgsReadWriteContext &context ) const; + virtual QString encodedSource( const QString &source, const QgsReadWriteContext &context ) const; + + virtual QString decodedSource( const QString &source, const QString &provider, const QgsReadWriteContext &context ) const; }; diff --git a/src/core/mesh/qgsmeshlayer.cpp b/src/core/mesh/qgsmeshlayer.cpp index bbd382ac08d..d9f0f6c266a 100644 --- a/src/core/mesh/qgsmeshlayer.cpp +++ b/src/core/mesh/qgsmeshlayer.cpp @@ -25,6 +25,7 @@ #include "qgsmeshlayer.h" #include "qgsmeshlayerrenderer.h" #include "qgsproviderregistry.h" +#include "qgsreadwritecontext.h" #include "qgstriangularmesh.h" QgsMeshLayer::QgsMeshLayer( const QString &meshLayerPath, @@ -180,9 +181,84 @@ bool QgsMeshLayer::writeSymbology( QDomNode &node, QDomDocument &doc, QString &e return true; } +QString QgsMeshLayer::decodedSource( const QString &source, const QString &provider, const QgsReadWriteContext &context ) const +{ + QString src( source ); + if ( provider == QLatin1String( "mdal" ) ) + { + src = context.pathResolver().readPath( src ); + } + return src; +} + +QString QgsMeshLayer::encodedSource( const QString &source, const QgsReadWriteContext &context ) const +{ + QString src( source ); + if ( providerType() == QLatin1String( "mdal" ) ) + { + src = context.pathResolver().writePath( src ); + } + return src; +} + +bool QgsMeshLayer::readXml( const QDomNode &layer_node, QgsReadWriteContext &context ) +{ + Q_UNUSED( context ); + + QgsDebugMsgLevel( QStringLiteral( "Datasource in QgsMeshLayer::readXml: %1" ).arg( mDataSource.toLocal8Bit().data() ), 3 ); + + //process provider key + QDomNode pkeyNode = layer_node.namedItem( QStringLiteral( "provider" ) ); + + if ( pkeyNode.isNull() ) + { + mProviderKey.clear(); + } + else + { + QDomElement pkeyElt = pkeyNode.toElement(); + mProviderKey = pkeyElt.text(); + } + + if ( !setDataProvider( mProviderKey ) ) + { + return false; + } + + return mValid; // should be true if read successfully +} + +bool QgsMeshLayer::writeXml( QDomNode &layer_node, QDomDocument &document, const QgsReadWriteContext &context ) const +{ + // first get the layer element so that we can append the type attribute + QDomElement mapLayerNode = layer_node.toElement(); + + if ( mapLayerNode.isNull() || ( "maplayer" != mapLayerNode.nodeName() ) ) + { + QgsDebugMsgLevel( QStringLiteral( "can't find " ), 2 ); + return false; + } + + mapLayerNode.setAttribute( QStringLiteral( "type" ), QStringLiteral( "mesh" ) ); + + // add provider node + if ( mDataProvider ) + { + QDomElement provider = document.createElement( QStringLiteral( "provider" ) ); + QDomText providerText = document.createTextNode( providerType() ); + provider.appendChild( providerText ); + layer_node.appendChild( provider ); + } + + // renderer specific settings + QString errorMsg; + return writeSymbology( layer_node, document, errorMsg, context ); +} + bool QgsMeshLayer::setDataProvider( QString const &provider ) { - Q_ASSERT( !mDataProvider ); //called from ctor + if ( mDataProvider ) + delete mDataProvider; mProviderKey = provider; QString dataSource = mDataSource; diff --git a/src/core/mesh/qgsmeshlayer.h b/src/core/mesh/qgsmeshlayer.h index 1359ce2380e..aea269b7c17 100644 --- a/src/core/mesh/qgsmeshlayer.h +++ b/src/core/mesh/qgsmeshlayer.h @@ -114,6 +114,10 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer virtual QgsMapLayerRenderer *createMapRenderer( QgsRenderContext &rendererContext ) override SIP_FACTORY; bool readSymbology( const QDomNode &node, QString &errorMessage, QgsReadWriteContext &context ) override; bool writeSymbology( QDomNode &node, QDomDocument &doc, QString &errorMessage, const QgsReadWriteContext &context ) const override; + QString encodedSource( const QString &source, const QgsReadWriteContext &context ) const override; + QString decodedSource( const QString &source, const QString &provider, const QgsReadWriteContext &context ) const override; + bool readXml( const QDomNode &layer_node, QgsReadWriteContext &context ) override; + bool writeXml( QDomNode &layer_node, QDomDocument &doc, const QgsReadWriteContext &context ) const override; //! Return the provider type for this layer QString providerType() const; diff --git a/src/core/qgsmaplayer.cpp b/src/core/qgsmaplayer.cpp index 402dff6a726..9c7ecfb8fc6 100644 --- a/src/core/qgsmaplayer.cpp +++ b/src/core/qgsmaplayer.cpp @@ -41,6 +41,7 @@ #include "qgsmaplayer.h" #include "qgsmaplayerlegend.h" #include "qgsmaplayerstylemanager.h" +#include "qgsmeshlayer.h" #include "qgspathresolver.h" #include "qgsprojectfiletransform.h" #include "qgsproject.h" @@ -228,182 +229,7 @@ bool QgsMapLayer::readLayerXml( const QDomElement &layerElement, QgsReadWriteCo return false; } - // TODO: this should go to providers - if ( provider == QLatin1String( "spatialite" ) ) - { - QgsDataSourceUri uri( mDataSource ); - uri.setDatabase( context.pathResolver().readPath( uri.database() ) ); - mDataSource = uri.uri(); - } - else if ( provider == QLatin1String( "ogr" ) ) - { - QStringList theURIParts = mDataSource.split( '|' ); - theURIParts[0] = context.pathResolver().readPath( theURIParts[0] ); - mDataSource = theURIParts.join( QStringLiteral( "|" ) ); - } - else if ( provider == QLatin1String( "gpx" ) ) - { - QStringList theURIParts = mDataSource.split( '?' ); - theURIParts[0] = context.pathResolver().readPath( theURIParts[0] ); - mDataSource = theURIParts.join( QStringLiteral( "?" ) ); - } - else if ( provider == QLatin1String( "delimitedtext" ) ) - { - QUrl urlSource = QUrl::fromEncoded( mDataSource.toLatin1() ); - - if ( !mDataSource.startsWith( QLatin1String( "file:" ) ) ) - { - QUrl file = QUrl::fromLocalFile( mDataSource.left( mDataSource.indexOf( '?' ) ) ); - urlSource.setScheme( QStringLiteral( "file" ) ); - urlSource.setPath( file.path() ); - } - - QUrl urlDest = QUrl::fromLocalFile( context.pathResolver().readPath( urlSource.toLocalFile() ) ); - urlDest.setQueryItems( urlSource.queryItems() ); - mDataSource = QString::fromLatin1( urlDest.toEncoded() ); - } - else if ( provider == QLatin1String( "wms" ) ) - { - // >>> BACKWARD COMPATIBILITY < 1.9 - // For project file backward compatibility we must support old format: - // 1. mode: - // example: http://example.org/wms? - // 2. mode: tiled=;;;...,ignoreUrl=GetMap;GetFeatureInfo,featureCount=,username=,password=,url= - // example: tiled=256;256;0.703;0.351,url=http://example.org/tilecache? - // example: featureCount=10,http://example.org/wms? - // example: ignoreUrl=GetMap;GetFeatureInfo,username=cimrman,password=jara,url=http://example.org/wms? - // This is modified version of old QgsWmsProvider::parseUri - // The new format has always params crs,format,layers,styles and that params - // should not appear in old format url -> use them to identify version - // XYZ tile layers do not need to contain crs,format params, but they have type=xyz - if ( !mDataSource.contains( QLatin1String( "type=" ) ) && - !mDataSource.contains( QLatin1String( "crs=" ) ) && !mDataSource.contains( QLatin1String( "format=" ) ) ) - { - QgsDebugMsg( "Old WMS URI format detected -> converting to new format" ); - QgsDataSourceUri uri; - if ( !mDataSource.startsWith( QLatin1String( "http:" ) ) ) - { - QStringList parts = mDataSource.split( ',' ); - QStringListIterator iter( parts ); - while ( iter.hasNext() ) - { - QString item = iter.next(); - if ( item.startsWith( QLatin1String( "username=" ) ) ) - { - uri.setParam( QStringLiteral( "username" ), item.mid( 9 ) ); - } - else if ( item.startsWith( QLatin1String( "password=" ) ) ) - { - uri.setParam( QStringLiteral( "password" ), item.mid( 9 ) ); - } - else if ( item.startsWith( QLatin1String( "tiled=" ) ) ) - { - // in < 1.9 tiled= may apper in to variants: - // tiled=width;height - non tiled mode, specifies max width and max height - // tiled=width;height;resolutions-1;resolution2;... - tile mode - - QStringList params = item.mid( 6 ).split( ';' ); - - if ( params.size() == 2 ) // non tiled mode - { - uri.setParam( QStringLiteral( "maxWidth" ), params.takeFirst() ); - uri.setParam( QStringLiteral( "maxHeight" ), params.takeFirst() ); - } - else if ( params.size() > 2 ) // tiled mode - { - // resolutions are no more needed and size limit is not used for tiles - // we have to tell to the provider however that it is tiled - uri.setParam( QStringLiteral( "tileMatrixSet" ), QLatin1String( "" ) ); - } - } - else if ( item.startsWith( QLatin1String( "featureCount=" ) ) ) - { - uri.setParam( QStringLiteral( "featureCount" ), item.mid( 13 ) ); - } - else if ( item.startsWith( QLatin1String( "url=" ) ) ) - { - uri.setParam( QStringLiteral( "url" ), item.mid( 4 ) ); - } - else if ( item.startsWith( QLatin1String( "ignoreUrl=" ) ) ) - { - uri.setParam( QStringLiteral( "ignoreUrl" ), item.mid( 10 ).split( ';' ) ); - } - } - } - else - { - uri.setParam( QStringLiteral( "url" ), mDataSource ); - } - mDataSource = uri.encodedUri(); - // At this point, the URI is obviously incomplete, we add additional params - // in QgsRasterLayer::readXml - } - // <<< BACKWARD COMPATIBILITY < 1.9 - } - else - { - bool handled = false; - - if ( provider == QLatin1String( "gdal" ) ) - { - if ( mDataSource.startsWith( QLatin1String( "NETCDF:" ) ) ) - { - // NETCDF:filename:variable - // filename can be quoted with " as it can contain colons - QRegExp r( "NETCDF:(.+):([^:]+)" ); - if ( r.exactMatch( mDataSource ) ) - { - QString filename = r.cap( 1 ); - if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) ) - filename = filename.mid( 1, filename.length() - 2 ); - mDataSource = "NETCDF:\"" + context.pathResolver().readPath( filename ) + "\":" + r.cap( 2 ); - handled = true; - } - } - else if ( mDataSource.startsWith( QLatin1String( "HDF4_SDS:" ) ) ) - { - // HDF4_SDS:subdataset_type:file_name:subdataset_index - // filename can be quoted with " as it can contain colons - QRegExp r( "HDF4_SDS:([^:]+):(.+):([^:]+)" ); - if ( r.exactMatch( mDataSource ) ) - { - QString filename = r.cap( 2 ); - if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) ) - filename = filename.mid( 1, filename.length() - 2 ); - mDataSource = "HDF4_SDS:" + r.cap( 1 ) + ":\"" + context.pathResolver().readPath( filename ) + "\":" + r.cap( 3 ); - handled = true; - } - } - else if ( mDataSource.startsWith( QLatin1String( "HDF5:" ) ) ) - { - // HDF5:file_name:subdataset - // filename can be quoted with " as it can contain colons - QRegExp r( "HDF5:(.+):([^:]+)" ); - if ( r.exactMatch( mDataSource ) ) - { - QString filename = r.cap( 1 ); - if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) ) - filename = filename.mid( 1, filename.length() - 2 ); - mDataSource = "HDF5:\"" + context.pathResolver().readPath( filename ) + "\":" + r.cap( 2 ); - handled = true; - } - } - else if ( mDataSource.contains( QRegExp( "^(NITF_IM|RADARSAT_2_CALIB):" ) ) ) - { - // NITF_IM:0:filename - // RADARSAT_2_CALIB:?:filename - QRegExp r( "([^:]+):([^:]+):(.+)" ); - if ( r.exactMatch( mDataSource ) ) - { - mDataSource = r.cap( 1 ) + ':' + r.cap( 2 ) + ':' + context.pathResolver().readPath( r.cap( 3 ) ); - handled = true; - } - } - } - - if ( !handled ) - mDataSource = context.pathResolver().readPath( mDataSource ); - } + mDataSource = decodedSource( mDataSource, provider, context ); // Set the CRS from project file, asking the user if necessary. // Make it the saved CRS to have WMS layer projected correctly. @@ -599,118 +425,11 @@ bool QgsMapLayer::writeLayerXml( QDomElement &layerElement, QDomDocument &docume // data source QDomElement dataSource = document.createElement( QStringLiteral( "datasource" ) ); - - QString src = source(); - - const QgsVectorLayer *vlayer = qobject_cast( this ); - // TODO: what about postgres, mysql and others, they should not go through writePath() - if ( vlayer && vlayer->providerType() == QLatin1String( "spatialite" ) ) - { - QgsDataSourceUri uri( src ); - QString database = context.pathResolver().writePath( uri.database() ); - uri.setConnection( uri.host(), uri.port(), database, uri.username(), uri.password() ); - src = uri.uri(); - } - else if ( vlayer && vlayer->providerType() == QLatin1String( "ogr" ) ) - { - QStringList theURIParts = src.split( '|' ); - theURIParts[0] = context.pathResolver().writePath( theURIParts[0] ); - src = theURIParts.join( QStringLiteral( "|" ) ); - } - else if ( vlayer && vlayer->providerType() == QLatin1String( "gpx" ) ) - { - QStringList theURIParts = src.split( '?' ); - theURIParts[0] = context.pathResolver().writePath( theURIParts[0] ); - src = theURIParts.join( QStringLiteral( "?" ) ); - } - else if ( vlayer && vlayer->providerType() == QLatin1String( "delimitedtext" ) ) - { - QUrl urlSource = QUrl::fromEncoded( src.toLatin1() ); - QUrl urlDest = QUrl::fromLocalFile( context.pathResolver().writePath( urlSource.toLocalFile() ) ); - urlDest.setQueryItems( urlSource.queryItems() ); - src = QString::fromLatin1( urlDest.toEncoded() ); - } - else if ( vlayer && vlayer->providerType() == QLatin1String( "memory" ) ) - { - // Refetch the source from the provider, because adding fields actually changes the source for this provider. - src = vlayer->dataProvider()->dataSourceUri(); - } - else - { - bool handled = false; - - if ( !vlayer ) - { - const QgsRasterLayer *rlayer = qobject_cast( this ); - // Update path for subdataset - if ( rlayer && rlayer->providerType() == QLatin1String( "gdal" ) ) - { - if ( src.startsWith( QLatin1String( "NETCDF:" ) ) ) - { - // NETCDF:filename:variable - // filename can be quoted with " as it can contain colons - QRegExp r( "NETCDF:(.+):([^:]+)" ); - if ( r.exactMatch( src ) ) - { - QString filename = r.cap( 1 ); - if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) ) - filename = filename.mid( 1, filename.length() - 2 ); - src = "NETCDF:\"" + context.pathResolver().writePath( filename ) + "\":" + r.cap( 2 ); - handled = true; - } - } - else if ( src.startsWith( QLatin1String( "HDF4_SDS:" ) ) ) - { - // HDF4_SDS:subdataset_type:file_name:subdataset_index - // filename can be quoted with " as it can contain colons - QRegExp r( "HDF4_SDS:([^:]+):(.+):([^:]+)" ); - if ( r.exactMatch( src ) ) - { - QString filename = r.cap( 2 ); - if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) ) - filename = filename.mid( 1, filename.length() - 2 ); - src = "HDF4_SDS:" + r.cap( 1 ) + ":\"" + context.pathResolver().writePath( filename ) + "\":" + r.cap( 3 ); - handled = true; - } - } - else if ( src.startsWith( QLatin1String( "HDF5:" ) ) ) - { - // HDF5:file_name:subdataset - // filename can be quoted with " as it can contain colons - QRegExp r( "HDF5:(.+):([^:]+)" ); - if ( r.exactMatch( src ) ) - { - QString filename = r.cap( 1 ); - if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) ) - filename = filename.mid( 1, filename.length() - 2 ); - src = "HDF5:\"" + context.pathResolver().writePath( filename ) + "\":" + r.cap( 2 ); - handled = true; - } - } - else if ( src.contains( QRegExp( "^(NITF_IM|RADARSAT_2_CALIB):" ) ) ) - { - // NITF_IM:0:filename - // RADARSAT_2_CALIB:?:filename - QRegExp r( "([^:]+):([^:]+):(.+)" ); - if ( r.exactMatch( src ) ) - { - src = r.cap( 1 ) + ':' + r.cap( 2 ) + ':' + context.pathResolver().writePath( r.cap( 3 ) ); - handled = true; - } - } - } - } - - if ( !handled ) - src = context.pathResolver().writePath( src ); - } - + QString src = encodedSource( source(), context ); QDomText dataSourceText = document.createTextNode( src ); dataSource.appendChild( dataSourceText ); - layerElement.appendChild( dataSource ); - // layer name QDomElement layerName = document.createElement( QStringLiteral( "layername" ) ); QDomText layerNameText = document.createTextNode( name() ); @@ -846,7 +565,20 @@ bool QgsMapLayer::writeXml( QDomNode &layer_node, QDomDocument &document, const // NOP by default; children will over-ride with behavior specific to them return true; -} // void QgsMapLayer::writeXml +} + +QString QgsMapLayer::encodedSource( const QString &source, const QgsReadWriteContext &context ) const +{ + Q_UNUSED( context ); + return source; +} + +QString QgsMapLayer::decodedSource( const QString &source, const QString &dataProvider, const QgsReadWriteContext &context ) const +{ + Q_UNUSED( context ); + Q_UNUSED( dataProvider ); + return source; +} void QgsMapLayer::resolveReferences( QgsProject *project ) { diff --git a/src/core/qgsmaplayer.h b/src/core/qgsmaplayer.h index 3b381f30e64..31e97e89a32 100644 --- a/src/core/qgsmaplayer.h +++ b/src/core/qgsmaplayer.h @@ -1197,6 +1197,33 @@ class CORE_EXPORT QgsMapLayer : public QObject */ virtual bool writeXml( QDomNode &layer_node, QDomDocument &document, const QgsReadWriteContext &context ) const; + /** + * Called by writeLayerXML(), used by derived classes to encode provider's specific data + * source to project files. Typically resolving absolute or relative paths, usernames and + * passwords or drivers prefixes ("HDF5:") + * + * \param source data source to encode, typically QgsMapLayer::source() + * \param context writing context (e.g. for conversion between relative and absolute paths) + * \return encoded source, typically to be written in the dom element "datasource" + * + * \since QGIS 3.2 + */ + virtual QString encodedSource( const QString &source, const QgsReadWriteContext &context ) const; + + /** + * Called by readLayerXML(), used by derived classes to decode provider's specific data + * source from project files. Typically resolving absolute or relative paths, usernames and + * passwords or drivers prefixes ("HDF5:") + * + * \param source data source to decode, typically read from layer's dom element "datasource" + * \param dataProvider string identification of data provider (e.g. "ogr"), typically read from layer's dom element + * \param context reading context (e.g. for conversion between relative and absolute paths) + * \return decoded source, typically to be used as the layer's datasource + * + * \since QGIS 3.2 + */ + virtual QString decodedSource( const QString &source, const QString &dataProvider, const QgsReadWriteContext &context ) const; + /** * Read custom properties from project file. \param layerNode note to read from diff --git a/src/core/qgsproject.cpp b/src/core/qgsproject.cpp index f339e9025c9..070adcc7c4c 100644 --- a/src/core/qgsproject.cpp +++ b/src/core/qgsproject.cpp @@ -48,6 +48,7 @@ #include "qgsprojectbadlayerhandler.h" #include "qgssettings.h" #include "qgsmaplayerlistutils.h" +#include "qgsmeshlayer.h" #include "qgslayoutmanager.h" #include "qgsmaplayerstore.h" #include "qgsziputils.h" @@ -820,6 +821,10 @@ bool QgsProject::addLayer( const QDomElement &layerElem, QList &broken { mapLayer = new QgsRasterLayer; } + else if ( type == QLatin1String( "mesh" ) ) + { + mapLayer = new QgsMeshLayer; + } else if ( type == QLatin1String( "plugin" ) ) { QString typeName = layerElem.attribute( QStringLiteral( "name" ) ); diff --git a/src/core/qgsvectorlayer.cpp b/src/core/qgsvectorlayer.cpp index 4ad78c235aa..00542c91159 100644 --- a/src/core/qgsvectorlayer.cpp +++ b/src/core/qgsvectorlayer.cpp @@ -1718,7 +1718,97 @@ bool QgsVectorLayer::writeXml( QDomNode &layer_node, // renderer specific settings QString errorMsg; return writeSymbology( layer_node, document, errorMsg, context ); -} // bool QgsVectorLayer::writeXml +} + +QString QgsVectorLayer::encodedSource( const QString &source, const QgsReadWriteContext &context ) const +{ + QString src( source ); + + // TODO: what about postgres, mysql and others, they should not go through writePath() + if ( providerType() == QLatin1String( "spatialite" ) ) + { + QgsDataSourceUri uri( src ); + QString database = context.pathResolver().writePath( uri.database() ); + uri.setConnection( uri.host(), uri.port(), database, uri.username(), uri.password() ); + src = uri.uri(); + } + else if ( providerType() == QLatin1String( "ogr" ) ) + { + QStringList theURIParts = src.split( '|' ); + theURIParts[0] = context.pathResolver().writePath( theURIParts[0] ); + src = theURIParts.join( QStringLiteral( "|" ) ); + } + else if ( providerType() == QLatin1String( "gpx" ) ) + { + QStringList theURIParts = src.split( '?' ); + theURIParts[0] = context.pathResolver().writePath( theURIParts[0] ); + src = theURIParts.join( QStringLiteral( "?" ) ); + } + else if ( providerType() == QLatin1String( "delimitedtext" ) ) + { + QUrl urlSource = QUrl::fromEncoded( src.toLatin1() ); + QUrl urlDest = QUrl::fromLocalFile( context.pathResolver().writePath( urlSource.toLocalFile() ) ); + urlDest.setQueryItems( urlSource.queryItems() ); + src = QString::fromLatin1( urlDest.toEncoded() ); + } + else if ( providerType() == QLatin1String( "memory" ) ) + { + // Refetch the source from the provider, because adding fields actually changes the source for this provider. + src = dataProvider()->dataSourceUri(); + } + else + { + src = context.pathResolver().writePath( src ); + } + + return src; +} + +QString QgsVectorLayer::decodedSource( const QString &source, const QString &provider, const QgsReadWriteContext &context ) const +{ + QString src( source ); + + if ( provider == QLatin1String( "spatialite" ) ) + { + QgsDataSourceUri uri( src ); + uri.setDatabase( context.pathResolver().readPath( uri.database() ) ); + src = uri.uri(); + } + else if ( provider == QLatin1String( "ogr" ) ) + { + QStringList theURIParts = src.split( '|' ); + theURIParts[0] = context.pathResolver().readPath( theURIParts[0] ); + src = theURIParts.join( QStringLiteral( "|" ) ); + } + else if ( provider == QLatin1String( "gpx" ) ) + { + QStringList theURIParts = src.split( '?' ); + theURIParts[0] = context.pathResolver().readPath( theURIParts[0] ); + src = theURIParts.join( QStringLiteral( "?" ) ); + } + else if ( provider == QLatin1String( "delimitedtext" ) ) + { + QUrl urlSource = QUrl::fromEncoded( src.toLatin1() ); + + if ( !src.startsWith( QLatin1String( "file:" ) ) ) + { + QUrl file = QUrl::fromLocalFile( src.left( src.indexOf( '?' ) ) ); + urlSource.setScheme( QStringLiteral( "file" ) ); + urlSource.setPath( file.path() ); + } + + QUrl urlDest = QUrl::fromLocalFile( context.pathResolver().readPath( urlSource.toLocalFile() ) ); + urlDest.setQueryItems( urlSource.queryItems() ); + src = QString::fromLatin1( urlDest.toEncoded() ); + } + else + { + src = context.pathResolver().readPath( src ); + } + + return src; +} + void QgsVectorLayer::resolveReferences( QgsProject *project ) diff --git a/src/core/qgsvectorlayer.h b/src/core/qgsvectorlayer.h index dc8d4be71e5..8bcd113c3b4 100644 --- a/src/core/qgsvectorlayer.h +++ b/src/core/qgsvectorlayer.h @@ -764,6 +764,9 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte */ bool writeXml( QDomNode &layer_node, QDomDocument &doc, const QgsReadWriteContext &context ) const override; + QString encodedSource( const QString &source, const QgsReadWriteContext &context ) const override; + QString decodedSource( const QString &source, const QString &provider, const QgsReadWriteContext &context ) const override; + /** * Resolve references to other layers (kept as layer IDs after reading XML) into layer objects. * \since QGIS 3.0 diff --git a/src/core/raster/qgsrasterlayer.cpp b/src/core/raster/qgsrasterlayer.cpp index f7b87e8762e..d553d5c3c81 100644 --- a/src/core/raster/qgsrasterlayer.cpp +++ b/src/core/raster/qgsrasterlayer.cpp @@ -29,6 +29,7 @@ email : tim at linfiniti.com #include "qgsmultibandcolorrenderer.h" #include "qgspainting.h" #include "qgspalettedrasterrenderer.h" +#include "qgspathresolver.h" #include "qgsprojectfiletransform.h" #include "qgsproviderregistry.h" #include "qgsrasterdataprovider.h" @@ -41,6 +42,7 @@ email : tim at linfiniti.com #include "qgsrasterrendererregistry.h" #include "qgsrasterresamplefilter.h" #include "qgsrastershader.h" +#include "qgsreadwritecontext.h" #include "qgsrectangle.h" #include "qgsrendercontext.h" #include "qgssinglebandcolordatarenderer.h" @@ -1621,6 +1623,225 @@ bool QgsRasterLayer::writeXml( QDomNode &layer_node, return writeSymbology( layer_node, document, errorMsg, context ); } +QString QgsRasterLayer::encodedSource( const QString &source, const QgsReadWriteContext &context ) const +{ + QString src( source ); + bool handled = false; + + // Update path for subdataset + if ( providerType() == QLatin1String( "gdal" ) ) + { + if ( src.startsWith( QLatin1String( "NETCDF:" ) ) ) + { + // NETCDF:filename:variable + // filename can be quoted with " as it can contain colons + QRegExp r( "NETCDF:(.+):([^:]+)" ); + if ( r.exactMatch( src ) ) + { + QString filename = r.cap( 1 ); + if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) ) + filename = filename.mid( 1, filename.length() - 2 ); + src = "NETCDF:\"" + context.pathResolver().writePath( filename ) + "\":" + r.cap( 2 ); + handled = true; + } + } + else if ( src.startsWith( QLatin1String( "HDF4_SDS:" ) ) ) + { + // HDF4_SDS:subdataset_type:file_name:subdataset_index + // filename can be quoted with " as it can contain colons + QRegExp r( "HDF4_SDS:([^:]+):(.+):([^:]+)" ); + if ( r.exactMatch( src ) ) + { + QString filename = r.cap( 2 ); + if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) ) + filename = filename.mid( 1, filename.length() - 2 ); + src = "HDF4_SDS:" + r.cap( 1 ) + ":\"" + context.pathResolver().writePath( filename ) + "\":" + r.cap( 3 ); + handled = true; + } + } + else if ( src.startsWith( QLatin1String( "HDF5:" ) ) ) + { + // HDF5:file_name:subdataset + // filename can be quoted with " as it can contain colons + QRegExp r( "HDF5:(.+):([^:]+)" ); + if ( r.exactMatch( src ) ) + { + QString filename = r.cap( 1 ); + if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) ) + filename = filename.mid( 1, filename.length() - 2 ); + src = "HDF5:\"" + context.pathResolver().writePath( filename ) + "\":" + r.cap( 2 ); + handled = true; + } + } + else if ( src.contains( QRegExp( "^(NITF_IM|RADARSAT_2_CALIB):" ) ) ) + { + // NITF_IM:0:filename + // RADARSAT_2_CALIB:?:filename + QRegExp r( "([^:]+):([^:]+):(.+)" ); + if ( r.exactMatch( src ) ) + { + src = r.cap( 1 ) + ':' + r.cap( 2 ) + ':' + context.pathResolver().writePath( r.cap( 3 ) ); + handled = true; + } + } + } + + if ( !handled ) + src = context.pathResolver().writePath( src ); + + return src; +} + +QString QgsRasterLayer::decodedSource( const QString &source, const QString &provider, const QgsReadWriteContext &context ) const +{ + QString src( source ); + + if ( provider == QLatin1String( "wms" ) ) + { + // >>> BACKWARD COMPATIBILITY < 1.9 + // For project file backward compatibility we must support old format: + // 1. mode: + // example: http://example.org/wms? + // 2. mode: tiled=;;;...,ignoreUrl=GetMap;GetFeatureInfo,featureCount=,username=,password=,url= + // example: tiled=256;256;0.703;0.351,url=http://example.org/tilecache? + // example: featureCount=10,http://example.org/wms? + // example: ignoreUrl=GetMap;GetFeatureInfo,username=cimrman,password=jara,url=http://example.org/wms? + // This is modified version of old QgsWmsProvider::parseUri + // The new format has always params crs,format,layers,styles and that params + // should not appear in old format url -> use them to identify version + // XYZ tile layers do not need to contain crs,format params, but they have type=xyz + if ( !src.contains( QLatin1String( "type=" ) ) && + !src.contains( QLatin1String( "crs=" ) ) && !src.contains( QLatin1String( "format=" ) ) ) + { + QgsDebugMsg( "Old WMS URI format detected -> converting to new format" ); + QgsDataSourceUri uri; + if ( !src.startsWith( QLatin1String( "http:" ) ) ) + { + QStringList parts = src.split( ',' ); + QStringListIterator iter( parts ); + while ( iter.hasNext() ) + { + QString item = iter.next(); + if ( item.startsWith( QLatin1String( "username=" ) ) ) + { + uri.setParam( QStringLiteral( "username" ), item.mid( 9 ) ); + } + else if ( item.startsWith( QLatin1String( "password=" ) ) ) + { + uri.setParam( QStringLiteral( "password" ), item.mid( 9 ) ); + } + else if ( item.startsWith( QLatin1String( "tiled=" ) ) ) + { + // in < 1.9 tiled= may apper in to variants: + // tiled=width;height - non tiled mode, specifies max width and max height + // tiled=width;height;resolutions-1;resolution2;... - tile mode + + QStringList params = item.mid( 6 ).split( ';' ); + + if ( params.size() == 2 ) // non tiled mode + { + uri.setParam( QStringLiteral( "maxWidth" ), params.takeFirst() ); + uri.setParam( QStringLiteral( "maxHeight" ), params.takeFirst() ); + } + else if ( params.size() > 2 ) // tiled mode + { + // resolutions are no more needed and size limit is not used for tiles + // we have to tell to the provider however that it is tiled + uri.setParam( QStringLiteral( "tileMatrixSet" ), QLatin1String( "" ) ); + } + } + else if ( item.startsWith( QLatin1String( "featureCount=" ) ) ) + { + uri.setParam( QStringLiteral( "featureCount" ), item.mid( 13 ) ); + } + else if ( item.startsWith( QLatin1String( "url=" ) ) ) + { + uri.setParam( QStringLiteral( "url" ), item.mid( 4 ) ); + } + else if ( item.startsWith( QLatin1String( "ignoreUrl=" ) ) ) + { + uri.setParam( QStringLiteral( "ignoreUrl" ), item.mid( 10 ).split( ';' ) ); + } + } + } + else + { + uri.setParam( QStringLiteral( "url" ), src ); + } + src = uri.encodedUri(); + // At this point, the URI is obviously incomplete, we add additional params + // in QgsRasterLayer::readXml + } + // <<< BACKWARD COMPATIBILITY < 1.9 + } + else + { + bool handled = false; + + if ( provider == QLatin1String( "gdal" ) ) + { + if ( src.startsWith( QLatin1String( "NETCDF:" ) ) ) + { + // NETCDF:filename:variable + // filename can be quoted with " as it can contain colons + QRegExp r( "NETCDF:(.+):([^:]+)" ); + if ( r.exactMatch( src ) ) + { + QString filename = r.cap( 1 ); + if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) ) + filename = filename.mid( 1, filename.length() - 2 ); + src = "NETCDF:\"" + context.pathResolver().readPath( filename ) + "\":" + r.cap( 2 ); + handled = true; + } + } + else if ( src.startsWith( QLatin1String( "HDF4_SDS:" ) ) ) + { + // HDF4_SDS:subdataset_type:file_name:subdataset_index + // filename can be quoted with " as it can contain colons + QRegExp r( "HDF4_SDS:([^:]+):(.+):([^:]+)" ); + if ( r.exactMatch( src ) ) + { + QString filename = r.cap( 2 ); + if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) ) + filename = filename.mid( 1, filename.length() - 2 ); + src = "HDF4_SDS:" + r.cap( 1 ) + ":\"" + context.pathResolver().readPath( filename ) + "\":" + r.cap( 3 ); + handled = true; + } + } + else if ( src.startsWith( QLatin1String( "HDF5:" ) ) ) + { + // HDF5:file_name:subdataset + // filename can be quoted with " as it can contain colons + QRegExp r( "HDF5:(.+):([^:]+)" ); + if ( r.exactMatch( src ) ) + { + QString filename = r.cap( 1 ); + if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) ) + filename = filename.mid( 1, filename.length() - 2 ); + src = "HDF5:\"" + context.pathResolver().readPath( filename ) + "\":" + r.cap( 2 ); + handled = true; + } + } + else if ( src.contains( QRegExp( "^(NITF_IM|RADARSAT_2_CALIB):" ) ) ) + { + // NITF_IM:0:filename + // RADARSAT_2_CALIB:?:filename + QRegExp r( "([^:]+):([^:]+):(.+)" ); + if ( r.exactMatch( src ) ) + { + src = r.cap( 1 ) + ':' + r.cap( 2 ) + ':' + context.pathResolver().readPath( r.cap( 3 ) ); + handled = true; + } + } + } + + if ( !handled ) + src = context.pathResolver().readPath( src ); + } + + return src; +} + int QgsRasterLayer::width() const { if ( !mDataProvider ) return 0; diff --git a/src/core/raster/qgsrasterlayer.h b/src/core/raster/qgsrasterlayer.h index d4638b834a4..3ba0f65f5b3 100644 --- a/src/core/raster/qgsrasterlayer.h +++ b/src/core/raster/qgsrasterlayer.h @@ -388,7 +388,8 @@ class CORE_EXPORT QgsRasterLayer : public QgsMapLayer bool writeSymbology( QDomNode &, QDomDocument &doc, QString &errorMessage, const QgsReadWriteContext &context ) const override; bool writeStyle( QDomNode &node, QDomDocument &doc, QString &errorMessage, const QgsReadWriteContext &context ) const override; bool writeXml( QDomNode &layer_node, QDomDocument &doc, const QgsReadWriteContext &context ) const override; - + QString encodedSource( const QString &source, const QgsReadWriteContext &context ) const override; + QString decodedSource( const QString &source, const QString &provider, const QgsReadWriteContext &context ) const override; private: //! \brief Initialize default values void init(); diff --git a/src/providers/mdal/qgsmdalprovider.h b/src/providers/mdal/qgsmdalprovider.h index db593dfc06f..4c838fdcf5f 100644 --- a/src/providers/mdal/qgsmdalprovider.h +++ b/src/providers/mdal/qgsmdalprovider.h @@ -60,4 +60,3 @@ class QgsMdalProvider : public QgsMeshDataProvider }; #endif //QGSMDALPROVIDER_H - diff --git a/tests/src/core/testqgsmeshlayer.cpp b/tests/src/core/testqgsmeshlayer.cpp index 61c6b763967..5f7b62a259e 100644 --- a/tests/src/core/testqgsmeshlayer.cpp +++ b/tests/src/core/testqgsmeshlayer.cpp @@ -47,10 +47,12 @@ class TestQgsMeshLayer : public QObject void init() {} // will be called before each testfunction is executed. void cleanup() {} // will be called after every testfunction. + void test_write_read_project(); void test_data_provider(); void test_extent(); }; + void TestQgsMeshLayer::initTestCase() { // init QGIS's paths - true means that all path will be inited from prefix @@ -120,5 +122,24 @@ void TestQgsMeshLayer::test_extent() QCOMPARE( mMdalLayer->dataProvider()->extent(), mMdalLayer->extent() ); } +void TestQgsMeshLayer::test_write_read_project() +{ + QgsProject prj; + prj.addMapLayer( mMemoryLayer->clone() ); + prj.addMapLayer( mMdalLayer->clone() ); + + QTemporaryFile f; + QVERIFY( f.open() ); + f.close(); + prj.setFileName( f.fileName() ); + prj.write(); + + QgsProject prj2; + prj2.setFileName( f.fileName() ); + QVERIFY( prj2.read() ); + QVector layers = prj2.layers(); + QVERIFY( layers.size() == 2 ); +} + QGSTEST_MAIN( TestQgsMeshLayer ) #include "testqgsmeshlayer.moc"