Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgsvirtuallayerdefinition.cpp
3 : : begin : December 2015
4 : : copyright : (C) 2015 Hugo Mercier, Oslandia
5 : : email : hugo dot mercier at oslandia dot com
6 : : ***************************************************************************/
7 : :
8 : : /***************************************************************************
9 : : * *
10 : : * This program is free software; you can redistribute it and/or modify *
11 : : * it under the terms of the GNU General Public License as published by *
12 : : * the Free Software Foundation; either version 2 of the License, or *
13 : : * (at your option) any later version. *
14 : : * *
15 : : ***************************************************************************/
16 : :
17 : : #include <QUrl>
18 : : #include <QRegExp>
19 : : #include <QStringList>
20 : : #include <QUrlQuery>
21 : : #include <QtEndian>
22 : :
23 : : #include "qgsvirtuallayerdefinition.h"
24 : : #include "qgsvectorlayer.h"
25 : : #include "qgsvectordataprovider.h"
26 : :
27 : 0 : QgsVirtualLayerDefinition::QgsVirtualLayerDefinition( const QString &filePath )
28 : 0 : : mFilePath( filePath )
29 : : {
30 : 0 : }
31 : :
32 : 0 : QgsVirtualLayerDefinition QgsVirtualLayerDefinition::fromUrl( const QUrl &url )
33 : : {
34 : 0 : QgsVirtualLayerDefinition def;
35 : :
36 : 0 : def.setFilePath( url.toLocalFile() );
37 : :
38 : : // regexp for column name
39 : 0 : const QString columnNameRx( QStringLiteral( "[a-zA-Z_\x80-\xFF][a-zA-Z0-9_\x80-\xFF]*" ) );
40 : :
41 : 0 : QgsFields fields;
42 : :
43 : 0 : int layerIdx = 0;
44 : :
45 : 0 : const QList<QPair<QString, QString> > items = QUrlQuery( url ).queryItems( QUrl::FullyEncoded );
46 : 0 : for ( int i = 0; i < items.size(); i++ )
47 : : {
48 : 0 : QString key = items.at( i ).first;
49 : 0 : QString value = items.at( i ).second;
50 : 0 : if ( key == QLatin1String( "layer_ref" ) )
51 : : {
52 : 0 : layerIdx++;
53 : : // layer id, with optional layer_name
54 : 0 : int pos = value.indexOf( ':' );
55 : 0 : QString layerId, vlayerName;
56 : 0 : if ( pos == -1 )
57 : : {
58 : 0 : layerId = value;
59 : 0 : vlayerName = QStringLiteral( "vtab%1" ).arg( layerIdx );
60 : 0 : }
61 : : else
62 : : {
63 : 0 : layerId = value.left( pos );
64 : 0 : vlayerName = QUrl::fromPercentEncoding( value.mid( pos + 1 ).toUtf8() );
65 : : }
66 : : // add the layer to the list
67 : 0 : def.addSource( vlayerName, layerId );
68 : 0 : }
69 : 0 : else if ( key == QLatin1String( "layer" ) )
70 : : {
71 : 0 : layerIdx++;
72 : : // syntax: layer=provider:url_encoded_source_URI(:name(:encoding)?)?
73 : 0 : int pos = value.indexOf( ':' );
74 : 0 : if ( pos != -1 )
75 : : {
76 : 0 : QString providerKey, source, vlayerName, encoding = QStringLiteral( "UTF-8" );
77 : :
78 : 0 : providerKey = value.left( pos );
79 : 0 : int pos2 = value.indexOf( ':', pos + 1 );
80 : 0 : if ( pos2 - pos == 2 )
81 : 0 : pos2 = value.indexOf( ':', pos + 3 );
82 : 0 : if ( pos2 != -1 )
83 : : {
84 : 0 : source = QUrl::fromPercentEncoding( value.mid( pos + 1, pos2 - pos - 1 ).toUtf8() );
85 : 0 : int pos3 = value.indexOf( ':', pos2 + 1 );
86 : 0 : if ( pos3 != -1 )
87 : : {
88 : 0 : vlayerName = QUrl::fromPercentEncoding( value.mid( pos2 + 1, pos3 - pos2 - 1 ).toUtf8() );
89 : 0 : encoding = value.mid( pos3 + 1 );
90 : 0 : }
91 : : else
92 : : {
93 : 0 : vlayerName = QUrl::fromPercentEncoding( value.mid( pos2 + 1 ).toUtf8() );
94 : : }
95 : 0 : }
96 : : else
97 : : {
98 : 0 : source = QUrl::fromPercentEncoding( value.mid( pos + 1 ).toUtf8() );
99 : 0 : vlayerName = QStringLiteral( "vtab%1" ).arg( layerIdx );
100 : : }
101 : :
102 : 0 : def.addSource( vlayerName, source, providerKey, encoding );
103 : 0 : }
104 : 0 : }
105 : 0 : else if ( key == QLatin1String( "geometry" ) )
106 : : {
107 : : // geometry field definition, optional
108 : : // geometry_column(:wkb_type:srid)?
109 : 0 : QRegExp reGeom( "(" + columnNameRx + ")(?::([a-zA-Z0-9]+):(\\d+))?" );
110 : 0 : int pos = reGeom.indexIn( value );
111 : 0 : if ( pos >= 0 )
112 : : {
113 : 0 : def.setGeometryField( reGeom.cap( 1 ) );
114 : 0 : if ( reGeom.captureCount() > 1 )
115 : : {
116 : : // not used by the spatialite provider for now ...
117 : 0 : QgsWkbTypes::Type wkbType = QgsWkbTypes::parseType( reGeom.cap( 2 ) );
118 : 0 : if ( wkbType == QgsWkbTypes::Unknown )
119 : : {
120 : 0 : wkbType = static_cast<QgsWkbTypes::Type>( reGeom.cap( 2 ).toLong() );
121 : 0 : }
122 : 0 : def.setGeometryWkbType( wkbType );
123 : 0 : def.setGeometrySrid( reGeom.cap( 3 ).toLong() );
124 : 0 : }
125 : 0 : }
126 : 0 : }
127 : 0 : else if ( key == QLatin1String( "nogeometry" ) )
128 : : {
129 : 0 : def.setGeometryWkbType( QgsWkbTypes::NoGeometry );
130 : 0 : }
131 : 0 : else if ( key == QLatin1String( "uid" ) )
132 : : {
133 : 0 : def.setUid( value );
134 : 0 : }
135 : 0 : else if ( key == QLatin1String( "query" ) )
136 : : {
137 : : // url encoded query
138 : 0 : def.setQuery( QUrl::fromPercentEncoding( value.toUtf8() ) );
139 : 0 : }
140 : 0 : else if ( key == QLatin1String( "field" ) )
141 : : {
142 : : // field_name:type (int, real, text)
143 : 0 : QRegExp reField( "(" + columnNameRx + "):(int|real|text)" );
144 : 0 : int pos = reField.indexIn( value );
145 : 0 : if ( pos >= 0 )
146 : : {
147 : 0 : QString fieldName( reField.cap( 1 ) );
148 : 0 : QString fieldType( reField.cap( 2 ) );
149 : 0 : if ( fieldType == QLatin1String( "int" ) )
150 : : {
151 : 0 : fields.append( QgsField( fieldName, QVariant::LongLong, fieldType ) );
152 : 0 : }
153 : 0 : else if ( fieldType == QLatin1String( "real" ) )
154 : : {
155 : 0 : fields.append( QgsField( fieldName, QVariant::Double, fieldType ) );
156 : 0 : }
157 : 0 : if ( fieldType == QLatin1String( "text" ) )
158 : : {
159 : 0 : fields.append( QgsField( fieldName, QVariant::String, fieldType ) );
160 : 0 : }
161 : 0 : }
162 : 0 : }
163 : 0 : else if ( key == QLatin1String( "lazy" ) )
164 : : {
165 : 0 : def.setLazy( true );
166 : 0 : }
167 : 0 : else if ( key == QLatin1String( "subsetstring" ) )
168 : : {
169 : 0 : def.setSubsetString( QUrl::fromPercentEncoding( value.toUtf8() ) );
170 : 0 : }
171 : 0 : }
172 : 0 : def.setFields( fields );
173 : :
174 : 0 : return def;
175 : 0 : }
176 : :
177 : : // Mega ewwww. all this is taken from Qt's QUrl::addEncodedQueryItem compatibility helper.
178 : : // (I can't see any way to port the below code to NOT require this without breaking
179 : : // existing projects.)
180 : :
181 : 0 : inline char toHexUpper( uint value ) noexcept
182 : : {
183 : 0 : return "0123456789ABCDEF"[value & 0xF];
184 : : }
185 : :
186 : 0 : static inline ushort encodeNibble( ushort c )
187 : : {
188 : 0 : return ushort( toHexUpper( c ) );
189 : : }
190 : :
191 : 0 : static bool qt_is_ascii( const char *&ptr, const char *end ) noexcept
192 : : {
193 : 0 : while ( ptr + 4 <= end )
194 : : {
195 : 0 : quint32 data = qFromUnaligned<quint32>( ptr );
196 : 0 : if ( data &= 0x80808080U )
197 : : {
198 : : #if Q_BYTE_ORDER == Q_BIG_ENDIAN
199 : : uint idx = qCountLeadingZeroBits( data );
200 : : #else
201 : 0 : uint idx = qCountTrailingZeroBits( data );
202 : : #endif
203 : 0 : ptr += idx / 8;
204 : 0 : return false;
205 : : }
206 : 0 : ptr += 4;
207 : : }
208 : 0 : while ( ptr != end )
209 : 0 : {
210 : 0 : if ( quint8( *ptr ) & 0x80 )
211 : 0 : return false;
212 : 0 : ++ptr;
213 : : }
214 : 0 : return true;
215 : 0 : }
216 : :
217 : 0 : QString fromEncodedComponent_helper( const QByteArray &ba )
218 : : {
219 : 0 : if ( ba.isNull() )
220 : 0 : return QString();
221 : : // scan ba for anything above or equal to 0x80
222 : : // control points below 0x20 are fine in QString
223 : 0 : const char *in = ba.constData();
224 : 0 : const char *const end = ba.constEnd();
225 : 0 : if ( qt_is_ascii( in, end ) )
226 : : {
227 : : // no non-ASCII found, we're safe to convert to QString
228 : 0 : return QString::fromLatin1( ba, ba.size() );
229 : : }
230 : : // we found something that we need to encode
231 : 0 : QByteArray intermediate = ba;
232 : 0 : intermediate.resize( ba.size() * 3 - ( in - ba.constData() ) );
233 : 0 : uchar *out = reinterpret_cast<uchar *>( intermediate.data() + ( in - ba.constData() ) );
234 : 0 : for ( ; in < end; ++in )
235 : : {
236 : 0 : if ( *in & 0x80 )
237 : : {
238 : : // encode
239 : 0 : *out++ = '%';
240 : 0 : *out++ = encodeNibble( uchar( *in ) >> 4 );
241 : 0 : *out++ = encodeNibble( uchar( *in ) & 0xf );
242 : 0 : }
243 : : else
244 : : {
245 : : // keep
246 : 0 : *out++ = uchar( *in );
247 : : }
248 : 0 : }
249 : : // now it's safe to call fromLatin1
250 : 0 : return QString::fromLatin1( intermediate, out - reinterpret_cast<uchar *>( intermediate.data() ) );
251 : 0 : }
252 : :
253 : 0 : QUrl QgsVirtualLayerDefinition::toUrl() const
254 : : {
255 : 0 : QUrl url;
256 : 0 : if ( !filePath().isEmpty() )
257 : 0 : url = QUrl::fromLocalFile( filePath() );
258 : :
259 : 0 : QUrlQuery urlQuery( url );
260 : :
261 : 0 : const auto constSourceLayers = sourceLayers();
262 : 0 : for ( const QgsVirtualLayerDefinition::SourceLayer &l : constSourceLayers )
263 : : {
264 : 0 : if ( l.isReferenced() )
265 : 0 : urlQuery.addQueryItem( QStringLiteral( "layer_ref" ), QStringLiteral( "%1:%2" ).arg( l.reference(), l.name() ) );
266 : : else
267 : : // if you can find a way to port this away from fromEncodedComponent_helper without breaking existing projects,
268 : : // please do so... this is GROSS!
269 : 0 : urlQuery.addQueryItem( fromEncodedComponent_helper( "layer" ),
270 : 0 : fromEncodedComponent_helper( QStringLiteral( "%1:%4:%2:%3" ) // the order is important, since the 4th argument may contain '%2' as well
271 : 0 : .arg( l.provider(),
272 : 0 : QString( QUrl::toPercentEncoding( l.name() ) ),
273 : 0 : l.encoding(),
274 : 0 : QString( QUrl::toPercentEncoding( l.source() ) ) ).toUtf8() ) );
275 : : }
276 : :
277 : 0 : if ( !query().isEmpty() )
278 : : {
279 : 0 : urlQuery.addQueryItem( QStringLiteral( "query" ), query() );
280 : 0 : }
281 : :
282 : 0 : if ( !uid().isEmpty() )
283 : 0 : urlQuery.addQueryItem( QStringLiteral( "uid" ), uid() );
284 : :
285 : 0 : if ( geometryWkbType() == QgsWkbTypes::NoGeometry )
286 : 0 : urlQuery.addQueryItem( QStringLiteral( "nogeometry" ), QString() );
287 : 0 : else if ( !geometryField().isEmpty() )
288 : : {
289 : 0 : if ( hasDefinedGeometry() )
290 : 0 : urlQuery.addQueryItem( QStringLiteral( "geometry" ), QStringLiteral( "%1:%2:%3" ).arg( geometryField() ). arg( geometryWkbType() ).arg( geometrySrid() ).toUtf8() );
291 : : else
292 : 0 : urlQuery.addQueryItem( QStringLiteral( "geometry" ), geometryField() );
293 : 0 : }
294 : :
295 : 0 : const auto constFields = fields();
296 : 0 : for ( const QgsField &f : constFields )
297 : : {
298 : 0 : if ( f.type() == QVariant::Int
299 : 0 : || f.type() == QVariant::UInt
300 : 0 : || f.type() == QVariant::Bool
301 : 0 : || f.type() == QVariant::LongLong )
302 : 0 : urlQuery.addQueryItem( QStringLiteral( "field" ), f.name() + ":int" );
303 : 0 : else if ( f.type() == QVariant::Double )
304 : 0 : urlQuery.addQueryItem( QStringLiteral( "field" ), f.name() + ":real" );
305 : 0 : else if ( f.type() == QVariant::String )
306 : 0 : urlQuery.addQueryItem( QStringLiteral( "field" ), f.name() + ":text" );
307 : : }
308 : :
309 : 0 : if ( isLazy() )
310 : : {
311 : 0 : urlQuery.addQueryItem( QStringLiteral( "lazy" ), QString() );
312 : 0 : }
313 : :
314 : 0 : if ( ! subsetString().isEmpty() )
315 : : {
316 : 0 : urlQuery.addQueryItem( QStringLiteral( "subsetstring" ), QUrl::toPercentEncoding( subsetString() ) );
317 : 0 : }
318 : :
319 : 0 : url.setQuery( urlQuery );
320 : :
321 : 0 : return url;
322 : 0 : }
323 : :
324 : 0 : QString QgsVirtualLayerDefinition::toString() const
325 : : {
326 : 0 : return QString( toUrl().toEncoded() );
327 : 0 : }
328 : :
329 : 0 : void QgsVirtualLayerDefinition::addSource( const QString &name, const QString &ref )
330 : : {
331 : 0 : mSourceLayers.append( SourceLayer( name, ref ) );
332 : 0 : }
333 : :
334 : 0 : void QgsVirtualLayerDefinition::addSource( const QString &name, const QString &source, const QString &provider, const QString &encoding )
335 : : {
336 : 0 : mSourceLayers.append( SourceLayer( name, source, provider, encoding ) );
337 : 0 : }
338 : :
339 : 0 : bool QgsVirtualLayerDefinition::hasSourceLayer( const QString &name ) const
340 : : {
341 : 0 : const auto constSourceLayers = sourceLayers();
342 : 0 : for ( const QgsVirtualLayerDefinition::SourceLayer &l : constSourceLayers )
343 : : {
344 : 0 : if ( l.name() == name )
345 : : {
346 : 0 : return true;
347 : : }
348 : : }
349 : 0 : return false;
350 : 0 : }
351 : :
352 : 0 : bool QgsVirtualLayerDefinition::hasReferencedLayers() const
353 : : {
354 : 0 : const auto constSourceLayers = sourceLayers();
355 : 0 : for ( const QgsVirtualLayerDefinition::SourceLayer &l : constSourceLayers )
356 : : {
357 : 0 : if ( l.isReferenced() )
358 : : {
359 : 0 : return true;
360 : : }
361 : : }
362 : 0 : return false;
363 : 0 : }
364 : :
365 : 0 : QString QgsVirtualLayerDefinition::subsetString() const
366 : : {
367 : 0 : return mSubsetString;
368 : : }
369 : :
370 : 0 : void QgsVirtualLayerDefinition::setSubsetString( const QString &subsetString )
371 : : {
372 : 0 : mSubsetString = subsetString;
373 : 0 : }
|