LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/gml - ogrgmldatasource.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1429 1594 89.6 %
Date: 2024-11-21 22:18:42 Functions: 30 37 81.1 %

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

Generated by: LCOV version 1.14