LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/gml - gmlhandler.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 804 913 88.1 %
Date: 2024-05-07 17:03:27 Functions: 47 51 92.2 %

          Line data    Source code
       1             : /**********************************************************************
       2             :  *
       3             :  * Project:  GML Reader
       4             :  * Purpose:  Implementation of GMLHandler class.
       5             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       6             :  *
       7             :  **********************************************************************
       8             :  * Copyright (c) 2002, Frank Warmerdam
       9             :  * Copyright (c) 2008-2014, Even Rouault <even dot rouault at spatialys.com>
      10             :  *
      11             :  * Permission is hereby granted, free of charge, to any person obtaining a
      12             :  * copy of this software and associated documentation files (the "Software"),
      13             :  * to deal in the Software without restriction, including without limitation
      14             :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      15             :  * and/or sell copies of the Software, and to permit persons to whom the
      16             :  * Software is furnished to do so, subject to the following conditions:
      17             :  *
      18             :  * The above copyright notice and this permission notice shall be included
      19             :  * in all copies or substantial portions of the Software.
      20             :  *
      21             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      22             :  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      23             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
      24             :  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      25             :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      26             :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      27             :  * DEALINGS IN THE SOFTWARE.
      28             :  ****************************************************************************/
      29             : 
      30             : #include "cpl_port.h"
      31             : #include "gmlreader.h"
      32             : #include "gmlreaderp.h"
      33             : 
      34             : #include <climits>
      35             : #include <cstddef>
      36             : #include <cstdlib>
      37             : #include <cstring>
      38             : #include <memory>
      39             : #include <string>
      40             : #include <vector>
      41             : 
      42             : #include "cpl_conv.h"
      43             : #include "cpl_error.h"
      44             : #include "cpl_hash_set.h"
      45             : #include "cpl_minixml.h"
      46             : #include "cpl_string.h"
      47             : #include "cpl_vsi.h"
      48             : #ifdef HAVE_EXPAT
      49             : #include "expat.h"
      50             : #include "expat_external.h"
      51             : #endif
      52             : #include "ogr_core.h"
      53             : #ifdef HAVE_XERCES
      54             : #include "ogr_xerces.h"
      55             : #endif
      56             : 
      57             : #ifdef HAVE_XERCES
      58             : 
      59             : /************************************************************************/
      60             : /*                        GMLXercesHandler()                            */
      61             : /************************************************************************/
      62             : 
      63           1 : GMLXercesHandler::GMLXercesHandler(GMLReader *poReader)
      64           1 :     : GMLHandler(poReader), m_nEntityCounter(0)
      65             : {
      66           1 : }
      67             : 
      68             : /************************************************************************/
      69             : /*                    GMLXercesHandlerDealWithError()                   */
      70             : /************************************************************************/
      71             : 
      72          42 : static void GMLXercesHandlerDealWithError(OGRErr eErr)
      73             : {
      74          42 :     if (eErr == OGRERR_NOT_ENOUGH_MEMORY)
      75             :     {
      76           0 :         throw SAXNotSupportedException("Out of memory");
      77             :     }
      78          42 :     else if (eErr != OGRERR_NONE)
      79             :     {
      80           0 :         throw SAXNotSupportedException("Other error during parsing");
      81             :     }
      82          42 : }
      83             : 
      84             : /************************************************************************/
      85             : /*                            startElement()                            */
      86             : /************************************************************************/
      87             : 
      88          17 : void GMLXercesHandler::startElement(const XMLCh *const /*uri*/,
      89             :                                     const XMLCh *const localname,
      90             :                                     const XMLCh *const /*qname*/,
      91             :                                     const Attributes &attrs)
      92             : {
      93          17 :     m_nEntityCounter = 0;
      94             : 
      95          17 :     transcode(localname, m_osElement);
      96             : 
      97          17 :     GMLXercesHandlerDealWithError(GMLHandler::startElement(
      98          17 :         m_osElement.c_str(), static_cast<int>(m_osElement.size()),
      99             :         const_cast<Attributes *>(&attrs)));
     100          17 : }
     101             : 
     102             : /************************************************************************/
     103             : /*                             endElement()                             */
     104             : /************************************************************************/
     105          15 : void GMLXercesHandler::endElement(const XMLCh *const /*uri*/,
     106             :                                   const XMLCh *const /*localname*/,
     107             :                                   const XMLCh *const /*qname */)
     108             : {
     109          15 :     m_nEntityCounter = 0;
     110             : 
     111          15 :     GMLXercesHandlerDealWithError(GMLHandler::endElement());
     112          15 : }
     113             : 
     114             : /************************************************************************/
     115             : /*                             characters()                             */
     116             : /************************************************************************/
     117             : 
     118          10 : void GMLXercesHandler::characters(const XMLCh *const chars_in,
     119             :                                   const XMLSize_t length)
     120             : 
     121             : {
     122          10 :     transcode(chars_in, m_osCharacters, static_cast<int>(length));
     123          10 :     GMLXercesHandlerDealWithError(GMLHandler::dataHandler(
     124          10 :         m_osCharacters.c_str(), static_cast<int>(m_osCharacters.size())));
     125          10 : }
     126             : 
     127             : /************************************************************************/
     128             : /*                             fatalError()                             */
     129             : /************************************************************************/
     130             : 
     131           0 : void GMLXercesHandler::fatalError(const SAXParseException &exception)
     132             : 
     133             : {
     134           0 :     CPLString osMsg;
     135           0 :     transcode(exception.getMessage(), osMsg);
     136           0 :     CPLError(CE_Failure, CPLE_AppDefined,
     137             :              "XML Parsing Error: %s at line %d, column %d\n", osMsg.c_str(),
     138           0 :              static_cast<int>(exception.getLineNumber()),
     139           0 :              static_cast<int>(exception.getColumnNumber()));
     140           0 : }
     141             : 
     142             : /************************************************************************/
     143             : /*                             startEntity()                            */
     144             : /************************************************************************/
     145             : 
     146           0 : void GMLXercesHandler::startEntity(const XMLCh *const /* name */)
     147             : {
     148           0 :     m_nEntityCounter++;
     149           0 :     if (m_nEntityCounter > 1000 && !m_poReader->HasStoppedParsing())
     150             :     {
     151             :         throw SAXNotSupportedException(
     152           0 :             "File probably corrupted (million laugh pattern)");
     153             :     }
     154           0 : }
     155             : 
     156             : /************************************************************************/
     157             : /*                               GetFID()                               */
     158             : /************************************************************************/
     159             : 
     160           1 : const char *GMLXercesHandler::GetFID(void *attr)
     161             : {
     162           1 :     const Attributes *attrs = static_cast<const Attributes *>(attr);
     163           1 :     const XMLCh achFID[] = {'f', 'i', 'd', '\0'};
     164           1 :     int nFIDIndex = attrs->getIndex(achFID);
     165           1 :     if (nFIDIndex != -1)
     166             :     {
     167           1 :         transcode(attrs->getValue(nFIDIndex), m_osFID);
     168           1 :         return m_osFID.c_str();
     169             :     }
     170             :     else
     171             :     {
     172           0 :         const XMLCh achGMLID[] = {'g', 'm', 'l', ':', 'i', 'd', '\0'};
     173           0 :         nFIDIndex = attrs->getIndex(achGMLID);
     174           0 :         if (nFIDIndex != -1)
     175             :         {
     176           0 :             transcode(attrs->getValue(nFIDIndex), m_osFID);
     177           0 :             return m_osFID.c_str();
     178             :         }
     179             :     }
     180             : 
     181           0 :     m_osFID.resize(0);
     182           0 :     return nullptr;
     183             : }
     184             : 
     185             : /************************************************************************/
     186             : /*                        AddAttributes()                               */
     187             : /************************************************************************/
     188             : 
     189           2 : CPLXMLNode *GMLXercesHandler::AddAttributes(CPLXMLNode *psNode, void *attr)
     190             : {
     191           2 :     const Attributes *attrs = static_cast<const Attributes *>(attr);
     192             : 
     193           2 :     CPLXMLNode *psLastChild = nullptr;
     194             : 
     195           5 :     for (unsigned int i = 0; i < attrs->getLength(); i++)
     196             :     {
     197           3 :         transcode(attrs->getQName(i), m_osAttrName);
     198           3 :         transcode(attrs->getValue(i), m_osAttrValue);
     199             : 
     200             :         CPLXMLNode *psChild =
     201           3 :             CPLCreateXMLNode(nullptr, CXT_Attribute, m_osAttrName.c_str());
     202           3 :         CPLCreateXMLNode(psChild, CXT_Text, m_osAttrValue.c_str());
     203             : 
     204           3 :         if (psLastChild == nullptr)
     205           1 :             psNode->psChild = psChild;
     206             :         else
     207           2 :             psLastChild->psNext = psChild;
     208           3 :         psLastChild = psChild;
     209             :     }
     210             : 
     211           2 :     return psLastChild;
     212             : }
     213             : 
     214             : /************************************************************************/
     215             : /*                    GetAttributeValue()                               */
     216             : /************************************************************************/
     217             : 
     218           8 : char *GMLXercesHandler::GetAttributeValue(void *attr,
     219             :                                           const char *pszAttributeName)
     220             : {
     221           8 :     const Attributes *attrs = static_cast<const Attributes *>(attr);
     222           8 :     for (unsigned int i = 0; i < attrs->getLength(); i++)
     223             :     {
     224           0 :         transcode(attrs->getQName(i), m_osAttrName);
     225           0 :         if (m_osAttrName == pszAttributeName)
     226             :         {
     227           0 :             transcode(attrs->getValue(i), m_osAttrValue);
     228           0 :             return CPLStrdup(m_osAttrValue);
     229             :         }
     230             :     }
     231           8 :     return nullptr;
     232             : }
     233             : 
     234             : /************************************************************************/
     235             : /*                    GetAttributeByIdx()                               */
     236             : /************************************************************************/
     237             : 
     238           9 : char *GMLXercesHandler::GetAttributeByIdx(void *attr, unsigned int idx,
     239             :                                           char **ppszKey)
     240             : {
     241           9 :     const Attributes *attrs = static_cast<const Attributes *>(attr);
     242           9 :     if (idx >= attrs->getLength())
     243             :     {
     244           9 :         *ppszKey = nullptr;
     245           9 :         return nullptr;
     246             :     }
     247           0 :     transcode(attrs->getQName(idx), m_osAttrName);
     248           0 :     transcode(attrs->getValue(idx), m_osAttrValue);
     249             : 
     250           0 :     *ppszKey = CPLStrdup(m_osAttrName);
     251           0 :     return CPLStrdup(m_osAttrValue);
     252             : }
     253             : 
     254             : #endif
     255             : 
     256             : #ifdef HAVE_EXPAT
     257             : 
     258             : /************************************************************************/
     259             : /*                            GMLExpatHandler()                         */
     260             : /************************************************************************/
     261             : 
     262         503 : GMLExpatHandler::GMLExpatHandler(GMLReader *poReader, XML_Parser oParser)
     263             :     : GMLHandler(poReader), m_oParser(oParser), m_bStopParsing(false),
     264         503 :       m_nDataHandlerCounter(0)
     265             : {
     266         503 : }
     267             : 
     268             : /************************************************************************/
     269             : /*                  GMLExpatHandler::DealWithError()                    */
     270             : /************************************************************************/
     271             : 
     272      201196 : void GMLExpatHandler::DealWithError(OGRErr eErr)
     273             : {
     274      201196 :     if (eErr != OGRERR_NONE)
     275             :     {
     276           2 :         m_bStopParsing = true;
     277           2 :         XML_StopParser(m_oParser, XML_FALSE);
     278           2 :         if (eErr == OGRERR_NOT_ENOUGH_MEMORY)
     279           0 :             CPLError(CE_Failure, CPLE_OutOfMemory, "Out of memory");
     280             :     }
     281      201196 : }
     282             : 
     283             : /************************************************************************/
     284             : /*                           startElementCbk()                          */
     285             : /************************************************************************/
     286             : 
     287       45007 : void XMLCALL GMLExpatHandler::startElementCbk(void *pUserData,
     288             :                                               const char *pszName,
     289             :                                               const char **ppszAttr)
     290             : 
     291             : {
     292       45007 :     GMLExpatHandler *pThis = static_cast<GMLExpatHandler *>(pUserData);
     293       45007 :     if (pThis->m_bStopParsing)
     294           0 :         return;
     295             : 
     296       45007 :     const char *pszIter = pszName;
     297       45007 :     char ch = '\0';
     298      599020 :     while ((ch = *pszIter) != '\0')
     299             :     {
     300      554013 :         if (ch == ':')
     301       36563 :             pszName = pszIter + 1;
     302      554013 :         pszIter++;
     303             :     }
     304             : 
     305       45007 :     pThis->DealWithError(pThis->GMLHandler::startElement(
     306       45007 :         pszName, static_cast<int>(pszIter - pszName), ppszAttr));
     307             : }
     308             : 
     309             : /************************************************************************/
     310             : /*                            endElementCbk()                           */
     311             : /************************************************************************/
     312       44914 : void XMLCALL GMLExpatHandler::endElementCbk(void *pUserData,
     313             :                                             const char * /* pszName */)
     314             : {
     315       44914 :     GMLExpatHandler *pThis = static_cast<GMLExpatHandler *>(pUserData);
     316       44914 :     if (pThis->m_bStopParsing)
     317           1 :         return;
     318             : 
     319       44913 :     pThis->DealWithError(pThis->GMLHandler::endElement());
     320             : }
     321             : 
     322             : /************************************************************************/
     323             : /*                            dataHandlerCbk()                          */
     324             : /************************************************************************/
     325             : 
     326      111276 : void XMLCALL GMLExpatHandler::dataHandlerCbk(void *pUserData, const char *data,
     327             :                                              int nLen)
     328             : 
     329             : {
     330      111276 :     GMLExpatHandler *pThis = static_cast<GMLExpatHandler *>(pUserData);
     331      111276 :     if (pThis->m_bStopParsing)
     332           0 :         return;
     333             : 
     334      111276 :     pThis->m_nDataHandlerCounter++;
     335             :     // The size of the buffer that is fetched and that Expat parses is
     336             :     // PARSER_BUF_SIZE bytes. If the dataHandlerCbk() callback is called
     337             :     // more than PARSER_BUF_SIZE times, this means that one byte in the
     338             :     // file expands to more XML text fragments, which is the sign of a
     339             :     // likely abuse of <!ENTITY>
     340             :     // Note: the counter is zeroed by ResetDataHandlerCounter() before each
     341             :     // new XML parsing.
     342      111276 :     if (pThis->m_nDataHandlerCounter >= PARSER_BUF_SIZE)
     343             :     {
     344           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     345             :                  "File probably corrupted (million laugh pattern)");
     346           0 :         pThis->m_bStopParsing = true;
     347           0 :         XML_StopParser(pThis->m_oParser, XML_FALSE);
     348           0 :         return;
     349             :     }
     350             : 
     351      111276 :     pThis->DealWithError(pThis->GMLHandler::dataHandler(data, nLen));
     352             : }
     353             : 
     354             : /************************************************************************/
     355             : /*                               GetFID()                               */
     356             : /************************************************************************/
     357             : 
     358        2587 : const char *GMLExpatHandler::GetFID(void *attr)
     359             : {
     360        2587 :     const char **papszIter = (const char **)attr;
     361        2605 :     while (*papszIter)
     362             :     {
     363        1604 :         if (strcmp(*papszIter, "fid") == 0 || strcmp(*papszIter, "gml:id") == 0)
     364             :         {
     365        1586 :             return papszIter[1];
     366             :         }
     367          18 :         papszIter += 2;
     368             :     }
     369        1001 :     return nullptr;
     370             : }
     371             : 
     372             : /************************************************************************/
     373             : /*                        AddAttributes()                               */
     374             : /************************************************************************/
     375             : 
     376       22004 : CPLXMLNode *GMLExpatHandler::AddAttributes(CPLXMLNode *psNode, void *attr)
     377             : {
     378       22004 :     const char **papszIter = static_cast<const char **>(attr);
     379             : 
     380       22004 :     CPLXMLNode *psLastChild = nullptr;
     381             : 
     382       31504 :     while (*papszIter)
     383             :     {
     384             :         CPLXMLNode *psChild =
     385        9500 :             CPLCreateXMLNode(nullptr, CXT_Attribute, papszIter[0]);
     386        9500 :         CPLCreateXMLNode(psChild, CXT_Text, papszIter[1]);
     387             : 
     388        9500 :         if (psLastChild == nullptr)
     389        8702 :             psNode->psChild = psChild;
     390             :         else
     391         798 :             psLastChild->psNext = psChild;
     392        9500 :         psLastChild = psChild;
     393             : 
     394        9500 :         papszIter += 2;
     395             :     }
     396             : 
     397       22004 :     return psLastChild;
     398             : }
     399             : 
     400             : /************************************************************************/
     401             : /*                    GetAttributeValue()                               */
     402             : /************************************************************************/
     403             : 
     404        8823 : char *GMLExpatHandler::GetAttributeValue(void *attr,
     405             :                                          const char *pszAttributeName)
     406             : {
     407        8823 :     const char **papszIter = (const char **)attr;
     408        9341 :     while (*papszIter)
     409             :     {
     410         734 :         if (strcmp(*papszIter, pszAttributeName) == 0)
     411             :         {
     412         216 :             return CPLStrdup(papszIter[1]);
     413             :         }
     414         518 :         papszIter += 2;
     415             :     }
     416        8607 :     return nullptr;
     417             : }
     418             : 
     419             : /************************************************************************/
     420             : /*                    GetAttributeByIdx()                               */
     421             : /************************************************************************/
     422             : 
     423             : // CAUTION: should be called with increasing idx starting from 0 and
     424             : // no attempt to read beyond end of list.
     425       11344 : char *GMLExpatHandler::GetAttributeByIdx(void *attr, unsigned int idx,
     426             :                                          char **ppszKey)
     427             : {
     428       11344 :     const char **papszIter = (const char **)attr;
     429       11344 :     if (papszIter[2 * idx] == nullptr)
     430             :     {
     431       10807 :         *ppszKey = nullptr;
     432       10807 :         return nullptr;
     433             :     }
     434         537 :     *ppszKey = CPLStrdup(papszIter[2 * idx]);
     435         537 :     return CPLStrdup(papszIter[2 * idx + 1]);
     436             : }
     437             : 
     438             : #endif
     439             : 
     440             : static const char *const apszGMLGeometryElements[] = {
     441             :     "BoundingBox", /* ows:BoundingBox */
     442             :     "CompositeCurve",
     443             :     "CompositeSurface",
     444             :     "Curve",
     445             :     "GeometryCollection", /* OGR < 1.8.0 bug... */
     446             :     "LineString",
     447             :     "MultiCurve",
     448             :     "MultiGeometry",
     449             :     "MultiLineString",
     450             :     "MultiPoint",
     451             :     "MultiPolygon",
     452             :     "MultiSurface",
     453             :     "Point",
     454             :     "Polygon",
     455             :     "PolygonPatch",
     456             :     "PolyhedralSurface",
     457             :     "SimplePolygon",    /* GML 3.3 compact encoding */
     458             :     "SimpleRectangle",  /* GML 3.3 compact encoding */
     459             :     "SimpleTriangle",   /* GML 3.3 compact encoding */
     460             :     "SimpleMultiPoint", /* GML 3.3 compact encoding */
     461             :     "Solid",
     462             :     "Surface",
     463             :     "Tin",
     464             :     "TopoCurve",
     465             :     "TopoSurface",
     466             :     "Triangle",
     467             :     "TriangulatedSurface"};
     468             : 
     469             : #define GML_GEOMETRY_TYPE_COUNT                                                \
     470             :     static_cast<int>(sizeof(apszGMLGeometryElements) /                         \
     471             :                      sizeof(apszGMLGeometryElements[0]))
     472             : 
     473          94 : bool OGRGMLIsGeometryElement(const char *pszElement)
     474             : {
     475        2618 :     for (const auto &pszGMLElement : apszGMLGeometryElements)
     476             :     {
     477        2525 :         if (strcmp(pszElement, pszGMLElement) == 0)
     478           1 :             return true;
     479             :     }
     480          93 :     return false;
     481             : }
     482             : 
     483             : struct _GeometryNamesStruct
     484             : {
     485             :     unsigned long nHash;
     486             :     const char *pszName;
     487             : };
     488             : 
     489             : /************************************************************************/
     490             : /*                    GMLHandlerSortGeometryElements()                  */
     491             : /************************************************************************/
     492             : 
     493       49392 : static int GMLHandlerSortGeometryElements(const void *_pA, const void *_pB)
     494             : {
     495       49392 :     GeometryNamesStruct *pA = (GeometryNamesStruct *)_pA;
     496       49392 :     GeometryNamesStruct *pB = (GeometryNamesStruct *)_pB;
     497       49392 :     CPLAssert(pA->nHash != pB->nHash);
     498       49392 :     if (pA->nHash < pB->nHash)
     499       21168 :         return -1;
     500             :     else
     501       28224 :         return 1;
     502             : }
     503             : 
     504             : /************************************************************************/
     505             : /*                            GMLHandler()                              */
     506             : /************************************************************************/
     507             : 
     508         504 : GMLHandler::GMLHandler(GMLReader *poReader)
     509             :     : m_pszCurField(nullptr), m_nCurFieldAlloc(0), m_nCurFieldLen(0),
     510             :       m_bInCurField(false), m_nAttributeIndex(-1), m_nAttributeDepth(0),
     511             :       m_pszGeometry(nullptr), m_nGeomAlloc(0), m_nGeomLen(0),
     512             :       m_nGeometryDepth(0), m_bAlreadyFoundGeometry(false),
     513             :       m_nGeometryPropertyIndex(0), m_nDepth(0), m_nDepthFeature(0),
     514             :       m_inBoundedByDepth(0), m_pszCityGMLGenericAttrName(nullptr),
     515             :       m_inCityGMLGenericAttrDepth(0), m_bReportHref(false), m_pszHref(nullptr),
     516             :       m_pszUom(nullptr), m_pszValue(nullptr), m_pszKieli(nullptr),
     517             :       pasGeometryNames(static_cast<GeometryNamesStruct *>(
     518        1008 :           CPLMalloc(GML_GEOMETRY_TYPE_COUNT * sizeof(GeometryNamesStruct)))),
     519             :       m_nSRSDimensionIfMissing(
     520         504 :           atoi(CPLGetConfigOption("GML_SRS_DIMENSION_IF_MISSING", "0"))),
     521        1008 :       m_poReader(poReader), eAppSchemaType(APPSCHEMA_GENERIC), nStackDepth(0)
     522             : {
     523       14112 :     for (int i = 0; i < GML_GEOMETRY_TYPE_COUNT; i++)
     524             :     {
     525       13608 :         pasGeometryNames[i].pszName = apszGMLGeometryElements[i];
     526       27216 :         pasGeometryNames[i].nHash =
     527       13608 :             CPLHashSetHashStr(pasGeometryNames[i].pszName);
     528             :     }
     529         504 :     qsort(pasGeometryNames, GML_GEOMETRY_TYPE_COUNT,
     530             :           sizeof(GeometryNamesStruct), GMLHandlerSortGeometryElements);
     531             : 
     532         504 :     stateStack[0] = STATE_TOP;
     533         504 : }
     534             : 
     535             : /************************************************************************/
     536             : /*                            ~GMLHandler()                             */
     537             : /************************************************************************/
     538             : 
     539         504 : GMLHandler::~GMLHandler()
     540             : 
     541             : {
     542         504 :     if (apsXMLNode.size() >= 2 && apsXMLNode[1].psNode != nullptr)
     543           2 :         CPLDestroyXMLNode(apsXMLNode[1].psNode);
     544             : 
     545         504 :     CPLFree(m_pszCurField);
     546         504 :     CPLFree(m_pszGeometry);
     547         504 :     CPLFree(m_pszCityGMLGenericAttrName);
     548         504 :     CPLFree(m_pszHref);
     549         504 :     CPLFree(m_pszUom);
     550         504 :     CPLFree(m_pszValue);
     551         504 :     CPLFree(m_pszKieli);
     552         504 :     CPLFree(pasGeometryNames);
     553         504 : }
     554             : 
     555             : /************************************************************************/
     556             : /*                             startElement()                           */
     557             : /************************************************************************/
     558             : 
     559       45024 : OGRErr GMLHandler::startElement(const char *pszName, int nLenName, void *attr)
     560             : {
     561             :     OGRErr eRet;
     562       45024 :     switch (stateStack[nStackDepth])
     563             :     {
     564         502 :         case STATE_TOP:
     565         502 :             eRet = startElementTop(pszName, nLenName, attr);
     566         502 :             break;
     567        6190 :         case STATE_DEFAULT:
     568        6190 :             eRet = startElementDefault(pszName, nLenName, attr);
     569        6190 :             break;
     570       10855 :         case STATE_FEATURE:
     571       10855 :             eRet = startElementFeatureAttribute(pszName, nLenName, attr);
     572       10855 :             break;
     573        2762 :         case STATE_PROPERTY:
     574        2762 :             eRet = startElementFeatureAttribute(pszName, nLenName, attr);
     575        2762 :             break;
     576         165 :         case STATE_FEATUREPROPERTY:
     577         165 :             eRet = startElementFeatureProperty(pszName, nLenName, attr);
     578         165 :             break;
     579       18545 :         case STATE_GEOMETRY:
     580       18545 :             eRet = startElementGeometry(pszName, nLenName, attr);
     581       18545 :             break;
     582        4003 :         case STATE_IGNORED_FEATURE:
     583        4003 :             eRet = OGRERR_NONE;
     584        4003 :             break;
     585         902 :         case STATE_BOUNDED_BY:
     586         902 :             eRet = startElementBoundedBy(pszName, nLenName, attr);
     587         902 :             break;
     588        1058 :         case STATE_BOUNDED_BY_IN_FEATURE:
     589        1058 :             eRet = startElementGeometry(pszName, nLenName, attr);
     590        1058 :             break;
     591          42 :         case STATE_CITYGML_ATTRIBUTE:
     592          42 :             eRet = startElementCityGMLGenericAttr(pszName, nLenName, attr);
     593          42 :             break;
     594           0 :         default:
     595           0 :             eRet = OGRERR_NONE;
     596           0 :             break;
     597             :     }
     598       45024 :     m_nDepth++;
     599       45024 :     if (m_nDepth == 64)
     600             :     {
     601             :         // Avoid performance issues on files like
     602             :         // https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=21737
     603           3 :         if (m_nUnlimitedDepth < 0)
     604             :         {
     605           3 :             m_nUnlimitedDepth = EQUAL(
     606             :                 CPLGetConfigOption("OGR_GML_NESTING_LEVEL", ""), "UNLIMITED");
     607             :         }
     608           3 :         if (!m_nUnlimitedDepth)
     609             :         {
     610           1 :             CPLError(CE_Failure, CPLE_NotSupported,
     611             :                      "Too deep XML nesting level (%d). "
     612             :                      "Set the OGR_GML_NESTING_LEVEL configuration option to "
     613             :                      "UNLIMITED to remove that limitation.",
     614             :                      m_nDepth);
     615           1 :             eRet = OGRERR_FAILURE;
     616             :         }
     617             :     }
     618       45024 :     return eRet;
     619             : }
     620             : 
     621             : /************************************************************************/
     622             : /*                              endElement()                            */
     623             : /************************************************************************/
     624             : 
     625       44928 : OGRErr GMLHandler::endElement()
     626             : {
     627       44928 :     m_nDepth--;
     628       44928 :     switch (stateStack[nStackDepth])
     629             :     {
     630           0 :         case STATE_TOP:
     631           0 :             return OGRERR_NONE;
     632             :             break;
     633        3639 :         case STATE_DEFAULT:
     634        3639 :             return endElementDefault();
     635             :             break;
     636        5070 :         case STATE_FEATURE:
     637        5070 :             return endElementFeature();
     638             :             break;
     639        8398 :         case STATE_PROPERTY:
     640        8398 :             return endElementAttribute();
     641             :             break;
     642         189 :         case STATE_FEATUREPROPERTY:
     643         189 :             return endElementFeatureProperty();
     644             :             break;
     645       20933 :         case STATE_GEOMETRY:
     646       20933 :             return endElementGeometry();
     647             :             break;
     648        4348 :         case STATE_IGNORED_FEATURE:
     649        4348 :             return endElementIgnoredFeature();
     650             :             break;
     651        1137 :         case STATE_BOUNDED_BY:
     652        1137 :             return endElementBoundedBy();
     653             :             break;
     654        1130 :         case STATE_BOUNDED_BY_IN_FEATURE:
     655        1130 :             return endElementBoundedByInFeature();
     656             :             break;
     657          84 :         case STATE_CITYGML_ATTRIBUTE:
     658          84 :             return endElementCityGMLGenericAttr();
     659             :             break;
     660           0 :         default:
     661           0 :             return OGRERR_NONE;
     662             :             break;
     663             :     }
     664             : }
     665             : 
     666             : /************************************************************************/
     667             : /*                              dataHandler()                           */
     668             : /************************************************************************/
     669             : 
     670      111286 : OGRErr GMLHandler::dataHandler(const char *data, int nLen)
     671             : {
     672      111286 :     switch (stateStack[nStackDepth])
     673             :     {
     674           0 :         case STATE_TOP:
     675           0 :             return OGRERR_NONE;
     676             :             break;
     677       17479 :         case STATE_DEFAULT:
     678       17479 :             return OGRERR_NONE;
     679             :             break;
     680       22645 :         case STATE_FEATURE:
     681       22645 :             return OGRERR_NONE;
     682             :             break;
     683       10445 :         case STATE_PROPERTY:
     684       10445 :             return dataHandlerAttribute(data, nLen);
     685             :             break;
     686         625 :         case STATE_FEATUREPROPERTY:
     687         625 :             return OGRERR_NONE;
     688             :             break;
     689       52494 :         case STATE_GEOMETRY:
     690       52494 :             return dataHandlerGeometry(data, nLen);
     691             :             break;
     692        3295 :         case STATE_IGNORED_FEATURE:
     693        3295 :             return OGRERR_NONE;
     694             :             break;
     695        1883 :         case STATE_BOUNDED_BY:
     696        1883 :             return OGRERR_NONE;
     697             :             break;
     698        2282 :         case STATE_BOUNDED_BY_IN_FEATURE:
     699        2282 :             return dataHandlerGeometry(data, nLen);
     700             :             break;
     701         138 :         case STATE_CITYGML_ATTRIBUTE:
     702         138 :             return dataHandlerAttribute(data, nLen);
     703             :             break;
     704           0 :         default:
     705           0 :             return OGRERR_NONE;
     706             :             break;
     707             :     }
     708             : }
     709             : 
     710             : #define PUSH_STATE(val)                                                        \
     711             :     do                                                                         \
     712             :     {                                                                          \
     713             :         nStackDepth++;                                                         \
     714             :         CPLAssert(nStackDepth < STACK_SIZE);                                   \
     715             :         stateStack[nStackDepth] = val;                                         \
     716             :     } while (false)
     717             : #define POP_STATE() nStackDepth--
     718             : 
     719             : /************************************************************************/
     720             : /*                       startElementBoundedBy()                        */
     721             : /************************************************************************/
     722             : 
     723         902 : OGRErr GMLHandler::startElementBoundedBy(const char *pszName, int /*nLenName*/,
     724             :                                          void *attr)
     725             : {
     726         902 :     if (m_nDepth == 2 && strcmp(pszName, "Envelope") == 0)
     727             :     {
     728         125 :         char *pszGlobalSRSName = GetAttributeValue(attr, "srsName");
     729         125 :         m_poReader->SetGlobalSRSName(pszGlobalSRSName);
     730         125 :         CPLFree(pszGlobalSRSName);
     731             : 
     732         125 :         if (m_nSRSDimensionIfMissing == 0)
     733             :         {
     734             :             char *pszGlobalSRSDimension =
     735         121 :                 GetAttributeValue(attr, "srsDimension");
     736         121 :             if (pszGlobalSRSDimension)
     737          15 :                 m_nSRSDimensionIfMissing = atoi(pszGlobalSRSDimension);
     738         121 :             CPLFree(pszGlobalSRSDimension);
     739             :         }
     740             :     }
     741             : 
     742         902 :     return OGRERR_NONE;
     743             : }
     744             : 
     745             : /************************************************************************/
     746             : /*                       startElementGeometry()                         */
     747             : /************************************************************************/
     748             : 
     749       22007 : OGRErr GMLHandler::startElementGeometry(const char *pszName, int nLenName,
     750             :                                         void *attr)
     751             : {
     752       23065 :     if (stateStack[nStackDepth] == STATE_BOUNDED_BY_IN_FEATURE &&
     753        1058 :         apsXMLNode.empty())
     754             :     {
     755           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid <boundedBy> construct");
     756           1 :         return OGRERR_FAILURE;
     757             :     }
     758             : 
     759             :     /* Create new XML Element */
     760       22006 :     CPLXMLNode *psCurNode = (CPLXMLNode *)CPLCalloc(sizeof(CPLXMLNode), 1);
     761       22006 :     psCurNode->eType = CXT_Element;
     762       22006 :     psCurNode->pszValue = (char *)CPLMalloc(nLenName + 1);
     763       22006 :     memcpy(psCurNode->pszValue, pszName, nLenName + 1);
     764             : 
     765             :     /* Attach element as the last child of its parent */
     766       22006 :     NodeLastChild &sNodeLastChild = apsXMLNode.back();
     767       22006 :     CPLXMLNode *psLastChildParent = sNodeLastChild.psLastChild;
     768             : 
     769       22006 :     if (psLastChildParent == nullptr)
     770             :     {
     771       12279 :         CPLXMLNode *psParent = sNodeLastChild.psNode;
     772       12279 :         if (psParent)
     773        9801 :             psParent->psChild = psCurNode;
     774             :     }
     775             :     else
     776             :     {
     777        9727 :         psLastChildParent->psNext = psCurNode;
     778             :     }
     779       22006 :     sNodeLastChild.psLastChild = psCurNode;
     780             : 
     781             :     /* Add attributes to the element */
     782       22006 :     CPLXMLNode *psLastChildCurNode = AddAttributes(psCurNode, attr);
     783       31509 :     for (CPLXMLNode *psIter = psCurNode->psChild; psIter;
     784        9503 :          psIter = psIter->psNext)
     785             :     {
     786        9503 :         if (psIter->eType == CXT_Attribute &&
     787        9503 :             strcmp(psIter->pszValue, "xlink:href") == 0 &&
     788         722 :             psIter->psChild->pszValue && psIter->psChild->pszValue[0] == '#')
     789             :         {
     790         722 :             m_oMapElementToSubstitute[psIter->psChild->pszValue + 1] =
     791             :                 psCurNode;
     792             :         }
     793             :     }
     794             : 
     795             :     /* Some CityGML lack a srsDimension="3" in posList, such as in */
     796             :     /* http://www.citygml.org/fileadmin/count.php?f=fileadmin%2Fcitygml%2Fdocs%2FFrankfurt_Street_Setting_LOD3.zip
     797             :      */
     798             :     /* So we have to add it manually */
     799       46292 :     if (strcmp(pszName, "posList") == 0 &&
     800       22854 :         CPLGetXMLValue(psCurNode, "srsDimension", nullptr) == nullptr &&
     801         848 :         m_nSRSDimensionIfMissing != 0)
     802             :     {
     803             :         CPLXMLNode *psChild =
     804         118 :             CPLCreateXMLNode(nullptr, CXT_Attribute, "srsDimension");
     805         118 :         CPLCreateXMLNode(psChild, CXT_Text,
     806         118 :                          (m_nSRSDimensionIfMissing == 3) ? "3" : "2");
     807             : 
     808         118 :         if (psLastChildCurNode == nullptr)
     809         118 :             psCurNode->psChild = psChild;
     810             :         else
     811           0 :             psLastChildCurNode->psNext = psChild;
     812         118 :         psLastChildCurNode = psChild;
     813             :     }
     814             : 
     815             :     /* Push the element on the stack */
     816             :     NodeLastChild sNewNodeLastChild;
     817       22006 :     sNewNodeLastChild.psNode = psCurNode;
     818       22006 :     sNewNodeLastChild.psLastChild = psLastChildCurNode;
     819       22006 :     apsXMLNode.push_back(sNewNodeLastChild);
     820             : 
     821       22006 :     if (m_pszGeometry)
     822             :     {
     823           0 :         CPLFree(m_pszGeometry);
     824           0 :         m_pszGeometry = nullptr;
     825           0 :         m_nGeomAlloc = 0;
     826           0 :         m_nGeomLen = 0;
     827             :     }
     828             : 
     829       22006 :     return OGRERR_NONE;
     830             : }
     831             : 
     832             : /************************************************************************/
     833             : /*                    startElementCityGMLGenericAttr()                  */
     834             : /************************************************************************/
     835             : 
     836          42 : OGRErr GMLHandler::startElementCityGMLGenericAttr(const char *pszName,
     837             :                                                   int /*nLenName*/,
     838             :                                                   void * /*attr*/)
     839             : {
     840          42 :     if (strcmp(pszName, "value") == 0)
     841             :     {
     842          42 :         if (m_pszCurField)
     843             :         {
     844           0 :             CPLFree(m_pszCurField);
     845           0 :             m_pszCurField = nullptr;
     846           0 :             m_nCurFieldLen = 0;
     847           0 :             m_nCurFieldAlloc = 0;
     848             :         }
     849          42 :         m_bInCurField = true;
     850             :     }
     851             : 
     852          42 :     return OGRERR_NONE;
     853             : }
     854             : 
     855             : /************************************************************************/
     856             : /*                       DealWithAttributes()                           */
     857             : /************************************************************************/
     858             : 
     859       10816 : void GMLHandler::DealWithAttributes(const char *pszName, int nLenName,
     860             :                                     void *attr)
     861             : {
     862       10816 :     GMLReadState *poState = m_poReader->GetState();
     863       10816 :     GMLFeatureClass *poClass = poState->m_poFeature->GetClass();
     864             : 
     865       10816 :     for (unsigned int idx = 0; true; idx++)
     866             :     {
     867       11353 :         char *pszAttrKey = nullptr;
     868             : 
     869       11353 :         char *pszAttrVal = GetAttributeByIdx(attr, idx, &pszAttrKey);
     870       11353 :         if (pszAttrVal == nullptr)
     871       10816 :             break;
     872             : 
     873         537 :         int nAttrIndex = 0;
     874         537 :         const char *pszAttrKeyNoNS = strchr(pszAttrKey, ':');
     875         537 :         if (pszAttrKeyNoNS != nullptr)
     876         101 :             pszAttrKeyNoNS++;
     877             : 
     878             :         /* If attribute is referenced by the .gfs */
     879         683 :         if (poClass->IsSchemaLocked() &&
     880          48 :             ((pszAttrKeyNoNS != nullptr &&
     881          48 :               (nAttrIndex = m_poReader->GetAttributeElementIndex(
     882         113 :                    pszName, nLenName, pszAttrKeyNoNS)) != -1) ||
     883         113 :              ((nAttrIndex = m_poReader->GetAttributeElementIndex(
     884             :                    pszName, nLenName, pszAttrKey)) != -1)))
     885             :         {
     886          67 :             nAttrIndex = FindRealPropertyByCheckingConditions(nAttrIndex, attr);
     887          67 :             if (nAttrIndex >= 0)
     888             :             {
     889          65 :                 m_poReader->SetFeaturePropertyDirectly(nullptr, pszAttrVal,
     890             :                                                        nAttrIndex);
     891          65 :                 pszAttrVal = nullptr;
     892             :             }
     893             :         }
     894             : 
     895             :         /* Hard-coded historic cases */
     896         470 :         else if (strcmp(pszAttrKey, "xlink:href") == 0)
     897             :         {
     898          14 :             if ((m_bReportHref || m_poReader->ReportAllAttributes()) &&
     899           7 :                 m_bInCurField)
     900             :             {
     901           4 :                 CPLFree(m_pszHref);
     902           4 :                 m_pszHref = pszAttrVal;
     903           4 :                 pszAttrVal = nullptr;
     904             :             }
     905           9 :             else if ((!poClass->IsSchemaLocked() &&
     906           9 :                       (m_bReportHref || m_poReader->ReportAllAttributes())) ||
     907           3 :                      (poClass->IsSchemaLocked() &&
     908           3 :                       (nAttrIndex = m_poReader->GetAttributeElementIndex(
     909           6 :                            (std::string(pszName) + "_href").c_str(),
     910             :                            nLenName + 5)) != -1))
     911             :             {
     912           3 :                 poState->PushPath(pszName, nLenName);
     913           3 :                 CPLString osPropNameHref = poState->osPath + "_href";
     914           3 :                 poState->PopPath();
     915           3 :                 m_poReader->SetFeaturePropertyDirectly(osPropNameHref,
     916             :                                                        pszAttrVal, nAttrIndex);
     917           3 :                 pszAttrVal = nullptr;
     918             :             }
     919             :         }
     920         463 :         else if (strcmp(pszAttrKey, "uom") == 0)
     921             :         {
     922          14 :             CPLFree(m_pszUom);
     923          14 :             m_pszUom = pszAttrVal;
     924          14 :             pszAttrVal = nullptr;
     925             :         }
     926         449 :         else if (strcmp(pszAttrKey, "value") == 0)
     927             :         {
     928          10 :             CPLFree(m_pszValue);
     929          10 :             m_pszValue = pszAttrVal;
     930          10 :             pszAttrVal = nullptr;
     931             :         }
     932             :         else /* Get language in 'kieli' attribute of 'teksti' element */
     933         439 :             if (eAppSchemaType == APPSCHEMA_MTKGML && nLenName == 6 &&
     934           7 :                 strcmp(pszName, "teksti") == 0 &&
     935           7 :                 strcmp(pszAttrKey, "kieli") == 0)
     936             :             {
     937           7 :                 CPLFree(m_pszKieli);
     938           7 :                 m_pszKieli = pszAttrVal;
     939           7 :                 pszAttrVal = nullptr;
     940             :             }
     941             : 
     942             :             /* Should we report all attributes ? */
     943         440 :             else if (m_poReader->ReportAllAttributes() &&
     944           8 :                      !poClass->IsSchemaLocked())
     945             :             {
     946           8 :                 poState->PushPath(pszName, nLenName);
     947           8 :                 CPLString osPropName = poState->osPath;
     948           8 :                 poState->PopPath();
     949             : 
     950           8 :                 m_poReader->SetFeaturePropertyDirectly(
     951             :                     CPLSPrintf("%s@%s", osPropName.c_str(),
     952             :                                pszAttrKeyNoNS ? pszAttrKeyNoNS : pszAttrKey),
     953             :                     pszAttrVal, -1);
     954           8 :                 pszAttrVal = nullptr;
     955             :             }
     956             : 
     957         537 :         CPLFree(pszAttrKey);
     958         537 :         CPLFree(pszAttrVal);
     959         537 :     }
     960             : 
     961             : #if 0
     962             :     if( poClass->IsSchemaLocked() )
     963             :     {
     964             :         poState->PushPath( pszName, nLenName );
     965             :         CPLString osPath = poState->osPath;
     966             :         poState->PopPath();
     967             :         /* Find fields that match an attribute that is missing */
     968             :         for(int i=0; i < poClass->GetPropertyCount(); i++ )
     969             :         {
     970             :             GMLPropertyDefn* poProp = poClass->GetProperty(i);
     971             :             const char* pszSrcElement = poProp->GetSrcElement();
     972             :             if( poProp->GetType() == OFTStringList &&
     973             :                 poProp->GetSrcElementLen() > osPath.size() &&
     974             :                 strncmp(pszSrcElement, osPath, osPath.size()) == 0 &&
     975             :                 pszSrcElement[osPath.size()] == '@' )
     976             :             {
     977             :                 char* pszAttrVal = GetAttributeValue(attr, pszSrcElement + osPath.size() + 1);
     978             :                 if( pszAttrVal == NULL )
     979             :                 {
     980             :                     const char* pszCond = poProp->GetCondition();
     981             :                     if( pszCond == NULL || IsConditionMatched(pszCond, attr) )
     982             :                     {
     983             :                         m_poReader->SetFeaturePropertyDirectly( NULL, CPLStrdup(""), i );
     984             :                     }
     985             :                 }
     986             :                 else
     987             :                     CPLFree(pszAttrVal);
     988             :             }
     989             :         }
     990             :     }
     991             : #endif
     992       10816 : }
     993             : 
     994             : /************************************************************************/
     995             : /*                        IsConditionMatched()                          */
     996             : /************************************************************************/
     997             : 
     998             : /* FIXME! 'and' / 'or' operators are evaluated left to right, without */
     999             : /* and precedence rules between them ! */
    1000             : 
    1001          15 : bool GMLHandler::IsConditionMatched(const char *pszCondition, void *attr)
    1002             : {
    1003          15 :     if (pszCondition == nullptr)
    1004           0 :         return true;
    1005             : 
    1006          15 :     bool bSyntaxError = false;
    1007          30 :     CPLString osCondAttr, osCondVal;
    1008          15 :     const char *pszIter = pszCondition;
    1009          15 :     bool bOpEqual = true;
    1010          19 :     while (*pszIter == ' ')
    1011           4 :         pszIter++;
    1012          15 :     if (*pszIter != '@')
    1013           0 :         bSyntaxError = true;
    1014             :     else
    1015             :     {
    1016          15 :         pszIter++;
    1017          75 :         while (*pszIter != '\0' && *pszIter != ' ' && *pszIter != '!' &&
    1018          65 :                *pszIter != '=')
    1019             :         {
    1020          60 :             osCondAttr += *pszIter;
    1021          60 :             pszIter++;
    1022             :         }
    1023          15 :         while (*pszIter == ' ')
    1024           0 :             pszIter++;
    1025             : 
    1026          15 :         if (*pszIter == '!')
    1027             :         {
    1028          10 :             bOpEqual = false;
    1029          10 :             pszIter++;
    1030             :         }
    1031             : 
    1032          15 :         if (*pszIter != '=')
    1033           0 :             bSyntaxError = true;
    1034             :         else
    1035             :         {
    1036          15 :             pszIter++;
    1037          15 :             while (*pszIter == ' ')
    1038           0 :                 pszIter++;
    1039          15 :             if (*pszIter != '\'')
    1040           0 :                 bSyntaxError = true;
    1041             :             else
    1042             :             {
    1043          15 :                 pszIter++;
    1044          45 :                 while (*pszIter != '\0' && *pszIter != '\'')
    1045             :                 {
    1046          30 :                     osCondVal += *pszIter;
    1047          30 :                     pszIter++;
    1048             :                 }
    1049          15 :                 if (*pszIter != '\'')
    1050           0 :                     bSyntaxError = true;
    1051             :                 else
    1052             :                 {
    1053          15 :                     pszIter++;
    1054          21 :                     while (*pszIter == ' ')
    1055           6 :                         pszIter++;
    1056             :                 }
    1057             :             }
    1058             :         }
    1059             :     }
    1060             : 
    1061          15 :     if (bSyntaxError)
    1062             :     {
    1063           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1064             :                  "Invalid condition : %s. Must be of the form "
    1065             :                  "@attrname[!]='attrvalue' [and|or other_cond]*. "
    1066             :                  "'and' and 'or' operators cannot be mixed",
    1067             :                  pszCondition);
    1068           0 :         return false;
    1069             :     }
    1070             : 
    1071          15 :     char *pszVal = GetAttributeValue(attr, osCondAttr);
    1072          15 :     if (pszVal == nullptr)
    1073           0 :         pszVal = CPLStrdup("");
    1074          28 :     const bool bCondMet = (bOpEqual && strcmp(pszVal, osCondVal) == 0) ||
    1075          13 :                           (!bOpEqual && strcmp(pszVal, osCondVal) != 0);
    1076          15 :     CPLFree(pszVal);
    1077          15 :     if (*pszIter == '\0')
    1078           9 :         return bCondMet;
    1079             : 
    1080           6 :     if (STARTS_WITH(pszIter, "and"))
    1081             :     {
    1082           6 :         pszIter += 3;
    1083           6 :         if (!bCondMet)
    1084           2 :             return false;
    1085           4 :         return IsConditionMatched(pszIter, attr);
    1086             :     }
    1087             : 
    1088           0 :     if (STARTS_WITH(pszIter, "or"))
    1089             :     {
    1090           0 :         pszIter += 2;
    1091           0 :         if (bCondMet)
    1092           0 :             return true;
    1093           0 :         return IsConditionMatched(pszIter, attr);
    1094             :     }
    1095             : 
    1096           0 :     CPLError(
    1097             :         CE_Failure, CPLE_NotSupported,
    1098             :         "Invalid condition : %s. Must be of the form @attrname[!]='attrvalue' "
    1099             :         "[and|or other_cond]*. 'and' and 'or' operators cannot be mixed",
    1100             :         pszCondition);
    1101           0 :     return false;
    1102             : }
    1103             : 
    1104             : /************************************************************************/
    1105             : /*                FindRealPropertyByCheckingConditions()                */
    1106             : /************************************************************************/
    1107             : 
    1108        2670 : int GMLHandler::FindRealPropertyByCheckingConditions(int nIdx, void *attr)
    1109             : {
    1110        2670 :     GMLReadState *poState = m_poReader->GetState();
    1111        2670 :     GMLFeatureClass *poClass = poState->m_poFeature->GetClass();
    1112             : 
    1113        2670 :     GMLPropertyDefn *poProp = poClass->GetProperty(nIdx);
    1114        2670 :     const char *pszCond = poProp->GetCondition();
    1115        2670 :     if (pszCond != nullptr && !IsConditionMatched(pszCond, attr))
    1116             :     {
    1117             :         /* try other attributes with same source element, but with different */
    1118             :         /* condition */
    1119           4 :         const char *pszSrcElement = poProp->GetSrcElement();
    1120           4 :         nIdx = -1;
    1121          11 :         for (int i = m_nAttributeIndex + 1; i < poClass->GetPropertyCount();
    1122             :              i++)
    1123             :         {
    1124           9 :             poProp = poClass->GetProperty(i);
    1125           9 :             if (strcmp(poProp->GetSrcElement(), pszSrcElement) == 0)
    1126             :             {
    1127           5 :                 pszCond = poProp->GetCondition();
    1128           5 :                 if (IsConditionMatched(pszCond, attr))
    1129             :                 {
    1130           2 :                     nIdx = i;
    1131           2 :                     break;
    1132             :                 }
    1133             :             }
    1134             :         }
    1135             :     }
    1136        2670 :     return nIdx;
    1137             : }
    1138             : 
    1139             : /************************************************************************/
    1140             : /*                      startElementFeatureAttribute()                  */
    1141             : /************************************************************************/
    1142             : 
    1143       13617 : OGRErr GMLHandler::startElementFeatureAttribute(const char *pszName,
    1144             :                                                 int nLenName, void *attr)
    1145             : {
    1146             :     /* Reset flag */
    1147       13617 :     m_bInCurField = false;
    1148             : 
    1149       13617 :     GMLReadState *poState = m_poReader->GetState();
    1150             : 
    1151             :     /* -------------------------------------------------------------------- */
    1152             :     /*      If we are collecting geometry, or if we determine this is a     */
    1153             :     /*      geometry element then append to the geometry info.              */
    1154             :     /* -------------------------------------------------------------------- */
    1155       13617 :     if (IsGeometryElement(pszName))
    1156             :     {
    1157             :         bool bReadGeometry;
    1158             : 
    1159             :         /* If the <GeometryElementPath> is defined in the .gfs, use it */
    1160             :         /* to read the appropriate geometry element */
    1161        2409 :         GMLFeatureClass *poClass = poState->m_poFeature->GetClass();
    1162        2409 :         m_nGeometryPropertyIndex = 0;
    1163        3533 :         if (poClass->IsSchemaLocked() &&
    1164        1124 :             poClass->GetGeometryPropertyCount() == 0)
    1165             :         {
    1166           0 :             bReadGeometry = false;
    1167             :         }
    1168        3533 :         else if (poClass->IsSchemaLocked() &&
    1169        3533 :                  poClass->GetGeometryPropertyCount() == 1 &&
    1170         962 :                  poClass->GetGeometryProperty(0)->GetSrcElement()[0] == '\0')
    1171             :         {
    1172          64 :             bReadGeometry = true;
    1173             :         }
    1174        3405 :         else if (poClass->IsSchemaLocked() &&
    1175        1060 :                  poClass->GetGeometryPropertyCount() > 0)
    1176             :         {
    1177        1060 :             m_nGeometryPropertyIndex =
    1178        1060 :                 poClass->GetGeometryPropertyIndexBySrcElement(
    1179             :                     poState->osPath.c_str());
    1180        1060 :             bReadGeometry = (m_nGeometryPropertyIndex >= 0);
    1181             :         }
    1182        1285 :         else if (m_poReader->FetchAllGeometries())
    1183             :         {
    1184           0 :             bReadGeometry = true;
    1185             :         }
    1186        1285 :         else if (!poClass->IsSchemaLocked() && m_poReader->IsWFSJointLayer())
    1187             :         {
    1188          12 :             m_nGeometryPropertyIndex =
    1189          12 :                 poClass->GetGeometryPropertyIndexBySrcElement(
    1190             :                     poState->osPath.c_str());
    1191          12 :             if (m_nGeometryPropertyIndex < 0)
    1192             :             {
    1193           4 :                 const char *pszElement = poState->osPath.c_str();
    1194           4 :                 CPLString osFieldName;
    1195             :                 /* Strip member| prefix. Should always be true normally */
    1196           4 :                 if (STARTS_WITH(pszElement, "member|"))
    1197           4 :                     osFieldName = pszElement + strlen("member|");
    1198             : 
    1199             :                 /* Replace layer|property by layer_property */
    1200           4 :                 size_t iPos = osFieldName.find('|');
    1201           4 :                 if (iPos != std::string::npos)
    1202           4 :                     osFieldName[iPos] = '.';
    1203             : 
    1204           8 :                 poClass->AddGeometryProperty(new GMLGeometryPropertyDefn(
    1205           4 :                     osFieldName, poState->osPath.c_str(), wkbUnknown, -1,
    1206           8 :                     true));
    1207           4 :                 m_nGeometryPropertyIndex = poClass->GetGeometryPropertyCount();
    1208             :             }
    1209          12 :             bReadGeometry = true;
    1210             :         }
    1211             :         else
    1212             :         {
    1213             :             /* AIXM special case: for RouteSegment, we only want to read Curve
    1214             :              * geometries */
    1215             :             /* not 'start' and 'end' geometries */
    1216        1275 :             if (eAppSchemaType == APPSCHEMA_AIXM &&
    1217           2 :                 strcmp(poState->m_poFeature->GetClass()->GetName(),
    1218             :                        "RouteSegment") == 0)
    1219           0 :                 bReadGeometry = strcmp(pszName, "Curve") == 0;
    1220             : 
    1221             :             /* For Inspire objects : the "main" geometry is in a <geometry>
    1222             :              * element */
    1223        1273 :             else if (m_bAlreadyFoundGeometry)
    1224           2 :                 bReadGeometry = false;
    1225        1271 :             else if (strcmp(poState->osPath.c_str(), "geometry") == 0)
    1226             :             {
    1227           8 :                 m_bAlreadyFoundGeometry = true;
    1228           8 :                 bReadGeometry = true;
    1229           8 :                 m_nGeometryPropertyIndex =
    1230           8 :                     poClass->GetGeometryPropertyIndexBySrcElement(
    1231             :                         poState->osPath.c_str());
    1232           8 :                 if (m_nGeometryPropertyIndex < 0)
    1233             :                 {
    1234           6 :                     poClass->AddGeometryProperty(new GMLGeometryPropertyDefn(
    1235           3 :                         "geometry", poState->osPath.c_str(), wkbUnknown, -1,
    1236           3 :                         true));
    1237           3 :                     m_nGeometryPropertyIndex =
    1238           3 :                         poClass->GetGeometryPropertyCount();
    1239             :                 }
    1240             :             }
    1241             : 
    1242             :             else
    1243             :             {
    1244        2526 :                 if (!poClass->IsSchemaLocked() &&
    1245        1263 :                     poClass->IsConsistentSingleGeomElemPath())
    1246             :                 {
    1247             :                     const std::string &osGeomElemPath =
    1248        1255 :                         poClass->GetSingleGeomElemPath();
    1249        1255 :                     if (osGeomElemPath.empty())
    1250             :                     {
    1251         245 :                         poClass->SetSingleGeomElemPath(poState->osPath);
    1252             :                     }
    1253        1010 :                     else if (poState->osPath != osGeomElemPath)
    1254             :                     {
    1255           4 :                         poClass->SetConsistentSingleGeomElemPath(false);
    1256           4 :                         poClass->SetSingleGeomElemPath(std::string());
    1257             :                     }
    1258             :                 }
    1259        1263 :                 bReadGeometry = true;
    1260             :             }
    1261             :         }
    1262        2409 :         if (bReadGeometry)
    1263             :         {
    1264        2404 :             m_nGeometryDepth = m_nDepth;
    1265             : 
    1266        2404 :             CPLAssert(apsXMLNode.empty());
    1267             : 
    1268             :             NodeLastChild sNodeLastChild;
    1269        2404 :             sNodeLastChild.psNode = nullptr;
    1270        2404 :             sNodeLastChild.psLastChild = nullptr;
    1271        2404 :             apsXMLNode.push_back(sNodeLastChild);
    1272             : 
    1273        2404 :             PUSH_STATE(STATE_GEOMETRY);
    1274             : 
    1275        2404 :             return startElementGeometry(pszName, nLenName, attr);
    1276             :         }
    1277             :     }
    1278       12165 :     else if (nLenName == 9 && strcmp(pszName, "boundedBy") == 0 &&
    1279             :              // We ignore the UseBBOX() flag for CityGML, since CityGML
    1280             :              // has elements like bldg:boundedBy, which are not a simple
    1281             :              // rectangular bbox. This is needed to read properly
    1282             :              // autotest/ogr/data/gml/citygml_lod2_713_5322.xml
    1283             :              // (this is a workaround of not being namespace aware)
    1284         957 :              (eAppSchemaType == APPSCHEMA_CITYGML || m_poReader->UseBBOX()))
    1285             :     {
    1286          74 :         m_inBoundedByDepth = m_nDepth;
    1287             : 
    1288          74 :         CPLAssert(apsXMLNode.empty());
    1289             : 
    1290             :         NodeLastChild sNodeLastChild;
    1291          74 :         sNodeLastChild.psNode = nullptr;
    1292          74 :         sNodeLastChild.psLastChild = nullptr;
    1293          74 :         apsXMLNode.push_back(sNodeLastChild);
    1294             : 
    1295          74 :         PUSH_STATE(STATE_BOUNDED_BY_IN_FEATURE);
    1296             : 
    1297          74 :         return OGRERR_NONE;
    1298             :     }
    1299             : 
    1300             :     /* -------------------------------------------------------------------- */
    1301             :     /*      Is it a CityGML generic attribute ?                             */
    1302             :     /* -------------------------------------------------------------------- */
    1303       11266 :     else if (eAppSchemaType == APPSCHEMA_CITYGML &&
    1304         132 :              m_poReader->IsCityGMLGenericAttributeElement(pszName, attr))
    1305             :     {
    1306          42 :         CPLFree(m_pszCityGMLGenericAttrName);
    1307          42 :         m_pszCityGMLGenericAttrName = GetAttributeValue(attr, "name");
    1308          42 :         m_inCityGMLGenericAttrDepth = m_nDepth;
    1309             : 
    1310          42 :         PUSH_STATE(STATE_CITYGML_ATTRIBUTE);
    1311             : 
    1312          42 :         return OGRERR_NONE;
    1313             :     }
    1314             : 
    1315       11092 :     else if (m_poReader->IsWFSJointLayer() && m_nDepth == m_nDepthFeature + 1)
    1316             :     {
    1317             :     }
    1318             : 
    1319       10968 :     else if (m_poReader->IsWFSJointLayer() && m_nDepth == m_nDepthFeature + 2)
    1320             :     {
    1321         124 :         const char *pszFID = GetFID(attr);
    1322         124 :         if (pszFID)
    1323             :         {
    1324         124 :             poState->PushPath(pszName, nLenName);
    1325         248 :             CPLString osPropPath = poState->osPath + "@id";
    1326         124 :             poState->PopPath();
    1327         124 :             m_poReader->SetFeaturePropertyDirectly(osPropPath,
    1328             :                                                    CPLStrdup(pszFID), -1);
    1329             :         }
    1330             :     }
    1331             : 
    1332             :     /* -------------------------------------------------------------------- */
    1333             :     /*      If it is (or at least potentially is) a simple attribute,       */
    1334             :     /*      then start collecting it.                                       */
    1335             :     /* -------------------------------------------------------------------- */
    1336       10844 :     else if ((m_nAttributeIndex = m_poReader->GetAttributeElementIndex(
    1337       10844 :                   pszName, nLenName)) != -1)
    1338             :     {
    1339        8482 :         GMLFeatureClass *poClass = poState->m_poFeature->GetClass();
    1340       13735 :         if (poClass->IsSchemaLocked() &&
    1341        2627 :             (poClass->GetProperty(m_nAttributeIndex)->GetType() ==
    1342        2626 :                  GMLPT_FeatureProperty ||
    1343        2626 :              poClass->GetProperty(m_nAttributeIndex)->GetType() ==
    1344             :                  GMLPT_FeaturePropertyList))
    1345             :         {
    1346          24 :             m_nAttributeDepth = m_nDepth;
    1347          24 :             PUSH_STATE(STATE_FEATUREPROPERTY);
    1348             :         }
    1349             :         else
    1350             :         {
    1351             :             /* Is this a property with a condition on an attribute value ? */
    1352        8458 :             if (poClass->IsSchemaLocked())
    1353             :             {
    1354        2603 :                 m_nAttributeIndex = FindRealPropertyByCheckingConditions(
    1355             :                     m_nAttributeIndex, attr);
    1356             :             }
    1357             : 
    1358        8458 :             if (m_nAttributeIndex >= 0)
    1359             :             {
    1360        8458 :                 if (m_pszCurField)
    1361             :                 {
    1362         883 :                     CPLFree(m_pszCurField);
    1363         883 :                     m_pszCurField = nullptr;
    1364         883 :                     m_nCurFieldLen = 0;
    1365         883 :                     m_nCurFieldAlloc = 0;
    1366             :                 }
    1367        8458 :                 m_bInCurField = true;
    1368             : 
    1369        8458 :                 char *pszXSINil = GetAttributeValue(attr, "xsi:nil");
    1370        8458 :                 if (pszXSINil)
    1371             :                 {
    1372           4 :                     if (EQUAL(pszXSINil, "true"))
    1373           4 :                         m_poReader->SetFeaturePropertyDirectly(
    1374             :                             pszName, CPLStrdup(OGR_GML_NULL), -1);
    1375           4 :                     CPLFree(pszXSINil);
    1376             :                 }
    1377             :                 else
    1378             :                 {
    1379        8454 :                     DealWithAttributes(pszName, nLenName, attr);
    1380             :                 }
    1381             : 
    1382        8458 :                 if (stateStack[nStackDepth] != STATE_PROPERTY)
    1383             :                 {
    1384        6807 :                     m_nAttributeDepth = m_nDepth;
    1385        6807 :                     PUSH_STATE(STATE_PROPERTY);
    1386             :                 }
    1387             :             }
    1388             :             /*else
    1389             :             {
    1390             :                 DealWithAttributes(pszName, nLenName, attr);
    1391             :             }*/
    1392             :         }
    1393             :     }
    1394             :     else
    1395             :     {
    1396        2362 :         DealWithAttributes(pszName, nLenName, attr);
    1397             :     }
    1398             : 
    1399       11097 :     poState->PushPath(pszName, nLenName);
    1400             : 
    1401       11097 :     return OGRERR_NONE;
    1402             : }
    1403             : 
    1404             : /************************************************************************/
    1405             : /*                         startElementTop()                            */
    1406             : /************************************************************************/
    1407             : 
    1408         502 : OGRErr GMLHandler::startElementTop(const char *pszName, int /*nLenName*/,
    1409             :                                    void *attr)
    1410             : {
    1411         502 :     if (strcmp(pszName, "CityModel") == 0)
    1412             :     {
    1413           6 :         eAppSchemaType = APPSCHEMA_CITYGML;
    1414             :         // Default to 3D geometries for CityGML (#6989)
    1415           6 :         if (m_nSRSDimensionIfMissing == 0)
    1416           6 :             m_nSRSDimensionIfMissing = 3;
    1417             :     }
    1418         496 :     else if (strcmp(pszName, "AIXMBasicMessage") == 0)
    1419             :     {
    1420           2 :         eAppSchemaType = APPSCHEMA_AIXM;
    1421           2 :         m_bReportHref = true;
    1422             :     }
    1423         494 :     else if (strcmp(pszName, "Maastotiedot") == 0)
    1424             :     {
    1425           7 :         eAppSchemaType = APPSCHEMA_MTKGML;
    1426             : 
    1427           7 :         char *pszSRSName = GetAttributeValue(attr, "srsName");
    1428           7 :         m_poReader->SetGlobalSRSName(pszSRSName);
    1429           7 :         CPLFree(pszSRSName);
    1430             : 
    1431           7 :         m_bReportHref = true;
    1432             : 
    1433             :         /* the schemas of MTKGML don't have (string) width, so don't set it */
    1434           7 :         m_poReader->SetWidthFlag(false);
    1435             :     }
    1436             : 
    1437         502 :     stateStack[0] = STATE_DEFAULT;
    1438             : 
    1439         502 :     return OGRERR_NONE;
    1440             : }
    1441             : 
    1442             : /************************************************************************/
    1443             : /*                        startElementDefault()                         */
    1444             : /************************************************************************/
    1445             : 
    1446        6190 : OGRErr GMLHandler::startElementDefault(const char *pszName, int nLenName,
    1447             :                                        void *attr)
    1448             : 
    1449             : {
    1450             :     /* -------------------------------------------------------------------- */
    1451             :     /*      Is it a feature?  If so push a whole new state, and return.     */
    1452             :     /* -------------------------------------------------------------------- */
    1453             :     int nClassIndex;
    1454        6190 :     const char *pszFilteredClassName = nullptr;
    1455             : 
    1456        6190 :     if (nLenName == 9 && strcmp(pszName, "boundedBy") == 0)
    1457             :     {
    1458         235 :         m_inBoundedByDepth = m_nDepth;
    1459             : 
    1460         235 :         PUSH_STATE(STATE_BOUNDED_BY);
    1461             : 
    1462         235 :         return OGRERR_NONE;
    1463             :     }
    1464             : 
    1465        6776 :     else if (m_poReader->ShouldLookForClassAtAnyLevel() &&
    1466         821 :              (pszFilteredClassName = m_poReader->GetFilteredClassName()) !=
    1467             :                  nullptr)
    1468             :     {
    1469         821 :         if (strcmp(pszName, pszFilteredClassName) == 0)
    1470             :         {
    1471          49 :             m_poReader->PushFeature(pszName, GetFID(attr),
    1472          49 :                                     m_poReader->GetFilteredClassIndex());
    1473             : 
    1474          49 :             m_nDepthFeature = m_nDepth;
    1475             : 
    1476          49 :             PUSH_STATE(STATE_FEATURE);
    1477             : 
    1478          49 :             return OGRERR_NONE;
    1479             :         }
    1480             :     }
    1481             : 
    1482             :     /* WFS 2.0 GetFeature documents have a wfs:FeatureCollection */
    1483             :     /* as a wfs:member of the top wfs:FeatureCollection. We don't want this */
    1484             :     /* wfs:FeatureCollection to be recognized as a feature */
    1485        5148 :     else if ((!(nLenName == (int)strlen("FeatureCollection") &&
    1486       10258 :                 strcmp(pszName, "FeatureCollection") == 0)) &&
    1487        5124 :              (nClassIndex = m_poReader->GetFeatureElementIndex(
    1488             :                   pszName, nLenName, eAppSchemaType)) != -1)
    1489             :     {
    1490        2757 :         m_bAlreadyFoundGeometry = false;
    1491             : 
    1492        2757 :         pszFilteredClassName = m_poReader->GetFilteredClassName();
    1493        2757 :         if (pszFilteredClassName != nullptr &&
    1494         377 :             strcmp(pszName, pszFilteredClassName) != 0)
    1495             :         {
    1496         345 :             m_nDepthFeature = m_nDepth;
    1497             : 
    1498         345 :             PUSH_STATE(STATE_IGNORED_FEATURE);
    1499             : 
    1500         345 :             return OGRERR_NONE;
    1501             :         }
    1502             :         else
    1503             :         {
    1504        2412 :             if (eAppSchemaType == APPSCHEMA_MTKGML)
    1505             :             {
    1506          21 :                 m_poReader->PushFeature(pszName, nullptr, nClassIndex);
    1507             : 
    1508          21 :                 char *pszGID = GetAttributeValue(attr, "gid");
    1509          21 :                 if (pszGID)
    1510          21 :                     m_poReader->SetFeaturePropertyDirectly("gid", pszGID, -1,
    1511             :                                                            GMLPT_String);
    1512             :             }
    1513             :             else
    1514        2391 :                 m_poReader->PushFeature(pszName, GetFID(attr), nClassIndex);
    1515             : 
    1516        2412 :             m_nDepthFeature = m_nDepth;
    1517             : 
    1518        2412 :             PUSH_STATE(STATE_FEATURE);
    1519             : 
    1520        2412 :             return OGRERR_NONE;
    1521             :         }
    1522             :     }
    1523             : 
    1524             :     /* -------------------------------------------------------------------- */
    1525             :     /*      Push the element onto the current state's path.                 */
    1526             :     /* -------------------------------------------------------------------- */
    1527        3149 :     m_poReader->GetState()->PushPath(pszName, nLenName);
    1528             : 
    1529        3149 :     return OGRERR_NONE;
    1530             : }
    1531             : 
    1532             : /************************************************************************/
    1533             : /*                      endElementIgnoredFeature()                      */
    1534             : /************************************************************************/
    1535             : 
    1536        4348 : OGRErr GMLHandler::endElementIgnoredFeature()
    1537             : 
    1538             : {
    1539        4348 :     if (m_nDepth == m_nDepthFeature)
    1540             :     {
    1541         345 :         POP_STATE();
    1542             :     }
    1543        4348 :     return OGRERR_NONE;
    1544             : }
    1545             : 
    1546             : /************************************************************************/
    1547             : /*                         endElementBoundedBy()                        */
    1548             : /************************************************************************/
    1549        1137 : OGRErr GMLHandler::endElementBoundedBy()
    1550             : 
    1551             : {
    1552        1137 :     if (m_inBoundedByDepth == m_nDepth)
    1553             :     {
    1554         235 :         POP_STATE();
    1555             :     }
    1556             : 
    1557        1137 :     return OGRERR_NONE;
    1558             : }
    1559             : 
    1560             : /************************************************************************/
    1561             : /*                     endElementBoundedByInFeature()                   */
    1562             : /************************************************************************/
    1563        1130 : OGRErr GMLHandler::endElementBoundedByInFeature()
    1564             : 
    1565             : {
    1566        1130 :     if (m_nDepth > m_inBoundedByDepth)
    1567             :     {
    1568        1057 :         if (m_nDepth == m_inBoundedByDepth + 1)
    1569             :         {
    1570          74 :             m_nGeometryDepth = m_nDepth;
    1571             :         }
    1572        1057 :         return endElementGeometry();
    1573             :     }
    1574             :     else
    1575             :     {
    1576          73 :         POP_STATE();
    1577          73 :         if (apsXMLNode.size() >= 2 && apsXMLNode[1].psNode != nullptr)
    1578           0 :             CPLDestroyXMLNode(apsXMLNode[1].psNode);
    1579          73 :         apsXMLNode.clear();
    1580          73 :         return OGRERR_NONE;
    1581             :     }
    1582             : }
    1583             : 
    1584             : /************************************************************************/
    1585             : /*                       ParseAIXMElevationPoint()                      */
    1586             : /************************************************************************/
    1587             : 
    1588           0 : CPLXMLNode *GMLHandler::ParseAIXMElevationPoint(CPLXMLNode *psGML)
    1589             : {
    1590           0 :     const char *pszElevation = CPLGetXMLValue(psGML, "elevation", nullptr);
    1591           0 :     if (pszElevation)
    1592             :     {
    1593           0 :         m_poReader->SetFeaturePropertyDirectly("elevation",
    1594             :                                                CPLStrdup(pszElevation), -1);
    1595             :         const char *pszElevationUnit =
    1596           0 :             CPLGetXMLValue(psGML, "elevation.uom", nullptr);
    1597           0 :         if (pszElevationUnit)
    1598             :         {
    1599           0 :             m_poReader->SetFeaturePropertyDirectly(
    1600             :                 "elevation_uom", CPLStrdup(pszElevationUnit), -1);
    1601             :         }
    1602             :     }
    1603             : 
    1604             :     const char *pszGeoidUndulation =
    1605           0 :         CPLGetXMLValue(psGML, "geoidUndulation", nullptr);
    1606           0 :     if (pszGeoidUndulation)
    1607             :     {
    1608           0 :         m_poReader->SetFeaturePropertyDirectly(
    1609             :             "geoidUndulation", CPLStrdup(pszGeoidUndulation), -1);
    1610             :         const char *pszGeoidUndulationUnit =
    1611           0 :             CPLGetXMLValue(psGML, "geoidUndulation.uom", nullptr);
    1612           0 :         if (pszGeoidUndulationUnit)
    1613             :         {
    1614           0 :             m_poReader->SetFeaturePropertyDirectly(
    1615             :                 "geoidUndulation_uom", CPLStrdup(pszGeoidUndulationUnit), -1);
    1616             :         }
    1617             :     }
    1618             : 
    1619           0 :     const char *pszPos = CPLGetXMLValue(psGML, "pos", nullptr);
    1620           0 :     const char *pszCoordinates = CPLGetXMLValue(psGML, "coordinates", nullptr);
    1621           0 :     if (pszPos != nullptr || pszCoordinates != nullptr)
    1622             :     {
    1623           0 :         CPLFree(psGML->pszValue);
    1624           0 :         psGML->pszValue = CPLStrdup("gml:Point");
    1625             :     }
    1626             :     else
    1627             :     {
    1628           0 :         CPLDestroyXMLNode(psGML);
    1629           0 :         psGML = nullptr;
    1630             :     }
    1631             : 
    1632           0 :     return psGML;
    1633             : }
    1634             : 
    1635             : /************************************************************************/
    1636             : /*                         endElementGeometry()                         */
    1637             : /************************************************************************/
    1638       21990 : OGRErr GMLHandler::endElementGeometry()
    1639             : 
    1640             : {
    1641       21990 :     if (m_nGeomLen)
    1642             :     {
    1643        3751 :         CPLXMLNode *psNode = (CPLXMLNode *)CPLCalloc(sizeof(CPLXMLNode), 1);
    1644        3751 :         psNode->eType = CXT_Text;
    1645        3751 :         psNode->pszValue = m_pszGeometry;
    1646             : 
    1647        3751 :         NodeLastChild &sNodeLastChild = apsXMLNode.back();
    1648        3751 :         CPLXMLNode *psLastChildParent = sNodeLastChild.psLastChild;
    1649        3751 :         if (psLastChildParent == nullptr)
    1650             :         {
    1651        2135 :             CPLXMLNode *psParent = sNodeLastChild.psNode;
    1652        2135 :             if (psParent)
    1653        2135 :                 psParent->psChild = psNode;
    1654             :         }
    1655             :         else
    1656        1616 :             psLastChildParent->psNext = psNode;
    1657        3751 :         sNodeLastChild.psLastChild = psNode;
    1658             : 
    1659        3751 :         m_pszGeometry = nullptr;
    1660        3751 :         m_nGeomAlloc = 0;
    1661        3751 :         m_nGeomLen = 0;
    1662             :     }
    1663             : 
    1664       21990 :     CPLXMLNode *psThisNode = apsXMLNode.back().psNode;
    1665       21990 :     CPLXMLNode *psThisNodeChild = psThisNode->psChild;
    1666       26656 :     if (!m_oMapElementToSubstitute.empty() && psThisNodeChild &&
    1667        4666 :         psThisNodeChild->eType == CXT_Attribute &&
    1668       27375 :         strcmp(psThisNodeChild->pszValue, "gml:id") == 0 &&
    1669         719 :         psThisNodeChild->psChild->pszValue)
    1670             :     {
    1671             :         auto oIter =
    1672         719 :             m_oMapElementToSubstitute.find(psThisNodeChild->psChild->pszValue);
    1673         719 :         if (oIter != m_oMapElementToSubstitute.end())
    1674             :         {
    1675         108 :             auto psLastChild = oIter->second->psChild;
    1676         108 :             if (psLastChild)
    1677             :             {
    1678             :                 // CPLDebug("GML", "Substitution of xlink:href=\"#%s\" with actual content", psThisNodeChild->psChild->pszValue);
    1679         108 :                 CPLXMLNode *psAfter = psThisNode->psNext;
    1680         108 :                 psThisNode->psNext = nullptr;
    1681             :                 // We can patch oIter->second as it stored as it in the current
    1682             :                 // GMLFeature.
    1683             :                 // Of course that would no longer be the case in case of
    1684             :                 // cross-references between different GMLFeature, hence we clear
    1685             :                 // m_oMapElementToSubstitute at the end of the current feature.
    1686         152 :                 while (psLastChild->psNext)
    1687          44 :                     psLastChild = psLastChild->psNext;
    1688         108 :                 psLastChild->psNext = CPLCloneXMLTree(psThisNode);
    1689         108 :                 psThisNode->psNext = psAfter;
    1690             :             }
    1691             :         }
    1692             :     }
    1693             : 
    1694       21990 :     if (m_nDepth == m_nGeometryDepth)
    1695             :     {
    1696        2476 :         m_nGeometryDepth = 0;
    1697             : 
    1698        2476 :         CPLAssert(apsXMLNode.size() == 2);
    1699        2476 :         CPLXMLNode *psInterestNode = apsXMLNode.back().psNode;
    1700             : 
    1701             :         /*char* pszXML = CPLSerializeXMLTree(psInterestNode);
    1702             :         CPLDebug("GML", "geometry = %s", pszXML);
    1703             :         CPLFree(pszXML);*/
    1704             : 
    1705        2476 :         apsXMLNode.pop_back();
    1706             : 
    1707             :         /* AIXM ElevatedPoint. We want to parse this */
    1708             :         /* a bit specially because ElevatedPoint is aixm: stuff and */
    1709             :         /* the srsDimension of the <gml:pos> can be set to true although */
    1710             :         /* they are only 2 coordinates in practice */
    1711        2476 :         if (eAppSchemaType == APPSCHEMA_AIXM && psInterestNode != nullptr &&
    1712           2 :             strcmp(psInterestNode->pszValue, "ElevatedPoint") == 0)
    1713             :         {
    1714           0 :             psInterestNode = ParseAIXMElevationPoint(psInterestNode);
    1715             :         }
    1716        2476 :         else if (eAppSchemaType == APPSCHEMA_MTKGML &&
    1717             :                  psInterestNode != nullptr)
    1718             :         {
    1719          35 :             if (strcmp(psInterestNode->pszValue, "Murtoviiva") == 0)
    1720             :             {
    1721           7 :                 CPLFree(psInterestNode->pszValue);
    1722           7 :                 psInterestNode->pszValue = CPLStrdup("gml:LineString");
    1723             :             }
    1724          28 :             else if (strcmp(psInterestNode->pszValue, "Alue") == 0)
    1725             :             {
    1726           7 :                 CPLFree(psInterestNode->pszValue);
    1727           7 :                 psInterestNode->pszValue = CPLStrdup("gml:Polygon");
    1728             :             }
    1729          21 :             else if (strcmp(psInterestNode->pszValue, "Piste") == 0)
    1730             :             {
    1731          21 :                 CPLFree(psInterestNode->pszValue);
    1732          21 :                 psInterestNode->pszValue = CPLStrdup("gml:Point");
    1733             :             }
    1734             :         }
    1735        2441 :         else if (psInterestNode != nullptr &&
    1736        2441 :                  strcmp(psInterestNode->pszValue, "BoundingBox") == 0)
    1737             :         {
    1738          27 :             CPLFree(psInterestNode->pszValue);
    1739          27 :             psInterestNode->pszValue = CPLStrdup("Envelope");
    1740          30 :             for (CPLXMLNode *psChild = psInterestNode->psChild; psChild;
    1741           3 :                  psChild = psChild->psNext)
    1742             :             {
    1743          29 :                 if (psChild->eType == CXT_Attribute &&
    1744          27 :                     strcmp(psChild->pszValue, "crs") == 0)
    1745             :                 {
    1746          26 :                     CPLFree(psChild->pszValue);
    1747          26 :                     psChild->pszValue = CPLStrdup("srsName");
    1748          26 :                     break;
    1749             :                 }
    1750             :             }
    1751             :         }
    1752             : 
    1753        2476 :         GMLFeature *poGMLFeature = m_poReader->GetState()->m_poFeature;
    1754        2476 :         if (stateStack[nStackDepth] == STATE_BOUNDED_BY_IN_FEATURE)
    1755             :         {
    1756          74 :             if (eAppSchemaType == APPSCHEMA_CITYGML)
    1757          54 :                 CPLDestroyXMLNode(psInterestNode);
    1758             :             else
    1759          20 :                 poGMLFeature->SetBoundedByGeometry(psInterestNode);
    1760             :         }
    1761             :         else
    1762             :         {
    1763        2402 :             if (m_poReader->FetchAllGeometries())
    1764           0 :                 poGMLFeature->AddGeometry(psInterestNode);
    1765             :             else
    1766             :             {
    1767        2402 :                 GMLFeatureClass *poClass = poGMLFeature->GetClass();
    1768        2402 :                 if (poClass->GetGeometryPropertyCount() > 1)
    1769             :                 {
    1770         172 :                     if (poGMLFeature->GetGeometryRef(m_nGeometryPropertyIndex))
    1771             :                     {
    1772             :                         // If we have already a geometry, setting a new one
    1773             :                         // will invalidate nodes potentially stored in
    1774             :                         // m_oMapElementToSubstitute, so clear it
    1775           0 :                         m_oMapElementToSubstitute.clear();
    1776             :                     }
    1777         172 :                     poGMLFeature->SetGeometryDirectly(m_nGeometryPropertyIndex,
    1778             :                                                       psInterestNode);
    1779             :                 }
    1780             :                 else
    1781             :                 {
    1782        2230 :                     if (poGMLFeature->GetGeometryRef(0))
    1783             :                     {
    1784             :                         // If we have already a geometry, setting a new one
    1785             :                         // will invalidate nodes potentially stored in
    1786             :                         // m_oMapElementToSubstitute, so clear it
    1787          16 :                         m_oMapElementToSubstitute.clear();
    1788             :                     }
    1789        2230 :                     poGMLFeature->SetGeometryDirectly(psInterestNode);
    1790             :                 }
    1791             :             }
    1792             : 
    1793        2402 :             POP_STATE();
    1794             :         }
    1795             :     }
    1796             : 
    1797       21990 :     apsXMLNode.pop_back();
    1798             : 
    1799       21990 :     return OGRERR_NONE;
    1800             : }
    1801             : 
    1802             : /************************************************************************/
    1803             : /*                    endElementCityGMLGenericAttr()                    */
    1804             : /************************************************************************/
    1805          84 : OGRErr GMLHandler::endElementCityGMLGenericAttr()
    1806             : 
    1807             : {
    1808          84 :     if (m_pszCityGMLGenericAttrName != nullptr && m_bInCurField)
    1809             :     {
    1810          42 :         if (m_pszCurField != nullptr)
    1811             :         {
    1812          42 :             m_poReader->SetFeaturePropertyDirectly(m_pszCityGMLGenericAttrName,
    1813             :                                                    m_pszCurField, -1);
    1814             :         }
    1815          42 :         m_pszCurField = nullptr;
    1816          42 :         m_nCurFieldLen = 0;
    1817          42 :         m_nCurFieldAlloc = 0;
    1818          42 :         m_bInCurField = false;
    1819          42 :         CPLFree(m_pszCityGMLGenericAttrName);
    1820          42 :         m_pszCityGMLGenericAttrName = nullptr;
    1821             :     }
    1822             : 
    1823          84 :     if (m_inCityGMLGenericAttrDepth == m_nDepth)
    1824             :     {
    1825          42 :         POP_STATE();
    1826             :     }
    1827             : 
    1828          84 :     return OGRERR_NONE;
    1829             : }
    1830             : 
    1831             : /************************************************************************/
    1832             : /*                        endElementAttribute()                         */
    1833             : /************************************************************************/
    1834        8398 : OGRErr GMLHandler::endElementAttribute()
    1835             : 
    1836             : {
    1837        8398 :     GMLReadState *poState = m_poReader->GetState();
    1838             : 
    1839        8398 :     if (m_bInCurField)
    1840             :     {
    1841        6159 :         if (m_pszCurField == nullptr && m_poReader->IsEmptyAsNull())
    1842             :         {
    1843          31 :             if (m_pszValue != nullptr)
    1844             :             {
    1845          10 :                 m_poReader->SetFeaturePropertyDirectly(poState->osPath.c_str(),
    1846             :                                                        m_pszValue, -1);
    1847          10 :                 m_pszValue = nullptr;
    1848             :             }
    1849             :         }
    1850             :         else
    1851             :         {
    1852        6128 :             m_poReader->SetFeaturePropertyDirectly(
    1853             :                 poState->osPath.c_str(),
    1854        6128 :                 m_pszCurField ? m_pszCurField : CPLStrdup(""),
    1855             :                 m_nAttributeIndex);
    1856        6128 :             m_pszCurField = nullptr;
    1857             :         }
    1858             : 
    1859        6159 :         if (m_pszHref != nullptr)
    1860             :         {
    1861           4 :             CPLString osPropNameHref = poState->osPath + "_href";
    1862           4 :             m_poReader->SetFeaturePropertyDirectly(osPropNameHref, m_pszHref,
    1863             :                                                    -1);
    1864           4 :             m_pszHref = nullptr;
    1865             :         }
    1866             : 
    1867        6159 :         if (m_pszUom != nullptr)
    1868             :         {
    1869          14 :             CPLString osPropNameUom = poState->osPath + "_uom";
    1870          14 :             m_poReader->SetFeaturePropertyDirectly(osPropNameUom, m_pszUom, -1);
    1871          14 :             m_pszUom = nullptr;
    1872             :         }
    1873             : 
    1874        6159 :         if (m_pszKieli != nullptr)
    1875             :         {
    1876           7 :             CPLString osPropName = poState->osPath + "_kieli";
    1877           7 :             m_poReader->SetFeaturePropertyDirectly(osPropName, m_pszKieli, -1);
    1878           7 :             m_pszKieli = nullptr;
    1879             :         }
    1880             : 
    1881        6159 :         m_nCurFieldLen = 0;
    1882        6159 :         m_nCurFieldAlloc = 0;
    1883        6159 :         m_bInCurField = false;
    1884        6159 :         m_nAttributeIndex = -1;
    1885             : 
    1886        6159 :         CPLFree(m_pszValue);
    1887        6159 :         m_pszValue = nullptr;
    1888             :     }
    1889             : 
    1890        8398 :     poState->PopPath();
    1891             : 
    1892        8398 :     if (m_nAttributeDepth == m_nDepth)
    1893             :     {
    1894        6805 :         POP_STATE();
    1895             :     }
    1896             : 
    1897        8398 :     return OGRERR_NONE;
    1898             : }
    1899             : 
    1900             : /************************************************************************/
    1901             : /*                    startElementFeatureProperty()                     */
    1902             : /************************************************************************/
    1903             : 
    1904         165 : OGRErr GMLHandler::startElementFeatureProperty(const char * /*pszName*/,
    1905             :                                                int /*nLenName*/, void *attr)
    1906             : {
    1907         165 :     if (m_nDepth == m_nAttributeDepth + 1)
    1908             :     {
    1909          24 :         const char *pszGMLId = GetFID(attr);
    1910          24 :         if (pszGMLId != nullptr)
    1911             :         {
    1912          24 :             m_poReader->SetFeaturePropertyDirectly(
    1913             :                 nullptr, CPLStrdup(CPLSPrintf("#%s", pszGMLId)),
    1914             :                 m_nAttributeIndex);
    1915             :         }
    1916             :     }
    1917             : 
    1918         165 :     return OGRERR_NONE;
    1919             : }
    1920             : 
    1921             : /************************************************************************/
    1922             : /*                      endElementFeatureProperty()                      */
    1923             : /************************************************************************/
    1924             : 
    1925         189 : OGRErr GMLHandler::endElementFeatureProperty()
    1926             : 
    1927             : {
    1928         189 :     if (m_nDepth == m_nAttributeDepth)
    1929             :     {
    1930          24 :         GMLReadState *poState = m_poReader->GetState();
    1931          24 :         poState->PopPath();
    1932             : 
    1933          24 :         POP_STATE();
    1934             :     }
    1935         189 :     return OGRERR_NONE;
    1936             : }
    1937             : 
    1938             : /************************************************************************/
    1939             : /*                          endElementFeature()                         */
    1940             : /************************************************************************/
    1941        5070 : OGRErr GMLHandler::endElementFeature()
    1942             : 
    1943             : {
    1944             :     /* -------------------------------------------------------------------- */
    1945             :     /*      If we are collecting a feature, and this element tag matches    */
    1946             :     /*      element name for the class, then we have finished the           */
    1947             :     /*      feature, and we pop the feature read state.                     */
    1948             :     /* -------------------------------------------------------------------- */
    1949        5070 :     if (m_nDepth == m_nDepthFeature)
    1950             :     {
    1951        2457 :         m_oMapElementToSubstitute.clear();
    1952        2457 :         m_poReader->PopState();
    1953             : 
    1954        2457 :         POP_STATE();
    1955             :     }
    1956             : 
    1957             :     /* -------------------------------------------------------------------- */
    1958             :     /*      Otherwise, we just pop the element off the local read states    */
    1959             :     /*      element stack.                                                  */
    1960             :     /* -------------------------------------------------------------------- */
    1961             :     else
    1962             :     {
    1963        2613 :         m_poReader->GetState()->PopPath();
    1964             :     }
    1965             : 
    1966        5070 :     return OGRERR_NONE;
    1967             : }
    1968             : 
    1969             : /************************************************************************/
    1970             : /*                          endElementDefault()                         */
    1971             : /************************************************************************/
    1972        3639 : OGRErr GMLHandler::endElementDefault()
    1973             : 
    1974             : {
    1975        3639 :     if (m_nDepth > 0)
    1976        3144 :         m_poReader->GetState()->PopPath();
    1977             : 
    1978        3639 :     return OGRERR_NONE;
    1979             : }
    1980             : 
    1981             : /************************************************************************/
    1982             : /*                         dataHandlerAttribute()                       */
    1983             : /************************************************************************/
    1984             : 
    1985       10583 : OGRErr GMLHandler::dataHandlerAttribute(const char *data, int nLen)
    1986             : 
    1987             : {
    1988       10583 :     if (!m_bInCurField)
    1989        2313 :         return OGRERR_NONE;
    1990             : 
    1991        8270 :     int nIter = 0;
    1992             : 
    1993             :     // Ignore white space.
    1994        8270 :     if (m_nCurFieldLen == 0)
    1995             :     {
    1996       17227 :         while (nIter < nLen)
    1997             :         {
    1998       15129 :             const char ch = data[nIter];
    1999       15129 :             if (!(ch == ' ' || ch == 10 || ch == 13 || ch == '\t'))
    2000        6170 :                 break;
    2001        8959 :             nIter++;
    2002             :         }
    2003             :     }
    2004             : 
    2005        8270 :     const int nCharsLen = nLen - nIter;
    2006             : 
    2007        8270 :     if (nCharsLen > INT_MAX - static_cast<int>(m_nCurFieldLen) - 1)
    2008             :     {
    2009           0 :         CPLError(CE_Failure, CPLE_OutOfMemory,
    2010             :                  "Too much data in a single element");
    2011           0 :         return OGRERR_NOT_ENOUGH_MEMORY;
    2012             :     }
    2013        8270 :     if (m_nCurFieldLen + nCharsLen + 1 > m_nCurFieldAlloc)
    2014             :     {
    2015        7096 :         if (m_nCurFieldAlloc < INT_MAX - m_nCurFieldAlloc / 3 - nCharsLen - 1)
    2016        7096 :             m_nCurFieldAlloc =
    2017        7096 :                 m_nCurFieldAlloc + m_nCurFieldAlloc / 3 + nCharsLen + 1;
    2018             :         else
    2019           0 :             m_nCurFieldAlloc = m_nCurFieldLen + nCharsLen + 1;
    2020             :         char *pszNewCurField = static_cast<char *>(
    2021        7096 :             VSI_REALLOC_VERBOSE(m_pszCurField, m_nCurFieldAlloc));
    2022        7096 :         if (pszNewCurField == nullptr)
    2023             :         {
    2024           0 :             return OGRERR_NOT_ENOUGH_MEMORY;
    2025             :         }
    2026        7096 :         m_pszCurField = pszNewCurField;
    2027             :     }
    2028        8270 :     memcpy(m_pszCurField + m_nCurFieldLen, data + nIter, nCharsLen);
    2029        8270 :     m_nCurFieldLen += nCharsLen;
    2030        8270 :     m_pszCurField[m_nCurFieldLen] = '\0';
    2031             : 
    2032        8270 :     return OGRERR_NONE;
    2033             : }
    2034             : 
    2035             : /************************************************************************/
    2036             : /*                         dataHandlerGeometry()                        */
    2037             : /************************************************************************/
    2038             : 
    2039       54776 : OGRErr GMLHandler::dataHandlerGeometry(const char *data, int nLen)
    2040             : 
    2041             : {
    2042       54776 :     int nIter = 0;
    2043             : 
    2044             :     // Ignore white space
    2045       54776 :     if (m_nGeomLen == 0)
    2046             :     {
    2047      404410 :         while (nIter < nLen)
    2048             :         {
    2049      353418 :             char ch = data[nIter];
    2050      353418 :             if (!(ch == ' ' || ch == 10 || ch == 13 || ch == '\t'))
    2051        3751 :                 break;
    2052      349667 :             nIter++;
    2053             :         }
    2054             :     }
    2055             : 
    2056       54776 :     const int nCharsLen = nLen - nIter;
    2057       54776 :     if (nCharsLen)
    2058             :     {
    2059        3784 :         if (nCharsLen > INT_MAX - static_cast<int>(m_nGeomLen) - 1)
    2060             :         {
    2061           0 :             CPLError(CE_Failure, CPLE_OutOfMemory,
    2062             :                      "Too much data in a single element");
    2063           0 :             return OGRERR_NOT_ENOUGH_MEMORY;
    2064             :         }
    2065        3784 :         if (m_nGeomLen + nCharsLen + 1 > m_nGeomAlloc)
    2066             :         {
    2067        3779 :             if (m_nGeomAlloc < INT_MAX - m_nGeomAlloc / 3 - nCharsLen - 1)
    2068        3779 :                 m_nGeomAlloc = m_nGeomAlloc + m_nGeomAlloc / 3 + nCharsLen + 1;
    2069             :             else
    2070           0 :                 m_nGeomAlloc = m_nGeomAlloc + nCharsLen + 1;
    2071             :             char *pszNewGeometry = static_cast<char *>(
    2072        3779 :                 VSI_REALLOC_VERBOSE(m_pszGeometry, m_nGeomAlloc));
    2073        3779 :             if (pszNewGeometry == nullptr)
    2074             :             {
    2075           0 :                 return OGRERR_NOT_ENOUGH_MEMORY;
    2076             :             }
    2077        3779 :             m_pszGeometry = pszNewGeometry;
    2078             :         }
    2079        3784 :         memcpy(m_pszGeometry + m_nGeomLen, data + nIter, nCharsLen);
    2080        3784 :         m_nGeomLen += nCharsLen;
    2081        3784 :         m_pszGeometry[m_nGeomLen] = '\0';
    2082             :     }
    2083             : 
    2084       54776 :     return OGRERR_NONE;
    2085             : }
    2086             : 
    2087             : /************************************************************************/
    2088             : /*                         IsGeometryElement()                          */
    2089             : /************************************************************************/
    2090             : 
    2091       13617 : bool GMLHandler::IsGeometryElement(const char *pszElement)
    2092             : 
    2093             : {
    2094       13617 :     int nFirst = 0;
    2095       13617 :     int nLast = GML_GEOMETRY_TYPE_COUNT - 1;
    2096       13617 :     unsigned long nHash = CPLHashSetHashStr(pszElement);
    2097       47220 :     do
    2098             :     {
    2099       60837 :         const int nMiddle = (nFirst + nLast) / 2;
    2100       60837 :         if (nHash == pasGeometryNames[nMiddle].nHash)
    2101        2372 :             return strcmp(pszElement, pasGeometryNames[nMiddle].pszName) == 0;
    2102       58465 :         if (nHash < pasGeometryNames[nMiddle].nHash)
    2103       33303 :             nLast = nMiddle - 1;
    2104             :         else
    2105       25162 :             nFirst = nMiddle + 1;
    2106       58465 :     } while (nFirst <= nLast);
    2107             : 
    2108       11245 :     if (eAppSchemaType == APPSCHEMA_AIXM &&
    2109          10 :         (strcmp(pszElement, "ElevatedPoint") == 0 ||
    2110          10 :          strcmp(pszElement, "ElevatedSurface") == 0))
    2111             :     {
    2112           2 :         return true;
    2113             :     }
    2114             : 
    2115       11243 :     if (eAppSchemaType == APPSCHEMA_MTKGML &&
    2116          77 :         (strcmp(pszElement, "Piste") == 0 || strcmp(pszElement, "Alue") == 0 ||
    2117          49 :          strcmp(pszElement, "Murtoviiva") == 0))
    2118          35 :         return true;
    2119             : 
    2120       11208 :     return false;
    2121             : }

Generated by: LCOV version 1.14