/src/gdal/ogr/ogrsf_frmts/wfs/ogrwfsdatasource.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: WFS Translator |
4 | | * Purpose: Implements OGRWFSDataSource class |
5 | | * Author: Even Rouault, even dot rouault at spatialys.com |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2010-2013, Even Rouault <even dot rouault at spatialys.com> |
9 | | * |
10 | | * SPDX-License-Identifier: MIT |
11 | | ****************************************************************************/ |
12 | | |
13 | | #include "cpl_port.h" |
14 | | #include "ogr_wfs.h" |
15 | | #include "ogr_api.h" |
16 | | #include "cpl_minixml.h" |
17 | | #include "cpl_http.h" |
18 | | #include "gmlutils.h" |
19 | | #include "parsexsd.h" |
20 | | #include "ogr_swq.h" |
21 | | #include "ogr_p.h" |
22 | | #include "ogrwfsfilter.h" |
23 | | #include "memdataset.h" |
24 | | |
25 | | #include <algorithm> |
26 | | |
27 | | typedef struct |
28 | | { |
29 | | const char *pszPath; |
30 | | const char *pszMDI; |
31 | | } MetadataItem; |
32 | | |
33 | | static const MetadataItem asMetadata[] = { |
34 | | {"Service.Title", "TITLE"}, /*1.0 */ |
35 | | {"ServiceIdentification.Title", "TITLE"}, /* 1.1 or 2.0 */ |
36 | | {"Service.Abstract", "ABSTRACT"}, /* 1.0 */ |
37 | | {"ServiceIdentification.Abstract", "ABSTRACT"}, /* 1.1 or 2.0 */ |
38 | | {"ServiceProvider.ProviderName", "PROVIDER_NAME"}, /* 1.1 or 2.0 */ |
39 | | }; |
40 | | |
41 | | /************************************************************************/ |
42 | | /* WFSFindNode() */ |
43 | | /************************************************************************/ |
44 | | |
45 | | const CPLXMLNode *WFSFindNode(const CPLXMLNode *psXML, const char *pszRootName) |
46 | 3.70k | { |
47 | 3.70k | const CPLXMLNode *psIter = psXML; |
48 | 3.70k | do |
49 | 141k | { |
50 | 141k | if (psIter->eType == CXT_Element) |
51 | 65.2k | { |
52 | 65.2k | const char *pszNodeName = psIter->pszValue; |
53 | 65.2k | const char *pszSep = strchr(pszNodeName, ':'); |
54 | 65.2k | if (pszSep) |
55 | 19.2k | pszNodeName = pszSep + 1; |
56 | 65.2k | if (EQUAL(pszNodeName, pszRootName)) |
57 | 2.22k | { |
58 | 2.22k | return psIter; |
59 | 2.22k | } |
60 | 65.2k | } |
61 | 138k | psIter = psIter->psNext; |
62 | 138k | } while (psIter); |
63 | | |
64 | 1.48k | psIter = psXML->psChild; |
65 | 3.59k | while (psIter) |
66 | 2.14k | { |
67 | 2.14k | if (psIter->eType == CXT_Element) |
68 | 101 | { |
69 | 101 | const char *pszNodeName = psIter->pszValue; |
70 | 101 | const char *pszSep = strchr(pszNodeName, ':'); |
71 | 101 | if (pszSep) |
72 | 3 | pszNodeName = pszSep + 1; |
73 | 101 | if (EQUAL(pszNodeName, pszRootName)) |
74 | 29 | { |
75 | 29 | return psIter; |
76 | 29 | } |
77 | 101 | } |
78 | 2.11k | psIter = psIter->psNext; |
79 | 2.11k | } |
80 | 1.45k | return nullptr; |
81 | 1.48k | } |
82 | | |
83 | | /************************************************************************/ |
84 | | /* OGRWFSWrappedResultLayer */ |
85 | | /************************************************************************/ |
86 | | |
87 | | class OGRWFSWrappedResultLayer final : public OGRLayer |
88 | | { |
89 | | std::unique_ptr<GDALDataset> poDS{}; |
90 | | OGRLayer *poLayer = nullptr; |
91 | | |
92 | | CPL_DISALLOW_COPY_ASSIGN(OGRWFSWrappedResultLayer) |
93 | | |
94 | | public: |
95 | | OGRWFSWrappedResultLayer(GDALDataset *poDSIn, OGRLayer *poLayerIn) |
96 | 0 | : poDS(poDSIn), poLayer(poLayerIn) |
97 | 0 | { |
98 | 0 | } |
99 | | |
100 | | void ResetReading() override; |
101 | | |
102 | | OGRFeature *GetNextFeature() override |
103 | 0 | { |
104 | 0 | return poLayer->GetNextFeature(); |
105 | 0 | } |
106 | | |
107 | | OGRErr SetNextByIndex(GIntBig nIndex) override |
108 | 0 | { |
109 | 0 | return poLayer->SetNextByIndex(nIndex); |
110 | 0 | } |
111 | | |
112 | | OGRFeature *GetFeature(GIntBig nFID) override |
113 | 0 | { |
114 | 0 | return poLayer->GetFeature(nFID); |
115 | 0 | } |
116 | | |
117 | | const OGRFeatureDefn *GetLayerDefn() const override |
118 | 0 | { |
119 | 0 | return poLayer->GetLayerDefn(); |
120 | 0 | } |
121 | | |
122 | | GIntBig GetFeatureCount(int bForce = TRUE) override |
123 | 0 | { |
124 | 0 | return poLayer->GetFeatureCount(bForce); |
125 | 0 | } |
126 | | |
127 | | bool TestCapability(const char *pszCap) const override |
128 | 0 | { |
129 | 0 | return poLayer->TestCapability(pszCap); |
130 | 0 | } |
131 | | }; |
132 | | |
133 | | void OGRWFSWrappedResultLayer::ResetReading() |
134 | 0 | { |
135 | 0 | poLayer->ResetReading(); |
136 | 0 | } |
137 | | |
138 | | /************************************************************************/ |
139 | | /* OGRWFSDataSource() */ |
140 | | /************************************************************************/ |
141 | | |
142 | | OGRWFSDataSource::OGRWFSDataSource() |
143 | | : bPagingAllowed( |
144 | 2.76k | CPLTestBool(CPLGetConfigOption("OGR_WFS_PAGING_ALLOWED", "OFF"))), |
145 | 2.76k | bLoadMultipleLayerDefn(CPLTestBool( |
146 | 2.76k | CPLGetConfigOption("OGR_WFS_LOAD_MULTIPLE_LAYER_DEFN", "TRUE"))) |
147 | 2.76k | { |
148 | 2.76k | if (bPagingAllowed) |
149 | 0 | { |
150 | 0 | const char *pszOption = |
151 | 0 | CPLGetConfigOption("OGR_WFS_PAGE_SIZE", nullptr); |
152 | 0 | if (pszOption != nullptr) |
153 | 0 | { |
154 | 0 | nPageSize = atoi(pszOption); |
155 | 0 | if (nPageSize <= 0) |
156 | 0 | nPageSize = DEFAULT_PAGE_SIZE; |
157 | 0 | } |
158 | |
|
159 | 0 | pszOption = CPLGetConfigOption("OGR_WFS_BASE_START_INDEX", nullptr); |
160 | 0 | if (pszOption != nullptr) |
161 | 0 | nBaseStartIndex = atoi(pszOption); |
162 | 0 | } |
163 | 2.76k | } |
164 | | |
165 | | /************************************************************************/ |
166 | | /* ~OGRWFSDataSource() */ |
167 | | /************************************************************************/ |
168 | | |
169 | | OGRWFSDataSource::~OGRWFSDataSource() |
170 | | |
171 | 2.76k | { |
172 | 2.76k | if (psFileXML) |
173 | 27 | { |
174 | 27 | if (bRewriteFile) |
175 | 0 | { |
176 | 0 | CPLSerializeXMLTreeToFile(psFileXML, GetDescription()); |
177 | 0 | } |
178 | | |
179 | 27 | CPLDestroyXMLNode(psFileXML); |
180 | 27 | } |
181 | | |
182 | 9.63k | for (int i = 0; i < nLayers; i++) |
183 | 6.86k | delete papoLayers[i]; |
184 | 2.76k | CPLFree(papoLayers); |
185 | | |
186 | 2.76k | if (!osLayerMetadataTmpFileName.empty()) |
187 | 0 | VSIUnlink(osLayerMetadataTmpFileName); |
188 | 2.76k | delete poLayerMetadataDS; |
189 | 2.76k | delete poLayerGetCapabilitiesDS; |
190 | | |
191 | 2.76k | CSLDestroy(papszIdGenMethods); |
192 | 2.76k | CSLDestroy(papszHttpOptions); |
193 | 2.76k | } |
194 | | |
195 | | /************************************************************************/ |
196 | | /* GetLayer() */ |
197 | | /************************************************************************/ |
198 | | |
199 | | const OGRLayer *OGRWFSDataSource::GetLayer(int iLayer) const |
200 | | |
201 | 1.23k | { |
202 | 1.23k | if (iLayer < 0 || iLayer >= nLayers) |
203 | 0 | return nullptr; |
204 | 1.23k | else |
205 | 1.23k | return papoLayers[iLayer]; |
206 | 1.23k | } |
207 | | |
208 | | /************************************************************************/ |
209 | | /* GetLayerByName() */ |
210 | | /************************************************************************/ |
211 | | |
212 | | OGRLayer *OGRWFSDataSource::GetLayerByName(const char *pszNameIn) |
213 | 186 | { |
214 | 186 | if (!pszNameIn) |
215 | 0 | return nullptr; |
216 | | |
217 | 186 | if (EQUAL(pszNameIn, "WFSLayerMetadata")) |
218 | 0 | { |
219 | 0 | if (!osLayerMetadataTmpFileName.empty()) |
220 | 0 | return poLayerMetadataLayer; |
221 | | |
222 | 0 | osLayerMetadataTmpFileName = |
223 | 0 | VSIMemGenerateHiddenFilename("WFSLayerMetadata.csv"); |
224 | 0 | osLayerMetadataCSV = "layer_name,title,abstract\n" + osLayerMetadataCSV; |
225 | |
|
226 | 0 | VSIFCloseL(VSIFileFromMemBuffer( |
227 | 0 | osLayerMetadataTmpFileName, |
228 | 0 | reinterpret_cast<GByte *>(osLayerMetadataCSV.data()), |
229 | 0 | osLayerMetadataCSV.size(), FALSE)); |
230 | 0 | poLayerMetadataDS = |
231 | 0 | GDALDataset::Open(osLayerMetadataTmpFileName, GDAL_OF_VECTOR, |
232 | 0 | nullptr, nullptr, nullptr); |
233 | 0 | if (poLayerMetadataDS) |
234 | 0 | poLayerMetadataLayer = poLayerMetadataDS->GetLayer(0); |
235 | 0 | return poLayerMetadataLayer; |
236 | 0 | } |
237 | 186 | else if (EQUAL(pszNameIn, "WFSGetCapabilities")) |
238 | 0 | { |
239 | 0 | if (poLayerGetCapabilitiesLayer != nullptr) |
240 | 0 | return poLayerGetCapabilitiesLayer; |
241 | | |
242 | 0 | poLayerGetCapabilitiesDS = MEMDataset::Create( |
243 | 0 | "WFSGetCapabilities", 0, 0, 0, GDT_Unknown, nullptr); |
244 | 0 | poLayerGetCapabilitiesLayer = poLayerGetCapabilitiesDS->CreateLayer( |
245 | 0 | "WFSGetCapabilities", nullptr, wkbNone, nullptr); |
246 | 0 | OGRFieldDefn oFDefn("content", OFTString); |
247 | 0 | CPL_IGNORE_RET_VAL(poLayerGetCapabilitiesLayer->CreateField(&oFDefn)); |
248 | 0 | OGRFeature *poFeature = |
249 | 0 | new OGRFeature(poLayerGetCapabilitiesLayer->GetLayerDefn()); |
250 | 0 | poFeature->SetField(0, osGetCapabilities); |
251 | 0 | CPL_IGNORE_RET_VAL( |
252 | 0 | poLayerGetCapabilitiesLayer->CreateFeature(poFeature)); |
253 | 0 | delete poFeature; |
254 | |
|
255 | 0 | return poLayerGetCapabilitiesLayer; |
256 | 0 | } |
257 | | |
258 | 186 | int nIndex = GetLayerIndex(pszNameIn); |
259 | 186 | if (nIndex < 0) |
260 | 0 | return nullptr; |
261 | 186 | else |
262 | 186 | return papoLayers[nIndex]; |
263 | 186 | } |
264 | | |
265 | | /************************************************************************/ |
266 | | /* GetMetadataDomainList() */ |
267 | | /************************************************************************/ |
268 | | |
269 | | char **OGRWFSDataSource::GetMetadataDomainList() |
270 | 0 | { |
271 | 0 | return BuildMetadataDomainList(GDALDataset::GetMetadataDomainList(), TRUE, |
272 | 0 | "", "xml:capabilities", nullptr); |
273 | 0 | } |
274 | | |
275 | | /************************************************************************/ |
276 | | /* GetMetadata() */ |
277 | | /************************************************************************/ |
278 | | |
279 | | CSLConstList OGRWFSDataSource::GetMetadata(const char *pszDomain) |
280 | 0 | { |
281 | 0 | if (pszDomain != nullptr && EQUAL(pszDomain, "xml:capabilities")) |
282 | 0 | { |
283 | 0 | apszGetCapabilities[0] = osGetCapabilities.c_str(); |
284 | 0 | apszGetCapabilities[1] = nullptr; |
285 | 0 | return const_cast<char **>(apszGetCapabilities); |
286 | 0 | } |
287 | 0 | return GDALDataset::GetMetadata(pszDomain); |
288 | 0 | } |
289 | | |
290 | | /************************************************************************/ |
291 | | /* GetLayerIndex() */ |
292 | | /************************************************************************/ |
293 | | |
294 | | int OGRWFSDataSource::GetLayerIndex(const char *pszNameIn) |
295 | 186 | { |
296 | 186 | bool bHasFoundLayerWithColon = false; |
297 | | |
298 | | /* first a case sensitive check */ |
299 | 197 | for (int i = 0; i < nLayers; i++) |
300 | 197 | { |
301 | 197 | OGRWFSLayer *poLayer = papoLayers[i]; |
302 | | |
303 | 197 | if (strcmp(pszNameIn, poLayer->GetName()) == 0) |
304 | 186 | return i; |
305 | | |
306 | 11 | bHasFoundLayerWithColon |= strchr(poLayer->GetName(), ':') != nullptr; |
307 | 11 | } |
308 | | |
309 | | /* then case insensitive */ |
310 | 0 | for (int i = 0; i < nLayers; i++) |
311 | 0 | { |
312 | 0 | OGRWFSLayer *poLayer = papoLayers[i]; |
313 | |
|
314 | 0 | if (EQUAL(pszNameIn, poLayer->GetName())) |
315 | 0 | return i; |
316 | 0 | } |
317 | | |
318 | | /* now try looking after the colon character */ |
319 | 0 | if (!bKeepLayerNamePrefix && bHasFoundLayerWithColon && |
320 | 0 | strchr(pszNameIn, ':') == nullptr) |
321 | 0 | { |
322 | 0 | for (int i = 0; i < nLayers; i++) |
323 | 0 | { |
324 | 0 | OGRWFSLayer *poLayer = papoLayers[i]; |
325 | |
|
326 | 0 | const char *pszAfterColon = strchr(poLayer->GetName(), ':'); |
327 | 0 | if (pszAfterColon && EQUAL(pszNameIn, pszAfterColon + 1)) |
328 | 0 | return i; |
329 | 0 | } |
330 | 0 | } |
331 | | |
332 | 0 | return -1; |
333 | 0 | } |
334 | | |
335 | | /************************************************************************/ |
336 | | /* FindSubStringInsensitive() */ |
337 | | /************************************************************************/ |
338 | | |
339 | | const char *FindSubStringInsensitive(const char *pszStr, const char *pszSubStr) |
340 | 1.49k | { |
341 | 1.49k | size_t nSubStrPos = CPLString(pszStr).ifind(pszSubStr); |
342 | 1.49k | if (nSubStrPos == std::string::npos) |
343 | 1.44k | return nullptr; |
344 | 50 | return pszStr + nSubStrPos; |
345 | 1.49k | } |
346 | | |
347 | | /************************************************************************/ |
348 | | /* DetectIfGetFeatureSupportHits() */ |
349 | | /************************************************************************/ |
350 | | |
351 | | static bool DetectIfGetFeatureSupportHits(const CPLXMLNode *psRoot) |
352 | 28 | { |
353 | 28 | const CPLXMLNode *psOperationsMetadata = |
354 | 28 | CPLGetXMLNode(psRoot, "OperationsMetadata"); |
355 | 28 | if (!psOperationsMetadata) |
356 | 0 | { |
357 | 0 | CPLDebug("WFS", "Could not find <OperationsMetadata>"); |
358 | 0 | return false; |
359 | 0 | } |
360 | | |
361 | 28 | const CPLXMLNode *psChild = psOperationsMetadata->psChild; |
362 | 85 | while (psChild) |
363 | 85 | { |
364 | 85 | if (psChild->eType == CXT_Element && |
365 | 84 | strcmp(psChild->pszValue, "Operation") == 0 && |
366 | 84 | strcmp(CPLGetXMLValue(psChild, "name", ""), "GetFeature") == 0) |
367 | 28 | { |
368 | 28 | break; |
369 | 28 | } |
370 | 57 | psChild = psChild->psNext; |
371 | 57 | } |
372 | 28 | if (!psChild) |
373 | 0 | { |
374 | 0 | CPLDebug("WFS", "Could not find <Operation name=\"GetFeature\">"); |
375 | 0 | return false; |
376 | 0 | } |
377 | | |
378 | 28 | psChild = psChild->psChild; |
379 | 109 | while (psChild) |
380 | 109 | { |
381 | 109 | if (psChild->eType == CXT_Element && |
382 | 81 | strcmp(psChild->pszValue, "Parameter") == 0 && |
383 | 28 | strcmp(CPLGetXMLValue(psChild, "name", ""), "resultType") == 0) |
384 | 28 | { |
385 | 28 | break; |
386 | 28 | } |
387 | 81 | psChild = psChild->psNext; |
388 | 81 | } |
389 | 28 | if (!psChild) |
390 | 0 | { |
391 | 0 | CPLDebug("WFS", "Could not find <Parameter name=\"resultType\">"); |
392 | 0 | return false; |
393 | 0 | } |
394 | | |
395 | 28 | psChild = psChild->psChild; |
396 | 84 | while (psChild) |
397 | 84 | { |
398 | 84 | if (psChild->eType == CXT_Element && |
399 | 56 | strcmp(psChild->pszValue, "Value") == 0) |
400 | 55 | { |
401 | 55 | CPLXMLNode *psChild2 = psChild->psChild; |
402 | 82 | while (psChild2) |
403 | 55 | { |
404 | 55 | if (psChild2->eType == CXT_Text && |
405 | 55 | strcmp(psChild2->pszValue, "hits") == 0) |
406 | 28 | { |
407 | 28 | CPLDebug("WFS", "GetFeature operation supports hits"); |
408 | 28 | return true; |
409 | 28 | } |
410 | 27 | psChild2 = psChild2->psNext; |
411 | 27 | } |
412 | 55 | } |
413 | 56 | psChild = psChild->psNext; |
414 | 56 | } |
415 | | |
416 | 0 | return false; |
417 | 28 | } |
418 | | |
419 | | /************************************************************************/ |
420 | | /* DetectRequiresEnvelopeSpatialFilter() */ |
421 | | /************************************************************************/ |
422 | | |
423 | | bool OGRWFSDataSource::DetectRequiresEnvelopeSpatialFilter( |
424 | | const CPLXMLNode *psRoot) |
425 | 30 | { |
426 | | // This is a heuristic to detect Deegree 3 servers, such as |
427 | | // http://deegree3-demo.deegree.org:80/deegree-utah-demo/services that are |
428 | | // very GML3 strict, and don't like <gml:Box> in a <Filter><BBOX> request, |
429 | | // but requires instead <gml:Envelope>, but some servers (such as MapServer) |
430 | | // don't like <gml:Envelope> so we are obliged to detect the kind of server. |
431 | | |
432 | 30 | const CPLXMLNode *psGeometryOperands = CPLGetXMLNode( |
433 | 30 | psRoot, "Filter_Capabilities.Spatial_Capabilities.GeometryOperands"); |
434 | 30 | if (!psGeometryOperands) |
435 | 0 | { |
436 | 0 | return false; |
437 | 0 | } |
438 | | |
439 | 30 | int nCount = 0; |
440 | 30 | const CPLXMLNode *psChild = psGeometryOperands->psChild; |
441 | 151 | while (psChild) |
442 | 121 | { |
443 | 121 | nCount++; |
444 | 121 | psChild = psChild->psNext; |
445 | 121 | } |
446 | | // Magic number... Might be fragile. |
447 | 30 | return nCount == 19; |
448 | 30 | } |
449 | | |
450 | | /************************************************************************/ |
451 | | /* GetPostTransactionURL() */ |
452 | | /************************************************************************/ |
453 | | |
454 | | CPLString OGRWFSDataSource::GetPostTransactionURL() |
455 | 0 | { |
456 | 0 | if (!osPostTransactionURL.empty()) |
457 | 0 | return osPostTransactionURL; |
458 | | |
459 | 0 | osPostTransactionURL = osBaseURL; |
460 | 0 | const char *pszPostTransactionURL = osPostTransactionURL.c_str(); |
461 | 0 | const char *pszEsperluet = strchr(pszPostTransactionURL, '?'); |
462 | 0 | if (pszEsperluet) |
463 | 0 | osPostTransactionURL.resize(pszEsperluet - pszPostTransactionURL); |
464 | |
|
465 | 0 | return osPostTransactionURL; |
466 | 0 | } |
467 | | |
468 | | /************************************************************************/ |
469 | | /* DetectTransactionSupport() */ |
470 | | /************************************************************************/ |
471 | | |
472 | | bool OGRWFSDataSource::DetectTransactionSupport(const CPLXMLNode *psRoot) |
473 | 252 | { |
474 | 252 | const CPLXMLNode *psTransactionWFS100 = |
475 | 252 | CPLGetXMLNode(psRoot, "Capability.Request.Transaction"); |
476 | 252 | if (psTransactionWFS100) |
477 | 0 | { |
478 | 0 | const CPLXMLNode *psPostURL = |
479 | 0 | CPLGetXMLNode(psTransactionWFS100, "DCPType.HTTP.Post"); |
480 | 0 | if (psPostURL) |
481 | 0 | { |
482 | 0 | const char *pszPOSTURL = |
483 | 0 | CPLGetXMLValue(psPostURL, "onlineResource", nullptr); |
484 | 0 | if (pszPOSTURL) |
485 | 0 | { |
486 | 0 | osPostTransactionURL = pszPOSTURL; |
487 | 0 | } |
488 | 0 | } |
489 | |
|
490 | 0 | bTransactionSupport = true; |
491 | 0 | return true; |
492 | 0 | } |
493 | | |
494 | 252 | const CPLXMLNode *psOperationsMetadata = |
495 | 252 | CPLGetXMLNode(psRoot, "OperationsMetadata"); |
496 | 252 | if (!psOperationsMetadata) |
497 | 0 | { |
498 | 0 | return false; |
499 | 0 | } |
500 | | |
501 | 252 | const CPLXMLNode *psChild = psOperationsMetadata->psChild; |
502 | 1.02k | while (psChild) |
503 | 794 | { |
504 | 794 | if (psChild->eType == CXT_Element && |
505 | 353 | strcmp(psChild->pszValue, "Operation") == 0 && |
506 | 132 | strcmp(CPLGetXMLValue(psChild, "name", ""), "Transaction") == 0) |
507 | 26 | { |
508 | 26 | break; |
509 | 26 | } |
510 | 768 | psChild = psChild->psNext; |
511 | 768 | } |
512 | 252 | if (!psChild) |
513 | 226 | { |
514 | 226 | CPLDebug("WFS", "No transaction support"); |
515 | 226 | return false; |
516 | 226 | } |
517 | | |
518 | 26 | bTransactionSupport = true; |
519 | 26 | CPLDebug("WFS", "Transaction support !"); |
520 | | |
521 | 26 | const CPLXMLNode *psPostURL = CPLGetXMLNode(psChild, "DCP.HTTP.Post"); |
522 | 26 | if (psPostURL) |
523 | 3 | { |
524 | 3 | const char *pszPOSTURL = CPLGetXMLValue(psPostURL, "href", nullptr); |
525 | 3 | if (pszPOSTURL) |
526 | 3 | osPostTransactionURL = pszPOSTURL; |
527 | 3 | } |
528 | | |
529 | 26 | psChild = psChild->psChild; |
530 | 129 | while (psChild) |
531 | 129 | { |
532 | 129 | if (psChild->eType == CXT_Element && |
533 | 101 | strcmp(psChild->pszValue, "Parameter") == 0 && |
534 | 51 | strcmp(CPLGetXMLValue(psChild, "name", ""), "idgen") == 0) |
535 | 26 | { |
536 | 26 | break; |
537 | 26 | } |
538 | 103 | psChild = psChild->psNext; |
539 | 103 | } |
540 | 26 | if (!psChild) |
541 | 0 | { |
542 | 0 | papszIdGenMethods = CSLAddString(nullptr, "GenerateNew"); |
543 | 0 | return true; |
544 | 0 | } |
545 | | |
546 | 26 | psChild = psChild->psChild; |
547 | 107 | while (psChild) |
548 | 81 | { |
549 | 81 | if (psChild->eType == CXT_Element && |
550 | 55 | strcmp(psChild->pszValue, "Value") == 0) |
551 | 55 | { |
552 | 55 | const CPLXMLNode *psChild2 = psChild->psChild; |
553 | 110 | while (psChild2) |
554 | 55 | { |
555 | 55 | if (psChild2->eType == CXT_Text) |
556 | 55 | { |
557 | 55 | papszIdGenMethods = |
558 | 55 | CSLAddString(papszIdGenMethods, psChild2->pszValue); |
559 | 55 | } |
560 | 55 | psChild2 = psChild2->psNext; |
561 | 55 | } |
562 | 55 | } |
563 | 81 | psChild = psChild->psNext; |
564 | 81 | } |
565 | | |
566 | 26 | return true; |
567 | 26 | } |
568 | | |
569 | | /************************************************************************/ |
570 | | /* DetectSupportPagingWFS2() */ |
571 | | /************************************************************************/ |
572 | | |
573 | | bool OGRWFSDataSource::DetectSupportPagingWFS2( |
574 | | const CPLXMLNode *psGetCapabilitiesResponse, |
575 | | const CPLXMLNode *psConfigurationRoot) |
576 | 2 | { |
577 | 2 | const char *pszPagingAllowed = CPLGetConfigOption( |
578 | 2 | "OGR_WFS_PAGING_ALLOWED", |
579 | 2 | CPLGetXMLValue(psConfigurationRoot, "PagingAllowed", nullptr)); |
580 | 2 | if (pszPagingAllowed != nullptr && !CPLTestBool(pszPagingAllowed)) |
581 | 0 | return false; |
582 | | |
583 | 2 | const CPLXMLNode *psOperationsMetadata = |
584 | 2 | CPLGetXMLNode(psGetCapabilitiesResponse, "OperationsMetadata"); |
585 | 2 | if (!psOperationsMetadata) |
586 | 0 | { |
587 | 0 | return false; |
588 | 0 | } |
589 | | |
590 | 2 | const CPLXMLNode *psChild = psOperationsMetadata->psChild; |
591 | 10 | while (psChild) |
592 | 8 | { |
593 | 8 | if (psChild->eType == CXT_Element && |
594 | 8 | strcmp(psChild->pszValue, "Constraint") == 0 && |
595 | 0 | strcmp(CPLGetXMLValue(psChild, "name", ""), |
596 | 0 | "ImplementsResultPaging") == 0) |
597 | 0 | { |
598 | 0 | if (!EQUAL(CPLGetXMLValue(psChild, "DefaultValue", ""), "TRUE")) |
599 | 0 | { |
600 | 0 | psChild = nullptr; |
601 | 0 | break; |
602 | 0 | } |
603 | 0 | break; |
604 | 0 | } |
605 | 8 | psChild = psChild->psNext; |
606 | 8 | } |
607 | 2 | if (!psChild) |
608 | 2 | { |
609 | 2 | CPLDebug("WFS", "No paging support"); |
610 | 2 | return false; |
611 | 2 | } |
612 | | |
613 | 0 | psChild = psOperationsMetadata->psChild; |
614 | 0 | while (psChild) |
615 | 0 | { |
616 | 0 | if (psChild->eType == CXT_Element && |
617 | 0 | strcmp(psChild->pszValue, "Operation") == 0 && |
618 | 0 | strcmp(CPLGetXMLValue(psChild, "name", ""), "GetFeature") == 0) |
619 | 0 | { |
620 | 0 | break; |
621 | 0 | } |
622 | 0 | psChild = psChild->psNext; |
623 | 0 | } |
624 | |
|
625 | 0 | const char *pszPageSize = CPLGetConfigOption( |
626 | 0 | "OGR_WFS_PAGE_SIZE", |
627 | 0 | CPLGetXMLValue(psConfigurationRoot, "PageSize", nullptr)); |
628 | 0 | if (psChild && pszPageSize == nullptr) |
629 | 0 | { |
630 | 0 | psChild = psChild->psChild; |
631 | 0 | while (psChild) |
632 | 0 | { |
633 | 0 | if (psChild->eType == CXT_Element && |
634 | 0 | strcmp(psChild->pszValue, "Constraint") == 0 && |
635 | 0 | strcmp(CPLGetXMLValue(psChild, "name", ""), "CountDefault") == |
636 | 0 | 0) |
637 | 0 | { |
638 | 0 | int nVal = atoi(CPLGetXMLValue(psChild, "DefaultValue", "0")); |
639 | 0 | if (nVal > 0) |
640 | 0 | { |
641 | 0 | nPageSize = nVal; |
642 | 0 | const int nPageSizeURL = |
643 | 0 | atoi(CPLURLGetValue(osBaseURL, "COUNT")); |
644 | 0 | if (nPageSizeURL > 0 && nPageSizeURL < nPageSize) |
645 | 0 | { |
646 | 0 | nPageSize = nPageSizeURL; |
647 | 0 | } |
648 | 0 | } |
649 | |
|
650 | 0 | break; |
651 | 0 | } |
652 | 0 | psChild = psChild->psNext; |
653 | 0 | } |
654 | 0 | } |
655 | 0 | if (pszPageSize != nullptr) |
656 | 0 | { |
657 | 0 | nPageSize = atoi(pszPageSize); |
658 | 0 | if (nPageSize <= 0) |
659 | 0 | nPageSize = DEFAULT_PAGE_SIZE; |
660 | 0 | } |
661 | |
|
662 | 0 | CPLDebug("WFS", "Paging support with page size %d", nPageSize); |
663 | 0 | bPagingAllowed = true; |
664 | |
|
665 | 0 | return true; |
666 | 2 | } |
667 | | |
668 | | /************************************************************************/ |
669 | | /* DetectSupportStandardJoinsWFS2() */ |
670 | | /************************************************************************/ |
671 | | |
672 | | bool OGRWFSDataSource::DetectSupportStandardJoinsWFS2(const CPLXMLNode *psRoot) |
673 | 2 | { |
674 | 2 | const CPLXMLNode *psOperationsMetadata = |
675 | 2 | CPLGetXMLNode(psRoot, "OperationsMetadata"); |
676 | 2 | if (!psOperationsMetadata) |
677 | 0 | { |
678 | 0 | return false; |
679 | 0 | } |
680 | | |
681 | 2 | const CPLXMLNode *psChild = psOperationsMetadata->psChild; |
682 | 10 | while (psChild) |
683 | 8 | { |
684 | 8 | if (psChild->eType == CXT_Element && |
685 | 8 | strcmp(psChild->pszValue, "Constraint") == 0 && |
686 | 0 | strcmp(CPLGetXMLValue(psChild, "name", ""), |
687 | 0 | "ImplementsStandardJoins") == 0) |
688 | 0 | { |
689 | 0 | if (!EQUAL(CPLGetXMLValue(psChild, "DefaultValue", ""), "TRUE")) |
690 | 0 | { |
691 | 0 | psChild = nullptr; |
692 | 0 | break; |
693 | 0 | } |
694 | 0 | break; |
695 | 0 | } |
696 | 8 | psChild = psChild->psNext; |
697 | 8 | } |
698 | 2 | if (!psChild) |
699 | 2 | { |
700 | 2 | CPLDebug("WFS", "No ImplementsStandardJoins support"); |
701 | 2 | return false; |
702 | 2 | } |
703 | 0 | bStandardJoinsWFS2 = true; |
704 | 0 | return true; |
705 | 2 | } |
706 | | |
707 | | /************************************************************************/ |
708 | | /* FindComparisonOperator() */ |
709 | | /************************************************************************/ |
710 | | |
711 | | static bool FindComparisonOperator(const CPLXMLNode *psNode, const char *pszVal) |
712 | 248 | { |
713 | 248 | const CPLXMLNode *psChild = psNode->psChild; |
714 | 1.22k | while (psChild) |
715 | 1.21k | { |
716 | 1.21k | if (psChild->eType == CXT_Element && |
717 | 1.19k | strcmp(psChild->pszValue, "ComparisonOperator") == 0) |
718 | 1.19k | { |
719 | 1.19k | if (strcmp(CPLGetXMLValue(psChild, nullptr, ""), pszVal) == 0) |
720 | 242 | return true; |
721 | | |
722 | | /* For WFS 2.0.0 */ |
723 | 955 | const char *pszName = CPLGetXMLValue(psChild, "name", nullptr); |
724 | 955 | if (pszName != nullptr && STARTS_WITH(pszName, "PropertyIs") && |
725 | 0 | strcmp(pszName + 10, pszVal) == 0) |
726 | 0 | return true; |
727 | 955 | } |
728 | 972 | psChild = psChild->psNext; |
729 | 972 | } |
730 | 6 | return false; |
731 | 248 | } |
732 | | |
733 | | /************************************************************************/ |
734 | | /* LoadFromFile() */ |
735 | | /************************************************************************/ |
736 | | |
737 | | CPLXMLNode *OGRWFSDataSource::LoadFromFile(const char *pszFilename) |
738 | 2.76k | { |
739 | 2.76k | VSIStatBufL sStatBuf; |
740 | 2.76k | if (VSIStatExL(pszFilename, &sStatBuf, |
741 | 2.76k | VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG) != 0 || |
742 | 2.67k | VSI_ISDIR(sStatBuf.st_mode)) |
743 | 92 | return nullptr; |
744 | | |
745 | 2.67k | VSILFILE *fp = VSIFOpenL(pszFilename, "rb"); |
746 | | |
747 | 2.67k | if (fp == nullptr) |
748 | 0 | return nullptr; |
749 | | |
750 | | /* -------------------------------------------------------------------- */ |
751 | | /* It is the right file, now load the full XML definition. */ |
752 | | /* -------------------------------------------------------------------- */ |
753 | 2.67k | VSIFSeekL(fp, 0, SEEK_END); |
754 | 2.67k | const auto nLenLarge = VSIFTellL(fp); |
755 | 2.67k | VSIFSeekL(fp, 0, SEEK_SET); |
756 | 2.67k | if (nLenLarge > 100 * 1024 * 1024) |
757 | 0 | { |
758 | 0 | VSIFCloseL(fp); |
759 | 0 | return nullptr; |
760 | 0 | } |
761 | 2.67k | const int nLen = static_cast<int>(nLenLarge); |
762 | | |
763 | 2.67k | char *pszXML = static_cast<char *>(VSI_MALLOC_VERBOSE(nLen + 1)); |
764 | 2.67k | if (pszXML == nullptr) |
765 | 0 | { |
766 | 0 | VSIFCloseL(fp); |
767 | 0 | return nullptr; |
768 | 0 | } |
769 | 2.67k | pszXML[nLen] = '\0'; |
770 | 2.67k | if (static_cast<int>(VSIFReadL(pszXML, 1, nLen, fp)) != nLen) |
771 | 0 | { |
772 | 0 | CPLFree(pszXML); |
773 | 0 | VSIFCloseL(fp); |
774 | |
|
775 | 0 | return nullptr; |
776 | 0 | } |
777 | 2.67k | VSIFCloseL(fp); |
778 | | |
779 | 2.67k | if (!STARTS_WITH_CI(pszXML, "<OGRWFSDataSource>") && |
780 | 2.56k | strstr(pszXML, "<WFS_Capabilities") == nullptr && |
781 | 1.35k | strstr(pszXML, "<wfs:WFS_Capabilities") == nullptr) |
782 | 0 | { |
783 | 0 | return nullptr; |
784 | 0 | } |
785 | | |
786 | 2.67k | if (strstr(pszXML, "CubeWerx")) |
787 | 82 | { |
788 | | /* At least true for CubeWerx Suite 4.15.1 */ |
789 | 82 | bUseFeatureId = true; |
790 | 82 | } |
791 | 2.59k | else if (strstr(pszXML, "deegree")) |
792 | 24 | { |
793 | 24 | bGmlObjectIdNeedsGMLPrefix = true; |
794 | 24 | } |
795 | | |
796 | 2.67k | CPLXMLNode *psXML = CPLParseXMLString(pszXML); |
797 | 2.67k | CPLFree(pszXML); |
798 | | |
799 | 2.67k | return psXML; |
800 | 2.67k | } |
801 | | |
802 | | /************************************************************************/ |
803 | | /* SendGetCapabilities() */ |
804 | | /************************************************************************/ |
805 | | |
806 | | CPLHTTPResult *OGRWFSDataSource::SendGetCapabilities(const char *pszBaseURL, |
807 | | CPLString &osTypeName) |
808 | 109 | { |
809 | 109 | CPLString osURL(pszBaseURL); |
810 | | |
811 | 109 | osURL = CPLURLAddKVP(osURL, "SERVICE", "WFS"); |
812 | 109 | osURL = CPLURLAddKVP(osURL, "REQUEST", "GetCapabilities"); |
813 | 109 | osTypeName = CPLURLGetValue(osURL, "TYPENAME"); |
814 | 109 | if (osTypeName.empty()) |
815 | 109 | osTypeName = CPLURLGetValue(osURL, "TYPENAMES"); |
816 | 109 | osURL = CPLURLAddKVP(osURL, "TYPENAME", nullptr); |
817 | 109 | osURL = CPLURLAddKVP(osURL, "TYPENAMES", nullptr); |
818 | 109 | osURL = CPLURLAddKVP(osURL, "FILTER", nullptr); |
819 | 109 | osURL = CPLURLAddKVP(osURL, "PROPERTYNAME", nullptr); |
820 | 109 | osURL = CPLURLAddKVP(osURL, "MAXFEATURES", nullptr); |
821 | 109 | osURL = CPLURLAddKVP(osURL, "OUTPUTFORMAT", nullptr); |
822 | | |
823 | 109 | CPLDebug("WFS", "%s", osURL.c_str()); |
824 | | |
825 | 109 | CPLHTTPResult *psResult = HTTPFetch(osURL, nullptr); |
826 | 109 | if (psResult == nullptr) |
827 | 109 | { |
828 | 109 | return nullptr; |
829 | 109 | } |
830 | | |
831 | 0 | if (strstr(reinterpret_cast<const char *>(psResult->pabyData), |
832 | 0 | "<ServiceExceptionReport") != nullptr || |
833 | 0 | strstr(reinterpret_cast<const char *>(psResult->pabyData), |
834 | 0 | "<ows:ExceptionReport") != nullptr || |
835 | 0 | strstr(reinterpret_cast<const char *>(psResult->pabyData), |
836 | 0 | "<ExceptionReport") != nullptr) |
837 | 0 | { |
838 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Error returned by server : %s", |
839 | 0 | psResult->pabyData); |
840 | 0 | CPLHTTPDestroyResult(psResult); |
841 | 0 | return nullptr; |
842 | 0 | } |
843 | | |
844 | 0 | return psResult; |
845 | 0 | } |
846 | | |
847 | | /************************************************************************/ |
848 | | /* Open() */ |
849 | | /************************************************************************/ |
850 | | |
851 | | int OGRWFSDataSource::Open(const char *pszFilename, int bUpdateIn, |
852 | | CSLConstList papszOpenOptionsIn) |
853 | | |
854 | 2.76k | { |
855 | 2.76k | bUpdate = CPL_TO_BOOL(bUpdateIn); |
856 | | |
857 | 2.76k | const CPLXMLNode *psWFSCapabilities = nullptr; |
858 | 2.76k | CPLXMLNode *psXML = nullptr; |
859 | 2.76k | if (!STARTS_WITH(pszFilename, "http://") && |
860 | 2.76k | !STARTS_WITH(pszFilename, "https://")) |
861 | 2.76k | { |
862 | 2.76k | psXML = LoadFromFile(pszFilename); |
863 | 2.76k | } |
864 | 2.76k | CPLString osTypeName; |
865 | 2.76k | const char *pszBaseURL = nullptr; |
866 | | |
867 | 2.76k | bEmptyAsNull = CPLFetchBool(papszOpenOptionsIn, "EMPTY_AS_NULL", true); |
868 | | |
869 | 2.76k | const CPLXMLNode *psConfigurationRoot = nullptr; |
870 | | |
871 | 2.76k | if (psXML == nullptr) |
872 | 1.55k | { |
873 | 1.55k | if (!STARTS_WITH_CI(pszFilename, "WFS:") && |
874 | 1.46k | !STARTS_WITH(pszFilename, "http://") && |
875 | 1.46k | !STARTS_WITH(pszFilename, "https://") && |
876 | 1.46k | FindSubStringInsensitive(pszFilename, "SERVICE=WFS") == nullptr) |
877 | 1.41k | { |
878 | 1.41k | return FALSE; |
879 | 1.41k | } |
880 | | |
881 | 142 | pszBaseURL = CSLFetchNameValue(papszOpenOptionsIn, "URL"); |
882 | 142 | if (pszBaseURL == nullptr) |
883 | 142 | { |
884 | 142 | pszBaseURL = pszFilename; |
885 | 142 | if (STARTS_WITH_CI(pszFilename, "WFS:")) |
886 | 92 | pszBaseURL += strlen("WFS:"); |
887 | 142 | } |
888 | | |
889 | 142 | osBaseURL = pszBaseURL; |
890 | | |
891 | 142 | if (!STARTS_WITH(pszBaseURL, "http://") && |
892 | 142 | !STARTS_WITH(pszBaseURL, "https://") && |
893 | 142 | !STARTS_WITH(pszBaseURL, "/vsimem/")) |
894 | 39 | return FALSE; |
895 | | |
896 | 103 | CPLString strOriginalTypeName = ""; |
897 | 103 | CPLHTTPResult *psResult = |
898 | 103 | SendGetCapabilities(pszBaseURL, strOriginalTypeName); |
899 | 103 | osTypeName = WFS_DecodeURL(strOriginalTypeName); |
900 | 103 | if (psResult == nullptr) |
901 | 103 | { |
902 | 103 | return FALSE; |
903 | 103 | } |
904 | | |
905 | 0 | if (strstr(reinterpret_cast<const char *>(psResult->pabyData), |
906 | 0 | "CubeWerx")) |
907 | 0 | { |
908 | | /* At least true for CubeWerx Suite 4.15.1 */ |
909 | 0 | bUseFeatureId = true; |
910 | 0 | } |
911 | 0 | else if (strstr(reinterpret_cast<const char *>(psResult->pabyData), |
912 | 0 | "deegree")) |
913 | 0 | { |
914 | 0 | bGmlObjectIdNeedsGMLPrefix = true; |
915 | 0 | } |
916 | |
|
917 | 0 | psXML = CPLParseXMLString( |
918 | 0 | reinterpret_cast<const char *>(psResult->pabyData)); |
919 | 0 | if (psXML == nullptr) |
920 | 0 | { |
921 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Invalid XML content : %s", |
922 | 0 | psResult->pabyData); |
923 | 0 | CPLHTTPDestroyResult(psResult); |
924 | 0 | return FALSE; |
925 | 0 | } |
926 | 0 | osGetCapabilities = reinterpret_cast<const char *>(psResult->pabyData); |
927 | |
|
928 | 0 | CPLHTTPDestroyResult(psResult); |
929 | 0 | } |
930 | 1.21k | else if (WFSFindNode(psXML, "OGRWFSDataSource") == nullptr && |
931 | 1.17k | WFSFindNode(psXML, "WFS_Capabilities") != nullptr) |
932 | 1.04k | { |
933 | | /* This is directly the Capabilities document */ |
934 | 1.04k | char *pszXML = |
935 | 1.04k | CPLSerializeXMLTree(WFSFindNode(psXML, "WFS_Capabilities")); |
936 | 1.04k | osGetCapabilities = pszXML; |
937 | 1.04k | CPLFree(pszXML); |
938 | 1.04k | } |
939 | 169 | else |
940 | 169 | { |
941 | 169 | psConfigurationRoot = WFSFindNode(psXML, "OGRWFSDataSource"); |
942 | 169 | if (psConfigurationRoot == nullptr) |
943 | 134 | { |
944 | 134 | CPLError(CE_Failure, CPLE_AppDefined, |
945 | 134 | "Cannot find <OGRWFSDataSource>"); |
946 | 134 | CPLDestroyXMLNode(psXML); |
947 | 134 | return FALSE; |
948 | 134 | } |
949 | | |
950 | 35 | pszBaseURL = CPLGetXMLValue(psConfigurationRoot, "URL", nullptr); |
951 | 35 | if (pszBaseURL == nullptr) |
952 | 2 | { |
953 | 2 | CPLError(CE_Failure, CPLE_AppDefined, "Cannot find <URL>"); |
954 | 2 | CPLDestroyXMLNode(psXML); |
955 | 2 | return FALSE; |
956 | 2 | } |
957 | 33 | osBaseURL = pszBaseURL; |
958 | | |
959 | | /* -------------------------------------------------------------------- |
960 | | */ |
961 | | /* Capture other parameters. */ |
962 | | /* -------------------------------------------------------------------- |
963 | | */ |
964 | 33 | const char *pszParam = |
965 | 33 | CPLGetXMLValue(psConfigurationRoot, "Timeout", nullptr); |
966 | 33 | if (pszParam) |
967 | 0 | papszHttpOptions = |
968 | 0 | CSLSetNameValue(papszHttpOptions, "TIMEOUT", pszParam); |
969 | | |
970 | 33 | pszParam = CPLGetXMLValue(psConfigurationRoot, "HTTPAUTH", nullptr); |
971 | 33 | if (pszParam) |
972 | 0 | papszHttpOptions = |
973 | 0 | CSLSetNameValue(papszHttpOptions, "HTTPAUTH", pszParam); |
974 | | |
975 | 33 | pszParam = CPLGetXMLValue(psConfigurationRoot, "USERPWD", nullptr); |
976 | 33 | if (pszParam) |
977 | 0 | papszHttpOptions = |
978 | 0 | CSLSetNameValue(papszHttpOptions, "USERPWD", pszParam); |
979 | | |
980 | 33 | pszParam = CPLGetXMLValue(psConfigurationRoot, "COOKIE", nullptr); |
981 | 33 | if (pszParam) |
982 | 0 | papszHttpOptions = |
983 | 0 | CSLSetNameValue(papszHttpOptions, "COOKIE", pszParam); |
984 | | |
985 | 33 | pszParam = CPLGetXMLValue(psConfigurationRoot, "Version", nullptr); |
986 | 33 | if (pszParam) |
987 | 0 | osVersion = pszParam; |
988 | | |
989 | 33 | pszParam = |
990 | 33 | CPLGetXMLValue(psConfigurationRoot, "BaseStartIndex", nullptr); |
991 | 33 | if (pszParam) |
992 | 0 | nBaseStartIndex = atoi(pszParam); |
993 | | |
994 | 33 | CPLString strOriginalTypeName = CPLURLGetValue(pszBaseURL, "TYPENAME"); |
995 | 33 | if (strOriginalTypeName.empty()) |
996 | 33 | strOriginalTypeName = CPLURLGetValue(pszBaseURL, "TYPENAMES"); |
997 | 33 | osTypeName = WFS_DecodeURL(strOriginalTypeName); |
998 | | |
999 | 33 | psWFSCapabilities = |
1000 | 33 | WFSFindNode(psConfigurationRoot, "WFS_Capabilities"); |
1001 | 33 | if (psWFSCapabilities == nullptr) |
1002 | 6 | { |
1003 | 6 | CPLHTTPResult *psResult = |
1004 | 6 | SendGetCapabilities(pszBaseURL, strOriginalTypeName); |
1005 | 6 | if (psResult == nullptr) |
1006 | 6 | { |
1007 | 6 | CPLDestroyXMLNode(psXML); |
1008 | 6 | return FALSE; |
1009 | 6 | } |
1010 | | |
1011 | 0 | CPLXMLNode *psXML2 = CPLParseXMLString( |
1012 | 0 | reinterpret_cast<const char *>(psResult->pabyData)); |
1013 | 0 | if (psXML2 == nullptr) |
1014 | 0 | { |
1015 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
1016 | 0 | "Invalid XML content : %s", psResult->pabyData); |
1017 | 0 | CPLHTTPDestroyResult(psResult); |
1018 | 0 | CPLDestroyXMLNode(psXML); |
1019 | 0 | return FALSE; |
1020 | 0 | } |
1021 | | |
1022 | 0 | CPLHTTPDestroyResult(psResult); |
1023 | |
|
1024 | 0 | psWFSCapabilities = WFSFindNode(psXML2, "WFS_Capabilities"); |
1025 | 0 | if (psWFSCapabilities == nullptr) |
1026 | 0 | { |
1027 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
1028 | 0 | "Cannot find <WFS_Capabilities>"); |
1029 | 0 | CPLDestroyXMLNode(psXML); |
1030 | 0 | CPLDestroyXMLNode(psXML2); |
1031 | 0 | return FALSE; |
1032 | 0 | } |
1033 | | |
1034 | 0 | CPLAddXMLChild(psXML, CPLCloneXMLTree(psWFSCapabilities)); |
1035 | |
|
1036 | 0 | const bool bOK = |
1037 | 0 | CPL_TO_BOOL(CPLSerializeXMLTreeToFile(psXML, pszFilename)); |
1038 | |
|
1039 | 0 | CPLDestroyXMLNode(psXML); |
1040 | 0 | CPLDestroyXMLNode(psXML2); |
1041 | |
|
1042 | 0 | if (bOK) |
1043 | 0 | return Open(pszFilename, bUpdate, papszOpenOptionsIn); |
1044 | | |
1045 | 0 | return FALSE; |
1046 | 0 | } |
1047 | 27 | else |
1048 | 27 | { |
1049 | 27 | psFileXML = psXML; |
1050 | | |
1051 | | /* To avoid to have nodes after WFSCapabilities */ |
1052 | 27 | CPLXMLNode *psAfterWFSCapabilities = psWFSCapabilities->psNext; |
1053 | 27 | const_cast<CPLXMLNode *>(psWFSCapabilities)->psNext = nullptr; |
1054 | 27 | char *pszXML = CPLSerializeXMLTree(psWFSCapabilities); |
1055 | 27 | const_cast<CPLXMLNode *>(psWFSCapabilities)->psNext = |
1056 | 27 | psAfterWFSCapabilities; |
1057 | 27 | osGetCapabilities = pszXML; |
1058 | 27 | CPLFree(pszXML); |
1059 | 27 | } |
1060 | 33 | } |
1061 | | |
1062 | 1.07k | bInvertAxisOrderIfLatLong = CPLTestBool(CSLFetchNameValueDef( |
1063 | 1.07k | papszOpenOptionsIn, "INVERT_AXIS_ORDER_IF_LAT_LONG", |
1064 | 1.07k | CPLGetConfigOption("GML_INVERT_AXIS_ORDER_IF_LAT_LONG", "YES"))); |
1065 | 1.07k | osConsiderEPSGAsURN = CSLFetchNameValueDef( |
1066 | 1.07k | papszOpenOptionsIn, "CONSIDER_EPSG_AS_URN", |
1067 | 1.07k | CPLGetConfigOption("GML_CONSIDER_EPSG_AS_URN", "AUTO")); |
1068 | 1.07k | bExposeGMLId = CPLTestBool( |
1069 | 1.07k | CSLFetchNameValueDef(papszOpenOptionsIn, "EXPOSE_GML_ID", |
1070 | 1.07k | CPLGetConfigOption("GML_EXPOSE_GML_ID", "YES"))); |
1071 | | |
1072 | 1.07k | CPLXMLNode *psStrippedXML = CPLCloneXMLTree(psXML); |
1073 | 1.07k | CPLStripXMLNamespace(psStrippedXML, nullptr, TRUE); |
1074 | 1.07k | psWFSCapabilities = CPLGetXMLNode(psStrippedXML, "=WFS_Capabilities"); |
1075 | 1.07k | if (psWFSCapabilities == nullptr) |
1076 | 28 | { |
1077 | 28 | psWFSCapabilities = |
1078 | 28 | CPLGetXMLNode(psStrippedXML, "=OGRWFSDataSource.WFS_Capabilities"); |
1079 | 28 | } |
1080 | 1.07k | if (psWFSCapabilities == nullptr) |
1081 | 1 | { |
1082 | 1 | CPLError(CE_Failure, CPLE_AppDefined, "Cannot find <WFS_Capabilities>"); |
1083 | 1 | if (!psFileXML) |
1084 | 1 | CPLDestroyXMLNode(psXML); |
1085 | 1 | CPLDestroyXMLNode(psStrippedXML); |
1086 | 1 | return FALSE; |
1087 | 1 | } |
1088 | | |
1089 | 1.07k | if (pszBaseURL == nullptr) |
1090 | 1.04k | { |
1091 | | /* This is directly the Capabilities document */ |
1092 | 1.04k | pszBaseURL = CPLGetXMLValue( |
1093 | 1.04k | psWFSCapabilities, "OperationsMetadata.Operation.DCP.HTTP.Get.href", |
1094 | 1.04k | nullptr); |
1095 | 1.04k | if (pszBaseURL == nullptr) /* WFS 1.0.0 variant */ |
1096 | 818 | pszBaseURL = CPLGetXMLValue(psWFSCapabilities, |
1097 | 818 | "Capability.Request.GetCapabilities." |
1098 | 818 | "DCPType.HTTP.Get.onlineResource", |
1099 | 818 | nullptr); |
1100 | | |
1101 | 1.04k | if (pszBaseURL == nullptr) |
1102 | 818 | { |
1103 | 818 | CPLError(CE_Failure, CPLE_AppDefined, "Cannot find base URL"); |
1104 | 818 | if (!psFileXML) |
1105 | 818 | CPLDestroyXMLNode(psXML); |
1106 | 818 | CPLDestroyXMLNode(psStrippedXML); |
1107 | 818 | return FALSE; |
1108 | 818 | } |
1109 | | |
1110 | 225 | osBaseURL = pszBaseURL; |
1111 | 225 | } |
1112 | | |
1113 | 252 | pszBaseURL = nullptr; |
1114 | | |
1115 | 252 | for (const auto &sMetadata : asMetadata) |
1116 | 1.26k | { |
1117 | 1.26k | const char *pszVal = |
1118 | 1.26k | CPLGetXMLValue(psWFSCapabilities, sMetadata.pszPath, nullptr); |
1119 | 1.26k | if (pszVal) |
1120 | 65 | SetMetadataItem(sMetadata.pszMDI, pszVal); |
1121 | 1.26k | } |
1122 | | |
1123 | 252 | if (osVersion.empty()) |
1124 | 252 | osVersion = CPLGetXMLValue(psWFSCapabilities, "version", "1.0.0"); |
1125 | 252 | if (strcmp(osVersion.c_str(), "1.0.0") == 0) |
1126 | 222 | { |
1127 | 222 | bUseFeatureId = true; |
1128 | 222 | } |
1129 | 30 | else |
1130 | 30 | { |
1131 | | /* Some servers happen to support RESULTTYPE=hits in 1.0.0, but there */ |
1132 | | /* is no way to advertises this */ |
1133 | 30 | if (atoi(osVersion) >= 2) |
1134 | 2 | bGetFeatureSupportHits = true; /* WFS >= 2.0.0 supports hits */ |
1135 | 28 | else |
1136 | 28 | bGetFeatureSupportHits = |
1137 | 28 | DetectIfGetFeatureSupportHits(psWFSCapabilities); |
1138 | 30 | bRequiresEnvelopeSpatialFilter = |
1139 | 30 | DetectRequiresEnvelopeSpatialFilter(psWFSCapabilities); |
1140 | 30 | } |
1141 | | |
1142 | 252 | bool bRequestJSON = false; |
1143 | | |
1144 | 252 | if (atoi(osVersion) >= 2) |
1145 | 2 | { |
1146 | 2 | CPLString osMaxFeatures = CPLURLGetValue(osBaseURL, "COUNT"); |
1147 | | /* Ok, people are used to MAXFEATURES, so be nice to recognize it if it |
1148 | | * is used for WFS 2.0 ... */ |
1149 | 2 | if (osMaxFeatures.empty()) |
1150 | 2 | { |
1151 | 2 | osMaxFeatures = CPLURLGetValue(osBaseURL, "MAXFEATURES"); |
1152 | 2 | if (!osMaxFeatures.empty() && |
1153 | 0 | CPLTestBool( |
1154 | 0 | CPLGetConfigOption("OGR_WFS_FIX_MAXFEATURES", "YES"))) |
1155 | 0 | { |
1156 | 0 | CPLDebug("WFS", "MAXFEATURES wrongly used for WFS 2.0. Using " |
1157 | 0 | "COUNT instead"); |
1158 | 0 | osBaseURL = CPLURLAddKVP(osBaseURL, "MAXFEATURES", nullptr); |
1159 | 0 | osBaseURL = CPLURLAddKVP(osBaseURL, "COUNT", osMaxFeatures); |
1160 | 0 | } |
1161 | 2 | } |
1162 | | |
1163 | 2 | DetectSupportPagingWFS2(psWFSCapabilities, psConfigurationRoot); |
1164 | 2 | DetectSupportStandardJoinsWFS2(psWFSCapabilities); |
1165 | | |
1166 | 2 | const CPLXMLNode *psOperationsMetadata = |
1167 | 2 | CPLGetXMLNode(psWFSCapabilities, "OperationsMetadata"); |
1168 | 2 | auto poGMLDriver = GDALDriver::FromHandle(GDALGetDriverByName("GML")); |
1169 | 2 | if (psOperationsMetadata && !(poGMLDriver && poGMLDriver->pfnOpen) && |
1170 | 0 | CPLURLGetValue(osBaseURL, "OUTPUTFORMAT").empty()) |
1171 | 0 | { |
1172 | 0 | const CPLXMLNode *psChild = psOperationsMetadata->psChild; |
1173 | 0 | while (psChild) |
1174 | 0 | { |
1175 | 0 | if (psChild->eType == CXT_Element && |
1176 | 0 | strcmp(psChild->pszValue, "Operation") == 0 && |
1177 | 0 | strcmp(CPLGetXMLValue(psChild, "name", ""), "GetFeature") == |
1178 | 0 | 0) |
1179 | 0 | { |
1180 | 0 | break; |
1181 | 0 | } |
1182 | 0 | psChild = psChild->psNext; |
1183 | 0 | } |
1184 | 0 | if (psChild) |
1185 | 0 | { |
1186 | 0 | psChild = psChild->psChild; |
1187 | 0 | while (psChild) |
1188 | 0 | { |
1189 | 0 | if (psChild->eType == CXT_Element && |
1190 | 0 | strcmp(psChild->pszValue, "Parameter") == 0 && |
1191 | 0 | strcmp(CPLGetXMLValue(psChild, "name", ""), |
1192 | 0 | "outputFormat") == 0) |
1193 | 0 | { |
1194 | 0 | break; |
1195 | 0 | } |
1196 | 0 | psChild = psChild->psNext; |
1197 | 0 | } |
1198 | 0 | if (psChild) |
1199 | 0 | { |
1200 | 0 | const CPLXMLNode *psAllowedValues = |
1201 | 0 | CPLGetXMLNode(psChild, "AllowedValues"); |
1202 | 0 | if (psAllowedValues) |
1203 | 0 | { |
1204 | 0 | psChild = psAllowedValues->psChild; |
1205 | 0 | while (psChild) |
1206 | 0 | { |
1207 | 0 | if (psChild->eType == CXT_Element && |
1208 | 0 | strcmp(psChild->pszValue, "Value") == 0 && |
1209 | 0 | psChild->psChild && |
1210 | 0 | psChild->psChild->eType == CXT_Text) |
1211 | 0 | { |
1212 | 0 | CPLDebug("WFS", "Available output format: %s", |
1213 | 0 | psChild->psChild->pszValue); |
1214 | 0 | if (strcmp(psChild->psChild->pszValue, |
1215 | 0 | "json") == 0) |
1216 | 0 | { |
1217 | 0 | bRequestJSON = true; |
1218 | 0 | } |
1219 | 0 | } |
1220 | 0 | psChild = psChild->psNext; |
1221 | 0 | } |
1222 | 0 | } |
1223 | 0 | } |
1224 | 0 | } |
1225 | 0 | } |
1226 | 2 | } |
1227 | | |
1228 | 252 | DetectTransactionSupport(psWFSCapabilities); |
1229 | | |
1230 | 252 | if (bUpdate && !bTransactionSupport) |
1231 | 0 | { |
1232 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
1233 | 0 | "Server is read-only WFS; no WFS-T feature advertized"); |
1234 | 0 | if (!psFileXML) |
1235 | 0 | CPLDestroyXMLNode(psXML); |
1236 | 0 | CPLDestroyXMLNode(psStrippedXML); |
1237 | 0 | return FALSE; |
1238 | 0 | } |
1239 | | |
1240 | 252 | const CPLXMLNode *psFilterCap = CPLGetXMLNode( |
1241 | 252 | psWFSCapabilities, "Filter_Capabilities.Scalar_Capabilities"); |
1242 | 252 | if (psFilterCap) |
1243 | 31 | { |
1244 | 31 | bHasMinOperators = |
1245 | 31 | CPLGetXMLNode(psFilterCap, "LogicalOperators") != nullptr || |
1246 | 1 | CPLGetXMLNode(psFilterCap, "Logical_Operators") != nullptr; |
1247 | 31 | if (CPLGetXMLNode(psFilterCap, "ComparisonOperators")) |
1248 | 31 | psFilterCap = CPLGetXMLNode(psFilterCap, "ComparisonOperators"); |
1249 | 0 | else if (CPLGetXMLNode(psFilterCap, "Comparison_Operators")) |
1250 | 0 | psFilterCap = CPLGetXMLNode(psFilterCap, "Comparison_Operators"); |
1251 | 0 | else |
1252 | 0 | psFilterCap = nullptr; |
1253 | 31 | if (psFilterCap) |
1254 | 31 | { |
1255 | 31 | if (CPLGetXMLNode(psFilterCap, "Simple_Comparisons") == nullptr) |
1256 | 31 | { |
1257 | 31 | bHasMinOperators &= |
1258 | 31 | FindComparisonOperator(psFilterCap, "LessThan"); |
1259 | 31 | bHasMinOperators &= |
1260 | 31 | FindComparisonOperator(psFilterCap, "GreaterThan"); |
1261 | 31 | if (atoi(osVersion) >= 2) |
1262 | 2 | { |
1263 | 2 | bHasMinOperators &= FindComparisonOperator( |
1264 | 2 | psFilterCap, "LessThanOrEqualTo"); |
1265 | 2 | bHasMinOperators &= FindComparisonOperator( |
1266 | 2 | psFilterCap, "GreaterThanOrEqualTo"); |
1267 | 2 | } |
1268 | 29 | else |
1269 | 29 | { |
1270 | 29 | bHasMinOperators &= |
1271 | 29 | FindComparisonOperator(psFilterCap, "LessThanEqualTo"); |
1272 | 29 | bHasMinOperators &= FindComparisonOperator( |
1273 | 29 | psFilterCap, "GreaterThanEqualTo"); |
1274 | 29 | } |
1275 | 31 | bHasMinOperators &= |
1276 | 31 | FindComparisonOperator(psFilterCap, "EqualTo"); |
1277 | 31 | bHasMinOperators &= |
1278 | 31 | FindComparisonOperator(psFilterCap, "NotEqualTo"); |
1279 | 31 | bHasMinOperators &= FindComparisonOperator(psFilterCap, "Like"); |
1280 | 31 | } |
1281 | 0 | else |
1282 | 0 | { |
1283 | 0 | bHasMinOperators &= |
1284 | 0 | CPLGetXMLNode(psFilterCap, "Simple_Comparisons") != |
1285 | 0 | nullptr && |
1286 | 0 | CPLGetXMLNode(psFilterCap, "Like") != nullptr; |
1287 | 0 | } |
1288 | 31 | bHasNullCheck = |
1289 | 31 | FindComparisonOperator(psFilterCap, "NullCheck") || |
1290 | 0 | FindComparisonOperator(psFilterCap, "Null") || /* WFS 2.0.0 */ |
1291 | 0 | CPLGetXMLNode(psFilterCap, "NullCheck") != nullptr; |
1292 | 31 | } |
1293 | 0 | else |
1294 | 0 | { |
1295 | 0 | bHasMinOperators = false; |
1296 | 0 | } |
1297 | 31 | } |
1298 | | |
1299 | 252 | const CPLXMLNode *psChild = |
1300 | 252 | CPLGetXMLNode(psWFSCapabilities, "FeatureTypeList"); |
1301 | 252 | if (psChild == nullptr) |
1302 | 1 | { |
1303 | 1 | CPLError(CE_Failure, CPLE_AppDefined, "Cannot find <FeatureTypeList>"); |
1304 | 1 | if (!psFileXML) |
1305 | 1 | CPLDestroyXMLNode(psXML); |
1306 | 1 | CPLDestroyXMLNode(psStrippedXML); |
1307 | 1 | return FALSE; |
1308 | 1 | } |
1309 | | |
1310 | | /* Check if there are layer names whose identical except their prefix */ |
1311 | 251 | std::set<CPLString> aosSetLayerNames; |
1312 | 1.66k | for (CPLXMLNode *psChildIter = psChild->psChild; psChildIter != nullptr; |
1313 | 1.41k | psChildIter = psChildIter->psNext) |
1314 | 1.50k | { |
1315 | 1.50k | if (psChildIter->eType == CXT_Element && |
1316 | 1.25k | strcmp(psChildIter->pszValue, "FeatureType") == 0) |
1317 | 1.20k | { |
1318 | 1.20k | const char *l_pszName = |
1319 | 1.20k | CPLGetXMLValue(psChildIter, "Name", nullptr); |
1320 | 1.20k | if (l_pszName != nullptr) |
1321 | 1.16k | { |
1322 | 1.16k | const char *pszShortName = strchr(l_pszName, ':'); |
1323 | 1.16k | if (pszShortName) |
1324 | 421 | l_pszName = pszShortName + 1; |
1325 | 1.16k | if (aosSetLayerNames.find(l_pszName) != aosSetLayerNames.end()) |
1326 | 89 | { |
1327 | 89 | bKeepLayerNamePrefix = true; |
1328 | 89 | CPLDebug("WFS", |
1329 | 89 | "At least 2 layers have names that are only " |
1330 | 89 | "distinguishable by keeping the prefix"); |
1331 | 89 | break; |
1332 | 89 | } |
1333 | 1.07k | aosSetLayerNames.insert(l_pszName); |
1334 | 1.07k | } |
1335 | 1.20k | } |
1336 | 1.50k | } |
1337 | | |
1338 | 251 | char **papszTypenames = nullptr; |
1339 | 251 | if (!osTypeName.empty()) |
1340 | 0 | papszTypenames = |
1341 | 0 | CSLTokenizeStringComplex(osTypeName, ",", FALSE, FALSE); |
1342 | | |
1343 | 7.51k | for (CPLXMLNode *psChildIter = psChild->psChild; psChildIter != nullptr; |
1344 | 7.25k | psChildIter = psChildIter->psNext) |
1345 | 7.25k | { |
1346 | 7.25k | if (psChildIter->eType == CXT_Element && |
1347 | 7.01k | strcmp(psChildIter->pszValue, "FeatureType") == 0) |
1348 | 6.93k | { |
1349 | 6.93k | const char *pszNS = nullptr; |
1350 | 6.93k | const char *pszNSVal = nullptr; |
1351 | 6.93k | CPLXMLNode *psFeatureTypeIter = psChildIter->psChild; |
1352 | 22.2k | while (psFeatureTypeIter != nullptr) |
1353 | 15.2k | { |
1354 | 15.2k | if (psFeatureTypeIter->eType == CXT_Attribute) |
1355 | 122 | { |
1356 | 122 | pszNS = psFeatureTypeIter->pszValue; |
1357 | 122 | pszNSVal = psFeatureTypeIter->psChild->pszValue; |
1358 | 122 | } |
1359 | 15.2k | psFeatureTypeIter = psFeatureTypeIter->psNext; |
1360 | 15.2k | } |
1361 | | |
1362 | 6.93k | const char *l_pszName = |
1363 | 6.93k | CPLGetXMLValue(psChildIter, "Name", nullptr); |
1364 | 6.93k | const char *pszTitle = |
1365 | 6.93k | CPLGetXMLValue(psChildIter, "Title", nullptr); |
1366 | 6.93k | const char *pszAbstract = |
1367 | 6.93k | CPLGetXMLValue(psChildIter, "Abstract", nullptr); |
1368 | 6.93k | if (l_pszName != nullptr && |
1369 | 6.86k | (papszTypenames == nullptr || |
1370 | 0 | CSLFindString(papszTypenames, l_pszName) != -1)) |
1371 | 6.86k | { |
1372 | 6.86k | const char *pszDefaultSRS = |
1373 | 6.86k | CPLGetXMLValue(psChildIter, "DefaultSRS", nullptr); |
1374 | 6.86k | if (pszDefaultSRS == nullptr) |
1375 | 6.74k | pszDefaultSRS = CPLGetXMLValue(psChildIter, "SRS", nullptr); |
1376 | 6.86k | if (pszDefaultSRS == nullptr) |
1377 | 6.74k | pszDefaultSRS = CPLGetXMLValue(psChildIter, "DefaultCRS", |
1378 | 6.74k | nullptr); /* WFS 2.0.0 */ |
1379 | | |
1380 | 6.86k | const CPLXMLNode *psOtherSRS = |
1381 | 6.86k | CPLGetXMLNode(psChildIter, "OtherSRS"); // WFS 1.1 |
1382 | 6.86k | if (psOtherSRS == nullptr) |
1383 | 6.86k | psOtherSRS = |
1384 | 6.86k | CPLGetXMLNode(psChildIter, "OtherCRS"); // WFS 2.0 |
1385 | | |
1386 | 6.86k | const auto IsValidCRSName = [](const char *pszStr) |
1387 | 6.86k | { |
1388 | | // EPSG:404000 is a GeoServer joke to indicate an unknown SRS |
1389 | | // https://osgeo-org.atlassian.net/browse/GEOS-8993 |
1390 | 112 | return !EQUAL(pszStr, "EPSG:404000") && |
1391 | 112 | !EQUAL(pszStr, "urn:ogc:def:crs:EPSG::404000"); |
1392 | 112 | }; |
1393 | | |
1394 | 6.86k | if (pszDefaultSRS && !IsValidCRSName(pszDefaultSRS)) |
1395 | 0 | pszDefaultSRS = nullptr; |
1396 | | |
1397 | 6.86k | std::vector<std::string> aosSupportedCRSList{}; |
1398 | 6.86k | OGRLayer::GetSupportedSRSListRetType apoSupportedCRSList; |
1399 | 6.86k | if (psOtherSRS) |
1400 | 0 | { |
1401 | 0 | if (pszDefaultSRS) |
1402 | 0 | { |
1403 | 0 | auto poSRS = |
1404 | 0 | OGRSpatialReferenceRefCountedPtr::makeInstance(); |
1405 | 0 | if (poSRS->SetFromUserInput( |
1406 | 0 | pszDefaultSRS, |
1407 | 0 | OGRSpatialReference:: |
1408 | 0 | SET_FROM_USER_INPUT_LIMITATIONS_get()) == |
1409 | 0 | OGRERR_NONE) |
1410 | 0 | { |
1411 | 0 | aosSupportedCRSList.emplace_back(pszDefaultSRS); |
1412 | 0 | apoSupportedCRSList.emplace_back(std::move(poSRS)); |
1413 | 0 | } |
1414 | 0 | } |
1415 | |
|
1416 | 0 | CPLErrorStateBackuper oErrorStateBackuper( |
1417 | 0 | CPLQuietErrorHandler); |
1418 | 0 | for (const CPLXMLNode *psIter = psOtherSRS; psIter; |
1419 | 0 | psIter = psIter->psNext) |
1420 | 0 | { |
1421 | 0 | if (psIter->eType == CXT_Element) |
1422 | 0 | { |
1423 | 0 | const char *pszSRS = |
1424 | 0 | CPLGetXMLValue(psIter, "", nullptr); |
1425 | 0 | if (pszSRS && IsValidCRSName(pszSRS)) |
1426 | 0 | { |
1427 | 0 | auto poSRS = OGRSpatialReferenceRefCountedPtr:: |
1428 | 0 | makeInstance(); |
1429 | 0 | if (poSRS->SetFromUserInput( |
1430 | 0 | EQUAL(pszSRS, "CRS:84") ? "OGC:CRS84" |
1431 | 0 | : pszSRS, |
1432 | 0 | OGRSpatialReference:: |
1433 | 0 | SET_FROM_USER_INPUT_LIMITATIONS_get()) == |
1434 | 0 | OGRERR_NONE) |
1435 | 0 | { |
1436 | 0 | aosSupportedCRSList.emplace_back(pszSRS); |
1437 | 0 | apoSupportedCRSList.emplace_back( |
1438 | 0 | std::move(poSRS)); |
1439 | 0 | } |
1440 | 0 | else |
1441 | 0 | { |
1442 | 0 | CPLDebug("WFS", "Invalid CRS %s", pszSRS); |
1443 | 0 | } |
1444 | 0 | } |
1445 | 0 | } |
1446 | 0 | } |
1447 | 0 | } |
1448 | | |
1449 | 6.86k | CPLXMLNode *psOutputFormats = |
1450 | 6.86k | CPLGetXMLNode(psChildIter, "OutputFormats"); |
1451 | 6.86k | CPLString osOutputFormat; |
1452 | 6.86k | if (psOutputFormats) |
1453 | 0 | { |
1454 | 0 | std::vector<CPLString> osFormats; |
1455 | 0 | CPLXMLNode *psOutputFormatIter = psOutputFormats->psChild; |
1456 | 0 | while (psOutputFormatIter) |
1457 | 0 | { |
1458 | 0 | if (psOutputFormatIter->eType == CXT_Element && |
1459 | 0 | EQUAL(psOutputFormatIter->pszValue, "Format") && |
1460 | 0 | psOutputFormatIter->psChild != nullptr && |
1461 | 0 | psOutputFormatIter->psChild->eType == CXT_Text) |
1462 | 0 | { |
1463 | 0 | osFormats.push_back( |
1464 | 0 | psOutputFormatIter->psChild->pszValue); |
1465 | 0 | } |
1466 | 0 | psOutputFormatIter = psOutputFormatIter->psNext; |
1467 | 0 | } |
1468 | |
|
1469 | 0 | if (strcmp(osVersion.c_str(), "1.1.0") == 0 && |
1470 | 0 | !osFormats.empty()) |
1471 | 0 | { |
1472 | 0 | bool bFoundGML31 = false; |
1473 | 0 | for (size_t i = 0; i < osFormats.size(); i++) |
1474 | 0 | { |
1475 | 0 | if (strstr(osFormats[i].c_str(), "3.1") != nullptr) |
1476 | 0 | { |
1477 | 0 | bFoundGML31 = true; |
1478 | 0 | break; |
1479 | 0 | } |
1480 | 0 | } |
1481 | | |
1482 | | /* If we didn't find any mention to GML 3.1, then |
1483 | | * arbitrarily */ |
1484 | | /* use the first output format */ |
1485 | 0 | if (!bFoundGML31) |
1486 | 0 | osOutputFormat = osFormats[0].c_str(); |
1487 | 0 | } |
1488 | 0 | } |
1489 | 6.86k | if (osOutputFormat.empty() && bRequestJSON) |
1490 | 0 | osOutputFormat = "json"; |
1491 | | |
1492 | 6.86k | OGRSpatialReference *poSRS = nullptr; |
1493 | 6.86k | bool bAxisOrderAlreadyInverted = false; |
1494 | | |
1495 | | /* If a SRSNAME parameter has been encoded in the URL, use it as |
1496 | | * the SRS */ |
1497 | 6.86k | CPLString osSRSName = CPLURLGetValue(osBaseURL, "SRSNAME"); |
1498 | 6.86k | if (!osSRSName.empty()) |
1499 | 0 | { |
1500 | 0 | pszDefaultSRS = osSRSName.c_str(); |
1501 | 0 | } |
1502 | | |
1503 | 6.86k | if (pszDefaultSRS) |
1504 | 112 | { |
1505 | 112 | OGRSpatialReference oSRS; |
1506 | 112 | if (oSRS.SetFromUserInput( |
1507 | 112 | pszDefaultSRS, |
1508 | 112 | OGRSpatialReference:: |
1509 | 112 | SET_FROM_USER_INPUT_LIMITATIONS_get()) == |
1510 | 112 | OGRERR_NONE) |
1511 | 95 | { |
1512 | 95 | poSRS = oSRS.Clone(); |
1513 | 95 | poSRS->SetAxisMappingStrategy( |
1514 | 95 | bInvertAxisOrderIfLatLong |
1515 | 95 | ? OAMS_TRADITIONAL_GIS_ORDER |
1516 | 95 | : OAMS_AUTHORITY_COMPLIANT); |
1517 | 95 | if (GML_IsSRSLatLongOrder(pszDefaultSRS) && |
1518 | 62 | bInvertAxisOrderIfLatLong) |
1519 | 62 | { |
1520 | 62 | bAxisOrderAlreadyInverted = true; |
1521 | 62 | } |
1522 | 95 | } |
1523 | 112 | } |
1524 | | |
1525 | 6.86k | CPLXMLNode *psBBox = nullptr; |
1526 | 6.86k | CPLXMLNode *psLatLongBBox = nullptr; |
1527 | | /* bool bFoundBBox = false; */ |
1528 | 6.86k | double dfMinX = 0.0; |
1529 | 6.86k | double dfMinY = 0.0; |
1530 | 6.86k | double dfMaxX = 0.0; |
1531 | 6.86k | double dfMaxY = 0.0; |
1532 | 6.86k | if ((psBBox = CPLGetXMLNode(psChildIter, "WGS84BoundingBox")) != |
1533 | 6.86k | nullptr) |
1534 | 112 | { |
1535 | 112 | const char *pszLC = |
1536 | 112 | CPLGetXMLValue(psBBox, "LowerCorner", nullptr); |
1537 | 112 | const char *pszUC = |
1538 | 112 | CPLGetXMLValue(psBBox, "UpperCorner", nullptr); |
1539 | 112 | if (pszLC != nullptr && pszUC != nullptr) |
1540 | 112 | { |
1541 | 112 | CPLString osConcat(pszLC); |
1542 | 112 | osConcat += " "; |
1543 | 112 | osConcat += pszUC; |
1544 | 112 | char **papszTokens = CSLTokenizeStringComplex( |
1545 | 112 | osConcat, " ,", FALSE, FALSE); |
1546 | 112 | if (CSLCount(papszTokens) == 4) |
1547 | 112 | { |
1548 | | // bFoundBBox = true; |
1549 | 112 | dfMinX = CPLAtof(papszTokens[0]); |
1550 | 112 | dfMinY = CPLAtof(papszTokens[1]); |
1551 | 112 | dfMaxX = CPLAtof(papszTokens[2]); |
1552 | 112 | dfMaxY = CPLAtof(papszTokens[3]); |
1553 | 112 | } |
1554 | 112 | CSLDestroy(papszTokens); |
1555 | 112 | } |
1556 | 112 | } |
1557 | 6.74k | else if ((psLatLongBBox = CPLGetXMLNode( |
1558 | 6.74k | psChildIter, "LatLongBoundingBox")) != nullptr) |
1559 | 0 | { |
1560 | 0 | const char *pszMinX = |
1561 | 0 | CPLGetXMLValue(psLatLongBBox, "minx", nullptr); |
1562 | 0 | const char *pszMinY = |
1563 | 0 | CPLGetXMLValue(psLatLongBBox, "miny", nullptr); |
1564 | 0 | const char *pszMaxX = |
1565 | 0 | CPLGetXMLValue(psLatLongBBox, "maxx", nullptr); |
1566 | 0 | const char *pszMaxY = |
1567 | 0 | CPLGetXMLValue(psLatLongBBox, "maxy", nullptr); |
1568 | 0 | if (pszMinX != nullptr && pszMinY != nullptr && |
1569 | 0 | pszMaxX != nullptr && pszMaxY != nullptr) |
1570 | 0 | { |
1571 | | // bFoundBBox = true; |
1572 | 0 | dfMinX = CPLAtof(pszMinX); |
1573 | 0 | dfMinY = CPLAtof(pszMinY); |
1574 | 0 | dfMaxX = CPLAtof(pszMaxX); |
1575 | 0 | dfMaxY = CPLAtof(pszMaxY); |
1576 | 0 | } |
1577 | 0 | } |
1578 | | |
1579 | 6.86k | char *pszCSVEscaped = CPLEscapeString(l_pszName, -1, CPLES_CSV); |
1580 | 6.86k | osLayerMetadataCSV += pszCSVEscaped; |
1581 | 6.86k | CPLFree(pszCSVEscaped); |
1582 | | |
1583 | 6.86k | osLayerMetadataCSV += ","; |
1584 | 6.86k | if (pszTitle) |
1585 | 112 | { |
1586 | 112 | pszCSVEscaped = CPLEscapeString(pszTitle, -1, CPLES_CSV); |
1587 | 112 | osLayerMetadataCSV += pszCSVEscaped; |
1588 | 112 | CPLFree(pszCSVEscaped); |
1589 | 112 | } |
1590 | 6.86k | osLayerMetadataCSV += ","; |
1591 | 6.86k | if (pszAbstract) |
1592 | 42 | { |
1593 | 42 | pszCSVEscaped = CPLEscapeString(pszAbstract, -1, CPLES_CSV); |
1594 | 42 | osLayerMetadataCSV += pszCSVEscaped; |
1595 | 42 | CPLFree(pszCSVEscaped); |
1596 | 42 | } |
1597 | 6.86k | osLayerMetadataCSV += "\n"; |
1598 | | |
1599 | 6.86k | OGRWFSLayer *poLayer = |
1600 | 6.86k | new OGRWFSLayer(this, poSRS, bAxisOrderAlreadyInverted, |
1601 | 6.86k | osBaseURL, l_pszName, pszNS, pszNSVal); |
1602 | 6.86k | if (!osOutputFormat.empty()) |
1603 | 0 | poLayer->SetRequiredOutputFormat(osOutputFormat); |
1604 | | |
1605 | 6.86k | if (pszTitle) |
1606 | 112 | poLayer->SetMetadataItem("TITLE", pszTitle); |
1607 | 6.86k | if (pszAbstract) |
1608 | 42 | poLayer->SetMetadataItem("ABSTRACT", pszAbstract); |
1609 | 6.86k | CPLXMLNode *psKeywords = CPLGetXMLNode(psChildIter, "Keywords"); |
1610 | 6.86k | if (psKeywords) |
1611 | 42 | { |
1612 | 42 | int nKeywordCounter = 1; |
1613 | 42 | for (CPLXMLNode *psKeyword = psKeywords->psChild; |
1614 | 189 | psKeyword != nullptr; psKeyword = psKeyword->psNext) |
1615 | 147 | { |
1616 | 147 | if (psKeyword->eType == CXT_Element && |
1617 | 147 | psKeyword->psChild != nullptr) |
1618 | 147 | { |
1619 | 147 | poLayer->SetMetadataItem( |
1620 | 147 | CPLSPrintf("KEYWORD_%d", nKeywordCounter), |
1621 | 147 | psKeyword->psChild->pszValue); |
1622 | 147 | nKeywordCounter++; |
1623 | 147 | } |
1624 | 0 | else if (psKeyword->eType == CXT_Text) |
1625 | 0 | { |
1626 | 0 | poLayer->SetMetadataItem("KEYWORDS", |
1627 | 0 | psKeyword->pszValue); |
1628 | 0 | } |
1629 | 147 | } |
1630 | 42 | } |
1631 | | |
1632 | 6.86k | if (poSRS) |
1633 | 95 | { |
1634 | 95 | char *pszProj4 = nullptr; |
1635 | 95 | if (poSRS->exportToProj4(&pszProj4) == OGRERR_NONE) |
1636 | 95 | { |
1637 | | /* See http://trac.osgeo.org/gdal/ticket/4041 */ |
1638 | 95 | const bool bTrustBounds = CPLFetchBool( |
1639 | 95 | papszOpenOptionsIn, "TRUST_CAPABILITIES_BOUNDS", |
1640 | 95 | CPLTestBool(CPLGetConfigOption( |
1641 | 95 | "OGR_WFS_TRUST_CAPABILITIES_BOUNDS", "FALSE"))); |
1642 | | |
1643 | 95 | if (((bTrustBounds || |
1644 | 95 | (dfMinX == -180 && dfMinY == -90 && |
1645 | 3 | dfMaxX == 180 && dfMaxY == 90)) && |
1646 | 3 | strcmp(pszProj4, |
1647 | 3 | "+proj=longlat +datum=WGS84 +no_defs") == |
1648 | 3 | 0) || |
1649 | 92 | strcmp(pszDefaultSRS, |
1650 | 92 | "urn:ogc:def:crs:OGC:1.3:CRS84") == 0) |
1651 | 3 | { |
1652 | 3 | poLayer->SetWGS84Extents(dfMinX, dfMinY, dfMaxX, |
1653 | 3 | dfMaxY); |
1654 | 3 | poLayer->SetExtents(dfMinX, dfMinY, dfMaxX, dfMaxY); |
1655 | 3 | } |
1656 | | |
1657 | 92 | else if (bTrustBounds) |
1658 | 0 | { |
1659 | 0 | OGRSpatialReference oWGS84; |
1660 | 0 | oWGS84.SetWellKnownGeogCS("WGS84"); |
1661 | 0 | oWGS84.SetAxisMappingStrategy( |
1662 | 0 | OAMS_TRADITIONAL_GIS_ORDER); |
1663 | 0 | CPLPushErrorHandler(CPLQuietErrorHandler); |
1664 | 0 | auto poCT = |
1665 | 0 | std::unique_ptr<OGRCoordinateTransformation>( |
1666 | 0 | OGRCreateCoordinateTransformation(&oWGS84, |
1667 | 0 | poSRS)); |
1668 | 0 | if (poCT) |
1669 | 0 | { |
1670 | 0 | poLayer->SetWGS84Extents(dfMinX, dfMinY, dfMaxX, |
1671 | 0 | dfMaxY); |
1672 | 0 | poCT->TransformBounds(dfMinX, dfMinY, dfMaxX, |
1673 | 0 | dfMaxY, &dfMinX, &dfMinY, |
1674 | 0 | &dfMaxX, &dfMaxY, 20); |
1675 | 0 | poLayer->SetExtents(dfMinX, dfMinY, dfMaxX, |
1676 | 0 | dfMaxY); |
1677 | 0 | } |
1678 | 0 | CPLPopErrorHandler(); |
1679 | 0 | CPLErrorReset(); |
1680 | 0 | } |
1681 | 95 | } |
1682 | 95 | CPLFree(pszProj4); |
1683 | 95 | } |
1684 | 6.86k | poLayer->SetSupportedSRSList(std::move(aosSupportedCRSList), |
1685 | 6.86k | std::move(apoSupportedCRSList)); |
1686 | | |
1687 | 6.86k | papoLayers = static_cast<OGRWFSLayer **>(CPLRealloc( |
1688 | 6.86k | papoLayers, sizeof(OGRWFSLayer *) * (nLayers + 1))); |
1689 | 6.86k | papoLayers[nLayers++] = poLayer; |
1690 | | |
1691 | 6.86k | if (psFileXML != nullptr) |
1692 | 70 | { |
1693 | 70 | CPLXMLNode *psIter = psXML->psChild; |
1694 | 282 | while (psIter) |
1695 | 279 | { |
1696 | 279 | if (psIter->eType == CXT_Element && psIter->psChild && |
1697 | 278 | EQUAL(psIter->pszValue, "OGRWFSLayer") && |
1698 | 138 | strcmp(CPLGetXMLValue(psIter, "name", ""), |
1699 | 138 | l_pszName) == 0) |
1700 | 67 | { |
1701 | 67 | const CPLXMLNode *psSchema = |
1702 | 67 | WFSFindNode(psIter->psChild, "schema"); |
1703 | 67 | if (psSchema) |
1704 | 67 | { |
1705 | 67 | OGRFeatureDefn *poSrcFDefn = |
1706 | 67 | poLayer->ParseSchema(psSchema); |
1707 | 67 | if (poSrcFDefn) |
1708 | 54 | poLayer->BuildLayerDefn(poSrcFDefn); |
1709 | 67 | } |
1710 | 67 | break; |
1711 | 67 | } |
1712 | 212 | psIter = psIter->psNext; |
1713 | 212 | } |
1714 | 70 | } |
1715 | 6.86k | } |
1716 | 6.93k | } |
1717 | 7.25k | } |
1718 | | |
1719 | 251 | CSLDestroy(papszTypenames); |
1720 | | |
1721 | 251 | if (!psFileXML) |
1722 | 224 | CPLDestroyXMLNode(psXML); |
1723 | 251 | CPLDestroyXMLNode(psStrippedXML); |
1724 | | |
1725 | 251 | return TRUE; |
1726 | 252 | } |
1727 | | |
1728 | | /************************************************************************/ |
1729 | | /* LoadMultipleLayerDefn() */ |
1730 | | /************************************************************************/ |
1731 | | |
1732 | | /* TinyOWS doesn't support POST, but MapServer, GeoServer and Deegree do */ |
1733 | | #define USE_GET_FOR_DESCRIBE_FEATURE_TYPE 1 |
1734 | | |
1735 | | void OGRWFSDataSource::LoadMultipleLayerDefn(const char *pszLayerName, |
1736 | | char *pszNS, char *pszNSVal) |
1737 | 1.13k | { |
1738 | 1.13k | if (!bLoadMultipleLayerDefn) |
1739 | 953 | return; |
1740 | | |
1741 | 186 | if (aoSetAlreadyTriedLayers.find(pszLayerName) != |
1742 | 186 | aoSetAlreadyTriedLayers.end()) |
1743 | 0 | return; |
1744 | | |
1745 | 186 | std::string osPrefix(pszLayerName); |
1746 | 186 | const auto nColumnPos = osPrefix.find(':'); |
1747 | 186 | if (nColumnPos == std::string::npos) |
1748 | 117 | osPrefix.clear(); |
1749 | 69 | else |
1750 | 69 | osPrefix.resize(nColumnPos); |
1751 | | |
1752 | 186 | OGRWFSLayer *poRefLayer = |
1753 | 186 | dynamic_cast<OGRWFSLayer *>(GetLayerByName(pszLayerName)); |
1754 | 186 | if (poRefLayer == nullptr) |
1755 | 0 | return; |
1756 | | |
1757 | 186 | const char *pszRequiredOutputFormat = poRefLayer->GetRequiredOutputFormat(); |
1758 | | |
1759 | 186 | #if USE_GET_FOR_DESCRIBE_FEATURE_TYPE == 1 |
1760 | 186 | CPLString osLayerToFetch(pszLayerName); |
1761 | | #else |
1762 | | CPLString osTypeNameToPost; |
1763 | | osTypeNameToPost += " <TypeName>"; |
1764 | | osTypeNameToPost += pszLayerName; |
1765 | | osTypeNameToPost += "</TypeName>\n"; |
1766 | | #endif |
1767 | | |
1768 | 186 | int nLayersToFetch = 1; |
1769 | 186 | aoSetAlreadyTriedLayers.insert(pszLayerName); |
1770 | | |
1771 | 6.96k | for (int i = 0; i < nLayers; i++) |
1772 | 6.77k | { |
1773 | 6.77k | if (!papoLayers[i]->HasLayerDefn()) |
1774 | 6.76k | { |
1775 | | /* We must be careful to requests only layers with the same |
1776 | | * prefix/namespace */ |
1777 | 6.76k | const char *l_pszName = papoLayers[i]->GetName(); |
1778 | 6.76k | if (((osPrefix.empty() && strchr(l_pszName, ':') == nullptr) || |
1779 | 3.89k | (!osPrefix.empty() && |
1780 | 2.75k | strncmp(l_pszName, osPrefix.c_str(), osPrefix.size()) == 0 && |
1781 | 149 | l_pszName[osPrefix.size()] == ':')) && |
1782 | 2.94k | ((pszRequiredOutputFormat == nullptr && |
1783 | 2.94k | papoLayers[i]->GetRequiredOutputFormat() == nullptr) || |
1784 | 0 | (pszRequiredOutputFormat != nullptr && |
1785 | 0 | papoLayers[i]->GetRequiredOutputFormat() != nullptr && |
1786 | 0 | strcmp(pszRequiredOutputFormat, |
1787 | 0 | papoLayers[i]->GetRequiredOutputFormat()) == 0))) |
1788 | 2.94k | { |
1789 | 2.94k | if (aoSetAlreadyTriedLayers.find(l_pszName) != |
1790 | 2.94k | aoSetAlreadyTriedLayers.end()) |
1791 | 2.01k | continue; |
1792 | 932 | aoSetAlreadyTriedLayers.insert(l_pszName); |
1793 | | |
1794 | 932 | #if USE_GET_FOR_DESCRIBE_FEATURE_TYPE == 1 |
1795 | 932 | if (nLayersToFetch > 0) |
1796 | 932 | osLayerToFetch += ","; |
1797 | 932 | osLayerToFetch += papoLayers[i]->GetName(); |
1798 | | #else |
1799 | | osTypeNameToPost += " <TypeName>"; |
1800 | | osTypeNameToPost += l_pszName; |
1801 | | osTypeNameToPost += "</TypeName>\n"; |
1802 | | #endif |
1803 | 932 | nLayersToFetch++; |
1804 | | |
1805 | | /* Avoid fetching to many layer definition at a time */ |
1806 | 932 | if (nLayersToFetch >= 50) |
1807 | 0 | break; |
1808 | 932 | } |
1809 | 6.76k | } |
1810 | 6.77k | } |
1811 | | |
1812 | 186 | #if USE_GET_FOR_DESCRIBE_FEATURE_TYPE == 1 |
1813 | 186 | CPLString osURL(osBaseURL); |
1814 | 186 | osURL = CPLURLAddKVP(osURL, "SERVICE", "WFS"); |
1815 | 186 | osURL = CPLURLAddKVP(osURL, "VERSION", GetVersion()); |
1816 | 186 | osURL = CPLURLAddKVP(osURL, "REQUEST", "DescribeFeatureType"); |
1817 | 186 | osURL = CPLURLAddKVP(osURL, "TYPENAME", WFS_EscapeURL(osLayerToFetch)); |
1818 | 186 | osURL = CPLURLAddKVP(osURL, "PROPERTYNAME", nullptr); |
1819 | 186 | osURL = CPLURLAddKVP(osURL, "MAXFEATURES", nullptr); |
1820 | 186 | osURL = CPLURLAddKVP(osURL, "FILTER", nullptr); |
1821 | 186 | osURL = CPLURLAddKVP(osURL, "OUTPUTFORMAT", |
1822 | 186 | pszRequiredOutputFormat |
1823 | 186 | ? WFS_EscapeURL(pszRequiredOutputFormat).c_str() |
1824 | 186 | : nullptr); |
1825 | | |
1826 | 186 | if (pszNS && GetNeedNAMESPACE()) |
1827 | 0 | { |
1828 | | /* Older Deegree version require NAMESPACE */ |
1829 | | /* This has been now corrected */ |
1830 | 0 | CPLString osValue("xmlns("); |
1831 | 0 | osValue += pszNS; |
1832 | 0 | osValue += "="; |
1833 | 0 | osValue += pszNSVal; |
1834 | 0 | osValue += ")"; |
1835 | 0 | osURL = CPLURLAddKVP(osURL, "NAMESPACE", WFS_EscapeURL(osValue)); |
1836 | 0 | } |
1837 | | |
1838 | 186 | CPLHTTPResult *psResult = HTTPFetch(osURL, nullptr); |
1839 | | #else |
1840 | | CPLString osPost; |
1841 | | osPost += "<?xml version=\"1.0\"?>\n"; |
1842 | | osPost += |
1843 | | "<wfs:DescribeFeatureType xmlns:wfs=\"http://www.opengis.net/wfs\"\n"; |
1844 | | osPost += " " |
1845 | | "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"; |
1846 | | osPost += " service=\"WFS\" version=\""; |
1847 | | osPost += GetVersion(); |
1848 | | osPost += "\"\n"; |
1849 | | osPost += " xmlns:gml=\"http://www.opengis.net/gml\"\n"; |
1850 | | osPost += " xmlns:ogc=\"http://www.opengis.net/ogc\"\n"; |
1851 | | if (pszNS && pszNSVal) |
1852 | | { |
1853 | | osPost += " xmlns:"; |
1854 | | osPost += pszNS; |
1855 | | osPost += "=\""; |
1856 | | osPost += pszNSVal; |
1857 | | osPost += "\"\n"; |
1858 | | } |
1859 | | osPost += |
1860 | | " xsi:schemaLocation=\"http://www.opengis.net/wfs " |
1861 | | "http://schemas.opengis.net/wfs/"; |
1862 | | osPost += GetVersion(); |
1863 | | osPost += "/wfs.xsd\""; |
1864 | | const char *pszRequiredOutputFormat = poRefLayer->GetRequiredOutputFormat(); |
1865 | | if (pszRequiredOutputFormat) |
1866 | | { |
1867 | | osPost += "\n"; |
1868 | | osPost += " outputFormat=\""; |
1869 | | osPost += pszRequiredOutputFormat; |
1870 | | osPost += "\""; |
1871 | | } |
1872 | | osPost += ">\n"; |
1873 | | osPost += osTypeNameToPost; |
1874 | | osPost += "</wfs:DescribeFeatureType>\n"; |
1875 | | |
1876 | | // CPLDebug("WFS", "%s", osPost.c_str()); |
1877 | | |
1878 | | CSLConstList papszOptions = NULL; |
1879 | | papszOptions = CSLAddNameValue(papszOptions, "POSTFIELDS", osPost.c_str()); |
1880 | | papszOptions = |
1881 | | CSLAddNameValue(papszOptions, "HEADERS", |
1882 | | "Content-Type: application/xml; charset=UTF-8"); |
1883 | | |
1884 | | CPLHTTPResult *psResult = HTTPFetch(GetPostTransactionURL(), papszOptions); |
1885 | | CSLDestroy(papszOptions); |
1886 | | #endif |
1887 | | |
1888 | 186 | if (psResult == nullptr) |
1889 | 186 | { |
1890 | 186 | bLoadMultipleLayerDefn = false; |
1891 | 186 | return; |
1892 | 186 | } |
1893 | | |
1894 | 0 | const char *pszResponse = |
1895 | 0 | reinterpret_cast<const char *>(psResult->pabyData); |
1896 | 0 | if (STARTS_WITH(pszResponse, "<html")) |
1897 | 0 | { |
1898 | | // WFS:https://www.statistik.at/gs-open/GEODATA/ows returns a HTML error |
1899 | | // (with code 200) when requesting all feature types... |
1900 | 0 | CPLDebug("WFS", "Response to multi-layer DescribeFeatureType: %s", |
1901 | 0 | pszResponse); |
1902 | 0 | CPLHTTPDestroyResult(psResult); |
1903 | 0 | bLoadMultipleLayerDefn = false; |
1904 | 0 | return; |
1905 | 0 | } |
1906 | | |
1907 | 0 | if (strstr(pszResponse, "<ServiceExceptionReport") != nullptr) |
1908 | 0 | { |
1909 | 0 | if (IsOldDeegree(pszResponse)) |
1910 | 0 | { |
1911 | | /* just silently forgive */ |
1912 | 0 | } |
1913 | 0 | else |
1914 | 0 | { |
1915 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
1916 | 0 | "Error returned by server : %s", pszResponse); |
1917 | 0 | } |
1918 | 0 | CPLHTTPDestroyResult(psResult); |
1919 | 0 | bLoadMultipleLayerDefn = false; |
1920 | 0 | return; |
1921 | 0 | } |
1922 | | |
1923 | 0 | CPLXMLNode *psXML = CPLParseXMLString(pszResponse); |
1924 | 0 | if (psXML == nullptr) |
1925 | 0 | { |
1926 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Invalid XML content : %s", |
1927 | 0 | pszResponse); |
1928 | 0 | CPLHTTPDestroyResult(psResult); |
1929 | 0 | bLoadMultipleLayerDefn = false; |
1930 | 0 | return; |
1931 | 0 | } |
1932 | 0 | CPLHTTPDestroyResult(psResult); |
1933 | |
|
1934 | 0 | const CPLXMLNode *psSchema = WFSFindNode(psXML, "schema"); |
1935 | 0 | if (psSchema == nullptr) |
1936 | 0 | { |
1937 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Cannot find <Schema>"); |
1938 | 0 | CPLDestroyXMLNode(psXML); |
1939 | 0 | bLoadMultipleLayerDefn = false; |
1940 | 0 | return; |
1941 | 0 | } |
1942 | | |
1943 | 0 | const CPLString osTmpFileName = VSIMemGenerateHiddenFilename("file.xsd"); |
1944 | 0 | CPLSerializeXMLTreeToFile(psSchema, osTmpFileName); |
1945 | |
|
1946 | 0 | std::vector<GMLFeatureClass *> aosClasses; |
1947 | 0 | bool bFullyUnderstood = false; |
1948 | 0 | bool bUseSchemaImports = false; |
1949 | 0 | GMLParseXSD(osTmpFileName, bUseSchemaImports, aosClasses, bFullyUnderstood); |
1950 | |
|
1951 | 0 | int nLayersFound = 0; |
1952 | 0 | if (!aosClasses.empty()) |
1953 | 0 | { |
1954 | 0 | std::vector<GMLFeatureClass *>::const_iterator oIter = |
1955 | 0 | aosClasses.begin(); |
1956 | 0 | std::vector<GMLFeatureClass *>::const_iterator oEndIter = |
1957 | 0 | aosClasses.end(); |
1958 | 0 | while (oIter != oEndIter) |
1959 | 0 | { |
1960 | 0 | GMLFeatureClass *poClass = *oIter; |
1961 | 0 | ++oIter; |
1962 | |
|
1963 | 0 | OGRWFSLayer *poLayer = nullptr; |
1964 | |
|
1965 | 0 | if (bKeepLayerNamePrefix && pszNS != nullptr && |
1966 | 0 | strchr(poClass->GetName(), ':') == nullptr) |
1967 | 0 | { |
1968 | 0 | CPLString osWithPrefix(pszNS); |
1969 | 0 | osWithPrefix += ":"; |
1970 | 0 | osWithPrefix += poClass->GetName(); |
1971 | 0 | poLayer = |
1972 | 0 | dynamic_cast<OGRWFSLayer *>(GetLayerByName(osWithPrefix)); |
1973 | 0 | } |
1974 | 0 | else |
1975 | 0 | poLayer = dynamic_cast<OGRWFSLayer *>( |
1976 | 0 | GetLayerByName(poClass->GetName())); |
1977 | |
|
1978 | 0 | if (poLayer) |
1979 | 0 | { |
1980 | 0 | if (!poLayer->HasLayerDefn()) |
1981 | 0 | { |
1982 | 0 | nLayersFound++; |
1983 | |
|
1984 | 0 | CPLXMLNode *psSchemaForLayer = CPLCloneXMLTree(psSchema); |
1985 | 0 | CPLStripXMLNamespace(psSchemaForLayer, nullptr, TRUE); |
1986 | 0 | CPLXMLNode *psIter = psSchemaForLayer->psChild; |
1987 | 0 | bool bHasAlreadyImportedGML = false; |
1988 | 0 | bool bFoundComplexType = false; |
1989 | 0 | bool bFoundElement = false; |
1990 | 0 | while (psIter != nullptr) |
1991 | 0 | { |
1992 | 0 | CPLXMLNode *psIterNext = psIter->psNext; |
1993 | 0 | if (psIter->eType == CXT_Element && |
1994 | 0 | strcmp(psIter->pszValue, "complexType") == 0) |
1995 | 0 | { |
1996 | 0 | const char *l_pszName = |
1997 | 0 | CPLGetXMLValue(psIter, "name", ""); |
1998 | 0 | CPLString osExpectedName(poLayer->GetShortName()); |
1999 | 0 | osExpectedName += "Type"; |
2000 | 0 | CPLString osExpectedName2(poLayer->GetShortName()); |
2001 | 0 | osExpectedName2 += "_Type"; |
2002 | 0 | if (strcmp(l_pszName, osExpectedName) == 0 || |
2003 | 0 | strcmp(l_pszName, osExpectedName2) == 0 || |
2004 | 0 | strcmp(l_pszName, poLayer->GetShortName()) == 0) |
2005 | 0 | { |
2006 | 0 | bFoundComplexType = true; |
2007 | 0 | } |
2008 | 0 | else |
2009 | 0 | { |
2010 | 0 | CPLRemoveXMLChild(psSchemaForLayer, psIter); |
2011 | 0 | CPLDestroyXMLNode(psIter); |
2012 | 0 | } |
2013 | 0 | } |
2014 | 0 | else if (psIter->eType == CXT_Element && |
2015 | 0 | strcmp(psIter->pszValue, "element") == 0) |
2016 | 0 | { |
2017 | 0 | const char *l_pszName = |
2018 | 0 | CPLGetXMLValue(psIter, "name", ""); |
2019 | 0 | CPLString osExpectedName(poLayer->GetShortName()); |
2020 | 0 | osExpectedName += "Type"; |
2021 | 0 | CPLString osExpectedName2(poLayer->GetShortName()); |
2022 | 0 | osExpectedName2 += "_Type"; |
2023 | |
|
2024 | 0 | const char *pszType = |
2025 | 0 | CPLGetXMLValue(psIter, "type", ""); |
2026 | 0 | CPLString osExpectedType(poLayer->GetName()); |
2027 | 0 | osExpectedType += "Type"; |
2028 | 0 | CPLString osExpectedType2(poLayer->GetName()); |
2029 | 0 | osExpectedType2 += "_Type"; |
2030 | 0 | if (strcmp(pszType, osExpectedType) == 0 || |
2031 | 0 | strcmp(pszType, osExpectedType2) == 0 || |
2032 | 0 | strcmp(pszType, poLayer->GetName()) == 0 || |
2033 | 0 | (strchr(pszType, ':') && |
2034 | 0 | (strcmp(strchr(pszType, ':') + 1, |
2035 | 0 | osExpectedType) == 0 || |
2036 | 0 | strcmp(strchr(pszType, ':') + 1, |
2037 | 0 | osExpectedType2) == 0))) |
2038 | 0 | { |
2039 | 0 | bFoundElement = true; |
2040 | 0 | } |
2041 | 0 | else if (*pszType == '\0' && |
2042 | 0 | CPLGetXMLNode(psIter, "complexType") != |
2043 | 0 | nullptr && |
2044 | 0 | (strcmp(l_pszName, osExpectedName) == 0 || |
2045 | 0 | strcmp(l_pszName, osExpectedName2) == 0 || |
2046 | 0 | strcmp(l_pszName, |
2047 | 0 | poLayer->GetShortName()) == 0)) |
2048 | 0 | { |
2049 | 0 | bFoundElement = true; |
2050 | 0 | bFoundComplexType = true; |
2051 | 0 | } |
2052 | 0 | else |
2053 | 0 | { |
2054 | 0 | CPLRemoveXMLChild(psSchemaForLayer, psIter); |
2055 | 0 | CPLDestroyXMLNode(psIter); |
2056 | 0 | } |
2057 | 0 | } |
2058 | 0 | else if (psIter->eType == CXT_Element && |
2059 | 0 | strcmp(psIter->pszValue, "import") == 0 && |
2060 | 0 | strcmp(CPLGetXMLValue(psIter, "namespace", ""), |
2061 | 0 | "http://www.opengis.net/gml") == 0) |
2062 | 0 | { |
2063 | 0 | if (bHasAlreadyImportedGML) |
2064 | 0 | { |
2065 | 0 | CPLRemoveXMLChild(psSchemaForLayer, psIter); |
2066 | 0 | CPLDestroyXMLNode(psIter); |
2067 | 0 | } |
2068 | 0 | else |
2069 | 0 | { |
2070 | 0 | bHasAlreadyImportedGML = true; |
2071 | 0 | } |
2072 | 0 | } |
2073 | 0 | psIter = psIterNext; |
2074 | 0 | } |
2075 | |
|
2076 | 0 | if (bFoundComplexType && bFoundElement) |
2077 | 0 | { |
2078 | 0 | OGRFeatureDefn *poSrcFDefn = |
2079 | 0 | poLayer->ParseSchema(psSchemaForLayer); |
2080 | 0 | if (poSrcFDefn) |
2081 | 0 | { |
2082 | 0 | poLayer->BuildLayerDefn(poSrcFDefn); |
2083 | 0 | SaveLayerSchema(poLayer->GetName(), |
2084 | 0 | psSchemaForLayer); |
2085 | 0 | } |
2086 | 0 | } |
2087 | |
|
2088 | 0 | CPLDestroyXMLNode(psSchemaForLayer); |
2089 | 0 | } |
2090 | 0 | else |
2091 | 0 | { |
2092 | 0 | CPLDebug("WFS", |
2093 | 0 | "Found several time schema for layer %s in " |
2094 | 0 | "server response. Should not happen", |
2095 | 0 | poClass->GetName()); |
2096 | 0 | } |
2097 | 0 | } |
2098 | 0 | delete poClass; |
2099 | 0 | } |
2100 | 0 | } |
2101 | |
|
2102 | 0 | if (nLayersFound != nLayersToFetch) |
2103 | 0 | { |
2104 | 0 | CPLDebug("WFS", "Turn off loading of multiple layer definitions at a " |
2105 | 0 | "single time"); |
2106 | 0 | bLoadMultipleLayerDefn = false; |
2107 | 0 | } |
2108 | |
|
2109 | 0 | VSIUnlink(osTmpFileName); |
2110 | |
|
2111 | 0 | CPLDestroyXMLNode(psXML); |
2112 | 0 | } |
2113 | | |
2114 | | /************************************************************************/ |
2115 | | /* SaveLayerSchema() */ |
2116 | | /************************************************************************/ |
2117 | | |
2118 | | void OGRWFSDataSource::SaveLayerSchema(const char *pszLayerName, |
2119 | | const CPLXMLNode *psSchema) |
2120 | 0 | { |
2121 | 0 | if (psFileXML != nullptr) |
2122 | 0 | { |
2123 | 0 | bRewriteFile = true; |
2124 | 0 | CPLXMLNode *psLayerNode = |
2125 | 0 | CPLCreateXMLNode(nullptr, CXT_Element, "OGRWFSLayer"); |
2126 | 0 | CPLSetXMLValue(psLayerNode, "#name", pszLayerName); |
2127 | 0 | CPLAddXMLChild(psLayerNode, CPLCloneXMLTree(psSchema)); |
2128 | 0 | CPLAddXMLChild(psFileXML, psLayerNode); |
2129 | 0 | } |
2130 | 0 | } |
2131 | | |
2132 | | /************************************************************************/ |
2133 | | /* IsOldDeegree() */ |
2134 | | /************************************************************************/ |
2135 | | |
2136 | | bool OGRWFSDataSource::IsOldDeegree(const char *pszErrorString) |
2137 | 0 | { |
2138 | 0 | if (!bNeedNAMESPACE && |
2139 | 0 | strstr(pszErrorString, "Invalid \"TYPENAME\" parameter. " |
2140 | 0 | "No binding for prefix") != nullptr) |
2141 | 0 | { |
2142 | 0 | bNeedNAMESPACE = true; |
2143 | 0 | return true; |
2144 | 0 | } |
2145 | 0 | return false; |
2146 | 0 | } |
2147 | | |
2148 | | /************************************************************************/ |
2149 | | /* WFS_EscapeURL() */ |
2150 | | /************************************************************************/ |
2151 | | |
2152 | | CPLString WFS_EscapeURL(const char *pszURL) |
2153 | 3.79k | { |
2154 | 3.79k | CPLString osEscapedURL; |
2155 | | |
2156 | | /* Difference with CPLEscapeString(, CPLES_URL) : we do not escape */ |
2157 | | /* colon (:) or comma (,). Causes problems with servers such as |
2158 | | * http://www.mapinfo.com/miwfs? */ |
2159 | | |
2160 | 2.52M | for (int i = 0; pszURL[i] != '\0'; i++) |
2161 | 2.52M | { |
2162 | 2.52M | char ch = pszURL[i]; |
2163 | 2.52M | if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || |
2164 | 2.01M | (ch >= '0' && ch <= '9') || ch == '_' || ch == '.' || ch == ':' || |
2165 | 827k | ch == ',') |
2166 | 1.72M | { |
2167 | 1.72M | osEscapedURL += ch; |
2168 | 1.72M | } |
2169 | 792k | else |
2170 | 792k | { |
2171 | 792k | char szPercentEncoded[10]; |
2172 | 792k | snprintf(szPercentEncoded, sizeof(szPercentEncoded), "%%%02X", |
2173 | 792k | reinterpret_cast<const unsigned char *>(pszURL)[i]); |
2174 | 792k | osEscapedURL += szPercentEncoded; |
2175 | 792k | } |
2176 | 2.52M | } |
2177 | | |
2178 | 3.79k | return osEscapedURL; |
2179 | 3.79k | } |
2180 | | |
2181 | | /************************************************************************/ |
2182 | | /* WFS_DecodeURL() */ |
2183 | | /************************************************************************/ |
2184 | | |
2185 | | CPLString WFS_DecodeURL(const CPLString &osSrc) |
2186 | 136 | { |
2187 | 136 | CPLString ret; |
2188 | 136 | for (size_t i = 0; i < osSrc.length(); i++) |
2189 | 0 | { |
2190 | 0 | if (osSrc[i] == '%' && i + 2 < osSrc.length()) |
2191 | 0 | { |
2192 | 0 | unsigned int ii = 0; |
2193 | 0 | sscanf(osSrc.substr(i + 1, 2).c_str(), "%x", &ii); |
2194 | 0 | char ch = static_cast<char>(ii); |
2195 | 0 | ret += ch; |
2196 | 0 | i = i + 2; |
2197 | 0 | } |
2198 | 0 | else |
2199 | 0 | { |
2200 | 0 | ret += osSrc[i]; |
2201 | 0 | } |
2202 | 0 | } |
2203 | 136 | return ret; |
2204 | 136 | } |
2205 | | |
2206 | | /************************************************************************/ |
2207 | | /* HTTPFetch() */ |
2208 | | /************************************************************************/ |
2209 | | |
2210 | | CPLHTTPResult *OGRWFSDataSource::HTTPFetch(const char *pszURL, |
2211 | | CSLConstList papszOptions) |
2212 | 3.90k | { |
2213 | 3.90k | char **papszNewOptions = CSLDuplicate(papszOptions); |
2214 | 3.90k | if (bUseHttp10) |
2215 | 0 | papszNewOptions = |
2216 | 0 | CSLAddNameValue(papszNewOptions, "HTTP_VERSION", "1.0"); |
2217 | 3.90k | if (papszHttpOptions) |
2218 | 0 | papszNewOptions = CSLMerge(papszNewOptions, papszHttpOptions); |
2219 | 3.90k | CPLHTTPResult *psResult = CPLHTTPFetch(pszURL, papszNewOptions); |
2220 | 3.90k | CSLDestroy(papszNewOptions); |
2221 | | |
2222 | 3.90k | if (psResult == nullptr) |
2223 | 0 | { |
2224 | 0 | return nullptr; |
2225 | 0 | } |
2226 | 3.90k | if (psResult->nStatus != 0 || psResult->pszErrBuf != nullptr) |
2227 | 3.89k | { |
2228 | | // A few buggy servers return chunked data with erroneous |
2229 | | // remaining bytes value curl does not like this. Retry with |
2230 | | // HTTP 1.0 protocol instead that does not support chunked |
2231 | | // data. |
2232 | 3.89k | if (psResult->pszErrBuf && |
2233 | 3.89k | strstr(psResult->pszErrBuf, |
2234 | 3.89k | "transfer closed with outstanding read data remaining") && |
2235 | 0 | !bUseHttp10) |
2236 | 0 | { |
2237 | 0 | CPLDebug("WFS", "Probably buggy remote server. Retrying with HTTP " |
2238 | 0 | "1.0 protocol"); |
2239 | 0 | bUseHttp10 = true; |
2240 | 0 | CPLHTTPDestroyResult(psResult); |
2241 | 0 | return HTTPFetch(pszURL, papszOptions); |
2242 | 0 | } |
2243 | | |
2244 | 3.89k | CPLError(CE_Failure, CPLE_AppDefined, |
2245 | 3.89k | "Error returned by server : %s (%d)", |
2246 | 3.89k | (psResult->pszErrBuf) ? psResult->pszErrBuf : "unknown", |
2247 | 3.89k | psResult->nStatus); |
2248 | 3.89k | CPLHTTPDestroyResult(psResult); |
2249 | 3.89k | return nullptr; |
2250 | 3.89k | } |
2251 | 5 | if (psResult->pabyData == nullptr) |
2252 | 0 | { |
2253 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
2254 | 0 | "Empty content returned by server"); |
2255 | 0 | CPLHTTPDestroyResult(psResult); |
2256 | 0 | return nullptr; |
2257 | 0 | } |
2258 | 5 | return psResult; |
2259 | 5 | } |
2260 | | |
2261 | | /************************************************************************/ |
2262 | | /* ExecuteSQL() */ |
2263 | | /************************************************************************/ |
2264 | | |
2265 | | OGRLayer *OGRWFSDataSource::ExecuteSQL(const char *pszSQLCommand, |
2266 | | OGRGeometry *poSpatialFilter, |
2267 | | const char *pszDialect) |
2268 | | |
2269 | 0 | { |
2270 | 0 | while (*pszSQLCommand && |
2271 | 0 | isspace(static_cast<unsigned char>(*pszSQLCommand))) |
2272 | 0 | ++pszSQLCommand; |
2273 | |
|
2274 | 0 | swq_select_parse_options oParseOptions; |
2275 | 0 | oParseOptions.poCustomFuncRegistrar = WFSGetCustomFuncRegistrar(); |
2276 | | |
2277 | | /* -------------------------------------------------------------------- */ |
2278 | | /* Use generic implementation for recognized dialects */ |
2279 | | /* -------------------------------------------------------------------- */ |
2280 | 0 | if (IsGenericSQLDialect(pszDialect)) |
2281 | 0 | { |
2282 | 0 | OGRLayer *poResLayer = GDALDataset::ExecuteSQL( |
2283 | 0 | pszSQLCommand, poSpatialFilter, pszDialect, &oParseOptions); |
2284 | 0 | oMap[poResLayer] = nullptr; |
2285 | 0 | return poResLayer; |
2286 | 0 | } |
2287 | | |
2288 | | /* -------------------------------------------------------------------- */ |
2289 | | /* Deal with "SELECT _LAST_INSERTED_FIDS_ FROM layername" statement */ |
2290 | | /* -------------------------------------------------------------------- */ |
2291 | 0 | if (STARTS_WITH_CI(pszSQLCommand, "SELECT _LAST_INSERTED_FIDS_ FROM ")) |
2292 | 0 | { |
2293 | 0 | const char *pszIter = pszSQLCommand + 33; |
2294 | 0 | while (*pszIter && *pszIter != ' ') |
2295 | 0 | pszIter++; |
2296 | |
|
2297 | 0 | CPLString osName = pszSQLCommand + 33; |
2298 | 0 | osName.resize(pszIter - (pszSQLCommand + 33)); |
2299 | 0 | OGRWFSLayer *poLayer = |
2300 | 0 | dynamic_cast<OGRWFSLayer *>(GetLayerByName(osName)); |
2301 | 0 | if (poLayer == nullptr) |
2302 | 0 | { |
2303 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer : %s", |
2304 | 0 | osName.c_str()); |
2305 | 0 | return nullptr; |
2306 | 0 | } |
2307 | | |
2308 | 0 | auto poMEMDS = std::unique_ptr<MEMDataset>( |
2309 | 0 | MEMDataset::Create("dummy_name", 0, 0, 0, GDT_Unknown, nullptr)); |
2310 | 0 | OGRLayer *poMEMLayer = |
2311 | 0 | poMEMDS->CreateLayer("FID_LIST", nullptr, wkbNone, nullptr); |
2312 | 0 | OGRFieldDefn oFDefn("gml_id", OFTString); |
2313 | 0 | CPL_IGNORE_RET_VAL(poMEMLayer->CreateField(&oFDefn)); |
2314 | |
|
2315 | 0 | for (const auto &osFID : poLayer->GetLastInsertedFIDList()) |
2316 | 0 | { |
2317 | 0 | OGRFeature oFeature(poMEMLayer->GetLayerDefn()); |
2318 | 0 | oFeature.SetField(0, osFID); |
2319 | 0 | CPL_IGNORE_RET_VAL(poMEMLayer->CreateFeature(&oFeature)); |
2320 | 0 | } |
2321 | |
|
2322 | 0 | OGRLayer *poResLayer = |
2323 | 0 | new OGRWFSWrappedResultLayer(poMEMDS.release(), poMEMLayer); |
2324 | 0 | oMap[poResLayer] = nullptr; |
2325 | 0 | return poResLayer; |
2326 | 0 | } |
2327 | | |
2328 | | /* -------------------------------------------------------------------- */ |
2329 | | /* Deal with "DELETE FROM layer_name WHERE expression" statement */ |
2330 | | /* -------------------------------------------------------------------- */ |
2331 | 0 | if (STARTS_WITH_CI(pszSQLCommand, "DELETE FROM ")) |
2332 | 0 | { |
2333 | 0 | const char *pszIter = pszSQLCommand + 12; |
2334 | 0 | while (*pszIter && *pszIter != ' ') |
2335 | 0 | pszIter++; |
2336 | 0 | if (*pszIter == 0) |
2337 | 0 | { |
2338 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Invalid statement"); |
2339 | 0 | return nullptr; |
2340 | 0 | } |
2341 | | |
2342 | 0 | CPLString osName = pszSQLCommand + 12; |
2343 | 0 | osName.resize(pszIter - (pszSQLCommand + 12)); |
2344 | 0 | OGRWFSLayer *poLayer = |
2345 | 0 | dynamic_cast<OGRWFSLayer *>(GetLayerByName(osName)); |
2346 | 0 | if (poLayer == nullptr) |
2347 | 0 | { |
2348 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Unknown layer : %s", |
2349 | 0 | osName.c_str()); |
2350 | 0 | return nullptr; |
2351 | 0 | } |
2352 | | |
2353 | 0 | while (*pszIter == ' ') |
2354 | 0 | pszIter++; |
2355 | 0 | if (!STARTS_WITH_CI(pszIter, "WHERE ")) |
2356 | 0 | { |
2357 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "WHERE clause missing"); |
2358 | 0 | return nullptr; |
2359 | 0 | } |
2360 | 0 | pszIter += 5; |
2361 | |
|
2362 | 0 | const char *pszQuery = pszIter; |
2363 | | |
2364 | | /* Check with the generic SQL engine that this is a valid WHERE clause |
2365 | | */ |
2366 | 0 | OGRFeatureQuery oQuery; |
2367 | 0 | OGRErr eErr = oQuery.Compile(poLayer->GetLayerDefn(), pszQuery); |
2368 | 0 | if (eErr != OGRERR_NONE) |
2369 | 0 | { |
2370 | 0 | return nullptr; |
2371 | 0 | } |
2372 | | |
2373 | | /* Now turn this into OGC Filter language if possible */ |
2374 | 0 | int bNeedsNullCheck = FALSE; |
2375 | 0 | int nVersion = (strcmp(GetVersion(), "1.0.0") == 0) ? 100 : 110; |
2376 | 0 | swq_expr_node *poNode = |
2377 | 0 | static_cast<swq_expr_node *>(oQuery.GetSWQExpr()); |
2378 | 0 | poNode->ReplaceBetweenByGEAndLERecurse(); |
2379 | 0 | poNode->ReplaceInByOrRecurse(); |
2380 | 0 | CPLString osOGCFilter = WFS_TurnSQLFilterToOGCFilter( |
2381 | 0 | poNode, nullptr, poLayer->GetLayerDefn(), nVersion, |
2382 | 0 | bPropertyIsNotEqualToSupported, bUseFeatureId, |
2383 | 0 | bGmlObjectIdNeedsGMLPrefix, "", &bNeedsNullCheck); |
2384 | 0 | if (bNeedsNullCheck && !HasNullCheck()) |
2385 | 0 | osOGCFilter = ""; |
2386 | |
|
2387 | 0 | if (osOGCFilter.empty()) |
2388 | 0 | { |
2389 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
2390 | 0 | "Cannot convert WHERE clause into a OGC filter"); |
2391 | 0 | return nullptr; |
2392 | 0 | } |
2393 | | |
2394 | 0 | poLayer->DeleteFromFilter(osOGCFilter); |
2395 | |
|
2396 | 0 | return nullptr; |
2397 | 0 | } |
2398 | | |
2399 | | /* -------------------------------------------------------------------- */ |
2400 | | /* Deal with "SELECT xxxx ORDER BY" statement */ |
2401 | | /* -------------------------------------------------------------------- */ |
2402 | 0 | if (STARTS_WITH_CI(pszSQLCommand, "SELECT")) |
2403 | 0 | { |
2404 | 0 | swq_select *psSelectInfo = new swq_select(); |
2405 | 0 | if (psSelectInfo->preparse(pszSQLCommand, TRUE) != CE_None) |
2406 | 0 | { |
2407 | 0 | delete psSelectInfo; |
2408 | 0 | return nullptr; |
2409 | 0 | } |
2410 | 0 | int iLayer = 0; |
2411 | 0 | if (strcmp(GetVersion(), "1.0.0") != 0 && |
2412 | 0 | psSelectInfo->table_count == 1 && |
2413 | 0 | psSelectInfo->table_defs[0].data_source == nullptr && |
2414 | 0 | (iLayer = GetLayerIndex(psSelectInfo->table_defs[0].table_name)) >= |
2415 | 0 | 0 && |
2416 | 0 | psSelectInfo->join_count == 0 && psSelectInfo->order_specs > 0 && |
2417 | 0 | psSelectInfo->poOtherSelect == nullptr) |
2418 | 0 | { |
2419 | 0 | OGRWFSLayer *poSrcLayer = papoLayers[iLayer]; |
2420 | 0 | std::vector<OGRWFSSortDesc> aoSortColumns; |
2421 | 0 | int i = 0; // Used after for. |
2422 | 0 | for (; i < psSelectInfo->order_specs; i++) |
2423 | 0 | { |
2424 | 0 | int nFieldIndex = poSrcLayer->GetLayerDefn()->GetFieldIndex( |
2425 | 0 | psSelectInfo->order_defs[i].field_name); |
2426 | 0 | if (poSrcLayer->HasGotApproximateLayerDefn() || nFieldIndex < 0) |
2427 | 0 | break; |
2428 | | |
2429 | | /* Make sure to have the right case */ |
2430 | 0 | const char *pszFieldName = poSrcLayer->GetLayerDefn() |
2431 | 0 | ->GetFieldDefn(nFieldIndex) |
2432 | 0 | ->GetNameRef(); |
2433 | |
|
2434 | 0 | aoSortColumns.emplace_back( |
2435 | 0 | pszFieldName, psSelectInfo->order_defs[i].ascending_flag); |
2436 | 0 | } |
2437 | |
|
2438 | 0 | if (i == psSelectInfo->order_specs) |
2439 | 0 | { |
2440 | 0 | OGRWFSLayer *poDupLayer = poSrcLayer->Clone(); |
2441 | |
|
2442 | 0 | poDupLayer->SetOrderBy(aoSortColumns); |
2443 | 0 | int nBackup = psSelectInfo->order_specs; |
2444 | 0 | psSelectInfo->order_specs = 0; |
2445 | 0 | char *pszSQLWithoutOrderBy = psSelectInfo->Unparse(); |
2446 | 0 | CPLDebug("WFS", "SQL without ORDER BY: %s", |
2447 | 0 | pszSQLWithoutOrderBy); |
2448 | 0 | psSelectInfo->order_specs = nBackup; |
2449 | 0 | delete psSelectInfo; |
2450 | 0 | psSelectInfo = nullptr; |
2451 | | |
2452 | | /* Just set poDupLayer in the papoLayers for the time of the */ |
2453 | | /* base ExecuteSQL(), so that the OGRGenSQLResultsLayer |
2454 | | * references */ |
2455 | | /* that temporary layer */ |
2456 | 0 | papoLayers[iLayer] = poDupLayer; |
2457 | |
|
2458 | 0 | OGRLayer *poResLayer = GDALDataset::ExecuteSQL( |
2459 | 0 | pszSQLWithoutOrderBy, poSpatialFilter, pszDialect, |
2460 | 0 | &oParseOptions); |
2461 | 0 | papoLayers[iLayer] = poSrcLayer; |
2462 | |
|
2463 | 0 | CPLFree(pszSQLWithoutOrderBy); |
2464 | |
|
2465 | 0 | if (poResLayer != nullptr) |
2466 | 0 | oMap[poResLayer] = poDupLayer; |
2467 | 0 | else |
2468 | 0 | delete poDupLayer; |
2469 | 0 | return poResLayer; |
2470 | 0 | } |
2471 | 0 | } |
2472 | 0 | else if (bStandardJoinsWFS2 && psSelectInfo->join_count > 0 && |
2473 | 0 | psSelectInfo->poOtherSelect == nullptr) |
2474 | 0 | { |
2475 | | // Just to make sure everything is valid, but we won't use |
2476 | | // that one as we want to run the join on server-side |
2477 | 0 | oParseOptions.bAllowFieldsInSecondaryTablesInWhere = TRUE; |
2478 | 0 | oParseOptions.bAddSecondaryTablesGeometryFields = TRUE; |
2479 | 0 | oParseOptions.bAlwaysPrefixWithTableName = TRUE; |
2480 | 0 | oParseOptions.bAllowDistinctOnGeometryField = TRUE; |
2481 | 0 | oParseOptions.bAllowDistinctOnMultipleFields = TRUE; |
2482 | 0 | GDALSQLParseInfo *psParseInfo = |
2483 | 0 | BuildParseInfo(psSelectInfo, &oParseOptions); |
2484 | 0 | oParseOptions.bAllowFieldsInSecondaryTablesInWhere = FALSE; |
2485 | 0 | oParseOptions.bAddSecondaryTablesGeometryFields = FALSE; |
2486 | 0 | oParseOptions.bAlwaysPrefixWithTableName = FALSE; |
2487 | 0 | oParseOptions.bAllowDistinctOnGeometryField = FALSE; |
2488 | 0 | oParseOptions.bAllowDistinctOnMultipleFields = FALSE; |
2489 | 0 | const bool bOK = psParseInfo != nullptr; |
2490 | 0 | DestroyParseInfo(psParseInfo); |
2491 | |
|
2492 | 0 | OGRLayer *poResLayer = nullptr; |
2493 | 0 | if (bOK) |
2494 | 0 | { |
2495 | 0 | poResLayer = OGRWFSJoinLayer::Build(this, psSelectInfo); |
2496 | 0 | oMap[poResLayer] = nullptr; |
2497 | 0 | } |
2498 | |
|
2499 | 0 | delete psSelectInfo; |
2500 | 0 | return poResLayer; |
2501 | 0 | } |
2502 | | |
2503 | 0 | delete psSelectInfo; |
2504 | 0 | } |
2505 | | |
2506 | 0 | OGRLayer *poResLayer = GDALDataset::ExecuteSQL( |
2507 | 0 | pszSQLCommand, poSpatialFilter, pszDialect, &oParseOptions); |
2508 | 0 | oMap[poResLayer] = nullptr; |
2509 | 0 | return poResLayer; |
2510 | 0 | } |
2511 | | |
2512 | | /************************************************************************/ |
2513 | | /* ReleaseResultSet() */ |
2514 | | /************************************************************************/ |
2515 | | |
2516 | | void OGRWFSDataSource::ReleaseResultSet(OGRLayer *poResultsSet) |
2517 | 0 | { |
2518 | 0 | if (poResultsSet == nullptr) |
2519 | 0 | return; |
2520 | | |
2521 | 0 | std::map<OGRLayer *, OGRLayer *>::iterator oIter = oMap.find(poResultsSet); |
2522 | 0 | if (oIter != oMap.end()) |
2523 | 0 | { |
2524 | | /* Destroy first the result layer, because it still references */ |
2525 | | /* the poDupLayer (oIter->second) */ |
2526 | 0 | delete poResultsSet; |
2527 | |
|
2528 | 0 | delete oIter->second; |
2529 | 0 | oMap.erase(oIter); |
2530 | 0 | } |
2531 | 0 | else |
2532 | 0 | { |
2533 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
2534 | 0 | "Trying to destroy an invalid result set !"); |
2535 | 0 | } |
2536 | 0 | } |