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