LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/gml - ogrgmldatasource.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1412 1588 88.9 %
Date: 2024-05-07 17:03:27 Functions: 29 36 80.6 %

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

Generated by: LCOV version 1.14