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

Generated by: LCOV version 1.14