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