Skip to content

Commit 532cd10

Browse files
author
mhugent
committedJul 30, 2010
[FEATURE]: WFS-T support (experimental). Additionally ported wfs to network manager (code from Juergen, patch #2892)
git-svn-id: http://svn.osgeo.org/qgis/trunk@13985 c8812cc2-4d05-0410-92ff-de0c093fc19c
1 parent 695a700 commit 532cd10

File tree

6 files changed

+1208
-341
lines changed

6 files changed

+1208
-341
lines changed
 

‎src/plugins/wfs/qgswfssourceselect.cpp

Lines changed: 156 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,26 @@
1919
#include "qgswfssourceselect.h"
2020
#include "qgsnewhttpconnection.h"
2121
#include "qgsgenericprojectionselector.h"
22-
#include "qgshttptransaction.h"
2322
#include "qgscontexthelp.h"
2423
#include "qgsproject.h"
2524
#include "qgscoordinatereferencesystem.h"
2625
#include "qgslogger.h"
2726
#include "qgsmapcanvas.h" //for current view extent
27+
#include "qgsnetworkaccessmanager.h"
28+
2829
#include <QDomDocument>
2930
#include <QListWidgetItem>
3031
#include <QMessageBox>
3132
#include <QSettings>
33+
#include <QNetworkRequest>
34+
#include <QNetworkReply>
3235

3336
static const QString WFS_NAMESPACE = "http://www.opengis.net/wfs";
3437

35-
QgsWFSSourceSelect::QgsWFSSourceSelect( QWidget* parent, QgisInterface* iface ): QDialog( parent ), mIface( iface )
38+
QgsWFSSourceSelect::QgsWFSSourceSelect( QWidget* parent, QgisInterface* iface )
39+
: QDialog( parent )
40+
, mIface( iface )
41+
, mCapabilitiesReply( 0 )
3642
{
3743
setupUi( this );
3844
btnAdd = buttonBox->button( QDialogButtonBox::Ok );
@@ -128,127 +134,165 @@ QString QgsWFSSourceSelect::getPreferredCrs( const QSet<QString>& crsSet ) const
128134
return *( crsSet.constBegin() );
129135
}
130136

131-
int QgsWFSSourceSelect::getCapabilities( const QString& uri, QgsWFSSourceSelect::REQUEST_ENCODING e, std::list<QString>& typenames, std::list< std::list<QString> >& crs, std::list<QString>& titles, std::list<QString>& abstracts )
137+
void QgsWFSSourceSelect::capabilitiesReplyFinished()
132138
{
133-
switch ( e )
139+
if ( mCapabilitiesReply->error() == QNetworkReply::NoError )
134140
{
135-
case QgsWFSSourceSelect::GET:
136-
return getCapabilitiesGET( uri, typenames, crs, titles, abstracts );
137-
case QgsWFSSourceSelect::POST:
138-
return getCapabilitiesPOST( uri, typenames, crs, titles, abstracts );
139-
case QgsWFSSourceSelect::SOAP:
140-
return getCapabilitiesSOAP( uri, typenames, crs, titles, abstracts );
141-
}
142-
return 1;
143-
}
144-
145-
int QgsWFSSourceSelect::getCapabilitiesGET( QString uri, std::list<QString>& typenames, std::list< std::list<QString> >& crs, std::list<QString>& titles, std::list<QString>& abstracts )
146-
{
147-
QString request = uri + "SERVICE=WFS&REQUEST=GetCapabilities&VERSION=1.0.0";
141+
QVariant redirect = mCapabilitiesReply->attribute( QNetworkRequest::RedirectionTargetAttribute );
142+
if ( !redirect.isNull() )
143+
{
144+
QgsDebugMsg( "redirecting to " + redirect.toUrl().toString() );
145+
QNetworkRequest request( redirect.toUrl() );
146+
request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork );
147+
request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
148148

149-
QByteArray result;
150-
QgsHttpTransaction http( request );
151-
if ( !http.getSynchronously( result ) )
152-
{
153-
QMessageBox::critical( 0, tr( "Error" ),
154-
tr( "Could not download capabilities document: " ) + http.errorString() );
155-
return 1;
156-
}
149+
mCapabilitiesReply->deleteLater();
150+
mCapabilitiesReply = QgsNetworkAccessManager::instance()->get( request );
157151

158-
QDomDocument capabilitiesDocument;
159-
QString capabilitiesDocError;
160-
if ( !capabilitiesDocument.setContent( result, true, &capabilitiesDocError ) )
161-
{
162-
QMessageBox::critical( 0, tr( "Error" ),
163-
tr( "Capabilities document is not valid: " ) + capabilitiesDocError );
164-
return 1;
165-
}
152+
connect( mCapabilitiesReply, SIGNAL( finished() ), this, SLOT( capabilitiesReplyFinished() ) );
153+
connect( mCapabilitiesReply, SIGNAL( downloadProgress( qint64, qint64 ) ), this, SLOT( capabilitiesReplyProgress( qint64, qint64 ) ) );
154+
return;
155+
}
166156

167-
QDomElement doc = capabilitiesDocument.documentElement();
168-
if ( doc.tagName() == "ExceptionReport" )
169-
{
170-
QDomNode ex = doc.firstChild();
171-
QString exc = ex.toElement().attribute("exceptionCode", "Exception");
172-
QDomElement ext = ex.firstChild().toElement();
173-
QMessageBox::critical( 0, tr( "Error" ),
174-
exc + ": " + ext.firstChild().nodeValue() );
175-
return 1;
176-
}
157+
QByteArray buffer = mCapabilitiesReply->readAll();
177158

178-
//get the <FeatureType> elements
179-
QDomNodeList featureTypeList = capabilitiesDocument.elementsByTagNameNS( WFS_NAMESPACE, "FeatureType" );
180-
for ( unsigned int i = 0; i < featureTypeList.length(); ++i )
181-
{
182-
QString tname, title, abstract;
183-
QDomElement featureTypeElem = featureTypeList.at( i ).toElement();
184-
std::list<QString> featureCRSList; //CRS list for this feature
159+
QgsDebugMsg( "parsing capabilities: " + buffer );
185160

186-
//Name
187-
QDomNodeList nameList = featureTypeElem.elementsByTagNameNS( WFS_NAMESPACE, "Name" );
188-
if ( nameList.length() > 0 )
189-
{
190-
tname = nameList.at( 0 ).toElement().text();
191-
//strip away namespace prefixes
192-
/* if ( tname.contains( ":" ) )
193-
{
194-
tname = tname.section( ":", 1, 1 );
195-
}*/
196-
}
197-
//Title
198-
QDomNodeList titleList = featureTypeElem.elementsByTagNameNS( WFS_NAMESPACE, "Title" );
199-
if ( titleList.length() > 0 )
161+
QString capabilitiesDocError;
162+
QDomDocument capabilitiesDocument;
163+
if ( capabilitiesDocument.setContent( buffer, true, &capabilitiesDocError ) )
200164
{
201-
title = titleList.at( 0 ).toElement().text();
202-
}
203-
//Abstract
204-
QDomNodeList abstractList = featureTypeElem.elementsByTagNameNS( WFS_NAMESPACE, "Abstract" );
205-
if ( abstractList.length() > 0 )
206-
{
207-
abstract = abstractList.at( 0 ).toElement().text();
208-
}
165+
QDomElement doc = capabilitiesDocument.documentElement();
166+
if ( doc.tagName() != "ExceptionReport" )
167+
{
168+
std::list<QString> typenames;
169+
std::list< std::list<QString> > crs;
170+
std::list<QString> titles;
171+
std::list<QString> abstracts;
172+
173+
//get the <FeatureType> elements
174+
QDomNodeList featureTypeList = capabilitiesDocument.elementsByTagNameNS( WFS_NAMESPACE, "FeatureType" );
175+
for ( unsigned int i = 0; i < featureTypeList.length(); ++i )
176+
{
177+
QString tname, title, abstract;
178+
QDomElement featureTypeElem = featureTypeList.at( i ).toElement();
179+
std::list<QString> featureCRSList; //CRS list for this feature
180+
181+
//Name
182+
QDomNodeList nameList = featureTypeElem.elementsByTagNameNS( WFS_NAMESPACE, "Name" );
183+
if ( nameList.length() > 0 )
184+
{
185+
tname = nameList.at( 0 ).toElement().text();
186+
//strip away namespace prefixes
187+
/* if ( tname.contains( ":" ) )
188+
{
189+
tname = tname.section( ":", 1, 1 );
190+
}*/
191+
}
192+
//Title
193+
QDomNodeList titleList = featureTypeElem.elementsByTagNameNS( WFS_NAMESPACE, "Title" );
194+
if ( titleList.length() > 0 )
195+
{
196+
title = titleList.at( 0 ).toElement().text();
197+
}
198+
//Abstract
199+
QDomNodeList abstractList = featureTypeElem.elementsByTagNameNS( WFS_NAMESPACE, "Abstract" );
200+
if ( abstractList.length() > 0 )
201+
{
202+
abstract = abstractList.at( 0 ).toElement().text();
203+
}
204+
205+
//DefaultSRS is always the first entry in the feature srs list
206+
QDomNodeList defaultCRSList = featureTypeElem.elementsByTagNameNS( WFS_NAMESPACE, "DefaultSRS" );
207+
if ( defaultCRSList.length() > 0 )
208+
{
209+
featureCRSList.push_back( defaultCRSList.at( 0 ).toElement().text() );
210+
}
211+
212+
//OtherSRS
213+
QDomNodeList otherCRSList = featureTypeElem.elementsByTagNameNS( WFS_NAMESPACE, "OtherSRS" );
214+
for ( unsigned int i = 0; i < otherCRSList.length(); ++i )
215+
{
216+
featureCRSList.push_back( otherCRSList.at( i ).toElement().text() );
217+
}
218+
219+
//Support <SRS> for compatibility with older versions
220+
QDomNodeList srsList = featureTypeElem.elementsByTagNameNS( WFS_NAMESPACE, "SRS" );
221+
for ( unsigned int i = 0; i < srsList.length(); ++i )
222+
{
223+
featureCRSList.push_back( srsList.at( i ).toElement().text() );
224+
}
225+
226+
crs.push_back( featureCRSList );
227+
typenames.push_back( tname );
228+
titles.push_back( title );
229+
abstracts.push_back( abstract );
230+
}
209231

210-
//DefaultSRS is always the first entry in the feature srs list
211-
QDomNodeList defaultCRSList = featureTypeElem.elementsByTagNameNS( WFS_NAMESPACE, "DefaultSRS" );
212-
if ( defaultCRSList.length() > 0 )
213-
{
214-
featureCRSList.push_back( defaultCRSList.at( 0 ).toElement().text() );
215-
}
232+
//insert the available CRS into mAvailableCRS
233+
mAvailableCRS.clear();
234+
std::list<QString>::const_iterator typeNameIter;
235+
std::list< std::list<QString> >::const_iterator crsIter;
236+
for ( typeNameIter = typenames.begin(), crsIter = crs.begin(); typeNameIter != typenames.end(); ++typeNameIter, ++crsIter )
237+
{
238+
std::list<QString> currentCRSList;
239+
for ( std::list<QString>::const_iterator it = crsIter->begin(); it != crsIter->end(); ++it )
240+
{
241+
currentCRSList.push_back( *it );
242+
}
243+
mAvailableCRS.insert( std::make_pair( *typeNameIter, currentCRSList ) );
244+
}
216245

217-
//OtherSRS
218-
QDomNodeList otherCRSList = featureTypeElem.elementsByTagNameNS( WFS_NAMESPACE, "OtherSRS" );
219-
for ( unsigned int i = 0; i < otherCRSList.length(); ++i )
220-
{
221-
featureCRSList.push_back( otherCRSList.at( i ).toElement().text() );
222-
}
246+
//insert the typenames, titles and abstracts into the tree view
247+
std::list<QString>::const_iterator t_it = titles.begin();
248+
std::list<QString>::const_iterator n_it = typenames.begin();
249+
std::list<QString>::const_iterator a_it = abstracts.begin();
250+
for ( ; t_it != titles.end(); ++t_it, ++n_it, ++a_it )
251+
{
252+
QTreeWidgetItem* newItem = new QTreeWidgetItem();
253+
newItem->setText( 0, *t_it );
254+
newItem->setText( 1, *n_it );
255+
newItem->setText( 2, *a_it );
256+
treeWidget->addTopLevelItem( newItem );
257+
}
223258

224-
//Support <SRS> for compatibility with older versions
225-
QDomNodeList srsList = featureTypeElem.elementsByTagNameNS( WFS_NAMESPACE, "SRS" );
226-
for ( unsigned int i = 0; i < srsList.length(); ++i )
259+
if ( typenames.size() > 0 )
260+
{
261+
btnAdd->setEnabled( true );
262+
treeWidget->setCurrentItem( treeWidget->topLevelItem( 0 ) );
263+
btnChangeSpatialRefSys->setEnabled( true );
264+
}
265+
else
266+
{
267+
QMessageBox::information( 0, tr( "No Layers" ), tr( "capabilities document contained no layers." ) );
268+
btnAdd->setEnabled( false );
269+
}
270+
}
271+
else
272+
{
273+
QDomNode ex = doc.firstChild();
274+
QString exc = ex.toElement().attribute( "exceptionCode", "Exception" );
275+
QDomElement ext = ex.firstChild().toElement();
276+
QMessageBox::critical( 0, tr( "Error" ), exc + ": " + ext.firstChild().nodeValue() );
277+
}
278+
}
279+
else
227280
{
228-
featureCRSList.push_back( srsList.at( i ).toElement().text() );
281+
QMessageBox::critical( 0, tr( "Capabilities document is not valid" ), capabilitiesDocError );
229282
}
230-
231-
crs.push_back( featureCRSList );
232-
typenames.push_back( tname );
233-
titles.push_back( title );
234-
abstracts.push_back( abstract );
283+
}
284+
else
285+
{
286+
QMessageBox::critical( 0, tr( "GetCapabilities Error" ), mCapabilitiesReply->errorString() );
235287
}
236288

237-
238-
//print out result for a test
239-
QgsDebugMsg( result );
240-
241-
return 0;
242-
}
243-
244-
int QgsWFSSourceSelect::getCapabilitiesPOST( const QString& uri, std::list<QString>& typenames, std::list< std::list<QString> >& crs, std::list<QString>& titles, std::list<QString>& abstracts )
245-
{
246-
return 1; //soon...
289+
btnConnect->setEnabled( true );
290+
mCapabilitiesReply->deleteLater();
291+
mCapabilitiesReply = 0;
247292
}
248293

249-
int QgsWFSSourceSelect::getCapabilitiesSOAP( const QString& uri, std::list<QString>& typenames, std::list< std::list<QString> >& crs, std::list<QString>& titles, std::list<QString>& abstracts )
294+
void QgsWFSSourceSelect::capabilitiesReplyProgress( qint64, qint64 )
250295
{
251-
return 1; //soon...
252296
}
253297

254298
void QgsWFSSourceSelect::addEntryToServerList()
@@ -296,11 +340,6 @@ void QgsWFSSourceSelect::connectToServer()
296340
QgsDebugMsg( QString( "url is: %1" ).arg( mUri ) );
297341

298342
//make a GetCapabilities request
299-
std::list<QString> typenames;
300-
std::list< std::list<QString> > crsList;
301-
std::list<QString> titles;
302-
std::list<QString> abstracts;
303-
304343
//modify mUri to add '?' or '&' at the end if it is not already there
305344
if ( !( mUri.contains( "?" ) ) )
306345
{
@@ -311,51 +350,17 @@ void QgsWFSSourceSelect::connectToServer()
311350
mUri.append( "&" );
312351
}
313352

314-
if ( getCapabilities( mUri, QgsWFSSourceSelect::GET, typenames, crsList, titles, abstracts ) != 0 )
315-
{
316-
QgsDebugMsg( "error during GetCapabilities request" );
317-
}
318-
319-
//insert the available CRS into mAvailableCRS
320-
mAvailableCRS.clear();
321-
std::list<QString>::const_iterator typeNameIter;
322-
std::list< std::list<QString> >::const_iterator crsIter;
323-
for ( typeNameIter = typenames.begin(), crsIter = crsList.begin(); typeNameIter != typenames.end(); ++typeNameIter, ++crsIter )
324-
{
325-
std::list<QString> currentCRSList;
326-
for ( std::list<QString>::const_iterator it = crsIter->begin(); it != crsIter->end(); ++it )
327-
{
328-
currentCRSList.push_back( *it );
329-
}
330-
mAvailableCRS.insert( std::make_pair( *typeNameIter, currentCRSList ) );
331-
}
332-
333-
//insert the typenames, titles and abstracts into the tree view
353+
btnConnect->setEnabled( false );
334354
treeWidget->clear();
335-
std::list<QString>::const_iterator t_it = titles.begin();
336-
std::list<QString>::const_iterator n_it = typenames.begin();
337-
std::list<QString>::const_iterator a_it = abstracts.begin();
338-
for ( ; t_it != titles.end(); ++t_it, ++n_it, ++a_it )
339-
{
340-
QTreeWidgetItem* newItem = new QTreeWidgetItem();
341-
newItem->setText( 0, *t_it );
342-
newItem->setText( 1, *n_it );
343-
newItem->setText( 2, *a_it );
344-
treeWidget->addTopLevelItem( newItem );
345-
}
346355

347-
if ( typenames.size() > 0 )
348-
{
349-
btnAdd->setEnabled( true );
350-
treeWidget->setCurrentItem( treeWidget->topLevelItem( 0 ) );
351-
btnChangeSpatialRefSys->setEnabled( true );
352-
}
353-
else
354-
{
355-
btnAdd->setEnabled( false );
356-
}
356+
QNetworkRequest request( mUri + "SERVICE=WFS&REQUEST=GetCapabilities&VERSION=1.0.0" );
357+
request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
358+
mCapabilitiesReply = QgsNetworkAccessManager::instance()->get( request );
359+
connect( mCapabilitiesReply, SIGNAL( finished() ), this, SLOT( capabilitiesReplyFinished() ) );
360+
connect( mCapabilitiesReply, SIGNAL( downloadProgress( qint64, qint64 ) ), this, SLOT( capabilitiesReplyProgress( qint64, qint64 ) ) );
357361
}
358362

363+
359364
void QgsWFSSourceSelect::addLayer()
360365
{
361366
//get selected entry in lstWidget

‎src/plugins/wfs/qgswfssourceselect.h

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,14 @@
2323

2424
class QgisInterface;
2525
class QgsGenericProjectionSelector;
26+
class QNetworkReply;
2627

2728
class QgsWFSSourceSelect: public QDialog, private Ui::QgsWFSSourceSelectBase
2829
{
2930
Q_OBJECT
3031

3132
public:
3233

33-
enum REQUEST_ENCODING
34-
{
35-
GET,
36-
POST,
37-
SOAP /*Note that this goes also through HTTP POST but additionally uses soap envelope and friends*/
38-
};
39-
4034
QgsWFSSourceSelect( QWidget* parent, QgisInterface* iface );
4135
~QgsWFSSourceSelect();
4236

@@ -50,6 +44,8 @@ class QgsWFSSourceSelect: public QDialog, private Ui::QgsWFSSourceSelectBase
5044
stores the CRS for the typename in the form 'EPSG:XXXX'*/
5145
std::map<QString, std::list<QString> > mAvailableCRS;
5246
QAbstractButton* btnAdd;
47+
QNetworkReply *mCapabilitiesReply;
48+
5349
void populateConnectionList();
5450

5551
/**Returns the best suited CRS from a set of authority ids
@@ -59,19 +55,6 @@ class QgsWFSSourceSelect: public QDialog, private Ui::QgsWFSSourceSelectBase
5955
@return the authority id of the crs or an empty string in case of error*/
6056
QString getPreferredCrs( const QSet<QString>& crsSet ) const;
6157

62-
/**Makes a GetCapabilities and returns the typenamse and crs supported by the server.
63-
@param typenames a list of layers provided by the server
64-
@param crs a list of crs supported by the server. The place in the list corresponds to the
65-
typenames list (means that the crs list at position 0 is a crs for typename at position 0 etc.)
66-
@param title title list
67-
@param abstract textual descriptions for the types
68-
@return 0 in case of success*/
69-
int getCapabilities( const QString& uri, QgsWFSSourceSelect::REQUEST_ENCODING e, std::list<QString>& typenames, std::list< std::list<QString> >& crs, std::list<QString>& titles, std::list<QString>& abstracts );
70-
//encoding specific methods of getCapabilities
71-
int getCapabilitiesGET( QString uri, std::list<QString>& typenames, std::list< std::list<QString> >& crs, std::list<QString>& titles, std::list<QString>& abstracts );
72-
int getCapabilitiesPOST( const QString& uri, std::list<QString>& typenames, std::list< std::list<QString> >& crs, std::list<QString>& titles, std::list<QString>& abstracts );
73-
int getCapabilitiesSOAP( const QString& uri, std::list<QString>& typenames, std::list< std::list<QString> >& crs, std::list<QString>& titles, std::list<QString>& abstracts );
74-
7558
private slots:
7659
void addEntryToServerList();
7760
void modifyEntryOfServerList();
@@ -81,6 +64,8 @@ class QgsWFSSourceSelect: public QDialog, private Ui::QgsWFSSourceSelectBase
8164
void changeCRS();
8265
void changeCRSFilter();
8366
void on_cmbConnections_activated( int index );
67+
void capabilitiesReplyFinished();
68+
void capabilitiesReplyProgress( qint64, qint64 );
8469

8570
void on_buttonBox_helpRequested() { QgsContextHelp::run( metaObject()->className() ); }
8671
};

‎src/providers/wfs/qgswfsdata.cpp

Lines changed: 62 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,16 @@
1616
#include "qgsrectangle.h"
1717
#include "qgscoordinatereferencesystem.h"
1818
#include "qgsgeometry.h"
19-
#include "qgshttptransaction.h"
2019
#include "qgslogger.h"
20+
#include "qgsnetworkaccessmanager.h"
2121
#include <QBuffer>
22-
#include <QUrl>
2322
#include <QList>
23+
#include <QNetworkRequest>
24+
#include <QNetworkReply>
2425
#include <QProgressDialog>
2526
#include <QSet>
2627
#include <QSettings>
27-
28-
//just for a test
29-
//#include <QProgressDialog>
28+
#include <QUrl>
3029

3130
const char NS_SEPARATOR = '?';
3231
const QString GML_NAMESPACE = "http://www.opengis.net/gml";
@@ -35,7 +34,8 @@ QgsWFSData::QgsWFSData(
3534
const QString& uri,
3635
QgsRectangle* extent,
3736
QgsCoordinateReferenceSystem* srs,
38-
QList<QgsFeature*> &features,
37+
QMap<int, QgsFeature*> &features,
38+
QMap<int, QString > &idMap,
3939
const QString& geometryAttribute,
4040
const QMap<QString, QPair<int, QgsField> >& thematicAttributes,
4141
QGis::WkbType* wkbType )
@@ -44,15 +44,13 @@ QgsWFSData::QgsWFSData(
4444
mExtent( extent ),
4545
mSrs( srs ),
4646
mFeatures( features ),
47+
mIdMap( idMap ),
4748
mGeometryAttribute( geometryAttribute ),
4849
mThematicAttributes( thematicAttributes ),
4950
mWkbType( wkbType ),
5051
mFinished( false ),
5152
mFeatureCount( 0 )
5253
{
53-
//qWarning("Name of the geometry attribute is:");
54-
//qWarning(mGeometryAttribute.toLocal8Bit().data());
55-
5654
//find out mTypeName from uri
5755
QStringList arguments = uri.split( "&" );
5856
QStringList::const_iterator it;
@@ -61,16 +59,17 @@ QgsWFSData::QgsWFSData(
6159
if ( it->startsWith( "TYPENAME", Qt::CaseInsensitive ) )
6260
{
6361
mTypeName = it->section( "=", 1, 1 );
62+
//and strip away namespace prefix
63+
QStringList splitList = mTypeName.split( ":" );
64+
if ( splitList.size() > 1 )
65+
{
66+
mTypeName = splitList.at( 1 );
67+
}
6468
QgsDebugMsg( QString( "mTypeName is: %1" ).arg( mTypeName ) );
6569
}
6670
}
6771

68-
QSettings s;
69-
mNetworkTimeoutMsec = s.value( "/qgis/networkAndProxy/networkTimeout", "60000" ).toInt();
70-
7172
mEndian = QgsApplication::endian();
72-
QObject::connect( &mHttp, SIGNAL( done( bool ) ), this, SLOT( setFinished( bool ) ) );
73-
QObject::connect( &mNetworkTimeoutTimer, SIGNAL( timeout() ), this, SLOT( setFinished() ) );
7473
}
7574

7675
QgsWFSData::~QgsWFSData()
@@ -91,19 +90,12 @@ int QgsWFSData::getWFSData()
9190
mExtent->set( 0, 0, 0, 0 );
9291
}
9392

94-
//separate host from query string
95-
QUrl requestUrl( mUri );
96-
int portNr = requestUrl.port();
97-
if ( portNr != -1 )
98-
{
99-
mHttp.setHost( requestUrl.host(), portNr );
100-
}
101-
else
102-
{
103-
mHttp.setHost( requestUrl.host() );
104-
}
93+
//QUrl requestUrl( mUri );
94+
QNetworkRequest request( mUri );
95+
QNetworkReply* reply = QgsNetworkAccessManager::instance()->get( request );
10596

106-
QgsHttpTransaction::applyProxySettings( mHttp, mUri );
97+
connect( reply, SIGNAL( finished() ), this, SLOT( setFinished() ) );
98+
connect( reply, SIGNAL( downloadProgress( qint64, qint64 ) ), this, SLOT( handleProgressEvent( qint64, qint64 ) ) );
10799

108100
//find out if there is a QGIS main window. If yes, display a progress dialog
109101
QProgressDialog* progressDialog = 0;
@@ -113,37 +105,29 @@ int QgsWFSData::getWFSData()
113105
{
114106
progressDialog = new QProgressDialog( tr( "Loading WFS data" ), tr( "Abort" ), 0, 0, mainWindow );
115107
progressDialog->setWindowModality( Qt::ApplicationModal );
116-
connect( &mHttp, SIGNAL( dataReadProgress( int, int ) ), this, SLOT( handleProgressEvent( int, int ) ) );
117108
connect( this, SIGNAL( dataReadProgress( int ) ), progressDialog, SLOT( setValue( int ) ) );
118109
connect( this, SIGNAL( totalStepsUpdate( int ) ), progressDialog, SLOT( setMaximum( int ) ) );
119-
connect( progressDialog, SIGNAL( canceled() ), &mHttp, SLOT( abort() ) );
110+
connect( progressDialog, SIGNAL( canceled() ), this, SLOT( setFinished() ) );
120111
progressDialog->show();
121112
}
122113

123-
//setup timer
124-
mNetworkTimeoutTimer.setSingleShot( true );
125-
mNetworkTimeoutTimer.start( mNetworkTimeoutMsec );
126-
mHttp.get( requestUrl.path() + "?" + QString( requestUrl.encodedQuery() ) );
127-
128-
129-
//loop to read the data
130114
QByteArray readData;
131115
int atEnd = 0;
132-
QgsDebugMsg( "Entering loop" );
133-
while ( !mFinished || mHttp.bytesAvailable() > 0 )
116+
while ( !atEnd )
134117
{
135118
if ( mFinished )
136119
{
137120
atEnd = 1;
138121
}
139-
if ( mHttp.bytesAvailable() != 0 )
122+
readData = reply->readAll();
123+
if ( readData.size() > 0 )
140124
{
141-
readData = mHttp.readAll();
142125
XML_Parse( p, readData.data(), readData.size(), atEnd );
143126
}
144-
qApp->processEvents();
127+
QCoreApplication::processEvents();
145128
}
146129

130+
delete reply;
147131
delete progressDialog;
148132

149133
if ( mExtent )
@@ -155,28 +139,23 @@ int QgsWFSData::getWFSData()
155139
}
156140
}
157141

158-
return 0; //soon
142+
return 0;
159143
}
160144

161-
void QgsWFSData::setFinished( bool error )
145+
void QgsWFSData::setFinished( )
162146
{
163-
if ( error )
164-
{
165-
//qWarning("Finished with error");
166-
//qWarning(mHttp.errorString().toLocal8Bit().data());
167-
}
168-
else
169-
{
170-
//qWarning("Finished without error");
171-
}
172147
mFinished = true;
173148
}
174149

175-
void QgsWFSData::handleProgressEvent( int progress, int totalSteps )
150+
void QgsWFSData::handleProgressEvent( qint64 progress, qint64 totalSteps )
176151
{
177152
emit dataReadProgress( progress );
153+
if ( totalSteps < 0 )
154+
{
155+
totalSteps = 0;
156+
}
178157
emit totalStepsUpdate( totalSteps );
179-
mNetworkTimeoutTimer.start( mNetworkTimeoutMsec );
158+
emit dataProgressAndSteps( progress, totalSteps );
180159
}
181160

182161
void QgsWFSData::startElement( const XML_Char* el, const XML_Char** attr )
@@ -187,8 +166,16 @@ void QgsWFSData::startElement( const XML_Char* el, const XML_Char** attr )
187166
{
188167
mParseModeStack.push( QgsWFSData::coordinate );
189168
mStringCash.clear();
190-
mCoordinateSeparator = readCsFromAttribute( attr );
191-
mTupleSeparator = readTsFromAttribute( attr );
169+
mCoordinateSeparator = readAttribute( "cs", attr );
170+
if ( mCoordinateSeparator.isEmpty() )
171+
{
172+
mCoordinateSeparator = ",";
173+
}
174+
mTupleSeparator = readAttribute( "ts", attr );
175+
if ( mTupleSeparator.isEmpty() )
176+
{
177+
mTupleSeparator = " ";
178+
}
192179
}
193180
else if ( localName == mGeometryAttribute )
194181
{
@@ -203,6 +190,10 @@ void QgsWFSData::startElement( const XML_Char* el, const XML_Char** attr )
203190
mCurrentFeature = new QgsFeature( mFeatureCount );
204191
mParseModeStack.push( QgsWFSData::featureMember );
205192
}
193+
else if ( localName == mTypeName )
194+
{
195+
mCurrentFeatureId = readAttribute( "fid", attr );
196+
}
206197
else if ( elementName == GML_NAMESPACE + NS_SEPARATOR + "Box" && mParseModeStack.top() == QgsWFSData::boundingBox )
207198
{
208199
//read attribute srsName="EPSG:26910"
@@ -211,7 +202,7 @@ void QgsWFSData::startElement( const XML_Char* el, const XML_Char** attr )
211202
{
212203
QgsDebugMsg( "error, could not get epsg id" );
213204
}
214-
//qWarning(("epsg id is: " + QString::number(epsgNr)).toLocal8Bit().data());
205+
215206
if ( mSrs )
216207
{
217208
if ( !mSrs->createFromOgcWmsCrs( QString( "EPSG:%1" ).arg( epsgNr ) ) )
@@ -337,14 +328,18 @@ void QgsWFSData::endElement( const XML_Char* el )
337328

338329

339330
mCurrentFeature->setGeometryAndOwnership( mCurrentWKB, mCurrentWKBSize );
340-
mFeatures << mCurrentFeature;
331+
mFeatures.insert( mCurrentFeature->id(), mCurrentFeature );
332+
if ( !mCurrentFeatureId.isEmpty() )
333+
{
334+
mIdMap.insert( mCurrentFeature->id(), mCurrentFeatureId );
335+
}
341336
++mFeatureCount;
342337
mParseModeStack.pop();
343338
}
344339
else if ( elementName == GML_NAMESPACE + NS_SEPARATOR + "Point" )
345340
{
346341
std::list<QgsPoint> pointList;
347-
if ( pointsFromCoordinateString( pointList, mStringCash, mCoordinateSeparator, mTupleSeparator ) != 0 )
342+
if ( pointsFromCoordinateString( pointList, mStringCash ) != 0 )
348343
{
349344
//error
350345
}
@@ -381,7 +376,7 @@ void QgsWFSData::endElement( const XML_Char* el )
381376
//add WKB point to the feature
382377

383378
std::list<QgsPoint> pointList;
384-
if ( pointsFromCoordinateString( pointList, mStringCash, mCoordinateSeparator, mTupleSeparator ) != 0 )
379+
if ( pointsFromCoordinateString( pointList, mStringCash ) != 0 )
385380
{
386381
//error
387382
}
@@ -414,7 +409,7 @@ void QgsWFSData::endElement( const XML_Char* el )
414409
else if ( elementName == GML_NAMESPACE + NS_SEPARATOR + "LinearRing" )
415410
{
416411
std::list<QgsPoint> pointList;
417-
if ( pointsFromCoordinateString( pointList, mStringCash, mCoordinateSeparator, mTupleSeparator ) != 0 )
412+
if ( pointsFromCoordinateString( pointList, mStringCash ) != 0 )
418413
{
419414
//error
420415
}
@@ -508,32 +503,18 @@ int QgsWFSData::readEpsgFromAttribute( int& epsgNr, const XML_Char** attr ) cons
508503
return 2;
509504
}
510505

511-
QString QgsWFSData::readCsFromAttribute( const XML_Char** attr ) const
512-
{
513-
int i = 0;
514-
while ( attr[i] != NULL )
515-
{
516-
if ( strcmp( attr[i], "cs" ) == 0 )
517-
{
518-
return QString( attr[i+1] );
519-
}
520-
++i;
521-
}
522-
return ",";
523-
}
524-
525-
QString QgsWFSData::readTsFromAttribute( const XML_Char** attr ) const
506+
QString QgsWFSData::readAttribute( const QString& attributeName, const XML_Char** attr ) const
526507
{
527508
int i = 0;
528509
while ( attr[i] != NULL )
529510
{
530-
if ( strcmp( attr[i], "ts" ) == 0 )
511+
if ( attributeName.compare( attr[i] ) == 0 )
531512
{
532513
return QString( attr[i+1] );
533514
}
534515
++i;
535516
}
536-
return " ";
517+
return QString();
537518
}
538519

539520
int QgsWFSData::createBBoxFromCoordinateString( QgsRectangle* bb, const QString& coordString ) const
@@ -544,9 +525,7 @@ int QgsWFSData::createBBoxFromCoordinateString( QgsRectangle* bb, const QString&
544525
}
545526

546527
std::list<QgsPoint> points;
547-
//qWarning("string is: ");
548-
//qWarning(coordString.toLocal8Bit().data());
549-
if ( pointsFromCoordinateString( points, coordString, mCoordinateSeparator, mTupleSeparator ) != 0 )
528+
if ( pointsFromCoordinateString( points, coordString ) != 0 )
550529
{
551530
return 2;
552531
}
@@ -562,18 +541,18 @@ int QgsWFSData::createBBoxFromCoordinateString( QgsRectangle* bb, const QString&
562541
return 0;
563542
}
564543

565-
int QgsWFSData::pointsFromCoordinateString( std::list<QgsPoint>& points, const QString& coordString, const QString& cs, const QString& ts ) const
544+
int QgsWFSData::pointsFromCoordinateString( std::list<QgsPoint>& points, const QString& coordString ) const
566545
{
567546
//tuples are separated by space, x/y by ','
568-
QStringList tuples = coordString.split( ts, QString::SkipEmptyParts );
547+
QStringList tuples = coordString.split( mTupleSeparator, QString::SkipEmptyParts );
569548
QStringList tuples_coordinates;
570549
double x, y;
571550
bool conversionSuccess;
572551

573552
QStringList::const_iterator tupleIterator;
574553
for ( tupleIterator = tuples.constBegin(); tupleIterator != tuples.constEnd(); ++tupleIterator )
575554
{
576-
tuples_coordinates = tupleIterator->split( cs, QString::SkipEmptyParts );
555+
tuples_coordinates = tupleIterator->split( mCoordinateSeparator, QString::SkipEmptyParts );
577556
if ( tuples_coordinates.size() < 2 )
578557
{
579558
continue;

‎src/providers/wfs/qgswfsdata.h

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
#ifndef QGSWFSDATA_H
1616
#define QGSWFSDATA_H
1717

18-
#include <QHttp>
19-
#include <QTimer>
2018
#include <expat.h>
2119
#include "qgis.h"
2220
#include "qgsapplication.h"
@@ -40,7 +38,8 @@ class QgsWFSData: public QObject
4038
const QString& uri,
4139
QgsRectangle* extent,
4240
QgsCoordinateReferenceSystem* srs,
43-
QList<QgsFeature*> &features,
41+
QMap<int, QgsFeature* > &features,
42+
QMap<int, QString > &idMap,
4443
const QString& geometryAttribute,
4544
const QMap<QString, QPair<int, QgsField> >& thematicAttributes,
4645
QGis::WkbType* wkbType );
@@ -53,18 +52,18 @@ class QgsWFSData: public QObject
5352
@param features the features of the layer
5453
@return 0 in case of success*/
5554
int getWFSData();
56-
/**Returns a pointer to the internal QHttp object (mainly for the purpose of making singal/slot connections*/
57-
const QHttp* http() const {return &mHttp;}
5855

5956
private slots:
60-
void setFinished( bool error = true );
57+
void setFinished();
6158

6259
/**Takes progress value and total steps and emit signals 'dataReadProgress' and 'totalStepUpdate'*/
63-
void handleProgressEvent( int progress, int totalSteps );
60+
void handleProgressEvent( qint64 progress, qint64 totalSteps );
6461

6562
signals:
6663
void dataReadProgress( int progress );
6764
void totalStepsUpdate( int totalSteps );
65+
//also emit signal with progress and totalSteps together (this is better for the status message)
66+
void dataProgressAndSteps( int progress, int totalSteps );
6867

6968
private:
7069

@@ -108,12 +107,9 @@ class QgsWFSData: public QObject
108107
@param attr attribute strings
109108
@return 0 in case of success*/
110109
int readEpsgFromAttribute( int& epsgNr, const XML_Char** attr ) const;
111-
/**Reads the 'cs' (coordinate separator) attribute.
112-
@return the cs attribute value or the default value ","*/
113-
QString readCsFromAttribute( const XML_Char** attr ) const;
114-
/**Reads the 'ts' (tuple separator) attribute.
115-
@return the ts attribute value or the devault value " "*/
116-
QString readTsFromAttribute( const XML_Char** attr ) const;
110+
/**Reads attribute as string
111+
@return attribute value or an empty string if no such attribute*/
112+
QString readAttribute( const QString& attributeName, const XML_Char** attr ) const;
117113
/**Creates a rectangle from a coordinate string.
118114
@return 0 in case of success*/
119115
int createBBoxFromCoordinateString( QgsRectangle* bb, const QString& coordString ) const;
@@ -123,7 +119,8 @@ class QgsWFSData: public QObject
123119
@param cs coortinate separator
124120
@param ts tuple separator
125121
@return 0 in case of success*/
126-
int pointsFromCoordinateString( std::list<QgsPoint>& points, const QString& coordString, const QString& cs, const QString& ts ) const;
122+
int pointsFromCoordinateString( std::list<QgsPoint>& points, const QString& coordString ) const;
123+
127124
int getPointWKB( unsigned char** wkb, int* size, const QgsPoint& ) const;
128125
int getLineWKB( unsigned char** wkb, int* size, const std::list<QgsPoint>& lineCoordinates ) const;
129126
int getRingWKB( unsigned char** wkb, int* size, const std::list<QgsPoint>& ringCoordinates ) const;
@@ -149,20 +146,21 @@ class QgsWFSData: public QObject
149146
/**Source srs of the layer*/
150147
QgsCoordinateReferenceSystem* mSrs;
151148
/**The features of the layer*/
152-
QList<QgsFeature*> &mFeatures;
149+
QMap<int, QgsFeature* > &mFeatures;
150+
/**Stores the relation between provider ids and WFS server ids*/
151+
QMap<int, QString > &mIdMap;
153152
/**Name of geometry attribute*/
154153
QString mGeometryAttribute;
155154
const QMap<QString, QPair<int, QgsField> > &mThematicAttributes;
156155
QGis::WkbType* mWkbType;
157156
/**True if the request is finished*/
158157
bool mFinished;
159-
/**The HTTP client object*/
160-
QHttp mHttp;
161158
/**Keep track about the most important nested elements*/
162159
std::stack<parseMode> mParseModeStack;
163160
/**This contains the character data if an important element has been encountered*/
164161
QString mStringCash;
165162
QgsFeature* mCurrentFeature;
163+
QString mCurrentFeatureId;
166164
int mFeatureCount;
167165
/**The total WKB for a feature*/
168166
unsigned char* mCurrentWKB;
@@ -179,8 +177,6 @@ class QgsWFSData: public QObject
179177
QString mCoordinateSeparator;
180178
/**Tuple separator for coordinate strings. Usually " " */
181179
QString mTupleSeparator;
182-
int mNetworkTimeoutMsec;
183-
QTimer mNetworkTimeoutTimer;
184180
};
185181

186182
#endif

‎src/providers/wfs/qgswfsprovider.cpp

Lines changed: 873 additions & 63 deletions
Large diffs are not rendered by default.

‎src/providers/wfs/qgswfsprovider.h

Lines changed: 97 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@ class QgsWFSProvider: public QgsVectorDataProvider
3636
enum REQUEST_ENCODING
3737
{
3838
GET,
39-
POST,
40-
SOAP,/*Note that this goes also through HTTP POST but additionally uses soap envelope and friends*/
4139
FILE //reads from a file on disk
4240
};
4341

@@ -80,6 +78,8 @@ class QgsWFSProvider: public QgsVectorDataProvider
8078
QString name() const;
8179
QString description() const;
8280

81+
virtual int capabilities() const;
82+
8383
/* new functions */
8484

8585
/**Sets the encoding type in which the provider makes requests and interprets
@@ -90,6 +90,35 @@ class QgsWFSProvider: public QgsVectorDataProvider
9090
stores them in a vector*/
9191
int getFeature( const QString& uri );
9292

93+
//Editing operations
94+
/**
95+
* Adds a list of features
96+
* @return true in case of success and false in case of failure
97+
*/
98+
virtual bool addFeatures( QgsFeatureList &flist );
99+
100+
/**
101+
* Deletes one or more features
102+
* @param id list containing feature ids to delete
103+
* @return true in case of success and false in case of failure
104+
*/
105+
virtual bool deleteFeatures( const QgsFeatureIds &id );
106+
107+
/**
108+
* Changes geometries of existing features
109+
* @param geometry_map A QgsGeometryMap whose index contains the feature IDs
110+
* that will have their geometries changed.
111+
* The second map parameter being the new geometries themselves
112+
* @return True in case of success and false in case of failure
113+
*/
114+
virtual bool changeGeometryValues( QgsGeometryMap & geometry_map );
115+
116+
/**
117+
* Changes attribute values of existing features.
118+
* @param attr_map a map containing changed attributes
119+
* @return true in case of success and false in case of failure
120+
*/
121+
virtual bool changeAttributeValues( const QgsChangedAttributesMap &attr_map );
93122

94123
signals:
95124
void dataReadProgressMessage( QString message );
@@ -99,9 +128,17 @@ class QgsWFSProvider: public QgsVectorDataProvider
99128
and emits the dataReadProgressMessage signal*/
100129
void handleWFSProgressMessage( int done, int total );
101130

131+
/**Sets mNetworkRequestFinished flag to true*/
132+
void networkRequestFinished();
133+
134+
private:
135+
bool mNetworkRequestFinished;
102136

103137
protected:
138+
/**Thematic attributes*/
104139
QgsFieldMap mFields;
140+
/**Name of geometry attribute*/
141+
QString mGeometryAttribute;
105142
/**The encoding used for request/response. Can be GET, POST or SOAP*/
106143
REQUEST_ENCODING mEncoding;
107144
/**Bounding box for the layer*/
@@ -116,15 +153,21 @@ class QgsWFSProvider: public QgsVectorDataProvider
116153
QList<int> mSelectedFeatures;
117154
/**Iterator on the feature vector for use in rewind(), nextFeature(), etc...*/
118155
QList<int>::iterator mFeatureIterator;
119-
/**Vector where the features are inserted*/
120-
QList<QgsFeature*> mFeatures;
156+
/**Map <feature Id / feature> */
157+
QMap<int, QgsFeature* > mFeatures;
158+
/**Stores the relation between provider ids and WFS server ids*/
159+
QMap<int, QString > mIdMap;
121160
/**Geometry type of the features in this layer*/
122161
mutable QGis::WkbType mWKBType;
123162
/**Source CRS*/
124163
QgsCoordinateReferenceSystem mSourceCRS;
125164
int mFeatureCount;
126165
/**Flag if provider is valid*/
127166
bool mValid;
167+
/**Namespace URL of the server (comes from DescribeFeatureDocument)*/
168+
QString mWfsNamespace;
169+
/**Server capabilities for this layer (generated from capabilities document)*/
170+
int mCapabilities;
128171

129172

130173
/**Collects information about the field types. Is called internally from QgsWFSProvider::getFeature. The method delegates the work to request specific ones and gives back the name of the geometry attribute and the thematic attributes with their types*/
@@ -142,7 +185,7 @@ class QgsWFSProvider: public QgsVectorDataProvider
142185
int describeFeatureTypeFile( const QString& uri, QString& geometryAttribute, QgsFieldMap& fields );
143186

144187
/**Reads the name of the geometry attribute, the thematic attributes and their types from a dom document. Returns 0 in case of success*/
145-
int readAttributesFromSchema( QDomDocument& schemaDoc, QString& geometryAttribute, QgsFieldMap& fields ) const;
188+
int readAttributesFromSchema( QDomDocument& schemaDoc, QString& geometryAttribute, QgsFieldMap& fields );
146189
/**This method tries to guess the geometry attribute and the other attribute names from the .gml file if no schema is present. Returns 0 in case of success*/
147190
int guessAttributesFromFile( const QString& uri, QString& geometryAttribute, std::list<QString>& thematicAttributes ) const;
148191

@@ -171,6 +214,55 @@ class QgsWFSProvider: public QgsVectorDataProvider
171214
int readGML2Coordinates( std::list<QgsPoint>& coords, const QDomElement elem ) const;
172215
/**Tries to create a QgsCoordinateReferenceSystem object and assign it to mSourceCRS. Returns 0 in case of success*/
173216
int setCRSFromGML2( const QDomElement& wfsCollectionElement );
217+
218+
219+
//methods to write GML2
220+
221+
QDomElement createGeometryElem( QgsGeometry* g, QDomDocument& doc ) /*const*/;
222+
QDomElement createLineStringElem( QgsGeometry* geom, QDomDocument& doc ) const;
223+
QDomElement createMultiLineStringElem( QgsGeometry* geom, QDomDocument& doc ) const;
224+
QDomElement createPointElem( QgsGeometry* geom, QDomDocument& doc ) const;
225+
QDomElement createMultiPointElem( QgsGeometry* geom, QDomDocument& doc ) const;
226+
QDomElement createPolygonElem( QgsGeometry* geom, QDomDocument& doc ) const;
227+
QDomElement createMultiPolygonElem( QgsGeometry* geom, QDomDocument& doc ) const;
228+
229+
/**Create a GML coordinate string from a point list.
230+
@param points list of data points
231+
@param coordString out: GML coord string
232+
@return 0 in case of success*/
233+
QDomElement createCoordinateElem( const QVector<QgsPoint> points, QDomDocument& doc ) const;
234+
235+
//helper methods for WFS-T
236+
237+
/**Extracts the typename from the providers url
238+
@return typename or a null string in case of error*/
239+
QString typeNameFromUrl() const;
240+
241+
/**Removes a possible namespace prefix from a typename*/
242+
void removeNamespacePrefix( QString& tname ) const;
243+
/**Returns namespace prefix (or an empty string if there is no prefix)*/
244+
QString nameSpacePrefix( const QString& tname ) const;
245+
246+
/**Sends the transaction document to the server using HTTP POST
247+
@return true if transmission to the server succeeded, otherwise false
248+
note: true does not automatically mean that the transaction succeeded*/
249+
bool sendTransactionDocument( const QDomDocument& doc, QDomDocument& serverResponse );
250+
251+
/**Creates a transaction element and adds it (normally as first element) to the document*/
252+
QDomElement createTransactionElement( QDomDocument& doc ) const;
253+
254+
/**True if the server response means success*/
255+
bool transactionSuccess( const QDomDocument& serverResponse ) const;
256+
/**Returns the inserted ids*/
257+
QStringList insertedFeatureIds( const QDomDocument& serverResponse ) const;
258+
/**Returns a key suitable for new items*/
259+
int findNewKey() const;
260+
/**Retrieve capabilities for this layer from GetCapabilities document (will be stored in mCapabilites)*/
261+
void getLayerCapabilities();
262+
/**Takes <Operations> element and updates the capabilities*/
263+
void appendSupportedOperations( const QDomElement& operationsElem, int& capabilities ) const;
264+
/**Shows a message box with the exception string (or does nothing if the xml document is not an exception)*/
265+
void handleException( const QDomDocument& serverResponse ) const;
174266
};
175267

176268
#endif

0 commit comments

Comments
 (0)
Please sign in to comment.