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