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