QGIS API Documentation  2.2.0-Valmiera
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
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 
40 QgsGmlFeatureClass::QgsGmlFeatureClass( QString name, QString path )
41  : mName( name )
42  , mPath( path )
43 {
44 }
45 
47 {
48 }
49 
50 int QgsGmlFeatureClass::fieldIndex( const QString & name )
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 {
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] != NULL )
77  {
78  if ( attributeName.compare( attr[i] ) == 0 )
79  {
80  return QString( attr[i+1] );
81  }
82  ++i;
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, "element" );
102 
103  //QgsDebugMsg( QString( "%1 elemets read" ).arg( elementElements.size() ) );
104 
105  foreach ( 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  foreach ( 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  foreach ( 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 ).arg( 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 ).arg( 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.size() == 0 ) 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.size() == 0 )
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  foreach ( 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  QgsDebugMsg( "Entered" );
334  mLevel = 0;
336  XML_Parser p = XML_ParserCreateNS( NULL, NS_SEPARATOR );
337  XML_SetUserData( p, this );
338  XML_SetElementHandler( p, QgsGmlSchema::start, QgsGmlSchema::end );
339  XML_SetCharacterDataHandler( p, QgsGmlSchema::chars );
340  int atEnd = 1;
341  int res = XML_Parse( p, data.constData(), data.size(), atEnd );
342 
343  if ( res == 0 )
344  {
345  QString err = QString( XML_ErrorString( XML_GetErrorCode( p ) ) );
346  QgsDebugMsg( QString( "XML_Parse returned %1 error %2" ).arg( res ).arg( err ) );
347  mError = QgsError( err, "GML schema" );
348  mError.append( tr( "Cannot guess schema" ) );
349  }
350 
351  return res != 0;
352 }
353 
354 void QgsGmlSchema::startElement( const XML_Char* el, const XML_Char** attr )
355 {
356  Q_UNUSED( attr );
357  mLevel++;
358 
359  QString elementName( el );
360  QgsDebugMsgLevel( QString( "-> %1 %2 %3" ).arg( mLevel ).arg( elementName ).arg( mLevel >= mSkipLevel ? "skip" : "" ), 5 );
361 
362  if ( mLevel >= mSkipLevel )
363  {
364  //QgsDebugMsg( QString("skip level %1").arg( mLevel ) );
365  return;
366  }
367 
368  mParsePathStack.append( elementName );
369  QString path = mParsePathStack.join( "." );
370 
371  QStringList splitName = elementName.split( NS_SEPARATOR );
372  QString localName = splitName.last();
373  QString ns = splitName.size() > 1 ? splitName.first() : "";
374  //QgsDebugMsg( "ns = " + ns + " localName = " + localName );
375 
376  ParseMode parseMode = modeStackTop();
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  {
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  else if ( localName.endsWith( "member", Qt::CaseInsensitive ) )
392  {
394  }
395  // UMN Mapserver simple GetFeatureInfo response layer element (ends with _layer)
396  else if ( elementName.endsWith( "_layer" ) )
397  {
398  // do nothing, we catch _feature children
399  }
400  // UMN Mapserver simple GetFeatureInfo response feature element (ends with _feature)
401  // or featureMember children
402  else if ( elementName.endsWith( "_feature" )
403  || parseMode == QgsGmlSchema::featureMember
404  || parseMode == QgsGmlSchema::featureMembers )
405  {
406  //QgsDebugMsg ( "is feature path = " + path );
407  if ( mFeatureClassMap.count( localName ) == 0 )
408  {
409  mFeatureClassMap.insert( localName, QgsGmlFeatureClass( localName, path ) );
410  }
411  mCurrentFeatureName = localName;
413  }
414  else if ( parseMode == QgsGmlSchema::attribute && ns == GML_NAMESPACE && mGeometryTypes.indexOf( localName ) >= 0 )
415  {
416  // Geometry (Point,MultiPoint,...) in geometry attribute
417  QStringList &geometryAttributes = mFeatureClassMap[mCurrentFeatureName].geometryAttributes();
418  if ( geometryAttributes.count( mAttributeName ) == 0 )
419  {
420  geometryAttributes.append( mAttributeName );
421  }
422  mSkipLevel = mLevel + 1; // no need to parse children
423  }
424  else if ( parseMode == QgsGmlSchema::feature )
425  {
426  // An element in feature should be ordinary or geometry attribute
427  //QgsDebugMsg( "is attribute");
429  mAttributeName = localName;
430  mStringCash.clear();
431  }
432 }
433 
434 void QgsGmlSchema::endElement( const XML_Char* el )
435 {
436  QString elementName( el );
437  QgsDebugMsgLevel( QString( "<- %1 %2" ).arg( mLevel ).arg( elementName ), 5 );
438 
439  if ( mLevel >= mSkipLevel )
440  {
441  //QgsDebugMsg( QString("skip level %1").arg( mLevel ) );
442  mLevel--;
443  return;
444  }
445  else
446  {
447  // clear possible skip level
449  }
450 
451  QStringList splitName = elementName.split( NS_SEPARATOR );
452  QString localName = splitName.last();
453  QString ns = splitName.size() > 1 ? splitName.first() : "";
454 
456 
457  if ( parseMode == QgsGmlSchema::featureMembers )
458  {
459  modeStackPop();
460  }
461  else if ( parseMode == QgsGmlSchema::attribute && localName == mAttributeName )
462  {
463  // End of attribute
464  //QgsDebugMsg("end attribute");
465  modeStackPop(); // go up to feature
466 
468  {
469  // It is not geometry attribute -> analyze value
470  bool ok;
471  mStringCash.toInt( &ok );
472  QVariant::Type type = QVariant::String;
473  if ( ok )
474  {
475  type = QVariant::Int;
476  }
477  else
478  {
479  mStringCash.toDouble( &ok );
480  if ( ok )
481  {
482  type = QVariant::Double;
483  }
484  }
485  //QgsDebugMsg( "mStringCash = " + mStringCash + " type = " + QVariant::typeToName( type ) );
486  //QMap<QString, QgsField> & fields = mFeatureClassMap[mCurrentFeatureName].fields();
487  QList<QgsField> & fields = mFeatureClassMap[mCurrentFeatureName].fields();
488  int fieldIndex = mFeatureClassMap[mCurrentFeatureName].fieldIndex( mAttributeName );
489  if ( fieldIndex == -1 )
490  {
491  QgsField field( mAttributeName, type );
492  fields.append( field );
493  }
494  else
495  {
496  QgsField &field = fields[fieldIndex];
497  // check if type is sufficient
498  if (( field.type() == QVariant::Int && ( type == QVariant::String || type == QVariant::Double ) ) ||
499  ( field.type() == QVariant::Double && type == QVariant::String ) )
500  {
501  field.setType( type );
502  }
503  }
504  }
505  }
506  else if ( ns == GML_NAMESPACE && localName == "boundedBy" )
507  {
508  // was skipped
509  }
510  else if ( localName.endsWith( "member", Qt::CaseInsensitive ) )
511  {
512  modeStackPop();
513  }
514  mParsePathStack.removeLast();
515  mLevel--;
516 }
517 
518 void QgsGmlSchema::characters( const XML_Char* chars, int len )
519 {
520  //QgsDebugMsg( QString("level %1 : %2").arg( mLevel ).arg( QString::fromUtf8( chars, len ) ) );
521  if ( mLevel >= mSkipLevel )
522  {
523  //QgsDebugMsg( QString("skip level %1").arg( mLevel ) );
524  return;
525  }
526 
527  //save chars in mStringCash attribute mode for value type analysis
529  {
530  mStringCash.append( QString::fromUtf8( chars, len ) );
531  }
532 }
533 
534 QStringList QgsGmlSchema::typeNames() const
535 {
536  return mFeatureClassMap.keys();
537 }
538 
539 QList<QgsField> QgsGmlSchema::fields( const QString & typeName )
540 {
541  if ( mFeatureClassMap.count( typeName ) == 0 ) return QList<QgsField>();
542  return mFeatureClassMap[typeName].fields();
543 }
544 
545 QStringList QgsGmlSchema::geometryAttributes( const QString & typeName )
546 {
547  if ( mFeatureClassMap.count( typeName ) == 0 ) return QStringList();
548  return mFeatureClassMap[typeName].geometryAttributes();
549 }