QGIS API Documentation 3.99.0-Master (2fe06baccd8)
Loading...
Searching...
No Matches
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
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
17#include <limits>
18
20#include "qgserror.h"
21#include "qgsgeometry.h"
22#include "qgslogger.h"
24#include "qgsrectangle.h"
25
26#include <QBuffer>
27#include <QList>
28#include <QNetworkReply>
29#include <QNetworkRequest>
30#include <QProgressDialog>
31#include <QSet>
32#include <QSettings>
33#include <QUrl>
34
35#include "moc_qgsgmlschema.cpp"
36
37#ifndef NS_SEPARATOR_DEFINED
38#define NS_SEPARATOR_DEFINED
39static const char NS_SEPARATOR = '?';
40#endif
41
42#define GML_NAMESPACE QStringLiteral( "http://www.opengis.net/gml" )
43
44
45QgsGmlFeatureClass::QgsGmlFeatureClass( const QString &name, const QString &path )
46 : mName( name )
47 , mPath( path )
48{
49}
50
51int QgsGmlFeatureClass::fieldIndex( const QString &name )
52{
53 for ( int i = 0; i < mFields.size(); i++ )
54 {
55 if ( mFields[i].name() == name ) return i;
56 }
57 return -1;
58}
59
60// --------------------------- QgsGmlSchema -------------------------------
62 : mSkipLevel( std::numeric_limits<int>::max() )
63{
64 mGeometryTypes << QStringLiteral( "Point" ) << QStringLiteral( "MultiPoint" )
65 << QStringLiteral( "LineString" ) << QStringLiteral( "MultiLineString" )
66 << QStringLiteral( "Polygon" ) << QStringLiteral( "MultiPolygon" );
67}
68
69QString QgsGmlSchema::readAttribute( const QString &attributeName, const XML_Char **attr ) const
70{
71 int i = 0;
72 while ( attr[i] )
73 {
74 if ( attributeName.compare( attr[i] ) == 0 )
75 {
76 return QString( attr[i + 1] );
77 }
78 i += 2;
79 }
80 return QString();
81}
82
83bool QgsGmlSchema::parseXSD( const QByteArray &xml )
84{
85 QDomDocument dom;
86 QString errorMsg;
87 int errorLine;
88 int errorColumn;
89 if ( !dom.setContent( xml, false, &errorMsg, &errorLine, &errorColumn ) )
90 {
91 // TODO: error
92 return false;
93 }
94
95 const QDomElement docElem = dom.documentElement();
96
97 const QList<QDomElement> elementElements = domElements( docElem, QStringLiteral( "element" ) );
98
99 //QgsDebugMsgLevel( QStringLiteral( "%1 elements read" ).arg( elementElements.size() ), 2);
100
101 const auto constElementElements = elementElements;
102 for ( const QDomElement &elementElement : constElementElements )
103 {
104 const QString name = elementElement.attribute( QStringLiteral( "name" ) );
105 const QString type = elementElement.attribute( QStringLiteral( "type" ) );
106
107 const QString gmlBaseType = xsdComplexTypeGmlBaseType( docElem, stripNS( type ) );
108 //QgsDebugMsgLevel( QStringLiteral( "gmlBaseType = %1" ).arg( gmlBaseType ), 2 );
109 //QgsDebugMsgLevel( QStringLiteral( "name = %1 gmlBaseType = %2" ).arg( name ).arg( gmlBaseType ), 2 );
110 // We should only use gml:AbstractFeatureType descendants which have
111 // ancestor listed in gml:FeatureAssociationType (featureMember) descendant
112 // But we could only loose some data if XSD was not correct, I think.
113
114 if ( gmlBaseType == QLatin1String( "AbstractFeatureType" ) )
115 {
116 // Get feature type definition
117 QgsGmlFeatureClass featureClass( name, QString() );
118 xsdFeatureClass( docElem, stripNS( type ), featureClass );
119 mFeatureClassMap.insert( name, featureClass );
120 }
121 // A feature may have more geometries, we take just the first one
122 }
123
124 return true;
125}
126
127bool QgsGmlSchema::xsdFeatureClass( const QDomElement &element, const QString &typeName, QgsGmlFeatureClass &featureClass )
128{
129 //QgsDebugMsgLevel("typeName = " + typeName, 2 );
130 const QDomElement complexTypeElement = domElement( element, QStringLiteral( "complexType" ), QStringLiteral( "name" ), typeName );
131 if ( complexTypeElement.isNull() ) return false;
132
133 // extension or restriction
134 QDomElement extrest = domElement( complexTypeElement, QStringLiteral( "complexContent.extension" ) );
135 if ( extrest.isNull() )
136 {
137 extrest = domElement( complexTypeElement, QStringLiteral( "complexContent.restriction" ) );
138 }
139 if ( extrest.isNull() ) return false;
140
141 const QString extrestName = extrest.attribute( QStringLiteral( "base" ) );
142 if ( extrestName == QLatin1String( "gml:AbstractFeatureType" ) )
143 {
144 // In theory we should add gml:AbstractFeatureType default attributes gml:description
145 // and gml:name but it does not seem to be a common practice and we would probably
146 // confuse most users
147 }
148 else
149 {
150 // Get attributes from extrest
151 if ( !xsdFeatureClass( element, stripNS( extrestName ), featureClass ) ) return false;
152 }
153
154 // Supported geometry types
155 QStringList geometryPropertyTypes;
156 const auto constMGeometryTypes = mGeometryTypes;
157 for ( const QString &geom : constMGeometryTypes )
158 {
159 geometryPropertyTypes << geom + "PropertyType";
160 }
161
162 QStringList geometryAliases;
163 geometryAliases << QStringLiteral( "location" ) << QStringLiteral( "centerOf" ) << QStringLiteral( "position" ) << QStringLiteral( "extentOf" )
164 << QStringLiteral( "coverage" ) << QStringLiteral( "edgeOf" ) << QStringLiteral( "centerLineOf" ) << QStringLiteral( "multiLocation" )
165 << QStringLiteral( "multiCenterOf" ) << QStringLiteral( "multiPosition" ) << QStringLiteral( "multiCenterLineOf" )
166 << QStringLiteral( "multiEdgeOf" ) << QStringLiteral( "multiCoverage" ) << QStringLiteral( "multiExtentOf" );
167
168 // Add attributes from current comple type
169 const QList<QDomElement> sequenceElements = domElements( extrest, QStringLiteral( "sequence.element" ) );
170 const auto constSequenceElements = sequenceElements;
171 for ( const QDomElement &sequenceElement : constSequenceElements )
172 {
173 const QString fieldName = sequenceElement.attribute( QStringLiteral( "name" ) );
174 QString fieldTypeName = stripNS( sequenceElement.attribute( QStringLiteral( "type" ) ) );
175 const QString ref = sequenceElement.attribute( QStringLiteral( "ref" ) );
176 //QgsDebugMsg ( QString("fieldName = %1 fieldTypeName = %2 ref = %3").arg(fieldName).arg(fieldTypeName).arg(ref) );
177
178 if ( !ref.isEmpty() )
179 {
180 if ( ref.startsWith( QLatin1String( "gml:" ) ) )
181 {
182 if ( geometryAliases.contains( stripNS( ref ) ) )
183 {
184 featureClass.geometryAttributes().append( stripNS( ref ) );
185 }
186 else
187 {
188 QgsDebugError( QStringLiteral( "Unknown referenced GML element: %1" ).arg( ref ) );
189 }
190 }
191 else
192 {
193 // TODO: get type from referenced element
194 QgsDebugError( QStringLiteral( "field %1.%2 is referencing %3 - not supported" ).arg( typeName, fieldName ) );
195 }
196 continue;
197 }
198
199 if ( fieldName.isEmpty() )
200 {
201 QgsDebugError( QStringLiteral( "field in %1 without name" ).arg( typeName ) );
202 continue;
203 }
204
205 // type is either type attribute
206 if ( fieldTypeName.isEmpty() )
207 {
208 // or type is inheriting from xs:simpleType
209 const QDomElement sequenceElementRestriction = domElement( sequenceElement, QStringLiteral( "simpleType.restriction" ) );
210 fieldTypeName = stripNS( sequenceElementRestriction.attribute( QStringLiteral( "base" ) ) );
211 }
212
213 QMetaType::Type fieldType = QMetaType::Type::QString;
214 if ( fieldTypeName.isEmpty() )
215 {
216 QgsDebugError( QStringLiteral( "Cannot get %1.%2 field type" ).arg( typeName, fieldName ) );
217 }
218 else
219 {
220 if ( geometryPropertyTypes.contains( fieldTypeName ) )
221 {
222 // Geometry attribute
223 featureClass.geometryAttributes().append( fieldName );
224 continue;
225 }
226
227 if ( fieldTypeName == QLatin1String( "decimal" ) )
228 {
229 fieldType = QMetaType::Type::Double;
230 }
231 else if ( fieldTypeName == QLatin1String( "integer" ) )
232 {
233 fieldType = QMetaType::Type::Int;
234 }
235 }
236
237 const QgsField field( fieldName, fieldType, fieldTypeName );
238 featureClass.fields().append( field );
239 }
240
241 return true;
242}
243
244QString QgsGmlSchema::xsdComplexTypeGmlBaseType( const QDomElement &element, const QString &name )
245{
246 //QgsDebugMsgLevel("name = " + name, 2 );
247 const QDomElement complexTypeElement = domElement( element, QStringLiteral( "complexType" ), QStringLiteral( "name" ), name );
248 if ( complexTypeElement.isNull() ) return QString();
249
250 QDomElement extrest = domElement( complexTypeElement, QStringLiteral( "complexContent.extension" ) );
251 if ( extrest.isNull() )
252 {
253 extrest = domElement( complexTypeElement, QStringLiteral( "complexContent.restriction" ) );
254 }
255 if ( extrest.isNull() ) return QString();
256
257 const QString extrestName = extrest.attribute( QStringLiteral( "base" ) );
258 if ( extrestName.startsWith( QLatin1String( "gml:" ) ) )
259 {
260 // GML base type found
261 return stripNS( extrestName );
262 }
263 // Continue recursively until GML base type is reached
264 return xsdComplexTypeGmlBaseType( element, stripNS( extrestName ) );
265}
266
267QString QgsGmlSchema::stripNS( const QString &name )
268{
269 return name.contains( ':' ) ? name.section( ':', 1 ) : name;
270}
271
272QList<QDomElement> QgsGmlSchema::domElements( const QDomElement &element, const QString &path )
273{
274 QList<QDomElement> list;
275
276 QStringList names = path.split( '.' );
277 if ( names.isEmpty() ) return list;
278 const QString name = names.value( 0 );
279 names.removeFirst();
280
281 QDomNode n1 = element.firstChild();
282 while ( !n1.isNull() )
283 {
284 const QDomElement el = n1.toElement();
285 if ( !el.isNull() )
286 {
287 const QString tagName = stripNS( el.tagName() );
288 if ( tagName == name )
289 {
290 if ( names.isEmpty() )
291 {
292 list.append( el );
293 }
294 else
295 {
296 list.append( domElements( el, names.join( QLatin1Char( '.' ) ) ) );
297 }
298 }
299 }
300 n1 = n1.nextSibling();
301 }
302
303 return list;
304}
305
306QDomElement QgsGmlSchema::domElement( const QDomElement &element, const QString &path )
307{
308 return domElements( element, path ).value( 0 );
309}
310
311QList<QDomElement> QgsGmlSchema::domElements( QList<QDomElement> &elements, const QString &attr, const QString &attrVal )
312{
313 QList<QDomElement> list;
314 const auto constElements = elements;
315 for ( const QDomElement &el : constElements )
316 {
317 if ( el.attribute( attr ) == attrVal )
318 {
319 list << el;
320 }
321 }
322 return list;
323}
324
325QDomElement 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
331bool 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 const int atEnd = 1;
340 const int res = XML_Parse( p, data.constData(), data.size(), atEnd );
341
342 if ( res == 0 )
343 {
344 const QString err = QString( XML_ErrorString( XML_GetErrorCode( p ) ) );
345 QgsDebugError( QStringLiteral( "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
353void QgsGmlSchema::startElement( const XML_Char *el, const XML_Char **attr )
354{
355 Q_UNUSED( attr )
356 mLevel++;
357
358 const QString elementName = QString::fromUtf8( el );
359 QgsDebugMsgLevel( QStringLiteral( "-> %1 %2 %3" ).arg( mLevel ).arg( elementName, mLevel >= mSkipLevel ? "skip" : "" ), 5 );
360
361 if ( mLevel >= mSkipLevel )
362 {
363 //QgsDebugMsgLevel( QStringLiteral("skip level %1").arg( mLevel ), 2 );
364 return;
365 }
366
367 mParsePathStack.append( elementName );
368 const QString path = mParsePathStack.join( QLatin1Char( '.' ) );
369
370 QStringList splitName = elementName.split( NS_SEPARATOR );
371 const QString localName = splitName.last();
372 const QString ns = splitName.size() > 1 ? splitName.first() : QString();
373 //QgsDebugMsgLevel( "ns = " + ns + " localName = " + localName, 2 );
374
375 const ParseMode parseMode = modeStackTop();
376 //QgsDebugMsgLevel( QString("localName = %1 parseMode = %2").arg(localName).arg(parseMode), 2 );
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 QgsDebugMsgLevel( "is feature path = " + path, 2 );
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 //QgsDebugMsgLevel( "is attribute", 2);
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 const 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 const 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
455void QgsGmlSchema::endElement( const XML_Char *el )
456{
457 const QString elementName = QString::fromUtf8( el );
458 QgsDebugMsgLevel( QStringLiteral( "<- %1 %2" ).arg( mLevel ).arg( elementName ), 5 );
459
460 if ( mLevel >= mSkipLevel )
461 {
462 //QgsDebugMsgLevel( QStringLiteral("skip level %1").arg( mLevel ), 2 );
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 const QString localName = splitName.last();
474 const QString ns = splitName.size() > 1 ? splitName.first() : QString();
475
476 const 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 //QgsDebugMsgLevel("end attribute", 2);
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
505void QgsGmlSchema::characters( const XML_Char *chars, int len )
506{
507 //QgsDebugMsgLevel( QStringLiteral("level %1 : %2").arg( mLevel ).arg( QString::fromUtf8( chars, len ) ), 2 );
508 if ( mLevel >= mSkipLevel )
509 {
510 //QgsDebugMsgLevel( QStringLiteral("skip level %1").arg( mLevel ),2 );
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
521void QgsGmlSchema::addAttribute( const QString &name, const QString &value )
522{
523 // It is not geometry attribute -> analyze value
524 bool ok;
525 ( void ) value.toInt( &ok );
526 QMetaType::Type type = QMetaType::Type::QString;
527 if ( ok )
528 {
529 type = QMetaType::Type::Int;
530 }
531 else
532 {
533 ( void ) value.toDouble( &ok );
534 if ( ok )
535 {
536 type = QMetaType::Type::Double;
537 }
538 }
539 //QgsDebugMsgLevel( "mStringCash = " + mStringCash + " type = " + QVariant::typeToName( type ),2 );
540 //QMap<QString, QgsField> & fields = mFeatureClassMap[mCurrentFeatureName].fields();
541 QList<QgsField> &fields = mFeatureClassMap[mCurrentFeatureName].fields();
542 const int fieldIndex = mFeatureClassMap[mCurrentFeatureName].fieldIndex( name );
543 if ( fieldIndex == -1 )
544 {
545 const 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() == QMetaType::Type::Int && ( type == QMetaType::Type::QString || type == QMetaType::Type::Double ) ) ||
553 ( field.type() == QMetaType::Type::Double && type == QMetaType::Type::QString ) )
554 {
555 field.setType( type );
556 }
557 }
558}
559
560QStringList QgsGmlSchema::typeNames() const
561{
562 return mFeatureClassMap.keys();
563}
564
565QList<QgsField> QgsGmlSchema::fields( const QString &typeName )
566{
567 if ( mFeatureClassMap.count( typeName ) == 0 ) return QList<QgsField>();
568 return mFeatureClassMap[typeName].fields();
569}
570
571QStringList QgsGmlSchema::geometryAttributes( const QString &typeName )
572{
573 if ( mFeatureClassMap.count( typeName ) == 0 ) return QStringList();
574 return mFeatureClassMap[typeName].geometryAttributes();
575}
A container for error messages.
Definition qgserror.h:81
QMetaType::Type type
Definition qgsfield.h:61
void setType(QMetaType::Type type)
Set variant type.
Definition qgsfield.cpp:234
Description of feature class in GML.
int fieldIndex(const QString &name)
QString path() const
QStringList & geometryAttributes()
QList< QgsField > & fields()
QgsGmlFeatureClass()=default
bool parseXSD(const QByteArray &xml)
Gets fields info from XSD.
QList< QgsField > fields(const QString &typeName)
Gets fields for type/class name parsed from GML or XSD.
QStringList geometryAttributes(const QString &typeName)
Gets list of geometry attributes for type/class name.
bool guessSchema(const QByteArray &data)
Guess GML schema from data if XSD does not exist.
QStringList typeNames() const
Gets list of dot separated paths to feature classes parsed from GML or XSD.
#define GML_NAMESPACE
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:61
#define QgsDebugError(str)
Definition qgslogger.h:57