LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/nas - nasreader.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 318 472 67.4 %
Date: 2025-08-01 10:10:57 Functions: 25 30 83.3 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  NAS Reader
       4             :  * Purpose:  Implementation of NASReader class.
       5             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2002, Frank Warmerdam
       9             :  * Copyright (c) 2010-2013, Even Rouault <even dot rouault at spatialys.com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  ****************************************************************************/
      13             : 
      14             : #include "gmlreaderp.h"
      15             : #include "gmlreader.h"
      16             : 
      17             : #include <algorithm>
      18             : 
      19             : #include "cpl_conv.h"
      20             : #include "cpl_error.h"
      21             : #include "cpl_multiproc.h"
      22             : #include "cpl_string.h"
      23             : #include "gmlutils.h"
      24             : #include "ogr_geometry.h"
      25             : 
      26             : /************************************************************************/
      27             : /* ==================================================================== */
      28             : /*                  With XERCES Library                                 */
      29             : /* ==================================================================== */
      30             : /************************************************************************/
      31             : 
      32             : #include "nasreaderp.h"
      33             : 
      34             : /************************************************************************/
      35             : /*                          CreateNASReader()                           */
      36             : /************************************************************************/
      37             : 
      38           5 : IGMLReader *CreateNASReader()
      39             : 
      40             : {
      41           5 :     return new NASReader();
      42             : }
      43             : 
      44             : /************************************************************************/
      45             : /*                             NASReader()                              */
      46             : /************************************************************************/
      47             : 
      48           5 : NASReader::NASReader()
      49             :     : m_bClassListLocked(false), m_nClassCount(0), m_papoClass(nullptr),
      50             :       m_pszFilename(nullptr), m_poNASHandler(nullptr), m_poSAXReader(nullptr),
      51             :       m_bReadStarted(false), m_bXercesInitialized(false), m_poState(nullptr),
      52             :       m_poCompleteFeature(nullptr), m_fp(nullptr), m_GMLInputSource(nullptr),
      53           5 :       m_pszFilteredClassName(nullptr)
      54             : {
      55           5 : }
      56             : 
      57             : /************************************************************************/
      58             : /*                             ~NASReader()                             */
      59             : /************************************************************************/
      60             : 
      61          10 : NASReader::~NASReader()
      62             : 
      63             : {
      64           5 :     NASReader::ClearClasses();
      65             : 
      66           5 :     CPLFree(m_pszFilename);
      67             : 
      68           5 :     CleanupParser();
      69             : 
      70           5 :     if (m_fp)
      71           5 :         VSIFCloseL(m_fp);
      72             : 
      73           5 :     if (m_bXercesInitialized)
      74           5 :         OGRDeinitializeXerces();
      75             : 
      76           5 :     CPLFree(m_pszFilteredClassName);
      77          10 : }
      78             : 
      79             : /************************************************************************/
      80             : /*                          SetSourceFile()                             */
      81             : /************************************************************************/
      82             : 
      83           5 : void NASReader::SetSourceFile(const char *pszFilename)
      84             : 
      85             : {
      86           5 :     CPLFree(m_pszFilename);
      87           5 :     m_pszFilename = CPLStrdup(pszFilename);
      88           5 : }
      89             : 
      90             : /************************************************************************/
      91             : /*                       GetSourceFileName()                            */
      92             : /************************************************************************/
      93             : 
      94           0 : const char *NASReader::GetSourceFileName()
      95             : {
      96           0 :     return m_pszFilename;
      97             : }
      98             : 
      99             : /************************************************************************/
     100             : /*                            SetupParser()                             */
     101             : /************************************************************************/
     102             : 
     103           8 : bool NASReader::SetupParser()
     104             : 
     105             : {
     106           8 :     if (m_fp == nullptr)
     107           5 :         m_fp = VSIFOpenL(m_pszFilename, "rb");
     108           8 :     if (m_fp == nullptr)
     109           0 :         return false;
     110           8 :     VSIFSeekL(m_fp, 0, SEEK_SET);
     111             : 
     112           8 :     if (!m_bXercesInitialized)
     113             :     {
     114           5 :         if (!OGRInitializeXerces())
     115           0 :             return false;
     116           5 :         m_bXercesInitialized = true;
     117             :     }
     118             : 
     119             :     // Cleanup any old parser.
     120           8 :     if (m_poSAXReader != nullptr)
     121           0 :         CleanupParser();
     122             : 
     123             :     // Create and initialize parser.
     124           8 :     XMLCh *xmlUriValid = nullptr;
     125           8 :     XMLCh *xmlUriNS = nullptr;
     126             : 
     127             :     try
     128             :     {
     129           8 :         m_poSAXReader = XMLReaderFactory::createXMLReader();
     130             : 
     131           8 :         m_poNASHandler = new NASHandler(this);
     132             : 
     133           8 :         m_poSAXReader->setContentHandler(m_poNASHandler);
     134           8 :         m_poSAXReader->setErrorHandler(m_poNASHandler);
     135           8 :         m_poSAXReader->setLexicalHandler(m_poNASHandler);
     136           8 :         m_poSAXReader->setEntityResolver(m_poNASHandler);
     137           8 :         m_poSAXReader->setDTDHandler(m_poNASHandler);
     138           8 :         m_poSAXReader->setFeature(
     139           8 :             XMLUni::fgXercesDisableDefaultEntityResolution, true);
     140             : 
     141           8 :         xmlUriValid =
     142           8 :             XMLString::transcode("http://xml.org/sax/features/validation");
     143           8 :         xmlUriNS =
     144           8 :             XMLString::transcode("http://xml.org/sax/features/namespaces");
     145             : 
     146             : #if (OGR_GML_VALIDATION)
     147             :         m_poSAXReader->setFeature(xmlUriValid, true);
     148             :         m_poSAXReader->setFeature(xmlUriNS, true);
     149             : 
     150             :         m_poSAXReader->setFeature(XMLUni::fgSAX2CoreNameSpaces, true);
     151             :         m_poSAXReader->setFeature(XMLUni::fgXercesSchema, true);
     152             : 
     153             :         // m_poSAXReader->setDoSchema(true);
     154             :         // m_poSAXReader->setValidationSchemaFullChecking(true);
     155             : #else
     156           8 :         m_poSAXReader->setFeature(XMLUni::fgSAX2CoreValidation, false);
     157             : 
     158           8 :         m_poSAXReader->setFeature(XMLUni::fgXercesSchema, false);
     159             : 
     160             : #endif
     161           8 :         XMLString::release(&xmlUriValid);
     162           8 :         XMLString::release(&xmlUriNS);
     163             :     }
     164           0 :     catch (...)
     165             :     {
     166           0 :         XMLString::release(&xmlUriValid);
     167           0 :         XMLString::release(&xmlUriNS);
     168             : 
     169           0 :         CPLError(CE_Warning, CPLE_AppDefined,
     170             :                  "NAS: Exception initializing Xerces based GML reader.\n");
     171           0 :         return false;
     172             :     }
     173             : 
     174           8 :     m_bReadStarted = false;
     175             : 
     176             :     // Push an empty state.
     177           8 :     PushState(new GMLReadState());
     178             : 
     179           8 :     if (m_GMLInputSource == nullptr)
     180             :     {
     181           8 :         m_GMLInputSource = OGRCreateXercesInputSource(m_fp);
     182             :     }
     183             : 
     184           8 :     return true;
     185             : }
     186             : 
     187             : /************************************************************************/
     188             : /*                           CleanupParser()                            */
     189             : /************************************************************************/
     190             : 
     191          16 : void NASReader::CleanupParser()
     192             : 
     193             : {
     194          16 :     if (m_poSAXReader == nullptr)
     195           8 :         return;
     196             : 
     197          16 :     while (m_poState)
     198           8 :         PopState();
     199             : 
     200           8 :     delete m_poSAXReader;
     201           8 :     m_poSAXReader = nullptr;
     202             : 
     203           8 :     delete m_poNASHandler;
     204           8 :     m_poNASHandler = nullptr;
     205             : 
     206           8 :     delete m_poCompleteFeature;
     207           8 :     m_poCompleteFeature = nullptr;
     208             : 
     209           8 :     OGRDestroyXercesInputSource(m_GMLInputSource);
     210           8 :     m_GMLInputSource = nullptr;
     211             : 
     212           8 :     m_bReadStarted = false;
     213             : }
     214             : 
     215             : /************************************************************************/
     216             : /*                            NextFeature()                             */
     217             : /************************************************************************/
     218             : 
     219          15 : GMLFeature *NASReader::NextFeature()
     220             : 
     221             : {
     222          15 :     GMLFeature *poReturn = nullptr;
     223             : 
     224             :     try
     225             :     {
     226          15 :         if (!m_bReadStarted)
     227             :         {
     228           8 :             if (m_poSAXReader == nullptr)
     229           3 :                 SetupParser();
     230             : 
     231           8 :             if (m_poSAXReader == nullptr)
     232           0 :                 return nullptr;
     233             : 
     234           8 :             if (!m_poSAXReader->parseFirst(*m_GMLInputSource, m_oToFill))
     235           0 :                 return nullptr;
     236           8 :             m_bReadStarted = true;
     237             :         }
     238             : 
     239        3125 :         while (m_poCompleteFeature == nullptr && !m_bStopParsing &&
     240        3101 :                m_poSAXReader->parseNext(m_oToFill))
     241             :         {
     242             :         }
     243             : 
     244          14 :         poReturn = m_poCompleteFeature;
     245          14 :         m_poCompleteFeature = nullptr;
     246             :     }
     247           0 :     catch (const XMLException &toCatch)
     248             :     {
     249           0 :         m_bStopParsing = true;
     250           0 :         CPLDebug("NAS", "Error during NextFeature()! Message:\n%s",
     251           0 :                  transcode(toCatch.getMessage()).c_str());
     252             :     }
     253           2 :     catch (const SAXException &toCatch)
     254             :     {
     255           1 :         CPLString osErrMsg;
     256           1 :         transcode(toCatch.getMessage(), osErrMsg);
     257           1 :         CPLError(CE_Failure, CPLE_AppDefined, "%s", osErrMsg.c_str());
     258           1 :         m_bStopParsing = true;
     259             :     }
     260             : 
     261          15 :     return poReturn;
     262             : }
     263             : 
     264             : /************************************************************************/
     265             : /*                            PushFeature()                             */
     266             : /*                                                                      */
     267             : /*      Create a feature based on the named element.  If the            */
     268             : /*      corresponding feature class doesn't exist yet, then create      */
     269             : /*      it now.  A new GMLReadState will be created for the feature,    */
     270             : /*      and it will be placed within that state.  The state is          */
     271             : /*      pushed onto the readstate stack.                                */
     272             : /************************************************************************/
     273             : 
     274          10 : void NASReader::PushFeature(const char *pszElement, const Attributes &attrs)
     275             : 
     276             : {
     277             :     /* -------------------------------------------------------------------- */
     278             :     /*      Find the class of this element.                                 */
     279             :     /* -------------------------------------------------------------------- */
     280          10 :     int iClass = 0;
     281          13 :     for (; iClass < GetClassCount(); iClass++)
     282             :     {
     283           8 :         if (strcmp(pszElement, GetClass(iClass)->GetElementName()) == 0)
     284           5 :             break;
     285             :     }
     286             : 
     287             :     /* -------------------------------------------------------------------- */
     288             :     /*      Create a new feature class for this element, if there is no     */
     289             :     /*      existing class for it.                                          */
     290             :     /* -------------------------------------------------------------------- */
     291          10 :     if (iClass == GetClassCount())
     292             :     {
     293           5 :         CPLAssert(!IsClassListLocked());
     294             : 
     295           5 :         GMLFeatureClass *poNewClass = new GMLFeatureClass(pszElement);
     296             : 
     297           5 :         if (EQUAL(pszElement, "Delete"))
     298             :         {
     299             :             const struct
     300             :             {
     301             :                 const char *pszName;
     302             :                 GMLPropertyType eType;
     303             :                 int width;
     304           3 :             } types[] = {
     305             :                 {"typeName", GMLPT_String, -1},
     306             :                 {"FeatureId", GMLPT_String, -1},
     307             :                 {"context", GMLPT_String, -1},
     308             :                 {"safeToIgnore", GMLPT_String, -1},
     309             :                 {"replacedBy", GMLPT_String, -1},
     310             :                 {"anlass", GMLPT_StringList, -1},
     311             :                 {"endet", GMLPT_String, 20},
     312             :                 {"ignored", GMLPT_String, -1},
     313             :             };
     314             : 
     315          27 :             for (unsigned int i = 0; i < CPL_ARRAYSIZE(types); i++)
     316             :             {
     317             :                 GMLPropertyDefn *poPDefn =
     318          24 :                     new GMLPropertyDefn(types[i].pszName, types[i].pszName);
     319             : 
     320          24 :                 poPDefn->SetType(types[i].eType);
     321          24 :                 if (types[i].width > 0)
     322           3 :                     poPDefn->SetWidth(types[i].width);
     323             : 
     324          24 :                 poNewClass->AddProperty(poPDefn);
     325             :             }
     326             :         }
     327             : 
     328           5 :         iClass = AddClass(poNewClass);
     329             :     }
     330             : 
     331             :     /* -------------------------------------------------------------------- */
     332             :     /*      Create a feature of this feature class.                         */
     333             :     /* -------------------------------------------------------------------- */
     334          10 :     GMLFeature *poFeature = new GMLFeature(GetClass(iClass));
     335             : 
     336             :     /* -------------------------------------------------------------------- */
     337             :     /*      Create and push a new read state.                               */
     338             :     /* -------------------------------------------------------------------- */
     339          10 :     GMLReadState *poState = new GMLReadState();
     340          10 :     poState->m_poFeature = poFeature;
     341          10 :     PushState(poState);
     342             : 
     343             :     /* -------------------------------------------------------------------- */
     344             :     /*      Check for gml:id, and if found push it as an attribute named    */
     345             :     /*      gml_id.                                                         */
     346             :     /* -------------------------------------------------------------------- */
     347          10 :     const XMLCh achGmlId[] = {'g', 'm', 'l', ':', 'i', 'd', 0};
     348          10 :     int nFIDIndex = attrs.getIndex(achGmlId);
     349          10 :     if (nFIDIndex != -1)
     350             :     {
     351           3 :         char *pszFID = CPLStrdup(transcode(attrs.getValue(nFIDIndex)));
     352           3 :         SetFeaturePropertyDirectly("gml_id", pszFID);
     353             :     }
     354          10 : }
     355             : 
     356             : /************************************************************************/
     357             : /*                          IsFeatureElement()                          */
     358             : /*                                                                      */
     359             : /*      Based on context and the element name, is this element a new    */
     360             : /*      GML feature element?                                            */
     361             : /************************************************************************/
     362             : 
     363         184 : bool NASReader::IsFeatureElement(const char *pszElement)
     364             : 
     365             : {
     366         184 :     CPLAssert(m_poState != nullptr);
     367             : 
     368         184 :     const char *pszLast = m_poState->GetLastComponent();
     369         184 :     const int nLen = static_cast<int>(strlen(pszLast));
     370             : 
     371             :     // There seem to be two major NAS classes of feature identifiers
     372             :     // -- either a wfs:Insert or a gml:featureMember/wfs:member
     373             : 
     374         184 :     if ((nLen < 6 || !EQUAL(pszLast + nLen - 6, "Insert")) &&
     375         184 :         (nLen < 13 || !EQUAL(pszLast + nLen - 13, "featureMember")) &&
     376         184 :         (nLen < 6 || !EQUAL(pszLast + nLen - 6, "member")) &&
     377         172 :         (nLen < 7 || !EQUAL(pszLast + nLen - 7, "Replace")))
     378         177 :         return false;
     379             : 
     380             :     // If the class list isn't locked, any element that is a featureMember
     381             :     // will do.
     382           7 :     if (EQUAL(pszElement, "Filter"))
     383           3 :         return false;
     384             : 
     385           4 :     if (!IsClassListLocked())
     386           4 :         return true;
     387             : 
     388           0 :     if (EQUAL(pszElement, "Delete"))
     389           0 :         return false;
     390             : 
     391             :     // otherwise, find a class with the desired element name.
     392           0 :     for (int i = 0; i < GetClassCount(); i++)
     393             :     {
     394           0 :         if (EQUAL(pszElement, GetClass(i)->GetElementName()))
     395           0 :             return true;
     396             :     }
     397             : 
     398           0 :     return false;
     399             : }
     400             : 
     401             : /************************************************************************/
     402             : /*                         IsAttributeElement()                         */
     403             : /************************************************************************/
     404             : 
     405         159 : bool NASReader::IsAttributeElement(const char *pszElement,
     406             :                                    const Attributes &attrs)
     407             : 
     408             : {
     409         159 :     if (m_poState->m_poFeature == nullptr)
     410           0 :         return false;
     411             : 
     412         159 :     GMLFeatureClass *poClass = m_poState->m_poFeature->GetClass();
     413             : 
     414             :     // If the schema is not yet locked, then any simple element
     415             :     // is potentially an attribute.
     416         159 :     if (!poClass->IsSchemaLocked())
     417         159 :         return true;
     418             : 
     419             :     // Otherwise build the path to this element into a single string
     420             :     // and compare against known attributes.
     421           0 :     CPLString osElemPath;
     422             : 
     423           0 :     if (m_poState->m_nPathLength == 0)
     424           0 :         osElemPath = pszElement;
     425             :     else
     426             :     {
     427           0 :         osElemPath = m_poState->osPath;
     428           0 :         osElemPath += "|";
     429           0 :         osElemPath += pszElement;
     430             :     }
     431             : 
     432           0 :     if (poClass->GetPropertyIndexBySrcElement(
     433           0 :             osElemPath.c_str(), static_cast<int>(osElemPath.size())) >= 0)
     434           0 :         return true;
     435             : 
     436           0 :     for (unsigned int idx = 0; idx < attrs.getLength(); ++idx)
     437             :     {
     438           0 :         CPLString osAttrName = transcode(attrs.getQName(idx));
     439           0 :         CPLString osAttrPath;
     440             : 
     441           0 :         const char *pszName = strchr(osAttrName.c_str(), ':');
     442           0 :         if (pszName)
     443             :         {
     444           0 :             osAttrPath = osElemPath + "@" + (pszName + 1);
     445           0 :             if (poClass->GetPropertyIndexBySrcElement(
     446           0 :                     osAttrPath.c_str(), static_cast<int>(osAttrPath.size())) >=
     447             :                 0)
     448           0 :                 return true;
     449             :         }
     450             : 
     451           0 :         osAttrPath = osElemPath + "@" + osAttrName;
     452           0 :         if (poClass->GetPropertyIndexBySrcElement(
     453           0 :                 osAttrPath.c_str(), static_cast<int>(osAttrPath.size())) >= 0)
     454           0 :             return true;
     455             :     }
     456             : 
     457           0 :     return false;
     458             : }
     459             : 
     460             : /************************************************************************/
     461             : /*                              PopState()                              */
     462             : /************************************************************************/
     463             : 
     464          18 : void NASReader::PopState()
     465             : 
     466             : {
     467          18 :     if (m_poState != nullptr)
     468             :     {
     469          18 :         if (m_poState->m_poFeature != nullptr && m_poCompleteFeature == nullptr)
     470             :         {
     471          10 :             m_poCompleteFeature = m_poState->m_poFeature;
     472          10 :             m_poState->m_poFeature = nullptr;
     473             :         }
     474           8 :         else if (m_poState->m_poFeature != nullptr)
     475             :         {
     476           0 :             delete m_poState->m_poFeature;
     477           0 :             m_poState->m_poFeature = nullptr;
     478             :         }
     479             : 
     480          18 :         GMLReadState *poParent = m_poState->m_poParentState;
     481             : 
     482          18 :         delete m_poState;
     483          18 :         m_poState = poParent;
     484             :     }
     485          18 : }
     486             : 
     487             : /************************************************************************/
     488             : /*                             PushState()                              */
     489             : /************************************************************************/
     490             : 
     491          18 : void NASReader::PushState(GMLReadState *poState)
     492             : 
     493             : {
     494          18 :     poState->m_poParentState = m_poState;
     495          18 :     m_poState = poState;
     496          18 : }
     497             : 
     498             : /************************************************************************/
     499             : /*                              GetClass()                              */
     500             : /************************************************************************/
     501             : 
     502          28 : GMLFeatureClass *NASReader::GetClass(int iClass) const
     503             : 
     504             : {
     505          28 :     if (iClass < 0 || iClass >= m_nClassCount)
     506           0 :         return nullptr;
     507             : 
     508          28 :     return m_papoClass[iClass];
     509             : }
     510             : 
     511             : /************************************************************************/
     512             : /*                              GetClass()                              */
     513             : /************************************************************************/
     514             : 
     515          10 : GMLFeatureClass *NASReader::GetClass(const char *pszName) const
     516             : 
     517             : {
     518          14 :     for (int iClass = 0; iClass < m_nClassCount; iClass++)
     519             :     {
     520           9 :         if (strcmp(m_papoClass[iClass]->GetName(), pszName) == 0)
     521           5 :             return m_papoClass[iClass];
     522             :     }
     523             : 
     524           5 :     return nullptr;
     525             : }
     526             : 
     527             : /************************************************************************/
     528             : /*                              AddClass()                              */
     529             : /************************************************************************/
     530             : 
     531           5 : int NASReader::AddClass(GMLFeatureClass *poNewClass)
     532             : 
     533             : {
     534           5 :     CPLAssert(poNewClass != nullptr &&
     535             :               GetClass(poNewClass->GetName()) == nullptr);
     536             : 
     537           5 :     m_nClassCount++;
     538           5 :     m_papoClass = static_cast<GMLFeatureClass **>(
     539           5 :         CPLRealloc(m_papoClass, sizeof(void *) * m_nClassCount));
     540             : 
     541             :     // keep delete the last entry
     542           7 :     if (m_nClassCount > 1 &&
     543           2 :         EQUAL(m_papoClass[m_nClassCount - 2]->GetName(), "Delete"))
     544             :     {
     545           0 :         m_papoClass[m_nClassCount - 1] = m_papoClass[m_nClassCount - 2];
     546           0 :         m_papoClass[m_nClassCount - 2] = poNewClass;
     547           0 :         return m_nClassCount - 2;
     548             :     }
     549             :     else
     550             :     {
     551           5 :         m_papoClass[m_nClassCount - 1] = poNewClass;
     552           5 :         return m_nClassCount - 1;
     553             :     }
     554             : }
     555             : 
     556             : /************************************************************************/
     557             : /*                            ClearClasses()                            */
     558             : /************************************************************************/
     559             : 
     560           5 : void NASReader::ClearClasses()
     561             : 
     562             : {
     563           5 :     CPLDebug("NAS", "Clearing classes.");
     564             : 
     565          10 :     for (int i = 0; i < m_nClassCount; i++)
     566           5 :         delete m_papoClass[i];
     567           5 :     CPLFree(m_papoClass);
     568             : 
     569           5 :     m_nClassCount = 0;
     570           5 :     m_papoClass = nullptr;
     571           5 : }
     572             : 
     573             : /************************************************************************/
     574             : /*                     SetFeaturePropertyDirectly()                     */
     575             : /*                                                                      */
     576             : /*      Set the property value on the current feature, adding the       */
     577             : /*      property name to the GMLFeatureClass if required.               */
     578             : /*      The pszValue ownership is passed to this function.              */
     579             : /************************************************************************/
     580             : 
     581         117 : void NASReader::SetFeaturePropertyDirectly(const char *pszElement,
     582             :                                            char *pszValue)
     583             : 
     584             : {
     585         117 :     GMLFeature *poFeature = GetState()->m_poFeature;
     586             : 
     587         117 :     CPLAssert(poFeature != nullptr);
     588             : 
     589             :     /* -------------------------------------------------------------------- */
     590             :     /*      Does this property exist in the feature class?  If not, add     */
     591             :     /*      it.                                                             */
     592             :     /* -------------------------------------------------------------------- */
     593         117 :     GMLFeatureClass *poClass = poFeature->GetClass();
     594         234 :     int iProperty = poClass->GetPropertyIndexBySrcElement(
     595         117 :         pszElement, static_cast<int>(strlen(pszElement)));
     596             : 
     597         117 :     if (iProperty < 0)
     598             :     {
     599          52 :         if (poClass->IsSchemaLocked())
     600             :         {
     601             :             // CPLDebug("NAS", "Encountered property %s missing from class %s schema [%s].", pszElement, poClass->GetName(), pszValue);
     602           0 :             CPLFree(pszValue);
     603           0 :             return;
     604             :         }
     605             : 
     606          52 :         iProperty = poClass->GetPropertyCount();
     607             : 
     608         104 :         CPLString osFieldName;
     609             : 
     610          52 :         if (strchr(pszElement, '|') == nullptr)
     611          22 :             osFieldName = pszElement;
     612             :         else
     613             :         {
     614          30 :             osFieldName = strrchr(pszElement, '|') + 1;
     615          30 :             if (poClass->GetPropertyIndex(osFieldName) != -1)
     616           4 :                 osFieldName = pszElement;
     617             :         }
     618             : 
     619             :         // Does this conflict with an existing property name?
     620          52 :         while (poClass->GetProperty(osFieldName) != nullptr)
     621             :         {
     622           0 :             osFieldName += "_";
     623             :         }
     624             : 
     625          52 :         GMLPropertyDefn *poPDefn = new GMLPropertyDefn(osFieldName, pszElement);
     626             : 
     627          52 :         if (EQUAL(CPLGetConfigOption("GML_FIELDTYPES", ""), "ALWAYS_STRING"))
     628           0 :             poPDefn->SetType(GMLPT_String);
     629             : 
     630          52 :         poClass->AddProperty(poPDefn);
     631             :     }
     632             : 
     633         117 :     if (GMLPropertyDefn::IsSimpleType(
     634             :             poClass->GetProperty(iProperty)->GetType()))
     635             :     {
     636          51 :         const GMLProperty *poProp = poFeature->GetProperty(iProperty);
     637          51 :         if (poProp && poProp->nSubProperties > 0)
     638             :         {
     639           4 :             int iId = poClass->GetPropertyIndex("gml_id");
     640           4 :             const GMLProperty *poIdProp = poFeature->GetProperty(iId);
     641             : 
     642           8 :             CPLError(CE_Warning, CPLE_AppDefined,
     643             :                      "NAS: Overwriting existing property %s.%s of value '%s' "
     644             :                      "with '%s' (gml_id: %s; type:%d).",
     645             :                      poClass->GetName(), pszElement,
     646           4 :                      poProp->papszSubProperties[0], pszValue,
     647           4 :                      poIdProp && poIdProp->nSubProperties > 0 &&
     648           4 :                              poIdProp->papszSubProperties &&
     649           4 :                              poIdProp->papszSubProperties[0]
     650           4 :                          ? poIdProp->papszSubProperties[0]
     651             :                          : "(null)",
     652           4 :                      poClass->GetProperty(iProperty)->GetType());
     653             :         }
     654             :     }
     655             : 
     656             :     /* -------------------------------------------------------------------- */
     657             :     /*      Set the property                                                */
     658             :     /* -------------------------------------------------------------------- */
     659         117 :     poFeature->SetPropertyDirectly(iProperty, pszValue);
     660             : 
     661             :     /* -------------------------------------------------------------------- */
     662             :     /*      Do we need to update the property type?                         */
     663             :     /* -------------------------------------------------------------------- */
     664         117 :     if (!poClass->IsSchemaLocked())
     665             :     {
     666         117 :         auto poClassProperty = poClass->GetProperty(iProperty);
     667         117 :         if (poClassProperty)
     668             :         {
     669         117 :             const GMLProperty *poProp = poFeature->GetProperty(iProperty);
     670         117 :             if (poProp)
     671             :             {
     672         117 :                 poClassProperty->AnalysePropertyValue(poProp);
     673             :             }
     674             :         }
     675             :         else
     676             :         {
     677           0 :             CPLAssert(false);
     678             :         }
     679             :     }
     680             : }
     681             : 
     682             : /************************************************************************/
     683             : /*                            LoadClasses()                             */
     684             : /************************************************************************/
     685             : 
     686           0 : bool NASReader::LoadClasses(const char *pszFile)
     687             : 
     688             : {
     689             :     // Add logic later to determine reasonable default schema file.
     690           0 :     if (pszFile == nullptr)
     691           0 :         return false;
     692             : 
     693           0 :     CPLDebug("NAS", "Loading classes from %s", pszFile);
     694             : 
     695             :     /* -------------------------------------------------------------------- */
     696             :     /*      Load the raw XML file.                                          */
     697             :     /* -------------------------------------------------------------------- */
     698           0 :     VSILFILE *fp = VSIFOpenL(pszFile, "rb");
     699             : 
     700           0 :     if (fp == nullptr)
     701             :     {
     702           0 :         CPLError(CE_Failure, CPLE_OpenFailed, "NAS: Failed to open file %s.",
     703             :                  pszFile);
     704           0 :         return false;
     705             :     }
     706             : 
     707           0 :     VSIFSeekL(fp, 0, SEEK_END);
     708           0 :     int nLength = static_cast<int>(VSIFTellL(fp));
     709           0 :     VSIFSeekL(fp, 0, SEEK_SET);
     710             : 
     711           0 :     char *pszWholeText = static_cast<char *>(VSIMalloc(nLength + 1));
     712           0 :     if (pszWholeText == nullptr)
     713             :     {
     714           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     715             :                  "NAS: Failed to allocate %d byte buffer for %s,\n"
     716             :                  "is this really a GMLFeatureClassList file?",
     717             :                  nLength, pszFile);
     718           0 :         VSIFCloseL(fp);
     719           0 :         return false;
     720             :     }
     721             : 
     722           0 :     if (VSIFReadL(pszWholeText, nLength, 1, fp) != 1)
     723             :     {
     724           0 :         VSIFree(pszWholeText);
     725           0 :         VSIFCloseL(fp);
     726           0 :         CPLError(CE_Failure, CPLE_AppDefined, "NAS: Read failed on %s.",
     727             :                  pszFile);
     728           0 :         return false;
     729             :     }
     730           0 :     pszWholeText[nLength] = '\0';
     731             : 
     732           0 :     VSIFCloseL(fp);
     733             : 
     734           0 :     if (strstr(pszWholeText, "<GMLFeatureClassList") == nullptr)
     735             :     {
     736           0 :         VSIFree(pszWholeText);
     737           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     738             :                  "NAS: File %s does not contain a GMLFeatureClassList tree.",
     739             :                  pszFile);
     740           0 :         return false;
     741             :     }
     742             : 
     743             :     /* -------------------------------------------------------------------- */
     744             :     /*      Convert to XML parse tree.                                      */
     745             :     /* -------------------------------------------------------------------- */
     746           0 :     CPLXMLTreeCloser psRoot(CPLParseXMLString(pszWholeText));
     747           0 :     VSIFree(pszWholeText);
     748             : 
     749             :     // We assume parser will report errors via CPL.
     750           0 :     if (psRoot.get() == nullptr)
     751           0 :         return false;
     752             : 
     753           0 :     if (psRoot->eType != CXT_Element ||
     754           0 :         !EQUAL(psRoot->pszValue, "GMLFeatureClassList"))
     755             :     {
     756           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     757             :                  "NAS: File %s is not a GMLFeatureClassList document.",
     758             :                  pszFile);
     759           0 :         return false;
     760             :     }
     761             : 
     762             :     /* -------------------------------------------------------------------- */
     763             :     /*      Extract feature classes for all definitions found.              */
     764             :     /* -------------------------------------------------------------------- */
     765           0 :     for (CPLXMLNode *psThis = psRoot->psChild; psThis != nullptr;
     766           0 :          psThis = psThis->psNext)
     767             :     {
     768           0 :         if (psThis->eType == CXT_Element &&
     769           0 :             EQUAL(psThis->pszValue, "GMLFeatureClass"))
     770             :         {
     771           0 :             GMLFeatureClass *poClass = new GMLFeatureClass();
     772             : 
     773           0 :             if (!poClass->InitializeFromXML(psThis))
     774             :             {
     775           0 :                 delete poClass;
     776           0 :                 return false;
     777             :             }
     778             : 
     779           0 :             poClass->SetSchemaLocked(true);
     780             : 
     781           0 :             AddClass(poClass);
     782             :         }
     783             :     }
     784             : 
     785           0 :     SetClassListLocked(true);
     786             : 
     787           0 :     return true;
     788             : }
     789             : 
     790             : /************************************************************************/
     791             : /*                            SaveClasses()                             */
     792             : /************************************************************************/
     793             : 
     794           3 : bool NASReader::SaveClasses(const char *pszFile)
     795             : 
     796             : {
     797             :     // Add logic later to determine reasonable default schema file.
     798           3 :     if (pszFile == nullptr)
     799           0 :         return false;
     800             : 
     801             :     /* -------------------------------------------------------------------- */
     802             :     /*      Create in memory schema tree.                                   */
     803             :     /* -------------------------------------------------------------------- */
     804             :     CPLXMLNode *psRoot =
     805           3 :         CPLCreateXMLNode(nullptr, CXT_Element, "GMLFeatureClassList");
     806             : 
     807           8 :     for (int iClass = 0; iClass < GetClassCount(); iClass++)
     808             :     {
     809           5 :         GMLFeatureClass *poClass = GetClass(iClass);
     810             : 
     811           5 :         CPLAddXMLChild(psRoot, poClass->SerializeToXML());
     812             :     }
     813             : 
     814             :     /* -------------------------------------------------------------------- */
     815             :     /*      Serialize to disk.                                              */
     816             :     /* -------------------------------------------------------------------- */
     817           3 :     char *pszWholeText = CPLSerializeXMLTree(psRoot);
     818             : 
     819           3 :     CPLDestroyXMLNode(psRoot);
     820             : 
     821           3 :     VSILFILE *fp = VSIFOpenL(pszFile, "wb");
     822             : 
     823           3 :     bool bSuccess = true;
     824           3 :     if (fp == nullptr)
     825           0 :         bSuccess = false;
     826           3 :     else if (VSIFWriteL(pszWholeText, strlen(pszWholeText), 1, fp) != 1)
     827             :     {
     828           0 :         VSIFCloseL(fp);
     829           0 :         bSuccess = false;
     830             :     }
     831             :     else
     832             :     {
     833           3 :         if (VSIFWriteL(pszWholeText, strlen(pszWholeText), 1, fp) != 1)
     834           0 :             bSuccess = false;
     835           3 :         VSIFCloseL(fp);
     836             :     }
     837             : 
     838           3 :     CPLFree(pszWholeText);
     839             : 
     840           3 :     return bSuccess;
     841             : }
     842             : 
     843             : /************************************************************************/
     844             : /*                          PrescanForSchema()                          */
     845             : /*                                                                      */
     846             : /*      For now we use a pretty dumb approach of just doing a normal    */
     847             : /*      scan of the whole file, building up the schema information.     */
     848             : /*      Eventually we hope to do a more efficient scan when just        */
     849             : /*      looking for schema information.                                 */
     850             : /************************************************************************/
     851             : 
     852           5 : bool NASReader::PrescanForSchema(bool bGetExtents, bool /*bOnlyDetectSRS*/)
     853             : {
     854           5 :     if (m_pszFilename == nullptr)
     855           0 :         return false;
     856             : 
     857           5 :     CPLDebug("NAS", "Prescanning %s.", m_pszFilename);
     858             : 
     859           5 :     SetClassListLocked(false);
     860             : 
     861           5 :     if (!SetupParser())
     862           0 :         return false;
     863             : 
     864           5 :     std::string osWork;
     865             : 
     866           5 :     GMLFeature *poFeature = nullptr;
     867          12 :     while ((poFeature = NextFeature()) != nullptr)
     868             :     {
     869           7 :         GMLFeatureClass *poClass = poFeature->GetClass();
     870             : 
     871           7 :         if (poClass->GetFeatureCount() == -1)
     872           5 :             poClass->SetFeatureCount(1);
     873             :         else
     874           2 :             poClass->SetFeatureCount(poClass->GetFeatureCount() + 1);
     875             : 
     876           7 :         if (bGetExtents)
     877             :         {
     878           7 :             OGRGeometry *poGeometry = nullptr;
     879             : 
     880             :             const CPLXMLNode *const *papsGeometry =
     881           7 :                 poFeature->GetGeometryList();
     882           7 :             if (papsGeometry[0] != nullptr)
     883             :             {
     884             :                 poGeometry =
     885           2 :                     (OGRGeometry *)OGR_G_CreateFromGMLTree(papsGeometry[0]);
     886           2 :                 poGeometry = ConvertGeometry(poGeometry);
     887             :             }
     888             : 
     889           7 :             if (poGeometry != nullptr)
     890             :             {
     891           2 :                 OGREnvelope sEnvelope;
     892             : 
     893           2 :                 if (poClass->GetGeometryPropertyCount() == 0)
     894           4 :                     poClass->AddGeometryProperty(new GMLGeometryPropertyDefn(
     895           2 :                         "", "", wkbUnknown, -1, true));
     896             : 
     897             :                 OGRwkbGeometryType eGType =
     898             :                     (OGRwkbGeometryType)poClass->GetGeometryProperty(0)
     899           2 :                         ->GetType();
     900             : 
     901             :                 // Merge SRSName into layer.
     902             :                 const char *pszSRSName =
     903           2 :                     GML_ExtractSrsNameFromGeometry(papsGeometry, osWork, false);
     904             :                 // if (pszSRSName != NULL)
     905             :                 //     m_bCanUseGlobalSRSName = FALSE;
     906           2 :                 poClass->MergeSRSName(pszSRSName);
     907             : 
     908             :                 // Merge geometry type into layer.
     909           2 :                 if (poClass->GetFeatureCount() == 1 && eGType == wkbUnknown)
     910           2 :                     eGType = wkbNone;
     911             : 
     912           4 :                 poClass->GetGeometryProperty(0)->SetType(
     913             :                     OGRMergeGeometryTypesEx(
     914           2 :                         eGType, poGeometry->getGeometryType(), TRUE));
     915             : 
     916             :                 // merge extents.
     917           2 :                 poGeometry->getEnvelope(&sEnvelope);
     918           2 :                 delete poGeometry;
     919           2 :                 double dfXMin = 0.0;
     920           2 :                 double dfXMax = 0.0;
     921           2 :                 double dfYMin = 0.0;
     922           2 :                 double dfYMax = 0.0;
     923           2 :                 if (poClass->GetExtents(&dfXMin, &dfXMax, &dfYMin, &dfYMax))
     924             :                 {
     925           0 :                     dfXMin = std::min(dfXMin, sEnvelope.MinX);
     926           0 :                     dfXMax = std::max(dfXMax, sEnvelope.MaxX);
     927           0 :                     dfYMin = std::min(dfYMin, sEnvelope.MinY);
     928           0 :                     dfYMax = std::max(dfYMax, sEnvelope.MaxY);
     929             :                 }
     930             :                 else
     931             :                 {
     932           2 :                     dfXMin = sEnvelope.MinX;
     933           2 :                     dfXMax = sEnvelope.MaxX;
     934           2 :                     dfYMin = sEnvelope.MinY;
     935           2 :                     dfYMax = sEnvelope.MaxY;
     936             :                 }
     937             : 
     938           2 :                 poClass->SetExtents(dfXMin, dfXMax, dfYMin, dfYMax);
     939             :             }
     940             :             else
     941             :             {
     942           5 :                 if (poClass->GetGeometryPropertyCount() == 1 &&
     943           0 :                     poClass->GetGeometryProperty(0)->GetType() ==
     944           5 :                         (int)wkbUnknown &&
     945           0 :                     poClass->GetFeatureCount() == 1)
     946             :                 {
     947           0 :                     poClass->ClearGeometryProperties();
     948             :                 }
     949             :             }
     950             :         }
     951             : 
     952           7 :         delete poFeature;
     953             :     }
     954             : 
     955           5 :     CleanupParser();
     956             : 
     957             :     // Skip empty classes
     958           5 :     int j = 0;
     959          10 :     for (int i = 0, n = m_nClassCount; i < n; i++)
     960             :     {
     961           5 :         if (m_papoClass[i]->GetFeatureCount() > 0)
     962             :         {
     963           5 :             m_papoClass[j++] = m_papoClass[i];
     964           5 :             continue;
     965             :         }
     966             : 
     967           0 :         CPLDebug("NAS", "Skipping empty layer %s.", m_papoClass[i]->GetName());
     968             : 
     969           0 :         delete m_papoClass[i];
     970           0 :         m_papoClass[i] = nullptr;
     971             :     }
     972             : 
     973           5 :     m_nClassCount = j;
     974             : 
     975           5 :     CPLDebug("NAS", "%d remaining classes after prescan.", m_nClassCount);
     976             : 
     977          10 :     for (int i = 0; i < m_nClassCount; i++)
     978             :     {
     979           5 :         CPLDebug("NAS", "%s: " CPL_FRMT_GIB " features.",
     980           5 :                  m_papoClass[i]->GetName(), m_papoClass[i]->GetFeatureCount());
     981             :     }
     982             : 
     983           5 :     return GetClassCount() > 0;
     984             : }
     985             : 
     986             : /************************************************************************/
     987             : /*                            ResetReading()                            */
     988             : /************************************************************************/
     989             : 
     990           6 : void NASReader::ResetReading()
     991             : 
     992             : {
     993           6 :     CleanupParser();
     994           6 :     SetFilteredClassName(nullptr);
     995           6 : }
     996             : 
     997             : /************************************************************************/
     998             : /*                       GetAttributeElementIndex()                     */
     999             : /************************************************************************/
    1000             : 
    1001          24 : int NASReader::GetAttributeElementIndex(const char *pszElement, int nLen,
    1002             :                                         const char *pszAttrKey)
    1003             : 
    1004             : {
    1005          24 :     GMLFeatureClass *poClass = m_poState->m_poFeature->GetClass();
    1006             : 
    1007             :     // Otherwise build the path to this element into a single string
    1008             :     // and compare against known attributes.
    1009          24 :     if (m_poState->m_nPathLength == 0)
    1010             :     {
    1011          24 :         if (pszAttrKey == nullptr)
    1012           0 :             return poClass->GetPropertyIndexBySrcElement(pszElement, nLen);
    1013             :         else
    1014             :         {
    1015          48 :             CPLString osElemPath;
    1016          24 :             int nFullLen = nLen + 1 + static_cast<int>(strlen(pszAttrKey));
    1017          24 :             osElemPath.reserve(nFullLen);
    1018          24 :             osElemPath.assign(pszElement, nLen);
    1019          24 :             osElemPath.append(1, '@');
    1020          24 :             osElemPath.append(pszAttrKey);
    1021          24 :             return poClass->GetPropertyIndexBySrcElement(osElemPath.c_str(),
    1022          24 :                                                          nFullLen);
    1023             :         }
    1024             :     }
    1025             :     else
    1026             :     {
    1027           0 :         int nFullLen = nLen + static_cast<int>(m_poState->osPath.size()) + 1;
    1028           0 :         if (pszAttrKey != nullptr)
    1029           0 :             nFullLen += 1 + static_cast<int>(strlen(pszAttrKey));
    1030             : 
    1031           0 :         CPLString osElemPath;
    1032           0 :         osElemPath.reserve(nFullLen);
    1033           0 :         osElemPath.assign(m_poState->osPath);
    1034           0 :         osElemPath.append(1, '|');
    1035           0 :         osElemPath.append(pszElement, nLen);
    1036           0 :         if (pszAttrKey != nullptr)
    1037             :         {
    1038           0 :             osElemPath.append(1, '@');
    1039           0 :             osElemPath.append(pszAttrKey);
    1040             :         }
    1041           0 :         return poClass->GetPropertyIndexBySrcElement(osElemPath.c_str(),
    1042           0 :                                                      nFullLen);
    1043             :     }
    1044             : 
    1045             :     return -1;
    1046             : }
    1047             : 
    1048             : /************************************************************************/
    1049             : /*                         DealWithAttributes()                         */
    1050             : /************************************************************************/
    1051             : 
    1052         159 : void NASReader::DealWithAttributes(const char *pszName, int nLenName,
    1053             :                                    const Attributes &attrs)
    1054             : 
    1055             : {
    1056         159 :     GMLFeature *poFeature = GetState()->m_poFeature;
    1057         159 :     CPLAssert(poFeature != nullptr);
    1058             : 
    1059         174 :     for (unsigned int idx = 0; idx < attrs.getLength(); ++idx)
    1060             :     {
    1061          30 :         CPLString osAttrKey = transcode(attrs.getQName(idx));
    1062          30 :         CPLString osAttrVal = transcode(attrs.getValue(idx));
    1063             : 
    1064          15 :         int nAttrIndex = 0;
    1065          15 :         const char *pszAttrKeyNoNS = strchr(osAttrKey, ':');
    1066          15 :         if (pszAttrKeyNoNS)
    1067           9 :             pszAttrKeyNoNS++;
    1068             : 
    1069          24 :         if ((pszAttrKeyNoNS &&
    1070           9 :              (nAttrIndex = GetAttributeElementIndex(pszName, nLenName,
    1071          30 :                                                     pszAttrKeyNoNS)) != -1) ||
    1072          15 :             ((nAttrIndex = GetAttributeElementIndex(pszName, nLenName,
    1073             :                                                     osAttrKey)) != -1))
    1074             :         {
    1075           0 :             const char *pszAttrVal = osAttrVal;
    1076           0 :             if (osAttrKey == "xlink:href" ||
    1077           0 :                 (pszAttrKeyNoNS && EQUAL(pszAttrKeyNoNS, "href")))
    1078             :             {
    1079           0 :                 if (STARTS_WITH_CI(pszAttrVal, "urn:adv:oid:"))
    1080           0 :                     pszAttrVal += 12;
    1081           0 :                 else if (STARTS_WITH_CI(
    1082             :                              pszAttrVal,
    1083             :                              "https://registry.gdi-de.org/codelist/"))
    1084           0 :                     pszAttrVal = strrchr(pszAttrVal, '/') + 1;
    1085             :             }
    1086             : 
    1087           0 :             poFeature->SetPropertyDirectly(nAttrIndex, CPLStrdup(pszAttrVal));
    1088           0 :             pszAttrVal = nullptr;
    1089             :         }
    1090             :     }
    1091         159 : }
    1092             : 
    1093             : /************************************************************************/
    1094             : /*                         HugeFileResolver()                           */
    1095             : /*      Returns true for success                                        */
    1096             : /************************************************************************/
    1097             : 
    1098           0 : bool NASReader::HugeFileResolver(const char * /*pszFile */,
    1099             :                                  bool /* bSqliteIsTempFile */,
    1100             :                                  int /* iSqliteCacheMB */)
    1101             : {
    1102           0 :     CPLDebug("NAS", "HugeFileResolver() not currently implemented for NAS.");
    1103           0 :     return false;
    1104             : }
    1105             : 
    1106             : /************************************************************************/
    1107             : /*                         PrescanForTemplate()                         */
    1108             : /*      Returns true for success                                        */
    1109             : /************************************************************************/
    1110             : 
    1111           0 : bool NASReader::PrescanForTemplate(void)
    1112             : 
    1113             : {
    1114           0 :     CPLDebug("NAS", "PrescanForTemplate() not currently implemented for NAS.");
    1115           0 :     return false;
    1116             : }
    1117             : 
    1118             : /************************************************************************/
    1119             : /*                           ResolveXlinks()                            */
    1120             : /*      Returns true for success                                        */
    1121             : /************************************************************************/
    1122             : 
    1123           0 : bool NASReader::ResolveXlinks(const char * /*pszFile */,
    1124             :                               bool * /*pbOutIsTempFile */,
    1125             :                               char ** /*papszSkip */, const bool /*bStrict */)
    1126             : {
    1127           0 :     CPLDebug("NAS", "ResolveXlinks() not currently implemented for NAS.");
    1128           0 :     return false;
    1129             : }
    1130             : 
    1131             : /************************************************************************/
    1132             : /*                       SetFilteredClassName()                         */
    1133             : /************************************************************************/
    1134             : 
    1135          12 : bool NASReader::SetFilteredClassName(const char *pszClassName)
    1136             : {
    1137          12 :     CPLFree(m_pszFilteredClassName);
    1138          12 :     m_pszFilteredClassName = pszClassName ? CPLStrdup(pszClassName) : nullptr;
    1139          12 :     return true;
    1140             : }
    1141             : 
    1142             : /************************************************************************/
    1143             : /*                         ConvertGeometry()                            */
    1144             : /************************************************************************/
    1145             : 
    1146           3 : OGRGeometry *NASReader::ConvertGeometry(OGRGeometry *poGeom)
    1147             : {
    1148             :     // poGeom = OGRGeometryFactory::forceToLineString( poGeom, false );
    1149           3 :     if (poGeom != nullptr)
    1150             :     {
    1151           3 :         if (wkbFlatten(poGeom->getGeometryType()) == wkbMultiLineString)
    1152             :         {
    1153           0 :             poGeom = OGRGeometryFactory::forceTo(poGeom, wkbLineString);
    1154             :         }
    1155             :     }
    1156           3 :     return poGeom;
    1157             : }

Generated by: LCOV version 1.14