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

Generated by: LCOV version 1.14