LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/nas - nasreader.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 316 470 67.2 %
Date: 2024-11-21 22:18:42 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             :             // coverity[dereference]
     670         117 :             poClassProperty->AnalysePropertyValue(
     671             :                 poFeature->GetProperty(iProperty));
     672             :         }
     673             :         else
     674             :         {
     675           0 :             CPLAssert(false);
     676             :         }
     677             :     }
     678             : }
     679             : 
     680             : /************************************************************************/
     681             : /*                            LoadClasses()                             */
     682             : /************************************************************************/
     683             : 
     684           0 : bool NASReader::LoadClasses(const char *pszFile)
     685             : 
     686             : {
     687             :     // Add logic later to determine reasonable default schema file.
     688           0 :     if (pszFile == nullptr)
     689           0 :         return false;
     690             : 
     691           0 :     CPLDebug("NAS", "Loading classes from %s", pszFile);
     692             : 
     693             :     /* -------------------------------------------------------------------- */
     694             :     /*      Load the raw XML file.                                          */
     695             :     /* -------------------------------------------------------------------- */
     696           0 :     VSILFILE *fp = VSIFOpenL(pszFile, "rb");
     697             : 
     698           0 :     if (fp == nullptr)
     699             :     {
     700           0 :         CPLError(CE_Failure, CPLE_OpenFailed, "NAS: Failed to open file %s.",
     701             :                  pszFile);
     702           0 :         return false;
     703             :     }
     704             : 
     705           0 :     VSIFSeekL(fp, 0, SEEK_END);
     706           0 :     int nLength = static_cast<int>(VSIFTellL(fp));
     707           0 :     VSIFSeekL(fp, 0, SEEK_SET);
     708             : 
     709           0 :     char *pszWholeText = static_cast<char *>(VSIMalloc(nLength + 1));
     710           0 :     if (pszWholeText == nullptr)
     711             :     {
     712           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     713             :                  "NAS: Failed to allocate %d byte buffer for %s,\n"
     714             :                  "is this really a GMLFeatureClassList file?",
     715             :                  nLength, pszFile);
     716           0 :         VSIFCloseL(fp);
     717           0 :         return false;
     718             :     }
     719             : 
     720           0 :     if (VSIFReadL(pszWholeText, nLength, 1, fp) != 1)
     721             :     {
     722           0 :         VSIFree(pszWholeText);
     723           0 :         VSIFCloseL(fp);
     724           0 :         CPLError(CE_Failure, CPLE_AppDefined, "NAS: Read failed on %s.",
     725             :                  pszFile);
     726           0 :         return false;
     727             :     }
     728           0 :     pszWholeText[nLength] = '\0';
     729             : 
     730           0 :     VSIFCloseL(fp);
     731             : 
     732           0 :     if (strstr(pszWholeText, "<GMLFeatureClassList") == nullptr)
     733             :     {
     734           0 :         VSIFree(pszWholeText);
     735           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     736             :                  "NAS: File %s does not contain a GMLFeatureClassList tree.",
     737             :                  pszFile);
     738           0 :         return false;
     739             :     }
     740             : 
     741             :     /* -------------------------------------------------------------------- */
     742             :     /*      Convert to XML parse tree.                                      */
     743             :     /* -------------------------------------------------------------------- */
     744           0 :     CPLXMLTreeCloser psRoot(CPLParseXMLString(pszWholeText));
     745           0 :     VSIFree(pszWholeText);
     746             : 
     747             :     // We assume parser will report errors via CPL.
     748           0 :     if (psRoot.get() == nullptr)
     749           0 :         return false;
     750             : 
     751           0 :     if (psRoot->eType != CXT_Element ||
     752           0 :         !EQUAL(psRoot->pszValue, "GMLFeatureClassList"))
     753             :     {
     754           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     755             :                  "NAS: File %s is not a GMLFeatureClassList document.",
     756             :                  pszFile);
     757           0 :         return false;
     758             :     }
     759             : 
     760             :     /* -------------------------------------------------------------------- */
     761             :     /*      Extract feature classes for all definitions found.              */
     762             :     /* -------------------------------------------------------------------- */
     763           0 :     for (CPLXMLNode *psThis = psRoot->psChild; psThis != nullptr;
     764           0 :          psThis = psThis->psNext)
     765             :     {
     766           0 :         if (psThis->eType == CXT_Element &&
     767           0 :             EQUAL(psThis->pszValue, "GMLFeatureClass"))
     768             :         {
     769           0 :             GMLFeatureClass *poClass = new GMLFeatureClass();
     770             : 
     771           0 :             if (!poClass->InitializeFromXML(psThis))
     772             :             {
     773           0 :                 delete poClass;
     774           0 :                 return false;
     775             :             }
     776             : 
     777           0 :             poClass->SetSchemaLocked(true);
     778             : 
     779           0 :             AddClass(poClass);
     780             :         }
     781             :     }
     782             : 
     783           0 :     SetClassListLocked(true);
     784             : 
     785           0 :     return true;
     786             : }
     787             : 
     788             : /************************************************************************/
     789             : /*                            SaveClasses()                             */
     790             : /************************************************************************/
     791             : 
     792           3 : bool NASReader::SaveClasses(const char *pszFile)
     793             : 
     794             : {
     795             :     // Add logic later to determine reasonable default schema file.
     796           3 :     if (pszFile == nullptr)
     797           0 :         return false;
     798             : 
     799             :     /* -------------------------------------------------------------------- */
     800             :     /*      Create in memory schema tree.                                   */
     801             :     /* -------------------------------------------------------------------- */
     802             :     CPLXMLNode *psRoot =
     803           3 :         CPLCreateXMLNode(nullptr, CXT_Element, "GMLFeatureClassList");
     804             : 
     805           8 :     for (int iClass = 0; iClass < GetClassCount(); iClass++)
     806             :     {
     807           5 :         GMLFeatureClass *poClass = GetClass(iClass);
     808             : 
     809           5 :         CPLAddXMLChild(psRoot, poClass->SerializeToXML());
     810             :     }
     811             : 
     812             :     /* -------------------------------------------------------------------- */
     813             :     /*      Serialize to disk.                                              */
     814             :     /* -------------------------------------------------------------------- */
     815           3 :     char *pszWholeText = CPLSerializeXMLTree(psRoot);
     816             : 
     817           3 :     CPLDestroyXMLNode(psRoot);
     818             : 
     819           3 :     VSILFILE *fp = VSIFOpenL(pszFile, "wb");
     820             : 
     821           3 :     bool bSuccess = true;
     822           3 :     if (fp == nullptr)
     823           0 :         bSuccess = false;
     824           3 :     else if (VSIFWriteL(pszWholeText, strlen(pszWholeText), 1, fp) != 1)
     825             :     {
     826           0 :         VSIFCloseL(fp);
     827           0 :         bSuccess = false;
     828             :     }
     829             :     else
     830             :     {
     831           3 :         if (VSIFWriteL(pszWholeText, strlen(pszWholeText), 1, fp) != 1)
     832           0 :             bSuccess = false;
     833           3 :         VSIFCloseL(fp);
     834             :     }
     835             : 
     836           3 :     CPLFree(pszWholeText);
     837             : 
     838           3 :     return bSuccess;
     839             : }
     840             : 
     841             : /************************************************************************/
     842             : /*                          PrescanForSchema()                          */
     843             : /*                                                                      */
     844             : /*      For now we use a pretty dumb approach of just doing a normal    */
     845             : /*      scan of the whole file, building up the schema information.     */
     846             : /*      Eventually we hope to do a more efficient scan when just        */
     847             : /*      looking for schema information.                                 */
     848             : /************************************************************************/
     849             : 
     850           5 : bool NASReader::PrescanForSchema(bool bGetExtents, bool /*bOnlyDetectSRS*/)
     851             : {
     852           5 :     if (m_pszFilename == nullptr)
     853           0 :         return false;
     854             : 
     855           5 :     CPLDebug("NAS", "Prescanning %s.", m_pszFilename);
     856             : 
     857           5 :     SetClassListLocked(false);
     858             : 
     859           5 :     if (!SetupParser())
     860           0 :         return false;
     861             : 
     862           5 :     std::string osWork;
     863             : 
     864           5 :     GMLFeature *poFeature = nullptr;
     865          12 :     while ((poFeature = NextFeature()) != nullptr)
     866             :     {
     867           7 :         GMLFeatureClass *poClass = poFeature->GetClass();
     868             : 
     869           7 :         if (poClass->GetFeatureCount() == -1)
     870           5 :             poClass->SetFeatureCount(1);
     871             :         else
     872           2 :             poClass->SetFeatureCount(poClass->GetFeatureCount() + 1);
     873             : 
     874           7 :         if (bGetExtents)
     875             :         {
     876           7 :             OGRGeometry *poGeometry = nullptr;
     877             : 
     878             :             const CPLXMLNode *const *papsGeometry =
     879           7 :                 poFeature->GetGeometryList();
     880           7 :             if (papsGeometry[0] != nullptr)
     881             :             {
     882             :                 poGeometry =
     883           2 :                     (OGRGeometry *)OGR_G_CreateFromGMLTree(papsGeometry[0]);
     884           2 :                 poGeometry = ConvertGeometry(poGeometry);
     885             :             }
     886             : 
     887           7 :             if (poGeometry != nullptr)
     888             :             {
     889           2 :                 OGREnvelope sEnvelope;
     890             : 
     891           2 :                 if (poClass->GetGeometryPropertyCount() == 0)
     892           4 :                     poClass->AddGeometryProperty(new GMLGeometryPropertyDefn(
     893           2 :                         "", "", wkbUnknown, -1, true));
     894             : 
     895             :                 OGRwkbGeometryType eGType =
     896             :                     (OGRwkbGeometryType)poClass->GetGeometryProperty(0)
     897           2 :                         ->GetType();
     898             : 
     899             :                 // Merge SRSName into layer.
     900             :                 const char *pszSRSName =
     901           2 :                     GML_ExtractSrsNameFromGeometry(papsGeometry, osWork, false);
     902             :                 // if (pszSRSName != NULL)
     903             :                 //     m_bCanUseGlobalSRSName = FALSE;
     904           2 :                 poClass->MergeSRSName(pszSRSName);
     905             : 
     906             :                 // Merge geometry type into layer.
     907           2 :                 if (poClass->GetFeatureCount() == 1 && eGType == wkbUnknown)
     908           2 :                     eGType = wkbNone;
     909             : 
     910           4 :                 poClass->GetGeometryProperty(0)->SetType(
     911             :                     OGRMergeGeometryTypesEx(
     912           2 :                         eGType, poGeometry->getGeometryType(), TRUE));
     913             : 
     914             :                 // merge extents.
     915           2 :                 poGeometry->getEnvelope(&sEnvelope);
     916           2 :                 delete poGeometry;
     917           2 :                 double dfXMin = 0.0;
     918           2 :                 double dfXMax = 0.0;
     919           2 :                 double dfYMin = 0.0;
     920           2 :                 double dfYMax = 0.0;
     921           2 :                 if (poClass->GetExtents(&dfXMin, &dfXMax, &dfYMin, &dfYMax))
     922             :                 {
     923           0 :                     dfXMin = std::min(dfXMin, sEnvelope.MinX);
     924           0 :                     dfXMax = std::max(dfXMax, sEnvelope.MaxX);
     925           0 :                     dfYMin = std::min(dfYMin, sEnvelope.MinY);
     926           0 :                     dfYMax = std::max(dfYMax, sEnvelope.MaxY);
     927             :                 }
     928             :                 else
     929             :                 {
     930           2 :                     dfXMin = sEnvelope.MinX;
     931           2 :                     dfXMax = sEnvelope.MaxX;
     932           2 :                     dfYMin = sEnvelope.MinY;
     933           2 :                     dfYMax = sEnvelope.MaxY;
     934             :                 }
     935             : 
     936           2 :                 poClass->SetExtents(dfXMin, dfXMax, dfYMin, dfYMax);
     937             :             }
     938             :             else
     939             :             {
     940           5 :                 if (poClass->GetGeometryPropertyCount() == 1 &&
     941           0 :                     poClass->GetGeometryProperty(0)->GetType() ==
     942           5 :                         (int)wkbUnknown &&
     943           0 :                     poClass->GetFeatureCount() == 1)
     944             :                 {
     945           0 :                     poClass->ClearGeometryProperties();
     946             :                 }
     947             :             }
     948             :         }
     949             : 
     950           7 :         delete poFeature;
     951             :     }
     952             : 
     953           5 :     CleanupParser();
     954             : 
     955             :     // Skip empty classes
     956           5 :     int j = 0;
     957          10 :     for (int i = 0, n = m_nClassCount; i < n; i++)
     958             :     {
     959           5 :         if (m_papoClass[i]->GetFeatureCount() > 0)
     960             :         {
     961           5 :             m_papoClass[j++] = m_papoClass[i];
     962           5 :             continue;
     963             :         }
     964             : 
     965           0 :         CPLDebug("NAS", "Skipping empty layer %s.", m_papoClass[i]->GetName());
     966             : 
     967           0 :         delete m_papoClass[i];
     968           0 :         m_papoClass[i] = nullptr;
     969             :     }
     970             : 
     971           5 :     m_nClassCount = j;
     972             : 
     973           5 :     CPLDebug("NAS", "%d remaining classes after prescan.", m_nClassCount);
     974             : 
     975          10 :     for (int i = 0; i < m_nClassCount; i++)
     976             :     {
     977           5 :         CPLDebug("NAS", "%s: " CPL_FRMT_GIB " features.",
     978           5 :                  m_papoClass[i]->GetName(), m_papoClass[i]->GetFeatureCount());
     979             :     }
     980             : 
     981           5 :     return GetClassCount() > 0;
     982             : }
     983             : 
     984             : /************************************************************************/
     985             : /*                            ResetReading()                            */
     986             : /************************************************************************/
     987             : 
     988           6 : void NASReader::ResetReading()
     989             : 
     990             : {
     991           6 :     CleanupParser();
     992           6 :     SetFilteredClassName(nullptr);
     993           6 : }
     994             : 
     995             : /************************************************************************/
     996             : /*                       GetAttributeElementIndex()                     */
     997             : /************************************************************************/
     998             : 
     999          24 : int NASReader::GetAttributeElementIndex(const char *pszElement, int nLen,
    1000             :                                         const char *pszAttrKey)
    1001             : 
    1002             : {
    1003          24 :     GMLFeatureClass *poClass = m_poState->m_poFeature->GetClass();
    1004             : 
    1005             :     // Otherwise build the path to this element into a single string
    1006             :     // and compare against known attributes.
    1007          24 :     if (m_poState->m_nPathLength == 0)
    1008             :     {
    1009          24 :         if (pszAttrKey == nullptr)
    1010           0 :             return poClass->GetPropertyIndexBySrcElement(pszElement, nLen);
    1011             :         else
    1012             :         {
    1013          48 :             CPLString osElemPath;
    1014          24 :             int nFullLen = nLen + 1 + static_cast<int>(strlen(pszAttrKey));
    1015          24 :             osElemPath.reserve(nFullLen);
    1016          24 :             osElemPath.assign(pszElement, nLen);
    1017          24 :             osElemPath.append(1, '@');
    1018          24 :             osElemPath.append(pszAttrKey);
    1019          24 :             return poClass->GetPropertyIndexBySrcElement(osElemPath.c_str(),
    1020          24 :                                                          nFullLen);
    1021             :         }
    1022             :     }
    1023             :     else
    1024             :     {
    1025           0 :         int nFullLen = nLen + static_cast<int>(m_poState->osPath.size()) + 1;
    1026           0 :         if (pszAttrKey != nullptr)
    1027           0 :             nFullLen += 1 + static_cast<int>(strlen(pszAttrKey));
    1028             : 
    1029           0 :         CPLString osElemPath;
    1030           0 :         osElemPath.reserve(nFullLen);
    1031           0 :         osElemPath.assign(m_poState->osPath);
    1032           0 :         osElemPath.append(1, '|');
    1033           0 :         osElemPath.append(pszElement, nLen);
    1034           0 :         if (pszAttrKey != nullptr)
    1035             :         {
    1036           0 :             osElemPath.append(1, '@');
    1037           0 :             osElemPath.append(pszAttrKey);
    1038             :         }
    1039           0 :         return poClass->GetPropertyIndexBySrcElement(osElemPath.c_str(),
    1040           0 :                                                      nFullLen);
    1041             :     }
    1042             : 
    1043             :     return -1;
    1044             : }
    1045             : 
    1046             : /************************************************************************/
    1047             : /*                         DealWithAttributes()                         */
    1048             : /************************************************************************/
    1049             : 
    1050         159 : void NASReader::DealWithAttributes(const char *pszName, int nLenName,
    1051             :                                    const Attributes &attrs)
    1052             : 
    1053             : {
    1054         159 :     GMLFeature *poFeature = GetState()->m_poFeature;
    1055         159 :     CPLAssert(poFeature != nullptr);
    1056             : 
    1057         174 :     for (unsigned int idx = 0; idx < attrs.getLength(); ++idx)
    1058             :     {
    1059          30 :         CPLString osAttrKey = transcode(attrs.getQName(idx));
    1060          30 :         CPLString osAttrVal = transcode(attrs.getValue(idx));
    1061             : 
    1062          15 :         int nAttrIndex = 0;
    1063          15 :         const char *pszAttrKeyNoNS = strchr(osAttrKey, ':');
    1064          15 :         if (pszAttrKeyNoNS)
    1065           9 :             pszAttrKeyNoNS++;
    1066             : 
    1067          24 :         if ((pszAttrKeyNoNS &&
    1068           9 :              (nAttrIndex = GetAttributeElementIndex(pszName, nLenName,
    1069          30 :                                                     pszAttrKeyNoNS)) != -1) ||
    1070          15 :             ((nAttrIndex = GetAttributeElementIndex(pszName, nLenName,
    1071             :                                                     osAttrKey)) != -1))
    1072             :         {
    1073           0 :             const char *pszAttrVal = osAttrVal;
    1074           0 :             if (osAttrKey == "xlink:href" ||
    1075           0 :                 (pszAttrKeyNoNS && EQUAL(pszAttrKeyNoNS, "href")))
    1076             :             {
    1077           0 :                 if (STARTS_WITH_CI(pszAttrVal, "urn:adv:oid:"))
    1078           0 :                     pszAttrVal += 12;
    1079           0 :                 else if (STARTS_WITH_CI(
    1080             :                              pszAttrVal,
    1081             :                              "https://registry.gdi-de.org/codelist/"))
    1082           0 :                     pszAttrVal = strrchr(pszAttrVal, '/') + 1;
    1083             :             }
    1084             : 
    1085           0 :             poFeature->SetPropertyDirectly(nAttrIndex, CPLStrdup(pszAttrVal));
    1086           0 :             pszAttrVal = nullptr;
    1087             :         }
    1088             :     }
    1089         159 : }
    1090             : 
    1091             : /************************************************************************/
    1092             : /*                         HugeFileResolver()                           */
    1093             : /*      Returns true for success                                        */
    1094             : /************************************************************************/
    1095             : 
    1096           0 : bool NASReader::HugeFileResolver(const char * /*pszFile */,
    1097             :                                  bool /* bSqliteIsTempFile */,
    1098             :                                  int /* iSqliteCacheMB */)
    1099             : {
    1100           0 :     CPLDebug("NAS", "HugeFileResolver() not currently implemented for NAS.");
    1101           0 :     return false;
    1102             : }
    1103             : 
    1104             : /************************************************************************/
    1105             : /*                         PrescanForTemplate()                         */
    1106             : /*      Returns true for success                                        */
    1107             : /************************************************************************/
    1108             : 
    1109           0 : bool NASReader::PrescanForTemplate(void)
    1110             : 
    1111             : {
    1112           0 :     CPLDebug("NAS", "PrescanForTemplate() not currently implemented for NAS.");
    1113           0 :     return false;
    1114             : }
    1115             : 
    1116             : /************************************************************************/
    1117             : /*                           ResolveXlinks()                            */
    1118             : /*      Returns true for success                                        */
    1119             : /************************************************************************/
    1120             : 
    1121           0 : bool NASReader::ResolveXlinks(const char * /*pszFile */,
    1122             :                               bool * /*pbOutIsTempFile */,
    1123             :                               char ** /*papszSkip */, const bool /*bStrict */)
    1124             : {
    1125           0 :     CPLDebug("NAS", "ResolveXlinks() not currently implemented for NAS.");
    1126           0 :     return false;
    1127             : }
    1128             : 
    1129             : /************************************************************************/
    1130             : /*                       SetFilteredClassName()                         */
    1131             : /************************************************************************/
    1132             : 
    1133          12 : bool NASReader::SetFilteredClassName(const char *pszClassName)
    1134             : {
    1135          12 :     CPLFree(m_pszFilteredClassName);
    1136          12 :     m_pszFilteredClassName = pszClassName ? CPLStrdup(pszClassName) : nullptr;
    1137          12 :     return true;
    1138             : }
    1139             : 
    1140             : /************************************************************************/
    1141             : /*                         ConvertGeometry()                            */
    1142             : /************************************************************************/
    1143             : 
    1144           3 : OGRGeometry *NASReader::ConvertGeometry(OGRGeometry *poGeom)
    1145             : {
    1146             :     // poGeom = OGRGeometryFactory::forceToLineString( poGeom, false );
    1147           3 :     if (poGeom != nullptr)
    1148             :     {
    1149           3 :         if (wkbFlatten(poGeom->getGeometryType()) == wkbMultiLineString)
    1150             :         {
    1151           0 :             poGeom = OGRGeometryFactory::forceTo(poGeom, wkbLineString);
    1152             :         }
    1153             :     }
    1154           3 :     return poGeom;
    1155             : }

Generated by: LCOV version 1.14