LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/gmlas - ogrgmlaswriter.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1043 1237 84.3 %
Date: 2024-05-06 16:27:07 Functions: 36 38 94.7 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  * Project:  OGR
       3             :  * Purpose:  OGRGMLASDriver implementation
       4             :  * Author:   Even Rouault, <even dot rouault at spatialys dot com>
       5             :  *
       6             :  * Initial development funded by the European Earth observation programme
       7             :  * Copernicus
       8             :  *
       9             :  ******************************************************************************
      10             :  * Copyright (c) 2016, Even Rouault, <even dot rouault at spatialys dot com>
      11             :  *
      12             :  * Permission is hereby granted, free of charge, to any person obtaining a
      13             :  * copy of this software and associated documentation files (the "Software"),
      14             :  * to deal in the Software without restriction, including without limitation
      15             :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      16             :  * and/or sell copies of the Software, and to permit persons to whom the
      17             :  * Software is furnished to do so, subject to the following conditions:
      18             :  *
      19             :  * The above copyright notice and this permission notice shall be included
      20             :  * in all copies or substantial portions of the Software.
      21             :  *
      22             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      23             :  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      24             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
      25             :  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      26             :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      27             :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      28             :  * DEALINGS IN THE SOFTWARE.
      29             :  ****************************************************************************/
      30             : 
      31             : #include "ogr_gmlas.h"
      32             : #include "ogr_p.h"
      33             : #include "ogrgeojsonreader.h"
      34             : #include "cpl_time.h"
      35             : 
      36             : #include <algorithm>
      37             : 
      38             : namespace GMLAS
      39             : {
      40             : 
      41             : /************************************************************************/
      42             : /*                          GMLASWriter                                 */
      43             : /************************************************************************/
      44             : 
      45             : typedef std::pair<CPLString, CPLString> PairNSElement;
      46             : 
      47             : typedef std::vector<PairNSElement> XPathComponents;
      48             : 
      49             : typedef std::pair<CPLString, CPLString> PairLayerNameColName;
      50             : 
      51             : class LayerDescription
      52             : {
      53             :   public:
      54             :     CPLString osName{};
      55             :     CPLString osXPath{};
      56             :     CPLString osPKIDName{};
      57             :     CPLString osParentPKIDName{};
      58             :     bool bIsSelected = false;
      59             :     bool bIsTopLevel = false;
      60             :     bool bIsJunction = false;
      61             :     // map a field sequential number to a field
      62             :     std::map<int, GMLASField> oMapIdxToField{};
      63             :     // map a field xpath to its sequential number
      64             :     std::map<CPLString, int> oMapFieldXPathToIdx{};
      65             :     std::map<CPLString, int> oMapFieldNameToOGRIdx{};
      66             :     std::vector<PairLayerNameColName> aoReferencingLayers{};
      67             : 
      68             :     // NOTE: this doesn't scale to arbitrarily large datasets
      69             :     std::set<GIntBig> aoSetReferencedFIDs{};
      70             : 
      71         117 :     LayerDescription() = default;
      72             : 
      73         852 :     int GetOGRIdxFromFieldName(const CPLString &osFieldName) const
      74             :     {
      75         852 :         const auto oIter = oMapFieldNameToOGRIdx.find(osFieldName);
      76         852 :         if (oIter == oMapFieldNameToOGRIdx.end())
      77           0 :             return -1;
      78         852 :         return oIter->second;
      79             :     }
      80             : };
      81             : 
      82             : class GMLASWriter
      83             : {
      84             :     GMLASConfiguration m_oConf{};
      85             :     CPLString m_osFilename{};
      86             :     CPLString m_osGMLVersion{};
      87             :     CPLString m_osSRSNameFormat{};
      88             : #ifdef _WIN32
      89             :     CPLString m_osEOL = "\r\n";
      90             : #else
      91             :     CPLString m_osEOL = "\n";
      92             : #endif
      93             :     GDALDataset *m_poSrcDS;
      94             :     CPLStringList m_aosOptions{};
      95             :     VSIVirtualHandleUniquePtr m_fpXML{};
      96             :     std::unique_ptr<OGRGMLASDataSource> m_poTmpDS{};
      97             :     OGRLayer *m_poLayersMDLayer = nullptr;
      98             :     OGRLayer *m_poFieldsMDLayer = nullptr;
      99             :     OGRLayer *m_poLayerRelationshipsLayer = nullptr;
     100             :     std::vector<LayerDescription> m_aoLayerDesc{};
     101             :     std::map<CPLString, int> m_oMapLayerNameToIdx{};
     102             :     std::map<CPLString, int> m_oMapXPathToIdx{};
     103             :     std::map<CPLString, OGRLayer *> m_oMapLayerNameToLayer{};
     104             :     std::map<CPLString, XPathComponents> m_oMapXPathToComponents{};
     105             :     std::map<const OGRSpatialReference *, bool> m_oMapSRSToCoordSwap{};
     106             : 
     107             :     CPLString m_osTargetNameSpace = szOGRGMLAS_URI;
     108             :     CPLString m_osTargetNameSpacePrefix = szOGRGMLAS_PREFIX;
     109             : 
     110             :     CPLString m_osIndentation = std::string(INDENT_SIZE_DEFAULT, ' ');
     111             :     int m_nIndentLevel = 0;
     112             : 
     113         674 :     void IncIndent()
     114             :     {
     115         674 :         ++m_nIndentLevel;
     116         674 :     }
     117             : 
     118         674 :     void DecIndent()
     119             :     {
     120         674 :         --m_nIndentLevel;
     121         674 :     }
     122             : 
     123             :     void PrintIndent(VSILFILE *fp);
     124             : 
     125             :     void PrintLine(VSILFILE *fp, const char *fmt, ...)
     126             :         CPL_PRINT_FUNC_FORMAT(3, 4);
     127             : 
     128             :     bool WriteXSD(const CPLString &osXSDFilenameIn,
     129             :                   const std::vector<PairURIFilename> &aoXSDs);
     130             :     bool WriteXMLHeader(bool bWFS2FeatureCollection, GIntBig nTotalFeatures,
     131             :                         bool bGenerateXSD, const CPLString &osXSDFilenameIn,
     132             :                         const std::vector<PairURIFilename> &aoXSDs,
     133             :                         const std::map<CPLString, CPLString> &oMapURIToPrefix);
     134             :     bool CollectLayers();
     135             :     bool CollectFields();
     136             :     bool CollectRelationships();
     137             :     void ComputeTopLevelFIDs();
     138             :     bool WriteLayer(bool bWFS2FeatureCollection, const LayerDescription &oDesc,
     139             :                     GIntBig &nFeaturesWritten, GIntBig nTotalTopLevelFeatures,
     140             :                     GDALProgressFunc pfnProgress, void *pProgressData);
     141             : 
     142             :     bool WriteFeature(OGRFeature *poFeature, const LayerDescription &oLayerDesc,
     143             :                       const std::set<CPLString> &oSetLayersInIteration,
     144             :                       const XPathComponents &aoInitialComponents,
     145             :                       const XPathComponents &aoPrefixComponents, int nRecLevel);
     146             : 
     147             :     void WriteClosingTags(size_t nCommonLength,
     148             :                           const XPathComponents &aoCurComponents,
     149             :                           const XPathComponents &aoNewComponents,
     150             :                           bool bCurIsRegularField, bool bNewIsRegularField);
     151             : 
     152             :     void WriteClosingAndStartingTags(const XPathComponents &aoCurComponents,
     153             :                                      const XPathComponents &aoNewComponents,
     154             :                                      bool bCurIsRegularField);
     155             : 
     156             :     void PrintMultipleValuesSeparator(const GMLASField &oField,
     157             :                                       const XPathComponents &aoFieldComponents);
     158             : 
     159             :     OGRLayer *
     160             :     GetFilteredLayer(OGRLayer *poSrcLayer, const CPLString &osFilter,
     161             :                      const std::set<CPLString> &oSetLayersInIteration);
     162             :     void ReleaseFilteredLayer(OGRLayer *poSrcLayer, OGRLayer *poIterLayer);
     163             : 
     164             :     bool WriteFieldRegular(OGRFeature *poFeature, const GMLASField &oField,
     165             :                            const LayerDescription &oLayerDesc,
     166             :                            /*XPathComponents& aoLayerComponents,*/
     167             :                            XPathComponents &aoCurComponents,
     168             :                            const XPathComponents &aoPrefixComponents,
     169             :                            /*const std::set<CPLString>& oSetLayersInIteration,*/
     170             :                            bool &bAtLeastOneFieldWritten,
     171             :                            bool &bCurIsRegularField);
     172             : 
     173             :     bool WriteFieldNoLink(OGRFeature *poFeature, const GMLASField &oField,
     174             :                           const LayerDescription &oLayerDesc,
     175             :                           XPathComponents &aoLayerComponents,
     176             :                           XPathComponents &aoCurComponents,
     177             :                           const XPathComponents &aoPrefixComponents,
     178             :                           const std::set<CPLString> &oSetLayersInIteration,
     179             :                           int nRecLevel, bool &bAtLeastOneFieldWritten,
     180             :                           bool &bCurIsRegularField);
     181             : 
     182             :     bool WriteFieldWithLink(OGRFeature *poFeature, const GMLASField &oField,
     183             :                             const LayerDescription &oLayerDesc,
     184             :                             XPathComponents &aoLayerComponents,
     185             :                             XPathComponents &aoCurComponents,
     186             :                             const XPathComponents &aoPrefixComponents,
     187             :                             const std::set<CPLString> &oSetLayersInIteration,
     188             :                             int nRecLevel, bool &bAtLeastOneFieldWritten,
     189             :                             bool &bCurIsRegularField);
     190             : 
     191             :     bool WriteFieldJunctionTable(
     192             :         OGRFeature *poFeature, const GMLASField &oField,
     193             :         const LayerDescription &oLayerDesc, XPathComponents &aoLayerComponents,
     194             :         XPathComponents &aoCurComponents,
     195             :         const XPathComponents &aoPrefixComponents,
     196             :         const std::set<CPLString> &oSetLayersInIteration, int nRecLevel,
     197             :         bool &bAtLeastOneFieldWritten, bool &bCurIsRegularField);
     198             : 
     199             :     void Close();
     200             : 
     201             :     OGRLayer *GetLayerByName(const CPLString &osName);
     202             : 
     203             :     const XPathComponents &SplitXPath(const CPLString &osXPath);
     204             : 
     205             :     bool GetCoordSwap(const OGRSpatialReference *poSRS);
     206             : 
     207             :     CPL_DISALLOW_COPY_ASSIGN(GMLASWriter)
     208             : 
     209             :   public:
     210             :     GMLASWriter(const char *pszFilename, GDALDataset *poSrcDS,
     211             :                 CSLConstList papszOptions);
     212             : 
     213             :     bool Write(GDALProgressFunc pfnProgress, void *pProgressData);
     214             : };
     215             : 
     216             : /************************************************************************/
     217             : /*                            GMLASWriter()                             */
     218             : /************************************************************************/
     219             : 
     220          21 : GMLASWriter::GMLASWriter(const char *pszFilename, GDALDataset *poSrcDS,
     221          21 :                          CSLConstList papszOptions)
     222          21 :     : m_osFilename(pszFilename), m_poSrcDS(poSrcDS), m_aosOptions(papszOptions)
     223             : {
     224          21 : }
     225             : 
     226             : /************************************************************************/
     227             : /*                               Close()                                */
     228             : /************************************************************************/
     229             : 
     230          10 : void GMLASWriter::Close()
     231             : {
     232          10 :     m_fpXML.reset();
     233          10 :     m_poTmpDS.reset();
     234          10 : }
     235             : 
     236             : /************************************************************************/
     237             : /*                              Write()                                 */
     238             : /************************************************************************/
     239             : 
     240          21 : bool GMLASWriter::Write(GDALProgressFunc pfnProgress, void *pProgressData)
     241             : {
     242          22 :     if (m_poSrcDS->GetLayerCount() == 0 &&
     243           1 :         m_poSrcDS->GetLayerByName(szOGR_OTHER_METADATA) == nullptr)
     244             :     {
     245           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Source dataset has no layers");
     246           1 :         return false;
     247             :     }
     248             : 
     249             :     // Load configuration file
     250             :     CPLString osConfigFile =
     251          40 :         m_aosOptions.FetchNameValueDef(szCONFIG_FILE_OPTION, "");
     252          20 :     if (osConfigFile.empty())
     253             :     {
     254             :         const char *pszConfigFile =
     255          19 :             CPLFindFile("gdal", szDEFAULT_CONF_FILENAME);
     256          19 :         if (pszConfigFile)
     257          19 :             osConfigFile = pszConfigFile;
     258             :     }
     259          20 :     if (osConfigFile.empty())
     260             :     {
     261           0 :         CPLError(CE_Warning, CPLE_AppDefined,
     262             :                  "No configuration file found. Using hard-coded defaults");
     263           0 :         m_oConf.Finalize();
     264             :     }
     265             :     else
     266             :     {
     267          20 :         if (!m_oConf.Load(osConfigFile))
     268             :         {
     269           1 :             CPLError(CE_Failure, CPLE_AppDefined,
     270             :                      "Loading of configuration failed");
     271           1 :             return false;
     272             :         }
     273             :     }
     274             : 
     275             :     CPLString osXSDFilenames =
     276          38 :         m_aosOptions.FetchNameValueDef(szINPUT_XSD_OPTION, "");
     277          38 :     std::vector<PairURIFilename> aoXSDs;
     278          38 :     std::map<CPLString, CPLString> oMapURIToPrefix;
     279          38 :     CPLString osGMLVersion;
     280             : 
     281          19 :     if (!osXSDFilenames.empty())
     282             :     {
     283             :         // Create a fake GMLAS dataset from the XSD= value
     284           2 :         m_poTmpDS = std::make_unique<OGRGMLASDataSource>();
     285           2 :         GDALOpenInfo oOpenInfo(szGMLAS_PREFIX, GA_ReadOnly);
     286           2 :         oOpenInfo.papszOpenOptions = CSLSetNameValue(
     287             :             oOpenInfo.papszOpenOptions, szXSD_OPTION, osXSDFilenames);
     288           2 :         bool bRet = m_poTmpDS->Open(&oOpenInfo);
     289           2 :         CSLDestroy(oOpenInfo.papszOpenOptions);
     290           2 :         oOpenInfo.papszOpenOptions = nullptr;
     291           2 :         if (!bRet)
     292             :         {
     293           1 :             return false;
     294             :         }
     295             :     }
     296             : 
     297          18 :     GDALDataset *poQueryDS = m_poTmpDS ? m_poTmpDS.get() : m_poSrcDS;
     298             : 
     299             :     // No explicit XSD creation option, then we assume that the source
     300             :     // dataset contains all the metadata layers we need
     301             :     OGRLayer *poOtherMetadataLayer =
     302          18 :         poQueryDS->GetLayerByName(szOGR_OTHER_METADATA);
     303          18 :     if (poOtherMetadataLayer == nullptr)
     304             :     {
     305           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     306             :                  "Cannot establish schema since no %s creation option "
     307             :                  "specified and no %s found in source "
     308             :                  "dataset. One of them must be defined.",
     309             :                  szINPUT_XSD_OPTION, szOGR_OTHER_METADATA);
     310           1 :         return false;
     311             :     }
     312             : 
     313          17 :     m_poLayersMDLayer = poQueryDS->GetLayerByName(szOGR_LAYERS_METADATA);
     314          17 :     m_poFieldsMDLayer = poQueryDS->GetLayerByName(szOGR_FIELDS_METADATA);
     315          17 :     m_poLayerRelationshipsLayer =
     316          17 :         poQueryDS->GetLayerByName(szOGR_LAYER_RELATIONSHIPS);
     317          17 :     if (m_poLayersMDLayer == nullptr)
     318             :     {
     319           1 :         CPLError(CE_Failure, CPLE_AppDefined, "%s not found",
     320             :                  szOGR_LAYERS_METADATA);
     321           1 :         return false;
     322             :     }
     323          16 :     if (m_poFieldsMDLayer == nullptr)
     324             :     {
     325           1 :         CPLError(CE_Failure, CPLE_AppDefined, "%s not found",
     326             :                  szOGR_FIELDS_METADATA);
     327           1 :         return false;
     328             :     }
     329          15 :     if (m_poLayerRelationshipsLayer == nullptr)
     330             :     {
     331           1 :         CPLError(CE_Failure, CPLE_AppDefined, "%s not found",
     332             :                  szOGR_LAYER_RELATIONSHIPS);
     333           1 :         return false;
     334             :     }
     335             : 
     336          28 :     std::map<int, CPLString> oMapToUri;
     337          28 :     std::map<int, CPLString> oMapToLocation;
     338          28 :     std::map<int, CPLString> oMapToPrefix;
     339         129 :     for (auto &&poFeature : *poOtherMetadataLayer)
     340             :     {
     341         115 :         const char *pszKey = poFeature->GetFieldAsString(szKEY);
     342         115 :         int i = 0;
     343         115 :         if (sscanf(pszKey, szNAMESPACE_URI_FMT, &i) == 1 && i > 0)
     344             :         {
     345          26 :             oMapToUri[i] = poFeature->GetFieldAsString(szVALUE);
     346             :         }
     347          89 :         else if (sscanf(pszKey, szNAMESPACE_LOCATION_FMT, &i) == 1 && i > 0)
     348             :         {
     349          13 :             oMapToLocation[i] = poFeature->GetFieldAsString(szVALUE);
     350             :         }
     351          76 :         else if (sscanf(pszKey, szNAMESPACE_PREFIX_FMT, &i) == 1 && i > 0)
     352             :         {
     353          26 :             oMapToPrefix[i] = poFeature->GetFieldAsString(szVALUE);
     354             :         }
     355          50 :         else if (EQUAL(pszKey, szGML_VERSION))
     356             :         {
     357           0 :             osGMLVersion = poFeature->GetFieldAsString(szVALUE);
     358             :         }
     359             :     }
     360          14 :     poOtherMetadataLayer->ResetReading();
     361             : 
     362          40 :     for (int i = 1; i <= static_cast<int>(oMapToUri.size()); ++i)
     363             :     {
     364          26 :         if (oMapToUri.find(i) != oMapToUri.end())
     365             :         {
     366          26 :             const CPLString &osURI(oMapToUri[i]);
     367          26 :             aoXSDs.push_back(PairURIFilename(osURI, oMapToLocation[i]));
     368          26 :             if (oMapToPrefix.find(i) != oMapToPrefix.end())
     369             :             {
     370          26 :                 oMapURIToPrefix[osURI] = oMapToPrefix[i];
     371             :             }
     372             :         }
     373             :     }
     374             : 
     375          14 :     if (!CollectLayers())
     376           1 :         return false;
     377             : 
     378          13 :     if (!CollectFields())
     379           0 :         return false;
     380             : 
     381          13 :     if (!CollectRelationships())
     382           0 :         return false;
     383             : 
     384          13 :     const char *pszLayers = m_aosOptions.FetchNameValue(szLAYERS_OPTION);
     385          13 :     if (pszLayers)
     386             :     {
     387           6 :         for (const auto &oLayerIter : m_oMapLayerNameToIdx)
     388             :         {
     389           3 :             LayerDescription &oDesc = m_aoLayerDesc[oLayerIter.second];
     390           3 :             oDesc.bIsSelected = false;
     391             :         }
     392             : 
     393           3 :         char **papszLayers = CSLTokenizeString2(pszLayers, ",", 0);
     394           5 :         for (char **papszIter = papszLayers; *papszIter != nullptr; ++papszIter)
     395             :         {
     396           3 :             if (EQUAL(*papszIter, "{SPATIAL_LAYERS}"))
     397             :             {
     398           2 :                 for (const auto &oLayerIter : m_oMapLayerNameToIdx)
     399             :                 {
     400           1 :                     LayerDescription &oDesc = m_aoLayerDesc[oLayerIter.second];
     401           1 :                     if (oDesc.bIsTopLevel)
     402             :                     {
     403           1 :                         bool bIsGeometric = false;
     404           2 :                         for (const auto &oFieldIter : oDesc.oMapIdxToField)
     405             :                         {
     406           2 :                             if (oFieldIter.second.GetType() ==
     407             :                                 GMLAS_FT_GEOMETRY)
     408             :                             {
     409           1 :                                 bIsGeometric = true;
     410           1 :                                 break;
     411             :                             }
     412             :                         }
     413           1 :                         oDesc.bIsSelected = bIsGeometric;
     414             :                     }
     415             :                 }
     416             :             }
     417             :             else
     418             :             {
     419           2 :                 const auto oLayerIter = m_oMapLayerNameToIdx.find(*papszIter);
     420           2 :                 if (oLayerIter == m_oMapLayerNameToIdx.end())
     421             :                 {
     422           1 :                     CPLError(CE_Warning, CPLE_AppDefined,
     423             :                              "Layer %s specified in LAYERS option "
     424             :                              "does not exist",
     425             :                              *papszIter);
     426           1 :                     CSLDestroy(papszLayers);
     427           1 :                     return false;
     428             :                 }
     429             :                 else
     430             :                 {
     431           1 :                     LayerDescription &oDesc = m_aoLayerDesc[oLayerIter->second];
     432           1 :                     oDesc.bIsSelected = true;
     433             :                 }
     434             :             }
     435             :         }
     436           2 :         CSLDestroy(papszLayers);
     437             :     }
     438             :     else
     439             :     {
     440          10 :         ComputeTopLevelFIDs();
     441             :     }
     442             : 
     443          12 :     const bool bWFS2FeatureCollection = EQUAL(
     444             :         m_aosOptions.FetchNameValueDef(szWRAPPING_OPTION, m_oConf.m_osWrapping),
     445             :         szWFS2_FEATURECOLLECTION);
     446             : 
     447          12 :     if (pfnProgress == GDALDummyProgress)
     448          12 :         pfnProgress = nullptr;
     449             :     // Compute total number of top level features
     450          12 :     GIntBig nTotalTopLevelFeatures = -1;
     451          12 :     if (pfnProgress != nullptr || bWFS2FeatureCollection)
     452             :     {
     453           4 :         nTotalTopLevelFeatures = 0;
     454           8 :         for (const auto &oLayerIter : m_oMapLayerNameToIdx)
     455             :         {
     456           4 :             const LayerDescription &oDesc = m_aoLayerDesc[oLayerIter.second];
     457           4 :             OGRLayer *poSrcLayer = m_poSrcDS->GetLayerByName(oDesc.osName);
     458           4 :             if (oDesc.bIsSelected && poSrcLayer != nullptr)
     459             :             {
     460           4 :                 nTotalTopLevelFeatures += poSrcLayer->GetFeatureCount(true);
     461           4 :                 nTotalTopLevelFeatures -=
     462           4 :                     static_cast<GIntBig>(oDesc.aoSetReferencedFIDs.size());
     463             :             }
     464             :         }
     465           4 :         CPLDebug("GMLAS", CPL_FRMT_GIB " top level features to be written",
     466             :                  nTotalTopLevelFeatures);
     467             :     }
     468             : 
     469             :     // Now read options related to writing
     470             :     int nIndentSize =
     471             :         std::min(INDENT_SIZE_MAX,
     472             :                  std::max(INDENT_SIZE_MIN,
     473          12 :                           atoi(m_aosOptions.FetchNameValueDef(
     474             :                               szINDENT_SIZE_OPTION,
     475          12 :                               CPLSPrintf("%d", m_oConf.m_nIndentSize)))));
     476          12 :     m_osIndentation.assign(nIndentSize, ' ');
     477             : 
     478          36 :     if (oMapURIToPrefix.find(szGML32_URI) != oMapURIToPrefix.end() ||
     479             :         // Used by tests
     480          24 :         oMapURIToPrefix.find("http://fake_gml32") != oMapURIToPrefix.end())
     481             :     {
     482           8 :         m_osGMLVersion = "3.2.1";
     483             :     }
     484             :     else
     485             :     {
     486           4 :         m_osGMLVersion = osGMLVersion;
     487           4 :         CPL_IGNORE_RET_VAL(osGMLVersion);
     488             :     }
     489             : 
     490             :     m_osSRSNameFormat = m_aosOptions.FetchNameValueDef(
     491          12 :         szSRSNAME_FORMAT_OPTION, m_oConf.m_osSRSNameFormat);
     492             : 
     493             :     CPLString osLineFormat = m_aosOptions.FetchNameValueDef(
     494          24 :         szLINEFORMAT_OPTION, m_oConf.m_osLineFormat);
     495          12 :     if (!osLineFormat.empty())
     496             :     {
     497          12 :         if (EQUAL(osLineFormat, szCRLF))
     498           0 :             m_osEOL = "\r\n";
     499          12 :         else if (EQUAL(osLineFormat, szLF))
     500           0 :             m_osEOL = "\n";
     501             :     }
     502             : 
     503             :     CPLString osOutXSDFilename =
     504          24 :         m_aosOptions.FetchNameValueDef(szOUTPUT_XSD_FILENAME_OPTION, "");
     505             :     const bool bGenerateXSD =
     506          20 :         !bWFS2FeatureCollection &&
     507          20 :         (m_osFilename != "/vsistdout/" || !osOutXSDFilename.empty()) &&
     508           8 :         m_aosOptions.FetchBool(szGENERATE_XSD_OPTION, true);
     509             : 
     510             :     // Write .xsd
     511          12 :     if (bWFS2FeatureCollection)
     512           4 :         VSIUnlink(CPLResetExtension(m_osFilename, "xsd"));
     513           8 :     else if (bGenerateXSD && !WriteXSD(osOutXSDFilename, aoXSDs))
     514           1 :         return false;
     515             : 
     516             :     // Write .xml header
     517          11 :     if (!WriteXMLHeader(bWFS2FeatureCollection, nTotalTopLevelFeatures,
     518             :                         bGenerateXSD, osOutXSDFilename, aoXSDs,
     519             :                         oMapURIToPrefix))
     520           1 :         return false;
     521             : 
     522             :     // Iterate over layers
     523          10 :     GIntBig nFeaturesWritten = 0;
     524          10 :     bool bRet = true;
     525         124 :     for (const auto &oLayerIter : m_oMapLayerNameToIdx)
     526             :     {
     527         114 :         if (m_aoLayerDesc[oLayerIter.second].bIsSelected)
     528             :         {
     529             :             bRet =
     530          30 :                 WriteLayer(bWFS2FeatureCollection,
     531          30 :                            m_aoLayerDesc[oLayerIter.second], nFeaturesWritten,
     532             :                            nTotalTopLevelFeatures, pfnProgress, pProgressData);
     533          30 :             if (!bRet)
     534           0 :                 break;
     535             :         }
     536             :     }
     537          10 :     CPLDebug("GMLAS", CPL_FRMT_GIB " top level features written",
     538             :              nFeaturesWritten);
     539             : 
     540             :     // Epilogue of .xml file
     541          10 :     if (bWFS2FeatureCollection)
     542             :     {
     543           3 :         PrintLine(m_fpXML.get(), "</%s:%s>", szWFS_PREFIX,
     544             :                   szFEATURE_COLLECTION);
     545             :     }
     546             :     else
     547             :     {
     548           7 :         PrintLine(m_fpXML.get(), "</%s:%s>", m_osTargetNameSpacePrefix.c_str(),
     549             :                   szFEATURE_COLLECTION);
     550             :     }
     551             : 
     552          10 :     Close();
     553          10 :     return bRet;
     554             : }
     555             : 
     556             : /************************************************************************/
     557             : /*                           GetLayerByName()                           */
     558             : /************************************************************************/
     559             : 
     560             : // Mostly equivalent to m_poSrcDS->GetLayerByName(), except that we use
     561             : // a map to cache instead of linear search.
     562        1077 : OGRLayer *GMLASWriter::GetLayerByName(const CPLString &osName)
     563             : {
     564        1077 :     const auto oIter = m_oMapLayerNameToLayer.find(osName);
     565        1077 :     if (oIter == m_oMapLayerNameToLayer.end())
     566             :     {
     567         117 :         OGRLayer *poLayer = m_poSrcDS->GetLayerByName(osName);
     568         117 :         m_oMapLayerNameToLayer[osName] = poLayer;
     569         117 :         return poLayer;
     570             :     }
     571         960 :     return oIter->second;
     572             : }
     573             : 
     574             : /************************************************************************/
     575             : /*                            XMLEscape()                               */
     576             : /************************************************************************/
     577             : 
     578         399 : static CPLString XMLEscape(const CPLString &osStr)
     579             : {
     580         399 :     char *pszEscaped = CPLEscapeString(osStr, -1, CPLES_XML);
     581         399 :     CPLString osRet(pszEscaped);
     582         399 :     CPLFree(pszEscaped);
     583         399 :     return osRet;
     584             : }
     585             : 
     586             : /************************************************************************/
     587             : /*                            WriteXSD()                                */
     588             : /************************************************************************/
     589             : 
     590           8 : bool GMLASWriter::WriteXSD(const CPLString &osXSDFilenameIn,
     591             :                            const std::vector<PairURIFilename> &aoXSDs)
     592             : {
     593             :     const CPLString osXSDFilename(
     594           8 :         !osXSDFilenameIn.empty()
     595             :             ? osXSDFilenameIn
     596          16 :             : CPLString(CPLResetExtension(m_osFilename, "xsd")));
     597           8 :     VSILFILE *fpXSD = VSIFOpenL(osXSDFilename, "wb");
     598           8 :     if (fpXSD == nullptr)
     599             :     {
     600           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot create %s",
     601             :                  osXSDFilename.c_str());
     602           1 :         return false;
     603             :     }
     604             : 
     605           7 :     PrintLine(fpXSD, "<?xml version=\"1.0\" encoding=\"utf-8\" ?>");
     606           7 :     PrintLine(fpXSD, "<xs:schema ");
     607           7 :     PrintLine(fpXSD, "    targetNamespace=\"%s\"",
     608          14 :               XMLEscape(m_osTargetNameSpace).c_str());
     609           7 :     PrintLine(fpXSD, "    xmlns:%s=\"%s\"", m_osTargetNameSpacePrefix.c_str(),
     610          14 :               XMLEscape(m_osTargetNameSpace).c_str());
     611           7 :     PrintLine(fpXSD, "    xmlns:xs=\"%s\"", szXS_URI);
     612           7 :     PrintLine(fpXSD, "    elementFormDefault=\"qualified\" version=\"1.0\" >");
     613             : 
     614             :     // Those imports are not really needed, since the schemaLocation are
     615             :     // already specified in the .xml file, but that helps validating the
     616             :     // document with libxml2/xmllint since it can only accept one single main
     617             :     // schema.
     618          21 :     for (size_t i = 0; i < aoXSDs.size(); ++i)
     619             :     {
     620          14 :         if (!aoXSDs[i].second.empty())
     621             :         {
     622           7 :             if (!aoXSDs[i].first.empty())
     623             :             {
     624          14 :                 PrintLine(fpXSD,
     625             :                           "<xs:import namespace=\"%s\" schemaLocation=\"%s\"/>",
     626          14 :                           XMLEscape(aoXSDs[i].first).c_str(),
     627          14 :                           XMLEscape(aoXSDs[i].second).c_str());
     628             :             }
     629             :             else
     630             :             {
     631           0 :                 PrintLine(fpXSD, "<xs:import schemaLocation=\"%s\"/>",
     632           0 :                           XMLEscape(aoXSDs[i].second).c_str());
     633             :             }
     634             :         }
     635             :     }
     636             : 
     637           7 :     PrintLine(fpXSD,
     638             :               "<xs:element name=\"%s\" "
     639             :               "type=\"%s:%sType\"/>",
     640             :               szFEATURE_COLLECTION, m_osTargetNameSpacePrefix.c_str(),
     641             :               szFEATURE_COLLECTION);
     642             : 
     643           7 :     PrintLine(fpXSD, "<xs:complexType name=\"%sType\">", szFEATURE_COLLECTION);
     644           7 :     PrintLine(fpXSD, "  <xs:sequence>");
     645           7 :     PrintLine(fpXSD,
     646             :               "    <xs:element name=\"%s\" "
     647             :               "minOccurs=\"0\" maxOccurs=\"unbounded\">",
     648             :               szFEATURE_MEMBER);
     649           7 :     PrintLine(fpXSD, "      <xs:complexType>");
     650           7 :     PrintLine(fpXSD, "        <xs:sequence>");
     651           7 :     PrintLine(fpXSD, "           <xs:any/>");
     652           7 :     PrintLine(fpXSD, "        </xs:sequence>");
     653           7 :     PrintLine(fpXSD, "      </xs:complexType>");
     654           7 :     PrintLine(fpXSD, "    </xs:element>");
     655           7 :     PrintLine(fpXSD, "  </xs:sequence>");
     656           7 :     PrintLine(fpXSD, "</xs:complexType>");
     657           7 :     PrintLine(fpXSD, "</xs:schema>");
     658             : 
     659           7 :     VSIFCloseL(fpXSD);
     660             : 
     661           7 :     return true;
     662             : }
     663             : 
     664             : /************************************************************************/
     665             : /*                         WriteXMLHeader()                             */
     666             : /************************************************************************/
     667             : 
     668          11 : bool GMLASWriter::WriteXMLHeader(
     669             :     bool bWFS2FeatureCollection, GIntBig nTotalFeatures, bool bGenerateXSD,
     670             :     const CPLString &osXSDFilenameIn,
     671             :     const std::vector<PairURIFilename> &aoXSDs,
     672             :     const std::map<CPLString, CPLString> &oMapURIToPrefix)
     673             : {
     674          11 :     m_fpXML.reset(VSIFOpenL(m_osFilename, "wb"));
     675          11 :     if (m_fpXML == nullptr)
     676             :     {
     677           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot create %s",
     678             :                  m_osFilename.c_str());
     679           1 :         return false;
     680             :     }
     681             : 
     682             :     // Delete potentially existing .gfs file
     683          10 :     VSIUnlink(CPLResetExtension(m_osFilename, "gfs"));
     684             : 
     685          20 :     std::map<CPLString, CPLString> aoWrittenPrefixes;
     686          10 :     aoWrittenPrefixes[szXSI_PREFIX] = szXSI_URI;
     687             : 
     688          10 :     PrintLine(m_fpXML.get(), "<?xml version=\"1.0\" encoding=\"utf-8\" ?>");
     689          10 :     if (bWFS2FeatureCollection)
     690             :     {
     691           3 :         PrintLine(m_fpXML.get(), "<%s:%s", szWFS_PREFIX, szFEATURE_COLLECTION);
     692             : 
     693             :         const CPLString osTimestamp(m_aosOptions.FetchNameValueDef(
     694           3 :             szTIMESTAMP_OPTION, m_oConf.m_osTimestamp));
     695           3 :         if (osTimestamp.empty())
     696             :         {
     697             :             struct tm sTime;
     698           2 :             CPLUnixTimeToYMDHMS(time(nullptr), &sTime);
     699           2 :             PrintLine(m_fpXML.get(),
     700             :                       "    timeStamp=\"%04d-%02d-%02dT%02d:%02d:%02dZ\"",
     701           2 :                       sTime.tm_year + 1900, sTime.tm_mon + 1, sTime.tm_mday,
     702             :                       sTime.tm_hour, sTime.tm_min, sTime.tm_sec);
     703             :         }
     704             :         else
     705             :         {
     706           1 :             PrintLine(m_fpXML.get(), "    timeStamp=\"%s\"",
     707             :                       osTimestamp.c_str());
     708             :         }
     709           3 :         PrintLine(m_fpXML.get(), "    numberMatched=\"unknown\"");
     710           3 :         PrintLine(m_fpXML.get(), "    numberReturned=\"" CPL_FRMT_GIB "\"",
     711             :                   nTotalFeatures);
     712           3 :         PrintLine(m_fpXML.get(), "    xmlns:%s=\"%s\"", szWFS_PREFIX,
     713             :                   szWFS20_URI);
     714           3 :         aoWrittenPrefixes[szWFS_PREFIX] = szWFS20_URI;
     715             :     }
     716             :     else
     717             :     {
     718           7 :         PrintLine(m_fpXML.get(), "<%s:%s", m_osTargetNameSpacePrefix.c_str(),
     719             :                   szFEATURE_COLLECTION);
     720           7 :         PrintLine(m_fpXML.get(), "    xmlns:%s=\"%s\"",
     721             :                   m_osTargetNameSpacePrefix.c_str(),
     722          14 :                   XMLEscape(m_osTargetNameSpace).c_str());
     723             :     }
     724          10 :     PrintLine(m_fpXML.get(), "    xmlns:%s=\"%s\"", szXSI_PREFIX, szXSI_URI);
     725             : 
     726          20 :     CPLString osSchemaURI;
     727          10 :     if (bWFS2FeatureCollection)
     728             :     {
     729             :         const CPLString osWFS20SchemaLocation(m_aosOptions.FetchNameValueDef(
     730           6 :             szWFS20_SCHEMALOCATION_OPTION, m_oConf.m_osWFS20SchemaLocation));
     731           3 :         osSchemaURI += szWFS20_URI;
     732           3 :         osSchemaURI += " ";
     733           3 :         osSchemaURI += osWFS20SchemaLocation;
     734             :     }
     735           7 :     else if (bGenerateXSD || !osXSDFilenameIn.empty())
     736             :     {
     737             :         const CPLString osXSDFilename(
     738           7 :             !osXSDFilenameIn.empty()
     739             :                 ? osXSDFilenameIn
     740             :                 : CPLString(
     741          14 :                       CPLGetFilename(CPLResetExtension(m_osFilename, "xsd"))));
     742           7 :         osSchemaURI += m_osTargetNameSpace;
     743           7 :         osSchemaURI += " ";
     744           7 :         osSchemaURI += osXSDFilename;
     745             :     }
     746             : 
     747          30 :     for (size_t i = 0; i < aoXSDs.size(); ++i)
     748             :     {
     749          20 :         const CPLString &osURI(aoXSDs[i].first);
     750          20 :         const CPLString &osLocation(aoXSDs[i].second);
     751             : 
     752          20 :         CPLString osPrefix;
     753          20 :         if (!osURI.empty())
     754             :         {
     755          20 :             const auto oIter = oMapURIToPrefix.find(osURI);
     756          20 :             if (oIter != oMapURIToPrefix.end())
     757             :             {
     758          20 :                 osPrefix = oIter->second;
     759             :             }
     760             :         }
     761          20 :         if (!osPrefix.empty())
     762             :         {
     763          20 :             const auto &oIter = aoWrittenPrefixes.find(osPrefix);
     764          20 :             if (oIter != aoWrittenPrefixes.end())
     765             :             {
     766           0 :                 if (oIter->second != osURI)
     767             :                 {
     768           0 :                     CPLDebug("GMLAS",
     769             :                              "Namespace prefix %s already defined as URI %s "
     770             :                              "but now redefefined as %s. Skipped",
     771           0 :                              osPrefix.c_str(), oIter->second.c_str(),
     772             :                              osURI.c_str());
     773             :                 }
     774           0 :                 continue;
     775             :             }
     776          20 :             aoWrittenPrefixes[osPrefix] = osURI;
     777             :         }
     778             : 
     779          20 :         if (osURI.empty())
     780             :         {
     781           0 :             if (!osLocation.empty())
     782             :             {
     783           0 :                 PrintLine(m_fpXML.get(), "    xsi:%s=\"%s\"",
     784             :                           szNO_NAMESPACE_SCHEMA_LOCATION,
     785           0 :                           XMLEscape(osLocation).c_str());
     786             :             }
     787             :         }
     788             :         else
     789             :         {
     790          20 :             if (osPrefix.empty())
     791             :             {
     792           0 :                 osPrefix = CPLSPrintf("ns%d", static_cast<int>(i));
     793             :             }
     794             : 
     795          20 :             PrintLine(m_fpXML.get(), "    xmlns:%s=\"%s\"", osPrefix.c_str(),
     796          40 :                       XMLEscape(osURI).c_str());
     797             : 
     798          20 :             if (!osLocation.empty())
     799             :             {
     800          10 :                 if (!osSchemaURI.empty())
     801          10 :                     osSchemaURI += " ";
     802          10 :                 osSchemaURI += osURI;
     803          10 :                 osSchemaURI += " ";
     804          10 :                 osSchemaURI += osLocation;
     805             :             }
     806             :         }
     807             :     }
     808             : 
     809          10 :     if (!osSchemaURI.empty())
     810             :     {
     811          10 :         PrintLine(m_fpXML.get(), "    xsi:%s=\"%s\" >", szSCHEMA_LOCATION,
     812          20 :                   XMLEscape(osSchemaURI).c_str());
     813             :     }
     814             : 
     815             :     // Write optional user comment
     816             :     CPLString osComment(
     817          10 :         m_aosOptions.FetchNameValueDef(szCOMMENT_OPTION, m_oConf.m_osComment));
     818          10 :     if (!osComment.empty())
     819             :     {
     820             :         while (true)
     821             :         {
     822           3 :             const size_t nSizeBefore = osComment.size();
     823           3 :             osComment.replaceAll("--", "- -");
     824           3 :             if (nSizeBefore == osComment.size())
     825           1 :                 break;
     826           2 :         }
     827           1 :         PrintLine(m_fpXML.get(), "<!-- %s -->", osComment.c_str());
     828             :     }
     829             : 
     830          10 :     return true;
     831             : }
     832             : 
     833             : /************************************************************************/
     834             : /*                          CollectLayers()                             */
     835             : /************************************************************************/
     836             : 
     837          14 : bool GMLASWriter::CollectLayers()
     838             : {
     839          14 :     OGRFeatureDefn *poFDefn = m_poLayersMDLayer->GetLayerDefn();
     840          14 :     const char *const apszFields[] = {szLAYER_NAME, szLAYER_XPATH,
     841             :                                       szLAYER_CATEGORY, szLAYER_PKID_NAME,
     842             :                                       szLAYER_PARENT_PKID_NAME};
     843          79 :     for (size_t i = 0; i < CPL_ARRAYSIZE(apszFields); ++i)
     844             :     {
     845          66 :         if (poFDefn->GetFieldIndex(apszFields[i]) < 0)
     846             :         {
     847           2 :             CPLError(CE_Failure, CPLE_AppDefined,
     848           1 :                      "Cannot find field %s in %s layer", apszFields[i],
     849           1 :                      m_poLayersMDLayer->GetName());
     850           1 :             return false;
     851             :         }
     852             :     }
     853             : 
     854          13 :     m_poLayersMDLayer->SetAttributeFilter(nullptr);
     855          13 :     m_poLayersMDLayer->ResetReading();
     856         130 :     for (auto &&poFeature : *m_poLayersMDLayer)
     857             :     {
     858         117 :         LayerDescription desc;
     859         117 :         desc.osName = poFeature->GetFieldAsString(szLAYER_NAME);
     860         117 :         desc.osXPath = poFeature->GetFieldAsString(szLAYER_XPATH);
     861         117 :         desc.osPKIDName = poFeature->GetFieldAsString(szLAYER_PKID_NAME);
     862             :         desc.osParentPKIDName =
     863         117 :             poFeature->GetFieldAsString(szLAYER_PARENT_PKID_NAME);
     864         117 :         desc.bIsTopLevel = EQUAL(poFeature->GetFieldAsString(szLAYER_CATEGORY),
     865             :                                  szTOP_LEVEL_ELEMENT);
     866         117 :         desc.bIsSelected = desc.bIsTopLevel;
     867         117 :         desc.bIsJunction = EQUAL(poFeature->GetFieldAsString(szLAYER_CATEGORY),
     868             :                                  szJUNCTION_TABLE);
     869             : 
     870         117 :         OGRLayer *poLyr = GetLayerByName(desc.osName);
     871         117 :         if (poLyr)
     872             :         {
     873         117 :             if (!desc.osPKIDName.empty())
     874         101 :                 desc.oMapFieldNameToOGRIdx[desc.osPKIDName] =
     875         101 :                     poLyr->GetLayerDefn()->GetFieldIndex(desc.osPKIDName);
     876         117 :             if (!desc.osParentPKIDName.empty())
     877          64 :                 desc.oMapFieldNameToOGRIdx[desc.osParentPKIDName] =
     878          64 :                     poLyr->GetLayerDefn()->GetFieldIndex(desc.osParentPKIDName);
     879             :         }
     880             : 
     881         117 :         m_aoLayerDesc.push_back(desc);
     882         117 :         if (m_oMapLayerNameToIdx.find(desc.osName) !=
     883         234 :             m_oMapLayerNameToIdx.end())
     884             :         {
     885           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     886             :                      "Several layers with same %s = %s", szLAYER_NAME,
     887             :                      desc.osName.c_str());
     888           0 :             return false;
     889             :         }
     890         218 :         if (!desc.bIsJunction &&
     891         218 :             m_oMapXPathToIdx.find(desc.osXPath) != m_oMapXPathToIdx.end())
     892             :         {
     893           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     894             :                      "Several layers with same %s = %s", szLAYER_XPATH,
     895             :                      desc.osXPath.c_str());
     896           0 :             return false;
     897             :         }
     898         117 :         const int nIdx = static_cast<int>(m_aoLayerDesc.size() - 1);
     899         117 :         m_oMapLayerNameToIdx[desc.osName] = nIdx;
     900         117 :         if (!desc.bIsJunction)
     901         101 :             m_oMapXPathToIdx[desc.osXPath] = nIdx;
     902             :     }
     903          13 :     m_poLayersMDLayer->ResetReading();
     904             : 
     905          13 :     return true;
     906             : }
     907             : 
     908             : /************************************************************************/
     909             : /*                          CollectFields()                             */
     910             : /************************************************************************/
     911             : 
     912          13 : bool GMLASWriter::CollectFields()
     913             : {
     914          13 :     OGRFeatureDefn *poFDefn = m_poFieldsMDLayer->GetLayerDefn();
     915          13 :     const char *const apszFields[] = {
     916             :         szLAYER_NAME,          szFIELD_INDEX,
     917             :         szFIELD_NAME,          szFIELD_TYPE,
     918             :         szFIELD_XPATH,         szFIELD_CATEGORY,
     919             :         szFIELD_RELATED_LAYER, szFIELD_JUNCTION_LAYER,
     920             :         szFIELD_IS_LIST,       szFIELD_MIN_OCCURS,
     921             :         szFIELD_MAX_OCCURS,    szFIELD_REPETITION_ON_SEQUENCE,
     922             :         szFIELD_DEFAULT_VALUE};
     923         182 :     for (size_t i = 0; i < CPL_ARRAYSIZE(apszFields); ++i)
     924             :     {
     925         169 :         if (poFDefn->GetFieldIndex(apszFields[i]) < 0)
     926             :         {
     927           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     928           0 :                      "Cannot find field %s in %s layer", apszFields[i],
     929           0 :                      m_poFieldsMDLayer->GetName());
     930           0 :             return false;
     931             :         }
     932             :     }
     933             : 
     934          13 :     m_poFieldsMDLayer->SetAttributeFilter(
     935          26 :         (CPLString(szFIELD_CATEGORY) + " != '" + szSWE_FIELD + "'").c_str());
     936          13 :     m_poFieldsMDLayer->ResetReading();
     937         677 :     for (auto &&poFeature : m_poFieldsMDLayer)
     938             :     {
     939         664 :         GMLASField oField;
     940             : 
     941         664 :         oField.SetName(poFeature->GetFieldAsString(szFIELD_NAME));
     942             : 
     943         664 :         CPLString osLayerName(poFeature->GetFieldAsString(szLAYER_NAME));
     944         664 :         const auto &oIterToIdx = m_oMapLayerNameToIdx.find(osLayerName);
     945         664 :         if (oIterToIdx == m_oMapLayerNameToIdx.end())
     946             :         {
     947             :             // Shouldn't happen for well behaved metadata
     948           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     949             :                      "Cannot find in %s layer %s, "
     950             :                      "referenced in %s by field %s",
     951             :                      szOGR_LAYERS_METADATA, osLayerName.c_str(),
     952           0 :                      szOGR_FIELDS_METADATA, oField.GetName().c_str());
     953           0 :             continue;
     954             :         }
     955         664 :         if (m_aoLayerDesc[oIterToIdx->second].bIsJunction)
     956             :         {
     957           0 :             continue;
     958             :         }
     959             : 
     960         664 :         CPLString osXPath(poFeature->GetFieldAsString(szFIELD_XPATH));
     961         664 :         oField.SetXPath(osXPath);
     962             : 
     963         664 :         CPLString osType(poFeature->GetFieldAsString(szFIELD_TYPE));
     964         664 :         if (!osType.empty())
     965             :         {
     966         584 :             if (osType == szFAKEXS_JSON_DICT)
     967          20 :                 oField.SetType(GMLAS_FT_STRING, osType);
     968         564 :             else if (osType == szFAKEXS_GEOMETRY)
     969             :             {
     970         135 :                 oField.SetType(GMLAS_FT_GEOMETRY, osType);
     971             :                 // Hack for geometry field that have a xpath like
     972             :                 // foo/bar/gml:Point,foo/bar/gml:LineString,...
     973         135 :                 size_t nPos = osXPath.find("/gml:Point,");
     974         135 :                 if (nPos != std::string::npos)
     975           0 :                     osXPath.resize(nPos);
     976         135 :                 oField.SetXPath(osXPath);
     977             :             }
     978             :             else
     979         429 :                 oField.SetType(GMLASField::GetTypeFromString(osType), osType);
     980             :         }
     981             : 
     982         664 :         CPLString osCategory(poFeature->GetFieldAsString(szFIELD_CATEGORY));
     983         664 :         if (osCategory == szREGULAR)
     984             :         {
     985         568 :             oField.SetCategory(GMLASField::REGULAR);
     986             :         }
     987          96 :         else if (osCategory == szPATH_TO_CHILD_ELEMENT_NO_LINK)
     988             :         {
     989          56 :             oField.SetCategory(GMLASField::PATH_TO_CHILD_ELEMENT_NO_LINK);
     990             :         }
     991          40 :         else if (osCategory == szPATH_TO_CHILD_ELEMENT_WITH_LINK)
     992             :         {
     993          16 :             oField.SetCategory(GMLASField::PATH_TO_CHILD_ELEMENT_WITH_LINK);
     994             :         }
     995          24 :         else if (osCategory == szPATH_TO_CHILD_ELEMENT_WITH_JUNCTION_TABLE)
     996             :         {
     997          16 :             oField.SetCategory(
     998             :                 GMLASField::PATH_TO_CHILD_ELEMENT_WITH_JUNCTION_TABLE);
     999             : 
    1000             :             CPLString osJunctionLayer(
    1001          16 :                 poFeature->GetFieldAsString(szFIELD_JUNCTION_LAYER));
    1002          16 :             if (osJunctionLayer.empty())
    1003             :             {
    1004             :                 // Shouldn't happen for well behaved metadata
    1005           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1006             :                          "Missing value for %s for field (%s,%s)",
    1007             :                          szFIELD_JUNCTION_LAYER, osLayerName.c_str(),
    1008           0 :                          oField.GetName().c_str());
    1009           0 :                 continue;
    1010             :             }
    1011          16 :             oField.SetJunctionLayer(osJunctionLayer);
    1012             :         }
    1013           8 :         else if (osCategory == szGROUP)
    1014             :         {
    1015           8 :             oField.SetCategory(GMLASField::GROUP);
    1016             :         }
    1017             :         else
    1018             :         {
    1019             :             // Shouldn't happen for well behaved metadata
    1020           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    1021             :                      "Unknown category = %s for field (%s,%s)",
    1022             :                      osCategory.c_str(), osLayerName.c_str(),
    1023           0 :                      oField.GetName().c_str());
    1024           0 :             continue;
    1025             :         }
    1026             : 
    1027             :         CPLString osRelatedLayer(
    1028         664 :             poFeature->GetFieldAsString(szFIELD_RELATED_LAYER));
    1029         760 :         if (!osRelatedLayer.empty() &&
    1030          96 :             m_oMapLayerNameToIdx.find(osRelatedLayer) !=
    1031         760 :                 m_oMapLayerNameToIdx.end())
    1032             :         {
    1033          96 :             oField.SetRelatedClassXPath(
    1034          96 :                 m_aoLayerDesc[m_oMapLayerNameToIdx[osRelatedLayer]].osXPath);
    1035             :         }
    1036             : 
    1037         664 :         oField.SetList(
    1038         664 :             CPL_TO_BOOL(poFeature->GetFieldAsInteger(szFIELD_IS_LIST)));
    1039             : 
    1040         664 :         oField.SetMinOccurs(poFeature->GetFieldAsInteger(szFIELD_MIN_OCCURS));
    1041         664 :         oField.SetMaxOccurs(poFeature->GetFieldAsInteger(szFIELD_MAX_OCCURS));
    1042         664 :         oField.SetRepetitionOnSequence(CPL_TO_BOOL(
    1043             :             poFeature->GetFieldAsInteger(szFIELD_REPETITION_ON_SEQUENCE)));
    1044         664 :         oField.SetDefaultValue(
    1045             :             poFeature->GetFieldAsString(szFIELD_DEFAULT_VALUE));
    1046             : 
    1047         664 :         const int nIdx = poFeature->GetFieldAsInteger(szFIELD_INDEX);
    1048             : 
    1049         664 :         const int nLayerIdx = m_oMapLayerNameToIdx[osLayerName];
    1050         664 :         LayerDescription &oLayerDesc = m_aoLayerDesc[nLayerIdx];
    1051         664 :         if (oLayerDesc.oMapIdxToField.find(nIdx) !=
    1052        1328 :             oLayerDesc.oMapIdxToField.end())
    1053             :         {
    1054             :             // Shouldn't happen for well behaved metadata
    1055           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1056             :                      "Field %s of %s has the same index as field %s",
    1057           0 :                      oField.GetName().c_str(), osLayerName.c_str(),
    1058           0 :                      oLayerDesc.oMapIdxToField[nIdx].GetName().c_str());
    1059           0 :             return false;
    1060             :         }
    1061         664 :         oLayerDesc.oMapIdxToField[nIdx] = oField;
    1062             : 
    1063         664 :         if (!oField.GetXPath().empty())
    1064             :         {
    1065         656 :             if (oLayerDesc.oMapFieldXPathToIdx.find(oField.GetXPath()) !=
    1066        1312 :                 oLayerDesc.oMapFieldXPathToIdx.end())
    1067             :             {
    1068             :                 // Shouldn't happen for well behaved metadata
    1069           0 :                 CPLError(
    1070             :                     CE_Failure, CPLE_AppDefined,
    1071             :                     "Field %s of %s has the same XPath as field %s",
    1072           0 :                     oField.GetName().c_str(), osLayerName.c_str(),
    1073             :                     oLayerDesc
    1074             :                         .oMapIdxToField
    1075           0 :                             [oLayerDesc.oMapFieldXPathToIdx[oField.GetXPath()]]
    1076           0 :                         .GetName()
    1077             :                         .c_str());
    1078           0 :                 return false;
    1079             :             }
    1080         656 :             oLayerDesc.oMapFieldXPathToIdx[oField.GetXPath()] = nIdx;
    1081             :         }
    1082             : 
    1083         664 :         OGRLayer *poLyr = GetLayerByName(osLayerName);
    1084         664 :         if (poLyr)
    1085             :         {
    1086         664 :             oLayerDesc.oMapFieldNameToOGRIdx[oField.GetName()] =
    1087         664 :                 poLyr->GetLayerDefn()->GetFieldIndex(oField.GetName());
    1088         664 :             if (oField.GetType() == GMLAS_FT_GEOMETRY)
    1089             :             {
    1090         135 :                 oLayerDesc.oMapFieldNameToOGRIdx[oField.GetName() + "_xml"] =
    1091         270 :                     poLyr->GetLayerDefn()->GetFieldIndex(
    1092         270 :                         (oField.GetName() + "_xml").c_str());
    1093             :             }
    1094             :         }
    1095             :     }
    1096          13 :     m_poFieldsMDLayer->ResetReading();
    1097             : 
    1098          13 :     return true;
    1099             : }
    1100             : 
    1101             : /************************************************************************/
    1102             : /*                      CollectRelationships()                          */
    1103             : /************************************************************************/
    1104             : 
    1105          13 : bool GMLASWriter::CollectRelationships()
    1106             : {
    1107          13 :     OGRFeatureDefn *poFDefn = m_poLayerRelationshipsLayer->GetLayerDefn();
    1108          13 :     const char *const apszFields[] = {szPARENT_LAYER, szCHILD_LAYER,
    1109             :                                       szPARENT_ELEMENT_NAME};
    1110          52 :     for (size_t i = 0; i < CPL_ARRAYSIZE(apszFields); ++i)
    1111             :     {
    1112          39 :         if (poFDefn->GetFieldIndex(apszFields[i]) < 0)
    1113             :         {
    1114           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1115           0 :                      "Cannot find field %s in %s layer", apszFields[i],
    1116           0 :                      m_poLayerRelationshipsLayer->GetName());
    1117           0 :             return false;
    1118             :         }
    1119             :     }
    1120             : 
    1121          13 :     m_poLayerRelationshipsLayer->SetAttributeFilter(nullptr);
    1122          13 :     m_poLayerRelationshipsLayer->ResetReading();
    1123             : 
    1124         109 :     for (auto &&poFeature : m_poLayerRelationshipsLayer)
    1125             :     {
    1126             :         const CPLString osParentLayer(
    1127          96 :             poFeature->GetFieldAsString(szPARENT_LAYER));
    1128          96 :         if (m_oMapLayerNameToIdx.find(osParentLayer) ==
    1129         192 :             m_oMapLayerNameToIdx.end())
    1130             :         {
    1131             :             // Shouldn't happen for well behaved metadata
    1132           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    1133             :                      "Cannot find in %s layer %s, referenced in %s",
    1134             :                      szOGR_LAYERS_METADATA, osParentLayer.c_str(),
    1135             :                      szOGR_LAYER_RELATIONSHIPS);
    1136           0 :             continue;
    1137             :         }
    1138             : 
    1139             :         const CPLString osChildLayer(
    1140          96 :             poFeature->GetFieldAsString(szCHILD_LAYER));
    1141          96 :         if (m_oMapLayerNameToIdx.find(osChildLayer) ==
    1142         192 :             m_oMapLayerNameToIdx.end())
    1143             :         {
    1144             :             // Shouldn't happen for well behaved metadata
    1145           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    1146             :                      "Cannot find in %s layer %s, referenced in %s",
    1147             :                      szOGR_LAYERS_METADATA, osChildLayer.c_str(),
    1148             :                      szOGR_LAYER_RELATIONSHIPS);
    1149           0 :             continue;
    1150             :         }
    1151             : 
    1152          96 :         const int nChildLayerIdx = m_oMapLayerNameToIdx[osChildLayer];
    1153          96 :         if (m_aoLayerDesc[nChildLayerIdx].bIsTopLevel)
    1154             :         {
    1155             :             const CPLString osReferencingField(
    1156          24 :                 poFeature->GetFieldAsString(szPARENT_ELEMENT_NAME));
    1157             : 
    1158          48 :             m_aoLayerDesc[nChildLayerIdx].aoReferencingLayers.push_back(
    1159          48 :                 PairLayerNameColName(osParentLayer, osReferencingField));
    1160             :         }
    1161             :     }
    1162          13 :     m_poLayerRelationshipsLayer->ResetReading();
    1163             : 
    1164          13 :     return true;
    1165             : }
    1166             : 
    1167             : /************************************************************************/
    1168             : /*                      ComputeTopLevelFIDs()                           */
    1169             : /*                                                                      */
    1170             : /* Find which features of top-level layers are referenced by other      */
    1171             : /* features, in which case we don't need to emit them in their layer    */
    1172             : /************************************************************************/
    1173             : 
    1174          10 : void GMLASWriter::ComputeTopLevelFIDs()
    1175             : {
    1176         124 :     for (size_t i = 0; i < m_aoLayerDesc.size(); ++i)
    1177             :     {
    1178         114 :         LayerDescription &oDesc = m_aoLayerDesc[i];
    1179         114 :         OGRLayer *poLayer = GetLayerByName(oDesc.osName);
    1180         144 :         if (oDesc.bIsTopLevel && poLayer != nullptr &&
    1181          30 :             !oDesc.aoReferencingLayers.empty())
    1182             :         {
    1183          44 :             for (size_t j = 0; j < oDesc.aoReferencingLayers.size(); ++j)
    1184             :             {
    1185          48 :                 CPLString osSQL;
    1186          48 :                 CPLString osFID("FID");
    1187          48 :                 if (poLayer->GetFIDColumn() &&
    1188          24 :                     !EQUAL(poLayer->GetFIDColumn(), ""))
    1189             :                 {
    1190          24 :                     osFID = poLayer->GetFIDColumn();
    1191             :                 }
    1192             : 
    1193             :                 // Determine if the referencing field points to a junction
    1194             :                 // table
    1195             :                 const auto oIter = m_oMapLayerNameToIdx.find(
    1196          24 :                     oDesc.aoReferencingLayers[j].first);
    1197          24 :                 if (oIter != m_oMapLayerNameToIdx.end())
    1198             :                 {
    1199             :                     const LayerDescription &oReferencingLayerDesc =
    1200          24 :                         m_aoLayerDesc[oIter->second];
    1201        1212 :                     for (const auto &oIterField :
    1202        2448 :                          oReferencingLayerDesc.oMapIdxToField)
    1203             :                     {
    1204        1236 :                         const GMLASField &oField = oIterField.second;
    1205        1236 :                         if (oField.GetName() ==
    1206        1236 :                             oDesc.aoReferencingLayers[j].second)
    1207             :                         {
    1208          24 :                             if (oField.GetCategory() ==
    1209             :                                 GMLASField::
    1210             :                                     PATH_TO_CHILD_ELEMENT_WITH_JUNCTION_TABLE)
    1211             :                             {
    1212             :                                 osSQL.Printf(
    1213             :                                     "SELECT s.\"%s\" AS ogr_main_fid  "
    1214             :                                     "FROM \"%s\" s "
    1215             :                                     "JOIN \"%s\" j ON j.%s = s.\"%s\"",
    1216             :                                     osFID.c_str(), oDesc.osName.c_str(),
    1217           8 :                                     oField.GetJunctionLayer().c_str(),
    1218          16 :                                     szCHILD_PKID, oDesc.osPKIDName.c_str());
    1219             :                             }
    1220          24 :                             break;
    1221             :                         }
    1222             :                     }
    1223             :                 }
    1224             : 
    1225             :                 // Otherwise we can use the referencing (layer_name,
    1226             :                 // field_name) tuple directly.
    1227          24 :                 if (osSQL.empty())
    1228             :                 {
    1229             :                     osSQL.Printf("SELECT s.\"%s\" AS ogr_main_fid "
    1230             :                                  "FROM \"%s\" s "
    1231             :                                  "JOIN \"%s\" m ON m.\"%s\" = s.\"%s\"",
    1232             :                                  osFID.c_str(), oDesc.osName.c_str(),
    1233          16 :                                  oDesc.aoReferencingLayers[j].first.c_str(),
    1234          16 :                                  oDesc.aoReferencingLayers[j].second.c_str(),
    1235          48 :                                  oDesc.osPKIDName.c_str());
    1236             :                 }
    1237             : 
    1238          24 :                 CPLDebug("GMLAS", "Executing %s", osSQL.c_str());
    1239             :                 OGRLayer *poSQLLyr =
    1240          24 :                     m_poSrcDS->ExecuteSQL(osSQL, nullptr, nullptr);
    1241          24 :                 if (poSQLLyr)
    1242             :                 {
    1243          68 :                     for (auto &&poFeature : *poSQLLyr)
    1244             :                     {
    1245          44 :                         const GIntBig nFID = poFeature->GetFieldAsInteger64(0);
    1246          44 :                         oDesc.aoSetReferencedFIDs.insert(nFID);
    1247             :                     }
    1248          24 :                     m_poSrcDS->ReleaseResultSet(poSQLLyr);
    1249             :                 }
    1250             :             }
    1251             :         }
    1252             :     }
    1253          10 : }
    1254             : 
    1255             : /************************************************************************/
    1256             : /*                           SplitXPath()                               */
    1257             : /************************************************************************/
    1258             : 
    1259             : // Decompose a XPath ns1:foo1/@ns2:foo2/... in its components
    1260             : // [ (ns1,foo1), (ns2,@foo2), ... ]
    1261         600 : static XPathComponents SplitXPathInternal(const CPLString &osXPath)
    1262             : {
    1263         600 :     char **papszTokens = CSLTokenizeString2(osXPath, "/", 0);
    1264         600 :     XPathComponents aoComponents;
    1265        1946 :     for (int i = 0; papszTokens[i] != nullptr; ++i)
    1266             :     {
    1267        1346 :         const bool bAttr = (papszTokens[i][0] == '@');
    1268             :         char **papszNSElt =
    1269        1346 :             CSLTokenizeString2(papszTokens[i] + (bAttr ? 1 : 0), ":", 0);
    1270        1346 :         if (papszNSElt[0] != nullptr && papszNSElt[1] != nullptr &&
    1271        1238 :             papszNSElt[2] == nullptr)
    1272             :         {
    1273        1238 :             CPLString osVal(papszNSElt[1]);
    1274        1238 :             size_t nPos = osVal.find(szEXTRA_SUFFIX);
    1275        1238 :             if (nPos != std::string::npos)
    1276           4 :                 osVal.resize(nPos);
    1277        1238 :             aoComponents.push_back(PairNSElement(
    1278        2476 :                 papszNSElt[0], (bAttr ? CPLString("@") : CPLString()) + osVal));
    1279             :         }
    1280         108 :         else if (papszNSElt[0] != nullptr && papszNSElt[1] == nullptr)
    1281             :         {
    1282         108 :             CPLString osVal(papszNSElt[0]);
    1283         108 :             size_t nPos = osVal.find(szEXTRA_SUFFIX);
    1284         108 :             if (nPos != std::string::npos)
    1285           0 :                 osVal.resize(nPos);
    1286         108 :             aoComponents.push_back(PairNSElement(
    1287         216 :                 "", (bAttr ? CPLString("@") : CPLString()) + osVal));
    1288             :         }
    1289        1346 :         CSLDestroy(papszNSElt);
    1290             :     }
    1291         600 :     CSLDestroy(papszTokens);
    1292         600 :     return aoComponents;
    1293             : }
    1294             : 
    1295         988 : const XPathComponents &GMLASWriter::SplitXPath(const CPLString &osXPath)
    1296             : {
    1297         988 :     const auto oIter = m_oMapXPathToComponents.find(osXPath);
    1298         988 :     if (oIter != m_oMapXPathToComponents.end())
    1299         388 :         return oIter->second;
    1300             : 
    1301         600 :     m_oMapXPathToComponents[osXPath] = SplitXPathInternal(osXPath);
    1302         600 :     return m_oMapXPathToComponents[osXPath];
    1303             : }
    1304             : 
    1305             : /************************************************************************/
    1306             : /*                            IsAttr()                                  */
    1307             : /************************************************************************/
    1308             : 
    1309        3138 : static bool IsAttr(const PairNSElement &pair)
    1310             : {
    1311        3138 :     return !pair.second.empty() && pair.second[0] == '@';
    1312             : }
    1313             : 
    1314             : /************************************************************************/
    1315             : /*                           MakeXPath()                                */
    1316             : /************************************************************************/
    1317             : 
    1318        1462 : static CPLString MakeXPath(const PairNSElement &pair)
    1319             : {
    1320        1462 :     if (pair.first.empty())
    1321             :     {
    1322          64 :         if (IsAttr(pair))
    1323         128 :             return pair.second.substr(1);
    1324             :         else
    1325           0 :             return pair.second;
    1326             :     }
    1327        1398 :     else if (IsAttr(pair))
    1328          36 :         return pair.first + ":" + pair.second.substr(1);
    1329             :     else
    1330        2760 :         return pair.first + ":" + pair.second;
    1331             : }
    1332             : 
    1333             : /************************************************************************/
    1334             : /*                           WriteLayer()                               */
    1335             : /************************************************************************/
    1336             : 
    1337          30 : bool GMLASWriter::WriteLayer(bool bWFS2FeatureCollection,
    1338             :                              const LayerDescription &oDesc,
    1339             :                              GIntBig &nFeaturesWritten,
    1340             :                              GIntBig nTotalTopLevelFeatures,
    1341             :                              GDALProgressFunc pfnProgress, void *pProgressData)
    1342             : {
    1343          30 :     OGRLayer *poSrcLayer = GetLayerByName(oDesc.osName);
    1344          30 :     if (poSrcLayer == nullptr)
    1345           0 :         return true;
    1346             : 
    1347          30 :     poSrcLayer->ResetReading();
    1348          30 :     IncIndent();
    1349          30 :     std::set<CPLString> oSetLayersInIteration;
    1350          30 :     oSetLayersInIteration.insert(oDesc.osName);
    1351          30 :     bool bRet = true;
    1352          63 :     for (auto &&poFeature : *poSrcLayer)
    1353             :     {
    1354          33 :         if (oDesc.aoSetReferencedFIDs.find(poFeature->GetFID()) ==
    1355          66 :             oDesc.aoSetReferencedFIDs.end())
    1356             :         {
    1357          10 :             PrintIndent(m_fpXML.get());
    1358          10 :             if (bWFS2FeatureCollection)
    1359             :             {
    1360           3 :                 PrintLine(m_fpXML.get(), "<%s:%s>", szWFS_PREFIX, szMEMBER);
    1361             :             }
    1362             :             else
    1363             :             {
    1364           7 :                 PrintLine(m_fpXML.get(), "<%s:%s>",
    1365             :                           m_osTargetNameSpacePrefix.c_str(), szFEATURE_MEMBER);
    1366             :             }
    1367             : 
    1368          10 :             bRet = WriteFeature(poFeature.get(), oDesc, oSetLayersInIteration,
    1369          20 :                                 XPathComponents(), XPathComponents(), 0);
    1370             : 
    1371          10 :             PrintIndent(m_fpXML.get());
    1372          10 :             if (bWFS2FeatureCollection)
    1373             :             {
    1374           3 :                 PrintLine(m_fpXML.get(), "</%s:%s>", szWFS_PREFIX, szMEMBER);
    1375             :             }
    1376             :             else
    1377             :             {
    1378           7 :                 PrintLine(m_fpXML.get(), "</%s:%s>",
    1379             :                           m_osTargetNameSpacePrefix.c_str(), szFEATURE_MEMBER);
    1380             :             }
    1381             : 
    1382          10 :             if (bRet)
    1383             :             {
    1384          10 :                 nFeaturesWritten++;
    1385          10 :                 const double dfPct = static_cast<double>(nFeaturesWritten) /
    1386             :                                      nTotalTopLevelFeatures;
    1387          10 :                 if (pfnProgress && !pfnProgress(dfPct, "", pProgressData))
    1388             :                 {
    1389           0 :                     bRet = false;
    1390             :                 }
    1391             :             }
    1392          10 :             if (!bRet)
    1393           0 :                 break;
    1394             :         }
    1395             :     }
    1396          30 :     poSrcLayer->ResetReading();
    1397          30 :     DecIndent();
    1398             : 
    1399          30 :     return bRet;
    1400             : }
    1401             : 
    1402             : /************************************************************************/
    1403             : /*                        FindCommonPrefixLength()                      */
    1404             : /************************************************************************/
    1405             : 
    1406        1070 : static size_t FindCommonPrefixLength(const XPathComponents &a,
    1407             :                                      const XPathComponents &b)
    1408             : {
    1409        1070 :     size_t i = 0;
    1410        2588 :     for (; i < a.size() && i < b.size(); ++i)
    1411             :     {
    1412        1980 :         if (a[i].first != b[i].first || a[i].second != b[i].second)
    1413         462 :             break;
    1414             :     }
    1415        1070 :     return i;
    1416             : }
    1417             : 
    1418             : /************************************************************************/
    1419             : /*                        WriteClosingTags()                            */
    1420             : /************************************************************************/
    1421             : 
    1422         906 : void GMLASWriter::WriteClosingTags(size_t nCommonLength,
    1423             :                                    const XPathComponents &aoCurComponents,
    1424             :                                    const XPathComponents &aoNewComponents,
    1425             :                                    bool bCurIsRegularField,
    1426             :                                    bool bNewIsRegularField)
    1427             : {
    1428         906 :     if (nCommonLength < aoCurComponents.size())
    1429             :     {
    1430         608 :         bool bFieldIsAnotherAttrOfCurElt = false;
    1431         608 :         size_t i = aoCurComponents.size() - 1;
    1432             : 
    1433         608 :         bool bMustIndent = !bCurIsRegularField;
    1434             : 
    1435         608 :         if (IsAttr(aoCurComponents.back()))
    1436             :         {
    1437         160 :             if (nCommonLength + 1 == aoCurComponents.size() &&
    1438         160 :                 nCommonLength + 1 == aoNewComponents.size() &&
    1439          46 :                 IsAttr(aoNewComponents.back()))
    1440             :             {
    1441          24 :                 bFieldIsAnotherAttrOfCurElt = true;
    1442             :             }
    1443             :             else
    1444             :             {
    1445             :                 /*
    1446             :                 a/@b  cur
    1447             :                 a     new
    1448             :                 ==> <a b="">foo</a>
    1449             : 
    1450             :                 a/@b  cur
    1451             :                 a/c   new
    1452             :                 ==> <a b="">
    1453             :                         <c/>
    1454             :                      </a>
    1455             : 
    1456             :                 a/@b  cur
    1457             :                 c     new
    1458             :                 ==> <a b=""/>
    1459             :                     <c/>
    1460             : 
    1461             :                 */
    1462          66 :                 if ((nCommonLength == 0 ||
    1463         132 :                      nCommonLength + 2 <= aoCurComponents.size()) &&
    1464             :                     i >= 2)
    1465             :                 {
    1466          20 :                     PrintLine(m_fpXML.get(), " />");
    1467          20 :                     i -= 2;
    1468          20 :                     DecIndent();
    1469          20 :                     bMustIndent = true;
    1470             :                 }
    1471             :                 else
    1472             :                 {
    1473          46 :                     VSIFPrintfL(m_fpXML.get(), ">");
    1474          46 :                     CPLAssert(i > 0);
    1475          46 :                     i--;
    1476             :                     // Print a new line except in the <elt attr="foo">bar</elt>
    1477             :                     // situation
    1478         116 :                     if (!(nCommonLength + 1 == aoCurComponents.size() &&
    1479          46 :                           nCommonLength == aoNewComponents.size() &&
    1480          24 :                           bNewIsRegularField))
    1481             :                     {
    1482          30 :                         PrintLine(m_fpXML.get(), "%s", "");
    1483             :                     }
    1484             :                 }
    1485             :             }
    1486             :         }
    1487             : 
    1488         608 :         if (!bFieldIsAnotherAttrOfCurElt)
    1489             :         {
    1490        1178 :             for (; i >= nCommonLength; --i)
    1491             :             {
    1492         624 :                 if (bMustIndent)
    1493             :                 {
    1494         150 :                     PrintIndent(m_fpXML.get());
    1495             :                 }
    1496         624 :                 bMustIndent = true;
    1497         624 :                 PrintLine(m_fpXML.get(), "</%s>",
    1498        1248 :                           MakeXPath(aoCurComponents[i]).c_str());
    1499         624 :                 DecIndent();
    1500         624 :                 if (i == 0)
    1501          30 :                     break;
    1502             :             }
    1503             :         }
    1504             :     }
    1505         906 : }
    1506             : 
    1507             : /************************************************************************/
    1508             : /*                      WriteClosingAndStartingTags()                   */
    1509             : /************************************************************************/
    1510             : 
    1511         120 : void GMLASWriter::WriteClosingAndStartingTags(
    1512             :     const XPathComponents &aoCurComponents,
    1513             :     const XPathComponents &aoNewComponents, bool bCurIsRegularField)
    1514             : {
    1515             : 
    1516             :     const size_t nCommonLength =
    1517         120 :         FindCommonPrefixLength(aoCurComponents, aoNewComponents);
    1518             : 
    1519         120 :     WriteClosingTags(nCommonLength, aoCurComponents, aoNewComponents,
    1520             :                      bCurIsRegularField, false);
    1521         180 :     for (size_t i = nCommonLength; i < aoNewComponents.size(); ++i)
    1522             :     {
    1523          60 :         IncIndent();
    1524          60 :         PrintIndent(m_fpXML.get());
    1525          60 :         PrintLine(m_fpXML.get(), "<%s>", MakeXPath(aoNewComponents[i]).c_str());
    1526             :     }
    1527         120 : }
    1528             : 
    1529             : /************************************************************************/
    1530             : /*                          WriteFeature()                              */
    1531             : /************************************************************************/
    1532             : 
    1533         222 : bool GMLASWriter::WriteFeature(OGRFeature *poFeature,
    1534             :                                const LayerDescription &oLayerDesc,
    1535             :                                const std::set<CPLString> &oSetLayersInIteration,
    1536             :                                const XPathComponents &aoInitialComponents,
    1537             :                                const XPathComponents &aoPrefixComponents,
    1538             :                                int nRecLevel)
    1539             : {
    1540         222 :     if (nRecLevel == 100)
    1541             :     {
    1542           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1543             :                  "WriteFeature() called with 100 levels of recursion");
    1544           0 :         return false;
    1545             :     }
    1546             : 
    1547         444 :     XPathComponents aoCurComponents(aoInitialComponents);
    1548         444 :     XPathComponents aoLayerComponents;
    1549         222 :     bool bAtLeastOneFieldWritten = false;
    1550         222 :     bool bCurIsRegularField = false;
    1551        1086 :     for (const auto &oIter : oLayerDesc.oMapIdxToField)
    1552             :     {
    1553         864 :         const GMLASField &oField = oIter.second;
    1554         864 :         const GMLASField::Category eCategory(oField.GetCategory());
    1555         864 :         if (eCategory == GMLASField::REGULAR)
    1556             :         {
    1557         728 :             WriteFieldRegular(poFeature, oField, oLayerDesc,
    1558             :                               /*aoLayerComponents, */
    1559             :                               aoCurComponents, aoPrefixComponents,
    1560             :                               /*oSetLayersInIteration,*/
    1561             :                               bAtLeastOneFieldWritten, bCurIsRegularField);
    1562             :         }
    1563         136 :         else if (eCategory == GMLASField::PATH_TO_CHILD_ELEMENT_NO_LINK ||
    1564             :                  eCategory == GMLASField::GROUP)
    1565             :         {
    1566          84 :             if (!WriteFieldNoLink(
    1567             :                     poFeature, oField, oLayerDesc, aoLayerComponents,
    1568             :                     aoCurComponents, aoPrefixComponents, oSetLayersInIteration,
    1569             :                     nRecLevel, bAtLeastOneFieldWritten, bCurIsRegularField))
    1570             :             {
    1571           0 :                 return false;
    1572             :             }
    1573             :         }
    1574          52 :         else if (eCategory == GMLASField::PATH_TO_CHILD_ELEMENT_WITH_LINK)
    1575             :         {
    1576          36 :             if (!WriteFieldWithLink(
    1577             :                     poFeature, oField, oLayerDesc, aoLayerComponents,
    1578             :                     aoCurComponents, aoPrefixComponents, oSetLayersInIteration,
    1579             :                     nRecLevel, bAtLeastOneFieldWritten, bCurIsRegularField))
    1580             :             {
    1581           0 :                 return false;
    1582             :             }
    1583             :         }
    1584          16 :         else if (eCategory ==
    1585             :                  GMLASField::PATH_TO_CHILD_ELEMENT_WITH_JUNCTION_TABLE)
    1586             :         {
    1587          16 :             if (!WriteFieldJunctionTable(
    1588             :                     poFeature, oField, oLayerDesc, aoLayerComponents,
    1589             :                     aoCurComponents, aoPrefixComponents, oSetLayersInIteration,
    1590             :                     nRecLevel, bAtLeastOneFieldWritten, bCurIsRegularField))
    1591             :             {
    1592           0 :                 return false;
    1593             :             }
    1594             :         }
    1595             :     }
    1596             : 
    1597         222 :     if (!bAtLeastOneFieldWritten && aoInitialComponents.empty() &&
    1598           0 :         !oLayerDesc.osXPath.empty())
    1599             :     {
    1600           0 :         aoLayerComponents = SplitXPath(oLayerDesc.osXPath);
    1601           0 :         const CPLString osLayerElt(MakeXPath(aoLayerComponents.back()));
    1602           0 :         PrintIndent(m_fpXML.get());
    1603           0 :         VSIFPrintfL(m_fpXML.get(), "%s", m_osIndentation.c_str());
    1604           0 :         PrintLine(m_fpXML.get(), "<%s />", osLayerElt.c_str());
    1605             :     }
    1606             :     else
    1607             :     {
    1608             :         const size_t nCommonLength =
    1609         222 :             FindCommonPrefixLength(aoCurComponents, aoInitialComponents);
    1610         222 :         WriteClosingTags(nCommonLength, aoCurComponents, aoInitialComponents,
    1611             :                          bCurIsRegularField, false);
    1612             :     }
    1613             : 
    1614         222 :     return true;
    1615             : }
    1616             : 
    1617             : /************************************************************************/
    1618             : /*                     PrintMultipleValuesSeparator()                   */
    1619             : /************************************************************************/
    1620             : 
    1621         100 : void GMLASWriter::PrintMultipleValuesSeparator(
    1622             :     const GMLASField &oField, const XPathComponents &aoFieldComponents)
    1623             : {
    1624         100 :     if (oField.IsList())
    1625             :     {
    1626          44 :         VSIFPrintfL(m_fpXML.get(), " ");
    1627             :     }
    1628             :     else
    1629             :     {
    1630          56 :         PrintLine(m_fpXML.get(), "</%s>",
    1631         112 :                   MakeXPath(aoFieldComponents.back()).c_str());
    1632          56 :         PrintIndent(m_fpXML.get());
    1633          56 :         VSIFPrintfL(m_fpXML.get(), "<%s>",
    1634         112 :                     MakeXPath(aoFieldComponents.back()).c_str());
    1635             :     }
    1636         100 : }
    1637             : 
    1638             : /************************************************************************/
    1639             : /*                         PrintXMLDouble()                             */
    1640             : /************************************************************************/
    1641             : 
    1642          40 : static void PrintXMLDouble(VSILFILE *fp, double dfVal)
    1643             : {
    1644          40 :     if (CPLIsInf(dfVal))
    1645             :     {
    1646           0 :         if (dfVal > 0)
    1647           0 :             VSIFPrintfL(fp, "INF");
    1648             :         else
    1649           0 :             VSIFPrintfL(fp, "-INF");
    1650             :     }
    1651          40 :     else if (CPLIsNan(dfVal))
    1652           0 :         VSIFPrintfL(fp, "NaN");
    1653             :     else
    1654          40 :         VSIFPrintfL(fp, "%.16g", dfVal);
    1655          40 : }
    1656             : 
    1657             : /************************************************************************/
    1658             : /*                 AreGeomsEqualAxisOrderInsensitive()                  */
    1659             : /************************************************************************/
    1660             : 
    1661          13 : static bool AreGeomsEqualAxisOrderInsensitive(const OGRGeometry *poGeomRef,
    1662             :                                               OGRGeometry *poGeomModifiable)
    1663             : {
    1664          13 :     if (poGeomRef->Equals(poGeomModifiable))
    1665          11 :         return true;
    1666           2 :     poGeomModifiable->swapXY();
    1667           2 :     return CPL_TO_BOOL(poGeomRef->Equals(poGeomModifiable));
    1668             : }
    1669             : 
    1670             : /************************************************************************/
    1671             : /*                             GetCoordSwap()                           */
    1672             : /************************************************************************/
    1673             : 
    1674           0 : bool GMLASWriter::GetCoordSwap(const OGRSpatialReference *poSRS)
    1675             : {
    1676           0 :     const auto oIter = m_oMapSRSToCoordSwap.find(poSRS);
    1677           0 :     if (oIter != m_oMapSRSToCoordSwap.end())
    1678           0 :         return oIter->second;
    1679             : 
    1680           0 :     bool bCoordSwap = false;
    1681           0 :     if (m_osSRSNameFormat != "SHORT")
    1682             :     {
    1683           0 :         const auto &map = poSRS->GetDataAxisToSRSAxisMapping();
    1684           0 :         if (map.size() >= 2 && map[0] == 2 && map[1] == 1)
    1685             :         {
    1686           0 :             bCoordSwap = true;
    1687             :         }
    1688             :     }
    1689           0 :     m_oMapSRSToCoordSwap[poSRS] = bCoordSwap;
    1690           0 :     return bCoordSwap;
    1691             : }
    1692             : 
    1693             : /************************************************************************/
    1694             : /*                     WriteFieldRegular()                              */
    1695             : /************************************************************************/
    1696             : 
    1697         728 : bool GMLASWriter::WriteFieldRegular(
    1698             :     OGRFeature *poFeature, const GMLASField &oField,
    1699             :     const LayerDescription &oLayerDesc,
    1700             :     /*XPathComponents& aoLayerComponents,*/
    1701             :     XPathComponents &aoCurComponents, const XPathComponents &aoPrefixComponents,
    1702             :     /*const std::set<CPLString>& oSetLayersInIteration,*/
    1703             :     bool &bAtLeastOneFieldWritten, bool &bCurIsRegularField)
    1704             : {
    1705         728 :     const bool bIsGeometryField = oField.GetTypeName() == szFAKEXS_GEOMETRY;
    1706             :     const int nFieldIdx =
    1707         818 :         bIsGeometryField ?
    1708             :                          // Some drivers may not store the geometry field name,
    1709             :             // so for a feature with a single geometry, use it
    1710          90 :             (poFeature->GetGeomFieldCount() == 1
    1711          90 :                  ? 0
    1712          90 :                  : poFeature->GetGeomFieldIndex(oField.GetName()))
    1713         638 :                          : oLayerDesc.GetOGRIdxFromFieldName(oField.GetName());
    1714        1456 :     XPathComponents aoFieldComponents = SplitXPath(oField.GetXPath());
    1715         728 :     aoFieldComponents.insert(aoFieldComponents.begin(),
    1716             :                              aoPrefixComponents.begin(),
    1717        1456 :                              aoPrefixComponents.end());
    1718             : 
    1719             :     // For extension/* case
    1720         728 :     if (!aoFieldComponents.empty() && aoFieldComponents.back().second == "*")
    1721             :     {
    1722           8 :         aoFieldComponents.resize(aoFieldComponents.size() - 1);
    1723             :     }
    1724             : 
    1725             :     const size_t nCommonLength =
    1726         728 :         FindCommonPrefixLength(aoCurComponents, aoFieldComponents);
    1727             : 
    1728             :     const bool bEmptyContent =
    1729        1456 :         nFieldIdx < 0 ||
    1730          90 :         ((bIsGeometryField && !poFeature->GetGeomFieldRef(nFieldIdx)) ||
    1731         716 :          (!bIsGeometryField && !poFeature->IsFieldSetAndNotNull(nFieldIdx)));
    1732             :     const bool bIsNull =
    1733         728 :         m_oConf.m_bUseNullState && (!bIsGeometryField && nFieldIdx >= 0 &&
    1734           0 :                                     poFeature->IsFieldNull(nFieldIdx));
    1735         728 :     bool bMustBeEmittedEvenIfEmpty = oField.GetMinOccurs() > 0 || bIsNull;
    1736         728 :     if (!m_oConf.m_bUseNullState && oField.GetMinOccurs() == 0 &&
    1737         268 :         bEmptyContent && nCommonLength + 1 == aoCurComponents.size() &&
    1738         116 :         IsAttr(aoCurComponents.back()) &&
    1739        1464 :         nCommonLength == aoFieldComponents.size() &&
    1740         732 :         oLayerDesc.oMapFieldXPathToIdx.find(oField.GetXPath() + "/" +
    1741         736 :                                             szAT_XSI_NIL) ==
    1742         732 :             oLayerDesc.oMapFieldXPathToIdx.end())
    1743             :     {
    1744             :         // This is quite tricky to determine if a <foo bar="baz"/> node is
    1745             :         // valid or if we must add a xsi:nil="true" to make it valid
    1746             :         // For now assume that a string can be empty
    1747           0 :         if (oField.GetType() != GMLAS_FT_STRING)
    1748           0 :             bMustBeEmittedEvenIfEmpty = true;
    1749             :     }
    1750             : 
    1751         728 :     if (bEmptyContent && !bMustBeEmittedEvenIfEmpty)
    1752         160 :         return true;
    1753             : 
    1754             :     // Do not emit optional attributes at default/fixed value
    1755         808 :     if (!aoFieldComponents.empty() && oField.GetMinOccurs() == 0 &&
    1756         240 :         IsAttr(aoFieldComponents.back()))
    1757             :     {
    1758          86 :         const CPLString &osDefaultVal(!oField.GetDefaultValue().empty()
    1759          86 :                                           ? oField.GetDefaultValue()
    1760          78 :                                           : oField.GetFixedValue());
    1761          86 :         if (!osDefaultVal.empty())
    1762             :         {
    1763           8 :             if (oField.GetType() == GMLAS_FT_BOOLEAN)
    1764             :             {
    1765           0 :                 const int nVal = poFeature->GetFieldAsInteger(nFieldIdx);
    1766           0 :                 if (osDefaultVal == "false" && nVal == 0)
    1767           0 :                     return true;
    1768           0 :                 if (osDefaultVal == "true" && nVal == 1)
    1769           0 :                     return true;
    1770             :             }
    1771           8 :             else if (osDefaultVal == poFeature->GetFieldAsString(nFieldIdx))
    1772             :             {
    1773           4 :                 return true;
    1774             :             }
    1775             :         }
    1776             :     }
    1777             : 
    1778         564 :     bAtLeastOneFieldWritten = true;
    1779             : 
    1780           4 :     if (bEmptyContent && nCommonLength + 1 == aoCurComponents.size() &&
    1781         568 :         IsAttr(aoCurComponents.back()) &&
    1782           0 :         nCommonLength == aoFieldComponents.size())
    1783             :     {
    1784             :         // Particular case for <a foo="bar" xsi:nil="true"/>
    1785           0 :         VSIFPrintfL(m_fpXML.get(), " xsi:nil=\"true\">");
    1786           0 :         aoCurComponents = aoFieldComponents;
    1787           0 :         bCurIsRegularField = true;
    1788           0 :         return true;
    1789             :     }
    1790             :     else
    1791             :     {
    1792             :         // Emit closing tags
    1793         564 :         WriteClosingTags(nCommonLength, aoCurComponents, aoFieldComponents,
    1794         564 :                          bCurIsRegularField, true);
    1795             :     }
    1796             : 
    1797             :     // Emit opening tags and attribute names
    1798             :     // We may do a 0 iteration in case of returning from an attribute
    1799             :     // to its element
    1800         564 :     bool bWriteEltContent = true;
    1801        1238 :     for (size_t i = nCommonLength; i < aoFieldComponents.size(); ++i)
    1802             :     {
    1803         674 :         if (i + 1 == aoFieldComponents.size() && IsAttr(aoFieldComponents[i]))
    1804             :         {
    1805          90 :             if (aoFieldComponents[i].second != szAT_ANY_ATTR)
    1806             :             {
    1807          82 :                 VSIFPrintfL(m_fpXML.get(),
    1808         164 :                             " %s=", MakeXPath(aoFieldComponents[i]).c_str());
    1809          82 :                 bWriteEltContent = false;
    1810             :             }
    1811             :         }
    1812             :         else
    1813             :         {
    1814         584 :             if (i > nCommonLength)
    1815          60 :                 PrintLine(m_fpXML.get(), "%s", "");
    1816         584 :             IncIndent();
    1817         584 :             PrintIndent(m_fpXML.get());
    1818             : 
    1819         710 :             if (i + 2 == aoFieldComponents.size() &&
    1820         126 :                 IsAttr(aoFieldComponents[i + 1]))
    1821             :             {
    1822             :                 // Are we an element that is going to have an
    1823             :                 // attribute ?
    1824          66 :                 VSIFPrintfL(m_fpXML.get(), "<%s",
    1825         132 :                             MakeXPath(aoFieldComponents[i]).c_str());
    1826             :             }
    1827             :             else
    1828             :             {
    1829             :                 // Are we a regular element ?
    1830         518 :                 if (bEmptyContent)
    1831             :                 {
    1832           4 :                     VSIFPrintfL(m_fpXML.get(), "<%s xsi:nil=\"true\">",
    1833           8 :                                 MakeXPath(aoFieldComponents[i]).c_str());
    1834             :                 }
    1835             :                 else
    1836             :                 {
    1837         514 :                     VSIFPrintfL(m_fpXML.get(), "<%s>",
    1838        1028 :                                 MakeXPath(aoFieldComponents[i]).c_str());
    1839             :                 }
    1840             :             }
    1841             :         }
    1842             :     }
    1843             : 
    1844             :     // Write content
    1845         564 :     if (!bWriteEltContent)
    1846          82 :         VSIFPrintfL(m_fpXML.get(), "\"");
    1847             : 
    1848         564 :     if (!bEmptyContent && oField.GetTypeName() == szFAKEXS_JSON_DICT)
    1849             :     {
    1850           8 :         json_object *poObj = nullptr;
    1851           8 :         if (OGRJSonParse(poFeature->GetFieldAsString(nFieldIdx), &poObj))
    1852             :         {
    1853           8 :             if (json_type_object == json_object_get_type(poObj))
    1854             :             {
    1855             :                 json_object_iter it;
    1856           8 :                 it.key = nullptr;
    1857           8 :                 it.val = nullptr;
    1858           8 :                 it.entry = nullptr;
    1859          16 :                 json_object_object_foreachC(poObj, it)
    1860             :                 {
    1861          16 :                     if (it.val != nullptr &&
    1862           8 :                         json_object_get_type(it.val) == json_type_string)
    1863             :                     {
    1864           8 :                         VSIFPrintfL(
    1865             :                             m_fpXML.get(), " %s=\"%s\"", it.key,
    1866          16 :                             XMLEscape(json_object_get_string(it.val)).c_str());
    1867             :                     }
    1868             :                 }
    1869             :             }
    1870           8 :             json_object_put(poObj);
    1871             :         }
    1872             :     }
    1873         556 :     else if (!bEmptyContent && bIsGeometryField)
    1874             :     {
    1875          78 :         bool bWriteOGRGeom = true;
    1876          78 :         OGRGeometry *poGeom = poFeature->GetGeomFieldRef(nFieldIdx);
    1877             : 
    1878             :         // In case the original GML string was saved, fetch it and compare it
    1879             :         // to the current OGR geometry. If they match (in a axis order
    1880             :         // insensitive way), then use the original GML string
    1881             :         const int nFieldXMLIdx =
    1882          78 :             oLayerDesc.GetOGRIdxFromFieldName(oField.GetName() + "_xml");
    1883          78 :         if (nFieldXMLIdx >= 0 && poFeature->IsFieldSetAndNotNull(nFieldXMLIdx))
    1884             :         {
    1885          13 :             if (poFeature->GetFieldDefnRef(nFieldXMLIdx)->GetType() ==
    1886             :                 OFTStringList)
    1887             :             {
    1888           1 :                 if (wkbFlatten(poGeom->getGeometryType()) ==
    1889             :                     wkbGeometryCollection)
    1890             :                 {
    1891           2 :                     OGRGeometryCollection oGC;
    1892             :                     char **papszValues =
    1893           1 :                         poFeature->GetFieldAsStringList(nFieldXMLIdx);
    1894           4 :                     for (int j = 0;
    1895           4 :                          papszValues != nullptr && papszValues[j] != nullptr;
    1896             :                          ++j)
    1897             :                     {
    1898           3 :                         OGRGeometry *poPart = OGRGeometry::FromHandle(
    1899           3 :                             OGR_G_CreateFromGML(papszValues[j]));
    1900           3 :                         if (poPart)
    1901           3 :                             oGC.addGeometryDirectly(poPart);
    1902             :                     }
    1903           1 :                     if (AreGeomsEqualAxisOrderInsensitive(poGeom, &oGC))
    1904             :                     {
    1905           4 :                         for (int j = 0; papszValues != nullptr &&
    1906           4 :                                         papszValues[j] != nullptr;
    1907             :                              ++j)
    1908             :                         {
    1909           3 :                             if (j > 0)
    1910           2 :                                 PrintMultipleValuesSeparator(oField,
    1911             :                                                              aoFieldComponents);
    1912           3 :                             VSIFPrintfL(m_fpXML.get(), "%s", papszValues[j]);
    1913             :                         }
    1914           1 :                         bWriteOGRGeom = false;
    1915             :                     }
    1916             :                 }
    1917             :             }
    1918             :             else
    1919             :             {
    1920          12 :                 const char *pszXML = poFeature->GetFieldAsString(nFieldXMLIdx);
    1921             :                 auto poOrigGeom = std::unique_ptr<OGRGeometry>(
    1922          24 :                     OGRGeometry::FromHandle(OGR_G_CreateFromGML(pszXML)));
    1923             : 
    1924          12 :                 if (poOrigGeom != nullptr)
    1925             :                 {
    1926          12 :                     if (AreGeomsEqualAxisOrderInsensitive(poGeom,
    1927             :                                                           poOrigGeom.get()))
    1928             :                     {
    1929          12 :                         VSIFPrintfL(m_fpXML.get(), "%s", pszXML);
    1930          12 :                         bWriteOGRGeom = false;
    1931             :                     }
    1932             :                 }
    1933             :             }
    1934             :         }
    1935             : 
    1936          78 :         if (bWriteOGRGeom)
    1937             :         {
    1938         130 :             CPLString osExtraElt;
    1939          65 :             bool bGMLSurface311 = false;
    1940          65 :             bool bGMLCurve311 = false;
    1941          65 :             bool bGMLPoint311 = false;
    1942          65 :             if (m_osGMLVersion == "3.1.1" &&
    1943          65 :                 MakeXPath(aoFieldComponents.back()) == "gml:Surface")
    1944             :             {
    1945           0 :                 bGMLSurface311 = true;
    1946             :             }
    1947          65 :             else if (m_osGMLVersion == "3.1.1" &&
    1948          65 :                      MakeXPath(aoFieldComponents.back()) == "gml:Curve")
    1949             :             {
    1950           0 :                 bGMLCurve311 = true;
    1951             :             }
    1952          65 :             else if (m_osGMLVersion == "3.1.1" &&
    1953          65 :                      MakeXPath(aoFieldComponents.back()) == "gml:Point")
    1954             :             {
    1955           0 :                 bGMLPoint311 = true;
    1956             :             }
    1957             : 
    1958             :             const double dfGMLVersion =
    1959          65 :                 m_osGMLVersion.empty() ? 3.2 : CPLAtof(m_osGMLVersion);
    1960          65 :             char **papszOptions = CSLSetNameValue(
    1961             :                 nullptr, "FORMAT",
    1962          65 :                 (dfGMLVersion >= 2.0 && dfGMLVersion < 3.0)   ? "GML2"
    1963          65 :                 : (dfGMLVersion >= 3.0 && dfGMLVersion < 3.2) ? "GML3"
    1964             :                                                               : "GML32");
    1965          65 :             papszOptions = CSLSetNameValue(papszOptions, "SRSNAME_FORMAT",
    1966             :                                            m_osSRSNameFormat);
    1967             : 
    1968          65 :             if (dfGMLVersion < 3.0)
    1969             :             {
    1970           0 :                 bool bSwap = false;
    1971             :                 const OGRSpatialReference *poSRS =
    1972           0 :                     poGeom->getSpatialReference();
    1973           0 :                 if (poSRS != nullptr && GetCoordSwap(poSRS))
    1974           0 :                     bSwap = true;
    1975           0 :                 papszOptions = CSLSetNameValue(papszOptions, "COORD_SWAP",
    1976             :                                                bSwap ? "TRUE" : "FALSE");
    1977             :             }
    1978             : 
    1979          70 :             if (oField.GetMaxOccurs() > 1 &&
    1980           5 :                 wkbFlatten(poGeom->getGeometryType()) == wkbGeometryCollection)
    1981             :             {
    1982           5 :                 OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
    1983          20 :                 for (int j = 0; j < poGC->getNumGeometries(); ++j)
    1984             :                 {
    1985          15 :                     if (dfGMLVersion >= 3.2)
    1986             :                     {
    1987             :                         CPLString osGMLID =
    1988          15 :                             poFeature->GetFieldAsString(oLayerDesc.osPKIDName);
    1989          15 :                         osGMLID += CPLSPrintf(".geom%d.%d", nFieldIdx, j);
    1990             :                         papszOptions =
    1991          15 :                             CSLSetNameValue(papszOptions, "GMLID", osGMLID);
    1992             :                     }
    1993          15 :                     if (j > 0)
    1994          10 :                         PrintMultipleValuesSeparator(oField, aoFieldComponents);
    1995          15 :                     char *pszGML = OGR_G_ExportToGMLEx(
    1996             :                         OGRGeometry::ToHandle(poGC->getGeometryRef(j)),
    1997             :                         papszOptions);
    1998          15 :                     if (pszGML)
    1999          15 :                         VSIFPrintfL(m_fpXML.get(), "%s", pszGML);
    2000          15 :                     CPLFree(pszGML);
    2001             :                 }
    2002             :             }
    2003             :             else
    2004             :             {
    2005          60 :                 if (dfGMLVersion >= 3.2)
    2006             :                 {
    2007             :                     CPLString osGMLID =
    2008          60 :                         poFeature->GetFieldAsString(oLayerDesc.osPKIDName);
    2009          60 :                     osGMLID += CPLSPrintf(".geom%d", nFieldIdx);
    2010             :                     papszOptions =
    2011          60 :                         CSLSetNameValue(papszOptions, "GMLID", osGMLID);
    2012             :                 }
    2013          60 :                 char *pszGML = OGR_G_ExportToGMLEx(
    2014             :                     OGRGeometry::ToHandle(poGeom), papszOptions);
    2015          60 :                 if (pszGML)
    2016             :                 {
    2017          60 :                     if (bGMLSurface311 && STARTS_WITH(pszGML, "<gml:Polygon>"))
    2018             :                     {
    2019           0 :                         char *pszEnd = strstr(pszGML, "</gml:Polygon>");
    2020           0 :                         if (pszEnd)
    2021             :                         {
    2022           0 :                             *pszEnd = '\0';
    2023           0 :                             VSIFPrintfL(m_fpXML.get(),
    2024             :                                         "<gml:patches><gml:PolygonPatch>%s"
    2025             :                                         "</gml:PolygonPatch></gml:patches>",
    2026             :                                         pszGML + strlen("<gml:Polygon>"));
    2027           0 :                         }
    2028             :                     }
    2029          60 :                     else if (bGMLCurve311 &&
    2030           0 :                              STARTS_WITH(pszGML, "<gml:LineString>"))
    2031             :                     {
    2032           0 :                         char *pszEnd = strstr(pszGML, "</gml:LineString>");
    2033           0 :                         if (pszEnd)
    2034             :                         {
    2035           0 :                             *pszEnd = '\0';
    2036           0 :                             VSIFPrintfL(
    2037             :                                 m_fpXML.get(),
    2038             :                                 "<gml:segments><gml:LineStringSegment>%s"
    2039             :                                 "</gml:LineStringSegment></gml:segments>",
    2040             :                                 pszGML + strlen("<gml:LineString>"));
    2041           0 :                         }
    2042             :                     }
    2043          60 :                     else if (bGMLPoint311 && STARTS_WITH(pszGML, "<gml:Point>"))
    2044             :                     {
    2045           0 :                         char *pszEnd = strstr(pszGML, "</gml:Point>");
    2046           0 :                         if (pszEnd)
    2047             :                         {
    2048           0 :                             *pszEnd = '\0';
    2049           0 :                             VSIFPrintfL(m_fpXML.get(), "%s",
    2050             :                                         pszGML + strlen("<gml:Point>"));
    2051           0 :                         }
    2052             :                     }
    2053             :                     else
    2054             :                     {
    2055          60 :                         VSIFPrintfL(m_fpXML.get(), "%s", pszGML);
    2056             :                     }
    2057             :                 }
    2058          60 :                 CPLFree(pszGML);
    2059             :             }
    2060          65 :             CSLDestroy(papszOptions);
    2061          78 :         }
    2062             :     }
    2063         478 :     else if (!bEmptyContent && oField.GetTypeName() == szXS_ANY_TYPE)
    2064             :     {
    2065          48 :         CPLString osXML(poFeature->GetFieldAsString(nFieldIdx));
    2066             :         // Check that the content is valid XML
    2067          72 :         CPLString osValidatingXML("<X>" + osXML + "</X>");
    2068          24 :         CPLXMLNode *psNode = CPLParseXMLString(osValidatingXML);
    2069          24 :         if (psNode != nullptr)
    2070             :         {
    2071          24 :             VSIFPrintfL(m_fpXML.get(), "%s", osXML.c_str());
    2072          24 :             CPLDestroyXMLNode(psNode);
    2073             :         }
    2074             :         else
    2075             :         {
    2076             :             // Otherwise consider it as text and escape
    2077           0 :             VSIFPrintfL(m_fpXML.get(), "%s", XMLEscape(osXML).c_str());
    2078             :         }
    2079             :     }
    2080         454 :     else if (!bEmptyContent)
    2081             :     {
    2082             :         const OGRFieldType eOGRType(
    2083         450 :             poFeature->GetFieldDefnRef(nFieldIdx)->GetType());
    2084         450 :         switch (oField.GetType())
    2085             :         {
    2086          20 :             case GMLAS_FT_BOOLEAN:
    2087             :             {
    2088          20 :                 if ((oField.GetMaxOccurs() > 1 || oField.IsList()) &&
    2089             :                     eOGRType == OFTIntegerList)
    2090             :                 {
    2091           8 :                     int nCount = 0;
    2092             :                     const int *panValues =
    2093           8 :                         poFeature->GetFieldAsIntegerList(nFieldIdx, &nCount);
    2094          24 :                     for (int j = 0; j < nCount; ++j)
    2095             :                     {
    2096          16 :                         if (j > 0)
    2097           8 :                             PrintMultipleValuesSeparator(oField,
    2098             :                                                          aoFieldComponents);
    2099          16 :                         VSIFPrintfL(m_fpXML.get(),
    2100          16 :                                     panValues[j] ? "true" : "false");
    2101             :                     }
    2102             :                 }
    2103             :                 else
    2104             :                 {
    2105          12 :                     VSIFPrintfL(m_fpXML.get(),
    2106          12 :                                 poFeature->GetFieldAsInteger(nFieldIdx)
    2107             :                                     ? "true"
    2108             :                                     : "false");
    2109             :                 }
    2110          20 :                 break;
    2111             :             }
    2112             : 
    2113          76 :             case GMLAS_FT_DATETIME:
    2114             :             case GMLAS_FT_DATE:
    2115             :             case GMLAS_FT_TIME:
    2116             :             {
    2117          76 :                 if (eOGRType == OFTDateTime || eOGRType == OFTDate ||
    2118             :                     eOGRType == OFTTime)
    2119             :                 {
    2120             :                     char *pszFormatted =
    2121          76 :                         OGRGetXMLDateTime(poFeature->GetRawFieldRef(nFieldIdx));
    2122          76 :                     char *pszT = strchr(pszFormatted, 'T');
    2123          76 :                     if (oField.GetType() == GMLAS_FT_TIME && pszT != nullptr)
    2124             :                     {
    2125           4 :                         VSIFPrintfL(m_fpXML.get(), "%s", pszT + 1);
    2126             :                     }
    2127             :                     else
    2128             :                     {
    2129          72 :                         if (oField.GetType() == GMLAS_FT_DATE)
    2130             :                         {
    2131           4 :                             if (pszT)
    2132           4 :                                 *pszT = '\0';
    2133             :                         }
    2134          72 :                         VSIFPrintfL(m_fpXML.get(), "%s", pszFormatted);
    2135             :                     }
    2136          76 :                     VSIFree(pszFormatted);
    2137             :                 }
    2138             :                 else
    2139             :                 {
    2140           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    2141             :                              "Invalid content for field %s of type %s: %s",
    2142           0 :                              oField.GetName().c_str(),
    2143           0 :                              oField.GetTypeName().c_str(),
    2144             :                              poFeature->GetFieldAsString(nFieldIdx));
    2145             :                 }
    2146          76 :                 break;
    2147             :             }
    2148             : 
    2149           4 :             case GMLAS_FT_BASE64BINARY:
    2150             :             {
    2151           4 :                 if (eOGRType == OFTBinary)
    2152             :                 {
    2153           4 :                     int nCount = 0;
    2154             :                     GByte *pabyContent =
    2155           4 :                         poFeature->GetFieldAsBinary(nFieldIdx, &nCount);
    2156           4 :                     char *pszBase64 = CPLBase64Encode(nCount, pabyContent);
    2157           4 :                     VSIFPrintfL(m_fpXML.get(), "%s", pszBase64);
    2158           4 :                     CPLFree(pszBase64);
    2159             :                 }
    2160             :                 else
    2161             :                 {
    2162           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    2163             :                              "Invalid content for field %s of type %s: %s",
    2164           0 :                              oField.GetName().c_str(),
    2165           0 :                              oField.GetTypeName().c_str(),
    2166             :                              poFeature->GetFieldAsString(nFieldIdx));
    2167             :                 }
    2168           4 :                 break;
    2169             :             }
    2170             : 
    2171           4 :             case GMLAS_FT_HEXBINARY:
    2172             :             {
    2173           4 :                 if (eOGRType == OFTBinary)
    2174             :                 {
    2175           4 :                     int nCount = 0;
    2176             :                     GByte *pabyContent =
    2177           4 :                         poFeature->GetFieldAsBinary(nFieldIdx, &nCount);
    2178          40 :                     for (int i = 0; i < nCount; ++i)
    2179          36 :                         VSIFPrintfL(m_fpXML.get(), "%02X", pabyContent[i]);
    2180             :                 }
    2181             :                 else
    2182             :                 {
    2183           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    2184             :                              "Invalid content for field %s of type %s: %s",
    2185           0 :                              oField.GetName().c_str(),
    2186           0 :                              oField.GetTypeName().c_str(),
    2187             :                              poFeature->GetFieldAsString(nFieldIdx));
    2188             :                 }
    2189           4 :                 break;
    2190             :             }
    2191             : 
    2192         346 :             default:
    2193             :             {
    2194         382 :                 if ((oField.GetMaxOccurs() > 1 || oField.IsList()) &&
    2195          36 :                     (eOGRType == OFTStringList || eOGRType == OFTRealList ||
    2196           8 :                      eOGRType == OFTIntegerList ||
    2197             :                      eOGRType == OFTInteger64List))
    2198             :                 {
    2199          64 :                     if (eOGRType == OFTStringList)
    2200             :                     {
    2201             :                         char **papszValues =
    2202          28 :                             poFeature->GetFieldAsStringList(nFieldIdx);
    2203          84 :                         for (int j = 0; papszValues != nullptr &&
    2204          84 :                                         papszValues[j] != nullptr;
    2205             :                              ++j)
    2206             :                         {
    2207          56 :                             if (j > 0)
    2208          28 :                                 PrintMultipleValuesSeparator(oField,
    2209             :                                                              aoFieldComponents);
    2210          56 :                             VSIFPrintfL(m_fpXML.get(), "%s",
    2211         112 :                                         XMLEscape(papszValues[j]).c_str());
    2212             :                         }
    2213             :                     }
    2214          36 :                     else if (eOGRType == OFTRealList)
    2215             :                     {
    2216          12 :                         int nCount = 0;
    2217             :                         const double *padfValues =
    2218          12 :                             poFeature->GetFieldAsDoubleList(nFieldIdx, &nCount);
    2219          40 :                         for (int j = 0; j < nCount; ++j)
    2220             :                         {
    2221          28 :                             if (j > 0)
    2222          16 :                                 PrintMultipleValuesSeparator(oField,
    2223             :                                                              aoFieldComponents);
    2224          28 :                             PrintXMLDouble(m_fpXML.get(), padfValues[j]);
    2225             :                         }
    2226             :                     }
    2227          24 :                     else if (eOGRType == OFTIntegerList)
    2228             :                     {
    2229          16 :                         int nCount = 0;
    2230          16 :                         const int *panValues = poFeature->GetFieldAsIntegerList(
    2231             :                             nFieldIdx, &nCount);
    2232          56 :                         for (int j = 0; j < nCount; ++j)
    2233             :                         {
    2234          40 :                             if (j > 0)
    2235          24 :                                 PrintMultipleValuesSeparator(oField,
    2236             :                                                              aoFieldComponents);
    2237          40 :                             VSIFPrintfL(m_fpXML.get(), "%d", panValues[j]);
    2238             :                         }
    2239             :                     }
    2240           8 :                     else if (eOGRType == OFTInteger64List)
    2241             :                     {
    2242           8 :                         int nCount = 0;
    2243             :                         const GIntBig *panValues =
    2244           8 :                             poFeature->GetFieldAsInteger64List(nFieldIdx,
    2245             :                                                                &nCount);
    2246          28 :                         for (int j = 0; j < nCount; ++j)
    2247             :                         {
    2248          20 :                             if (j > 0)
    2249          12 :                                 PrintMultipleValuesSeparator(oField,
    2250             :                                                              aoFieldComponents);
    2251          20 :                             VSIFPrintfL(m_fpXML.get(), CPL_FRMT_GIB,
    2252          20 :                                         panValues[j]);
    2253             :                         }
    2254             :                     }
    2255             :                 }
    2256         282 :                 else if (eOGRType == OFTReal)
    2257             :                 {
    2258          12 :                     PrintXMLDouble(m_fpXML.get(),
    2259             :                                    poFeature->GetFieldAsDouble(nFieldIdx));
    2260             :                 }
    2261             :                 else
    2262             :                 {
    2263         270 :                     VSIFPrintfL(
    2264             :                         m_fpXML.get(), "%s",
    2265         540 :                         XMLEscape(poFeature->GetFieldAsString(nFieldIdx))
    2266             :                             .c_str());
    2267             :                 }
    2268         346 :                 break;
    2269             :             }
    2270             :         }
    2271             :     }
    2272             : 
    2273         564 :     if (!bWriteEltContent)
    2274          82 :         VSIFPrintfL(m_fpXML.get(), "\"");
    2275             : 
    2276         564 :     aoCurComponents = std::move(aoFieldComponents);
    2277         564 :     bCurIsRegularField = true;
    2278             : 
    2279         564 :     return true;
    2280             : }
    2281             : 
    2282             : /************************************************************************/
    2283             : /*                     WriteFieldNoLink()                               */
    2284             : /************************************************************************/
    2285             : 
    2286          84 : bool GMLASWriter::WriteFieldNoLink(
    2287             :     OGRFeature *poFeature, const GMLASField &oField,
    2288             :     const LayerDescription &oLayerDesc, XPathComponents &aoLayerComponents,
    2289             :     XPathComponents &aoCurComponents, const XPathComponents &aoPrefixComponents,
    2290             :     const std::set<CPLString> &oSetLayersInIteration, int nRecLevel,
    2291             :     bool &bAtLeastOneFieldWritten, bool &bCurIsRegularField)
    2292             : {
    2293          84 :     const auto oIter = m_oMapXPathToIdx.find(oField.GetRelatedClassXPath());
    2294          84 :     if (oIter == m_oMapXPathToIdx.end())
    2295             :     {
    2296             :         // Not necessary to be more verbose in case of truncated
    2297             :         // source dataset
    2298           0 :         CPLDebug("GMLAS", "No child layer of %s matching xpath = %s",
    2299             :                  oLayerDesc.osName.c_str(),
    2300           0 :                  oField.GetRelatedClassXPath().c_str());
    2301           0 :         return true;
    2302             :     }
    2303             : 
    2304          84 :     const LayerDescription &oChildLayerDesc = m_aoLayerDesc[oIter->second];
    2305          84 :     OGRLayer *poRelLayer = GetLayerByName(oChildLayerDesc.osName);
    2306          84 :     if (poRelLayer == nullptr)
    2307             :     {
    2308             :         // Not necessary to be more verbose in case of truncated
    2309             :         // source dataset
    2310           0 :         CPLDebug("GMLAS", "Child layer %s of %s not found",
    2311             :                  oChildLayerDesc.osName.c_str(), oLayerDesc.osName.c_str());
    2312           0 :         return true;
    2313             :     }
    2314             : 
    2315          84 :     if (oLayerDesc.osPKIDName.empty())
    2316             :     {
    2317           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Missing %s for layer %s",
    2318             :                  szLAYER_PKID_NAME, oLayerDesc.osName.c_str());
    2319           0 :         return true;
    2320             :     }
    2321             :     int nParentPKIDIdx;
    2322          84 :     if ((nParentPKIDIdx =
    2323          84 :              oLayerDesc.GetOGRIdxFromFieldName(oLayerDesc.osPKIDName)) < 0)
    2324             :     {
    2325           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2326             :                  "Cannot find field %s in layer %s",
    2327             :                  oLayerDesc.osPKIDName.c_str(), oLayerDesc.osName.c_str());
    2328           0 :         return true;
    2329             :     }
    2330          84 :     if (!poFeature->IsFieldSetAndNotNull(nParentPKIDIdx))
    2331             :     {
    2332           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2333             :                  "Missing value of %s field for feature " CPL_FRMT_GIB
    2334             :                  " of layer %s",
    2335             :                  oLayerDesc.osPKIDName.c_str(), poFeature->GetFID(),
    2336             :                  oLayerDesc.osName.c_str());
    2337           0 :         return true;
    2338             :     }
    2339          84 :     if (oChildLayerDesc.osParentPKIDName.empty())
    2340             :     {
    2341           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Missing %s for layer %s",
    2342             :                  szLAYER_PARENT_PKID_NAME, oChildLayerDesc.osName.c_str());
    2343             :     }
    2344          84 :     if (oSetLayersInIteration.find(oChildLayerDesc.osName) !=
    2345         168 :         oSetLayersInIteration.end())
    2346             :     {
    2347           0 :         CPLDebug("GMLAS", "Unexpected at line %d", __LINE__);
    2348           0 :         return true;
    2349             :     }
    2350             : 
    2351         168 :     std::set<CPLString> oSetLayersInIterationSub(oSetLayersInIteration);
    2352          84 :     oSetLayersInIterationSub.insert(oChildLayerDesc.osName);
    2353             : 
    2354          84 :     if (aoLayerComponents.empty())
    2355             :     {
    2356          64 :         aoLayerComponents = SplitXPath(oLayerDesc.osXPath);
    2357          64 :         aoLayerComponents.insert(aoLayerComponents.begin(),
    2358             :                                  aoPrefixComponents.begin(),
    2359         128 :                                  aoPrefixComponents.end());
    2360             :     }
    2361             : 
    2362         168 :     XPathComponents aoFieldComponents = SplitXPath(oField.GetXPath());
    2363          84 :     aoFieldComponents.insert(aoFieldComponents.begin(),
    2364             :                              aoPrefixComponents.begin(),
    2365         168 :                              aoPrefixComponents.end());
    2366             : 
    2367         168 :     CPLString osParentPKID(poFeature->GetFieldAsString(nParentPKIDIdx));
    2368          84 :     poRelLayer->SetAttributeFilter(
    2369             :         CPLSPrintf("%s = '%s'", oChildLayerDesc.osParentPKIDName.c_str(),
    2370          84 :                    osParentPKID.c_str()));
    2371          84 :     poRelLayer->ResetReading();
    2372             : 
    2373             :     auto poChildFeature =
    2374         168 :         std::unique_ptr<OGRFeature>(poRelLayer->GetNextFeature());
    2375         168 :     XPathComponents aoNewInitialContext;
    2376          84 :     if (poChildFeature != nullptr)
    2377             :     {
    2378         120 :         if (aoFieldComponents.size() == aoLayerComponents.size() + 1 &&
    2379          44 :             oField.GetRepetitionOnSequence())
    2380             :         {
    2381             :             /* Case of
    2382             :             <xs:element name="sequence_unbounded_dt_1">
    2383             :                 <xs:complexType>
    2384             :                     <xs:sequence maxOccurs="unbounded">
    2385             :                         <xs:element name="subelement"
    2386             :                                     type="xs:dateTime"/>
    2387             :                     </xs:sequence>
    2388             :                 </xs:complexType>
    2389             :             </xs:element>
    2390             :             */
    2391           8 :             aoNewInitialContext = std::move(aoFieldComponents);
    2392             :         }
    2393          68 :         else if (aoFieldComponents.size() == aoLayerComponents.size() + 2)
    2394             :         {
    2395             :             /* Case of
    2396             :             <xs:element name="sequence_1_dt_unbounded">
    2397             :                 <xs:complexType>
    2398             :                     <xs:sequence>
    2399             :                         <xs:element name="subelement"
    2400             :                                     type="xs:dateTime"
    2401             :                                     maxOccurs="unbounded"/>
    2402             :                     </xs:sequence>
    2403             :                 </xs:complexType>
    2404             :             </xs:element>
    2405             :             */
    2406          16 :             aoNewInitialContext = std::move(aoFieldComponents);
    2407          16 :             aoNewInitialContext.resize(aoNewInitialContext.size() - 1);
    2408             :         }
    2409             :         else
    2410             :         {
    2411             :             /* Case of
    2412             :             <xs:element name="unbounded_sequence_1_dt"
    2413             :                         maxOccurs="unbounded">
    2414             :                 <xs:complexType>
    2415             :                     <xs:sequence>
    2416             :                         <xs:element name="subelement"
    2417             :                                     type="xs:dateTime"/>
    2418             :                     </xs:sequence>
    2419             :                 </xs:complexType>
    2420             :             </xs:element>
    2421             :             */
    2422          52 :             aoNewInitialContext = std::move(aoLayerComponents);
    2423             :         }
    2424             : 
    2425          76 :         WriteClosingAndStartingTags(aoCurComponents, aoNewInitialContext,
    2426          76 :                                     bCurIsRegularField);
    2427             : 
    2428          76 :         bAtLeastOneFieldWritten = true;
    2429          76 :         aoCurComponents = aoNewInitialContext;
    2430          76 :         bCurIsRegularField = false;
    2431             :     }
    2432             : 
    2433         244 :     while (poChildFeature)
    2434             :     {
    2435         160 :         bool bRet = WriteFeature(poChildFeature.get(), oChildLayerDesc,
    2436             :                                  oSetLayersInIterationSub, aoNewInitialContext,
    2437             :                                  aoPrefixComponents, nRecLevel + 1);
    2438             : 
    2439         160 :         if (!bRet)
    2440           0 :             return false;
    2441             : 
    2442         160 :         poChildFeature.reset(poRelLayer->GetNextFeature());
    2443             :     }
    2444          84 :     poRelLayer->ResetReading();
    2445             : 
    2446          84 :     return true;
    2447             : }
    2448             : 
    2449             : /************************************************************************/
    2450             : /*                       GetFilteredLayer()                             */
    2451             : /************************************************************************/
    2452             : 
    2453             : OGRLayer *
    2454          52 : GMLASWriter::GetFilteredLayer(OGRLayer *poSrcLayer, const CPLString &osFilter,
    2455             :                               const std::set<CPLString> &oSetLayersInIteration)
    2456             : {
    2457          52 :     if (oSetLayersInIteration.find(poSrcLayer->GetName()) ==
    2458         104 :         oSetLayersInIteration.end())
    2459             :     {
    2460          32 :         poSrcLayer->SetAttributeFilter(osFilter);
    2461          32 :         poSrcLayer->ResetReading();
    2462          32 :         return poSrcLayer;
    2463             :     }
    2464             : 
    2465             :     // RDBMS drivers will really create a new iterator independent of the
    2466             :     // underlying layer when using a SELECT statement
    2467          20 :     GDALDriver *poDriver = m_poSrcDS->GetDriver();
    2468          40 :     if (poDriver != nullptr &&
    2469          20 :         (EQUAL(poDriver->GetDescription(), "SQLite") ||
    2470           0 :          EQUAL(poDriver->GetDescription(), "PostgreSQL")))
    2471             :     {
    2472          40 :         CPLString osSQL;
    2473          20 :         osSQL.Printf("SELECT * FROM \"%s\" WHERE %s", poSrcLayer->GetName(),
    2474          20 :                      osFilter.c_str());
    2475          20 :         return m_poSrcDS->ExecuteSQL(osSQL, nullptr, nullptr);
    2476             :     }
    2477             : 
    2478             :     // TODO ?
    2479           0 :     CPLDebug("GMLAS", "Cannot recursively iterate on %s on this driver",
    2480           0 :              poSrcLayer->GetName());
    2481           0 :     return nullptr;
    2482             : }
    2483             : 
    2484             : /************************************************************************/
    2485             : /*                      ReleaseFilteredLayer()                          */
    2486             : /************************************************************************/
    2487             : 
    2488          52 : void GMLASWriter::ReleaseFilteredLayer(OGRLayer *poSrcLayer,
    2489             :                                        OGRLayer *poIterLayer)
    2490             : {
    2491          52 :     if (poIterLayer != poSrcLayer)
    2492          20 :         m_poSrcDS->ReleaseResultSet(poIterLayer);
    2493             :     else
    2494          32 :         poSrcLayer->ResetReading();
    2495          52 : }
    2496             : 
    2497             : /************************************************************************/
    2498             : /*                     WriteFieldWithLink()                             */
    2499             : /************************************************************************/
    2500             : 
    2501          36 : bool GMLASWriter::WriteFieldWithLink(
    2502             :     OGRFeature *poFeature, const GMLASField &oField,
    2503             :     const LayerDescription &oLayerDesc, XPathComponents &aoLayerComponents,
    2504             :     XPathComponents &aoCurComponents, const XPathComponents &aoPrefixComponents,
    2505             :     const std::set<CPLString> &oSetLayersInIteration, int nRecLevel,
    2506             :     bool &bAtLeastOneFieldWritten, bool &bCurIsRegularField)
    2507             : {
    2508          36 :     const auto oIter = m_oMapXPathToIdx.find(oField.GetRelatedClassXPath());
    2509          36 :     if (oIter == m_oMapXPathToIdx.end())
    2510             :     {
    2511             :         // Not necessary to be more verbose in case of truncated
    2512             :         // source dataset
    2513           0 :         CPLDebug("GMLAS", "No child layer of %s matching xpath = %s",
    2514             :                  oLayerDesc.osName.c_str(),
    2515           0 :                  oField.GetRelatedClassXPath().c_str());
    2516           0 :         return true;
    2517             :     }
    2518             : 
    2519          36 :     const LayerDescription &oChildLayerDesc = m_aoLayerDesc[oIter->second];
    2520          36 :     OGRLayer *poRelLayer = GetLayerByName(oChildLayerDesc.osName);
    2521          36 :     if (poRelLayer == nullptr)
    2522             :     {
    2523             :         // Not necessary to be more verbose in case of truncated
    2524             :         // source dataset
    2525           0 :         CPLDebug("GMLAS", "Referenced layer %s of %s not found",
    2526             :                  oChildLayerDesc.osName.c_str(), oLayerDesc.osName.c_str());
    2527           0 :         return true;
    2528             :     }
    2529             : 
    2530          36 :     const int nFieldIdx = oLayerDesc.GetOGRIdxFromFieldName(oField.GetName());
    2531          72 :     XPathComponents aoFieldComponents = SplitXPath(oField.GetXPath());
    2532          36 :     aoFieldComponents.insert(aoFieldComponents.begin(),
    2533             :                              aoPrefixComponents.begin(),
    2534          72 :                              aoPrefixComponents.end());
    2535             : 
    2536          36 :     if (nFieldIdx < 0)
    2537             :     {
    2538           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Missing field %s for layer %s",
    2539           0 :                  oField.GetName().c_str(), oLayerDesc.osName.c_str());
    2540           0 :         return true;
    2541             :     }
    2542          36 :     if (!poFeature->IsFieldSetAndNotNull(nFieldIdx))
    2543             :     {
    2544             :         // Not an error (unless the field is required)
    2545           4 :         return true;
    2546             :     }
    2547          32 :     if (oLayerDesc.osPKIDName.empty())
    2548             :     {
    2549           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Missing %s for layer %s",
    2550             :                  szLAYER_PKID_NAME, oLayerDesc.osName.c_str());
    2551           0 :         return true;
    2552             :     }
    2553          32 :     if (oChildLayerDesc.osPKIDName.empty())
    2554             :     {
    2555           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Missing %s for layer %s",
    2556             :                  szLAYER_PKID_NAME, oChildLayerDesc.osName.c_str());
    2557           0 :         return true;
    2558             :     }
    2559          32 :     if (aoFieldComponents.size() < 2)
    2560             :     {
    2561             :         // Shouldn't happen for well behaved metadata
    2562           0 :         CPLDebug("GMLAS", "Unexpected at line %d", __LINE__);
    2563           0 :         return true;
    2564             :     }
    2565          64 :     if (oChildLayerDesc.osXPath.empty() ||
    2566          32 :         aoFieldComponents.back() != SplitXPath(oChildLayerDesc.osXPath).front())
    2567             :     {
    2568             :         // Shouldn't happen for well behaved metadata
    2569           0 :         CPLDebug("GMLAS", "Unexpected at line %d", __LINE__);
    2570           0 :         return true;
    2571             :     }
    2572             : 
    2573          64 :     CPLString osChildPKID(poFeature->GetFieldAsString(nFieldIdx));
    2574             :     const CPLString osFilter(CPLSPrintf(
    2575          64 :         "%s = '%s'", oChildLayerDesc.osPKIDName.c_str(), osChildPKID.c_str()));
    2576             :     OGRLayer *poIterLayer =
    2577          32 :         GetFilteredLayer(poRelLayer, osFilter, oSetLayersInIteration);
    2578          32 :     if (poIterLayer == nullptr)
    2579             :     {
    2580           0 :         return true;
    2581             :     }
    2582             : 
    2583          64 :     std::set<CPLString> oSetLayersInIterationSub(oSetLayersInIteration);
    2584          32 :     oSetLayersInIterationSub.insert(oChildLayerDesc.osName);
    2585             : 
    2586          64 :     XPathComponents aoPrefixComponentsNew(aoFieldComponents);
    2587          32 :     aoPrefixComponentsNew.resize(aoPrefixComponentsNew.size() - 1);
    2588             : 
    2589          32 :     if (aoLayerComponents.empty())
    2590             :     {
    2591          32 :         aoLayerComponents = SplitXPath(oLayerDesc.osXPath);
    2592          32 :         aoLayerComponents.insert(aoLayerComponents.begin(),
    2593             :                                  aoPrefixComponents.begin(),
    2594          64 :                                  aoPrefixComponents.end());
    2595             :     }
    2596             : 
    2597             :     auto poChildFeature =
    2598          64 :         std::unique_ptr<OGRFeature>(poIterLayer->GetNextFeature());
    2599          32 :     XPathComponents aoInitialComponents;
    2600          32 :     const bool bHasChild = poChildFeature != nullptr;
    2601          32 :     if (bHasChild)
    2602             :     {
    2603          32 :         aoInitialComponents = std::move(aoFieldComponents);
    2604          32 :         if (!aoInitialComponents.empty())
    2605          32 :             aoInitialComponents.resize(aoInitialComponents.size() - 1);
    2606          32 :         WriteClosingAndStartingTags(aoCurComponents, aoInitialComponents,
    2607          32 :                                     bCurIsRegularField);
    2608             :     }
    2609             : 
    2610          32 :     bool bRet = true;
    2611          64 :     while (poChildFeature)
    2612             :     {
    2613          32 :         bRet = WriteFeature(poChildFeature.get(), oChildLayerDesc,
    2614             :                             oSetLayersInIterationSub, aoInitialComponents,
    2615             :                             aoPrefixComponentsNew, nRecLevel + 1);
    2616          32 :         if (!bRet)
    2617           0 :             break;
    2618          32 :         poChildFeature.reset(poIterLayer->GetNextFeature());
    2619             :     }
    2620          32 :     ReleaseFilteredLayer(poRelLayer, poIterLayer);
    2621             : 
    2622          32 :     if (bHasChild)
    2623             :     {
    2624          32 :         bAtLeastOneFieldWritten = true;
    2625          32 :         aoCurComponents = std::move(aoInitialComponents);
    2626          32 :         bCurIsRegularField = false;
    2627             :     }
    2628             : 
    2629          32 :     return bRet;
    2630             : }
    2631             : 
    2632             : /************************************************************************/
    2633             : /*                   WriteFieldJunctionTable()                          */
    2634             : /************************************************************************/
    2635             : 
    2636          16 : bool GMLASWriter::WriteFieldJunctionTable(
    2637             :     OGRFeature *poFeature, const GMLASField &oField,
    2638             :     const LayerDescription &oLayerDesc,
    2639             :     XPathComponents & /*aoLayerComponents */, XPathComponents &aoCurComponents,
    2640             :     const XPathComponents &aoPrefixComponents,
    2641             :     const std::set<CPLString> &oSetLayersInIteration, int nRecLevel,
    2642             :     bool &bAtLeastOneFieldWritten, bool &bCurIsRegularField)
    2643             : {
    2644          16 :     const auto oIter = m_oMapXPathToIdx.find(oField.GetRelatedClassXPath());
    2645          16 :     if (oIter == m_oMapXPathToIdx.end())
    2646             :     {
    2647             :         // Not necessary to be more verbose in case of truncated
    2648             :         // source dataset
    2649           0 :         CPLDebug("GMLAS", "No related layer of %s matching xpath = %s",
    2650             :                  oLayerDesc.osName.c_str(),
    2651           0 :                  oField.GetRelatedClassXPath().c_str());
    2652           0 :         return true;
    2653             :     }
    2654             : 
    2655          16 :     const LayerDescription &oRelLayerDesc = m_aoLayerDesc[oIter->second];
    2656          16 :     OGRLayer *poRelLayer = GetLayerByName(oRelLayerDesc.osName);
    2657          16 :     OGRLayer *poJunctionLayer = GetLayerByName(oField.GetJunctionLayer());
    2658          16 :     if (poRelLayer == nullptr)
    2659             :     {
    2660             :         // Not necessary to be more verbose in case of truncated
    2661             :         // source dataset
    2662           0 :         CPLDebug("GMLAS", "Referenced layer %s of %s not found",
    2663             :                  oRelLayerDesc.osName.c_str(), oLayerDesc.osName.c_str());
    2664           0 :         return true;
    2665             :     }
    2666          16 :     if (poJunctionLayer == nullptr)
    2667             :     {
    2668             :         // Not necessary to be more verbose in case of truncated
    2669             :         // source dataset
    2670           0 :         CPLDebug("GMLAS", "Junction layer %s not found",
    2671           0 :                  oField.GetJunctionLayer().c_str());
    2672           0 :         return true;
    2673             :     }
    2674             : 
    2675          16 :     int nIndexPKID = -1;
    2676          16 :     if (oLayerDesc.osPKIDName.empty())
    2677             :     {
    2678           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Missing %s for layer %s",
    2679             :                  szLAYER_PKID_NAME, oLayerDesc.osName.c_str());
    2680           0 :         return true;
    2681             :     }
    2682          16 :     if ((nIndexPKID =
    2683          16 :              oLayerDesc.GetOGRIdxFromFieldName(oLayerDesc.osPKIDName)) < 0)
    2684             :     {
    2685           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find %s='%s' in layer %s",
    2686             :                  szLAYER_PKID_NAME, oLayerDesc.osPKIDName.c_str(),
    2687             :                  oLayerDesc.osName.c_str());
    2688           0 :         return true;
    2689             :     }
    2690          16 :     if (!poFeature->IsFieldSetAndNotNull(nIndexPKID))
    2691             :     {
    2692           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2693             :                  "Field '%s' in layer %s is not set for "
    2694             :                  "feature " CPL_FRMT_GIB,
    2695             :                  oLayerDesc.osPKIDName.c_str(), oLayerDesc.osName.c_str(),
    2696             :                  poFeature->GetFID());
    2697           0 :         return true;
    2698             :     }
    2699          16 :     if (oRelLayerDesc.osPKIDName.empty())
    2700             :     {
    2701           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Missing %s for layer %s",
    2702             :                  szLAYER_PKID_NAME, oRelLayerDesc.osName.c_str());
    2703           0 :         return true;
    2704             :     }
    2705          16 :     if (oSetLayersInIteration.find(oRelLayerDesc.osName) !=
    2706          32 :         oSetLayersInIteration.end())
    2707             :     {
    2708             :         // TODO... cycle situation. We will need to open a new
    2709             :         // source dataset or something
    2710           0 :         return true;
    2711             :     }
    2712             : 
    2713          32 :     std::set<CPLString> oSetLayersInIterationSub(oSetLayersInIteration);
    2714          16 :     oSetLayersInIterationSub.insert(oRelLayerDesc.osName);
    2715             : 
    2716          16 :     poJunctionLayer->SetAttributeFilter(CPLSPrintf(
    2717          16 :         "%s = '%s'", szPARENT_PKID, poFeature->GetFieldAsString(nIndexPKID)));
    2718          16 :     poJunctionLayer->ResetReading();
    2719          32 :     std::vector<CPLString> aoChildPKIDs;
    2720          36 :     for (auto &&poJunctionFeature : *poJunctionLayer)
    2721             :     {
    2722          20 :         aoChildPKIDs.push_back(
    2723             :             poJunctionFeature->GetFieldAsString(szCHILD_PKID));
    2724             :     }
    2725          16 :     poJunctionLayer->ResetReading();
    2726             : 
    2727          16 :     bool bRet = true;
    2728          16 :     bool bHasChild = false;
    2729          32 :     XPathComponents aoInitialComponents;
    2730          36 :     for (size_t j = 0; bRet && j < aoChildPKIDs.size(); j++)
    2731             :     {
    2732          20 :         CPLString osFilter;
    2733             :         osFilter.Printf("%s = '%s'", oRelLayerDesc.osPKIDName.c_str(),
    2734          20 :                         aoChildPKIDs[j].c_str());
    2735             :         OGRLayer *poIterLayer =
    2736          20 :             GetFilteredLayer(poRelLayer, osFilter, oSetLayersInIteration);
    2737          20 :         if (poIterLayer == nullptr)
    2738             :         {
    2739           0 :             return true;
    2740             :         }
    2741             : 
    2742             :         auto poChildFeature =
    2743          40 :             std::unique_ptr<OGRFeature>(poIterLayer->GetNextFeature());
    2744          20 :         if (poChildFeature != nullptr)
    2745             :         {
    2746          20 :             if (!bHasChild)
    2747             :             {
    2748          12 :                 bHasChild = true;
    2749             : 
    2750          12 :                 aoInitialComponents = SplitXPath(oField.GetXPath());
    2751          12 :                 aoInitialComponents.insert(aoInitialComponents.begin(),
    2752             :                                            aoPrefixComponents.begin(),
    2753          24 :                                            aoPrefixComponents.end());
    2754             : 
    2755          12 :                 if (!aoInitialComponents.empty())
    2756          12 :                     aoInitialComponents.resize(aoInitialComponents.size() - 1);
    2757          12 :                 WriteClosingAndStartingTags(
    2758          12 :                     aoCurComponents, aoInitialComponents, bCurIsRegularField);
    2759             :             }
    2760             : 
    2761          20 :             bRet = WriteFeature(poChildFeature.get(), oRelLayerDesc,
    2762          40 :                                 oSetLayersInIterationSub, XPathComponents(),
    2763          40 :                                 XPathComponents(), nRecLevel + 1);
    2764             : 
    2765          20 :             ReleaseFilteredLayer(poRelLayer, poIterLayer);
    2766             :         }
    2767             :         else
    2768             :         {
    2769           0 :             ReleaseFilteredLayer(poRelLayer, poIterLayer);
    2770             :         }
    2771             :     }
    2772             : 
    2773          16 :     if (bHasChild)
    2774             :     {
    2775          12 :         bAtLeastOneFieldWritten = true;
    2776          12 :         aoCurComponents = std::move(aoInitialComponents);
    2777          12 :         bCurIsRegularField = false;
    2778             :     }
    2779             : 
    2780          16 :     return bRet;
    2781             : }
    2782             : 
    2783             : /************************************************************************/
    2784             : /*                           PrintIndent()                              */
    2785             : /************************************************************************/
    2786             : 
    2787         870 : void GMLASWriter::PrintIndent(VSILFILE *fp)
    2788             : {
    2789        3888 :     for (int i = 0; i < m_nIndentLevel; i++)
    2790             :     {
    2791        3018 :         VSIFWriteL(m_osIndentation.c_str(), 1, m_osIndentation.size(), fp);
    2792             :     }
    2793         870 : }
    2794             : 
    2795             : /************************************************************************/
    2796             : /*                            PrintLine()                               */
    2797             : /************************************************************************/
    2798             : 
    2799        1100 : void GMLASWriter::PrintLine(VSILFILE *fp, const char *fmt, ...)
    2800             : {
    2801        2200 :     CPLString osWork;
    2802             :     va_list args;
    2803             : 
    2804        1100 :     va_start(args, fmt);
    2805        1100 :     osWork.vPrintf(fmt, args);
    2806        1100 :     va_end(args);
    2807             : 
    2808        1100 :     VSIFWriteL(osWork.c_str(), 1, osWork.size(), fp);
    2809        1100 :     VSIFWriteL(m_osEOL.c_str(), 1, m_osEOL.size(), fp);
    2810        1100 : }
    2811             : 
    2812             : } /* namespace GMLAS */
    2813             : 
    2814             : /************************************************************************/
    2815             : /*                           GMLASFakeDataset                           */
    2816             : /************************************************************************/
    2817             : 
    2818             : class GMLASFakeDataset final : public GDALDataset
    2819             : {
    2820             :   public:
    2821           0 :     GMLASFakeDataset()
    2822           0 :     {
    2823           0 :     }
    2824             : };
    2825             : 
    2826             : /************************************************************************/
    2827             : /*                        OGRGMLASDriverCreateCopy()                    */
    2828             : /************************************************************************/
    2829             : 
    2830          22 : GDALDataset *OGRGMLASDriverCreateCopy(const char *pszFilename,
    2831             :                                       GDALDataset *poSrcDS, int /*bStrict*/,
    2832             :                                       char **papszOptions,
    2833             :                                       GDALProgressFunc pfnProgress,
    2834             :                                       void *pProgressData)
    2835             : {
    2836          22 :     if (strcmp(CPLGetExtension(pszFilename), "xsd") == 0)
    2837             :     {
    2838           1 :         CPLError(CE_Failure, CPLE_AppDefined, ".xsd extension is not valid");
    2839           1 :         return nullptr;
    2840             :     }
    2841             : 
    2842             :     // Strip GMLAS: prefix if specified
    2843          21 :     if (STARTS_WITH_CI(pszFilename, szGMLAS_PREFIX))
    2844           1 :         pszFilename += strlen(szGMLAS_PREFIX);
    2845             : 
    2846          42 :     GMLAS::GMLASWriter oWriter(pszFilename, poSrcDS, papszOptions);
    2847          21 :     if (!oWriter.Write(pfnProgress, pProgressData))
    2848          11 :         return nullptr;
    2849             : 
    2850          20 :     if (CPLString(pszFilename) == "/vsistdout/" ||
    2851             :         // This option is mostly useful for tests where we don't want
    2852             :         // WFS 2.0 schemas to be pulled from the network
    2853          10 :         !CPLFetchBool(papszOptions, "REOPEN_DATASET_WITH_GMLAS", true))
    2854             :     {
    2855           0 :         return new GMLASFakeDataset();
    2856             :     }
    2857             :     else
    2858             :     {
    2859             :         GDALOpenInfo oOpenInfo(
    2860          30 :             (CPLString(szGMLAS_PREFIX) + pszFilename).c_str(), GA_ReadOnly);
    2861          20 :         auto poOutDS = std::make_unique<OGRGMLASDataSource>();
    2862          10 :         if (!poOutDS->Open(&oOpenInfo))
    2863             :         {
    2864           0 :             poOutDS.reset();
    2865             :         }
    2866          10 :         return poOutDS.release();
    2867             :     }
    2868             : }

Generated by: LCOV version 1.14