LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/gml - ogrgmldatasource.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1537 1720 89.4 %
Date: 2026-01-28 16:13:21 Functions: 34 41 82.9 %

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

Generated by: LCOV version 1.14