QGIS API Documentation  3.17.0-Master (a035f434f4)
qgsvirtuallayerdefinition.cpp
Go to the documentation of this file.
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 
22 #include "qgsvectorlayer.h"
23 #include "qgsvectordataprovider.h"
24 
26  : mFilePath( filePath )
27 {
28 }
29 
31 {
33 
34  def.setFilePath( url.toLocalFile() );
35 
36  // regexp for column name
37  const QString columnNameRx( QStringLiteral( "[a-zA-Z_\x80-\xFF][a-zA-Z0-9_\x80-\xFF]*" ) );
38 
40 
41  int layerIdx = 0;
42 
43  const QList<QPair<QString, QString> > items = QUrlQuery( url ).queryItems( QUrl::FullyEncoded );
44  for ( int i = 0; i < items.size(); i++ )
45  {
46  QString key = items.at( i ).first;
47  QString value = items.at( i ).second;
48  if ( key == QLatin1String( "layer_ref" ) )
49  {
50  layerIdx++;
51  // layer id, with optional layer_name
52  int pos = value.indexOf( ':' );
53  QString layerId, vlayerName;
54  if ( pos == -1 )
55  {
56  layerId = value;
57  vlayerName = QStringLiteral( "vtab%1" ).arg( layerIdx );
58  }
59  else
60  {
61  layerId = value.left( pos );
62  vlayerName = QUrl::fromPercentEncoding( value.mid( pos + 1 ).toUtf8() );
63  }
64  // add the layer to the list
65  def.addSource( vlayerName, layerId );
66  }
67  else if ( key == QLatin1String( "layer" ) )
68  {
69  layerIdx++;
70  // syntax: layer=provider:url_encoded_source_URI(:name(:encoding)?)?
71  int pos = value.indexOf( ':' );
72  if ( pos != -1 )
73  {
74  QString providerKey, source, vlayerName, encoding = QStringLiteral( "UTF-8" );
75 
76  providerKey = value.left( pos );
77  int pos2 = value.indexOf( ':', pos + 1 );
78  if ( pos2 - pos == 2 )
79  pos2 = value.indexOf( ':', pos + 3 );
80  if ( pos2 != -1 )
81  {
82  source = QUrl::fromPercentEncoding( value.mid( pos + 1, pos2 - pos - 1 ).toUtf8() );
83  int pos3 = value.indexOf( ':', pos2 + 1 );
84  if ( pos3 != -1 )
85  {
86  vlayerName = QUrl::fromPercentEncoding( value.mid( pos2 + 1, pos3 - pos2 - 1 ).toUtf8() );
87  encoding = value.mid( pos3 + 1 );
88  }
89  else
90  {
91  vlayerName = QUrl::fromPercentEncoding( value.mid( pos2 + 1 ).toUtf8() );
92  }
93  }
94  else
95  {
96  source = QUrl::fromPercentEncoding( value.mid( pos + 1 ).toUtf8() );
97  vlayerName = QStringLiteral( "vtab%1" ).arg( layerIdx );
98  }
99 
100  def.addSource( vlayerName, source, providerKey, encoding );
101  }
102  }
103  else if ( key == QLatin1String( "geometry" ) )
104  {
105  // geometry field definition, optional
106  // geometry_column(:wkb_type:srid)?
107  QRegExp reGeom( "(" + columnNameRx + ")(?::([a-zA-Z0-9]+):(\\d+))?" );
108  int pos = reGeom.indexIn( value );
109  if ( pos >= 0 )
110  {
111  def.setGeometryField( reGeom.cap( 1 ) );
112  if ( reGeom.captureCount() > 1 )
113  {
114  // not used by the spatialite provider for now ...
115  QgsWkbTypes::Type wkbType = QgsWkbTypes::parseType( reGeom.cap( 2 ) );
116  if ( wkbType == QgsWkbTypes::Unknown )
117  {
118  wkbType = static_cast<QgsWkbTypes::Type>( reGeom.cap( 2 ).toLong() );
119  }
120  def.setGeometryWkbType( wkbType );
121  def.setGeometrySrid( reGeom.cap( 3 ).toLong() );
122  }
123  }
124  }
125  else if ( key == QLatin1String( "nogeometry" ) )
126  {
128  }
129  else if ( key == QLatin1String( "uid" ) )
130  {
131  def.setUid( value );
132  }
133  else if ( key == QLatin1String( "query" ) )
134  {
135  // url encoded query
136  def.setQuery( QUrl::fromPercentEncoding( value.toUtf8() ) );
137  }
138  else if ( key == QLatin1String( "field" ) )
139  {
140  // field_name:type (int, real, text)
141  QRegExp reField( "(" + columnNameRx + "):(int|real|text)" );
142  int pos = reField.indexIn( value );
143  if ( pos >= 0 )
144  {
145  QString fieldName( reField.cap( 1 ) );
146  QString fieldType( reField.cap( 2 ) );
147  if ( fieldType == QLatin1String( "int" ) )
148  {
149  fields.append( QgsField( fieldName, QVariant::Int, fieldType ) );
150  }
151  else if ( fieldType == QLatin1String( "real" ) )
152  {
153  fields.append( QgsField( fieldName, QVariant::Double, fieldType ) );
154  }
155  if ( fieldType == QLatin1String( "text" ) )
156  {
157  fields.append( QgsField( fieldName, QVariant::String, fieldType ) );
158  }
159  }
160  }
161  else if ( key == QLatin1String( "lazy" ) )
162  {
163  def.setLazy( true );
164  }
165  else if ( key == QLatin1String( "subsetstring" ) )
166  {
167  def.setSubsetString( QUrl::fromPercentEncoding( value.toUtf8() ) );
168  }
169  }
170  def.setFields( fields );
171 
172  return def;
173 }
174 
175 // Mega ewwww. all this is taken from Qt's QUrl::addEncodedQueryItem compatibility helper.
176 // (I can't see any way to port the below code to NOT require this without breaking
177 // existing projects.)
178 
179 inline char toHexUpper( uint value ) noexcept
180 {
181  return "0123456789ABCDEF"[value & 0xF];
182 }
183 
184 static inline ushort encodeNibble( ushort c )
185 {
186  return ushort( toHexUpper( c ) );
187 }
188 
189 bool qt_is_ascii( const char *&ptr, const char *end ) noexcept
190 {
191  while ( ptr + 4 <= end )
192  {
193  quint32 data = qFromUnaligned<quint32>( ptr );
194  if ( data &= 0x80808080U )
195  {
196 #if Q_BYTE_ORDER == Q_BIG_ENDIAN
197  uint idx = qCountLeadingZeroBits( data );
198 #else
199  uint idx = qCountTrailingZeroBits( data );
200 #endif
201  ptr += idx / 8;
202  return false;
203  }
204  ptr += 4;
205  }
206  while ( ptr != end )
207  {
208  if ( quint8( *ptr ) & 0x80 )
209  return false;
210  ++ptr;
211  }
212  return true;
213 }
214 
215 QString fromEncodedComponent_helper( const QByteArray &ba )
216 {
217  if ( ba.isNull() )
218  return QString();
219  // scan ba for anything above or equal to 0x80
220  // control points below 0x20 are fine in QString
221  const char *in = ba.constData();
222  const char *const end = ba.constEnd();
223  if ( qt_is_ascii( in, end ) )
224  {
225  // no non-ASCII found, we're safe to convert to QString
226  return QString::fromLatin1( ba, ba.size() );
227  }
228  // we found something that we need to encode
229  QByteArray intermediate = ba;
230  intermediate.resize( ba.size() * 3 - ( in - ba.constData() ) );
231  uchar *out = reinterpret_cast<uchar *>( intermediate.data() + ( in - ba.constData() ) );
232  for ( ; in < end; ++in )
233  {
234  if ( *in & 0x80 )
235  {
236  // encode
237  *out++ = '%';
238  *out++ = encodeNibble( uchar( *in ) >> 4 );
239  *out++ = encodeNibble( uchar( *in ) & 0xf );
240  }
241  else
242  {
243  // keep
244  *out++ = uchar( *in );
245  }
246  }
247  // now it's safe to call fromLatin1
248  return QString::fromLatin1( intermediate, out - reinterpret_cast<uchar *>( intermediate.data() ) );
249 }
250 
252 {
253  QUrl url;
254  if ( !filePath().isEmpty() )
255  url = QUrl::fromLocalFile( filePath() );
256 
257  QUrlQuery urlQuery( url );
258 
259  const auto constSourceLayers = sourceLayers();
260  for ( const QgsVirtualLayerDefinition::SourceLayer &l : constSourceLayers )
261  {
262  if ( l.isReferenced() )
263  urlQuery.addQueryItem( QStringLiteral( "layer_ref" ), QStringLiteral( "%1:%2" ).arg( l.reference(), l.name() ) );
264  else
265  // if you can find a way to port this away from fromEncodedComponent_helper without breaking existing projects,
266  // please do so... this is GROSS!
267  urlQuery.addQueryItem( fromEncodedComponent_helper( "layer" ),
268  fromEncodedComponent_helper( QStringLiteral( "%1:%4:%2:%3" ) // the order is important, since the 4th argument may contain '%2' as well
269  .arg( l.provider(),
270  QString( QUrl::toPercentEncoding( l.name() ) ),
271  l.encoding(),
272  QString( QUrl::toPercentEncoding( l.source() ) ) ).toUtf8() ) );
273  }
274 
275  if ( !query().isEmpty() )
276  {
277  urlQuery.addQueryItem( QStringLiteral( "query" ), query() );
278  }
279 
280  if ( !uid().isEmpty() )
281  urlQuery.addQueryItem( QStringLiteral( "uid" ), uid() );
282 
284  urlQuery.addQueryItem( QStringLiteral( "nogeometry" ), QString() );
285  else if ( !geometryField().isEmpty() )
286  {
287  if ( hasDefinedGeometry() )
288  urlQuery.addQueryItem( QStringLiteral( "geometry" ), QStringLiteral( "%1:%2:%3" ).arg( geometryField() ). arg( geometryWkbType() ).arg( geometrySrid() ).toUtf8() );
289  else
290  urlQuery.addQueryItem( QStringLiteral( "geometry" ), geometryField() );
291  }
292 
293  const auto constFields = fields();
294  for ( const QgsField &f : constFields )
295  {
296  if ( f.type() == QVariant::Int )
297  urlQuery.addQueryItem( QStringLiteral( "field" ), f.name() + ":int" );
298  else if ( f.type() == QVariant::Double )
299  urlQuery.addQueryItem( QStringLiteral( "field" ), f.name() + ":real" );
300  else if ( f.type() == QVariant::String )
301  urlQuery.addQueryItem( QStringLiteral( "field" ), f.name() + ":text" );
302  }
303 
304  if ( isLazy() )
305  {
306  urlQuery.addQueryItem( QStringLiteral( "lazy" ), QString() );
307  }
308 
309  if ( ! subsetString().isEmpty() )
310  {
311  urlQuery.addQueryItem( QStringLiteral( "subsetstring" ), QUrl::toPercentEncoding( subsetString() ) );
312  }
313 
314  url.setQuery( urlQuery );
315 
316  return url;
317 }
318 
320 {
321  return QString( toUrl().toEncoded() );
322 }
323 
324 void QgsVirtualLayerDefinition::addSource( const QString &name, const QString &ref )
325 {
326  mSourceLayers.append( SourceLayer( name, ref ) );
327 }
328 
329 void QgsVirtualLayerDefinition::addSource( const QString &name, const QString &source, const QString &provider, const QString &encoding )
330 {
331  mSourceLayers.append( SourceLayer( name, source, provider, encoding ) );
332 }
333 
334 bool QgsVirtualLayerDefinition::hasSourceLayer( const QString &name ) const
335 {
336  const auto constSourceLayers = sourceLayers();
337  for ( const QgsVirtualLayerDefinition::SourceLayer &l : constSourceLayers )
338  {
339  if ( l.name() == name )
340  {
341  return true;
342  }
343  }
344  return false;
345 }
346 
348 {
349  const auto constSourceLayers = sourceLayers();
350  for ( const QgsVirtualLayerDefinition::SourceLayer &l : constSourceLayers )
351  {
352  if ( l.isReferenced() )
353  {
354  return true;
355  }
356  }
357  return false;
358 }
359 
361 {
362  return mSubsetString;
363 }
364 
366 {
367  mSubsetString = subsetString;
368 }
bool hasReferencedLayers() const
Convenience method to test whether the definition has referenced (live) layers.
bool qt_is_ascii(const char *&ptr, const char *end) noexcept
void setFields(const QgsFields &fields)
Sets field definitions.
QString uid() const
Gets the name of the field with unique identifiers.
static Type parseType(const QString &wktStr)
Attempts to extract the WKB type from a WKT string.
Container of fields for a vector layer.
Definition: qgsfields.h:44
QString toString() const
Convert into a QString that can be read by the virtual layer provider.
static QgsVirtualLayerDefinition fromUrl(const QUrl &url)
Constructor to build a definition from a QUrl The path part of the URL is extracted as well as the fo...
void addSource(const QString &name, const QString &ref)
Add a live layer source layer.
QString subsetString() const
Returns the subset string.
void setFilePath(const QString &filePath)
Sets the file path.
QgsField at(int i) const
Gets field at particular index (must be in range 0..N-1)
Definition: qgsfields.cpp:163
QUrl toUrl() const
Convert the definition into a QUrl.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
QString filePath() const
Gets the file path. May be empty.
Type
The WKB type describes the number of dimensions a geometry has.
Definition: qgswkbtypes.h:69
void setQuery(const QString &query)
Sets the SQL query.
char toHexUpper(uint value) noexcept
void setGeometryField(const QString &geometryField)
Sets the name of the geometry field.
bool isLazy() const
Returns the lazy mode.
QString geometryField() const
Gets the name of the geometry field. Empty if no geometry field.
QgsFields fields() const
Gets field definitions.
bool append(const QgsField &field, FieldOrigin origin=OriginProvider, int originIndex=-1)
Appends a field. The field must have unique name, otherwise it is rejected (returns false) ...
Definition: qgsfields.cpp:59
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:49
QString fromEncodedComponent_helper(const QByteArray &ba)
bool hasSourceLayer(const QString &name) const
Convenience method to test if a given source layer is part of the definition.
const QgsVirtualLayerDefinition::SourceLayers & sourceLayers() const
Gets access to the source layers.
void setGeometryWkbType(QgsWkbTypes::Type t)
Sets the type of the geometry.
bool hasDefinedGeometry() const
Convenient method to test if the geometry is defined (not NoGeometry and not Unknown) ...
void setUid(const QString &uid)
Sets the name of the field with unique identifiers.
void setGeometrySrid(long srid)
Sets the SRID of the geometry.
QString query() const
Gets the SQL query.
void setSubsetString(const QString &subsetString)
Sets the subsetString.
void setLazy(bool lazy)
Sets the lazy mode.
QgsVirtualLayerDefinition(const QString &filePath="")
Constructor with an optional file path.
QgsWkbTypes::Type geometryWkbType() const
Gets the type of the geometry QgsWkbTypes::NoGeometry to hide any geometry QgsWkbTypes::Unknown for u...
A SourceLayer is either a reference to a live layer in the registry or all the parameters needed to l...
Class to manipulate the definition of a virtual layer.
long geometrySrid() const
Gets the SRID of the geometry.