QGIS API Documentation  2.18.21-Las Palmas (9fba24a)
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 = "http://www.opengis.net/gml";
35 
37 {
38 }
39 
41  : mName( name )
42  , mPath( path )
43 {
44 }
45 
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 << "Point" << "MultiPoint"
64  << "LineString" << "MultiLineString"
65  << "Polygon" << "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 
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, "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( "name" );
108  QString type = elementElement.attribute( "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 == "AbstractFeatureType" )
118  {
119  // Get feature type definition
120  QgsGmlFeatureClass featureClass( name, "" );
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, "complexType", "name", typeName );
134  if ( complexTypeElement.isNull() ) return false;
135 
136  // extension or restriction
137  QDomElement extrest = domElement( complexTypeElement, "complexContent.extension" );
138  if ( extrest.isNull() )
139  {
140  extrest = domElement( complexTypeElement, "complexContent.restriction" );
141  }
142  if ( extrest.isNull() ) return false;
143 
144  QString extrestName = extrest.attribute( "base" );
145  if ( extrestName == "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 << "location" << "centerOf" << "position" << "extentOf"
166  << "coverage" << "edgeOf" << "centerLineOf" << "multiLocation"
167  << "multiCenterOf" << "multiPosition" << "multiCenterLineOf"
168  << "multiEdgeOf" << "multiCoverage" << "multiExtentOf";
169 
170  // Add attributes from current comple type
171  QList<QDomElement> sequenceElements = domElements( extrest, "sequence.element" );
172  Q_FOREACH ( const QDomElement& sequenceElement, sequenceElements )
173  {
174  QString fieldName = sequenceElement.attribute( "name" );
175  QString fieldTypeName = stripNS( sequenceElement.attribute( "type" ) );
176  QString ref = sequenceElement.attribute( "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( "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, "simpleType.restriction" );
211  fieldTypeName = stripNS( sequenceElementRestriction.attribute( "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 == "decimal" )
229  {
230  fieldType = QVariant::Double;
231  }
232  else if ( fieldTypeName == "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, "complexType", "name", name );
249  if ( complexTypeElement.isNull() ) return "";
250 
251  QDomElement extrest = domElement( complexTypeElement, "complexContent.extension" );
252  if ( extrest.isNull() )
253  {
254  extrest = domElement( complexTypeElement, "complexContent.restriction" );
255  }
256  if ( extrest.isNull() ) return "";
257 
258  QString extrestName = extrest.attribute( "base" );
259  if ( extrestName.startsWith( "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( "." ) ) );
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 
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, "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( "." );
369 
370  QStringList splitName = elementName.split( NS_SEPARATOR );
371  QString localName = splitName.last();
372  QString ns = splitName.size() > 1 ? splitName.first() : "";
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 == "boundedBy" )
379  {
380  // gml:boundedBy in feature or feature collection -> skip
381  mSkipLevel = mLevel + 1;
382  }
383  else if ( localName.compare( "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( "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( "_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( "_feature" )
406  || parseMode == QgsGmlSchema::featureMember
407  || parseMode == QgsGmlSchema::featureMembers
408  || localName.compare( "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( "name", attr );
438  //QgsDebugMsg ( "attribute name = " + name );
439  if ( localName.compare( "attribute", Qt::CaseInsensitive ) == 0
440  && !name.isEmpty() )
441  {
442  QString value = readAttribute( "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() : "";
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 == "boundedBy" )
494  {
495  // was skipped
496  }
497  else if ( localName.endsWith( "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 
561 {
562  return mFeatureClassMap.keys();
563 }
564 
566 {
567  if ( mFeatureClassMap.count( typeName ) == 0 ) return QList<QgsField>();
568  return mFeatureClassMap[typeName].fields();
569 }
570 
572 {
573  if ( mFeatureClassMap.count( typeName ) == 0 ) return QStringList();
574  return mFeatureClassMap[typeName].geometryAttributes();
575 }
QString & append(QChar ch)
bool guessSchema(const QByteArray &data)
Guess GML schema from data if XSD does not exist.
QString attribute(const QString &name, const QString &defValue) const
QString path() const
Definition: qgsgmlschema.h:51
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
QStringList split(const QString &sep, SplitBehavior behavior, Qt::CaseSensitivity cs) const
void push(const T &t)
void removeFirst()
bool contains(const QString &str, Qt::CaseSensitivity cs) const
QDomElement documentElement() const
QString join(const QString &separator) const
double toDouble(bool *ok) const
QString tr(const char *sourceText, const char *disambiguation, int n)
int size() const
QDomNode nextSibling() const
T value(int i) const
const QString GML_NAMESPACE
void clear()
QDomElement toElement() const
double ANALYSIS_EXPORT max(double x, double y)
Returns the maximum of two doubles or the first argument if both are equal.
QList< Key > keys() const
QList< QgsField > & fields()
Definition: qgsgmlschema.h:47
const char * name() const
int count(const T &value) const
void append(const T &value)
QString fromUtf8(const char *str, int size)
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:34
int toInt(bool *ok, int base) const
bool isEmpty() const
bool isEmpty() const
const char * constData() const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const
QList< QgsField > fields(const QString &typeName)
Get fields for type/class name parsed from GML or XSD.
T & first()
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:44
bool contains(QChar ch, Qt::CaseSensitivity cs) const
Description of feature class in GML.
Definition: qgsgmlschema.h:41
bool isNull() const
void setType(QVariant::Type type)
Set variant type.
Definition: qgsfield.cpp:138
QDomNode firstChild() const
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
T & last()
void removeLast()
QString section(QChar sep, int start, int end, QFlags< QString::SectionFlag > flags) const
int indexOf(const QRegExp &rx, int from) const
iterator insert(const Key &key, const T &value)
const char NS_SEPARATOR
QString tagName() const
QStringList & geometryAttributes()
Definition: qgsgmlschema.h:53
int size() const
QVariant::Type type() const
Gets variant type of the field as it will be retrieved from data source.
Definition: qgsfield.cpp:97
int compare(const QString &other) const
int count(const Key &key) const
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.
bool setContent(const QByteArray &data, bool namespaceProcessing, QString *errorMsg, int *errorLine, int *errorColumn)
QStringList geometryAttributes(const QString &typeName)
Get list of geometry attributes for type/class name.