QGIS API Documentation  3.12.1-BucureČ™ti (121cc00ff0)
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  }
166  def.setFields( fields );
167 
168  return def;
169 }
170 
171 // Mega ewwww. all this is taken from Qt's QUrl::addEncodedQueryItem compatibility helper.
172 // (I can't see any way to port the below code to NOT require this without breaking
173 // existing projects.)
174 
175 inline char toHexUpper( uint value ) noexcept
176 {
177  return "0123456789ABCDEF"[value & 0xF];
178 }
179 
180 static inline ushort encodeNibble( ushort c )
181 {
182  return ushort( toHexUpper( c ) );
183 }
184 
185 bool qt_is_ascii( const char *&ptr, const char *end ) noexcept
186 {
187  while ( ptr + 4 <= end )
188  {
189  quint32 data = qFromUnaligned<quint32>( ptr );
190  if ( data &= 0x80808080U )
191  {
192 #if Q_BYTE_ORDER == Q_BIG_ENDIAN
193  uint idx = qCountLeadingZeroBits( data );
194 #else
195  uint idx = qCountTrailingZeroBits( data );
196 #endif
197  ptr += idx / 8;
198  return false;
199  }
200  ptr += 4;
201  }
202  while ( ptr != end )
203  {
204  if ( quint8( *ptr ) & 0x80 )
205  return false;
206  ++ptr;
207  }
208  return true;
209 }
210 
211 QString fromEncodedComponent_helper( const QByteArray &ba )
212 {
213  if ( ba.isNull() )
214  return QString();
215  // scan ba for anything above or equal to 0x80
216  // control points below 0x20 are fine in QString
217  const char *in = ba.constData();
218  const char *const end = ba.constEnd();
219  if ( qt_is_ascii( in, end ) )
220  {
221  // no non-ASCII found, we're safe to convert to QString
222  return QString::fromLatin1( ba, ba.size() );
223  }
224  // we found something that we need to encode
225  QByteArray intermediate = ba;
226  intermediate.resize( ba.size() * 3 - ( in - ba.constData() ) );
227  uchar *out = reinterpret_cast<uchar *>( intermediate.data() + ( in - ba.constData() ) );
228  for ( ; in < end; ++in )
229  {
230  if ( *in & 0x80 )
231  {
232  // encode
233  *out++ = '%';
234  *out++ = encodeNibble( uchar( *in ) >> 4 );
235  *out++ = encodeNibble( uchar( *in ) & 0xf );
236  }
237  else
238  {
239  // keep
240  *out++ = uchar( *in );
241  }
242  }
243  // now it's safe to call fromLatin1
244  return QString::fromLatin1( intermediate, out - reinterpret_cast<uchar *>( intermediate.data() ) );
245 }
246 
248 {
249  QUrl url;
250  if ( !filePath().isEmpty() )
251  url = QUrl::fromLocalFile( filePath() );
252 
253  QUrlQuery urlQuery( url );
254 
255  const auto constSourceLayers = sourceLayers();
256  for ( const QgsVirtualLayerDefinition::SourceLayer &l : constSourceLayers )
257  {
258  if ( l.isReferenced() )
259  urlQuery.addQueryItem( QStringLiteral( "layer_ref" ), QStringLiteral( "%1:%2" ).arg( l.reference(), l.name() ) );
260  else
261  // if you can find a way to port this away from fromEncodedComponent_helper without breaking existing projects,
262  // please do so... this is GROSS!
263  urlQuery.addQueryItem( fromEncodedComponent_helper( "layer" ),
264  fromEncodedComponent_helper( QStringLiteral( "%1:%4:%2:%3" ) // the order is important, since the 4th argument may contain '%2' as well
265  .arg( l.provider(),
266  QString( QUrl::toPercentEncoding( l.name() ) ),
267  l.encoding(),
268  QString( QUrl::toPercentEncoding( l.source() ) ) ).toUtf8() ) );
269  }
270 
271  if ( !query().isEmpty() )
272  {
273  urlQuery.addQueryItem( QStringLiteral( "query" ), query() );
274  }
275 
276  if ( !uid().isEmpty() )
277  urlQuery.addQueryItem( QStringLiteral( "uid" ), uid() );
278 
280  urlQuery.addQueryItem( QStringLiteral( "nogeometry" ), QString() );
281  else if ( !geometryField().isEmpty() )
282  {
283  if ( hasDefinedGeometry() )
284  urlQuery.addQueryItem( QStringLiteral( "geometry" ), QStringLiteral( "%1:%2:%3" ).arg( geometryField() ). arg( geometryWkbType() ).arg( geometrySrid() ).toUtf8() );
285  else
286  urlQuery.addQueryItem( QStringLiteral( "geometry" ), geometryField() );
287  }
288 
289  const auto constFields = fields();
290  for ( const QgsField &f : constFields )
291  {
292  if ( f.type() == QVariant::Int )
293  urlQuery.addQueryItem( QStringLiteral( "field" ), f.name() + ":int" );
294  else if ( f.type() == QVariant::Double )
295  urlQuery.addQueryItem( QStringLiteral( "field" ), f.name() + ":real" );
296  else if ( f.type() == QVariant::String )
297  urlQuery.addQueryItem( QStringLiteral( "field" ), f.name() + ":text" );
298  }
299 
300  if ( isLazy() )
301  {
302  urlQuery.addQueryItem( QStringLiteral( "lazy" ), QString() );
303  }
304 
305  url.setQuery( urlQuery );
306 
307  return url;
308 }
309 
311 {
312  return QString( toUrl().toEncoded() );
313 }
314 
315 void QgsVirtualLayerDefinition::addSource( const QString &name, const QString &ref )
316 {
317  mSourceLayers.append( SourceLayer( name, ref ) );
318 }
319 
320 void QgsVirtualLayerDefinition::addSource( const QString &name, const QString &source, const QString &provider, const QString &encoding )
321 {
322  mSourceLayers.append( SourceLayer( name, source, provider, encoding ) );
323 }
324 
325 bool QgsVirtualLayerDefinition::hasSourceLayer( const QString &name ) const
326 {
327  const auto constSourceLayers = sourceLayers();
328  for ( const QgsVirtualLayerDefinition::SourceLayer &l : constSourceLayers )
329  {
330  if ( l.name() == name )
331  {
332  return true;
333  }
334  }
335  return false;
336 }
337 
339 {
340  const auto constSourceLayers = sourceLayers();
341  for ( const QgsVirtualLayerDefinition::SourceLayer &l : constSourceLayers )
342  {
343  if ( l.isReferenced() )
344  {
345  return true;
346  }
347  }
348  return false;
349 }
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:42
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.
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:68
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 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.