LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/wfs - ogrwfsdatasource.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1053 1247 84.4 %
Date: 2024-11-21 22:18:42 Functions: 38 38 100.0 %

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

Generated by: LCOV version 1.14