QGIS API Documentation  2.99.0-Master (6a61179)
qgsgmlschema.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsgmlschema.cpp
3  --------------------------------------
4  Date : February 2013
5  Copyright : (C) 2013 by Radim Blazek
6  Email : [email protected]
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 #include "qgsgmlschema.h"
16 #include "qgsrectangle.h"
18 #include "qgserror.h"
19 #include "qgsgeometry.h"
20 #include "qgslogger.h"
22 #include <QBuffer>
23 #include <QList>
24 #include <QNetworkRequest>
25 #include <QNetworkReply>
26 #include <QProgressDialog>
27 #include <QSet>
28 #include <QSettings>
29 #include <QUrl>
30 
31 #include <limits>
32 
33 const char NS_SEPARATOR = '?';
34 const QString GML_NAMESPACE = QStringLiteral( "http://www.opengis.net/gml" );
35 
37 {
38 }
39 
40 QgsGmlFeatureClass::QgsGmlFeatureClass( const QString& name, const QString& path )
41  : mName( name )
42  , mPath( path )
43 {
44 }
45 
46 int QgsGmlFeatureClass::fieldIndex( const QString & name )
47 {
48  for ( int i = 0; i < mFields.size(); i++ )
49  {
50  if ( mFields[i].name() == name ) return i;
51  }
52  return -1;
53 }
54 
55 // --------------------------- QgsGmlSchema -------------------------------
57  : QObject()
58  , mCurrentFeature( nullptr )
59  , mFeatureCount( 0 )
60  , mLevel( 0 )
61  , mSkipLevel( std::numeric_limits<int>::max() )
62 {
63  mGeometryTypes << QStringLiteral( "Point" ) << QStringLiteral( "MultiPoint" )
64  << QStringLiteral( "LineString" ) << QStringLiteral( "MultiLineString" )
65  << QStringLiteral( "Polygon" ) << QStringLiteral( "MultiPolygon" );
66 }
67 
69 {
70 
71 }
72 
73 QString QgsGmlSchema::readAttribute( const QString& attributeName, const XML_Char** attr ) const
74 {
75  int i = 0;
76  while ( attr[i] )
77  {
78  if ( attributeName.compare( attr[i] ) == 0 )
79  {
80  return QString( attr[i+1] );
81  }
82  i += 2;
83  }
84  return QString();
85 }
86 
87 bool QgsGmlSchema::parseXSD( const QByteArray &xml )
88 {
89  QDomDocument dom;
90  QString errorMsg;
91  int errorLine;
92  int errorColumn;
93  if ( !dom.setContent( xml, false, &errorMsg, &errorLine, &errorColumn ) )
94  {
95  // TODO: error
96  return false;
97  }
98 
99  QDomElement docElem = dom.documentElement();
100 
101  QList<QDomElement> elementElements = domElements( docElem, QStringLiteral( "element" ) );
102 
103  //QgsDebugMsg( QString( "%1 elemets read" ).arg( elementElements.size() ) );
104 
105  Q_FOREACH ( const QDomElement& elementElement, elementElements )
106  {
107  QString name = elementElement.attribute( QStringLiteral( "name" ) );
108  QString type = elementElement.attribute( QStringLiteral( "type" ) );
109 
110  QString gmlBaseType = xsdComplexTypeGmlBaseType( docElem, stripNS( type ) );
111  //QgsDebugMsg( QString( "gmlBaseType = %1" ).arg( gmlBaseType ) );
112  //QgsDebugMsg( QString( "name = %1 gmlBaseType = %2" ).arg( name ).arg( gmlBaseType ) );
113  // We should only use gml:AbstractFeatureType descendants which have
114  // ancestor listed in gml:FeatureAssociationType (featureMember) descendant
115  // But we could only loose some data if XSD was not correct, I think.
116 
117  if ( gmlBaseType == QLatin1String( "AbstractFeatureType" ) )
118  {
119  // Get feature type definition
120  QgsGmlFeatureClass featureClass( name, QLatin1String( "" ) );
121  xsdFeatureClass( docElem, stripNS( type ), featureClass );
122  mFeatureClassMap.insert( name, featureClass );
123  }
124  // A feature may have more geometries, we take just the first one
125  }
126 
127  return true;
128 }
129 
130 bool QgsGmlSchema::xsdFeatureClass( const QDomElement &element, const QString & typeName, QgsGmlFeatureClass & featureClass )
131 {
132  //QgsDebugMsg("typeName = " + typeName );
133  QDomElement complexTypeElement = domElement( element, QStringLiteral( "complexType" ), QStringLiteral( "name" ), typeName );
134  if ( complexTypeElement.isNull() ) return false;
135 
136  // extension or restriction
137  QDomElement extrest = domElement( complexTypeElement, QStringLiteral( "complexContent.extension" ) );
138  if ( extrest.isNull() )
139  {
140  extrest = domElement( complexTypeElement, QStringLiteral( "complexContent.restriction" ) );
141  }
142  if ( extrest.isNull() ) return false;
143 
144  QString extrestName = extrest.attribute( QStringLiteral( "base" ) );
145  if ( extrestName == QLatin1String( "gml:AbstractFeatureType" ) )
146  {
147  // In theory we should add gml:AbstractFeatureType default attributes gml:description
148  // and gml:name but it does not seem to be a common practice and we would probably
149  // confuse most users
150  }
151  else
152  {
153  // Get attributes from extrest
154  if ( !xsdFeatureClass( element, stripNS( extrestName ), featureClass ) ) return false;
155  }
156 
157  // Supported geometry types
158  QStringList geometryPropertyTypes;
159  Q_FOREACH ( const QString& geom, mGeometryTypes )
160  {
161  geometryPropertyTypes << geom + "PropertyType";
162  }
163 
164  QStringList geometryAliases;
165  geometryAliases << QStringLiteral( "location" ) << QStringLiteral( "centerOf" ) << QStringLiteral( "position" ) << QStringLiteral( "extentOf" )
166  << QStringLiteral( "coverage" ) << QStringLiteral( "edgeOf" ) << QStringLiteral( "centerLineOf" ) << QStringLiteral( "multiLocation" )
167  << QStringLiteral( "multiCenterOf" ) << QStringLiteral( "multiPosition" ) << QStringLiteral( "multiCenterLineOf" )
168  << QStringLiteral( "multiEdgeOf" ) << QStringLiteral( "multiCoverage" ) << QStringLiteral( "multiExtentOf" );
169 
170  // Add attributes from current comple type
171  QList<QDomElement> sequenceElements = domElements( extrest, QStringLiteral( "sequence.element" ) );
172  Q_FOREACH ( const QDomElement& sequenceElement, sequenceElements )
173  {
174  QString fieldName = sequenceElement.attribute( QStringLiteral( "name" ) );
175  QString fieldTypeName = stripNS( sequenceElement.attribute( QStringLiteral( "type" ) ) );
176  QString ref = sequenceElement.attribute( QStringLiteral( "ref" ) );
177  //QgsDebugMsg ( QString("fieldName = %1 fieldTypeName = %2 ref = %3").arg(fieldName).arg(fieldTypeName).arg(ref) );
178 
179  if ( !ref.isEmpty() )
180  {
181  if ( ref.startsWith( QLatin1String( "gml:" ) ) )
182  {
183  if ( geometryAliases.contains( stripNS( ref ) ) )
184  {
185  featureClass.geometryAttributes().append( stripNS( ref ) );
186  }
187  else
188  {
189  QgsDebugMsg( QString( "Unknown referenced GML element: %1" ).arg( ref ) );
190  }
191  }
192  else
193  {
194  // TODO: get type from referenced element
195  QgsDebugMsg( QString( "field %1.%2 is referencing %3 - not supported" ).arg( typeName, fieldName ) );
196  }
197  continue;
198  }
199 
200  if ( fieldName.isEmpty() )
201  {
202  QgsDebugMsg( QString( "field in %1 without name" ).arg( typeName ) );
203  continue;
204  }
205 
206  // type is either type attribute
207  if ( fieldTypeName.isEmpty() )
208  {
209  // or type is inheriting from xs:simpleType
210  QDomElement sequenceElementRestriction = domElement( sequenceElement, QStringLiteral( "simpleType.restriction" ) );
211  fieldTypeName = stripNS( sequenceElementRestriction.attribute( QStringLiteral( "base" ) ) );
212  }
213 
214  QVariant::Type fieldType = QVariant::String;
215  if ( fieldTypeName.isEmpty() )
216  {
217  QgsDebugMsg( QString( "Cannot get %1.%2 field type" ).arg( typeName, fieldName ) );
218  }
219  else
220  {
221  if ( geometryPropertyTypes.contains( fieldTypeName ) )
222  {
223  // Geometry attribute
224  featureClass.geometryAttributes().append( fieldName );
225  continue;
226  }
227 
228  if ( fieldTypeName == QLatin1String( "decimal" ) )
229  {
230  fieldType = QVariant::Double;
231  }
232  else if ( fieldTypeName == QLatin1String( "integer" ) )
233  {
234  fieldType = QVariant::Int;
235  }
236  }
237 
238  QgsField field( fieldName, fieldType, fieldTypeName );
239  featureClass.fields().append( field );
240  }
241 
242  return true;
243 }
244 
245 QString QgsGmlSchema::xsdComplexTypeGmlBaseType( const QDomElement &element, const QString & name )
246 {
247  //QgsDebugMsg("name = " + name );
248  QDomElement complexTypeElement = domElement( element, QStringLiteral( "complexType" ), QStringLiteral( "name" ), name );
249  if ( complexTypeElement.isNull() ) return QLatin1String( "" );
250 
251  QDomElement extrest = domElement( complexTypeElement, QStringLiteral( "complexContent.extension" ) );
252  if ( extrest.isNull() )
253  {
254  extrest = domElement( complexTypeElement, QStringLiteral( "complexContent.restriction" ) );
255  }
256  if ( extrest.isNull() ) return QLatin1String( "" );
257 
258  QString extrestName = extrest.attribute( QStringLiteral( "base" ) );
259  if ( extrestName.startsWith( QLatin1String( "gml:" ) ) )
260  {
261  // GML base type found
262  return stripNS( extrestName );
263  }
264  // Continue recursively until GML base type is reached
265  return xsdComplexTypeGmlBaseType( element, stripNS( extrestName ) );
266 }
267 
268 QString QgsGmlSchema::stripNS( const QString & name )
269 {
270  return name.contains( ':' ) ? name.section( ':', 1 ) : name;
271 }
272 
273 QList<QDomElement> QgsGmlSchema::domElements( const QDomElement &element, const QString & path )
274 {
275  QList<QDomElement> list;
276 
277  QStringList names = path.split( '.' );
278  if ( names.isEmpty() ) return list;
279  QString name = names.value( 0 );
280  names.removeFirst();
281 
282  QDomNode n1 = element.firstChild();
283  while ( !n1.isNull() )
284  {
285  QDomElement el = n1.toElement();
286  if ( !el.isNull() )
287  {
288  QString tagName = stripNS( el.tagName() );
289  if ( tagName == name )
290  {
291  if ( names.isEmpty() )
292  {
293  list.append( el );
294  }
295  else
296  {
297  list.append( domElements( el, names.join( QStringLiteral( "." ) ) ) );
298  }
299  }
300  }
301  n1 = n1.nextSibling();
302  }
303 
304  return list;
305 }
306 
307 QDomElement QgsGmlSchema::domElement( const QDomElement &element, const QString & path )
308 {
309  return domElements( element, path ).value( 0 );
310 }
311 
312 QList<QDomElement> QgsGmlSchema::domElements( QList<QDomElement> &elements, const QString & attr, const QString & attrVal )
313 {
314  QList<QDomElement> list;
315  Q_FOREACH ( const QDomElement& el, elements )
316  {
317  if ( el.attribute( attr ) == attrVal )
318  {
319  list << el;
320  }
321  }
322  return list;
323 }
324 
325 QDomElement QgsGmlSchema::domElement( const QDomElement &element, const QString & path, const QString & attr, const QString & attrVal )
326 {
327  QList<QDomElement> list = domElements( element, path );
328  return domElements( list, attr, attrVal ).value( 0 );
329 }
330 
331 bool QgsGmlSchema::guessSchema( const QByteArray &data )
332 {
333  mLevel = 0;
334  mSkipLevel = std::numeric_limits<int>::max();
335  XML_Parser p = XML_ParserCreateNS( nullptr, NS_SEPARATOR );
336  XML_SetUserData( p, this );
337  XML_SetElementHandler( p, QgsGmlSchema::start, QgsGmlSchema::end );
338  XML_SetCharacterDataHandler( p, QgsGmlSchema::chars );
339  int atEnd = 1;
340  int res = XML_Parse( p, data.constData(), data.size(), atEnd );
341 
342  if ( res == 0 )
343  {
344  QString err = QString( XML_ErrorString( XML_GetErrorCode( p ) ) );
345  QgsDebugMsg( QString( "XML_Parse returned %1 error %2" ).arg( res ).arg( err ) );
346  mError = QgsError( err, QStringLiteral( "GML schema" ) );
347  mError.append( tr( "Cannot guess schema" ) );
348  }
349 
350  return res != 0;
351 }
352 
353 void QgsGmlSchema::startElement( const XML_Char* el, const XML_Char** attr )
354 {
355  Q_UNUSED( attr );
356  mLevel++;
357 
358  QString elementName = QString::fromUtf8( el );
359  QgsDebugMsgLevel( QString( "-> %1 %2 %3" ).arg( mLevel ).arg( elementName, mLevel >= mSkipLevel ? "skip" : "" ), 5 );
360 
361  if ( mLevel >= mSkipLevel )
362  {
363  //QgsDebugMsg( QString("skip level %1").arg( mLevel ) );
364  return;
365  }
366 
367  mParsePathStack.append( elementName );
368  QString path = mParsePathStack.join( QStringLiteral( "." ) );
369 
370  QStringList splitName = elementName.split( NS_SEPARATOR );
371  QString localName = splitName.last();
372  QString ns = splitName.size() > 1 ? splitName.first() : QLatin1String( "" );
373  //QgsDebugMsg( "ns = " + ns + " localName = " + localName );
374 
375  ParseMode parseMode = modeStackTop();
376  //QgsDebugMsg ( QString("localName = %1 parseMode = %2").arg(localName).arg(parseMode) );
377 
378  if ( ns == GML_NAMESPACE && localName == QLatin1String( "boundedBy" ) )
379  {
380  // gml:boundedBy in feature or feature collection -> skip
381  mSkipLevel = mLevel + 1;
382  }
383  else if ( localName.compare( QLatin1String( "featureMembers" ), Qt::CaseInsensitive ) == 0 )
384  {
385  mParseModeStack.push( QgsGmlSchema::featureMembers );
386  }
387  // GML does not specify that gml:FeatureAssociationType elements should end
388  // with 'Member' apart standard gml:featureMember, but it is quite usual to
389  // that the names ends with 'Member', e.g.: osgb:topographicMember, cityMember,...
390  // so this is really fail if the name does not contain 'Member'
391 
392  else if ( localName.endsWith( QLatin1String( "member" ), Qt::CaseInsensitive ) )
393  {
394  mParseModeStack.push( QgsGmlSchema::featureMember );
395  }
396  // UMN Mapserver simple GetFeatureInfo response layer element (ends with _layer)
397  else if ( elementName.endsWith( QLatin1String( "_layer" ) ) )
398  {
399  // do nothing, we catch _feature children
400  }
401  // UMN Mapserver simple GetFeatureInfo response feature element (ends with _feature)
402  // or featureMember children.
403  // QGIS mapserver 2.2 GetFeatureInfo is using <Feature id="###"> for feature member,
404  // without any feature class distinction.
405  else if ( elementName.endsWith( QLatin1String( "_feature" ) )
406  || parseMode == QgsGmlSchema::featureMember
407  || parseMode == QgsGmlSchema::featureMembers
408  || localName.compare( QLatin1String( "feature" ), Qt::CaseInsensitive ) == 0 )
409  {
410  QgsDebugMsg( "is feature path = " + path );
411  if ( mFeatureClassMap.count( localName ) == 0 )
412  {
413  mFeatureClassMap.insert( localName, QgsGmlFeatureClass( localName, path ) );
414  }
415  mCurrentFeatureName = localName;
416  mParseModeStack.push( QgsGmlSchema::feature );
417  }
418  else if ( parseMode == QgsGmlSchema::attribute && ns == GML_NAMESPACE && mGeometryTypes.indexOf( localName ) >= 0 )
419  {
420  // Geometry (Point,MultiPoint,...) in geometry attribute
421  QStringList &geometryAttributes = mFeatureClassMap[mCurrentFeatureName].geometryAttributes();
422  if ( geometryAttributes.count( mAttributeName ) == 0 )
423  {
424  geometryAttributes.append( mAttributeName );
425  }
426  mSkipLevel = mLevel + 1; // no need to parse children
427  }
428  else if ( parseMode == QgsGmlSchema::feature )
429  {
430  // An element in feature should be ordinary or geometry attribute
431  //QgsDebugMsg( "is attribute");
432 
433  // Usually localName is attribute name, e.g.
434  // <gml:desc>My description</gml:desc>
435  // but QGIS server (2.2) is using:
436  // <Attribute value="My description" name="desc"/>
437  QString name = readAttribute( QStringLiteral( "name" ), attr );
438  //QgsDebugMsg ( "attribute name = " + name );
439  if ( localName.compare( QLatin1String( "attribute" ), Qt::CaseInsensitive ) == 0
440  && !name.isEmpty() )
441  {
442  QString value = readAttribute( QStringLiteral( "value" ), attr );
443  //QgsDebugMsg ( "attribute value = " + value );
444  addAttribute( name, value );
445  }
446  else
447  {
448  mAttributeName = localName;
449  mParseModeStack.push( QgsGmlSchema::attribute );
450  mStringCash.clear();
451  }
452  }
453 }
454 
455 void QgsGmlSchema::endElement( const XML_Char* el )
456 {
457  QString elementName = QString::fromUtf8( el );
458  QgsDebugMsgLevel( QString( "<- %1 %2" ).arg( mLevel ).arg( elementName ), 5 );
459 
460  if ( mLevel >= mSkipLevel )
461  {
462  //QgsDebugMsg( QString("skip level %1").arg( mLevel ) );
463  mLevel--;
464  return;
465  }
466  else
467  {
468  // clear possible skip level
469  mSkipLevel = std::numeric_limits<int>::max();
470  }
471 
472  QStringList splitName = elementName.split( NS_SEPARATOR );
473  QString localName = splitName.last();
474  QString ns = splitName.size() > 1 ? splitName.first() : QLatin1String( "" );
475 
476  QgsGmlSchema::ParseMode parseMode = modeStackTop();
477 
478  if ( parseMode == QgsGmlSchema::featureMembers )
479  {
480  modeStackPop();
481  }
482  else if ( parseMode == QgsGmlSchema::attribute && localName == mAttributeName )
483  {
484  // End of attribute
485  //QgsDebugMsg("end attribute");
486  modeStackPop(); // go up to feature
487 
488  if ( mFeatureClassMap[mCurrentFeatureName].geometryAttributes().count( mAttributeName ) == 0 )
489  {
490  addAttribute( mAttributeName, mStringCash );
491  }
492  }
493  else if ( ns == GML_NAMESPACE && localName == QLatin1String( "boundedBy" ) )
494  {
495  // was skipped
496  }
497  else if ( localName.endsWith( QLatin1String( "member" ), Qt::CaseInsensitive ) )
498  {
499  modeStackPop();
500  }
501  mParsePathStack.removeLast();
502  mLevel--;
503 }
504 
505 void QgsGmlSchema::characters( const XML_Char* chars, int len )
506 {
507  //QgsDebugMsg( QString("level %1 : %2").arg( mLevel ).arg( QString::fromUtf8( chars, len ) ) );
508  if ( mLevel >= mSkipLevel )
509  {
510  //QgsDebugMsg( QString("skip level %1").arg( mLevel ) );
511  return;
512  }
513 
514  //save chars in mStringCash attribute mode for value type analysis
515  if ( modeStackTop() == QgsGmlSchema::attribute )
516  {
517  mStringCash.append( QString::fromUtf8( chars, len ) );
518  }
519 }
520 
521 void QgsGmlSchema::addAttribute( const QString& name, const QString& value )
522 {
523  // It is not geometry attribute -> analyze value
524  bool ok;
525  value.toInt( &ok );
526  QVariant::Type type = QVariant::String;
527  if ( ok )
528  {
529  type = QVariant::Int;
530  }
531  else
532  {
533  value.toDouble( &ok );
534  if ( ok )
535  {
536  type = QVariant::Double;
537  }
538  }
539  //QgsDebugMsg( "mStringCash = " + mStringCash + " type = " + QVariant::typeToName( type ) );
540  //QMap<QString, QgsField> & fields = mFeatureClassMap[mCurrentFeatureName].fields();
541  QList<QgsField> & fields = mFeatureClassMap[mCurrentFeatureName].fields();
542  int fieldIndex = mFeatureClassMap[mCurrentFeatureName].fieldIndex( name );
543  if ( fieldIndex == -1 )
544  {
545  QgsField field( name, type );
546  fields.append( field );
547  }
548  else
549  {
550  QgsField &field = fields[fieldIndex];
551  // check if type is sufficient
552  if (( field.type() == QVariant::Int && ( type == QVariant::String || type == QVariant::Double ) ) ||
553  ( field.type() == QVariant::Double && type == QVariant::String ) )
554  {
555  field.setType( type );
556  }
557  }
558 }
559 
560 QStringList QgsGmlSchema::typeNames() const
561 {
562  return mFeatureClassMap.keys();
563 }
564 
565 QList<QgsField> QgsGmlSchema::fields( const QString & typeName )
566 {
567  if ( mFeatureClassMap.count( typeName ) == 0 ) return QList<QgsField>();
568  return mFeatureClassMap[typeName].fields();
569 }
570 
571 QStringList QgsGmlSchema::geometryAttributes( const QString & typeName )
572 {
573  if ( mFeatureClassMap.count( typeName ) == 0 ) return QStringList();
574  return mFeatureClassMap[typeName].geometryAttributes();
575 }
bool guessSchema(const QByteArray &data)
Guess GML schema from data if XSD does not exist.
QString path() const
Definition: qgsgmlschema.h:48
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
const QString GML_NAMESPACE
QList< QgsField > & fields()
Definition: qgsgmlschema.h:44
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:34
QList< QgsField > fields(const QString &typeName)
Get fields for type/class name parsed from GML or XSD.
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:47
double ANALYSIS_EXPORT max(double x, double y)
Returns the maximum of two doubles or the first argument if both are equal.
Definition: MathUtils.cc:437
Description of feature class in GML.
Definition: qgsgmlschema.h:38
void setType(QVariant::Type type)
Set variant type.
Definition: qgsfield.cpp:144
QgsError is container for error messages (report).
Definition: qgserror.h:80
void append(const QString &theMessage, const QString &theTag)
Append new error message.
Definition: qgserror.cpp:40
const char NS_SEPARATOR
QStringList & geometryAttributes()
Definition: qgsgmlschema.h:50
QVariant::Type type() const
Gets variant type of the field as it will be retrieved from data source.
Definition: qgsfield.cpp:98
QStringList typeNames() const
Get list of dot separated paths to feature classes parsed from GML or XSD.
int fieldIndex(const QString &name)
bool parseXSD(const QByteArray &xml)
Get fields info from XSD.
QStringList geometryAttributes(const QString &typeName)
Get list of geometry attributes for type/class name.