LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/gmlutils - parsexsd.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 443 533 83.1 %
Date: 2025-10-27 00:14:23 Functions: 15 15 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GML Reader
       4             :  * Purpose:  Implementation of GMLParseXSD()
       5             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2005, Frank Warmerdam
       9             :  * Copyright (c) 2010-2014, Even Rouault <even dot rouault at spatialys.com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  ****************************************************************************/
      13             : 
      14             : #include "cpl_port.h"
      15             : #include "parsexsd.h"
      16             : 
      17             : #include <cmath>
      18             : #include <cstdlib>
      19             : #include <cstring>
      20             : #include <set>
      21             : #include <string>
      22             : #include <utility>
      23             : 
      24             : #include "cpl_conv.h"
      25             : #include "cpl_error.h"
      26             : #include "cpl_http.h"
      27             : #include "cpl_minixml.h"
      28             : #include "cpl_string.h"
      29             : #include "ogr_core.h"
      30             : 
      31             : /************************************************************************/
      32             : /*                              StripNS()                               */
      33             : /*                                                                      */
      34             : /*      Return potentially shortened form of string with namespace      */
      35             : /*      stripped off if there is one.  Returns pointer into             */
      36             : /*      original string.                                                */
      37             : /************************************************************************/
      38        2999 : static const char *StripNS(const char *pszFullValue)
      39             : 
      40             : {
      41        2999 :     const char *pszColon = strstr(pszFullValue, ":");
      42        2999 :     if (pszColon == nullptr)
      43         219 :         return pszFullValue;
      44             :     else
      45        2780 :         return pszColon + 1;
      46             : }
      47             : 
      48             : /************************************************************************/
      49             : /*                   GetSimpleTypeProperties()                          */
      50             : /************************************************************************/
      51             : 
      52         300 : static bool GetSimpleTypeProperties(CPLXMLNode *psTypeNode,
      53             :                                     GMLPropertyType *pGMLType, int *pnWidth,
      54             :                                     int *pnPrecision)
      55             : {
      56             :     const char *pszBase =
      57         300 :         StripNS(CPLGetXMLValue(psTypeNode, "restriction.base", ""));
      58             : 
      59         300 :     if (EQUAL(pszBase, "decimal"))
      60             :     {
      61          91 :         *pGMLType = GMLPT_Real;
      62             :         const char *pszWidth =
      63          91 :             CPLGetXMLValue(psTypeNode, "restriction.totalDigits.value", "0");
      64             :         const char *pszPrecision =
      65          91 :             CPLGetXMLValue(psTypeNode, "restriction.fractionDigits.value", "0");
      66          91 :         *pnWidth = atoi(pszWidth);
      67          91 :         *pnPrecision = atoi(pszPrecision);
      68          91 :         return true;
      69             :     }
      70             : 
      71         209 :     else if (EQUAL(pszBase, "float"))
      72             :     {
      73           1 :         *pGMLType = GMLPT_Float;
      74           1 :         return true;
      75             :     }
      76             : 
      77         208 :     else if (EQUAL(pszBase, "double"))
      78             :     {
      79           0 :         *pGMLType = GMLPT_Real;
      80           0 :         return true;
      81             :     }
      82             : 
      83         208 :     else if (EQUAL(pszBase, "integer"))
      84             :     {
      85          71 :         *pGMLType = GMLPT_Integer;
      86             :         const char *pszWidth =
      87          71 :             CPLGetXMLValue(psTypeNode, "restriction.totalDigits.value", "0");
      88          71 :         *pnWidth = atoi(pszWidth);
      89          71 :         return true;
      90             :     }
      91             : 
      92         137 :     else if (EQUAL(pszBase, "long"))
      93             :     {
      94          12 :         *pGMLType = GMLPT_Integer64;
      95             :         const char *pszWidth =
      96          12 :             CPLGetXMLValue(psTypeNode, "restriction.totalDigits.value", "0");
      97          12 :         *pnWidth = atoi(pszWidth);
      98          12 :         return true;
      99             :     }
     100             : 
     101         125 :     else if (EQUAL(pszBase, "unsignedLong"))
     102             :     {
     103             :         // Optimistically map to signed integer...
     104           0 :         *pGMLType = GMLPT_Integer64;
     105             :         const char *pszWidth =
     106           0 :             CPLGetXMLValue(psTypeNode, "restriction.totalDigits.value", "0");
     107           0 :         *pnWidth = atoi(pszWidth);
     108           0 :         return true;
     109             :     }
     110             : 
     111         125 :     else if (EQUAL(pszBase, "string"))
     112             :     {
     113         121 :         *pGMLType = GMLPT_String;
     114             :         const char *pszWidth =
     115         121 :             CPLGetXMLValue(psTypeNode, "restriction.maxLength.value", "0");
     116         121 :         *pnWidth = atoi(pszWidth);
     117         121 :         return true;
     118             :     }
     119             : 
     120           4 :     else if (EQUAL(pszBase, "date"))
     121             :     {
     122           0 :         *pGMLType = GMLPT_Date;
     123           0 :         return true;
     124             :     }
     125             : 
     126           4 :     else if (EQUAL(pszBase, "time"))
     127             :     {
     128           0 :         *pGMLType = GMLPT_Time;
     129           0 :         return true;
     130             :     }
     131             : 
     132           4 :     else if (EQUAL(pszBase, "dateTime") || EQUAL(pszBase, "TimeInstantType"))
     133             :     {
     134           0 :         *pGMLType = GMLPT_DateTime;
     135           0 :         return true;
     136             :     }
     137             : 
     138           4 :     else if (EQUAL(pszBase, "boolean"))
     139             :     {
     140           3 :         *pGMLType = GMLPT_Boolean;
     141           3 :         return true;
     142             :     }
     143             : 
     144           1 :     else if (EQUAL(pszBase, "short"))
     145             :     {
     146           1 :         *pGMLType = GMLPT_Short;
     147           1 :         return true;
     148             :     }
     149             : 
     150           0 :     return false;
     151             : }
     152             : 
     153             : /************************************************************************/
     154             : /*                      LookForSimpleType()                             */
     155             : /************************************************************************/
     156             : 
     157          11 : static bool LookForSimpleType(CPLXMLNode *psSchemaNode,
     158             :                               const char *pszStrippedNSType,
     159             :                               GMLPropertyType *pGMLType, int *pnWidth,
     160             :                               int *pnPrecision)
     161             : {
     162          11 :     CPLXMLNode *psThis = psSchemaNode->psChild;
     163         102 :     for (; psThis != nullptr; psThis = psThis->psNext)
     164             :     {
     165         220 :         if (psThis->eType == CXT_Element &&
     166          95 :             EQUAL(psThis->pszValue, "simpleType") &&
     167           2 :             EQUAL(CPLGetXMLValue(psThis, "name", ""), pszStrippedNSType))
     168             :         {
     169           2 :             break;
     170             :         }
     171             :     }
     172          11 :     if (psThis == nullptr)
     173           9 :         return false;
     174             : 
     175           2 :     return GetSimpleTypeProperties(psThis, pGMLType, pnWidth, pnPrecision);
     176             : }
     177             : 
     178             : /************************************************************************/
     179             : /*                      GetSingleChildElement()                         */
     180             : /************************************************************************/
     181             : 
     182             : /* Returns the child element whose name is pszExpectedValue only if */
     183             : /* there is only one child that is an element. */
     184           3 : static CPLXMLNode *GetSingleChildElement(CPLXMLNode *psNode,
     185             :                                          const char *pszExpectedValue)
     186             : {
     187           3 :     if (psNode == nullptr)
     188           0 :         return nullptr;
     189             : 
     190           3 :     CPLXMLNode *psIter = psNode->psChild;
     191           3 :     if (psIter == nullptr)
     192           0 :         return nullptr;
     193             : 
     194           3 :     CPLXMLNode *psChild = nullptr;
     195           9 :     while (psIter != nullptr)
     196             :     {
     197           6 :         if (psIter->eType == CXT_Element)
     198             :         {
     199           3 :             if (psChild != nullptr)
     200           0 :                 return nullptr;
     201           3 :             if (pszExpectedValue != nullptr &&
     202           3 :                 strcmp(psIter->pszValue, pszExpectedValue) != 0)
     203           0 :                 return nullptr;
     204           3 :             psChild = psIter;
     205             :         }
     206           6 :         psIter = psIter->psNext;
     207             :     }
     208           3 :     return psChild;
     209             : }
     210             : 
     211             : /************************************************************************/
     212             : /*                      CheckMinMaxOccursCardinality()                  */
     213             : /************************************************************************/
     214             : 
     215           2 : static int CheckMinMaxOccursCardinality(CPLXMLNode *psNode)
     216             : {
     217           2 :     const char *pszMinOccurs = CPLGetXMLValue(psNode, "minOccurs", nullptr);
     218           2 :     const char *pszMaxOccurs = CPLGetXMLValue(psNode, "maxOccurs", nullptr);
     219           1 :     return (pszMinOccurs == nullptr || EQUAL(pszMinOccurs, "0") ||
     220           4 :             EQUAL(pszMinOccurs, "1")) &&
     221           3 :            (pszMaxOccurs == nullptr || EQUAL(pszMaxOccurs, "1"));
     222             : }
     223             : 
     224             : /************************************************************************/
     225             : /*                     GetListTypeFromSingleType()                      */
     226             : /************************************************************************/
     227             : 
     228          11 : static GMLPropertyType GetListTypeFromSingleType(GMLPropertyType eType)
     229             : {
     230          11 :     if (eType == GMLPT_String)
     231           2 :         return GMLPT_StringList;
     232           9 :     if (eType == GMLPT_Integer || eType == GMLPT_Short)
     233           2 :         return GMLPT_IntegerList;
     234           7 :     if (eType == GMLPT_Integer64)
     235           2 :         return GMLPT_Integer64List;
     236           5 :     if (eType == GMLPT_Real || eType == GMLPT_Float)
     237           2 :         return GMLPT_RealList;
     238           3 :     if (eType == GMLPT_Boolean)
     239           1 :         return GMLPT_BooleanList;
     240           2 :     if (eType == GMLPT_FeatureProperty)
     241           2 :         return GMLPT_FeaturePropertyList;
     242           0 :     return eType;
     243             : }
     244             : 
     245             : /************************************************************************/
     246             : /*                      ParseFeatureType()                              */
     247             : /************************************************************************/
     248             : 
     249             : typedef struct
     250             : {
     251             :     const char *pszName;
     252             :     OGRwkbGeometryType eType;
     253             : } AssocNameType;
     254             : 
     255             : static const AssocNameType apsPropertyTypes[] = {
     256             :     {"GeometryPropertyType", wkbUnknown},
     257             :     {"PointPropertyType", wkbPoint},
     258             :     {"LineStringPropertyType", wkbLineString},
     259             :     {"CurvePropertyType", wkbCompoundCurve},
     260             :     {"PolygonPropertyType", wkbPolygon},
     261             :     {"SurfacePropertyType", wkbCurvePolygon},
     262             :     {"MultiPointPropertyType", wkbMultiPoint},
     263             :     {"MultiLineStringPropertyType", wkbMultiLineString},
     264             :     {"MultiCurvePropertyType", wkbMultiCurve},
     265             :     {"MultiPolygonPropertyType", wkbMultiPolygon},
     266             :     {"MultiSurfacePropertyType", wkbMultiSurface},
     267             :     {"MultiGeometryPropertyType", wkbGeometryCollection},
     268             :     {"GeometryAssociationType", wkbUnknown},
     269             :     {nullptr, wkbUnknown},
     270             : };
     271             : 
     272             : /* Found in FME .xsd  (e.g. <element ref="gml:curveProperty" minOccurs="0"/>) */
     273             : static const AssocNameType apsRefTypes[] = {
     274             :     {"pointProperty", wkbPoint},
     275             :     {"curveProperty", wkbLineString},  // Should we promote to wkbCompoundCurve?
     276             :     {"surfaceProperty", wkbPolygon},   // Should we promote to wkbCurvePolygon?
     277             :     {"multiPointProperty", wkbMultiPoint},
     278             :     {"multiCurveProperty", wkbMultiLineString},
     279             :     // Should we promote to wkbMultiSurface?
     280             :     {"multiSurfaceProperty", wkbMultiPolygon},
     281             :     {nullptr, wkbUnknown},
     282             : };
     283             : 
     284             : static GMLFeatureClass *GMLParseFeatureType(CPLXMLNode *psSchemaNode,
     285             :                                             const char *pszName,
     286             :                                             CPLXMLNode *psThis);
     287             : 
     288         584 : static GMLFeatureClass *GMLParseFeatureType(CPLXMLNode *psSchemaNode,
     289             :                                             const char *pszName,
     290             :                                             const char *pszType)
     291             : {
     292         584 :     CPLXMLNode *psThis = psSchemaNode->psChild;
     293        5226 :     for (; psThis != nullptr; psThis = psThis->psNext)
     294             :     {
     295       12969 :         if (psThis->eType == CXT_Element &&
     296        6201 :             EQUAL(psThis->pszValue, "complexType") &&
     297         975 :             EQUAL(CPLGetXMLValue(psThis, "name", ""), pszType))
     298             :         {
     299         584 :             break;
     300             :         }
     301             :     }
     302         584 :     if (psThis == nullptr)
     303           0 :         return nullptr;
     304             : 
     305         584 :     return GMLParseFeatureType(psSchemaNode, pszName, psThis);
     306             : }
     307             : 
     308         598 : static GMLFeatureClass *GMLParseFeatureType(CPLXMLNode *psSchemaNode,
     309             :                                             const char *pszName,
     310             :                                             CPLXMLNode *psComplexType)
     311             : {
     312             : 
     313             :     /* -------------------------------------------------------------------- */
     314             :     /*      Grab the sequence of extensions greatgrandchild.                */
     315             :     /* -------------------------------------------------------------------- */
     316             :     CPLXMLNode *psAttrSeq =
     317         598 :         CPLGetXMLNode(psComplexType, "complexContent.extension.sequence");
     318             : 
     319         598 :     if (psAttrSeq == nullptr)
     320             :     {
     321           0 :         return nullptr;
     322             :     }
     323             : 
     324             :     /* -------------------------------------------------------------------- */
     325             :     /*      We are pretty sure this going to be a valid Feature class       */
     326             :     /*      now, so create it.                                              */
     327             :     /* -------------------------------------------------------------------- */
     328         598 :     GMLFeatureClass *poClass = new GMLFeatureClass(pszName);
     329             : 
     330             :     /* -------------------------------------------------------------------- */
     331             :     /*      Loop over each of the attribute elements being defined for      */
     332             :     /*      this feature class.                                             */
     333             :     /* -------------------------------------------------------------------- */
     334         598 :     int nAttributeIndex = 0;
     335             : 
     336         598 :     bool bGotUnrecognizedType = false;
     337             : 
     338         598 :     CPLXMLNode *psAttrDef = psAttrSeq->psChild;
     339        2945 :     for (; psAttrDef != nullptr; psAttrDef = psAttrDef->psNext)
     340             :     {
     341        2356 :         if (strcmp(psAttrDef->pszValue, "group") == 0)
     342             :         {
     343             :             /* Too complex schema for us. Aborts parsing */
     344           0 :             delete poClass;
     345           9 :             return nullptr;
     346             :         }
     347             : 
     348             :         /* Parse stuff like:
     349             :         <xs:choice>
     350             :             <xs:element ref="gml:polygonProperty"/>
     351             :             <xs:element ref="gml:multiPolygonProperty"/>
     352             :         </xs:choice>
     353             :         as found in
     354             :         https://downloadagiv.blob.core.windows.net/overstromingsgebieden-en-oeverzones/2014_01/Overstromingsgebieden_en_oeverzones_2014_01_GML.zip
     355             :         */
     356        2356 :         if (strcmp(psAttrDef->pszValue, "choice") == 0)
     357             :         {
     358           1 :             CPLXMLNode *psChild = psAttrDef->psChild;
     359           1 :             bool bPolygon = false;
     360           1 :             bool bMultiPolygon = false;
     361           3 :             for (; psChild; psChild = psChild->psNext)
     362             :             {
     363           2 :                 if (psChild->eType != CXT_Element)
     364           0 :                     continue;
     365           2 :                 if (strcmp(psChild->pszValue, "element") == 0)
     366             :                 {
     367             :                     const char *pszRef =
     368           2 :                         CPLGetXMLValue(psChild, "ref", nullptr);
     369           2 :                     if (pszRef != nullptr)
     370             :                     {
     371           2 :                         if (strcmp(pszRef, "gml:polygonProperty") == 0)
     372             :                         {
     373           1 :                             bPolygon = true;
     374             :                         }
     375           1 :                         else if (strcmp(pszRef, "gml:multiPolygonProperty") ==
     376             :                                  0)
     377             :                         {
     378           1 :                             bMultiPolygon = true;
     379             :                         }
     380             :                         else
     381             :                         {
     382           0 :                             delete poClass;
     383           0 :                             return nullptr;
     384             :                         }
     385             :                     }
     386             :                     else
     387             :                     {
     388           0 :                         delete poClass;
     389           0 :                         return nullptr;
     390             :                     }
     391             :                 }
     392             :             }
     393           1 :             if (bPolygon && bMultiPolygon)
     394             :             {
     395           2 :                 poClass->AddGeometryProperty(new GMLGeometryPropertyDefn(
     396           1 :                     "", "", wkbMultiPolygon, nAttributeIndex, true));
     397             : 
     398           1 :                 nAttributeIndex++;
     399             :             }
     400        2049 :             continue;
     401             :         }
     402             : 
     403        2355 :         if (!EQUAL(psAttrDef->pszValue, "element"))
     404          93 :             continue;
     405             : 
     406             :         // MapServer WFS writes element type as an attribute of element
     407             :         // not as a simpleType definition.
     408        2262 :         const char *pszType = CPLGetXMLValue(psAttrDef, "type", nullptr);
     409        2262 :         const char *pszElementName = CPLGetXMLValue(psAttrDef, "name", nullptr);
     410             :         bool bNullable =
     411        2262 :             EQUAL(CPLGetXMLValue(psAttrDef, "minOccurs", "1"), "0");
     412             :         const char *pszMaxOccurs =
     413        2262 :             CPLGetXMLValue(psAttrDef, "maxOccurs", nullptr);
     414        2262 :         if (pszType != nullptr)
     415             :         {
     416        1945 :             const char *pszStrippedNSType = StripNS(pszType);
     417        1945 :             int nWidth = 0;
     418        1945 :             int nPrecision = 0;
     419             : 
     420        1945 :             GMLPropertyType gmlType = GMLPT_Untyped;
     421        1945 :             if (EQUAL(pszStrippedNSType, "string") ||
     422        1436 :                 EQUAL(pszStrippedNSType, "Character"))
     423         509 :                 gmlType = GMLPT_String;
     424        1436 :             else if (EQUAL(pszStrippedNSType, "date"))
     425          64 :                 gmlType = GMLPT_Date;
     426        1372 :             else if (EQUAL(pszStrippedNSType, "time"))
     427           3 :                 gmlType = GMLPT_Time;
     428        1369 :             else if (EQUAL(pszStrippedNSType, "dateTime") ||
     429        1214 :                      EQUAL(pszStrippedNSType, "TimeInstantType"))
     430         156 :                 gmlType = GMLPT_DateTime;
     431        1213 :             else if (EQUAL(pszStrippedNSType, "real") ||
     432        1213 :                      EQUAL(pszStrippedNSType, "double") ||
     433        1089 :                      EQUAL(pszStrippedNSType, "decimal"))
     434         156 :                 gmlType = GMLPT_Real;
     435        1057 :             else if (EQUAL(pszStrippedNSType, "float"))
     436         115 :                 gmlType = GMLPT_Float;
     437         942 :             else if (EQUAL(pszStrippedNSType, "int") ||
     438         808 :                      EQUAL(pszStrippedNSType, "integer"))
     439         135 :                 gmlType = GMLPT_Integer;
     440         807 :             else if (EQUAL(pszStrippedNSType, "long"))
     441           9 :                 gmlType = GMLPT_Integer64;
     442         798 :             else if (EQUAL(pszStrippedNSType, "unsignedLong"))
     443             :             {
     444             :                 // Optimistically map to signed integer
     445           0 :                 gmlType = GMLPT_Integer64;
     446             :             }
     447         798 :             else if (EQUAL(pszStrippedNSType, "short"))
     448         119 :                 gmlType = GMLPT_Short;
     449         679 :             else if (EQUAL(pszStrippedNSType, "boolean"))
     450         115 :                 gmlType = GMLPT_Boolean;
     451             :             // TODO: Would be nice to have a binary type.
     452         564 :             else if (EQUAL(pszStrippedNSType, "hexBinary"))
     453           0 :                 gmlType = GMLPT_String;
     454         564 :             else if (strcmp(pszType, "gml:FeaturePropertyType") == 0)
     455             :             {
     456           3 :                 gmlType = GMLPT_FeatureProperty;
     457             :             }
     458         561 :             else if (STARTS_WITH(pszType, "gml:"))
     459             :             {
     460         550 :                 const AssocNameType *psIter = apsPropertyTypes;
     461        1652 :                 while (psIter->pszName)
     462             :                 {
     463        1652 :                     if (strncmp(pszType + 4, psIter->pszName,
     464        1652 :                                 strlen(psIter->pszName)) == 0)
     465             :                     {
     466         550 :                         OGRwkbGeometryType eType = psIter->eType;
     467        1100 :                         std::string osSRSName;
     468             : 
     469             :                         // Look if there's a comment restricting to subclasses.
     470         550 :                         for (const CPLXMLNode *psIter2 = psAttrDef->psNext;
     471        1026 :                              psIter2 != nullptr; psIter2 = psIter2->psNext)
     472             :                         {
     473         476 :                             if (psIter2->eType == CXT_Comment)
     474             :                             {
     475         102 :                                 if (strstr(psIter2->pszValue,
     476             :                                            "restricted to Polygon"))
     477          17 :                                     eType = wkbPolygon;
     478          85 :                                 else if (strstr(psIter2->pszValue,
     479             :                                                 "restricted to LineString"))
     480          25 :                                     eType = wkbLineString;
     481          60 :                                 else if (strstr(psIter2->pszValue,
     482             :                                                 "restricted to MultiPolygon"))
     483          13 :                                     eType = wkbMultiPolygon;
     484          47 :                                 else if (strstr(
     485          47 :                                              psIter2->pszValue,
     486             :                                              "restricted to MultiLineString"))
     487          11 :                                     eType = wkbMultiLineString;
     488             :                                 else
     489             :                                 {
     490             :                                     const char *pszSRSName =
     491          36 :                                         strstr(psIter2->pszValue, "srsName=\"");
     492          36 :                                     if (pszSRSName)
     493             :                                     {
     494             :                                         osSRSName =
     495          32 :                                             pszSRSName + strlen("srsName=\"");
     496          32 :                                         const auto nPos = osSRSName.find('"');
     497          32 :                                         if (nPos != std::string::npos)
     498          32 :                                             osSRSName.resize(nPos);
     499             :                                         else
     500           0 :                                             osSRSName.clear();
     501             :                                     }
     502             :                                 }
     503             :                             }
     504             :                         }
     505             : 
     506             :                         // Try to get coordinate precision from a construct like:
     507             :                         /*
     508             :                             <xs:element name="wkb_geometry" type="gml:SurfacePropertyType" nillable="true" minOccurs="0" maxOccurs="1">
     509             :                                 <xs:annotation>
     510             :                                   <xs:appinfo source="http://ogr.maptools.org/">
     511             :                                     <ogr:xy_coordinate_resolution>8.9e-9</ogr:xy_coordinate_resolution>
     512             :                                     <ogr:z_coordinate_resolution>1e-3</ogr:z_coordinate_resolution>
     513             :                                     <ogr:m_coordinate_resolution>1e-3</ogr:m_coordinate_resolution>
     514             :                                   </xs:appinfo>
     515             :                                 </xs:annotation>
     516             :                             </xs:element>
     517             :                         */
     518        1100 :                         OGRGeomCoordinatePrecision oGeomCoordPrec;
     519             :                         const auto psAnnotation =
     520         550 :                             CPLGetXMLNode(psAttrDef, "annotation");
     521         550 :                         if (psAnnotation)
     522             :                         {
     523           1 :                             for (const CPLXMLNode *psIterAppinfo =
     524             :                                      psAnnotation->psChild;
     525           2 :                                  psIterAppinfo;
     526           1 :                                  psIterAppinfo = psIterAppinfo->psNext)
     527             :                             {
     528           3 :                                 if (psIterAppinfo->eType == CXT_Element &&
     529           1 :                                     strcmp(psIterAppinfo->pszValue,
     530           2 :                                            "appinfo") == 0 &&
     531           1 :                                     strcmp(CPLGetXMLValue(psIterAppinfo,
     532             :                                                           "source", ""),
     533             :                                            "http://ogr.maptools.org/") == 0)
     534             :                                 {
     535           1 :                                     if (const char *pszXYRes = CPLGetXMLValue(
     536             :                                             psIterAppinfo,
     537             :                                             "xy_coordinate_resolution",
     538             :                                             nullptr))
     539             :                                     {
     540           1 :                                         const double dfVal = CPLAtof(pszXYRes);
     541           1 :                                         if (dfVal > 0 && std::isfinite(dfVal))
     542           1 :                                             oGeomCoordPrec.dfXYResolution =
     543             :                                                 dfVal;
     544             :                                     }
     545           1 :                                     if (const char *pszZRes = CPLGetXMLValue(
     546             :                                             psIterAppinfo,
     547             :                                             "z_coordinate_resolution", nullptr))
     548             :                                     {
     549           1 :                                         const double dfVal = CPLAtof(pszZRes);
     550           1 :                                         if (dfVal > 0 && std::isfinite(dfVal))
     551           1 :                                             oGeomCoordPrec.dfZResolution =
     552             :                                                 dfVal;
     553             :                                     }
     554             :                                 }
     555             :                             }
     556             :                         }
     557             : 
     558             :                         GMLGeometryPropertyDefn *poDefn =
     559             :                             new GMLGeometryPropertyDefn(
     560             :                                 pszElementName, pszElementName, eType,
     561         550 :                                 nAttributeIndex, bNullable, oGeomCoordPrec);
     562         550 :                         poDefn->SetSRSName(osSRSName);
     563             : 
     564         550 :                         if (poClass->AddGeometryProperty(poDefn) < 0)
     565           0 :                             delete poDefn;
     566             :                         else
     567         550 :                             nAttributeIndex++;
     568             : 
     569         550 :                         break;
     570             :                     }
     571             : 
     572        1102 :                     psIter++;
     573             :                 }
     574             : 
     575         550 :                 if (psIter->pszName == nullptr)
     576             :                 {
     577             :                     // Can be a non geometry gml type.
     578             :                     // Too complex schema for us. Aborts parsing.
     579           0 :                     delete poClass;
     580           9 :                     return nullptr;
     581             :                 }
     582             : 
     583         550 :                 if (poClass->GetGeometryPropertyCount() == 0)
     584           0 :                     bGotUnrecognizedType = true;
     585             : 
     586        1936 :                 continue;
     587             :             }
     588             : 
     589             :             /* Integraph stuff */
     590          11 :             else if (strcmp(pszType, "G:Point_MultiPointPropertyType") == 0 ||
     591          11 :                      strcmp(pszType, "gmgml:Point_MultiPointPropertyType") == 0)
     592             :             {
     593             :                 GMLGeometryPropertyDefn *poDefn = new GMLGeometryPropertyDefn(
     594             :                     pszElementName, pszElementName, wkbMultiPoint,
     595           0 :                     nAttributeIndex, bNullable);
     596             : 
     597           0 :                 if (poClass->AddGeometryProperty(poDefn) < 0)
     598           0 :                     delete poDefn;
     599             :                 else
     600           0 :                     nAttributeIndex++;
     601             : 
     602           0 :                 continue;
     603             :             }
     604          11 :             else if (strcmp(pszType,
     605          11 :                             "G:LineString_MultiLineStringPropertyType") == 0 ||
     606          11 :                      strcmp(pszType,
     607             :                             "gmgml:LineString_MultiLineStringPropertyType") ==
     608             :                          0)
     609             :             {
     610             :                 GMLGeometryPropertyDefn *poDefn = new GMLGeometryPropertyDefn(
     611             :                     pszElementName, pszElementName, wkbMultiLineString,
     612           0 :                     nAttributeIndex, bNullable);
     613             : 
     614           0 :                 if (poClass->AddGeometryProperty(poDefn) < 0)
     615           0 :                     delete poDefn;
     616             :                 else
     617           0 :                     nAttributeIndex++;
     618             : 
     619           0 :                 continue;
     620             :             }
     621          11 :             else if (strcmp(pszType, "G:Polygon_MultiPolygonPropertyType") ==
     622          11 :                          0 ||
     623          11 :                      strcmp(pszType,
     624          11 :                             "gmgml:Polygon_MultiPolygonPropertyType") == 0 ||
     625          11 :                      strcmp(pszType, "gmgml:Polygon_Surface_MultiSurface_"
     626             :                                      "CompositeSurfacePropertyType") == 0)
     627             :             {
     628             :                 GMLGeometryPropertyDefn *poDefn = new GMLGeometryPropertyDefn(
     629             :                     pszElementName, pszElementName, wkbMultiPolygon,
     630           0 :                     nAttributeIndex, bNullable);
     631             : 
     632           0 :                 if (poClass->AddGeometryProperty(poDefn) < 0)
     633           0 :                     delete poDefn;
     634             :                 else
     635           0 :                     nAttributeIndex++;
     636             : 
     637           0 :                 continue;
     638             :             }
     639             : 
     640             :             // ERDAS Apollo stufflike in
     641             :             // http://apollo.erdas.com/erdas-apollo/vector/WORLDWIDE?SERVICE=WFS&VERSION=1.0.0&REQUEST=DescribeFeatureType&TYPENAME=wfs:cntry98)
     642          11 :             else if (strcmp(pszType, "wfs:MixedPolygonPropertyType") == 0)
     643             :             {
     644             :                 GMLGeometryPropertyDefn *poDefn = new GMLGeometryPropertyDefn(
     645             :                     pszElementName, pszElementName, wkbMultiPolygon,
     646           0 :                     nAttributeIndex, bNullable);
     647             : 
     648           0 :                 if (poClass->AddGeometryProperty(poDefn) < 0)
     649           0 :                     delete poDefn;
     650             :                 else
     651           0 :                     nAttributeIndex++;
     652             : 
     653           0 :                 continue;
     654             :             }
     655             : 
     656             :             else
     657             :             {
     658          11 :                 gmlType = GMLPT_Untyped;
     659          11 :                 if (!LookForSimpleType(psSchemaNode, pszStrippedNSType,
     660             :                                        &gmlType, &nWidth, &nPrecision))
     661             :                 {
     662             :                     // Too complex schema for us. Aborts parsing.
     663           9 :                     delete poClass;
     664           9 :                     return nullptr;
     665             :                 }
     666             :             }
     667             : 
     668        1386 :             if (pszElementName == nullptr)
     669           0 :                 pszElementName = "unnamed";
     670        1386 :             const char *pszPropertyName = pszElementName;
     671        1386 :             if (gmlType == GMLPT_FeatureProperty)
     672             :             {
     673           3 :                 pszPropertyName = CPLSPrintf("%s_href", pszElementName);
     674             :             }
     675             :             GMLPropertyDefn *poProp =
     676        1386 :                 new GMLPropertyDefn(pszPropertyName, pszElementName);
     677             : 
     678        1386 :             if (pszMaxOccurs != nullptr && strcmp(pszMaxOccurs, "1") != 0)
     679           2 :                 gmlType = GetListTypeFromSingleType(gmlType);
     680             : 
     681        1386 :             poProp->SetType(gmlType);
     682        1386 :             poProp->SetWidth(nWidth);
     683        1386 :             poProp->SetPrecision(nPrecision);
     684        1386 :             poProp->SetNullable(bNullable);
     685             : 
     686             :             const CPLXMLNode *psAnnotation =
     687        1386 :                 CPLGetXMLNode(psAttrDef, "annotation");
     688        1386 :             if (psAnnotation)
     689             :             {
     690             :                 const char *pszDocumentation =
     691           3 :                     CPLGetXMLValue(psAnnotation, "documentation", nullptr);
     692           3 :                 if (pszDocumentation)
     693           3 :                     poProp->SetDocumentation(pszDocumentation);
     694             :             }
     695             : 
     696        1386 :             if (poClass->AddProperty(poProp) < 0)
     697           0 :                 delete poProp;
     698             :             else
     699        1386 :                 nAttributeIndex++;
     700             : 
     701        1386 :             continue;
     702             :         }
     703             : 
     704             :         // For now we skip geometries.  Fixup later.
     705         317 :         CPLXMLNode *psSimpleType = CPLGetXMLNode(psAttrDef, "simpleType");
     706         317 :         if (psSimpleType == nullptr)
     707             :         {
     708          19 :             const char *pszRef = CPLGetXMLValue(psAttrDef, "ref", nullptr);
     709             : 
     710             :             // FME .xsd
     711          19 :             if (pszRef != nullptr && STARTS_WITH(pszRef, "gml:"))
     712             :             {
     713          18 :                 const AssocNameType *psIter = apsRefTypes;
     714          63 :                 while (psIter->pszName)
     715             :                 {
     716          63 :                     if (strncmp(pszRef + 4, psIter->pszName,
     717          63 :                                 strlen(psIter->pszName)) == 0)
     718             :                     {
     719          18 :                         if (poClass->GetGeometryPropertyCount() > 0)
     720             :                         {
     721           9 :                             OGRwkbGeometryType eNewType = psIter->eType;
     722             :                             OGRwkbGeometryType eOldType =
     723             :                                 static_cast<OGRwkbGeometryType>(
     724           9 :                                     poClass->GetGeometryProperty(0)->GetType());
     725             : 
     726           9 :                             if ((eNewType == wkbMultiPoint &&
     727           6 :                                  eOldType == wkbPoint) ||
     728           3 :                                 (eNewType == wkbMultiLineString &&
     729           3 :                                  eOldType == wkbLineString) ||
     730           3 :                                 (eNewType == wkbMultiPolygon &&
     731             :                                  eOldType == wkbPolygon))
     732             :                             {
     733           9 :                                 poClass->GetGeometryProperty(0)->SetType(
     734             :                                     eNewType);
     735             :                             }
     736             :                             else
     737             :                             {
     738           0 :                                 CPLDebug("GML",
     739             :                                          "Geometry field already found ! "
     740             :                                          "Ignoring the following ones");
     741             :                             }
     742             :                         }
     743             :                         else
     744             :                         {
     745             :                             GMLGeometryPropertyDefn *poDefn =
     746             :                                 new GMLGeometryPropertyDefn(
     747             :                                     pszElementName, pszElementName,
     748           9 :                                     psIter->eType, nAttributeIndex, true);
     749             : 
     750           9 :                             if (poClass->AddGeometryProperty(poDefn) < 0)
     751           0 :                                 delete poDefn;
     752             :                             else
     753           9 :                                 nAttributeIndex++;
     754             :                         }
     755             : 
     756          18 :                         break;
     757             :                     }
     758             : 
     759          45 :                     psIter++;
     760             :                 }
     761             : 
     762          18 :                 if (psIter->pszName == nullptr)
     763             :                 {
     764             :                     // Can be a non geometry gml type .
     765             :                     // Too complex schema for us. Aborts parsing.
     766           0 :                     delete poClass;
     767           0 :                     return nullptr;
     768             :                 }
     769             : 
     770          18 :                 if (poClass->GetGeometryPropertyCount() == 0)
     771           0 :                     bGotUnrecognizedType = true;
     772             : 
     773          18 :                 continue;
     774             :             }
     775             : 
     776             :             /* Parse stuff like the following found in
     777             :             http://199.29.1.81:8181/miwfs/GetFeature.ashx?REQUEST=GetFeature&MAXFEATURES=1&SERVICE=WFS&VERSION=1.0.0&TYPENAME=miwfs:World
     778             :             : <xs:element name="Obj" minOccurs="0" maxOccurs="1">
     779             :                 <xs:complexType>
     780             :                     <xs:sequence>
     781             :                         <xs:element ref="gml:_Geometry"/>
     782             :                     </xs:sequence>
     783             :                 </xs:complexType>
     784             :             </xs:element>
     785             :             */
     786             :             CPLXMLNode *l_psComplexType =
     787           1 :                 GetSingleChildElement(psAttrDef, "complexType");
     788             :             CPLXMLNode *psComplexTypeSequence =
     789           1 :                 GetSingleChildElement(l_psComplexType, "sequence");
     790             :             CPLXMLNode *psComplexTypeSequenceElement =
     791           1 :                 GetSingleChildElement(psComplexTypeSequence, "element");
     792             : 
     793           2 :             if (pszElementName != nullptr &&
     794           1 :                 CheckMinMaxOccursCardinality(psAttrDef) &&
     795           1 :                 psComplexTypeSequenceElement != nullptr &&
     796           3 :                 CheckMinMaxOccursCardinality(psComplexTypeSequence) &&
     797           1 :                 strcmp(CPLGetXMLValue(psComplexTypeSequenceElement, "ref", ""),
     798             :                        "gml:_Geometry") == 0)
     799             :             {
     800             :                 GMLGeometryPropertyDefn *poDefn = new GMLGeometryPropertyDefn(
     801             :                     pszElementName, pszElementName, wkbUnknown, nAttributeIndex,
     802           1 :                     bNullable);
     803             : 
     804           1 :                 if (poClass->AddGeometryProperty(poDefn) < 0)
     805           0 :                     delete poDefn;
     806             :                 else
     807           1 :                     nAttributeIndex++;
     808             : 
     809           1 :                 continue;
     810             :             }
     811             :             else
     812             :             {
     813             :                 // Too complex schema for us. Aborts parsing.
     814           0 :                 delete poClass;
     815           0 :                 return nullptr;
     816             :             }
     817             :         }
     818             : 
     819         298 :         if (pszElementName == nullptr)
     820           0 :             pszElementName = "unnamed";
     821             :         GMLPropertyDefn *poProp =
     822         298 :             new GMLPropertyDefn(pszElementName, pszElementName);
     823             : 
     824         298 :         GMLPropertyType eType = GMLPT_Untyped;
     825         298 :         int nWidth = 0;
     826         298 :         int nPrecision = 0;
     827         298 :         GetSimpleTypeProperties(psSimpleType, &eType, &nWidth, &nPrecision);
     828             : 
     829         298 :         if (pszMaxOccurs != nullptr && strcmp(pszMaxOccurs, "1") != 0)
     830           9 :             eType = GetListTypeFromSingleType(eType);
     831             : 
     832         298 :         poProp->SetType(eType);
     833         298 :         poProp->SetWidth(nWidth);
     834         298 :         poProp->SetPrecision(nPrecision);
     835         298 :         poProp->SetNullable(bNullable);
     836             : 
     837         298 :         const CPLXMLNode *psAnnotation = CPLGetXMLNode(psAttrDef, "annotation");
     838         298 :         if (psAnnotation)
     839             :         {
     840             :             const char *pszDocumentation =
     841           8 :                 CPLGetXMLValue(psAnnotation, "documentation", nullptr);
     842           8 :             if (pszDocumentation)
     843           8 :                 poProp->SetDocumentation(pszDocumentation);
     844             :         }
     845             : 
     846         298 :         if (poClass->AddProperty(poProp) < 0)
     847           0 :             delete poProp;
     848             :         else
     849         298 :             nAttributeIndex++;
     850             :     }
     851             : 
     852             :     // If we have found an unknown types, let's be on the side of caution and
     853             :     // create a geometry field.
     854         589 :     if (poClass->GetGeometryPropertyCount() == 0 && bGotUnrecognizedType)
     855             :     {
     856           0 :         poClass->AddGeometryProperty(
     857           0 :             new GMLGeometryPropertyDefn("", "", wkbUnknown, -1, true));
     858             :     }
     859             : 
     860             :     /* -------------------------------------------------------------------- */
     861             :     /*      Class complete, add to reader class list.                       */
     862             :     /* -------------------------------------------------------------------- */
     863         589 :     poClass->SetSchemaLocked(true);
     864             : 
     865         589 :     return poClass;
     866             : }
     867             : 
     868             : /************************************************************************/
     869             : /*                         ExcludeBaseGMLSchemas()                      */
     870             : /************************************************************************/
     871             : 
     872          12 : static bool ExcludeBaseGMLSchemas(const std::string &osFilename)
     873             : {
     874             :     // List of substrings to exclude
     875             :     const std::vector<std::string> excludedBaseGMLReferencedSchemasList = {
     876          96 :         "/gml/3.2.1/", "gml/3.1.1/", "/gml/2.1.2/", "/gmlsfProfile/"};
     877          28 :     for (const auto &pattern : excludedBaseGMLReferencedSchemasList)
     878             :     {
     879          24 :         if (osFilename.find(pattern) != std::string::npos)
     880             :         {
     881           8 :             return false;  // Found one of the excluded base GML referenced schema
     882             :         }
     883             :     }
     884           4 :     return true;  // None of the base GML referenced schemas were found
     885             : }
     886             : 
     887             : /************************************************************************/
     888             : /*                         GMLParseXMLFile()                            */
     889             : /************************************************************************/
     890             : 
     891         478 : static CPLXMLNode *GMLParseXMLFile(const char *pszFilename)
     892             : {
     893         478 :     if (STARTS_WITH(pszFilename, "http://") ||
     894         477 :         STARTS_WITH(pszFilename, "https://"))
     895             :     {
     896           1 :         CPLXMLNode *psRet = nullptr;
     897           1 :         CPLHTTPResult *psResult = CPLHTTPFetch(pszFilename, nullptr);
     898           1 :         if (psResult != nullptr)
     899             :         {
     900           1 :             if (psResult->pabyData != nullptr)
     901             :             {
     902           1 :                 psRet = CPLParseXMLString(
     903           1 :                     reinterpret_cast<const char *>(psResult->pabyData));
     904             :             }
     905           1 :             CPLHTTPDestroyResult(psResult);
     906             :         }
     907           1 :         return psRet;
     908             :     }
     909             :     else
     910             :     {
     911         477 :         return CPLParseXMLFile(pszFilename);
     912             :     }
     913             : }
     914             : 
     915             : /************************************************************************/
     916             : /*                       CPLGetFirstChildNode()                         */
     917             : /************************************************************************/
     918             : 
     919          12 : static CPLXMLNode *CPLGetFirstChildNode(CPLXMLNode *psNode)
     920             : {
     921          12 :     if (psNode == nullptr)
     922           0 :         return nullptr;
     923          12 :     CPLXMLNode *psIter = psNode->psChild;
     924          94 :     while (psIter != nullptr)
     925             :     {
     926          94 :         if (psIter->eType == CXT_Element)
     927          12 :             return psIter;
     928          82 :         psIter = psIter->psNext;
     929             :     }
     930           0 :     return nullptr;
     931             : }
     932             : 
     933             : /************************************************************************/
     934             : /*                          CPLGetLastNode()                            */
     935             : /************************************************************************/
     936             : 
     937          12 : static CPLXMLNode *CPLGetLastNode(CPLXMLNode *psNode)
     938             : {
     939          12 :     CPLXMLNode *psIter = psNode;
     940          23 :     while (psIter->psNext != nullptr)
     941          11 :         psIter = psIter->psNext;
     942          12 :     return psIter;
     943             : }
     944             : 
     945             : /************************************************************************/
     946             : /*                       CPLXMLSchemaResolveInclude()                   */
     947             : /************************************************************************/
     948             : 
     949         466 : static void CPLXMLSchemaResolveInclude(const char *pszMainSchemaLocation,
     950             :                                        CPLXMLNode *psSchemaNode,
     951             :                                        bool bUseSchemaImports)
     952             : {
     953         932 :     std::set<CPLString> osAlreadyIncluded;
     954             : 
     955             :     bool bTryAgain;
     956         474 :     do
     957             :     {
     958         474 :         CPLXMLNode *psLast = nullptr;
     959         474 :         bTryAgain = false;
     960             : 
     961         474 :         CPLXMLNode *psThis = psSchemaNode->psChild;
     962        5102 :         for (; psThis != nullptr; psThis = psThis->psNext)
     963             :         {
     964             :             std::string osSchemaLocation =
     965        4628 :                 CPLGetXMLValue(psThis, "schemaLocation", "");
     966             : 
     967        6891 :             if (psThis->eType == CXT_Element &&
     968        2263 :                 (EQUAL(psThis->pszValue, "include") ||
     969        2254 :                  (bUseSchemaImports == TRUE &&
     970          20 :                   EQUAL(psThis->pszValue, "import") &&
     971          12 :                   ExcludeBaseGMLSchemas(osSchemaLocation))))
     972             :             {
     973             : 
     974          26 :                 if (!osSchemaLocation.empty() &&
     975          26 :                     osAlreadyIncluded.count(osSchemaLocation) == 0)
     976             :                 {
     977          12 :                     osAlreadyIncluded.insert(osSchemaLocation);
     978             : 
     979          12 :                     if (!STARTS_WITH(osSchemaLocation.c_str(), "http://") &&
     980          24 :                         !STARTS_WITH(osSchemaLocation.c_str(), "https://") &&
     981          12 :                         CPLIsFilenameRelative(osSchemaLocation.c_str()))
     982             :                     {
     983          36 :                         osSchemaLocation = CPLFormFilenameSafe(
     984          24 :                             CPLGetPathSafe(pszMainSchemaLocation).c_str(),
     985          12 :                             osSchemaLocation.c_str(), nullptr);
     986             :                     }
     987             : 
     988             :                     CPLXMLNode *psIncludedXSDTree =
     989          12 :                         GMLParseXMLFile(osSchemaLocation.c_str());
     990          12 :                     if (psIncludedXSDTree != nullptr)
     991             :                     {
     992          12 :                         CPLStripXMLNamespace(psIncludedXSDTree, nullptr, TRUE);
     993             :                         CPLXMLNode *psIncludedSchemaNode =
     994          12 :                             CPLGetXMLNode(psIncludedXSDTree, "=schema");
     995          12 :                         if (psIncludedSchemaNode != nullptr)
     996             :                         {
     997             :                             // Substitute de <include> node by its content.
     998             :                             CPLXMLNode *psFirstChildElement =
     999          12 :                                 CPLGetFirstChildNode(psIncludedSchemaNode);
    1000          12 :                             if (psFirstChildElement != nullptr)
    1001             :                             {
    1002             :                                 CPLXMLNode *psCopy =
    1003          12 :                                     CPLCloneXMLTree(psFirstChildElement);
    1004          12 :                                 if (psLast != nullptr)
    1005          12 :                                     psLast->psNext = psCopy;
    1006             :                                 else
    1007           0 :                                     psSchemaNode->psChild = psCopy;
    1008          12 :                                 CPLXMLNode *psNext = psThis->psNext;
    1009          12 :                                 psThis->psNext = nullptr;
    1010          12 :                                 CPLDestroyXMLNode(psThis);
    1011          12 :                                 psThis = CPLGetLastNode(psCopy);
    1012          12 :                                 psThis->psNext = psNext;
    1013             : 
    1014             :                                 // In case the included schema also contains
    1015             :                                 // includes.
    1016          12 :                                 bTryAgain = true;
    1017             :                             }
    1018             :                         }
    1019          12 :                         CPLDestroyXMLNode(psIncludedXSDTree);
    1020             :                     }
    1021             :                 }
    1022             :             }
    1023             : 
    1024        4628 :             psLast = psThis;
    1025             :         }
    1026             :     } while (bTryAgain);
    1027             : 
    1028             :     const char *pszSchemaOutputName =
    1029         466 :         CPLGetConfigOption("GML_SCHEMA_OUTPUT_NAME", nullptr);
    1030         466 :     if (pszSchemaOutputName != nullptr)
    1031             :     {
    1032           0 :         CPLSerializeXMLTreeToFile(psSchemaNode, pszSchemaOutputName);
    1033             :     }
    1034         466 : }
    1035             : 
    1036             : /************************************************************************/
    1037             : /*                       GetUniqueConstraints()                         */
    1038             : /************************************************************************/
    1039             : 
    1040             : static std::set<std::pair<std::string, std::string>>
    1041         145 : GetUniqueConstraints(const CPLXMLNode *psNode)
    1042             : {
    1043             :     /* Parse
    1044             :         <xs:unique name="uniqueConstraintpolyeas_id">
    1045             :             <xs:selector xpath="ogr:featureMember/ogr:poly"/>
    1046             :             <xs:field xpath="ogr:eas_id"/>
    1047             :         </xs:unique>
    1048             :     */
    1049         145 :     std::set<std::pair<std::string, std::string>> oSet;
    1050         580 :     for (const auto *psIter = psNode->psChild; psIter != nullptr;
    1051         435 :          psIter = psIter->psNext)
    1052             :     {
    1053         435 :         if (psIter->eType == CXT_Element && EQUAL(psIter->pszValue, "unique"))
    1054             :         {
    1055             :             const char *pszSelector =
    1056           0 :                 CPLGetXMLValue(psIter, "selector.xpath", nullptr);
    1057             :             const char *pszField =
    1058           0 :                 CPLGetXMLValue(psIter, "field.xpath", nullptr);
    1059           0 :             if (pszSelector && pszField && pszField[0] != '@')
    1060             :             {
    1061           0 :                 const char *pszSlash = strchr(pszSelector, '/');
    1062           0 :                 if (pszSlash)
    1063             :                 {
    1064             :                     oSet.insert(
    1065           0 :                         std::pair(StripNS(pszSlash + 1), StripNS(pszField)));
    1066             :                 }
    1067             :             }
    1068             :         }
    1069             :     }
    1070         145 :     return oSet;
    1071             : }
    1072             : 
    1073             : /************************************************************************/
    1074             : /*                          GMLParseXSD()                               */
    1075             : /************************************************************************/
    1076             : 
    1077         466 : bool GMLParseXSD(const char *pszFile, bool bUseSchemaImports,
    1078             :                  std::vector<GMLFeatureClass *> &aosClasses,
    1079             :                  bool &bFullyUnderstood)
    1080             : 
    1081             : {
    1082         466 :     bFullyUnderstood = false;
    1083             : 
    1084         466 :     if (pszFile == nullptr)
    1085           0 :         return false;
    1086             : 
    1087             :     /* -------------------------------------------------------------------- */
    1088             :     /*      Load the raw XML file.                                          */
    1089             :     /* -------------------------------------------------------------------- */
    1090         466 :     CPLXMLNode *psXSDTree = GMLParseXMLFile(pszFile);
    1091             : 
    1092         466 :     if (psXSDTree == nullptr)
    1093           0 :         return false;
    1094             : 
    1095             :     /* -------------------------------------------------------------------- */
    1096             :     /*      Strip off any namespace qualifiers.                             */
    1097             :     /* -------------------------------------------------------------------- */
    1098         466 :     CPLStripXMLNamespace(psXSDTree, nullptr, TRUE);
    1099             : 
    1100             :     /* -------------------------------------------------------------------- */
    1101             :     /*      Find <schema> root element.                                     */
    1102             :     /* -------------------------------------------------------------------- */
    1103         466 :     CPLXMLNode *psSchemaNode = CPLGetXMLNode(psXSDTree, "=schema");
    1104         466 :     if (psSchemaNode == nullptr)
    1105             :     {
    1106           0 :         CPLDestroyXMLNode(psXSDTree);
    1107           0 :         return false;
    1108             :     }
    1109             : 
    1110             :     /* ==================================================================== */
    1111             :     /*      Process each include directive.                                 */
    1112             :     /* ==================================================================== */
    1113         466 :     CPLXMLSchemaResolveInclude(pszFile, psSchemaNode, bUseSchemaImports);
    1114             : 
    1115             :     // CPLSerializeXMLTreeToFile(psSchemaNode, "/vsistdout/");
    1116             : 
    1117         466 :     bFullyUnderstood = true;
    1118             : 
    1119             :     /* ==================================================================== */
    1120             :     /*      Process each feature class definition.                          */
    1121             :     /* ==================================================================== */
    1122         466 :     CPLXMLNode *psThis = psSchemaNode->psChild;
    1123             : 
    1124         466 :     std::set<std::pair<std::string, std::string>> oSetUniqueConstraints;
    1125             : 
    1126        5034 :     for (; psThis != nullptr; psThis = psThis->psNext)
    1127             :     {
    1128             :         /* --------------------------------------------------------------------
    1129             :          */
    1130             :         /*      Check for <xs:element> node. */
    1131             :         /* --------------------------------------------------------------------
    1132             :          */
    1133        4568 :         if (psThis->eType != CXT_Element || !EQUAL(psThis->pszValue, "element"))
    1134        3984 :             continue;
    1135             : 
    1136             :         /* --------------------------------------------------------------------
    1137             :          */
    1138             :         /*      Get name */
    1139             :         /* --------------------------------------------------------------------
    1140             :          */
    1141         754 :         const char *pszName = CPLGetXMLValue(psThis, "name", nullptr);
    1142         754 :         if (pszName == nullptr)
    1143             :         {
    1144           0 :             continue;
    1145             :         }
    1146             : 
    1147             :         /* --------------------------------------------------------------------
    1148             :          */
    1149             :         /*      Check the substitution group. */
    1150             :         /* --------------------------------------------------------------------
    1151             :          */
    1152             :         const char *pszSubGroup =
    1153         754 :             StripNS(CPLGetXMLValue(psThis, "substitutionGroup", ""));
    1154             : 
    1155         754 :         if (EQUAL(pszName, "FeatureCollection") &&
    1156         145 :             (EQUAL(pszSubGroup, "_FeatureCollection") ||
    1157         105 :              EQUAL(pszSubGroup, "_GML") ||
    1158          95 :              EQUAL(pszSubGroup, "AbstractFeature")))
    1159             :         {
    1160         145 :             oSetUniqueConstraints = GetUniqueConstraints(psThis);
    1161         145 :             continue;
    1162             :         }
    1163             : 
    1164             :         // AbstractFeature used by GML 3.2.
    1165         609 :         if (!EQUAL(pszSubGroup, "_Feature") &&
    1166         139 :             !EQUAL(pszSubGroup, "AbstractFeature"))
    1167             :         {
    1168           0 :             continue;
    1169             :         }
    1170             : 
    1171             :         /* --------------------------------------------------------------------
    1172             :          */
    1173             :         /*      Get type and verify relationship with name. */
    1174             :         /* --------------------------------------------------------------------
    1175             :          */
    1176         609 :         const char *pszType = CPLGetXMLValue(psThis, "type", nullptr);
    1177         609 :         if (pszType == nullptr)
    1178             :         {
    1179          14 :             CPLXMLNode *psComplexType = CPLGetXMLNode(psThis, "complexType");
    1180          14 :             if (psComplexType)
    1181             :             {
    1182             :                 GMLFeatureClass *poClass =
    1183          14 :                     GMLParseFeatureType(psSchemaNode, pszName, psComplexType);
    1184          14 :                 if (poClass)
    1185          13 :                     aosClasses.push_back(poClass);
    1186             :                 else
    1187           1 :                     bFullyUnderstood = false;
    1188             :             }
    1189          14 :             continue;
    1190             :         }
    1191         595 :         if (strstr(pszType, ":") != nullptr)
    1192         587 :             pszType = strstr(pszType, ":") + 1;
    1193         595 :         if (EQUAL(pszType, pszName))
    1194             :         {
    1195             :             // A few WFS servers return a type name which is the element name
    1196             :             // without any _Type or Type suffix
    1197             :             // e.g.:
    1198             :             // http://apollo.erdas.com/erdas-apollo/vector/Cherokee?SERVICE=WFS&VERSION=1.0.0&REQUEST=DescribeFeatureType&TYPENAME=iwfs:Air
    1199             :             // */
    1200             : 
    1201             :             // TODO(schwehr): What was supposed to go here?
    1202             :         }
    1203             : 
    1204             :         // <element name="RekisteriyksikonPalstanTietoja"
    1205             :         // type="ktjkiiwfs:PalstanTietojaType" substitutionGroup="gml:_Feature"
    1206             :         // />
    1207         595 :         else if (strlen(pszType) > 4 &&
    1208         595 :                  strcmp(pszType + strlen(pszType) - 4, "Type") == 0 &&
    1209         595 :                  strlen(pszName) > strlen(pszType) - 4 &&
    1210           0 :                  strncmp(pszName + strlen(pszName) - (strlen(pszType) - 4),
    1211           0 :                          pszType, strlen(pszType) - 4) == 0)
    1212             :         {
    1213             :         }
    1214             : 
    1215         595 :         else if (!EQUALN(pszType, pszName, strlen(pszName)) ||
    1216         595 :                  !(EQUAL(pszType + strlen(pszName), "_Type") ||
    1217         412 :                    EQUAL(pszType + strlen(pszName), "Type") ||
    1218           9 :                    EQUAL(pszType + strlen(pszName), "FeatureType")))
    1219             :         {
    1220           8 :             continue;
    1221             :         }
    1222             : 
    1223             :         // CanVec .xsd contains weird types that are not used in the related
    1224             :         // GML.
    1225         587 :         if (STARTS_WITH(pszName, "XyZz") || STARTS_WITH(pszName, "XyZ1") ||
    1226         584 :             STARTS_WITH(pszName, "XyZ2"))
    1227           3 :             continue;
    1228             : 
    1229             :         GMLFeatureClass *poClass =
    1230         584 :             GMLParseFeatureType(psSchemaNode, pszName, pszType);
    1231         584 :         if (poClass)
    1232         576 :             aosClasses.push_back(poClass);
    1233             :         else
    1234           8 :             bFullyUnderstood = false;
    1235             :     }
    1236             : 
    1237         466 :     CPLDestroyXMLNode(psXSDTree);
    1238             : 
    1239             :     // Attach unique constraints to fields
    1240         466 :     for (const auto &typeFieldPair : oSetUniqueConstraints)
    1241             :     {
    1242           0 :         for (const auto *poClass : aosClasses)
    1243             :         {
    1244           0 :             if (poClass->GetName() == typeFieldPair.first)
    1245             :             {
    1246             :                 auto poProperty =
    1247           0 :                     poClass->GetProperty(typeFieldPair.second.c_str());
    1248           0 :                 if (poProperty)
    1249             :                 {
    1250           0 :                     poProperty->SetUnique(true);
    1251             :                 }
    1252           0 :                 break;
    1253             :             }
    1254             :         }
    1255             :     }
    1256             : 
    1257         466 :     return !aosClasses.empty();
    1258             : }

Generated by: LCOV version 1.14