EVOLUTION-MANAGER
Edit File: parsexsd.cpp
/****************************************************************************** * * Project: GML Reader * Purpose: Implementation of GMLParseXSD() * Author: Frank Warmerdam, warmerdam@pobox.com * ****************************************************************************** * Copyright (c) 2005, Frank Warmerdam * Copyright (c) 2010-2014, Even Rouault <even dot rouault at mines-paris dot org> * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #include "cpl_port.h" #include "parsexsd.h" #include <cstdlib> #include <cstring> #include <set> #include <string> #include "cpl_conv.h" #include "cpl_error.h" #include "cpl_http.h" #include "cpl_minixml.h" #include "cpl_string.h" #include "ogr_core.h" CPL_CVSID("$Id: parsexsd.cpp 37446 2017-02-25 14:56:54Z goatbar $"); /************************************************************************/ /* StripNS() */ /* */ /* Return potentially shortened form of string with namespace */ /* stripped off if there is one. Returns pointer into */ /* original string. */ /************************************************************************/ static const char *StripNS(const char *pszFullValue) { const char *pszColon = strstr(pszFullValue, ":"); if( pszColon == NULL ) return pszFullValue; else return pszColon + 1; } /************************************************************************/ /* GetSimpleTypeProperties() */ /************************************************************************/ static bool GetSimpleTypeProperties(CPLXMLNode *psTypeNode, GMLPropertyType *pGMLType, int *pnWidth, int *pnPrecision) { const char *pszBase = StripNS(CPLGetXMLValue(psTypeNode, "restriction.base", "")); if( EQUAL(pszBase, "decimal") ) { *pGMLType = GMLPT_Real; const char *pszWidth = CPLGetXMLValue(psTypeNode, "restriction.totalDigits.value", "0"); const char *pszPrecision = CPLGetXMLValue(psTypeNode, "restriction.fractionDigits.value", "0"); *pnWidth = atoi(pszWidth); *pnPrecision = atoi(pszPrecision); return true; } else if( EQUAL(pszBase, "float") ) { *pGMLType = GMLPT_Float; return true; } else if( EQUAL(pszBase, "double") ) { *pGMLType = GMLPT_Real; return true; } else if( EQUAL(pszBase, "integer") ) { *pGMLType = GMLPT_Integer; const char *pszWidth = CPLGetXMLValue(psTypeNode, "restriction.totalDigits.value", "0"); *pnWidth = atoi(pszWidth); return true; } else if( EQUAL(pszBase, "long") ) { *pGMLType = GMLPT_Integer64; const char *pszWidth = CPLGetXMLValue(psTypeNode, "restriction.totalDigits.value", "0"); *pnWidth = atoi(pszWidth); return true; } else if( EQUAL(pszBase, "long") ) { *pGMLType = GMLPT_Integer64; const char *pszWidth = CPLGetXMLValue(psTypeNode, "restriction.totalDigits.value", "0"); *pnWidth = atoi(pszWidth); return true; } else if( EQUAL(pszBase, "string") ) { *pGMLType = GMLPT_String; const char *pszWidth = CPLGetXMLValue(psTypeNode, "restriction.maxLength.value", "0"); *pnWidth = atoi(pszWidth); return true; } /* TODO: Would be nice to have a proper date type */ else if( EQUAL(pszBase, "date") || EQUAL(pszBase, "dateTime") ) { *pGMLType = GMLPT_String; return true; } else if( EQUAL(pszBase, "boolean") ) { *pGMLType = GMLPT_Boolean; return true; } else if( EQUAL(pszBase, "short") ) { *pGMLType = GMLPT_Short; return true; } return false; } /************************************************************************/ /* LookForSimpleType() */ /************************************************************************/ static bool LookForSimpleType(CPLXMLNode *psSchemaNode, const char* pszStrippedNSType, GMLPropertyType *pGMLType, int *pnWidth, int *pnPrecision) { CPLXMLNode *psThis = psSchemaNode->psChild; for( ; psThis != NULL; psThis = psThis->psNext ) { if( psThis->eType == CXT_Element && EQUAL(psThis->pszValue, "simpleType") && EQUAL(CPLGetXMLValue(psThis, "name", ""), pszStrippedNSType) ) { break; } } if( psThis == NULL ) return false; return GetSimpleTypeProperties(psThis, pGMLType, pnWidth, pnPrecision); } /************************************************************************/ /* GetSingleChildElement() */ /************************************************************************/ /* Returns the child element whose name is pszExpectedValue only if */ /* there is only one child that is an element. */ static CPLXMLNode *GetSingleChildElement(CPLXMLNode *psNode, const char *pszExpectedValue) { if( psNode == NULL ) return NULL; CPLXMLNode *psIter = psNode->psChild; if( psIter == NULL ) return NULL; CPLXMLNode *psChild = NULL; while( psIter != NULL ) { if( psIter->eType == CXT_Element ) { if( psChild != NULL ) return NULL; if( pszExpectedValue != NULL && strcmp(psIter->pszValue, pszExpectedValue) != 0 ) return NULL; psChild = psIter; } psIter = psIter->psNext; } return psChild; } /************************************************************************/ /* CheckMinMaxOccursCardinality() */ /************************************************************************/ static int CheckMinMaxOccursCardinality(CPLXMLNode *psNode) { const char *pszMinOccurs = CPLGetXMLValue(psNode, "minOccurs", NULL); const char *pszMaxOccurs = CPLGetXMLValue(psNode, "maxOccurs", NULL); return (pszMinOccurs == NULL || EQUAL(pszMinOccurs, "0") || EQUAL(pszMinOccurs, "1")) && (pszMaxOccurs == NULL || EQUAL(pszMaxOccurs, "1")); } /************************************************************************/ /* GetListTypeFromSingleType() */ /************************************************************************/ static GMLPropertyType GetListTypeFromSingleType(GMLPropertyType eType) { if( eType == GMLPT_String ) return GMLPT_StringList; if( eType == GMLPT_Integer || eType == GMLPT_Short ) return GMLPT_IntegerList; if( eType == GMLPT_Integer64 ) return GMLPT_Integer64List; if( eType == GMLPT_Real || eType == GMLPT_Float ) return GMLPT_RealList; if( eType == GMLPT_Boolean ) return GMLPT_BooleanList; if( eType == GMLPT_FeatureProperty ) return GMLPT_FeaturePropertyList; return eType; } /************************************************************************/ /* ParseFeatureType() */ /************************************************************************/ typedef struct { const char *pszName; OGRwkbGeometryType eType; } AssocNameType; static const AssocNameType apsPropertyTypes[] = { {"GeometryPropertyType", wkbUnknown}, {"PointPropertyType", wkbPoint}, {"LineStringPropertyType", wkbLineString}, {"CurvePropertyType", wkbCompoundCurve}, {"PolygonPropertyType", wkbPolygon}, {"SurfacePropertyType", wkbCurvePolygon}, {"MultiPointPropertyType", wkbMultiPoint}, {"MultiLineStringPropertyType", wkbMultiLineString}, {"MultiCurvePropertyType", wkbMultiCurve}, {"MultiPolygonPropertyType", wkbMultiPolygon}, {"MultiSurfacePropertyType", wkbMultiSurface}, {"MultiGeometryPropertyType", wkbGeometryCollection}, {"GeometryAssociationType", wkbUnknown}, {NULL, wkbUnknown}, }; /* Found in FME .xsd (e.g. <element ref="gml:curveProperty" minOccurs="0"/>) */ static const AssocNameType apsRefTypes[] = { {"pointProperty", wkbPoint}, {"curveProperty", wkbLineString}, // Should we promote to wkbCompoundCurve? {"surfaceProperty", wkbPolygon}, // Should we promote to wkbCurvePolygon? {"multiPointProperty", wkbMultiPoint}, {"multiCurveProperty", wkbMultiLineString}, // Should we promote to wkbMultiSurface? {"multiSurfaceProperty", wkbMultiPolygon}, {NULL, wkbUnknown}, }; static GMLFeatureClass *GMLParseFeatureType(CPLXMLNode *psSchemaNode, const char *pszName, CPLXMLNode *psThis); static GMLFeatureClass *GMLParseFeatureType(CPLXMLNode *psSchemaNode, const char *pszName, const char *pszType) { CPLXMLNode *psThis = psSchemaNode->psChild; for( ; psThis != NULL; psThis = psThis->psNext ) { if( psThis->eType == CXT_Element && EQUAL(psThis->pszValue, "complexType") && EQUAL(CPLGetXMLValue(psThis, "name", ""), pszType) ) { break; } } if( psThis == NULL ) return NULL; return GMLParseFeatureType(psSchemaNode, pszName, psThis); } static GMLFeatureClass *GMLParseFeatureType(CPLXMLNode *psSchemaNode, const char *pszName, CPLXMLNode *psComplexType) { /* -------------------------------------------------------------------- */ /* Grab the sequence of extensions greatgrandchild. */ /* -------------------------------------------------------------------- */ CPLXMLNode *psAttrSeq = CPLGetXMLNode(psComplexType, "complexContent.extension.sequence"); if( psAttrSeq == NULL ) { return NULL; } /* -------------------------------------------------------------------- */ /* We are pretty sure this going to be a valid Feature class */ /* now, so create it. */ /* -------------------------------------------------------------------- */ GMLFeatureClass *poClass = new GMLFeatureClass(pszName); /* -------------------------------------------------------------------- */ /* Loop over each of the attribute elements being defined for */ /* this feature class. */ /* -------------------------------------------------------------------- */ int nAttributeIndex = 0; bool bGotUnrecognizedType = false; CPLXMLNode *psAttrDef = psAttrSeq->psChild; for( ; psAttrDef != NULL; psAttrDef = psAttrDef->psNext ) { if( strcmp(psAttrDef->pszValue, "group") == 0 ) { /* Too complex schema for us. Aborts parsing */ delete poClass; return NULL; } /* Parse stuff like: <xs:choice> <xs:element ref="gml:polygonProperty"/> <xs:element ref="gml:multiPolygonProperty"/> </xs:choice> as found in https://downloadagiv.blob.core.windows.net/overstromingsgebieden-en-oeverzones/2014_01/Overstromingsgebieden_en_oeverzones_2014_01_GML.zip */ if( strcmp(psAttrDef->pszValue, "choice") == 0 ) { CPLXMLNode *psChild = psAttrDef->psChild; bool bPolygon = false; bool bMultiPolygon = false; for( ; psChild; psChild = psChild->psNext ) { if( psChild->eType != CXT_Element ) continue; if( strcmp(psChild->pszValue, "element") == 0 ) { const char *pszRef = CPLGetXMLValue(psChild, "ref", NULL); if( pszRef != NULL ) { if( strcmp(pszRef, "gml:polygonProperty") == 0 ) { bPolygon = true; } else if( strcmp(pszRef, "gml:multiPolygonProperty") == 0 ) { bMultiPolygon = true; } else { delete poClass; return NULL; } } else { delete poClass; return NULL; } } } if( bPolygon && bMultiPolygon ) { poClass->AddGeometryProperty(new GMLGeometryPropertyDefn( "", "", wkbMultiPolygon, nAttributeIndex, true)); nAttributeIndex++; } continue; } if( !EQUAL(psAttrDef->pszValue, "element") ) continue; // MapServer WFS writes element type as an attribute of element // not as a simpleType definition. const char *pszType = CPLGetXMLValue(psAttrDef, "type", NULL); const char *pszElementName = CPLGetXMLValue(psAttrDef, "name", NULL); bool bNullable = EQUAL(CPLGetXMLValue(psAttrDef, "minOccurs", "1"), "0"); const char *pszMaxOccurs = CPLGetXMLValue(psAttrDef, "maxOccurs", NULL); if (pszType != NULL) { const char *pszStrippedNSType = StripNS(pszType); int nWidth = 0; int nPrecision = 0; GMLPropertyType gmlType = GMLPT_Untyped; if (EQUAL(pszStrippedNSType, "string") || EQUAL(pszStrippedNSType, "Character")) gmlType = GMLPT_String; // TODO: Would be nice to have a proper date type. else if (EQUAL(pszStrippedNSType, "date") || EQUAL(pszStrippedNSType, "dateTime")) gmlType = GMLPT_String; else if (EQUAL(pszStrippedNSType, "real") || EQUAL(pszStrippedNSType, "double") || EQUAL(pszStrippedNSType, "decimal")) gmlType = GMLPT_Real; else if (EQUAL(pszStrippedNSType, "float") ) gmlType = GMLPT_Float; else if (EQUAL(pszStrippedNSType, "int") || EQUAL(pszStrippedNSType, "integer")) gmlType = GMLPT_Integer; else if (EQUAL(pszStrippedNSType, "long")) gmlType = GMLPT_Integer64; else if (EQUAL(pszStrippedNSType, "short") ) gmlType = GMLPT_Short; else if (EQUAL(pszStrippedNSType, "boolean") ) gmlType = GMLPT_Boolean; else if (strcmp(pszType, "gml:FeaturePropertyType") == 0 ) { gmlType = GMLPT_FeatureProperty; } else if (STARTS_WITH(pszType, "gml:")) { const AssocNameType *psIter = apsPropertyTypes; while(psIter->pszName) { if (strncmp(pszType + 4, psIter->pszName, strlen(psIter->pszName)) == 0) { OGRwkbGeometryType eType = psIter->eType; // Look if there's a comment restricting to subclasses. if( psAttrDef->psNext != NULL && psAttrDef->psNext->eType == CXT_Comment ) { if( strstr(psAttrDef->psNext->pszValue, "restricted to Polygon") ) eType = wkbPolygon; else if( strstr(psAttrDef->psNext->pszValue, "restricted to LineString") ) eType = wkbLineString; else if( strstr(psAttrDef->psNext->pszValue, "restricted to MultiPolygon") ) eType = wkbMultiPolygon; else if( strstr(psAttrDef->psNext->pszValue, "restricted to MultiLineString") ) eType = wkbMultiLineString; } poClass->AddGeometryProperty( new GMLGeometryPropertyDefn( pszElementName, pszElementName, eType, nAttributeIndex, bNullable)); nAttributeIndex++; break; } psIter++; } if (psIter->pszName == NULL) { // Can be a non geometry gml type. // Too complex schema for us. Aborts parsing. delete poClass; return NULL; } if (poClass->GetGeometryPropertyCount() == 0) bGotUnrecognizedType = true; continue; } /* Integraph stuff */ else if (strcmp(pszType, "G:Point_MultiPointPropertyType") == 0 || strcmp(pszType, "gmgml:Point_MultiPointPropertyType") == 0) { poClass->AddGeometryProperty(new GMLGeometryPropertyDefn( pszElementName, pszElementName, wkbMultiPoint, nAttributeIndex, bNullable)); nAttributeIndex++; continue; } else if (strcmp(pszType, "G:LineString_MultiLineStringPropertyType") == 0 || strcmp(pszType, "gmgml:LineString_MultiLineStringPropertyType") == 0) { poClass->AddGeometryProperty(new GMLGeometryPropertyDefn( pszElementName, pszElementName, wkbMultiLineString, nAttributeIndex, bNullable)); nAttributeIndex++; continue; } else if (strcmp(pszType, "G:Polygon_MultiPolygonPropertyType") == 0 || strcmp(pszType, "gmgml:Polygon_MultiPolygonPropertyType") == 0 || strcmp(pszType, "gmgml:Polygon_Surface_MultiSurface_CompositeSurfacePropertyType") == 0) { poClass->AddGeometryProperty(new GMLGeometryPropertyDefn( pszElementName, pszElementName, wkbMultiPolygon, nAttributeIndex, bNullable)); nAttributeIndex++; continue; } // ERDAS Apollo stufflike in // http://apollo.erdas.com/erdas-apollo/vector/WORLDWIDE?SERVICE=WFS&VERSION=1.0.0&REQUEST=DescribeFeatureType&TYPENAME=wfs:cntry98) else if (strcmp(pszType, "wfs:MixedPolygonPropertyType") == 0) { poClass->AddGeometryProperty(new GMLGeometryPropertyDefn( pszElementName, pszElementName, wkbMultiPolygon, nAttributeIndex, bNullable)); nAttributeIndex++; continue; } else { gmlType = GMLPT_Untyped; if ( !LookForSimpleType(psSchemaNode, pszStrippedNSType, &gmlType, &nWidth, &nPrecision) ) { // Too complex schema for us. Aborts parsing. delete poClass; return NULL; } } if (pszElementName == NULL) pszElementName = "unnamed"; const char *pszPropertyName = pszElementName; if( gmlType == GMLPT_FeatureProperty ) { pszPropertyName = CPLSPrintf("%s_href", pszElementName); } GMLPropertyDefn *poProp = new GMLPropertyDefn(pszPropertyName, pszElementName); if( pszMaxOccurs != NULL && strcmp(pszMaxOccurs, "1") != 0 ) gmlType = GetListTypeFromSingleType(gmlType); poProp->SetType(gmlType); poProp->SetWidth(nWidth); poProp->SetPrecision(nPrecision); poProp->SetNullable(bNullable); if (poClass->AddProperty( poProp ) < 0) delete poProp; else nAttributeIndex++; continue; } // For now we skip geometries. Fixup later. CPLXMLNode *psSimpleType = CPLGetXMLNode(psAttrDef, "simpleType"); if( psSimpleType == NULL ) { const char *pszRef = CPLGetXMLValue(psAttrDef, "ref", NULL); // FME .xsd if (pszRef != NULL && STARTS_WITH(pszRef, "gml:")) { const AssocNameType *psIter = apsRefTypes; while(psIter->pszName) { if (strncmp(pszRef + 4, psIter->pszName, strlen(psIter->pszName)) == 0) { if (poClass->GetGeometryPropertyCount() > 0) { OGRwkbGeometryType eNewType = psIter->eType; OGRwkbGeometryType eOldType = (OGRwkbGeometryType)poClass ->GetGeometryProperty(0) ->GetType(); if ((eNewType == wkbMultiPoint && eOldType == wkbPoint) || (eNewType == wkbMultiLineString && eOldType == wkbLineString) || (eNewType == wkbMultiPolygon && eOldType == wkbPolygon)) { poClass->GetGeometryProperty(0)->SetType( eNewType); } else { CPLDebug( "GML", "Geometry field already found ! " "Ignoring the following ones"); } } else { poClass->AddGeometryProperty( new GMLGeometryPropertyDefn( pszElementName, pszElementName, psIter->eType, nAttributeIndex, true)); nAttributeIndex++; } break; } psIter++; } if (psIter->pszName == NULL) { // Can be a non geometry gml type . // Too complex schema for us. Aborts parsing. delete poClass; return NULL; } if (poClass->GetGeometryPropertyCount() == 0) bGotUnrecognizedType = true; continue; } /* Parse stuff like the following found in http://199.29.1.81:8181/miwfs/GetFeature.ashx?REQUEST=GetFeature&MAXFEATURES=1&SERVICE=WFS&VERSION=1.0.0&TYPENAME=miwfs:World : <xs:element name="Obj" minOccurs="0" maxOccurs="1"> <xs:complexType> <xs:sequence> <xs:element ref="gml:_Geometry"/> </xs:sequence> </xs:complexType> </xs:element> */ CPLXMLNode *l_psComplexType = GetSingleChildElement(psAttrDef, "complexType"); CPLXMLNode *psComplexTypeSequence = GetSingleChildElement(l_psComplexType, "sequence"); CPLXMLNode *psComplexTypeSequenceElement = GetSingleChildElement(psComplexTypeSequence, "element"); if( pszElementName != NULL && CheckMinMaxOccursCardinality(psAttrDef) && psComplexTypeSequenceElement != NULL && CheckMinMaxOccursCardinality(psComplexTypeSequence) && strcmp(CPLGetXMLValue(psComplexTypeSequenceElement, "ref", ""), "gml:_Geometry") == 0 ) { poClass->AddGeometryProperty(new GMLGeometryPropertyDefn( pszElementName, pszElementName, wkbUnknown, nAttributeIndex, bNullable)); nAttributeIndex++; continue; } else { // Too complex schema for us. Aborts parsing. delete poClass; return NULL; } } if (pszElementName == NULL) pszElementName = "unnamed"; GMLPropertyDefn *poProp = new GMLPropertyDefn(pszElementName, pszElementName); GMLPropertyType eType = GMLPT_Untyped; int nWidth = 0; int nPrecision = 0; GetSimpleTypeProperties(psSimpleType, &eType, &nWidth, &nPrecision); if( pszMaxOccurs != NULL && strcmp(pszMaxOccurs, "1") != 0 ) eType = GetListTypeFromSingleType(eType); poProp->SetType(eType); poProp->SetWidth(nWidth); poProp->SetPrecision(nPrecision); poProp->SetNullable(bNullable); if (poClass->AddProperty(poProp) < 0) delete poProp; else nAttributeIndex++; } // If we have found an unknown types, let's be on the side of caution and // create a geometry field. if( poClass->GetGeometryPropertyCount() == 0 && bGotUnrecognizedType ) { poClass->AddGeometryProperty( new GMLGeometryPropertyDefn("", "", wkbUnknown, -1, true)); } /* -------------------------------------------------------------------- */ /* Class complete, add to reader class list. */ /* -------------------------------------------------------------------- */ poClass->SetSchemaLocked(true); return poClass; } /************************************************************************/ /* GMLParseXMLFile() */ /************************************************************************/ static CPLXMLNode *GMLParseXMLFile(const char *pszFilename) { if( STARTS_WITH(pszFilename, "http://") || STARTS_WITH(pszFilename, "https://") ) { CPLXMLNode *psRet = NULL; CPLHTTPResult *psResult = CPLHTTPFetch(pszFilename, NULL); if( psResult != NULL ) { if( psResult->pabyData != NULL ) { psRet = CPLParseXMLString((const char *)psResult->pabyData); } CPLHTTPDestroyResult(psResult); } return psRet; } else { return CPLParseXMLFile(pszFilename); } } /************************************************************************/ /* CPLGetFirstChildNode() */ /************************************************************************/ static CPLXMLNode *CPLGetFirstChildNode(CPLXMLNode *psNode) { if( psNode == NULL ) return NULL; CPLXMLNode *psIter = psNode->psChild; while( psIter != NULL ) { if( psIter->eType == CXT_Element ) return psIter; psIter = psIter->psNext; } return NULL; } /************************************************************************/ /* CPLGetLastNode() */ /************************************************************************/ static CPLXMLNode *CPLGetLastNode(CPLXMLNode *psNode) { CPLXMLNode *psIter = psNode; while( psIter->psNext != NULL ) psIter = psIter->psNext; return psIter; } /************************************************************************/ /* CPLXMLSchemaResolveInclude() */ /************************************************************************/ static void CPLXMLSchemaResolveInclude( const char *pszMainSchemaLocation, CPLXMLNode *psSchemaNode ) { std::set<CPLString> osAlreadyIncluded; bool bTryAgain; do { CPLXMLNode *psLast = NULL; bTryAgain = false; CPLXMLNode *psThis = psSchemaNode->psChild; for( ; psThis != NULL; psThis = psThis->psNext ) { if( psThis->eType == CXT_Element && EQUAL(psThis->pszValue, "include") ) { const char *pszSchemaLocation = CPLGetXMLValue(psThis, "schemaLocation", NULL); if( pszSchemaLocation != NULL && osAlreadyIncluded.count( pszSchemaLocation) == 0 ) { osAlreadyIncluded.insert( pszSchemaLocation ); if( !STARTS_WITH(pszSchemaLocation, "http://") && !STARTS_WITH(pszSchemaLocation, "https://") && CPLIsFilenameRelative(pszSchemaLocation) ) { pszSchemaLocation = CPLFormFilename(CPLGetPath(pszMainSchemaLocation), pszSchemaLocation, NULL); } CPLXMLNode *psIncludedXSDTree = GMLParseXMLFile( pszSchemaLocation ); if( psIncludedXSDTree != NULL ) { CPLStripXMLNamespace(psIncludedXSDTree, NULL, TRUE); CPLXMLNode *psIncludedSchemaNode = CPLGetXMLNode(psIncludedXSDTree, "=schema"); if( psIncludedSchemaNode != NULL ) { // Substitute de <include> node by its content. CPLXMLNode *psFirstChildElement = CPLGetFirstChildNode(psIncludedSchemaNode); if( psFirstChildElement != NULL ) { CPLXMLNode *psCopy = CPLCloneXMLTree(psFirstChildElement); if( psLast != NULL ) psLast->psNext = psCopy; else psSchemaNode->psChild = psCopy; CPLXMLNode *psNext = psThis->psNext; psThis->psNext = NULL; CPLDestroyXMLNode(psThis); psThis = CPLGetLastNode(psCopy); psThis->psNext = psNext; // In case the included schema also contains // includes. bTryAgain = true; } } CPLDestroyXMLNode(psIncludedXSDTree); } } } psLast = psThis; } } while( bTryAgain ); const char *pszSchemaOutputName = CPLGetConfigOption("GML_SCHEMA_OUTPUT_NAME", NULL); if( pszSchemaOutputName != NULL ) { CPLSerializeXMLTreeToFile(psSchemaNode, pszSchemaOutputName); } } /************************************************************************/ /* GMLParseXSD() */ /************************************************************************/ bool GMLParseXSD( const char *pszFile, std::vector<GMLFeatureClass*> &aosClasses, bool &bFullyUnderstood) { bFullyUnderstood = false; if( pszFile == NULL ) return false; /* -------------------------------------------------------------------- */ /* Load the raw XML file. */ /* -------------------------------------------------------------------- */ CPLXMLNode *psXSDTree = GMLParseXMLFile(pszFile); if( psXSDTree == NULL ) return false; /* -------------------------------------------------------------------- */ /* Strip off any namespace qualifiers. */ /* -------------------------------------------------------------------- */ CPLStripXMLNamespace( psXSDTree, NULL, TRUE ); /* -------------------------------------------------------------------- */ /* Find <schema> root element. */ /* -------------------------------------------------------------------- */ CPLXMLNode *psSchemaNode = CPLGetXMLNode(psXSDTree, "=schema"); if( psSchemaNode == NULL ) { CPLDestroyXMLNode( psXSDTree ); return false; } /* ==================================================================== */ /* Process each include directive. */ /* ==================================================================== */ CPLXMLSchemaResolveInclude(pszFile, psSchemaNode); // CPLSerializeXMLTreeToFile(psSchemaNode, "/vsistdout/"); bFullyUnderstood = true; /* ==================================================================== */ /* Process each feature class definition. */ /* ==================================================================== */ CPLXMLNode *psThis = psSchemaNode->psChild; for( ; psThis != NULL; psThis = psThis->psNext ) { /* -------------------------------------------------------------------- */ /* Check for <xs:element> node. */ /* -------------------------------------------------------------------- */ if( psThis->eType != CXT_Element || !EQUAL(psThis->pszValue, "element") ) continue; /* -------------------------------------------------------------------- */ /* Check the substitution group. */ /* -------------------------------------------------------------------- */ const char *pszSubGroup = StripNS(CPLGetXMLValue(psThis, "substitutionGroup", "")); // Old OGR produced elements for the feature collection. if( EQUAL(pszSubGroup, "_FeatureCollection") ) continue; // AbstractFeature used by GML 3.2. if( !EQUAL(pszSubGroup, "_Feature") && !EQUAL(pszSubGroup, "AbstractFeature") ) { continue; } /* -------------------------------------------------------------------- */ /* Get name */ /* -------------------------------------------------------------------- */ const char *pszName = CPLGetXMLValue(psThis, "name", NULL); if( pszName == NULL ) { continue; } /* -------------------------------------------------------------------- */ /* Get type and verify relationship with name. */ /* -------------------------------------------------------------------- */ const char *pszType = CPLGetXMLValue(psThis, "type", NULL); if (pszType == NULL) { CPLXMLNode *psComplexType = CPLGetXMLNode(psThis, "complexType"); if (psComplexType) { GMLFeatureClass *poClass = GMLParseFeatureType(psSchemaNode, pszName, psComplexType); if (poClass) aosClasses.push_back(poClass); else bFullyUnderstood = false; } continue; } if( strstr(pszType, ":") != NULL ) pszType = strstr(pszType, ":") + 1; if( EQUAL(pszType, pszName) ) { // A few WFS servers return a type name which is the element name // without any _Type or Type suffix // e.g.: // http://apollo.erdas.com/erdas-apollo/vector/Cherokee?SERVICE=WFS&VERSION=1.0.0&REQUEST=DescribeFeatureType&TYPENAME=iwfs:Air */ // TODO(schwehr): What was supposed to go here? } // <element name="RekisteriyksikonPalstanTietoja" type="ktjkiiwfs:PalstanTietojaType" substitutionGroup="gml:_Feature" /> else if( strlen(pszType) > 4 && strcmp(pszType + strlen(pszType) - 4, "Type") == 0 && strlen(pszName) > strlen(pszType) - 4 && strncmp(pszName + strlen(pszName) - (strlen(pszType) - 4), pszType, strlen(pszType) - 4) == 0 ) { } else if( !EQUALN(pszType, pszName, strlen(pszName)) || !(EQUAL(pszType + strlen(pszName), "_Type") || EQUAL(pszType + strlen(pszName), "Type")) ) { continue; } // CanVec .xsd contains weird types that are not used in the related // GML. if (STARTS_WITH(pszName, "XyZz") || STARTS_WITH(pszName, "XyZ1") || STARTS_WITH(pszName, "XyZ2")) continue; GMLFeatureClass *poClass = GMLParseFeatureType(psSchemaNode, pszName, pszType); if (poClass) aosClasses.push_back(poClass); else bFullyUnderstood = false; } CPLDestroyXMLNode(psXSDTree); return !aosClasses.empty(); }