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