LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/gml - ogrgmldatasource.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1509 1674 90.1 %
Date: 2025-01-18 12:42:00 Functions: 32 39 82.1 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  OGR
       4             :  * Purpose:  Implements OGRGMLDataSource class.
       5             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2002, Frank Warmerdam <warmerdam@pobox.com>
       9             :  * Copyright (c) 2007-2013, Even Rouault <even dot rouault at spatialys.com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  *
      13             :  ******************************************************************************
      14             :  * Contributor: Alessandro Furieri, a.furieri@lqt.it
      15             :  * Portions of this module implementing GML_SKIP_RESOLVE_ELEMS HUGE
      16             :  * Developed for Faunalia ( http://www.faunalia.it) with funding from
      17             :  * Regione Toscana - Settore SISTEMA INFORMATIVO TERRITORIALE ED AMBIENTALE
      18             :  *
      19             :  ****************************************************************************/
      20             : 
      21             : #include "cpl_port.h"
      22             : #include "ogr_gml.h"
      23             : 
      24             : #include <algorithm>
      25             : #include <vector>
      26             : 
      27             : #include "cpl_conv.h"
      28             : #include "cpl_http.h"
      29             : #include "cpl_string.h"
      30             : #include "cpl_vsi_error.h"
      31             : #include "gmlreaderp.h"
      32             : #include "gmlregistry.h"
      33             : #include "gmlutils.h"
      34             : #include "ogr_p.h"
      35             : #include "ogr_schema_override.h"
      36             : #include "parsexsd.h"
      37             : #include "../mem/ogr_mem.h"
      38             : 
      39             : /************************************************************************/
      40             : /*                   ReplaceSpaceByPct20IfNeeded()                      */
      41             : /************************************************************************/
      42             : 
      43         141 : static CPLString ReplaceSpaceByPct20IfNeeded(const char *pszURL)
      44             : {
      45             :     // Replace ' ' by '%20'.
      46         141 :     CPLString osRet = pszURL;
      47         141 :     const char *pszNeedle = strstr(pszURL, "; ");
      48         141 :     if (pszNeedle)
      49             :     {
      50           2 :         char *pszTmp = static_cast<char *>(CPLMalloc(strlen(pszURL) + 2 + 1));
      51           2 :         const int nBeforeNeedle = static_cast<int>(pszNeedle - pszURL);
      52           2 :         memcpy(pszTmp, pszURL, nBeforeNeedle);
      53           2 :         strcpy(pszTmp + nBeforeNeedle, ";%20");
      54           2 :         strcpy(pszTmp + nBeforeNeedle + strlen(";%20"),
      55             :                pszNeedle + strlen("; "));
      56           2 :         osRet = pszTmp;
      57           2 :         CPLFree(pszTmp);
      58             :     }
      59             : 
      60         141 :     return osRet;
      61             : }
      62             : 
      63             : /************************************************************************/
      64             : /*                         OGRGMLDataSource()                         */
      65             : /************************************************************************/
      66             : 
      67         535 : OGRGMLDataSource::OGRGMLDataSource()
      68             :     : papoLayers(nullptr), nLayers(0), papszCreateOptions(nullptr),
      69             :       fpOutput(nullptr), bFpOutputIsNonSeekable(false),
      70             :       bFpOutputSingleFile(false), bBBOX3D(false), nBoundedByLocation(-1),
      71             :       nSchemaInsertLocation(-1), bIsOutputGML3(false),
      72             :       bIsOutputGML3Deegree(false), bIsOutputGML32(false),
      73             :       eSRSNameFormat(SRSNAME_SHORT), bWriteSpaceIndentation(true),
      74             :       poReader(nullptr), bOutIsTempFile(false), bExposeGMLId(false),
      75             :       bExposeFid(false), bIsWFS(false), bUseGlobalSRSName(false),
      76             :       m_bInvertAxisOrderIfLatLong(false), m_bConsiderEPSGAsURN(false),
      77             :       m_eSwapCoordinates(GML_SWAP_AUTO), m_bGetSecondaryGeometryOption(false),
      78             :       eReadMode(STANDARD), poStoredGMLFeature(nullptr),
      79         535 :       poLastReadLayer(nullptr), bEmptyAsNull(true)
      80             : {
      81         535 : }
      82             : 
      83             : /************************************************************************/
      84             : /*                          ~OGRGMLDataSource()                         */
      85             : /************************************************************************/
      86             : 
      87        1070 : OGRGMLDataSource::~OGRGMLDataSource()
      88             : {
      89         535 :     OGRGMLDataSource::Close();
      90        1070 : }
      91             : 
      92             : /************************************************************************/
      93             : /*                                 Close()                              */
      94             : /************************************************************************/
      95             : 
      96        1056 : CPLErr OGRGMLDataSource::Close()
      97             : {
      98        1056 :     CPLErr eErr = CE_None;
      99        1056 :     if (nOpenFlags != OPEN_FLAGS_CLOSED)
     100             :     {
     101        1056 :         if (fpOutput && !m_bWriteError)
     102             :         {
     103          99 :             if (nLayers == 0)
     104           3 :                 WriteTopElements();
     105             : 
     106          99 :             const char *pszPrefix = GetAppPrefix();
     107          99 :             if (GMLFeatureCollection())
     108           1 :                 PrintLine(fpOutput, "</gml:FeatureCollection>");
     109          98 :             else if (RemoveAppPrefix())
     110           2 :                 PrintLine(fpOutput, "</FeatureCollection>");
     111             :             else
     112          96 :                 PrintLine(fpOutput, "</%s:FeatureCollection>", pszPrefix);
     113             : 
     114          99 :             if (bFpOutputIsNonSeekable)
     115             :             {
     116           0 :                 VSIFCloseL(fpOutput);
     117           0 :                 fpOutput = nullptr;
     118             :             }
     119             : 
     120          99 :             InsertHeader();
     121             : 
     122         198 :             if (!bFpOutputIsNonSeekable && nBoundedByLocation != -1 &&
     123          99 :                 VSIFSeekL(fpOutput, nBoundedByLocation, SEEK_SET) == 0)
     124             :             {
     125         166 :                 if (m_bWriteGlobalSRS && sBoundingRect.IsInit() &&
     126          67 :                     IsGML3Output())
     127             :                 {
     128          57 :                     bool bCoordSwap = false;
     129             :                     char *pszSRSName =
     130             :                         m_poWriteGlobalSRS
     131          57 :                             ? GML_GetSRSName(m_poWriteGlobalSRS.get(),
     132             :                                              eSRSNameFormat, &bCoordSwap)
     133          95 :                             : CPLStrdup("");
     134          57 :                     char szLowerCorner[75] = {};
     135          57 :                     char szUpperCorner[75] = {};
     136             : 
     137          57 :                     OGRWktOptions coordOpts;
     138             : 
     139          57 :                     if (OGRGMLDataSource::GetLayerCount() == 1)
     140             :                     {
     141          41 :                         OGRLayer *poLayer = OGRGMLDataSource::GetLayer(0);
     142          41 :                         if (poLayer->GetLayerDefn()->GetGeomFieldCount() == 1)
     143             :                         {
     144             :                             const auto &oCoordPrec =
     145          37 :                                 poLayer->GetLayerDefn()
     146          37 :                                     ->GetGeomFieldDefn(0)
     147          37 :                                     ->GetCoordinatePrecision();
     148          37 :                             if (oCoordPrec.dfXYResolution !=
     149             :                                 OGRGeomCoordinatePrecision::UNKNOWN)
     150             :                             {
     151           1 :                                 coordOpts.format = OGRWktFormat::F;
     152           1 :                                 coordOpts.xyPrecision =
     153             :                                     OGRGeomCoordinatePrecision::
     154           1 :                                         ResolutionToPrecision(
     155           1 :                                             oCoordPrec.dfXYResolution);
     156             :                             }
     157          37 :                             if (oCoordPrec.dfZResolution !=
     158             :                                 OGRGeomCoordinatePrecision::UNKNOWN)
     159             :                             {
     160           1 :                                 coordOpts.format = OGRWktFormat::F;
     161           1 :                                 coordOpts.zPrecision =
     162             :                                     OGRGeomCoordinatePrecision::
     163           1 :                                         ResolutionToPrecision(
     164           1 :                                             oCoordPrec.dfZResolution);
     165             :                             }
     166             :                         }
     167             :                     }
     168             : 
     169         114 :                     std::string wkt;
     170          57 :                     if (bCoordSwap)
     171             :                     {
     172          22 :                         wkt = OGRMakeWktCoordinate(
     173             :                             sBoundingRect.MinY, sBoundingRect.MinX,
     174          22 :                             sBoundingRect.MinZ, bBBOX3D ? 3 : 2, coordOpts);
     175          11 :                         memcpy(szLowerCorner, wkt.data(), wkt.size() + 1);
     176             : 
     177          22 :                         wkt = OGRMakeWktCoordinate(
     178             :                             sBoundingRect.MaxY, sBoundingRect.MaxX,
     179          22 :                             sBoundingRect.MaxZ, bBBOX3D ? 3 : 2, coordOpts);
     180          11 :                         memcpy(szUpperCorner, wkt.data(), wkt.size() + 1);
     181             :                     }
     182             :                     else
     183             :                     {
     184          92 :                         wkt = OGRMakeWktCoordinate(
     185             :                             sBoundingRect.MinX, sBoundingRect.MinY,
     186          92 :                             sBoundingRect.MinZ, bBBOX3D ? 3 : 2, coordOpts);
     187          46 :                         memcpy(szLowerCorner, wkt.data(), wkt.size() + 1);
     188             : 
     189          92 :                         wkt = OGRMakeWktCoordinate(
     190             :                             sBoundingRect.MaxX, sBoundingRect.MaxY,
     191          92 :                             sBoundingRect.MaxZ, (bBBOX3D) ? 3 : 2, coordOpts);
     192          46 :                         memcpy(szUpperCorner, wkt.data(), wkt.size() + 1);
     193             :                     }
     194          57 :                     if (bWriteSpaceIndentation)
     195          57 :                         VSIFPrintfL(fpOutput, "  ");
     196          57 :                     PrintLine(
     197             :                         fpOutput,
     198             :                         "<gml:boundedBy><gml:Envelope%s%s><gml:lowerCorner>%s"
     199             :                         "</gml:lowerCorner><gml:upperCorner>%s</"
     200             :                         "gml:upperCorner>"
     201             :                         "</gml:Envelope></gml:boundedBy>",
     202          57 :                         bBBOX3D ? " srsDimension=\"3\"" : "", pszSRSName,
     203             :                         szLowerCorner, szUpperCorner);
     204          57 :                     CPLFree(pszSRSName);
     205             :                 }
     206          42 :                 else if (m_bWriteGlobalSRS && sBoundingRect.IsInit())
     207             :                 {
     208          10 :                     if (bWriteSpaceIndentation)
     209          10 :                         VSIFPrintfL(fpOutput, "  ");
     210          10 :                     PrintLine(fpOutput, "<gml:boundedBy>");
     211          10 :                     if (bWriteSpaceIndentation)
     212          10 :                         VSIFPrintfL(fpOutput, "    ");
     213          10 :                     PrintLine(fpOutput, "<gml:Box>");
     214          10 :                     if (bWriteSpaceIndentation)
     215          10 :                         VSIFPrintfL(fpOutput, "      ");
     216          10 :                     VSIFPrintfL(fpOutput,
     217             :                                 "<gml:coord><gml:X>%.16g</gml:X>"
     218             :                                 "<gml:Y>%.16g</gml:Y>",
     219             :                                 sBoundingRect.MinX, sBoundingRect.MinY);
     220          10 :                     if (bBBOX3D)
     221           1 :                         VSIFPrintfL(fpOutput, "<gml:Z>%.16g</gml:Z>",
     222             :                                     sBoundingRect.MinZ);
     223          10 :                     PrintLine(fpOutput, "</gml:coord>");
     224          10 :                     if (bWriteSpaceIndentation)
     225          10 :                         VSIFPrintfL(fpOutput, "      ");
     226          10 :                     VSIFPrintfL(fpOutput,
     227             :                                 "<gml:coord><gml:X>%.16g</gml:X>"
     228             :                                 "<gml:Y>%.16g</gml:Y>",
     229             :                                 sBoundingRect.MaxX, sBoundingRect.MaxY);
     230          10 :                     if (bBBOX3D)
     231           1 :                         VSIFPrintfL(fpOutput, "<gml:Z>%.16g</gml:Z>",
     232             :                                     sBoundingRect.MaxZ);
     233          10 :                     PrintLine(fpOutput, "</gml:coord>");
     234          10 :                     if (bWriteSpaceIndentation)
     235          10 :                         VSIFPrintfL(fpOutput, "    ");
     236          10 :                     PrintLine(fpOutput, "</gml:Box>");
     237          10 :                     if (bWriteSpaceIndentation)
     238          10 :                         VSIFPrintfL(fpOutput, "  ");
     239          10 :                     PrintLine(fpOutput, "</gml:boundedBy>");
     240             :                 }
     241             :                 else
     242             :                 {
     243          32 :                     if (bWriteSpaceIndentation)
     244          32 :                         VSIFPrintfL(fpOutput, "  ");
     245          32 :                     if (IsGML3Output())
     246          32 :                         PrintLine(
     247             :                             fpOutput,
     248             :                             "<gml:boundedBy><gml:Null /></gml:boundedBy>");
     249             :                     else
     250           0 :                         PrintLine(fpOutput, "<gml:boundedBy><gml:null>missing"
     251             :                                             "</gml:null></gml:boundedBy>");
     252             :                 }
     253             :             }
     254             :         }
     255             : 
     256        1056 :         if (fpOutput)
     257         100 :             VSIFCloseL(fpOutput);
     258        1056 :         fpOutput = nullptr;
     259             : 
     260        1056 :         CSLDestroy(papszCreateOptions);
     261        1056 :         papszCreateOptions = nullptr;
     262             : 
     263        1739 :         for (int i = 0; i < nLayers; i++)
     264         683 :             delete papoLayers[i];
     265        1056 :         CPLFree(papoLayers);
     266        1056 :         papoLayers = nullptr;
     267        1056 :         nLayers = 0;
     268             : 
     269        1056 :         if (poReader)
     270             :         {
     271         431 :             if (bOutIsTempFile)
     272           0 :                 VSIUnlink(poReader->GetSourceFileName());
     273         431 :             delete poReader;
     274         431 :             poReader = nullptr;
     275             :         }
     276             : 
     277        1056 :         delete poStoredGMLFeature;
     278        1056 :         poStoredGMLFeature = nullptr;
     279             : 
     280        1056 :         if (m_bUnlinkXSDFilename)
     281             :         {
     282           6 :             VSIUnlink(osXSDFilename);
     283           6 :             m_bUnlinkXSDFilename = false;
     284             :         }
     285             : 
     286        1056 :         if (m_bWriteError)
     287           4 :             eErr = CE_Failure;
     288             :     }
     289        1056 :     return eErr;
     290             : }
     291             : 
     292             : /************************************************************************/
     293             : /*                            CheckHeader()                             */
     294             : /************************************************************************/
     295             : 
     296        1539 : bool OGRGMLDataSource::CheckHeader(const char *pszStr)
     297             : {
     298        1539 :     if (strstr(pszStr, "<wfs:FeatureCollection ") != nullptr)
     299         374 :         return true;
     300             : 
     301        1165 :     if (strstr(pszStr, "opengis.net/gml") == nullptr &&
     302         364 :         strstr(pszStr, "<csw:GetRecordsResponse") == nullptr)
     303             :     {
     304         307 :         return false;
     305             :     }
     306             : 
     307             :     // Ignore kml files
     308         858 :     if (strstr(pszStr, "<kml") != nullptr)
     309             :     {
     310           0 :         return false;
     311             :     }
     312             : 
     313             :     // Ignore .xsd schemas.
     314         858 :     if (strstr(pszStr, "<schema") != nullptr ||
     315         857 :         strstr(pszStr, "<xs:schema") != nullptr ||
     316         857 :         strstr(pszStr, "<xsd:schema") != nullptr)
     317             :     {
     318           3 :         return false;
     319             :     }
     320             : 
     321             :     // Ignore GeoRSS documents. They will be recognized by the GeoRSS driver.
     322         855 :     if (strstr(pszStr, "<rss") != nullptr &&
     323           4 :         strstr(pszStr, "xmlns:georss") != nullptr)
     324             :     {
     325           4 :         return false;
     326             :     }
     327             : 
     328             :     // Ignore OpenJUMP .jml documents.
     329             :     // They will be recognized by the OpenJUMP driver.
     330         851 :     if (strstr(pszStr, "<JCSDataFile") != nullptr)
     331             :     {
     332          42 :         return false;
     333             :     }
     334             : 
     335             :     // Ignore OGR WFS xml description files, or WFS Capabilities results.
     336         809 :     if (strstr(pszStr, "<OGRWFSDataSource>") != nullptr ||
     337         807 :         strstr(pszStr, "<wfs:WFS_Capabilities") != nullptr)
     338             :     {
     339           2 :         return false;
     340             :     }
     341             : 
     342             :     // Ignore WMTS capabilities results.
     343         807 :     if (strstr(pszStr, "http://www.opengis.net/wmts/1.0") != nullptr)
     344             :     {
     345           1 :         return false;
     346             :     }
     347             : 
     348         806 :     return true;
     349             : }
     350             : 
     351             : /************************************************************************/
     352             : /*                          ExtractSRSName()                            */
     353             : /************************************************************************/
     354             : 
     355          27 : static bool ExtractSRSName(const char *pszXML, char *szSRSName,
     356             :                            size_t sizeof_szSRSName)
     357             : {
     358          27 :     szSRSName[0] = '\0';
     359             : 
     360          27 :     const char *pszSRSName = strstr(pszXML, "srsName=\"");
     361          27 :     if (pszSRSName != nullptr)
     362             :     {
     363          23 :         pszSRSName += 9;
     364          23 :         const char *pszEndQuote = strchr(pszSRSName, '"');
     365          23 :         if (pszEndQuote != nullptr &&
     366          23 :             static_cast<size_t>(pszEndQuote - pszSRSName) < sizeof_szSRSName)
     367             :         {
     368          23 :             memcpy(szSRSName, pszSRSName, pszEndQuote - pszSRSName);
     369          23 :             szSRSName[pszEndQuote - pszSRSName] = '\0';
     370          23 :             return true;
     371             :         }
     372             :     }
     373           4 :     return false;
     374             : }
     375             : 
     376             : /************************************************************************/
     377             : /*                                Open()                                */
     378             : /************************************************************************/
     379             : 
     380         434 : bool OGRGMLDataSource::Open(GDALOpenInfo *poOpenInfo)
     381             : 
     382             : {
     383             :     // Extract XSD filename from connection string if present.
     384         434 :     osFilename = poOpenInfo->pszFilename;
     385         434 :     const char *pszXSDFilenameTmp = strstr(poOpenInfo->pszFilename, ",xsd=");
     386         434 :     if (pszXSDFilenameTmp != nullptr)
     387             :     {
     388           2 :         osFilename.resize(pszXSDFilenameTmp - poOpenInfo->pszFilename);
     389           2 :         osXSDFilename = pszXSDFilenameTmp + strlen(",xsd=");
     390             :     }
     391             :     else
     392             :     {
     393             :         osXSDFilename =
     394         432 :             CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "XSD", "");
     395             :     }
     396             : 
     397         434 :     const char *pszFilename = osFilename.c_str();
     398             : 
     399             :     // Open the source file.
     400         434 :     VSILFILE *fpToClose = nullptr;
     401         434 :     VSILFILE *fp = nullptr;
     402         434 :     if (poOpenInfo->fpL != nullptr)
     403             :     {
     404         432 :         fp = poOpenInfo->fpL;
     405         432 :         VSIFSeekL(fp, 0, SEEK_SET);
     406             :     }
     407             :     else
     408             :     {
     409           2 :         fp = VSIFOpenL(pszFilename, "r");
     410           2 :         if (fp == nullptr)
     411           0 :             return false;
     412           2 :         fpToClose = fp;
     413             :     }
     414             : 
     415             :     // Load a header chunk and check for signs it is GML.
     416         434 :     char szHeader[4096] = {};
     417         434 :     size_t nRead = VSIFReadL(szHeader, 1, sizeof(szHeader) - 1, fp);
     418         434 :     if (nRead == 0)
     419             :     {
     420           0 :         if (fpToClose)
     421           0 :             VSIFCloseL(fpToClose);
     422           0 :         return false;
     423             :     }
     424         434 :     szHeader[nRead] = '\0';
     425             : 
     426         868 :     CPLString osWithVsiGzip;
     427             : 
     428             :     // Might be a OS-Mastermap gzipped GML, so let be nice and try to open
     429             :     // it transparently with /vsigzip/.
     430         870 :     if (static_cast<GByte>(szHeader[0]) == 0x1f &&
     431           2 :         static_cast<GByte>(szHeader[1]) == 0x8b &&
     432         438 :         EQUAL(CPLGetExtensionSafe(pszFilename).c_str(), "gz") &&
     433           2 :         !STARTS_WITH(pszFilename, "/vsigzip/"))
     434             :     {
     435           2 :         if (fpToClose)
     436           0 :             VSIFCloseL(fpToClose);
     437           2 :         fpToClose = nullptr;
     438           2 :         osWithVsiGzip = "/vsigzip/";
     439           2 :         osWithVsiGzip += pszFilename;
     440             : 
     441           2 :         pszFilename = osWithVsiGzip;
     442             : 
     443           2 :         fp = fpToClose = VSIFOpenL(pszFilename, "r");
     444           2 :         if (fp == nullptr)
     445           0 :             return false;
     446             : 
     447           2 :         nRead = VSIFReadL(szHeader, 1, sizeof(szHeader) - 1, fp);
     448           2 :         if (nRead == 0)
     449             :         {
     450           0 :             VSIFCloseL(fpToClose);
     451           0 :             return false;
     452             :         }
     453           2 :         szHeader[nRead] = '\0';
     454             :     }
     455             : 
     456             :     // Check for a UTF-8 BOM and skip if found.
     457             : 
     458             :     // TODO: BOM is variable-length parameter and depends on encoding. */
     459             :     // Add BOM detection for other encodings.
     460             : 
     461             :     // Used to skip to actual beginning of XML data
     462         434 :     char *szPtr = szHeader;
     463             : 
     464         434 :     if ((static_cast<unsigned char>(szHeader[0]) == 0xEF) &&
     465           4 :         (static_cast<unsigned char>(szHeader[1]) == 0xBB) &&
     466           4 :         (static_cast<unsigned char>(szHeader[2]) == 0xBF))
     467             :     {
     468           4 :         szPtr += 3;
     469             :     }
     470             : 
     471         434 :     bool bExpatCompatibleEncoding = false;
     472             : 
     473         434 :     const char *pszEncoding = strstr(szPtr, "encoding=");
     474         434 :     if (pszEncoding)
     475         348 :         bExpatCompatibleEncoding =
     476         696 :             (pszEncoding[9] == '\'' || pszEncoding[9] == '"') &&
     477         348 :             (STARTS_WITH_CI(pszEncoding + 10, "UTF-8") ||
     478           2 :              STARTS_WITH_CI(pszEncoding + 10, "ISO-8859-15") ||
     479           2 :              (STARTS_WITH_CI(pszEncoding + 10, "ISO-8859-1") &&
     480           2 :               pszEncoding[20] == pszEncoding[9]));
     481             :     else
     482          86 :         bExpatCompatibleEncoding = true;  // utf-8 is the default.
     483             : 
     484         843 :     const bool bHas3D = strstr(szPtr, "srsDimension=\"3\"") != nullptr ||
     485         409 :                         strstr(szPtr, "<gml:Z>") != nullptr;
     486             : 
     487             :     // Here, we expect the opening chevrons of GML tree root element.
     488         434 :     if (szPtr[0] != '<' || !CheckHeader(szPtr))
     489             :     {
     490           3 :         if (fpToClose)
     491           0 :             VSIFCloseL(fpToClose);
     492           3 :         return false;
     493             :     }
     494             : 
     495             :     // Now we definitely own the file descriptor.
     496         431 :     if (fp == poOpenInfo->fpL)
     497         427 :         poOpenInfo->fpL = nullptr;
     498             : 
     499             :     // Small optimization: if we parse a <wfs:FeatureCollection> and
     500             :     // that numberOfFeatures is set, we can use it to set the FeatureCount
     501             :     // but *ONLY* if there's just one class.
     502         431 :     const char *pszFeatureCollection = strstr(szPtr, "wfs:FeatureCollection");
     503         431 :     if (pszFeatureCollection == nullptr)
     504             :         // GML 3.2.1 output.
     505         286 :         pszFeatureCollection = strstr(szPtr, "gml:FeatureCollection");
     506         431 :     if (pszFeatureCollection == nullptr)
     507             :     {
     508             :         // Deegree WFS 1.0.0 output.
     509         270 :         pszFeatureCollection = strstr(szPtr, "<FeatureCollection");
     510         270 :         if (pszFeatureCollection &&
     511           7 :             strstr(szPtr, "xmlns:wfs=\"http://www.opengis.net/wfs\"") ==
     512             :                 nullptr)
     513           6 :             pszFeatureCollection = nullptr;
     514             :     }
     515             : 
     516         431 :     GIntBig nNumberOfFeatures = 0;
     517         431 :     if (pszFeatureCollection)
     518             :     {
     519         162 :         bExposeGMLId = true;
     520         162 :         bIsWFS = true;
     521         162 :         const char *pszNumberOfFeatures = strstr(szPtr, "numberOfFeatures=");
     522         162 :         if (pszNumberOfFeatures)
     523             :         {
     524          46 :             pszNumberOfFeatures += 17;
     525          46 :             char ch = pszNumberOfFeatures[0];
     526          46 :             if ((ch == '\'' || ch == '"') &&
     527          46 :                 strchr(pszNumberOfFeatures + 1, ch) != nullptr)
     528             :             {
     529          46 :                 nNumberOfFeatures = CPLAtoGIntBig(pszNumberOfFeatures + 1);
     530             :             }
     531             :         }
     532         116 :         else if ((pszNumberOfFeatures = strstr(szPtr, "numberReturned=")) !=
     533             :                  nullptr)
     534             :         {
     535             :             // WFS 2.0.0
     536          93 :             pszNumberOfFeatures += 15;
     537          93 :             char ch = pszNumberOfFeatures[0];
     538          93 :             if ((ch == '\'' || ch == '"') &&
     539          93 :                 strchr(pszNumberOfFeatures + 1, ch) != nullptr)
     540             :             {
     541             :                 // 'unknown' might be a valid value in a corrected version of
     542             :                 // WFS 2.0 but it will also evaluate to 0, that is considered as
     543             :                 // unknown, so nothing particular to do.
     544          93 :                 nNumberOfFeatures = CPLAtoGIntBig(pszNumberOfFeatures + 1);
     545             :             }
     546             :         }
     547             :     }
     548         269 :     else if (STARTS_WITH(pszFilename, "/vsimem/") &&
     549         126 :              strstr(pszFilename, "_ogr_wfs_"))
     550             :     {
     551             :         // http://regis.intergraph.com/wfs/dcmetro/request.asp? returns a
     552             :         // <G:FeatureCollection> Who knows what servers can return?  When
     553             :         // in the context of the WFS driver always expose the gml:id to avoid
     554             :         // later crashes.
     555           0 :         bExposeGMLId = true;
     556           0 :         bIsWFS = true;
     557             :     }
     558             :     else
     559             :     {
     560         374 :         bExposeGMLId = strstr(szPtr, " gml:id=\"") != nullptr ||
     561         105 :                        strstr(szPtr, " gml:id='") != nullptr;
     562         472 :         bExposeFid = strstr(szPtr, " fid=\"") != nullptr ||
     563         203 :                      strstr(szPtr, " fid='") != nullptr;
     564             :     }
     565             : 
     566             :     const char *pszExposeGMLId =
     567         431 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "EXPOSE_GML_ID",
     568             :                              CPLGetConfigOption("GML_EXPOSE_GML_ID", nullptr));
     569         431 :     if (pszExposeGMLId)
     570          63 :         bExposeGMLId = CPLTestBool(pszExposeGMLId);
     571             : 
     572             :     const char *pszExposeFid =
     573         431 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "EXPOSE_FID",
     574             :                              CPLGetConfigOption("GML_EXPOSE_FID", nullptr));
     575             : 
     576         431 :     if (pszExposeFid)
     577           1 :         bExposeFid = CPLTestBool(pszExposeFid);
     578             : 
     579         431 :     const bool bHintConsiderEPSGAsURN =
     580         431 :         strstr(szPtr, "xmlns:fme=\"http://www.safe.com/gml/fme\"") != nullptr;
     581             : 
     582         431 :     char szSRSName[128] = {};
     583             : 
     584             :     // MTKGML.
     585         431 :     if (strstr(szPtr, "<Maastotiedot") != nullptr)
     586             :     {
     587           3 :         if (strstr(szPtr,
     588             :                    "http://xml.nls.fi/XML/Namespace/"
     589             :                    "Maastotietojarjestelma/SiirtotiedostonMalli/2011-02") ==
     590             :             nullptr)
     591           1 :             CPLDebug("GML", "Warning: a MTKGML file was detected, "
     592             :                             "but its namespace is unknown");
     593           3 :         bUseGlobalSRSName = true;
     594           3 :         if (!ExtractSRSName(szPtr, szSRSName, sizeof(szSRSName)))
     595           1 :             strcpy(szSRSName, "EPSG:3067");
     596             :     }
     597             : 
     598         431 :     const char *pszSchemaLocation = strstr(szPtr, "schemaLocation=");
     599         431 :     if (pszSchemaLocation)
     600         377 :         pszSchemaLocation += strlen("schemaLocation=");
     601             : 
     602         431 :     bool bCheckAuxFile = true;
     603         431 :     if (STARTS_WITH(pszFilename, "/vsicurl_streaming/"))
     604           2 :         bCheckAuxFile = false;
     605         429 :     else if (STARTS_WITH(pszFilename, "/vsicurl/") &&
     606           1 :              (strstr(pszFilename, "?SERVICE=") ||
     607           1 :               strstr(pszFilename, "&SERVICE=")))
     608           0 :         bCheckAuxFile = false;
     609             : 
     610         431 :     bool bIsWFSJointLayer = bIsWFS && strstr(szPtr, "<wfs:Tuple>");
     611         431 :     if (bIsWFSJointLayer)
     612          50 :         bExposeGMLId = false;
     613             : 
     614             :     // We assume now that it is GML.  Instantiate a GMLReader on it.
     615             :     const char *pszReadMode =
     616         431 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "READ_MODE",
     617             :                              CPLGetConfigOption("GML_READ_MODE", "AUTO"));
     618         431 :     if (EQUAL(pszReadMode, "AUTO"))
     619         428 :         pszReadMode = nullptr;
     620         431 :     if (pszReadMode == nullptr || EQUAL(pszReadMode, "STANDARD"))
     621         428 :         eReadMode = STANDARD;
     622           3 :     else if (EQUAL(pszReadMode, "SEQUENTIAL_LAYERS"))
     623           2 :         eReadMode = SEQUENTIAL_LAYERS;
     624           1 :     else if (EQUAL(pszReadMode, "INTERLEAVED_LAYERS"))
     625           1 :         eReadMode = INTERLEAVED_LAYERS;
     626             :     else
     627             :     {
     628           0 :         CPLDebug("GML",
     629             :                  "Unrecognized value for GML_READ_MODE configuration option.");
     630             :     }
     631             : 
     632         862 :     m_bInvertAxisOrderIfLatLong = CPLTestBool(CSLFetchNameValueDef(
     633         431 :         poOpenInfo->papszOpenOptions, "INVERT_AXIS_ORDER_IF_LAT_LONG",
     634             :         CPLGetConfigOption("GML_INVERT_AXIS_ORDER_IF_LAT_LONG", "YES")));
     635             : 
     636         431 :     const char *pszConsiderEPSGAsURN = CSLFetchNameValueDef(
     637         431 :         poOpenInfo->papszOpenOptions, "CONSIDER_EPSG_AS_URN",
     638             :         CPLGetConfigOption("GML_CONSIDER_EPSG_AS_URN", "AUTO"));
     639         431 :     if (!EQUAL(pszConsiderEPSGAsURN, "AUTO"))
     640           0 :         m_bConsiderEPSGAsURN = CPLTestBool(pszConsiderEPSGAsURN);
     641         431 :     else if (bHintConsiderEPSGAsURN)
     642             :     {
     643             :         // GML produced by FME (at least CanVec GML) seem to honour EPSG axis
     644             :         // ordering.
     645           5 :         CPLDebug("GML", "FME-produced GML --> "
     646             :                         "consider that GML_CONSIDER_EPSG_AS_URN is set to YES");
     647           5 :         m_bConsiderEPSGAsURN = true;
     648             :     }
     649             :     else
     650             :     {
     651         426 :         m_bConsiderEPSGAsURN = false;
     652             :     }
     653             : 
     654         431 :     const char *pszSwapCoordinates = CSLFetchNameValueDef(
     655         431 :         poOpenInfo->papszOpenOptions, "SWAP_COORDINATES",
     656             :         CPLGetConfigOption("GML_SWAP_COORDINATES", "AUTO"));
     657         437 :     m_eSwapCoordinates = EQUAL(pszSwapCoordinates, "AUTO") ? GML_SWAP_AUTO
     658           6 :                          : CPLTestBool(pszSwapCoordinates) ? GML_SWAP_YES
     659             :                                                            : GML_SWAP_NO;
     660             : 
     661         431 :     m_bGetSecondaryGeometryOption =
     662         431 :         CPLTestBool(CPLGetConfigOption("GML_GET_SECONDARY_GEOM", "NO"));
     663             : 
     664             :     // EXPAT is faster than Xerces, so when it is safe to use it, use it!
     665             :     // The only interest of Xerces is for rare encodings that Expat doesn't
     666             :     // handle, but UTF-8 is well handled by Expat.
     667         431 :     bool bUseExpatParserPreferably = bExpatCompatibleEncoding;
     668             : 
     669             :     // Override default choice.
     670         431 :     const char *pszGMLParser = CPLGetConfigOption("GML_PARSER", nullptr);
     671         431 :     if (pszGMLParser)
     672             :     {
     673           4 :         if (EQUAL(pszGMLParser, "EXPAT"))
     674           2 :             bUseExpatParserPreferably = true;
     675           2 :         else if (EQUAL(pszGMLParser, "XERCES"))
     676           2 :             bUseExpatParserPreferably = false;
     677             :     }
     678             : 
     679         431 :     poReader =
     680         862 :         CreateGMLReader(bUseExpatParserPreferably, m_bInvertAxisOrderIfLatLong,
     681         431 :                         m_bConsiderEPSGAsURN, m_eSwapCoordinates,
     682         431 :                         m_bGetSecondaryGeometryOption);
     683         431 :     if (poReader == nullptr)
     684             :     {
     685           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     686             :                  "File %s appears to be GML but the GML reader can't\n"
     687             :                  "be instantiated, likely because Xerces or Expat support was\n"
     688             :                  "not configured in.",
     689             :                  pszFilename);
     690           0 :         VSIFCloseL(fp);
     691           0 :         return false;
     692             :     }
     693             : 
     694         431 :     poReader->SetSourceFile(pszFilename);
     695         431 :     auto poGMLReader = cpl::down_cast<GMLReader *>(poReader);
     696         431 :     poGMLReader->SetIsWFSJointLayer(bIsWFSJointLayer);
     697         431 :     bEmptyAsNull =
     698         431 :         CPLFetchBool(poOpenInfo->papszOpenOptions, "EMPTY_AS_NULL", true);
     699         431 :     poGMLReader->SetEmptyAsNull(bEmptyAsNull);
     700         431 :     poGMLReader->SetReportAllAttributes(CPLFetchBool(
     701         431 :         poOpenInfo->papszOpenOptions, "GML_ATTRIBUTES_TO_OGR_FIELDS",
     702         431 :         CPLTestBool(CPLGetConfigOption("GML_ATTRIBUTES_TO_OGR_FIELDS", "NO"))));
     703         431 :     poGMLReader->SetUseBBOX(
     704         431 :         CPLFetchBool(poOpenInfo->papszOpenOptions, "USE_BBOX", false));
     705             : 
     706             :     // Find <gml:description>, <gml:name> and <gml:boundedBy> and if it is
     707             :     // a standalone geometry
     708             :     // Also look for <gml:description>, <gml:identifier> and <gml:name> inside
     709             :     // a feature
     710         431 :     FindAndParseTopElements(fp);
     711             : 
     712         431 :     if (m_poStandaloneGeom)
     713             :     {
     714           1 :         papoLayers = static_cast<OGRLayer **>(CPLMalloc(sizeof(OGRLayer *)));
     715           1 :         nLayers = 1;
     716             :         auto poLayer = new OGRMemLayer(
     717             :             "geometry",
     718           1 :             m_oStandaloneGeomSRS.IsEmpty() ? nullptr : &m_oStandaloneGeomSRS,
     719           1 :             m_poStandaloneGeom->getGeometryType());
     720           1 :         papoLayers[0] = poLayer;
     721           1 :         OGRFeature *poFeature = new OGRFeature(poLayer->GetLayerDefn());
     722           1 :         poFeature->SetGeometryDirectly(m_poStandaloneGeom.release());
     723           1 :         CPL_IGNORE_RET_VAL(poLayer->CreateFeature(poFeature));
     724           1 :         delete poFeature;
     725           1 :         poLayer->SetUpdatable(false);
     726           1 :         VSIFCloseL(fp);
     727           1 :         return true;
     728             :     }
     729             : 
     730         430 :     if (szSRSName[0] != '\0')
     731           3 :         poReader->SetGlobalSRSName(szSRSName);
     732             : 
     733             :     const bool bIsWFSFromServer =
     734         430 :         CPLString(pszFilename).ifind("SERVICE=WFS") != std::string::npos;
     735             : 
     736             :     // Resolve the xlinks in the source file and save it with the
     737             :     // extension ".resolved.gml". The source file will to set to that.
     738         430 :     char *pszXlinkResolvedFilename = nullptr;
     739         430 :     const char *pszOption = CPLGetConfigOption("GML_SAVE_RESOLVED_TO", nullptr);
     740         430 :     bool bResolve = true;
     741         430 :     bool bHugeFile = false;
     742         430 :     if (bIsWFSFromServer ||
     743           0 :         (pszOption != nullptr && STARTS_WITH_CI(pszOption, "SAME")))
     744             :     {
     745             :         // "SAME" will overwrite the existing gml file.
     746          59 :         pszXlinkResolvedFilename = CPLStrdup(pszFilename);
     747             :     }
     748         371 :     else if (pszOption != nullptr && CPLStrnlen(pszOption, 5) >= 5 &&
     749           0 :              STARTS_WITH_CI(pszOption - 4 + strlen(pszOption), ".gml"))
     750             :     {
     751             :         // Any string ending with ".gml" will try and write to it.
     752           0 :         pszXlinkResolvedFilename = CPLStrdup(pszOption);
     753             :     }
     754             :     else
     755             :     {
     756             :         // When no option is given or is not recognised,
     757             :         // use the same file name with the extension changed to .resolved.gml
     758         371 :         pszXlinkResolvedFilename = CPLStrdup(
     759         742 :             CPLResetExtensionSafe(pszFilename, "resolved.gml").c_str());
     760             : 
     761             :         // Check if the file already exists.
     762             :         VSIStatBufL sResStatBuf, sGMLStatBuf;
     763         742 :         if (bCheckAuxFile &&
     764         371 :             VSIStatL(pszXlinkResolvedFilename, &sResStatBuf) == 0)
     765             :         {
     766           6 :             if (VSIStatL(pszFilename, &sGMLStatBuf) == 0 &&
     767           3 :                 sGMLStatBuf.st_mtime > sResStatBuf.st_mtime)
     768             :             {
     769           0 :                 CPLDebug("GML",
     770             :                          "Found %s but ignoring because it appears\n"
     771             :                          "be older than the associated GML file.",
     772             :                          pszXlinkResolvedFilename);
     773             :             }
     774             :             else
     775             :             {
     776           3 :                 poReader->SetSourceFile(pszXlinkResolvedFilename);
     777           3 :                 bResolve = false;
     778             :             }
     779             :         }
     780             :     }
     781             : 
     782             :     const char *pszSkipOption =
     783         430 :         CPLGetConfigOption("GML_SKIP_RESOLVE_ELEMS", "ALL");
     784         430 :     char **papszSkip = nullptr;
     785         430 :     if (EQUAL(pszSkipOption, "ALL"))
     786         424 :         bResolve = false;
     787           6 :     else if (EQUAL(pszSkipOption, "HUGE"))
     788             :         // Exactly as NONE, but intended for HUGE files
     789           3 :         bHugeFile = true;
     790           3 :     else if (!EQUAL(pszSkipOption, "NONE"))  // Use this to resolve everything.
     791           0 :         papszSkip = CSLTokenizeString2(
     792             :             pszSkipOption, ",", CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES);
     793         430 :     bool bHaveSchema = false;
     794         430 :     bool bSchemaDone = false;
     795             : 
     796             :     // Is some GML Feature Schema (.gfs) TEMPLATE required?
     797             :     const char *pszGFSTemplateName =
     798         430 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "GFS_TEMPLATE",
     799             :                              CPLGetConfigOption("GML_GFS_TEMPLATE", nullptr));
     800         430 :     if (pszGFSTemplateName != nullptr)
     801             :     {
     802             :         // Attempt to load the GFS TEMPLATE.
     803           2 :         bHaveSchema = poReader->LoadClasses(pszGFSTemplateName);
     804             :     }
     805             : 
     806         430 :     if (bResolve)
     807             :     {
     808           6 :         if (bHugeFile)
     809             :         {
     810           3 :             bSchemaDone = true;
     811             :             bool bSqliteIsTempFile =
     812           3 :                 CPLTestBool(CPLGetConfigOption("GML_HUGE_TEMPFILE", "YES"));
     813             :             int iSqliteCacheMB =
     814           3 :                 atoi(CPLGetConfigOption("OGR_SQLITE_CACHE", "0"));
     815           6 :             if (poReader->HugeFileResolver(pszXlinkResolvedFilename,
     816             :                                            bSqliteIsTempFile,
     817           3 :                                            iSqliteCacheMB) == false)
     818             :             {
     819             :                 // Assume an error has been reported.
     820           0 :                 VSIFCloseL(fp);
     821           0 :                 CPLFree(pszXlinkResolvedFilename);
     822           0 :                 return false;
     823             :             }
     824             :         }
     825             :         else
     826             :         {
     827           3 :             poReader->ResolveXlinks(pszXlinkResolvedFilename, &bOutIsTempFile,
     828           3 :                                     papszSkip);
     829             :         }
     830             :     }
     831             : 
     832         430 :     CPLFree(pszXlinkResolvedFilename);
     833         430 :     pszXlinkResolvedFilename = nullptr;
     834         430 :     CSLDestroy(papszSkip);
     835         430 :     papszSkip = nullptr;
     836             : 
     837             :     // If the source filename for the reader is still the GML filename, then
     838             :     // we can directly provide the file pointer. Otherwise close it.
     839         430 :     if (strcmp(poReader->GetSourceFileName(), pszFilename) == 0)
     840         421 :         poReader->SetFP(fp);
     841             :     else
     842           9 :         VSIFCloseL(fp);
     843         430 :     fp = nullptr;
     844             : 
     845             :     // Is a prescan required?
     846         430 :     if (bHaveSchema && !bSchemaDone)
     847             :     {
     848             :         // We must detect which layers are actually present in the .gml
     849             :         // and how many features they have.
     850           2 :         if (!poReader->PrescanForTemplate())
     851             :         {
     852             :             // Assume an error has been reported.
     853           0 :             return false;
     854             :         }
     855             :     }
     856             : 
     857         860 :     CPLString osGFSFilename;
     858         430 :     if (!bIsWFSFromServer)
     859             :     {
     860         371 :         osGFSFilename = CPLResetExtensionSafe(pszFilename, "gfs");
     861         371 :         if (STARTS_WITH(osGFSFilename, "/vsigzip/"))
     862           2 :             osGFSFilename = osGFSFilename.substr(strlen("/vsigzip/"));
     863             :     }
     864             : 
     865             :     // Can we find a GML Feature Schema (.gfs) for the input file?
     866         796 :     if (!osGFSFilename.empty() && !bHaveSchema && !bSchemaDone &&
     867         366 :         osXSDFilename.empty())
     868             :     {
     869             :         VSIStatBufL sGFSStatBuf;
     870         363 :         if (bCheckAuxFile && VSIStatL(osGFSFilename, &sGFSStatBuf) == 0)
     871             :         {
     872             :             VSIStatBufL sGMLStatBuf;
     873          92 :             if (VSIStatL(pszFilename, &sGMLStatBuf) == 0 &&
     874          46 :                 sGMLStatBuf.st_mtime > sGFSStatBuf.st_mtime)
     875             :             {
     876           1 :                 CPLDebug("GML",
     877             :                          "Found %s but ignoring because it appears\n"
     878             :                          "be older than the associated GML file.",
     879             :                          osGFSFilename.c_str());
     880             :             }
     881             :             else
     882             :             {
     883          45 :                 bHaveSchema = poReader->LoadClasses(osGFSFilename);
     884          45 :                 if (bHaveSchema)
     885             :                 {
     886             :                     const std::string osXSDFilenameTmp =
     887          86 :                         CPLResetExtensionSafe(pszFilename, "xsd");
     888          43 :                     if (VSIStatExL(osXSDFilenameTmp.c_str(), &sGMLStatBuf,
     889          43 :                                    VSI_STAT_EXISTS_FLAG) == 0)
     890             :                     {
     891           0 :                         CPLDebug("GML", "Using %s file, ignoring %s",
     892             :                                  osGFSFilename.c_str(),
     893             :                                  osXSDFilenameTmp.c_str());
     894             :                     }
     895             :                 }
     896             :             }
     897             :         }
     898             :     }
     899             : 
     900             :     // Can we find an xsd which might conform to the GML3 Level 0
     901             :     // profile?  We really ought to look for it based on the rules
     902             :     // schemaLocation in the GML feature collection but for now we
     903             :     // just hopes it is in the same director with the same name.
     904             : 
     905         430 :     bool bHasFoundXSD = false;
     906             : 
     907         430 :     if (!bHaveSchema)
     908             :     {
     909         385 :         char **papszTypeNames = nullptr;
     910             : 
     911             :         VSIStatBufL sXSDStatBuf;
     912         385 :         if (osXSDFilename.empty())
     913             :         {
     914         323 :             osXSDFilename = CPLResetExtensionSafe(pszFilename, "xsd");
     915         323 :             if (bCheckAuxFile && VSIStatExL(osXSDFilename, &sXSDStatBuf,
     916             :                                             VSI_STAT_EXISTS_FLAG) == 0)
     917             :             {
     918         213 :                 bHasFoundXSD = true;
     919             :             }
     920             :         }
     921             :         else
     922             :         {
     923          62 :             if (STARTS_WITH(osXSDFilename, "http://") ||
     924         124 :                 STARTS_WITH(osXSDFilename, "https://") ||
     925          62 :                 VSIStatExL(osXSDFilename, &sXSDStatBuf, VSI_STAT_EXISTS_FLAG) ==
     926             :                     0)
     927             :             {
     928          62 :                 bHasFoundXSD = true;
     929             :             }
     930             :         }
     931             : 
     932             :         // If not found, try if there is a schema in the gml_registry.xml
     933             :         // that might match a declared namespace and featuretype.
     934         385 :         if (!bHasFoundXSD)
     935             :         {
     936             :             GMLRegistry oRegistry(
     937         110 :                 CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "REGISTRY",
     938         220 :                                      CPLGetConfigOption("GML_REGISTRY", "")));
     939         110 :             if (oRegistry.Parse())
     940             :             {
     941         220 :                 CPLString osHeader(szHeader);
     942         735 :                 for (size_t iNS = 0; iNS < oRegistry.aoNamespaces.size(); iNS++)
     943             :                 {
     944             :                     GMLRegistryNamespace &oNamespace =
     945         635 :                         oRegistry.aoNamespaces[iNS];
     946             :                     // When namespace is omitted or fit with case sensitive
     947             :                     // match for name space prefix, then go next to find feature
     948             :                     // match.
     949             :                     //
     950             :                     // Case sensitive comparison since below test that also
     951             :                     // uses the namespace prefix is case sensitive.
     952        1167 :                     if (!oNamespace.osPrefix.empty() &&
     953         532 :                         osHeader.find(CPLSPrintf(
     954             :                             "xmlns:%s", oNamespace.osPrefix.c_str())) ==
     955             :                             std::string::npos)
     956             :                     {
     957             :                         // namespace does not match with one of registry
     958             :                         // definition. go to next entry.
     959         525 :                         continue;
     960             :                     }
     961             : 
     962             :                     const char *pszURIToFind =
     963         110 :                         CPLSPrintf("\"%s\"", oNamespace.osURI.c_str());
     964         110 :                     if (strstr(szHeader, pszURIToFind) != nullptr)
     965             :                     {
     966          10 :                         if (oNamespace.bUseGlobalSRSName)
     967           7 :                             bUseGlobalSRSName = true;
     968             : 
     969          38 :                         for (size_t iTypename = 0;
     970          38 :                              iTypename < oNamespace.aoFeatureTypes.size();
     971             :                              iTypename++)
     972             :                         {
     973          38 :                             const char *pszElementToFind = nullptr;
     974             : 
     975             :                             GMLRegistryFeatureType &oFeatureType =
     976          38 :                                 oNamespace.aoFeatureTypes[iTypename];
     977             : 
     978          38 :                             if (!oNamespace.osPrefix.empty())
     979             :                             {
     980          15 :                                 if (!oFeatureType.osElementValue.empty())
     981           4 :                                     pszElementToFind = CPLSPrintf(
     982             :                                         "%s:%s>%s", oNamespace.osPrefix.c_str(),
     983             :                                         oFeatureType.osElementName.c_str(),
     984             :                                         oFeatureType.osElementValue.c_str());
     985             :                                 else
     986          11 :                                     pszElementToFind = CPLSPrintf(
     987             :                                         "%s:%s", oNamespace.osPrefix.c_str(),
     988             :                                         oFeatureType.osElementName.c_str());
     989             :                             }
     990             :                             else
     991             :                             {
     992          23 :                                 if (!oFeatureType.osElementValue.empty())
     993           0 :                                     pszElementToFind = CPLSPrintf(
     994             :                                         "%s>%s",
     995             :                                         oFeatureType.osElementName.c_str(),
     996             :                                         oFeatureType.osElementValue.c_str());
     997             :                                 else
     998          23 :                                     pszElementToFind = CPLSPrintf(
     999             :                                         "<%s",
    1000             :                                         oFeatureType.osElementName.c_str());
    1001             :                             }
    1002             : 
    1003             :                             // Case sensitive test since in a CadastralParcel
    1004             :                             // feature there is a property basicPropertyUnit
    1005             :                             // xlink, not to be confused with a top-level
    1006             :                             // BasicPropertyUnit feature.
    1007          38 :                             if (osHeader.find(pszElementToFind) !=
    1008             :                                 std::string::npos)
    1009             :                             {
    1010          10 :                                 if (!oFeatureType.osSchemaLocation.empty())
    1011             :                                 {
    1012             :                                     osXSDFilename =
    1013           1 :                                         oFeatureType.osSchemaLocation;
    1014           1 :                                     if (STARTS_WITH(osXSDFilename, "http://") ||
    1015           1 :                                         STARTS_WITH(osXSDFilename,
    1016           2 :                                                     "https://") ||
    1017           1 :                                         VSIStatExL(osXSDFilename, &sXSDStatBuf,
    1018             :                                                    VSI_STAT_EXISTS_FLAG) == 0)
    1019             :                                     {
    1020           1 :                                         bHasFoundXSD = true;
    1021           1 :                                         bHaveSchema = true;
    1022           1 :                                         CPLDebug(
    1023             :                                             "GML",
    1024             :                                             "Found %s for %s:%s in registry",
    1025             :                                             osXSDFilename.c_str(),
    1026             :                                             oNamespace.osPrefix.c_str(),
    1027             :                                             oFeatureType.osElementName.c_str());
    1028             :                                     }
    1029             :                                     else
    1030             :                                     {
    1031           0 :                                         CPLDebug("GML", "Cannot open %s",
    1032             :                                                  osXSDFilename.c_str());
    1033             :                                     }
    1034             :                                 }
    1035             :                                 else
    1036             :                                 {
    1037           9 :                                     bHaveSchema = poReader->LoadClasses(
    1038           9 :                                         oFeatureType.osGFSSchemaLocation);
    1039           9 :                                     if (bHaveSchema)
    1040             :                                     {
    1041           9 :                                         CPLDebug(
    1042             :                                             "GML",
    1043             :                                             "Found %s for %s:%s in registry",
    1044             :                                             oFeatureType.osGFSSchemaLocation
    1045             :                                                 .c_str(),
    1046             :                                             oNamespace.osPrefix.c_str(),
    1047             :                                             oFeatureType.osElementName.c_str());
    1048             :                                     }
    1049             :                                 }
    1050          10 :                                 break;
    1051             :                             }
    1052             :                         }
    1053          10 :                         break;
    1054             :                     }
    1055             :                 }
    1056             :             }
    1057             :         }
    1058             : 
    1059             :         /* For WFS, try to fetch the application schema */
    1060         385 :         if (bIsWFS && !bHaveSchema && pszSchemaLocation != nullptr &&
    1061         141 :             (pszSchemaLocation[0] == '\'' || pszSchemaLocation[0] == '"') &&
    1062         141 :             strchr(pszSchemaLocation + 1, pszSchemaLocation[0]) != nullptr)
    1063             :         {
    1064         141 :             char *pszSchemaLocationTmp1 = CPLStrdup(pszSchemaLocation + 1);
    1065         141 :             int nTruncLen = static_cast<int>(
    1066         141 :                 strchr(pszSchemaLocation + 1, pszSchemaLocation[0]) -
    1067         141 :                 (pszSchemaLocation + 1));
    1068         141 :             pszSchemaLocationTmp1[nTruncLen] = '\0';
    1069             :             char *pszSchemaLocationTmp2 =
    1070         141 :                 CPLUnescapeString(pszSchemaLocationTmp1, nullptr, CPLES_XML);
    1071             :             CPLString osEscaped =
    1072         282 :                 ReplaceSpaceByPct20IfNeeded(pszSchemaLocationTmp2);
    1073         141 :             CPLFree(pszSchemaLocationTmp2);
    1074         141 :             pszSchemaLocationTmp2 = CPLStrdup(osEscaped);
    1075         141 :             if (pszSchemaLocationTmp2)
    1076             :             {
    1077             :                 // pszSchemaLocationTmp2 is of the form:
    1078             :                 // http://namespace1 http://namespace1_schema_location
    1079             :                 // http://namespace2 http://namespace1_schema_location2 So we
    1080             :                 // try to find http://namespace1_schema_location that contains
    1081             :                 // hints that it is the WFS application */ schema, i.e. if it
    1082             :                 // contains typename= and request=DescribeFeatureType.
    1083             :                 char **papszTokens =
    1084         141 :                     CSLTokenizeString2(pszSchemaLocationTmp2, " \r\n", 0);
    1085         141 :                 int nTokens = CSLCount(papszTokens);
    1086         141 :                 if ((nTokens % 2) == 0)
    1087             :                 {
    1088         299 :                     for (int i = 0; i < nTokens; i += 2)
    1089             :                     {
    1090         283 :                         const char *pszEscapedURL = papszTokens[i + 1];
    1091         283 :                         char *pszLocation = CPLUnescapeString(
    1092             :                             pszEscapedURL, nullptr, CPLES_URL);
    1093         283 :                         CPLString osLocation = pszLocation;
    1094         283 :                         CPLFree(pszLocation);
    1095         283 :                         if (osLocation.ifind("typename=") !=
    1096         408 :                                 std::string::npos &&
    1097         125 :                             osLocation.ifind("request=DescribeFeatureType") !=
    1098             :                                 std::string::npos)
    1099             :                         {
    1100             :                             CPLString osTypeName =
    1101         250 :                                 CPLURLGetValue(osLocation, "typename");
    1102             :                             papszTypeNames =
    1103         125 :                                 CSLTokenizeString2(osTypeName, ",", 0);
    1104             : 
    1105             :                             // Old non-documented way
    1106             :                             const char *pszGML_DOWNLOAD_WFS_SCHEMA =
    1107         125 :                                 CPLGetConfigOption("GML_DOWNLOAD_WFS_SCHEMA",
    1108             :                                                    nullptr);
    1109         125 :                             if (pszGML_DOWNLOAD_WFS_SCHEMA)
    1110             :                             {
    1111           0 :                                 CPLError(
    1112             :                                     CE_Warning, CPLE_AppDefined,
    1113             :                                     "Configuration option "
    1114             :                                     "GML_DOWNLOAD_WFS_SCHEMA is deprecated. "
    1115             :                                     "Please use GML_DOWNLOAD_SCHEMA instead of "
    1116             :                                     "the DOWNLOAD_SCHEMA open option");
    1117             :                             }
    1118             :                             else
    1119             :                             {
    1120         125 :                                 pszGML_DOWNLOAD_WFS_SCHEMA = "YES";
    1121             :                             }
    1122         132 :                             if (!bHasFoundXSD && CPLHTTPEnabled() &&
    1123           7 :                                 CPLFetchBool(poOpenInfo->papszOpenOptions,
    1124             :                                              "DOWNLOAD_SCHEMA",
    1125           7 :                                              CPLTestBool(CPLGetConfigOption(
    1126             :                                                  "GML_DOWNLOAD_SCHEMA",
    1127             :                                                  pszGML_DOWNLOAD_WFS_SCHEMA))))
    1128             :                             {
    1129             :                                 CPLHTTPResult *psResult =
    1130           7 :                                     CPLHTTPFetch(pszEscapedURL, nullptr);
    1131           7 :                                 if (psResult)
    1132             :                                 {
    1133           7 :                                     if (psResult->nStatus == 0 &&
    1134           6 :                                         psResult->pabyData != nullptr)
    1135             :                                     {
    1136           6 :                                         bHasFoundXSD = true;
    1137           6 :                                         m_bUnlinkXSDFilename = true;
    1138             :                                         osXSDFilename =
    1139             :                                             VSIMemGenerateHiddenFilename(
    1140           6 :                                                 "tmp_ogr_gml.xsd");
    1141           6 :                                         VSILFILE *fpMem = VSIFileFromMemBuffer(
    1142             :                                             osXSDFilename, psResult->pabyData,
    1143           6 :                                             psResult->nDataLen, TRUE);
    1144           6 :                                         VSIFCloseL(fpMem);
    1145           6 :                                         psResult->pabyData = nullptr;
    1146             :                                     }
    1147           7 :                                     CPLHTTPDestroyResult(psResult);
    1148             :                                 }
    1149             :                             }
    1150         125 :                             break;
    1151             :                         }
    1152             :                     }
    1153             :                 }
    1154         141 :                 CSLDestroy(papszTokens);
    1155             :             }
    1156         141 :             CPLFree(pszSchemaLocationTmp2);
    1157         141 :             CPLFree(pszSchemaLocationTmp1);
    1158             :         }
    1159             : 
    1160         385 :         bool bHasFeatureProperties = false;
    1161         385 :         if (bHasFoundXSD)
    1162             :         {
    1163         564 :             std::vector<GMLFeatureClass *> aosClasses;
    1164         564 :             bool bUseSchemaImports = CPLFetchBool(
    1165         282 :                 poOpenInfo->papszOpenOptions, "USE_SCHEMA_IMPORT",
    1166         282 :                 CPLTestBool(CPLGetConfigOption("GML_USE_SCHEMA_IMPORT", "NO")));
    1167         282 :             bool bFullyUnderstood = false;
    1168         282 :             bHaveSchema = GMLParseXSD(osXSDFilename, bUseSchemaImports,
    1169             :                                       aosClasses, bFullyUnderstood);
    1170             : 
    1171         282 :             if (bHaveSchema && !bFullyUnderstood && bIsWFSJointLayer)
    1172             :             {
    1173           1 :                 CPLDebug("GML", "Schema found, but only partially understood. "
    1174             :                                 "Cannot be used in a WFS join context");
    1175             : 
    1176             :                 std::vector<GMLFeatureClass *>::const_iterator oIter =
    1177           1 :                     aosClasses.begin();
    1178             :                 std::vector<GMLFeatureClass *>::const_iterator oEndIter =
    1179           1 :                     aosClasses.end();
    1180           2 :                 while (oIter != oEndIter)
    1181             :                 {
    1182           1 :                     GMLFeatureClass *poClass = *oIter;
    1183             : 
    1184           1 :                     delete poClass;
    1185           1 :                     ++oIter;
    1186             :                 }
    1187           1 :                 aosClasses.resize(0);
    1188           1 :                 bHaveSchema = false;
    1189             :             }
    1190             : 
    1191         282 :             if (bHaveSchema)
    1192             :             {
    1193         273 :                 CPLDebug("GML", "Using %s", osXSDFilename.c_str());
    1194             :                 std::vector<GMLFeatureClass *>::const_iterator oIter =
    1195         273 :                     aosClasses.begin();
    1196             :                 std::vector<GMLFeatureClass *>::const_iterator oEndIter =
    1197         273 :                     aosClasses.end();
    1198         658 :                 while (oIter != oEndIter)
    1199             :                 {
    1200         386 :                     GMLFeatureClass *poClass = *oIter;
    1201             : 
    1202         386 :                     if (poClass->HasFeatureProperties())
    1203             :                     {
    1204           1 :                         bHasFeatureProperties = true;
    1205           1 :                         break;
    1206             :                     }
    1207         385 :                     ++oIter;
    1208             :                 }
    1209             : 
    1210         273 :                 oIter = aosClasses.begin();
    1211         661 :                 while (oIter != oEndIter)
    1212             :                 {
    1213         388 :                     GMLFeatureClass *poClass = *oIter;
    1214         388 :                     ++oIter;
    1215             : 
    1216             :                     // We have no way of knowing if the geometry type is 25D
    1217             :                     // when examining the xsd only, so if there was a hint
    1218             :                     // it is, we force to 25D.
    1219         388 :                     if (bHas3D && poClass->GetGeometryPropertyCount() == 1)
    1220             :                     {
    1221          35 :                         poClass->GetGeometryProperty(0)->SetType(
    1222             :                             wkbSetZ(static_cast<OGRwkbGeometryType>(
    1223             :                                 poClass->GetGeometryProperty(0)->GetType())));
    1224             :                     }
    1225             : 
    1226         388 :                     bool bAddClass = true;
    1227             :                     // If typenames are declared, only register the matching
    1228             :                     // classes, in case the XSD contains more layers, but not if
    1229             :                     // feature classes contain feature properties, in which case
    1230             :                     // we will have embedded features that will be reported as
    1231             :                     // top-level features.
    1232         388 :                     if (papszTypeNames != nullptr && !bHasFeatureProperties)
    1233             :                     {
    1234         178 :                         bAddClass = false;
    1235         178 :                         char **papszIter = papszTypeNames;
    1236         416 :                         while (*papszIter && !bAddClass)
    1237             :                         {
    1238         238 :                             const char *pszTypeName = *papszIter;
    1239         238 :                             if (strcmp(pszTypeName, poClass->GetName()) == 0)
    1240         177 :                                 bAddClass = true;
    1241         238 :                             papszIter++;
    1242             :                         }
    1243             : 
    1244             :                         // Retry by removing prefixes.
    1245         178 :                         if (!bAddClass)
    1246             :                         {
    1247           1 :                             papszIter = papszTypeNames;
    1248           2 :                             while (*papszIter && !bAddClass)
    1249             :                             {
    1250           1 :                                 const char *pszTypeName = *papszIter;
    1251           1 :                                 const char *pszColon = strchr(pszTypeName, ':');
    1252           1 :                                 if (pszColon)
    1253             :                                 {
    1254           1 :                                     pszTypeName = pszColon + 1;
    1255           1 :                                     if (strcmp(pszTypeName,
    1256           1 :                                                poClass->GetName()) == 0)
    1257             :                                     {
    1258           1 :                                         poClass->SetName(pszTypeName);
    1259           1 :                                         bAddClass = true;
    1260             :                                     }
    1261             :                                 }
    1262           1 :                                 papszIter++;
    1263             :                             }
    1264             :                         }
    1265             :                     }
    1266             : 
    1267         776 :                     if (bAddClass &&
    1268         388 :                         poReader->GetClass(poClass->GetName()) == nullptr)
    1269             :                     {
    1270         388 :                         poReader->AddClass(poClass);
    1271             :                     }
    1272             :                     else
    1273           0 :                         delete poClass;
    1274             :                 }
    1275             : 
    1276         273 :                 poReader->SetClassListLocked(true);
    1277             :             }
    1278             :         }
    1279             : 
    1280         385 :         if (bHaveSchema && bIsWFS)
    1281             :         {
    1282         136 :             if (bIsWFSJointLayer)
    1283             :             {
    1284          47 :                 BuildJointClassFromXSD();
    1285             :             }
    1286             : 
    1287             :             // For WFS, we can assume sequential layers.
    1288         155 :             if (poReader->GetClassCount() > 1 && pszReadMode == nullptr &&
    1289          19 :                 !bHasFeatureProperties)
    1290             :             {
    1291          19 :                 CPLDebug("GML",
    1292             :                          "WFS output. Using SEQUENTIAL_LAYERS read mode");
    1293          19 :                 eReadMode = SEQUENTIAL_LAYERS;
    1294             :             }
    1295             :             // Sometimes the returned schema contains only <xs:include> that we
    1296             :             // don't resolve so ignore it.
    1297         117 :             else if (poReader->GetClassCount() == 0)
    1298           0 :                 bHaveSchema = false;
    1299             :         }
    1300             : 
    1301         385 :         CSLDestroy(papszTypeNames);
    1302             :     }
    1303             : 
    1304             :     // Force a first pass to establish the schema.  Eventually we will have
    1305             :     // mechanisms for remembering the schema and related information.
    1306             :     const char *pszForceSRSDetection =
    1307         430 :         CSLFetchNameValue(poOpenInfo->papszOpenOptions, "FORCE_SRS_DETECTION");
    1308         439 :     if (!bHaveSchema ||
    1309           9 :         (pszForceSRSDetection && CPLTestBool(pszForceSRSDetection)))
    1310             :     {
    1311         112 :         bool bOnlyDetectSRS = bHaveSchema;
    1312         112 :         if (!poReader->PrescanForSchema(true, bOnlyDetectSRS))
    1313             :         {
    1314             :             // Assume an error was reported.
    1315           0 :             return false;
    1316             :         }
    1317         112 :         if (!bHaveSchema)
    1318             :         {
    1319         103 :             if (bIsWFSJointLayer && poReader->GetClassCount() == 1)
    1320             :             {
    1321           2 :                 BuildJointClassFromScannedSchema();
    1322             :             }
    1323             : 
    1324         103 :             if (bHasFoundXSD)
    1325             :             {
    1326           9 :                 CPLDebug("GML", "Generating %s file, ignoring %s",
    1327             :                          osGFSFilename.c_str(), osXSDFilename.c_str());
    1328             :             }
    1329             :         }
    1330             :     }
    1331             : 
    1332         430 :     if (poReader->GetClassCount() > 1 && poReader->IsSequentialLayers() &&
    1333             :         pszReadMode == nullptr)
    1334             :     {
    1335          21 :         CPLDebug("GML",
    1336             :                  "Layers are monoblock. Using SEQUENTIAL_LAYERS read mode");
    1337          21 :         eReadMode = SEQUENTIAL_LAYERS;
    1338             :     }
    1339             : 
    1340         430 :     if (!DealWithOgrSchemaOpenOption(poOpenInfo))
    1341             :     {
    1342          10 :         return false;
    1343             :     }
    1344             : 
    1345             :     // Save the schema file if possible.  Don't make a fuss if we
    1346             :     // can't.  It could be read-only directory or something.
    1347             :     const char *pszWriteGFS =
    1348         420 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "WRITE_GFS", "AUTO");
    1349         420 :     bool bWriteGFS = false;
    1350         420 :     if (EQUAL(pszWriteGFS, "AUTO"))
    1351             :     {
    1352         101 :         if (!bHaveSchema && !poReader->HasStoppedParsing() &&
    1353         615 :             VSIIsLocal(pszFilename) &&
    1354          97 :             VSISupportsSequentialWrite(pszFilename, false))
    1355             :         {
    1356             :             VSIStatBufL sGFSStatBuf;
    1357          97 :             if (VSIStatExL(osGFSFilename, &sGFSStatBuf, VSI_STAT_EXISTS_FLAG) !=
    1358             :                 0)
    1359             :             {
    1360          93 :                 bWriteGFS = true;
    1361             :             }
    1362             :             else
    1363             :             {
    1364           4 :                 CPLDebug("GML", "Not saving %s file: already exists.",
    1365             :                          osGFSFilename.c_str());
    1366             :             }
    1367             :         }
    1368             :     }
    1369           3 :     else if (CPLTestBool(pszWriteGFS))
    1370             :     {
    1371           1 :         if (bHaveSchema || !poReader->HasStoppedParsing())
    1372             :         {
    1373           1 :             bWriteGFS = true;
    1374             :         }
    1375             :         else
    1376             :         {
    1377           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    1378             :                      "GFS file saving asked, but cannot be done");
    1379             :         }
    1380             :     }
    1381             : 
    1382         420 :     if (bWriteGFS)
    1383             :     {
    1384          94 :         if (!poReader->SaveClasses(osGFSFilename))
    1385             :         {
    1386           0 :             if (CPLTestBool(pszWriteGFS))
    1387             :             {
    1388           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1389             :                          "GFS file saving asked, but failed");
    1390             :             }
    1391             :             else
    1392             :             {
    1393           0 :                 CPLDebug("GML", "Not saving %s file: can't be created.",
    1394             :                          osGFSFilename.c_str());
    1395             :             }
    1396             :         }
    1397             :     }
    1398             : 
    1399             :     // Translate the GMLFeatureClasses into layers.
    1400         420 :     papoLayers = static_cast<OGRLayer **>(
    1401         420 :         CPLCalloc(sizeof(OGRLayer *), poReader->GetClassCount()));
    1402         420 :     nLayers = 0;
    1403             : 
    1404         420 :     if (poReader->GetClassCount() == 1 && nNumberOfFeatures != 0)
    1405             :     {
    1406         117 :         GMLFeatureClass *poClass = poReader->GetClass(0);
    1407         117 :         GIntBig nFeatureCount = poClass->GetFeatureCount();
    1408         117 :         if (nFeatureCount < 0)
    1409             :         {
    1410         106 :             poClass->SetFeatureCount(nNumberOfFeatures);
    1411             :         }
    1412          11 :         else if (nFeatureCount != nNumberOfFeatures)
    1413             :         {
    1414           0 :             CPLDebug("GML", "Feature count in header, "
    1415             :                             "and actual feature count don't match");
    1416             :         }
    1417             :     }
    1418             : 
    1419         420 :     if (bIsWFS && poReader->GetClassCount() == 1)
    1420         137 :         bUseGlobalSRSName = true;
    1421             : 
    1422         976 :     while (nLayers < poReader->GetClassCount())
    1423             :     {
    1424         556 :         papoLayers[nLayers] = TranslateGMLSchema(poReader->GetClass(nLayers));
    1425         556 :         nLayers++;
    1426             :     }
    1427             : 
    1428             :     // Warn if we have geometry columns without known CRS due to only using
    1429             :     // the .xsd
    1430         420 :     if (bHaveSchema && pszForceSRSDetection == nullptr)
    1431             :     {
    1432         308 :         bool bExitLoop = false;
    1433         659 :         for (int i = 0; !bExitLoop && i < nLayers; ++i)
    1434             :         {
    1435         351 :             const auto poLayer = papoLayers[i];
    1436         351 :             const auto poLayerDefn = poLayer->GetLayerDefn();
    1437         351 :             const auto nGeomFieldCount = poLayerDefn->GetGeomFieldCount();
    1438         481 :             for (int j = 0; j < nGeomFieldCount; ++j)
    1439             :             {
    1440         366 :                 if (poLayerDefn->GetGeomFieldDefn(j)->GetSpatialRef() ==
    1441             :                     nullptr)
    1442             :                 {
    1443         236 :                     bExitLoop = true;
    1444         236 :                     break;
    1445             :                 }
    1446             :             }
    1447             :         }
    1448         308 :         if (bExitLoop)
    1449             :         {
    1450         236 :             CPLDebug("GML",
    1451             :                      "Geometry fields without known CRS have been detected. "
    1452             :                      "You may want to specify the FORCE_SRS_DETECTION open "
    1453             :                      "option to YES.");
    1454             :         }
    1455             :     }
    1456             : 
    1457         420 :     return true;
    1458             : }
    1459             : 
    1460             : /************************************************************************/
    1461             : /*                          BuildJointClassFromXSD()                    */
    1462             : /************************************************************************/
    1463             : 
    1464          47 : void OGRGMLDataSource::BuildJointClassFromXSD()
    1465             : {
    1466          94 :     CPLString osJointClassName = "join";
    1467         141 :     for (int i = 0; i < poReader->GetClassCount(); i++)
    1468             :     {
    1469          94 :         osJointClassName += "_";
    1470          94 :         osJointClassName += poReader->GetClass(i)->GetName();
    1471             :     }
    1472          47 :     GMLFeatureClass *poJointClass = new GMLFeatureClass(osJointClassName);
    1473          47 :     poJointClass->SetElementName("Tuple");
    1474         141 :     for (int i = 0; i < poReader->GetClassCount(); i++)
    1475             :     {
    1476          94 :         GMLFeatureClass *poClass = poReader->GetClass(i);
    1477             : 
    1478             :         {
    1479         188 :             CPLString osPropertyName;
    1480          94 :             osPropertyName.Printf("%s.%s", poClass->GetName(), "gml_id");
    1481             :             GMLPropertyDefn *poNewProperty =
    1482          94 :                 new GMLPropertyDefn(osPropertyName);
    1483         188 :             CPLString osSrcElement;
    1484          94 :             osSrcElement.Printf("member|%s@id", poClass->GetName());
    1485          94 :             poNewProperty->SetSrcElement(osSrcElement);
    1486          94 :             poNewProperty->SetType(GMLPT_String);
    1487          94 :             poJointClass->AddProperty(poNewProperty);
    1488             :         }
    1489             : 
    1490         200 :         for (int iField = 0; iField < poClass->GetPropertyCount(); iField++)
    1491             :         {
    1492         106 :             GMLPropertyDefn *poProperty = poClass->GetProperty(iField);
    1493         212 :             CPLString osPropertyName;
    1494             :             osPropertyName.Printf("%s.%s", poClass->GetName(),
    1495         106 :                                   poProperty->GetName());
    1496             :             GMLPropertyDefn *poNewProperty =
    1497         106 :                 new GMLPropertyDefn(osPropertyName);
    1498             : 
    1499         106 :             poNewProperty->SetType(poProperty->GetType());
    1500         212 :             CPLString osSrcElement;
    1501             :             osSrcElement.Printf("member|%s|%s", poClass->GetName(),
    1502         106 :                                 poProperty->GetSrcElement());
    1503         106 :             poNewProperty->SetSrcElement(osSrcElement);
    1504         106 :             poNewProperty->SetWidth(poProperty->GetWidth());
    1505         106 :             poNewProperty->SetPrecision(poProperty->GetPrecision());
    1506         106 :             poNewProperty->SetNullable(poProperty->IsNullable());
    1507             : 
    1508         106 :             poJointClass->AddProperty(poNewProperty);
    1509             :         }
    1510         188 :         for (int iField = 0; iField < poClass->GetGeometryPropertyCount();
    1511             :              iField++)
    1512             :         {
    1513             :             GMLGeometryPropertyDefn *poProperty =
    1514          94 :                 poClass->GetGeometryProperty(iField);
    1515         188 :             CPLString osPropertyName;
    1516             :             osPropertyName.Printf("%s.%s", poClass->GetName(),
    1517          94 :                                   poProperty->GetName());
    1518         188 :             CPLString osSrcElement;
    1519             :             osSrcElement.Printf("member|%s|%s", poClass->GetName(),
    1520          94 :                                 poProperty->GetSrcElement());
    1521             :             GMLGeometryPropertyDefn *poNewProperty =
    1522             :                 new GMLGeometryPropertyDefn(osPropertyName, osSrcElement,
    1523          94 :                                             poProperty->GetType(), -1,
    1524         188 :                                             poProperty->IsNullable());
    1525          94 :             poJointClass->AddGeometryProperty(poNewProperty);
    1526             :         }
    1527             :     }
    1528          47 :     poJointClass->SetSchemaLocked(true);
    1529             : 
    1530          47 :     poReader->ClearClasses();
    1531          47 :     poReader->AddClass(poJointClass);
    1532          47 : }
    1533             : 
    1534             : /************************************************************************/
    1535             : /*                   BuildJointClassFromScannedSchema()                 */
    1536             : /************************************************************************/
    1537             : 
    1538           2 : void OGRGMLDataSource::BuildJointClassFromScannedSchema()
    1539             : {
    1540             :     // Make sure that all properties of a same base feature type are
    1541             :     // consecutive. If not, reorder.
    1542           4 :     std::vector<std::vector<GMLPropertyDefn *>> aapoProps;
    1543           2 :     GMLFeatureClass *poClass = poReader->GetClass(0);
    1544           4 :     CPLString osJointClassName = "join";
    1545             : 
    1546          14 :     for (int iField = 0; iField < poClass->GetPropertyCount(); iField++)
    1547             :     {
    1548          12 :         GMLPropertyDefn *poProp = poClass->GetProperty(iField);
    1549          24 :         CPLString osPrefix(poProp->GetName());
    1550          12 :         size_t iPos = osPrefix.find('.');
    1551          12 :         if (iPos != std::string::npos)
    1552          12 :             osPrefix.resize(iPos);
    1553          12 :         int iSubClass = 0;  // Used after for.
    1554          18 :         for (; iSubClass < static_cast<int>(aapoProps.size()); iSubClass++)
    1555             :         {
    1556          14 :             CPLString osPrefixClass(aapoProps[iSubClass][0]->GetName());
    1557          14 :             iPos = osPrefixClass.find('.');
    1558          14 :             if (iPos != std::string::npos)
    1559          14 :                 osPrefixClass.resize(iPos);
    1560          14 :             if (osPrefix == osPrefixClass)
    1561           8 :                 break;
    1562             :         }
    1563          12 :         if (iSubClass == static_cast<int>(aapoProps.size()))
    1564             :         {
    1565           4 :             osJointClassName += "_";
    1566           4 :             osJointClassName += osPrefix;
    1567           4 :             aapoProps.push_back(std::vector<GMLPropertyDefn *>());
    1568             :         }
    1569          12 :         aapoProps[iSubClass].push_back(poProp);
    1570             :     }
    1571           2 :     poClass->SetElementName(poClass->GetName());
    1572           2 :     poClass->SetName(osJointClassName);
    1573             : 
    1574           2 :     poClass->StealProperties();
    1575             :     std::vector<std::pair<CPLString, std::vector<GMLGeometryPropertyDefn *>>>
    1576           4 :         aapoGeomProps;
    1577           6 :     for (int iSubClass = 0; iSubClass < static_cast<int>(aapoProps.size());
    1578             :          iSubClass++)
    1579             :     {
    1580           8 :         CPLString osPrefixClass(aapoProps[iSubClass][0]->GetName());
    1581           4 :         size_t iPos = osPrefixClass.find('.');
    1582           4 :         if (iPos != std::string::npos)
    1583           4 :             osPrefixClass.resize(iPos);
    1584             :         aapoGeomProps.emplace_back(
    1585           4 :             std::pair(osPrefixClass, std::vector<GMLGeometryPropertyDefn *>()));
    1586          16 :         for (int iField = 0;
    1587          16 :              iField < static_cast<int>(aapoProps[iSubClass].size()); iField++)
    1588             :         {
    1589          12 :             poClass->AddProperty(aapoProps[iSubClass][iField]);
    1590             :         }
    1591             :     }
    1592           2 :     aapoProps.resize(0);
    1593             : 
    1594             :     // Reorder geometry fields too
    1595           6 :     for (int iField = 0; iField < poClass->GetGeometryPropertyCount(); iField++)
    1596             :     {
    1597           4 :         GMLGeometryPropertyDefn *poProp = poClass->GetGeometryProperty(iField);
    1598           8 :         CPLString osPrefix(poProp->GetName());
    1599           4 :         size_t iPos = osPrefix.find('.');
    1600           4 :         if (iPos != std::string::npos)
    1601           4 :             osPrefix.resize(iPos);
    1602           4 :         int iSubClass = 0;  // Used after for.
    1603           6 :         for (; iSubClass < static_cast<int>(aapoGeomProps.size()); iSubClass++)
    1604             :         {
    1605           6 :             if (osPrefix == aapoGeomProps[iSubClass].first)
    1606           4 :                 break;
    1607             :         }
    1608           4 :         if (iSubClass == static_cast<int>(aapoGeomProps.size()))
    1609             :             aapoGeomProps.emplace_back(
    1610           0 :                 std::pair(osPrefix, std::vector<GMLGeometryPropertyDefn *>()));
    1611           4 :         aapoGeomProps[iSubClass].second.push_back(poProp);
    1612             :     }
    1613           2 :     poClass->StealGeometryProperties();
    1614           6 :     for (int iSubClass = 0; iSubClass < static_cast<int>(aapoGeomProps.size());
    1615             :          iSubClass++)
    1616             :     {
    1617           8 :         for (int iField = 0;
    1618           8 :              iField < static_cast<int>(aapoGeomProps[iSubClass].second.size());
    1619             :              iField++)
    1620             :         {
    1621           4 :             poClass->AddGeometryProperty(
    1622           4 :                 aapoGeomProps[iSubClass].second[iField]);
    1623             :         }
    1624             :     }
    1625           2 : }
    1626             : 
    1627             : /************************************************************************/
    1628             : /*                         TranslateGMLSchema()                         */
    1629             : /************************************************************************/
    1630             : 
    1631         556 : OGRGMLLayer *OGRGMLDataSource::TranslateGMLSchema(GMLFeatureClass *poClass)
    1632             : 
    1633             : {
    1634             :     // Create an empty layer.
    1635         556 :     const char *pszSRSName = poClass->GetSRSName();
    1636         556 :     OGRSpatialReference *poSRS = nullptr;
    1637         556 :     if (pszSRSName)
    1638             :     {
    1639         118 :         poSRS = new OGRSpatialReference();
    1640         118 :         poSRS->SetAxisMappingStrategy(m_bInvertAxisOrderIfLatLong
    1641             :                                           ? OAMS_TRADITIONAL_GIS_ORDER
    1642             :                                           : OAMS_AUTHORITY_COMPLIANT);
    1643         118 :         if (poSRS->SetFromUserInput(
    1644             :                 pszSRSName,
    1645         118 :                 OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()) !=
    1646             :             OGRERR_NONE)
    1647             :         {
    1648           0 :             delete poSRS;
    1649           0 :             poSRS = nullptr;
    1650             :         }
    1651             :     }
    1652             :     else
    1653             :     {
    1654         438 :         pszSRSName = GetGlobalSRSName();
    1655             : 
    1656         438 :         if (pszSRSName && GML_IsLegitSRSName(pszSRSName))
    1657             :         {
    1658          35 :             poSRS = new OGRSpatialReference();
    1659          35 :             poSRS->SetAxisMappingStrategy(m_bInvertAxisOrderIfLatLong
    1660             :                                               ? OAMS_TRADITIONAL_GIS_ORDER
    1661             :                                               : OAMS_AUTHORITY_COMPLIANT);
    1662          35 :             if (poSRS->SetFromUserInput(
    1663             :                     pszSRSName,
    1664             :                     OGRSpatialReference::
    1665          35 :                         SET_FROM_USER_INPUT_LIMITATIONS_get()) != OGRERR_NONE)
    1666             :             {
    1667           0 :                 delete poSRS;
    1668           0 :                 poSRS = nullptr;
    1669             :             }
    1670             : 
    1671          70 :             if (poSRS != nullptr && m_bInvertAxisOrderIfLatLong &&
    1672          35 :                 GML_IsSRSLatLongOrder(pszSRSName))
    1673             :             {
    1674          23 :                 if (!poClass->HasExtents() && sBoundingRect.IsInit())
    1675             :                 {
    1676          23 :                     poClass->SetExtents(sBoundingRect.MinY, sBoundingRect.MaxY,
    1677             :                                         sBoundingRect.MinX, sBoundingRect.MaxX);
    1678             :                 }
    1679             :             }
    1680             :         }
    1681             : 
    1682         438 :         if (!poClass->HasExtents() && sBoundingRect.IsInit())
    1683             :         {
    1684          12 :             poClass->SetExtents(sBoundingRect.MinX, sBoundingRect.MaxX,
    1685             :                                 sBoundingRect.MinY, sBoundingRect.MaxY);
    1686             :         }
    1687             :     }
    1688             : 
    1689             :     // Report a COMPD_CS only if GML_REPORT_COMPD_CS is explicitly set to TRUE.
    1690         556 :     if (poSRS != nullptr && poSRS->IsCompound())
    1691             :     {
    1692             :         const char *pszReportCompdCS =
    1693           8 :             CPLGetConfigOption("GML_REPORT_COMPD_CS", nullptr);
    1694           8 :         if (pszReportCompdCS == nullptr)
    1695             :         {
    1696           8 :             CPLDebug("GML", "Compound CRS detected but only horizontal part "
    1697             :                             "will be reported. Set the GML_REPORT_COMPD_CS=YES "
    1698             :                             "configuration option to get the Compound CRS");
    1699           8 :             pszReportCompdCS = "FALSE";
    1700             :         }
    1701           8 :         if (!CPLTestBool(pszReportCompdCS))
    1702             :         {
    1703           8 :             OGR_SRSNode *poCOMPD_CS = poSRS->GetAttrNode("COMPD_CS");
    1704           8 :             if (poCOMPD_CS != nullptr)
    1705             :             {
    1706           8 :                 OGR_SRSNode *poCandidateRoot = poCOMPD_CS->GetNode("PROJCS");
    1707           8 :                 if (poCandidateRoot == nullptr)
    1708           1 :                     poCandidateRoot = poCOMPD_CS->GetNode("GEOGCS");
    1709           8 :                 if (poCandidateRoot != nullptr)
    1710             :                 {
    1711           8 :                     poSRS->SetRoot(poCandidateRoot->Clone());
    1712             :                 }
    1713             :             }
    1714             :         }
    1715             :     }
    1716             : 
    1717         556 :     OGRGMLLayer *poLayer = new OGRGMLLayer(poClass->GetName(), false, this);
    1718             : 
    1719             :     // Added attributes (properties).
    1720         556 :     if (bExposeGMLId)
    1721             :     {
    1722         822 :         OGRFieldDefn oField("gml_id", OFTString);
    1723         411 :         oField.SetNullable(FALSE);
    1724         411 :         poLayer->GetLayerDefn()->AddFieldDefn(&oField);
    1725             :     }
    1726         145 :     else if (bExposeFid)
    1727             :     {
    1728         110 :         OGRFieldDefn oField("fid", OFTString);
    1729          55 :         oField.SetNullable(FALSE);
    1730          55 :         poLayer->GetLayerDefn()->AddFieldDefn(&oField);
    1731             :     }
    1732             : 
    1733        1171 :     for (int iField = 0; iField < poClass->GetGeometryPropertyCount(); iField++)
    1734             :     {
    1735             :         GMLGeometryPropertyDefn *poProperty =
    1736         615 :             poClass->GetGeometryProperty(iField);
    1737             : 
    1738             :         // Patch wrong .gfs file produced by earlier versions
    1739         615 :         if (poProperty->GetType() == wkbPolyhedralSurface &&
    1740           0 :             strcmp(poProperty->GetName(), "lod2Solid") == 0)
    1741             :         {
    1742           0 :             poProperty->SetType(wkbPolyhedralSurfaceZ);
    1743             :         }
    1744             : 
    1745        1230 :         OGRGeomFieldDefn oField(poProperty->GetName(), poProperty->GetType());
    1746        1036 :         if (poClass->GetGeometryPropertyCount() == 1 &&
    1747         421 :             poClass->GetFeatureCount() == 0)
    1748             :         {
    1749           0 :             oField.SetType(wkbUnknown);
    1750             :         }
    1751             : 
    1752         615 :         const auto &osSRSName = poProperty->GetSRSName();
    1753         615 :         if (!osSRSName.empty())
    1754             :         {
    1755          30 :             OGRSpatialReference *poSRS2 = new OGRSpatialReference();
    1756          30 :             poSRS2->SetAxisMappingStrategy(m_bInvertAxisOrderIfLatLong
    1757             :                                                ? OAMS_TRADITIONAL_GIS_ORDER
    1758             :                                                : OAMS_AUTHORITY_COMPLIANT);
    1759          30 :             if (poSRS2->SetFromUserInput(
    1760             :                     osSRSName.c_str(),
    1761             :                     OGRSpatialReference::
    1762          30 :                         SET_FROM_USER_INPUT_LIMITATIONS_get()) == OGRERR_NONE)
    1763             :             {
    1764          30 :                 oField.SetSpatialRef(poSRS2);
    1765             :             }
    1766          30 :             poSRS2->Release();
    1767             :         }
    1768             :         else
    1769             :         {
    1770         585 :             oField.SetSpatialRef(poSRS);
    1771             :         }
    1772         615 :         oField.SetNullable(poProperty->IsNullable());
    1773         615 :         oField.SetCoordinatePrecision(poProperty->GetCoordinatePrecision());
    1774         615 :         poLayer->GetLayerDefn()->AddGeomFieldDefn(&oField);
    1775             :     }
    1776             : 
    1777         556 :     if (poReader->GetClassCount() == 1)
    1778             :     {
    1779         326 :         int iInsertPos = 0;
    1780         331 :         for (const auto &osElt : m_aosGMLExtraElements)
    1781             :         {
    1782             :             GMLPropertyDefn *poProperty =
    1783           5 :                 new GMLPropertyDefn(osElt.c_str(), osElt.c_str());
    1784           5 :             poProperty->SetType(GMLPT_String);
    1785           5 :             if (poClass->AddProperty(poProperty, iInsertPos) == iInsertPos)
    1786           3 :                 ++iInsertPos;
    1787             :             else
    1788           2 :                 delete poProperty;
    1789             :         }
    1790             :     }
    1791             : 
    1792        2709 :     for (int iField = 0; iField < poClass->GetPropertyCount(); iField++)
    1793             :     {
    1794        2153 :         GMLPropertyDefn *poProperty = poClass->GetProperty(iField);
    1795        2153 :         OGRFieldSubType eSubType = poProperty->GetSubType();
    1796             :         const OGRFieldType eFType =
    1797        2153 :             GML_GetOGRFieldType(poProperty->GetType(), eSubType);
    1798        4306 :         OGRFieldDefn oField(poProperty->GetName(), eFType);
    1799        2153 :         oField.SetSubType(eSubType);
    1800        2153 :         if (STARTS_WITH_CI(oField.GetNameRef(), "ogr:"))
    1801           0 :             oField.SetName(poProperty->GetName() + 4);
    1802        2153 :         if (poProperty->GetWidth() > 0)
    1803         640 :             oField.SetWidth(poProperty->GetWidth());
    1804        2153 :         if (poProperty->GetPrecision() > 0)
    1805          45 :             oField.SetPrecision(poProperty->GetPrecision());
    1806        2153 :         if (!bEmptyAsNull)
    1807           2 :             oField.SetNullable(poProperty->IsNullable());
    1808        2153 :         oField.SetUnique(poProperty->IsUnique());
    1809        2153 :         oField.SetComment(poProperty->GetDocumentation());
    1810        2153 :         poLayer->GetLayerDefn()->AddFieldDefn(&oField);
    1811             :     }
    1812             : 
    1813         556 :     if (poSRS != nullptr)
    1814         153 :         poSRS->Release();
    1815             : 
    1816         556 :     return poLayer;
    1817             : }
    1818             : 
    1819             : /************************************************************************/
    1820             : /*                         GetGlobalSRSName()                           */
    1821             : /************************************************************************/
    1822             : 
    1823        1059 : const char *OGRGMLDataSource::GetGlobalSRSName()
    1824             : {
    1825        1059 :     if (poReader->CanUseGlobalSRSName() || bUseGlobalSRSName)
    1826         486 :         return poReader->GetGlobalSRSName();
    1827             :     else
    1828         573 :         return nullptr;
    1829             : }
    1830             : 
    1831             : /************************************************************************/
    1832             : /*                               Create()                               */
    1833             : /************************************************************************/
    1834             : 
    1835         101 : bool OGRGMLDataSource::Create(const char *pszFilename, char **papszOptions)
    1836             : 
    1837             : {
    1838         101 :     if (fpOutput != nullptr || poReader != nullptr)
    1839             :     {
    1840           0 :         CPLAssert(false);
    1841             :         return false;
    1842             :     }
    1843             : 
    1844         101 :     if (strcmp(pszFilename, "/dev/stdout") == 0)
    1845           0 :         pszFilename = "/vsistdout/";
    1846             : 
    1847             :     // Read options.
    1848         101 :     CSLDestroy(papszCreateOptions);
    1849         101 :     papszCreateOptions = CSLDuplicate(papszOptions);
    1850             : 
    1851             :     const char *pszFormat =
    1852         101 :         CSLFetchNameValueDef(papszCreateOptions, "FORMAT", "GML3.2");
    1853         101 :     bIsOutputGML3 = EQUAL(pszFormat, "GML3");
    1854         101 :     bIsOutputGML3Deegree = EQUAL(pszFormat, "GML3Deegree");
    1855         101 :     bIsOutputGML32 = EQUAL(pszFormat, "GML3.2");
    1856         101 :     if (bIsOutputGML3Deegree || bIsOutputGML32)
    1857          79 :         bIsOutputGML3 = true;
    1858             : 
    1859         101 :     eSRSNameFormat = (bIsOutputGML3) ? SRSNAME_OGC_URN : SRSNAME_SHORT;
    1860         101 :     if (bIsOutputGML3)
    1861             :     {
    1862             :         const char *pszLongSRS =
    1863          91 :             CSLFetchNameValue(papszCreateOptions, "GML3_LONGSRS");
    1864             :         const char *pszSRSNameFormat =
    1865          91 :             CSLFetchNameValue(papszCreateOptions, "SRSNAME_FORMAT");
    1866          91 :         if (pszSRSNameFormat)
    1867             :         {
    1868           6 :             if (pszLongSRS)
    1869             :             {
    1870           0 :                 CPLError(CE_Warning, CPLE_NotSupported,
    1871             :                          "Both GML3_LONGSRS and SRSNAME_FORMAT specified. "
    1872             :                          "Ignoring GML3_LONGSRS");
    1873             :             }
    1874           6 :             if (EQUAL(pszSRSNameFormat, "SHORT"))
    1875           1 :                 eSRSNameFormat = SRSNAME_SHORT;
    1876           5 :             else if (EQUAL(pszSRSNameFormat, "OGC_URN"))
    1877           1 :                 eSRSNameFormat = SRSNAME_OGC_URN;
    1878           4 :             else if (EQUAL(pszSRSNameFormat, "OGC_URL"))
    1879           4 :                 eSRSNameFormat = SRSNAME_OGC_URL;
    1880             :             else
    1881             :             {
    1882           0 :                 CPLError(CE_Warning, CPLE_NotSupported,
    1883             :                          "Invalid value for SRSNAME_FORMAT. "
    1884             :                          "Using SRSNAME_OGC_URN");
    1885             :             }
    1886             :         }
    1887          85 :         else if (pszLongSRS && !CPLTestBool(pszLongSRS))
    1888           0 :             eSRSNameFormat = SRSNAME_SHORT;
    1889             :     }
    1890             : 
    1891         101 :     bWriteSpaceIndentation = CPLTestBool(
    1892         101 :         CSLFetchNameValueDef(papszCreateOptions, "SPACE_INDENTATION", "YES"));
    1893             : 
    1894             :     // Create the output file.
    1895         101 :     osFilename = pszFilename;
    1896         101 :     SetDescription(pszFilename);
    1897             : 
    1898         101 :     if (strcmp(pszFilename, "/vsistdout/") == 0 ||
    1899         101 :         STARTS_WITH(pszFilename, "/vsigzip/"))
    1900             :     {
    1901           0 :         fpOutput = VSIFOpenExL(pszFilename, "wb", true);
    1902           0 :         bFpOutputIsNonSeekable = true;
    1903           0 :         bFpOutputSingleFile = true;
    1904             :     }
    1905         101 :     else if (STARTS_WITH(pszFilename, "/vsizip/"))
    1906             :     {
    1907           0 :         if (EQUAL(CPLGetExtensionSafe(pszFilename).c_str(), "zip"))
    1908             :         {
    1909           0 :             SetDescription(
    1910           0 :                 CPLFormFilenameSafe(pszFilename, "out.gml", nullptr).c_str());
    1911             :         }
    1912             : 
    1913           0 :         fpOutput = VSIFOpenExL(GetDescription(), "wb", true);
    1914           0 :         bFpOutputIsNonSeekable = true;
    1915             :     }
    1916             :     else
    1917         101 :         fpOutput = VSIFOpenExL(pszFilename, "wb+", true);
    1918         101 :     if (fpOutput == nullptr)
    1919             :     {
    1920           1 :         CPLError(CE_Failure, CPLE_OpenFailed,
    1921             :                  "Failed to create GML file %s: %s", pszFilename,
    1922             :                  VSIGetLastErrorMsg());
    1923           1 :         return false;
    1924             :     }
    1925             : 
    1926             :     // Write out "standard" header.
    1927         100 :     PrintLine(fpOutput, "%s", "<?xml version=\"1.0\" encoding=\"utf-8\" ?>");
    1928             : 
    1929         100 :     if (!bFpOutputIsNonSeekable)
    1930         100 :         nSchemaInsertLocation = static_cast<int>(VSIFTellL(fpOutput));
    1931             : 
    1932         100 :     const char *pszPrefix = GetAppPrefix();
    1933         100 :     const char *pszTargetNameSpace = CSLFetchNameValueDef(
    1934             :         papszOptions, "TARGET_NAMESPACE", "http://ogr.maptools.org/");
    1935             : 
    1936         100 :     if (GMLFeatureCollection())
    1937           1 :         PrintLine(fpOutput, "<gml:FeatureCollection");
    1938          99 :     else if (RemoveAppPrefix())
    1939           2 :         PrintLine(fpOutput, "<FeatureCollection");
    1940             :     else
    1941          97 :         PrintLine(fpOutput, "<%s:FeatureCollection", pszPrefix);
    1942             : 
    1943         100 :     if (IsGML32Output())
    1944             :     {
    1945          77 :         char *pszGMLId = CPLEscapeString(
    1946             :             CSLFetchNameValueDef(papszOptions, "GML_ID", "aFeatureCollection"),
    1947             :             -1, CPLES_XML);
    1948          77 :         PrintLine(fpOutput, "     gml:id=\"%s\"", pszGMLId);
    1949          77 :         CPLFree(pszGMLId);
    1950             :     }
    1951             : 
    1952             :     // Write out schema info if provided in creation options.
    1953         100 :     const char *pszSchemaURI = CSLFetchNameValue(papszOptions, "XSISCHEMAURI");
    1954         100 :     const char *pszSchemaOpt = CSLFetchNameValue(papszOptions, "XSISCHEMA");
    1955             : 
    1956         100 :     if (pszSchemaURI != nullptr)
    1957             :     {
    1958           0 :         PrintLine(
    1959             :             fpOutput,
    1960             :             "     xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
    1961           0 :         PrintLine(fpOutput, "     xsi:schemaLocation=\"%s\"", pszSchemaURI);
    1962             :     }
    1963         100 :     else if (pszSchemaOpt == nullptr || EQUAL(pszSchemaOpt, "EXTERNAL"))
    1964             :     {
    1965             :         char *pszBasename =
    1966          98 :             CPLStrdup(CPLGetBasenameSafe(GetDescription()).c_str());
    1967             : 
    1968          98 :         PrintLine(
    1969             :             fpOutput,
    1970             :             "     xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
    1971          98 :         PrintLine(fpOutput, "     xsi:schemaLocation=\"%s %s\"",
    1972             :                   pszTargetNameSpace,
    1973         196 :                   CPLResetExtensionSafe(pszBasename, "xsd").c_str());
    1974          98 :         CPLFree(pszBasename);
    1975             :     }
    1976             : 
    1977         100 :     if (RemoveAppPrefix())
    1978           2 :         PrintLine(fpOutput, "     xmlns=\"%s\"", pszTargetNameSpace);
    1979             :     else
    1980          98 :         PrintLine(fpOutput, "     xmlns:%s=\"%s\"", pszPrefix,
    1981             :                   pszTargetNameSpace);
    1982             : 
    1983         100 :     if (IsGML32Output())
    1984          77 :         PrintLine(fpOutput, "%s",
    1985             :                   "     xmlns:gml=\"http://www.opengis.net/gml/3.2\">");
    1986             :     else
    1987          23 :         PrintLine(fpOutput, "%s",
    1988             :                   "     xmlns:gml=\"http://www.opengis.net/gml\">");
    1989             : 
    1990         100 :     return true;
    1991             : }
    1992             : 
    1993             : /************************************************************************/
    1994             : /*                         WriteTopElements()                           */
    1995             : /************************************************************************/
    1996             : 
    1997         100 : void OGRGMLDataSource::WriteTopElements()
    1998             : {
    1999         300 :     const char *pszDescription = CSLFetchNameValueDef(
    2000         100 :         papszCreateOptions, "DESCRIPTION", GetMetadataItem("DESCRIPTION"));
    2001         100 :     if (pszDescription != nullptr)
    2002             :     {
    2003           2 :         if (bWriteSpaceIndentation)
    2004           2 :             VSIFPrintfL(fpOutput, "  ");
    2005           2 :         char *pszTmp = CPLEscapeString(pszDescription, -1, CPLES_XML);
    2006           2 :         PrintLine(fpOutput, "<gml:description>%s</gml:description>", pszTmp);
    2007           2 :         CPLFree(pszTmp);
    2008             :     }
    2009             : 
    2010         100 :     const char *l_pszName = CSLFetchNameValueDef(papszCreateOptions, "NAME",
    2011         100 :                                                  GetMetadataItem("NAME"));
    2012         100 :     if (l_pszName != nullptr)
    2013             :     {
    2014           2 :         if (bWriteSpaceIndentation)
    2015           2 :             VSIFPrintfL(fpOutput, "  ");
    2016           2 :         char *pszTmp = CPLEscapeString(l_pszName, -1, CPLES_XML);
    2017           2 :         PrintLine(fpOutput, "<gml:name>%s</gml:name>", pszTmp);
    2018           2 :         CPLFree(pszTmp);
    2019             :     }
    2020             : 
    2021             :     // Should we initialize an area to place the boundedBy element?
    2022             :     // We will need to seek back to fill it in.
    2023         100 :     nBoundedByLocation = -1;
    2024         100 :     if (CPLFetchBool(papszCreateOptions, "BOUNDEDBY", true))
    2025             :     {
    2026         100 :         if (!bFpOutputIsNonSeekable)
    2027             :         {
    2028         100 :             nBoundedByLocation = static_cast<int>(VSIFTellL(fpOutput));
    2029             : 
    2030         100 :             if (nBoundedByLocation != -1)
    2031         100 :                 PrintLine(fpOutput, "%350s", "");
    2032             :         }
    2033             :         else
    2034             :         {
    2035           0 :             if (bWriteSpaceIndentation)
    2036           0 :                 VSIFPrintfL(fpOutput, "  ");
    2037           0 :             if (IsGML3Output())
    2038           0 :                 PrintLine(fpOutput,
    2039             :                           "<gml:boundedBy><gml:Null /></gml:boundedBy>");
    2040             :             else
    2041           0 :                 PrintLine(fpOutput, "<gml:boundedBy><gml:null>missing</"
    2042             :                                     "gml:null></gml:boundedBy>");
    2043             :         }
    2044             :     }
    2045         100 : }
    2046             : 
    2047             : /************************************************************************/
    2048             : /*                 DealWithOgrSchemaOpenOption()                        */
    2049             : /************************************************************************/
    2050             : 
    2051         430 : bool OGRGMLDataSource::DealWithOgrSchemaOpenOption(
    2052             :     const GDALOpenInfo *poOpenInfo)
    2053             : {
    2054             : 
    2055             :     std::string osFieldsSchemaOverrideParam =
    2056         860 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "OGR_SCHEMA", "");
    2057             : 
    2058         430 :     if (!osFieldsSchemaOverrideParam.empty())
    2059             :     {
    2060             : 
    2061             :         // GML driver does not support update at the moment so this will never happen
    2062          26 :         if (poOpenInfo->eAccess == GA_Update)
    2063             :         {
    2064           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    2065             :                      "OGR_SCHEMA open option is not supported in update mode.");
    2066          10 :             return false;
    2067             :         }
    2068             : 
    2069          26 :         OGRSchemaOverride osSchemaOverride;
    2070          50 :         if (!osSchemaOverride.LoadFromJSON(osFieldsSchemaOverrideParam) ||
    2071          24 :             !osSchemaOverride.IsValid())
    2072             :         {
    2073           2 :             return false;
    2074             :         }
    2075             : 
    2076          24 :         const auto &oLayerOverrides = osSchemaOverride.GetLayerOverrides();
    2077          42 :         for (const auto &oLayer : oLayerOverrides)
    2078             :         {
    2079          26 :             const auto &oLayerName = oLayer.first;
    2080          26 :             const auto &oLayerFieldOverride = oLayer.second;
    2081          26 :             const bool bIsFullOverride{oLayerFieldOverride.IsFullOverride()};
    2082          26 :             std::vector<GMLPropertyDefn *> aoProperties;
    2083             : 
    2084          26 :             CPLDebug("GML", "Applying schema override for layer %s",
    2085             :                      oLayerName.c_str());
    2086             : 
    2087             :             // Fail if the layer name does not exist
    2088          26 :             const auto oClass = poReader->GetClass(oLayerName.c_str());
    2089          26 :             if (oClass == nullptr)
    2090             :             {
    2091           6 :                 CPLError(CE_Failure, CPLE_AppDefined, "Layer %s not found",
    2092             :                          oLayerName.c_str());
    2093           6 :                 return false;
    2094             :             }
    2095             : 
    2096          20 :             const auto &oLayerFields = oLayerFieldOverride.GetFieldOverrides();
    2097          42 :             for (const auto &oFieldOverrideIt : oLayerFields)
    2098             :             {
    2099             :                 const auto oProperty =
    2100          24 :                     oClass->GetProperty(oFieldOverrideIt.first.c_str());
    2101          24 :                 if (oProperty == nullptr)
    2102             :                 {
    2103           2 :                     CPLError(CE_Failure, CPLE_AppDefined,
    2104             :                              "Field %s not found in layer %s",
    2105             :                              oFieldOverrideIt.first.c_str(),
    2106             :                              oLayerName.c_str());
    2107           2 :                     return false;
    2108             :                 }
    2109             : 
    2110          22 :                 const auto &oFieldOverride = oFieldOverrideIt.second;
    2111             : 
    2112             :                 const OGRFieldSubType eSubType =
    2113          22 :                     oFieldOverride.GetFieldSubType().value_or(OFSTNone);
    2114             : 
    2115          22 :                 if (oFieldOverride.GetFieldSubType().has_value())
    2116             :                 {
    2117           8 :                     oProperty->SetSubType(eSubType);
    2118             :                 }
    2119             : 
    2120          22 :                 if (oFieldOverride.GetFieldType().has_value())
    2121             :                 {
    2122          12 :                     oProperty->SetType(GML_FromOGRFieldType(
    2123          24 :                         oFieldOverride.GetFieldType().value(), eSubType));
    2124             :                 }
    2125             : 
    2126          22 :                 if (oFieldOverride.GetFieldName().has_value())
    2127             :                 {
    2128           4 :                     oProperty->SetName(
    2129           8 :                         oFieldOverride.GetFieldName().value().c_str());
    2130             :                 }
    2131             : 
    2132          22 :                 if (oFieldOverride.GetFieldWidth().has_value())
    2133             :                 {
    2134           2 :                     oProperty->SetWidth(oFieldOverride.GetFieldWidth().value());
    2135             :                 }
    2136             : 
    2137          22 :                 if (oFieldOverride.GetFieldPrecision().has_value())
    2138             :                 {
    2139           4 :                     oProperty->SetPrecision(
    2140           2 :                         oFieldOverride.GetFieldPrecision().value());
    2141             :                 }
    2142             : 
    2143          22 :                 if (bIsFullOverride)
    2144             :                 {
    2145           4 :                     aoProperties.push_back(oProperty);
    2146             :                 }
    2147             :             }
    2148             : 
    2149             :             // Remove fields not in the override
    2150          20 :             if (bIsFullOverride &&
    2151           2 :                 aoProperties.size() !=
    2152           2 :                     static_cast<size_t>(oClass->GetPropertyCount()))
    2153             :             {
    2154           8 :                 for (int j = 0; j < oClass->GetPropertyCount(); ++j)
    2155             :                 {
    2156           6 :                     const auto oProperty = oClass->GetProperty(j);
    2157           6 :                     if (std::find(aoProperties.begin(), aoProperties.end(),
    2158           6 :                                   oProperty) == aoProperties.end())
    2159             :                     {
    2160           2 :                         delete (oProperty);
    2161             :                     }
    2162             :                 }
    2163             : 
    2164           2 :                 oClass->StealProperties();
    2165             : 
    2166           6 :                 for (const auto &oProperty : aoProperties)
    2167             :                 {
    2168           4 :                     oClass->AddProperty(oProperty);
    2169             :                 }
    2170             :             }
    2171             :         }
    2172             :     }
    2173         420 :     return true;
    2174             : }
    2175             : 
    2176             : /************************************************************************/
    2177             : /*                         DeclareNewWriteSRS()                         */
    2178             : /************************************************************************/
    2179             : 
    2180             : // Check that all SRS passed to ICreateLayer() and CreateGeomField()
    2181             : // are the same (or all null)
    2182             : 
    2183         124 : void OGRGMLDataSource::DeclareNewWriteSRS(const OGRSpatialReference *poSRS)
    2184             : {
    2185         124 :     if (m_bWriteGlobalSRS)
    2186             :     {
    2187         124 :         if (!m_bWriteGlobalSRSInit)
    2188             :         {
    2189          91 :             m_bWriteGlobalSRSInit = true;
    2190          91 :             if (poSRS)
    2191             :             {
    2192          27 :                 m_poWriteGlobalSRS.reset(poSRS->Clone());
    2193          27 :                 m_poWriteGlobalSRS->SetAxisMappingStrategy(
    2194             :                     OAMS_TRADITIONAL_GIS_ORDER);
    2195             :             }
    2196             :         }
    2197             :         else
    2198             :         {
    2199          33 :             if (m_poWriteGlobalSRS)
    2200             :             {
    2201           3 :                 const char *const apszOptions[] = {
    2202             :                     "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES", nullptr};
    2203           5 :                 if (!poSRS ||
    2204           2 :                     !poSRS->IsSame(m_poWriteGlobalSRS.get(), apszOptions))
    2205             :                 {
    2206           2 :                     m_bWriteGlobalSRS = false;
    2207             :                 }
    2208             :             }
    2209             :             else
    2210             :             {
    2211          30 :                 if (poSRS)
    2212           1 :                     m_bWriteGlobalSRS = false;
    2213             :             }
    2214             :         }
    2215             :     }
    2216         124 : }
    2217             : 
    2218             : /************************************************************************/
    2219             : /*                           ICreateLayer()                             */
    2220             : /************************************************************************/
    2221             : 
    2222             : OGRLayer *
    2223         126 : OGRGMLDataSource::ICreateLayer(const char *pszLayerName,
    2224             :                                const OGRGeomFieldDefn *poSrcGeomFieldDefn,
    2225             :                                CSLConstList /*papszOptions*/)
    2226             : {
    2227             :     // Verify we are in update mode.
    2228         126 :     if (fpOutput == nullptr)
    2229             :     {
    2230           0 :         CPLError(CE_Failure, CPLE_NoWriteAccess,
    2231             :                  "Data source %s opened for read access.\n"
    2232             :                  "New layer %s cannot be created.\n",
    2233           0 :                  GetDescription(), pszLayerName);
    2234             : 
    2235           0 :         return nullptr;
    2236             :     }
    2237             : 
    2238             :     const auto eType =
    2239         126 :         poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetType() : wkbNone;
    2240             :     const auto poSRS =
    2241         126 :         poSrcGeomFieldDefn ? poSrcGeomFieldDefn->GetSpatialRef() : nullptr;
    2242             : 
    2243             :     // Ensure name is safe as an element name.
    2244         126 :     char *pszCleanLayerName = CPLStrdup(pszLayerName);
    2245             : 
    2246         126 :     CPLCleanXMLElementName(pszCleanLayerName);
    2247         126 :     if (strcmp(pszCleanLayerName, pszLayerName) != 0)
    2248             :     {
    2249           0 :         CPLError(CE_Warning, CPLE_AppDefined,
    2250             :                  "Layer name '%s' adjusted to '%s' for XML validity.",
    2251             :                  pszLayerName, pszCleanLayerName);
    2252             :     }
    2253             : 
    2254         126 :     if (nLayers == 0)
    2255             :     {
    2256          97 :         WriteTopElements();
    2257             :     }
    2258             : 
    2259             :     // Create the layer object.
    2260         126 :     OGRGMLLayer *poLayer = new OGRGMLLayer(pszCleanLayerName, true, this);
    2261         126 :     poLayer->GetLayerDefn()->SetGeomType(eType);
    2262         126 :     if (eType != wkbNone)
    2263             :     {
    2264         105 :         auto poGeomFieldDefn = poLayer->GetLayerDefn()->GetGeomFieldDefn(0);
    2265         105 :         const char *pszGeomFieldName = poSrcGeomFieldDefn->GetNameRef();
    2266         105 :         if (!pszGeomFieldName || pszGeomFieldName[0] == 0)
    2267         104 :             pszGeomFieldName = "geometryProperty";
    2268         105 :         poGeomFieldDefn->SetName(pszGeomFieldName);
    2269         105 :         poGeomFieldDefn->SetNullable(poSrcGeomFieldDefn->IsNullable());
    2270         105 :         DeclareNewWriteSRS(poSRS);
    2271         105 :         if (poSRS != nullptr)
    2272             :         {
    2273          24 :             auto poSRSClone = poSRS->Clone();
    2274          24 :             poSRSClone->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    2275          24 :             poGeomFieldDefn->SetSpatialRef(poSRSClone);
    2276          24 :             poSRSClone->Dereference();
    2277             :         }
    2278         105 :         poGeomFieldDefn->SetCoordinatePrecision(
    2279             :             poSrcGeomFieldDefn->GetCoordinatePrecision());
    2280             :     }
    2281             : 
    2282         126 :     CPLFree(pszCleanLayerName);
    2283             : 
    2284             :     // Add layer to data source layer list.
    2285         126 :     papoLayers = static_cast<OGRLayer **>(
    2286         126 :         CPLRealloc(papoLayers, sizeof(OGRLayer *) * (nLayers + 1)));
    2287             : 
    2288         126 :     papoLayers[nLayers++] = poLayer;
    2289             : 
    2290         126 :     return poLayer;
    2291             : }
    2292             : 
    2293             : /************************************************************************/
    2294             : /*                           TestCapability()                           */
    2295             : /************************************************************************/
    2296             : 
    2297         172 : int OGRGMLDataSource::TestCapability(const char *pszCap)
    2298             : 
    2299             : {
    2300         172 :     if (EQUAL(pszCap, ODsCCreateLayer))
    2301          68 :         return TRUE;
    2302         104 :     else if (EQUAL(pszCap, ODsCCreateGeomFieldAfterCreateLayer))
    2303          42 :         return TRUE;
    2304          62 :     else if (EQUAL(pszCap, ODsCCurveGeometries))
    2305           5 :         return bIsOutputGML3;
    2306          57 :     else if (EQUAL(pszCap, ODsCZGeometries))
    2307           2 :         return TRUE;
    2308          55 :     else if (EQUAL(pszCap, ODsCRandomLayerWrite))
    2309           0 :         return TRUE;
    2310             :     else
    2311          55 :         return FALSE;
    2312             : }
    2313             : 
    2314             : /************************************************************************/
    2315             : /*                              GetLayer()                              */
    2316             : /************************************************************************/
    2317             : 
    2318         860 : OGRLayer *OGRGMLDataSource::GetLayer(int iLayer)
    2319             : 
    2320             : {
    2321         860 :     if (iLayer < 0 || iLayer >= nLayers)
    2322           7 :         return nullptr;
    2323             :     else
    2324         853 :         return papoLayers[iLayer];
    2325             : }
    2326             : 
    2327             : /************************************************************************/
    2328             : /*                            GrowExtents()                             */
    2329             : /************************************************************************/
    2330             : 
    2331         211 : void OGRGMLDataSource::GrowExtents(OGREnvelope3D *psGeomBounds,
    2332             :                                    int nCoordDimension)
    2333             : 
    2334             : {
    2335         211 :     sBoundingRect.Merge(*psGeomBounds);
    2336         211 :     if (nCoordDimension == 3)
    2337          46 :         bBBOX3D = true;
    2338         211 : }
    2339             : 
    2340             : /************************************************************************/
    2341             : /*                            InsertHeader()                            */
    2342             : /*                                                                      */
    2343             : /*      This method is used to update boundedby info for a              */
    2344             : /*      dataset, and insert schema descriptions depending on            */
    2345             : /*      selection options in effect.                                    */
    2346             : /************************************************************************/
    2347             : 
    2348          99 : void OGRGMLDataSource::InsertHeader()
    2349             : 
    2350             : {
    2351          99 :     int nSchemaStart = 0;
    2352             : 
    2353          99 :     if (bFpOutputSingleFile)
    2354           0 :         return;
    2355             : 
    2356             :     // Do we want to write the schema within the GML instance doc
    2357             :     // or to a separate file?  For now we only support external.
    2358             :     const char *pszSchemaURI =
    2359          99 :         CSLFetchNameValue(papszCreateOptions, "XSISCHEMAURI");
    2360             :     const char *pszSchemaOpt =
    2361          99 :         CSLFetchNameValue(papszCreateOptions, "XSISCHEMA");
    2362             : 
    2363          99 :     const bool bGMLFeatureCollection = GMLFeatureCollection();
    2364             : 
    2365          99 :     if (pszSchemaURI != nullptr)
    2366           0 :         return;
    2367             : 
    2368          99 :     VSILFILE *fpSchema = nullptr;
    2369          99 :     if (pszSchemaOpt == nullptr || EQUAL(pszSchemaOpt, "EXTERNAL"))
    2370             :     {
    2371             :         const std::string l_osXSDFilename =
    2372          98 :             CPLResetExtensionSafe(GetDescription(), "xsd");
    2373             : 
    2374          98 :         fpSchema = VSIFOpenL(l_osXSDFilename.c_str(), "wt");
    2375          98 :         if (fpSchema == nullptr)
    2376             :         {
    2377           0 :             CPLError(CE_Failure, CPLE_OpenFailed,
    2378             :                      "Failed to open file %.500s for schema output.",
    2379             :                      l_osXSDFilename.c_str());
    2380           0 :             return;
    2381             :         }
    2382         196 :         PrintLine(fpSchema, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
    2383             :     }
    2384           1 :     else if (EQUAL(pszSchemaOpt, "INTERNAL"))
    2385             :     {
    2386           0 :         if (fpOutput == nullptr)
    2387           0 :             return;
    2388           0 :         nSchemaStart = static_cast<int>(VSIFTellL(fpOutput));
    2389           0 :         fpSchema = fpOutput;
    2390             :     }
    2391             :     else
    2392             :     {
    2393           1 :         return;
    2394             :     }
    2395             : 
    2396             :     // Write the schema section at the end of the file.  Once
    2397             :     // complete, we will read it back in, and then move the whole
    2398             :     // file "down" enough to insert the schema at the beginning.
    2399             : 
    2400             :     // Detect if there are fields of List types.
    2401          98 :     bool bHasListFields = false;
    2402             : 
    2403          98 :     const int nLayerCount = OGRGMLDataSource::GetLayerCount();
    2404         223 :     for (int iLayer = 0; !bHasListFields && iLayer < nLayerCount; iLayer++)
    2405             :     {
    2406         125 :         OGRFeatureDefn *poFDefn = papoLayers[iLayer]->GetLayerDefn();
    2407         294 :         for (int iField = 0;
    2408         294 :              !bHasListFields && iField < poFDefn->GetFieldCount(); iField++)
    2409             :         {
    2410         169 :             OGRFieldDefn *poFieldDefn = poFDefn->GetFieldDefn(iField);
    2411             : 
    2412         336 :             if (poFieldDefn->GetType() == OFTIntegerList ||
    2413         334 :                 poFieldDefn->GetType() == OFTInteger64List ||
    2414         503 :                 poFieldDefn->GetType() == OFTRealList ||
    2415         167 :                 poFieldDefn->GetType() == OFTStringList)
    2416             :             {
    2417           3 :                 bHasListFields = true;
    2418             :             }
    2419             :         }
    2420             :     }
    2421             : 
    2422             :     // Emit the start of the schema section.
    2423          98 :     const char *pszPrefix = GetAppPrefix();
    2424          98 :     if (pszPrefix[0] == '\0')
    2425           0 :         pszPrefix = "ogr";
    2426         196 :     const char *pszTargetNameSpace = CSLFetchNameValueDef(
    2427          98 :         papszCreateOptions, "TARGET_NAMESPACE", "http://ogr.maptools.org/");
    2428             : 
    2429          98 :     if (IsGML3Output())
    2430             :     {
    2431          88 :         PrintLine(fpSchema, "<xs:schema ");
    2432          88 :         PrintLine(fpSchema, "    targetNamespace=\"%s\"", pszTargetNameSpace);
    2433          88 :         PrintLine(fpSchema, "    xmlns:%s=\"%s\"", pszPrefix,
    2434             :                   pszTargetNameSpace);
    2435          88 :         PrintLine(fpSchema,
    2436             :                   "    xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"");
    2437          88 :         if (IsGML32Output())
    2438             :         {
    2439          75 :             PrintLine(fpSchema,
    2440             :                       "    xmlns:gml=\"http://www.opengis.net/gml/3.2\"");
    2441          75 :             if (!bGMLFeatureCollection)
    2442             :             {
    2443          75 :                 PrintLine(
    2444             :                     fpSchema,
    2445             :                     "    xmlns:gmlsf=\"http://www.opengis.net/gmlsf/2.0\"");
    2446             :             }
    2447             :         }
    2448             :         else
    2449             :         {
    2450          13 :             PrintLine(fpSchema, "    xmlns:gml=\"http://www.opengis.net/gml\"");
    2451          13 :             if (!IsGML3DeegreeOutput() && !bGMLFeatureCollection)
    2452             :             {
    2453          11 :                 PrintLine(fpSchema,
    2454             :                           "    xmlns:gmlsf=\"http://www.opengis.net/gmlsf\"");
    2455             :             }
    2456             :         }
    2457          88 :         PrintLine(fpSchema, "    elementFormDefault=\"qualified\"");
    2458          88 :         PrintLine(fpSchema, "    version=\"1.0\">");
    2459             : 
    2460          88 :         if (IsGML32Output())
    2461             :         {
    2462          75 :             if (!bGMLFeatureCollection)
    2463             :             {
    2464          75 :                 PrintLine(fpSchema, "<xs:annotation>");
    2465          75 :                 PrintLine(fpSchema, "  <xs:appinfo "
    2466             :                                     "source=\"http://schemas.opengis.net/"
    2467             :                                     "gmlsfProfile/2.0/gmlsfLevels.xsd\">");
    2468          75 :                 PrintLine(
    2469             :                     fpSchema,
    2470             :                     "    <gmlsf:ComplianceLevel>%d</gmlsf:ComplianceLevel>",
    2471             :                     (bHasListFields) ? 1 : 0);
    2472          75 :                 PrintLine(fpSchema, "  </xs:appinfo>");
    2473          75 :                 PrintLine(fpSchema, "</xs:annotation>");
    2474             :             }
    2475             : 
    2476          75 :             PrintLine(fpSchema,
    2477             :                       "<xs:import namespace=\"http://www.opengis.net/gml/3.2\" "
    2478             :                       "schemaLocation=\"http://schemas.opengis.net/gml/3.2.1/"
    2479             :                       "gml.xsd\"/>");
    2480          75 :             if (!bGMLFeatureCollection)
    2481             :             {
    2482          75 :                 PrintLine(
    2483             :                     fpSchema,
    2484             :                     "<xs:import namespace=\"http://www.opengis.net/gmlsf/2.0\" "
    2485             :                     "schemaLocation=\"http://schemas.opengis.net/gmlsfProfile/"
    2486             :                     "2.0/gmlsfLevels.xsd\"/>");
    2487             :             }
    2488             :         }
    2489             :         else
    2490             :         {
    2491          13 :             if (!IsGML3DeegreeOutput() && !bGMLFeatureCollection)
    2492             :             {
    2493          11 :                 PrintLine(fpSchema, "<xs:annotation>");
    2494          11 :                 PrintLine(fpSchema,
    2495             :                           "  <xs:appinfo "
    2496             :                           "source=\"http://schemas.opengis.net/gml/3.1.1/"
    2497             :                           "profiles/gmlsfProfile/1.0.0/gmlsfLevels.xsd\">");
    2498          11 :                 PrintLine(
    2499             :                     fpSchema,
    2500             :                     "    <gmlsf:ComplianceLevel>%d</gmlsf:ComplianceLevel>",
    2501             :                     (bHasListFields) ? 1 : 0);
    2502          11 :                 PrintLine(fpSchema,
    2503             :                           "    "
    2504             :                           "<gmlsf:GMLProfileSchema>http://schemas.opengis.net/"
    2505             :                           "gml/3.1.1/profiles/gmlsfProfile/1.0.0/gmlsf.xsd</"
    2506             :                           "gmlsf:GMLProfileSchema>");
    2507          11 :                 PrintLine(fpSchema, "  </xs:appinfo>");
    2508          11 :                 PrintLine(fpSchema, "</xs:annotation>");
    2509             :             }
    2510             : 
    2511          13 :             PrintLine(fpSchema,
    2512             :                       "<xs:import namespace=\"http://www.opengis.net/gml\" "
    2513             :                       "schemaLocation=\"http://schemas.opengis.net/gml/3.1.1/"
    2514             :                       "base/gml.xsd\"/>");
    2515          13 :             if (!IsGML3DeegreeOutput() && !bGMLFeatureCollection)
    2516             :             {
    2517          11 :                 PrintLine(
    2518             :                     fpSchema,
    2519             :                     "<xs:import namespace=\"http://www.opengis.net/gmlsf\" "
    2520             :                     "schemaLocation=\"http://schemas.opengis.net/gml/3.1.1/"
    2521             :                     "profiles/gmlsfProfile/1.0.0/gmlsfLevels.xsd\"/>");
    2522             :             }
    2523             :         }
    2524             :     }
    2525             :     else
    2526             :     {
    2527          10 :         PrintLine(fpSchema,
    2528             :                   "<xs:schema targetNamespace=\"%s\" xmlns:%s=\"%s\" "
    2529             :                   "xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" "
    2530             :                   "xmlns:gml=\"http://www.opengis.net/gml\" "
    2531             :                   "elementFormDefault=\"qualified\" version=\"1.0\">",
    2532             :                   pszTargetNameSpace, pszPrefix, pszTargetNameSpace);
    2533             : 
    2534          10 :         PrintLine(fpSchema,
    2535             :                   "<xs:import namespace=\"http://www.opengis.net/gml\" "
    2536             :                   "schemaLocation=\"http://schemas.opengis.net/gml/2.1.2/"
    2537             :                   "feature.xsd\"/>");
    2538             :     }
    2539             : 
    2540             :     // Define the FeatureCollection element
    2541          98 :     if (!bGMLFeatureCollection)
    2542             :     {
    2543          97 :         bool bHasUniqueConstraints = false;
    2544         221 :         for (int iLayer = 0; (iLayer < nLayerCount) && !bHasUniqueConstraints;
    2545             :              iLayer++)
    2546             :         {
    2547         124 :             OGRFeatureDefn *poFDefn = papoLayers[iLayer]->GetLayerDefn();
    2548         124 :             const int nFieldCount = poFDefn->GetFieldCount();
    2549         302 :             for (int iField = 0;
    2550         302 :                  (iField < nFieldCount) && !bHasUniqueConstraints; iField++)
    2551             :             {
    2552         178 :                 const OGRFieldDefn *poFieldDefn = poFDefn->GetFieldDefn(iField);
    2553         178 :                 if (poFieldDefn->IsUnique())
    2554           0 :                     bHasUniqueConstraints = true;
    2555             :             }
    2556             :         }
    2557             : 
    2558          97 :         const char *pszFeatureMemberPrefix = pszPrefix;
    2559          97 :         if (IsGML3Output())
    2560             :         {
    2561          87 :             if (IsGML32Output())
    2562             :             {
    2563             :                 // GML Simple Features profile v2.0 mentions gml:AbstractGML as
    2564             :                 // substitutionGroup but using gml:AbstractFeature makes it
    2565             :                 // usablable by GMLJP2 v2.
    2566          75 :                 PrintLine(fpSchema,
    2567             :                           "<xs:element name=\"FeatureCollection\" "
    2568             :                           "type=\"%s:FeatureCollectionType\" "
    2569             :                           "substitutionGroup=\"gml:AbstractFeature\"%s>",
    2570             :                           pszPrefix, bHasUniqueConstraints ? "" : "/");
    2571             :             }
    2572          12 :             else if (IsGML3DeegreeOutput())
    2573             :             {
    2574           1 :                 PrintLine(fpSchema,
    2575             :                           "<xs:element name=\"FeatureCollection\" "
    2576             :                           "type=\"%s:FeatureCollectionType\" "
    2577             :                           "substitutionGroup=\"gml:_FeatureCollection\"%s>",
    2578             :                           pszPrefix, bHasUniqueConstraints ? "" : "/");
    2579             :             }
    2580             :             else
    2581             :             {
    2582          11 :                 PrintLine(fpSchema,
    2583             :                           "<xs:element name=\"FeatureCollection\" "
    2584             :                           "type=\"%s:FeatureCollectionType\" "
    2585             :                           "substitutionGroup=\"gml:_GML\"%s>",
    2586             :                           pszPrefix, bHasUniqueConstraints ? "" : "/");
    2587             :             }
    2588             :         }
    2589             :         else
    2590             :         {
    2591          10 :             pszFeatureMemberPrefix = "gml";
    2592          10 :             PrintLine(fpSchema,
    2593             :                       "<xs:element name=\"FeatureCollection\" "
    2594             :                       "type=\"%s:FeatureCollectionType\" "
    2595             :                       "substitutionGroup=\"gml:_FeatureCollection\"%s>",
    2596             :                       pszPrefix, bHasUniqueConstraints ? "" : "/");
    2597             :         }
    2598             : 
    2599          97 :         if (bHasUniqueConstraints)
    2600             :         {
    2601           0 :             for (int iLayer = 0; iLayer < nLayerCount; iLayer++)
    2602             :             {
    2603           0 :                 OGRFeatureDefn *poFDefn = papoLayers[iLayer]->GetLayerDefn();
    2604           0 :                 const int nFieldCount = poFDefn->GetFieldCount();
    2605           0 :                 for (int iField = 0; iField < nFieldCount; iField++)
    2606             :                 {
    2607             :                     const OGRFieldDefn *poFieldDefn =
    2608           0 :                         poFDefn->GetFieldDefn(iField);
    2609           0 :                     if (poFieldDefn->IsUnique())
    2610             :                     {
    2611           0 :                         PrintLine(
    2612             :                             fpSchema,
    2613             :                             "  <xs:unique name=\"uniqueConstraint_%s_%s\">",
    2614           0 :                             poFDefn->GetName(), poFieldDefn->GetNameRef());
    2615           0 :                         PrintLine(fpSchema,
    2616             :                                   "    <xs:selector "
    2617             :                                   "xpath=\"%s:featureMember/%s:%s\"/>",
    2618             :                                   pszFeatureMemberPrefix, pszPrefix,
    2619           0 :                                   poFDefn->GetName());
    2620           0 :                         PrintLine(fpSchema, "    <xs:field xpath=\"%s:%s\"/>",
    2621             :                                   pszPrefix, poFieldDefn->GetNameRef());
    2622           0 :                         PrintLine(fpSchema, "  </xs:unique>");
    2623             :                     }
    2624             :                 }
    2625             :             }
    2626           0 :             PrintLine(fpSchema, "</xs:element>");
    2627             :         }
    2628             :     }
    2629             : 
    2630             :     // Define the FeatureCollectionType
    2631          98 :     if (IsGML3Output() && !bGMLFeatureCollection)
    2632             :     {
    2633          87 :         PrintLine(fpSchema, "<xs:complexType name=\"FeatureCollectionType\">");
    2634          87 :         PrintLine(fpSchema, "  <xs:complexContent>");
    2635          87 :         if (IsGML3DeegreeOutput())
    2636             :         {
    2637           1 :             PrintLine(fpSchema, "    <xs:extension "
    2638             :                                 "base=\"gml:AbstractFeatureCollectionType\">");
    2639           1 :             PrintLine(fpSchema, "      <xs:sequence>");
    2640           1 :             PrintLine(fpSchema, "        <xs:element name=\"featureMember\" "
    2641             :                                 "minOccurs=\"0\" maxOccurs=\"unbounded\">");
    2642             :         }
    2643             :         else
    2644             :         {
    2645          86 :             PrintLine(fpSchema,
    2646             :                       "    <xs:extension base=\"gml:AbstractFeatureType\">");
    2647          86 :             PrintLine(
    2648             :                 fpSchema,
    2649             :                 "      <xs:sequence minOccurs=\"0\" maxOccurs=\"unbounded\">");
    2650          86 :             PrintLine(fpSchema, "        <xs:element name=\"featureMember\">");
    2651             :         }
    2652          87 :         PrintLine(fpSchema, "          <xs:complexType>");
    2653          87 :         if (IsGML32Output())
    2654             :         {
    2655          75 :             PrintLine(fpSchema, "            <xs:complexContent>");
    2656          75 :             PrintLine(fpSchema, "              <xs:extension "
    2657             :                                 "base=\"gml:AbstractFeatureMemberType\">");
    2658          75 :             PrintLine(fpSchema, "                <xs:sequence>");
    2659          75 :             PrintLine(
    2660             :                 fpSchema,
    2661             :                 "                  <xs:element ref=\"gml:AbstractFeature\"/>");
    2662          75 :             PrintLine(fpSchema, "                </xs:sequence>");
    2663          75 :             PrintLine(fpSchema, "              </xs:extension>");
    2664          75 :             PrintLine(fpSchema, "            </xs:complexContent>");
    2665             :         }
    2666             :         else
    2667             :         {
    2668          12 :             PrintLine(fpSchema, "            <xs:sequence>");
    2669          12 :             PrintLine(fpSchema,
    2670             :                       "              <xs:element ref=\"gml:_Feature\"/>");
    2671          12 :             PrintLine(fpSchema, "            </xs:sequence>");
    2672             :         }
    2673          87 :         PrintLine(fpSchema, "          </xs:complexType>");
    2674          87 :         PrintLine(fpSchema, "        </xs:element>");
    2675          87 :         PrintLine(fpSchema, "      </xs:sequence>");
    2676          87 :         PrintLine(fpSchema, "    </xs:extension>");
    2677          87 :         PrintLine(fpSchema, "  </xs:complexContent>");
    2678          87 :         PrintLine(fpSchema, "</xs:complexType>");
    2679             :     }
    2680          11 :     else if (!bGMLFeatureCollection)
    2681             :     {
    2682          10 :         PrintLine(fpSchema, "<xs:complexType name=\"FeatureCollectionType\">");
    2683          10 :         PrintLine(fpSchema, "  <xs:complexContent>");
    2684          10 :         PrintLine(
    2685             :             fpSchema,
    2686             :             "    <xs:extension base=\"gml:AbstractFeatureCollectionType\">");
    2687          10 :         PrintLine(fpSchema, "      <xs:attribute name=\"lockId\" "
    2688             :                             "type=\"xs:string\" use=\"optional\"/>");
    2689          10 :         PrintLine(fpSchema, "      <xs:attribute name=\"scope\" "
    2690             :                             "type=\"xs:string\" use=\"optional\"/>");
    2691          10 :         PrintLine(fpSchema, "    </xs:extension>");
    2692          10 :         PrintLine(fpSchema, "  </xs:complexContent>");
    2693          10 :         PrintLine(fpSchema, "</xs:complexType>");
    2694             :     }
    2695             : 
    2696             :     // Define the schema for each layer.
    2697         223 :     for (int iLayer = 0; iLayer < nLayerCount; iLayer++)
    2698             :     {
    2699         125 :         OGRFeatureDefn *poFDefn = papoLayers[iLayer]->GetLayerDefn();
    2700             : 
    2701             :         // Emit initial stuff for a feature type.
    2702         125 :         if (IsGML32Output())
    2703             :         {
    2704          91 :             PrintLine(fpSchema,
    2705             :                       "<xs:element name=\"%s\" type=\"%s:%s_Type\" "
    2706             :                       "substitutionGroup=\"gml:AbstractFeature\"/>",
    2707          91 :                       poFDefn->GetName(), pszPrefix, poFDefn->GetName());
    2708             :         }
    2709             :         else
    2710             :         {
    2711          34 :             PrintLine(fpSchema,
    2712             :                       "<xs:element name=\"%s\" type=\"%s:%s_Type\" "
    2713             :                       "substitutionGroup=\"gml:_Feature\"/>",
    2714          34 :                       poFDefn->GetName(), pszPrefix, poFDefn->GetName());
    2715             :         }
    2716             : 
    2717         125 :         PrintLine(fpSchema, "<xs:complexType name=\"%s_Type\">",
    2718         125 :                   poFDefn->GetName());
    2719         125 :         PrintLine(fpSchema, "  <xs:complexContent>");
    2720         125 :         PrintLine(fpSchema,
    2721             :                   "    <xs:extension base=\"gml:AbstractFeatureType\">");
    2722         125 :         PrintLine(fpSchema, "      <xs:sequence>");
    2723             : 
    2724         248 :         for (int iGeomField = 0; iGeomField < poFDefn->GetGeomFieldCount();
    2725             :              iGeomField++)
    2726             :         {
    2727             :             OGRGeomFieldDefn *poFieldDefn =
    2728         123 :                 poFDefn->GetGeomFieldDefn(iGeomField);
    2729             : 
    2730             :             // Define the geometry attribute.
    2731         123 :             const char *pszGeometryTypeName = "GeometryPropertyType";
    2732         123 :             const char *pszGeomTypeComment = "";
    2733         123 :             OGRwkbGeometryType eGType = wkbFlatten(poFieldDefn->GetType());
    2734         123 :             switch (eGType)
    2735             :             {
    2736          25 :                 case wkbPoint:
    2737          25 :                     pszGeometryTypeName = "PointPropertyType";
    2738          25 :                     break;
    2739          12 :                 case wkbLineString:
    2740             :                 case wkbCircularString:
    2741             :                 case wkbCompoundCurve:
    2742          12 :                     if (IsGML3Output())
    2743             :                     {
    2744          12 :                         if (eGType == wkbLineString)
    2745          11 :                             pszGeomTypeComment =
    2746             :                                 " <!-- restricted to LineString -->";
    2747           1 :                         else if (eGType == wkbCircularString)
    2748           0 :                             pszGeomTypeComment =
    2749             :                                 " <!-- contains CircularString -->";
    2750           1 :                         else if (eGType == wkbCompoundCurve)
    2751           1 :                             pszGeomTypeComment =
    2752             :                                 " <!-- contains CompoundCurve -->";
    2753          12 :                         pszGeometryTypeName = "CurvePropertyType";
    2754             :                     }
    2755             :                     else
    2756           0 :                         pszGeometryTypeName = "LineStringPropertyType";
    2757          12 :                     break;
    2758          16 :                 case wkbPolygon:
    2759             :                 case wkbCurvePolygon:
    2760          16 :                     if (IsGML3Output())
    2761             :                     {
    2762          14 :                         if (eGType == wkbPolygon)
    2763          13 :                             pszGeomTypeComment =
    2764             :                                 " <!-- restricted to Polygon -->";
    2765           1 :                         else if (eGType == wkbCurvePolygon)
    2766           1 :                             pszGeomTypeComment =
    2767             :                                 " <!-- contains CurvePolygon -->";
    2768          14 :                         pszGeometryTypeName = "SurfacePropertyType";
    2769             :                     }
    2770             :                     else
    2771           2 :                         pszGeometryTypeName = "PolygonPropertyType";
    2772          16 :                     break;
    2773           6 :                 case wkbMultiPoint:
    2774           6 :                     pszGeometryTypeName = "MultiPointPropertyType";
    2775           6 :                     break;
    2776           9 :                 case wkbMultiLineString:
    2777             :                 case wkbMultiCurve:
    2778           9 :                     if (IsGML3Output())
    2779             :                     {
    2780           9 :                         if (eGType == wkbMultiLineString)
    2781           8 :                             pszGeomTypeComment =
    2782             :                                 " <!-- restricted to MultiLineString -->";
    2783           1 :                         else if (eGType == wkbMultiCurve)
    2784           1 :                             pszGeomTypeComment =
    2785             :                                 " <!-- contains non-linear MultiCurve -->";
    2786           9 :                         pszGeometryTypeName = "MultiCurvePropertyType";
    2787             :                     }
    2788             :                     else
    2789           0 :                         pszGeometryTypeName = "MultiLineStringPropertyType";
    2790           9 :                     break;
    2791          12 :                 case wkbMultiPolygon:
    2792             :                 case wkbMultiSurface:
    2793          12 :                     if (IsGML3Output())
    2794             :                     {
    2795          11 :                         if (eGType == wkbMultiPolygon)
    2796          10 :                             pszGeomTypeComment =
    2797             :                                 " <!-- restricted to MultiPolygon -->";
    2798           1 :                         else if (eGType == wkbMultiSurface)
    2799           1 :                             pszGeomTypeComment =
    2800             :                                 " <!-- contains non-linear MultiSurface -->";
    2801          11 :                         pszGeometryTypeName = "MultiSurfacePropertyType";
    2802             :                     }
    2803             :                     else
    2804           1 :                         pszGeometryTypeName = "MultiPolygonPropertyType";
    2805          12 :                     break;
    2806           6 :                 case wkbGeometryCollection:
    2807           6 :                     pszGeometryTypeName = "MultiGeometryPropertyType";
    2808           6 :                     break;
    2809          37 :                 default:
    2810          37 :                     break;
    2811             :             }
    2812             : 
    2813         123 :             const auto poSRS = poFieldDefn->GetSpatialRef();
    2814         246 :             std::string osSRSNameComment;
    2815         123 :             if (poSRS)
    2816             :             {
    2817          30 :                 bool bCoordSwap = false;
    2818             :                 char *pszSRSName =
    2819          30 :                     GML_GetSRSName(poSRS, GetSRSNameFormat(), &bCoordSwap);
    2820          30 :                 if (pszSRSName[0])
    2821             :                 {
    2822          30 :                     osSRSNameComment = "<!--";
    2823          30 :                     osSRSNameComment += pszSRSName;
    2824          30 :                     osSRSNameComment += " -->";
    2825             :                 }
    2826          30 :                 CPLFree(pszSRSName);
    2827             :             }
    2828             : 
    2829         123 :             int nMinOccurs = poFieldDefn->IsNullable() ? 0 : 1;
    2830         123 :             const auto &oCoordPrec = poFieldDefn->GetCoordinatePrecision();
    2831         123 :             if (oCoordPrec.dfXYResolution ==
    2832         122 :                     OGRGeomCoordinatePrecision::UNKNOWN &&
    2833         122 :                 oCoordPrec.dfZResolution == OGRGeomCoordinatePrecision::UNKNOWN)
    2834             :             {
    2835         122 :                 PrintLine(
    2836             :                     fpSchema,
    2837             :                     "        <xs:element name=\"%s\" type=\"gml:%s\" "
    2838             :                     "nillable=\"true\" minOccurs=\"%d\" maxOccurs=\"1\"/>%s%s",
    2839             :                     poFieldDefn->GetNameRef(), pszGeometryTypeName, nMinOccurs,
    2840             :                     pszGeomTypeComment, osSRSNameComment.c_str());
    2841             :             }
    2842             :             else
    2843             :             {
    2844           1 :                 PrintLine(fpSchema,
    2845             :                           "        <xs:element name=\"%s\" type=\"gml:%s\" "
    2846             :                           "nillable=\"true\" minOccurs=\"%d\" maxOccurs=\"1\">",
    2847             :                           poFieldDefn->GetNameRef(), pszGeometryTypeName,
    2848             :                           nMinOccurs);
    2849           1 :                 PrintLine(fpSchema, "          <xs:annotation>");
    2850           1 :                 PrintLine(fpSchema, "            <xs:appinfo "
    2851             :                                     "source=\"http://ogr.maptools.org/\">");
    2852           1 :                 if (oCoordPrec.dfXYResolution !=
    2853             :                     OGRGeomCoordinatePrecision::UNKNOWN)
    2854             :                 {
    2855           1 :                     PrintLine(fpSchema,
    2856             :                               "              "
    2857             :                               "<ogr:xy_coordinate_resolution>%g</"
    2858             :                               "ogr:xy_coordinate_resolution>",
    2859           1 :                               oCoordPrec.dfXYResolution);
    2860             :                 }
    2861           1 :                 if (oCoordPrec.dfZResolution !=
    2862             :                     OGRGeomCoordinatePrecision::UNKNOWN)
    2863             :                 {
    2864           1 :                     PrintLine(fpSchema,
    2865             :                               "              "
    2866             :                               "<ogr:z_coordinate_resolution>%g</"
    2867             :                               "ogr:z_coordinate_resolution>",
    2868           1 :                               oCoordPrec.dfZResolution);
    2869             :                 }
    2870           1 :                 PrintLine(fpSchema, "            </xs:appinfo>");
    2871           1 :                 PrintLine(fpSchema, "          </xs:annotation>");
    2872           1 :                 PrintLine(fpSchema, "        </xs:element>%s%s",
    2873             :                           pszGeomTypeComment, osSRSNameComment.c_str());
    2874             :             }
    2875             :         }
    2876             : 
    2877             :         // Emit each of the attributes.
    2878         306 :         for (int iField = 0; iField < poFDefn->GetFieldCount(); iField++)
    2879             :         {
    2880         181 :             OGRFieldDefn *poFieldDefn = poFDefn->GetFieldDefn(iField);
    2881             : 
    2882         351 :             if (IsGML3Output() &&
    2883         170 :                 strcmp(poFieldDefn->GetNameRef(), "gml_id") == 0)
    2884           1 :                 continue;
    2885         191 :             else if (!IsGML3Output() &&
    2886          11 :                      strcmp(poFieldDefn->GetNameRef(), "fid") == 0)
    2887           0 :                 continue;
    2888             : 
    2889         202 :             const auto AddComment = [this, fpSchema, poFieldDefn]()
    2890             :             {
    2891         180 :                 if (!poFieldDefn->GetComment().empty())
    2892             :                 {
    2893          11 :                     char *pszComment = CPLEscapeString(
    2894          11 :                         poFieldDefn->GetComment().c_str(), -1, CPLES_XML);
    2895          11 :                     PrintLine(fpSchema,
    2896             :                               "          "
    2897             :                               "<xs:annotation><xs:documentation>%s</"
    2898             :                               "xs:documentation></xs:annotation>",
    2899             :                               pszComment);
    2900          11 :                     CPLFree(pszComment);
    2901             :                 }
    2902         360 :             };
    2903             : 
    2904         180 :             int nMinOccurs = poFieldDefn->IsNullable() ? 0 : 1;
    2905         180 :             const OGRFieldType eType = poFieldDefn->GetType();
    2906         180 :             if (eType == OFTInteger || eType == OFTIntegerList)
    2907             :             {
    2908             :                 int nWidth =
    2909          32 :                     poFieldDefn->GetWidth() > 0 ? poFieldDefn->GetWidth() : 16;
    2910             : 
    2911          32 :                 PrintLine(fpSchema,
    2912             :                           "        <xs:element name=\"%s\" nillable=\"true\" "
    2913             :                           "minOccurs=\"%d\" maxOccurs=\"%s\">",
    2914             :                           poFieldDefn->GetNameRef(), nMinOccurs,
    2915             :                           eType == OFTIntegerList ? "unbounded" : "1");
    2916          32 :                 AddComment();
    2917          32 :                 PrintLine(fpSchema, "          <xs:simpleType>");
    2918          32 :                 if (poFieldDefn->GetSubType() == OFSTBoolean)
    2919             :                 {
    2920           3 :                     PrintLine(
    2921             :                         fpSchema,
    2922             :                         "            <xs:restriction base=\"xs:boolean\">");
    2923             :                 }
    2924          29 :                 else if (poFieldDefn->GetSubType() == OFSTInt16)
    2925             :                 {
    2926           1 :                     PrintLine(fpSchema,
    2927             :                               "            <xs:restriction base=\"xs:short\">");
    2928             :                 }
    2929             :                 else
    2930             :                 {
    2931          28 :                     PrintLine(
    2932             :                         fpSchema,
    2933             :                         "            <xs:restriction base=\"xs:integer\">");
    2934          28 :                     PrintLine(fpSchema,
    2935             :                               "              <xs:totalDigits value=\"%d\"/>",
    2936             :                               nWidth);
    2937             :                 }
    2938          32 :                 PrintLine(fpSchema, "            </xs:restriction>");
    2939          32 :                 PrintLine(fpSchema, "          </xs:simpleType>");
    2940          32 :                 PrintLine(fpSchema, "        </xs:element>");
    2941             :             }
    2942         148 :             else if (eType == OFTInteger64 || eType == OFTInteger64List)
    2943             :             {
    2944             :                 int nWidth =
    2945          17 :                     poFieldDefn->GetWidth() > 0 ? poFieldDefn->GetWidth() : 16;
    2946             : 
    2947          17 :                 PrintLine(fpSchema,
    2948             :                           "        <xs:element name=\"%s\" nillable=\"true\" "
    2949             :                           "minOccurs=\"%d\" maxOccurs=\"%s\">",
    2950             :                           poFieldDefn->GetNameRef(), nMinOccurs,
    2951             :                           eType == OFTInteger64List ? "unbounded" : "1");
    2952          17 :                 AddComment();
    2953          17 :                 PrintLine(fpSchema, "          <xs:simpleType>");
    2954          17 :                 if (poFieldDefn->GetSubType() == OFSTBoolean)
    2955             :                 {
    2956           0 :                     PrintLine(
    2957             :                         fpSchema,
    2958             :                         "            <xs:restriction base=\"xs:boolean\">");
    2959             :                 }
    2960          17 :                 else if (poFieldDefn->GetSubType() == OFSTInt16)
    2961             :                 {
    2962           0 :                     PrintLine(fpSchema,
    2963             :                               "            <xs:restriction base=\"xs:short\">");
    2964             :                 }
    2965             :                 else
    2966             :                 {
    2967          17 :                     PrintLine(fpSchema,
    2968             :                               "            <xs:restriction base=\"xs:long\">");
    2969          17 :                     PrintLine(fpSchema,
    2970             :                               "              <xs:totalDigits value=\"%d\"/>",
    2971             :                               nWidth);
    2972             :                 }
    2973          17 :                 PrintLine(fpSchema, "            </xs:restriction>");
    2974          17 :                 PrintLine(fpSchema, "          </xs:simpleType>");
    2975          17 :                 PrintLine(fpSchema, "        </xs:element>");
    2976             :             }
    2977         131 :             else if (eType == OFTReal || eType == OFTRealList)
    2978             :             {
    2979             :                 int nWidth, nDecimals;
    2980             : 
    2981          35 :                 nWidth = poFieldDefn->GetWidth();
    2982          35 :                 nDecimals = poFieldDefn->GetPrecision();
    2983             : 
    2984          35 :                 PrintLine(fpSchema,
    2985             :                           "        <xs:element name=\"%s\" nillable=\"true\" "
    2986             :                           "minOccurs=\"%d\" maxOccurs=\"%s\">",
    2987             :                           poFieldDefn->GetNameRef(), nMinOccurs,
    2988             :                           eType == OFTRealList ? "unbounded" : "1");
    2989          35 :                 AddComment();
    2990          35 :                 PrintLine(fpSchema, "          <xs:simpleType>");
    2991          35 :                 if (poFieldDefn->GetSubType() == OFSTFloat32)
    2992           1 :                     PrintLine(fpSchema,
    2993             :                               "            <xs:restriction base=\"xs:float\">");
    2994             :                 else
    2995          34 :                     PrintLine(
    2996             :                         fpSchema,
    2997             :                         "            <xs:restriction base=\"xs:decimal\">");
    2998          35 :                 if (nWidth > 0)
    2999             :                 {
    3000          14 :                     PrintLine(fpSchema,
    3001             :                               "              <xs:totalDigits value=\"%d\"/>",
    3002             :                               nWidth);
    3003          14 :                     PrintLine(fpSchema,
    3004             :                               "              <xs:fractionDigits value=\"%d\"/>",
    3005             :                               nDecimals);
    3006             :                 }
    3007          35 :                 PrintLine(fpSchema, "            </xs:restriction>");
    3008          35 :                 PrintLine(fpSchema, "          </xs:simpleType>");
    3009          35 :                 PrintLine(fpSchema, "        </xs:element>");
    3010             :             }
    3011          96 :             else if (eType == OFTString || eType == OFTStringList)
    3012             :             {
    3013          58 :                 PrintLine(fpSchema,
    3014             :                           "        <xs:element name=\"%s\" nillable=\"true\" "
    3015             :                           "minOccurs=\"%d\" maxOccurs=\"%s\">",
    3016             :                           poFieldDefn->GetNameRef(), nMinOccurs,
    3017             :                           eType == OFTStringList ? "unbounded" : "1");
    3018          58 :                 AddComment();
    3019          58 :                 PrintLine(fpSchema, "          <xs:simpleType>");
    3020          58 :                 PrintLine(fpSchema,
    3021             :                           "            <xs:restriction base=\"xs:string\">");
    3022          58 :                 if (poFieldDefn->GetWidth() != 0)
    3023             :                 {
    3024          10 :                     PrintLine(fpSchema,
    3025             :                               "              <xs:maxLength value=\"%d\"/>",
    3026             :                               poFieldDefn->GetWidth());
    3027             :                 }
    3028          58 :                 PrintLine(fpSchema, "            </xs:restriction>");
    3029          58 :                 PrintLine(fpSchema, "          </xs:simpleType>");
    3030          58 :                 PrintLine(fpSchema, "        </xs:element>");
    3031             :             }
    3032          38 :             else if (eType == OFTDate)
    3033             :             {
    3034          18 :                 PrintLine(fpSchema,
    3035             :                           "        <xs:element name=\"%s\" nillable=\"true\" "
    3036             :                           "minOccurs=\"%d\" maxOccurs=\"1\" type=\"xs:date\">",
    3037             :                           poFieldDefn->GetNameRef(), nMinOccurs);
    3038          18 :                 AddComment();
    3039          18 :                 PrintLine(fpSchema, "        </xs:element>");
    3040             :             }
    3041          20 :             else if (eType == OFTTime)
    3042             :             {
    3043           2 :                 PrintLine(fpSchema,
    3044             :                           "        <xs:element name=\"%s\" nillable=\"true\" "
    3045             :                           "minOccurs=\"%d\" maxOccurs=\"1\" type=\"xs:time\">",
    3046             :                           poFieldDefn->GetNameRef(), nMinOccurs);
    3047           2 :                 AddComment();
    3048           2 :                 PrintLine(fpSchema, "        </xs:element>");
    3049             :             }
    3050          18 :             else if (eType == OFTDateTime)
    3051             :             {
    3052          18 :                 PrintLine(
    3053             :                     fpSchema,
    3054             :                     "        <xs:element name=\"%s\" nillable=\"true\" "
    3055             :                     "minOccurs=\"%d\" maxOccurs=\"1\" type=\"xs:dateTime\">",
    3056             :                     poFieldDefn->GetNameRef(), nMinOccurs);
    3057          18 :                 AddComment();
    3058          18 :                 PrintLine(fpSchema, "        </xs:element>");
    3059             :             }
    3060             :             else
    3061             :             {
    3062             :                 // TODO.
    3063             :             }
    3064             :         }  // Next field.
    3065             : 
    3066             :         // Finish off feature type.
    3067         125 :         PrintLine(fpSchema, "      </xs:sequence>");
    3068         125 :         PrintLine(fpSchema, "    </xs:extension>");
    3069         125 :         PrintLine(fpSchema, "  </xs:complexContent>");
    3070         125 :         PrintLine(fpSchema, "</xs:complexType>");
    3071             :     }  // Next layer.
    3072             : 
    3073          98 :     PrintLine(fpSchema, "</xs:schema>");
    3074             : 
    3075             :     // Move schema to the start of the file.
    3076          98 :     if (fpSchema == fpOutput)
    3077             :     {
    3078             :         // Read the schema into memory.
    3079           0 :         int nSchemaSize = static_cast<int>(VSIFTellL(fpSchema) - nSchemaStart);
    3080           0 :         char *pszSchema = static_cast<char *>(CPLMalloc(nSchemaSize + 1));
    3081             : 
    3082           0 :         VSIFSeekL(fpSchema, nSchemaStart, SEEK_SET);
    3083             : 
    3084           0 :         VSIFReadL(pszSchema, 1, nSchemaSize, fpSchema);
    3085           0 :         pszSchema[nSchemaSize] = '\0';
    3086             : 
    3087             :         // Move file data down by "schema size" bytes from after <?xml> header
    3088             :         // so we have room insert the schema.  Move in pretty big chunks.
    3089           0 :         int nChunkSize = std::min(nSchemaStart - nSchemaInsertLocation, 250000);
    3090           0 :         char *pszChunk = static_cast<char *>(CPLMalloc(nChunkSize));
    3091             : 
    3092           0 :         for (int nEndOfUnmovedData = nSchemaStart;
    3093           0 :              nEndOfUnmovedData > nSchemaInsertLocation;)
    3094             :         {
    3095             :             const int nBytesToMove =
    3096           0 :                 std::min(nChunkSize, nEndOfUnmovedData - nSchemaInsertLocation);
    3097             : 
    3098           0 :             VSIFSeekL(fpSchema, nEndOfUnmovedData - nBytesToMove, SEEK_SET);
    3099           0 :             VSIFReadL(pszChunk, 1, nBytesToMove, fpSchema);
    3100           0 :             VSIFSeekL(fpSchema, nEndOfUnmovedData - nBytesToMove + nSchemaSize,
    3101             :                       SEEK_SET);
    3102           0 :             VSIFWriteL(pszChunk, 1, nBytesToMove, fpSchema);
    3103             : 
    3104           0 :             nEndOfUnmovedData -= nBytesToMove;
    3105             :         }
    3106             : 
    3107           0 :         CPLFree(pszChunk);
    3108             : 
    3109             :         // Write the schema in the opened slot.
    3110           0 :         VSIFSeekL(fpSchema, nSchemaInsertLocation, SEEK_SET);
    3111           0 :         VSIFWriteL(pszSchema, 1, nSchemaSize, fpSchema);
    3112             : 
    3113           0 :         VSIFSeekL(fpSchema, 0, SEEK_END);
    3114             : 
    3115           0 :         nBoundedByLocation += nSchemaSize;
    3116             : 
    3117           0 :         CPLFree(pszSchema);
    3118             :     }
    3119             :     else
    3120             :     {
    3121             :         // Close external schema files.
    3122          98 :         VSIFCloseL(fpSchema);
    3123             :     }
    3124             : }
    3125             : 
    3126             : /************************************************************************/
    3127             : /*                            PrintLine()                               */
    3128             : /************************************************************************/
    3129             : 
    3130        8564 : void OGRGMLDataSource::PrintLine(VSILFILE *fp, const char *fmt, ...)
    3131             : {
    3132       17128 :     CPLString osWork;
    3133             :     va_list args;
    3134             : 
    3135        8564 :     va_start(args, fmt);
    3136        8564 :     osWork.vPrintf(fmt, args);
    3137        8564 :     va_end(args);
    3138             : 
    3139             : #ifdef _WIN32
    3140             :     constexpr const char *pszEOL = "\r\n";
    3141             : #else
    3142        8564 :     constexpr const char *pszEOL = "\n";
    3143             : #endif
    3144             : 
    3145       17123 :     if (VSIFWriteL(osWork.data(), osWork.size(), 1, fp) != 1 ||
    3146        8559 :         VSIFWriteL(pszEOL, strlen(pszEOL), 1, fp) != 1)
    3147             :     {
    3148           5 :         m_bWriteError = true;
    3149           5 :         ReportError(CE_Failure, CPLE_FileIO, "Could not write line %s",
    3150             :                     osWork.c_str());
    3151             :     }
    3152        8564 : }
    3153             : 
    3154             : /************************************************************************/
    3155             : /*                     OGRGMLSingleFeatureLayer                         */
    3156             : /************************************************************************/
    3157             : 
    3158             : class OGRGMLSingleFeatureLayer final : public OGRLayer
    3159             : {
    3160             :   private:
    3161             :     const int nVal;
    3162             :     OGRFeatureDefn *poFeatureDefn = nullptr;
    3163             :     int iNextShapeId = 0;
    3164             : 
    3165             :     CPL_DISALLOW_COPY_ASSIGN(OGRGMLSingleFeatureLayer)
    3166             : 
    3167             :   public:
    3168             :     explicit OGRGMLSingleFeatureLayer(int nVal);
    3169             : 
    3170           0 :     virtual ~OGRGMLSingleFeatureLayer()
    3171           0 :     {
    3172           0 :         poFeatureDefn->Release();
    3173           0 :     }
    3174             : 
    3175           0 :     virtual void ResetReading() override
    3176             :     {
    3177           0 :         iNextShapeId = 0;
    3178           0 :     }
    3179             : 
    3180             :     virtual OGRFeature *GetNextFeature() override;
    3181             : 
    3182           0 :     virtual OGRFeatureDefn *GetLayerDefn() override
    3183             :     {
    3184           0 :         return poFeatureDefn;
    3185             :     }
    3186             : 
    3187           0 :     virtual int TestCapability(const char *) override
    3188             :     {
    3189           0 :         return FALSE;
    3190             :     }
    3191             : };
    3192             : 
    3193             : /************************************************************************/
    3194             : /*                      OGRGMLSingleFeatureLayer()                      */
    3195             : /************************************************************************/
    3196             : 
    3197           0 : OGRGMLSingleFeatureLayer::OGRGMLSingleFeatureLayer(int nValIn)
    3198           0 :     : nVal(nValIn), poFeatureDefn(new OGRFeatureDefn("SELECT")), iNextShapeId(0)
    3199             : {
    3200           0 :     poFeatureDefn->Reference();
    3201           0 :     OGRFieldDefn oField("Validates", OFTInteger);
    3202           0 :     poFeatureDefn->AddFieldDefn(&oField);
    3203           0 : }
    3204             : 
    3205             : /************************************************************************/
    3206             : /*                           GetNextFeature()                           */
    3207             : /************************************************************************/
    3208             : 
    3209           0 : OGRFeature *OGRGMLSingleFeatureLayer::GetNextFeature()
    3210             : {
    3211           0 :     if (iNextShapeId != 0)
    3212           0 :         return nullptr;
    3213             : 
    3214           0 :     OGRFeature *poFeature = new OGRFeature(poFeatureDefn);
    3215           0 :     poFeature->SetField(0, nVal);
    3216           0 :     poFeature->SetFID(iNextShapeId++);
    3217           0 :     return poFeature;
    3218             : }
    3219             : 
    3220             : /************************************************************************/
    3221             : /*                            ExecuteSQL()                              */
    3222             : /************************************************************************/
    3223             : 
    3224           6 : OGRLayer *OGRGMLDataSource::ExecuteSQL(const char *pszSQLCommand,
    3225             :                                        OGRGeometry *poSpatialFilter,
    3226             :                                        const char *pszDialect)
    3227             : {
    3228           6 :     if (poReader != nullptr && EQUAL(pszSQLCommand, "SELECT ValidateSchema()"))
    3229             :     {
    3230           0 :         bool bIsValid = false;
    3231           0 :         if (!osXSDFilename.empty())
    3232             :         {
    3233           0 :             CPLErrorReset();
    3234             :             bIsValid =
    3235           0 :                 CPL_TO_BOOL(CPLValidateXML(osFilename, osXSDFilename, nullptr));
    3236             :         }
    3237           0 :         return new OGRGMLSingleFeatureLayer(bIsValid);
    3238             :     }
    3239             : 
    3240           6 :     return GDALDataset::ExecuteSQL(pszSQLCommand, poSpatialFilter, pszDialect);
    3241             : }
    3242             : 
    3243             : /************************************************************************/
    3244             : /*                          ReleaseResultSet()                          */
    3245             : /************************************************************************/
    3246             : 
    3247           5 : void OGRGMLDataSource::ReleaseResultSet(OGRLayer *poResultsSet)
    3248             : {
    3249           5 :     delete poResultsSet;
    3250           5 : }
    3251             : 
    3252             : /************************************************************************/
    3253             : /*                      FindAndParseTopElements()                       */
    3254             : /************************************************************************/
    3255             : 
    3256         431 : void OGRGMLDataSource::FindAndParseTopElements(VSILFILE *fp)
    3257             : {
    3258             :     // Build a shortened XML file that contain only the global
    3259             :     // boundedBy element, so as to be able to parse it easily.
    3260             : 
    3261             :     char szStartTag[128];
    3262         431 :     char *pszXML = static_cast<char *>(CPLMalloc(8192 + 128 + 3 + 1));
    3263         431 :     VSIFSeekL(fp, 0, SEEK_SET);
    3264         431 :     int nRead = static_cast<int>(VSIFReadL(pszXML, 1, 8192, fp));
    3265         431 :     pszXML[nRead] = 0;
    3266             : 
    3267         431 :     const char *pszStartTag = strchr(pszXML, '<');
    3268         431 :     if (pszStartTag != nullptr)
    3269             :     {
    3270         784 :         while (pszStartTag != nullptr && pszStartTag[1] == '?')
    3271         353 :             pszStartTag = strchr(pszStartTag + 1, '<');
    3272             : 
    3273         431 :         if (pszStartTag != nullptr)
    3274             :         {
    3275         431 :             pszStartTag++;
    3276         431 :             const char *pszEndTag = nullptr;
    3277        9221 :             for (const char *pszIter = pszStartTag; *pszIter != '\0'; pszIter++)
    3278             :             {
    3279        9221 :                 if (isspace(static_cast<unsigned char>(*pszIter)) ||
    3280        8790 :                     *pszIter == '>')
    3281             :                 {
    3282         431 :                     pszEndTag = pszIter;
    3283         431 :                     break;
    3284             :                 }
    3285             :             }
    3286         431 :             if (pszEndTag != nullptr && pszEndTag - pszStartTag < 128)
    3287             :             {
    3288         431 :                 memcpy(szStartTag, pszStartTag, pszEndTag - pszStartTag);
    3289         431 :                 szStartTag[pszEndTag - pszStartTag] = '\0';
    3290             :             }
    3291             :             else
    3292           0 :                 pszStartTag = nullptr;
    3293             :         }
    3294             :     }
    3295             : 
    3296         431 :     const char *pszFeatureMember = strstr(pszXML, "<gml:featureMember");
    3297         431 :     if (pszFeatureMember == nullptr)
    3298         270 :         pszFeatureMember = strstr(pszXML, ":featureMember>");
    3299         431 :     if (pszFeatureMember == nullptr)
    3300         161 :         pszFeatureMember = strstr(pszXML, "<wfs:member>");
    3301             : 
    3302             :     // Is it a standalone geometry ?
    3303         431 :     if (pszFeatureMember == nullptr && pszStartTag != nullptr)
    3304             :     {
    3305         100 :         const char *pszElement = szStartTag;
    3306         100 :         const char *pszColon = strchr(pszElement, ':');
    3307         100 :         if (pszColon)
    3308          73 :             pszElement = pszColon + 1;
    3309         100 :         if (OGRGMLIsGeometryElement(pszElement))
    3310             :         {
    3311           1 :             VSIFSeekL(fp, 0, SEEK_END);
    3312           1 :             const auto nLen = VSIFTellL(fp);
    3313           1 :             if (nLen < 10 * 1024 * 1024U)
    3314             :             {
    3315           1 :                 VSIFSeekL(fp, 0, SEEK_SET);
    3316           2 :                 std::string osBuffer;
    3317             :                 try
    3318             :                 {
    3319           1 :                     osBuffer.resize(static_cast<size_t>(nLen));
    3320           1 :                     VSIFReadL(&osBuffer[0], 1, osBuffer.size(), fp);
    3321             :                 }
    3322           0 :                 catch (const std::exception &)
    3323             :                 {
    3324             :                 }
    3325           1 :                 CPLPushErrorHandler(CPLQuietErrorHandler);
    3326           1 :                 CPLXMLNode *psTree = CPLParseXMLString(osBuffer.data());
    3327           1 :                 CPLPopErrorHandler();
    3328           1 :                 CPLErrorReset();
    3329           1 :                 if (psTree)
    3330             :                 {
    3331           1 :                     m_poStandaloneGeom.reset(GML2OGRGeometry_XMLNode(
    3332             :                         psTree, false, 0, 0, false, true, false));
    3333             : 
    3334           1 :                     if (m_poStandaloneGeom)
    3335             :                     {
    3336           2 :                         for (CPLXMLNode *psCur = psTree; psCur;
    3337           1 :                              psCur = psCur->psNext)
    3338             :                         {
    3339           2 :                             if (psCur->eType == CXT_Element &&
    3340           2 :                                 strcmp(psCur->pszValue, szStartTag) == 0)
    3341             :                             {
    3342             :                                 const char *pszSRSName =
    3343           1 :                                     CPLGetXMLValue(psCur, "srsName", nullptr);
    3344           1 :                                 if (pszSRSName)
    3345             :                                 {
    3346           1 :                                     m_oStandaloneGeomSRS.SetFromUserInput(
    3347             :                                         pszSRSName,
    3348             :                                         OGRSpatialReference::
    3349             :                                             SET_FROM_USER_INPUT_LIMITATIONS_get());
    3350           1 :                                     m_oStandaloneGeomSRS.SetAxisMappingStrategy(
    3351             :                                         OAMS_TRADITIONAL_GIS_ORDER);
    3352           1 :                                     if (GML_IsSRSLatLongOrder(pszSRSName))
    3353           1 :                                         m_poStandaloneGeom->swapXY();
    3354             :                                 }
    3355           1 :                                 break;
    3356             :                             }
    3357             :                         }
    3358             :                     }
    3359           1 :                     CPLDestroyXMLNode(psTree);
    3360             :                 }
    3361             :             }
    3362             :         }
    3363             :     }
    3364             : 
    3365         431 :     const char *pszDescription = strstr(pszXML, "<gml:description>");
    3366         431 :     if (pszDescription &&
    3367           1 :         (pszFeatureMember == nullptr || pszDescription < pszFeatureMember))
    3368             :     {
    3369           8 :         pszDescription += strlen("<gml:description>");
    3370             :         const char *pszEndDescription =
    3371           8 :             strstr(pszDescription, "</gml:description>");
    3372           8 :         if (pszEndDescription)
    3373             :         {
    3374          16 :             CPLString osTmp(pszDescription);
    3375           8 :             osTmp.resize(pszEndDescription - pszDescription);
    3376           8 :             char *pszTmp = CPLUnescapeString(osTmp, nullptr, CPLES_XML);
    3377           8 :             if (pszTmp)
    3378           8 :                 SetMetadataItem("DESCRIPTION", pszTmp);
    3379           8 :             CPLFree(pszTmp);
    3380             :         }
    3381             :     }
    3382             : 
    3383         431 :     const char *l_pszName = strstr(pszXML, "<gml:name");
    3384         431 :     if (l_pszName)
    3385          11 :         l_pszName = strchr(l_pszName, '>');
    3386         431 :     if (l_pszName &&
    3387           4 :         (pszFeatureMember == nullptr || l_pszName < pszFeatureMember))
    3388             :     {
    3389           8 :         l_pszName++;
    3390           8 :         const char *pszEndName = strstr(l_pszName, "</gml:name>");
    3391           8 :         if (pszEndName)
    3392             :         {
    3393          16 :             CPLString osTmp(l_pszName);
    3394           8 :             osTmp.resize(pszEndName - l_pszName);
    3395           8 :             char *pszTmp = CPLUnescapeString(osTmp, nullptr, CPLES_XML);
    3396           8 :             if (pszTmp)
    3397           8 :                 SetMetadataItem("NAME", pszTmp);
    3398           8 :             CPLFree(pszTmp);
    3399             :         }
    3400             :     }
    3401             : 
    3402             :     // Detect a few fields in gml: namespace inside features
    3403         431 :     if (pszFeatureMember)
    3404             :     {
    3405         331 :         if (strstr(pszFeatureMember, "<gml:description>"))
    3406           1 :             m_aosGMLExtraElements.push_back("description");
    3407         331 :         if (strstr(pszFeatureMember, "<gml:identifier>") ||
    3408         331 :             strstr(pszFeatureMember, "<gml:identifier "))
    3409           1 :             m_aosGMLExtraElements.push_back("identifier");
    3410         331 :         if (strstr(pszFeatureMember, "<gml:name>") ||
    3411         327 :             strstr(pszFeatureMember, "<gml:name "))
    3412           4 :             m_aosGMLExtraElements.push_back("name");
    3413             :     }
    3414             : 
    3415         431 :     char *pszEndBoundedBy = strstr(pszXML, "</wfs:boundedBy>");
    3416         431 :     bool bWFSBoundedBy = false;
    3417         431 :     if (pszEndBoundedBy != nullptr)
    3418           2 :         bWFSBoundedBy = true;
    3419             :     else
    3420         429 :         pszEndBoundedBy = strstr(pszXML, "</gml:boundedBy>");
    3421         431 :     if (pszStartTag != nullptr && pszEndBoundedBy != nullptr)
    3422             :     {
    3423         213 :         char szSRSName[128] = {};
    3424             : 
    3425             :         // Find a srsName somewhere for some WFS 2.0 documents that have not it
    3426             :         // set at the <wfs:boundedBy> element. e.g.
    3427             :         // http://geoserv.weichand.de:8080/geoserver/wfs?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAME=bvv:gmd_ex
    3428         213 :         if (bIsWFS)
    3429             :         {
    3430          24 :             ExtractSRSName(pszXML, szSRSName, sizeof(szSRSName));
    3431             :         }
    3432             : 
    3433         213 :         pszEndBoundedBy[strlen("</gml:boundedBy>")] = '\0';
    3434         213 :         strcat(pszXML, "</");
    3435         213 :         strcat(pszXML, szStartTag);
    3436         213 :         strcat(pszXML, ">");
    3437             : 
    3438         213 :         CPLPushErrorHandler(CPLQuietErrorHandler);
    3439         213 :         CPLXMLNode *psXML = CPLParseXMLString(pszXML);
    3440         213 :         CPLPopErrorHandler();
    3441         213 :         CPLErrorReset();
    3442         213 :         if (psXML != nullptr)
    3443             :         {
    3444         210 :             CPLXMLNode *psBoundedBy = nullptr;
    3445         210 :             CPLXMLNode *psIter = psXML;
    3446         416 :             while (psIter != nullptr)
    3447             :             {
    3448         416 :                 psBoundedBy = CPLGetXMLNode(
    3449             :                     psIter, bWFSBoundedBy ? "wfs:boundedBy" : "gml:boundedBy");
    3450         416 :                 if (psBoundedBy != nullptr)
    3451         210 :                     break;
    3452         206 :                 psIter = psIter->psNext;
    3453             :             }
    3454             : 
    3455         210 :             const char *pszLowerCorner = nullptr;
    3456         210 :             const char *pszUpperCorner = nullptr;
    3457         210 :             const char *pszSRSName = nullptr;
    3458         210 :             if (psBoundedBy != nullptr)
    3459             :             {
    3460             :                 CPLXMLNode *psEnvelope =
    3461         210 :                     CPLGetXMLNode(psBoundedBy, "gml:Envelope");
    3462         210 :                 if (psEnvelope)
    3463             :                 {
    3464         108 :                     pszSRSName = CPLGetXMLValue(psEnvelope, "srsName", nullptr);
    3465             :                     pszLowerCorner =
    3466         108 :                         CPLGetXMLValue(psEnvelope, "gml:lowerCorner", nullptr);
    3467             :                     pszUpperCorner =
    3468         108 :                         CPLGetXMLValue(psEnvelope, "gml:upperCorner", nullptr);
    3469             :                 }
    3470             :             }
    3471             : 
    3472         210 :             if (bIsWFS && pszSRSName == nullptr && pszLowerCorner != nullptr &&
    3473           0 :                 pszUpperCorner != nullptr && szSRSName[0] != '\0')
    3474             :             {
    3475           0 :                 pszSRSName = szSRSName;
    3476             :             }
    3477             : 
    3478         210 :             if (pszSRSName != nullptr && pszLowerCorner != nullptr &&
    3479             :                 pszUpperCorner != nullptr)
    3480             :             {
    3481          56 :                 char **papszLC = CSLTokenizeString(pszLowerCorner);
    3482          56 :                 char **papszUC = CSLTokenizeString(pszUpperCorner);
    3483          56 :                 if (CSLCount(papszLC) >= 2 && CSLCount(papszUC) >= 2)
    3484             :                 {
    3485          56 :                     CPLDebug("GML", "Global SRS = %s", pszSRSName);
    3486             : 
    3487          56 :                     if (STARTS_WITH(pszSRSName,
    3488             :                                     "http://www.opengis.net/gml/srs/epsg.xml#"))
    3489             :                     {
    3490           0 :                         std::string osWork;
    3491           0 :                         osWork.assign("EPSG:", 5);
    3492           0 :                         osWork.append(pszSRSName + 40);
    3493           0 :                         poReader->SetGlobalSRSName(osWork.c_str());
    3494             :                     }
    3495             :                     else
    3496             :                     {
    3497          56 :                         poReader->SetGlobalSRSName(pszSRSName);
    3498             :                     }
    3499             : 
    3500          56 :                     const double dfMinX = CPLAtofM(papszLC[0]);
    3501          56 :                     const double dfMinY = CPLAtofM(papszLC[1]);
    3502          56 :                     const double dfMaxX = CPLAtofM(papszUC[0]);
    3503          56 :                     const double dfMaxY = CPLAtofM(papszUC[1]);
    3504             : 
    3505          56 :                     SetExtents(dfMinX, dfMinY, dfMaxX, dfMaxY);
    3506             :                 }
    3507          56 :                 CSLDestroy(papszLC);
    3508          56 :                 CSLDestroy(papszUC);
    3509             :             }
    3510             : 
    3511         210 :             CPLDestroyXMLNode(psXML);
    3512             :         }
    3513             :     }
    3514             : 
    3515         431 :     CPLFree(pszXML);
    3516         431 : }
    3517             : 
    3518             : /************************************************************************/
    3519             : /*                             SetExtents()                             */
    3520             : /************************************************************************/
    3521             : 
    3522          56 : void OGRGMLDataSource::SetExtents(double dfMinX, double dfMinY, double dfMaxX,
    3523             :                                   double dfMaxY)
    3524             : {
    3525          56 :     sBoundingRect.MinX = dfMinX;
    3526          56 :     sBoundingRect.MinY = dfMinY;
    3527          56 :     sBoundingRect.MaxX = dfMaxX;
    3528          56 :     sBoundingRect.MaxY = dfMaxY;
    3529          56 : }
    3530             : 
    3531             : /************************************************************************/
    3532             : /*                             GetAppPrefix()                           */
    3533             : /************************************************************************/
    3534             : 
    3535        1100 : const char *OGRGMLDataSource::GetAppPrefix() const
    3536             : {
    3537        1100 :     return CSLFetchNameValueDef(papszCreateOptions, "PREFIX", "ogr");
    3538             : }
    3539             : 
    3540             : /************************************************************************/
    3541             : /*                            RemoveAppPrefix()                         */
    3542             : /************************************************************************/
    3543             : 
    3544         563 : bool OGRGMLDataSource::RemoveAppPrefix() const
    3545             : {
    3546         563 :     if (CPLTestBool(
    3547         563 :             CSLFetchNameValueDef(papszCreateOptions, "STRIP_PREFIX", "FALSE")))
    3548          26 :         return true;
    3549         537 :     const char *pszPrefix = GetAppPrefix();
    3550         537 :     return pszPrefix[0] == '\0';
    3551             : }
    3552             : 
    3553             : /************************************************************************/
    3554             : /*                        WriteFeatureBoundedBy()                       */
    3555             : /************************************************************************/
    3556             : 
    3557         181 : bool OGRGMLDataSource::WriteFeatureBoundedBy() const
    3558             : {
    3559         181 :     return CPLTestBool(CSLFetchNameValueDef(
    3560         362 :         papszCreateOptions, "WRITE_FEATURE_BOUNDED_BY", "TRUE"));
    3561             : }
    3562             : 
    3563             : /************************************************************************/
    3564             : /*                          GetSRSDimensionLoc()                        */
    3565             : /************************************************************************/
    3566             : 
    3567         215 : const char *OGRGMLDataSource::GetSRSDimensionLoc() const
    3568             : {
    3569         215 :     return CSLFetchNameValue(papszCreateOptions, "SRSDIMENSION_LOC");
    3570             : }
    3571             : 
    3572             : /************************************************************************/
    3573             : /*                        GMLFeatureCollection()                     */
    3574             : /************************************************************************/
    3575             : 
    3576         564 : bool OGRGMLDataSource::GMLFeatureCollection() const
    3577             : {
    3578        1064 :     return IsGML3Output() &&
    3579        1064 :            CPLFetchBool(papszCreateOptions, "GML_FEATURE_COLLECTION", false);
    3580             : }

Generated by: LCOV version 1.14