/******************************************************************************
 * Project:  OGR
 * Purpose:  OGRGMLASDriver implementation
 * Author:   Even Rouault, <even dot rouault at spatialys dot com>
 *
 * Initial development funded by the European Earth observation programme
 * Copernicus
 *
 ******************************************************************************
 * Copyright (c) 2016, Even Rouault, <even dot rouault at spatialys dot com>
 *
 * 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.
 ****************************************************************************/

// Must be first for DEBUG_BOOL case
#include "xercesc_headers.h"

// Hack to avoid bool, possibly redefined to pedantic bool class, being later used
static XSModel* getGrammarPool(XMLGrammarPool* pool)
{
    bool changed;
    return pool->getXSModel(changed);
}

#include "ogr_gmlas.h"
#include "ogr_pgdump.h"

CPL_CVSID("$Id: ogrgmlasschemaanalyzer.cpp 4632051ede3102b99efb2d80c7917e12ff1fdada 2019-04-14 22:55:27 +0200 Even Rouault $")

static OGRwkbGeometryType GetOGRGeometryType( XSTypeDefinition* poTypeDef );

/************************************************************************/
/*                        IsCompatibleOfArray()                         */
/************************************************************************/

static bool IsCompatibleOfArray( GMLASFieldType eType )
{
    return eType == GMLAS_FT_STRING ||
           eType == GMLAS_FT_BOOLEAN ||
           eType == GMLAS_FT_SHORT ||
           eType == GMLAS_FT_INT32 ||
           eType == GMLAS_FT_INT64 ||
           eType == GMLAS_FT_FLOAT ||
           eType == GMLAS_FT_DOUBLE ||
           eType == GMLAS_FT_DECIMAL ||
           eType == GMLAS_FT_ANYURI;
}

/************************************************************************/
/*                       GMLASPrefixMappingHander                       */
/************************************************************************/

class GMLASPrefixMappingHander: public DefaultHandler
{
        std::map<CPLString, CPLString>& m_oMapURIToPrefix;
        const std::map<CPLString, CPLString>& m_oMapDocNSURIToPrefix;
        CPLString& m_osGMLVersionFound;

  public:
        GMLASPrefixMappingHander(
                        std::map<CPLString, CPLString>& oMapURIToPrefix,
                        const std::map<CPLString, CPLString>& oMapDocNSURIToPrefix,
                        CPLString& osGMLVersionFound) :
            m_oMapURIToPrefix( oMapURIToPrefix ),
            m_oMapDocNSURIToPrefix( oMapDocNSURIToPrefix ),
            m_osGMLVersionFound( osGMLVersionFound )
        {}

        virtual void startElement(
            const   XMLCh* const    uri,
            const   XMLCh* const    localname,
            const   XMLCh* const    qname,
            const   Attributes& attrs) override;

        virtual void startPrefixMapping(const XMLCh* const prefix,
                                        const XMLCh* const uri) override;
};

/************************************************************************/
/*                           startElement()                             */
/************************************************************************/

void GMLASPrefixMappingHander::startElement(
                                    const   XMLCh* const    uri,
                                    const   XMLCh* const    localname,
                                    const   XMLCh* const    /*qname*/,
                                    const   Attributes& attrs )
{
    if( !m_osGMLVersionFound.empty() )
        return;

    const CPLString osURI( transcode(uri) );
    const CPLString osLocalname( transcode(localname) );
    if( osURI == szXS_URI && osLocalname == "schema" )
    {
        bool bIsGML = false;
        CPLString osVersion;
        for(unsigned int i=0; i < attrs.getLength(); i++)
        {
            CPLString osAttrLocalName(transcode(attrs.getLocalName(i)));
            if( osAttrLocalName == "targetNamespace" )
            {
                bIsGML = transcode(attrs.getValue(i)) == szGML_URI;
            }
            else if( osAttrLocalName == "version" )
            {
                osVersion = transcode(attrs.getValue(i));
            }
        }
        if( bIsGML && !osVersion.empty() )
        {
            m_osGMLVersionFound = osVersion;
        }
    }
}

/************************************************************************/
/*                         startPrefixMapping()                         */
/************************************************************************/

void GMLASPrefixMappingHander::startPrefixMapping(const XMLCh* const prefix,
                                                  const XMLCh* const uri)
{
    const CPLString osURI( transcode(uri) );
    CPLString osPrefix( transcode(prefix) );
    if( osPrefix.empty() )
    {
        const auto oIter = m_oMapDocNSURIToPrefix.find( osURI );
        if( oIter != m_oMapDocNSURIToPrefix.end() )
        {
            osPrefix = oIter->second;
        }
    }
    if( !osPrefix.empty() )
    {
        const auto oIter = m_oMapURIToPrefix.find( osURI );
        if( oIter == m_oMapURIToPrefix.end() )
        {
            m_oMapURIToPrefix[ osURI ] = osPrefix;
            CPLDebug("GMLAS", "Registering prefix=%s for uri=%s",
                     osPrefix.c_str(), osURI.c_str());
        }
        else if( oIter->second != osPrefix )
        {
            CPLDebug("GMLAS",
                     "Existing prefix=%s for uri=%s (new prefix %s not used)",
                    oIter->second.c_str(), osURI.c_str(), osPrefix.c_str());
        }
    }
}

/************************************************************************/
/*                        CollectNamespacePrefixes()                    */
/************************************************************************/

static
void CollectNamespacePrefixes(const char* pszXSDFilename,
                              VSILFILE* fpXSD,
                              std::map<CPLString, CPLString>& oMapURIToPrefix,
                              const std::map<CPLString, CPLString>& oMapDocNSURIToPrefix,
                              CPLString& osGMLVersionFound)
{
    GMLASInputSource oSource(pszXSDFilename, fpXSD, false);
    // This is a bit silly but the startPrefixMapping() callback only gets
    // called when using SAX2XMLReader::parse(), and not when using
    // loadGrammar(), so we have to parse the doc twice.
    SAX2XMLReader* poReader = XMLReaderFactory::createXMLReader ();

    GMLASPrefixMappingHander contentHandler(oMapURIToPrefix,
                                            oMapDocNSURIToPrefix,
                                            osGMLVersionFound);
    poReader->setContentHandler(&contentHandler);

    GMLASErrorHandler oErrorHandler;
    poReader->setErrorHandler(&oErrorHandler);

    poReader->parse(oSource);
    delete poReader;
}

/************************************************************************/
/*                       GMLASAnalyzerEntityResolver                    */
/************************************************************************/

class GMLASAnalyzerEntityResolver final: public GMLASBaseEntityResolver
{
        std::map<CPLString, CPLString>& m_oMapURIToPrefix;
        const std::map<CPLString, CPLString>& m_oMapDocNSURIToPrefix;

  public:
        GMLASAnalyzerEntityResolver(const CPLString& osBasePath,
                            std::map<CPLString, CPLString>& oMapURIToPrefix,
                            const std::map<CPLString, CPLString>& oMapDocNSURIToPrefix,
                            GMLASXSDCache& oCache)
            : GMLASBaseEntityResolver(osBasePath, oCache)
            , m_oMapURIToPrefix(oMapURIToPrefix)
            , m_oMapDocNSURIToPrefix(oMapDocNSURIToPrefix)
        {
        }

        virtual void DoExtraSchemaProcessing(const CPLString& osFilename,
                                             VSILFILE* fp) override;
};

/************************************************************************/
/*                         DoExtraSchemaProcessing()                    */
/************************************************************************/

void GMLASAnalyzerEntityResolver::DoExtraSchemaProcessing(
                                             const CPLString& osFilename,
                                             VSILFILE* fp)
{
    CollectNamespacePrefixes(osFilename, fp, m_oMapURIToPrefix,
                             m_oMapDocNSURIToPrefix,
                             m_osGMLVersionFound);
    VSIFSeekL(fp, 0, SEEK_SET);
}

/************************************************************************/
/*                        GMLASSchemaAnalyzer()                         */
/************************************************************************/

GMLASSchemaAnalyzer::GMLASSchemaAnalyzer(
                            GMLASXPathMatcher& oIgnoredXPathMatcher,
                            GMLASXPathMatcher& oChildrenElementsConstraintsXPathMatcher,
                            const std::map<CPLString, std::vector<CPLString> >&
                                                        oMapChildrenElementsConstraints,
                            GMLASXPathMatcher& oForcedFlattenedXPathMatcher,
                            GMLASXPathMatcher& oDisabledFlattenedXPathMatcher )
    : m_oIgnoredXPathMatcher(oIgnoredXPathMatcher)
    , m_oChildrenElementsConstraintsXPathMatcher(oChildrenElementsConstraintsXPathMatcher)
    , m_oForcedFlattenedXPathMatcher(oForcedFlattenedXPathMatcher)
    , m_oDisabledFlattenedXPathMatcher(oDisabledFlattenedXPathMatcher)
    , m_oMapChildrenElementsConstraints(oMapChildrenElementsConstraints)
    , m_bUseArrays(true)
    , m_bUseNullState(false)
    , m_bInstantiateGMLFeaturesOnly(true)
    , m_nIdentifierMaxLength(0)
    , m_bCaseInsensitiveIdentifier(CASE_INSENSITIVE_IDENTIFIER_DEFAULT)
    , m_bPGIdentifierLaundering(PG_IDENTIFIER_LAUNDERING_DEFAULT)
    , m_nMaximumFieldsForFlattening(MAXIMUM_FIELDS_FLATTENING_DEFAULT)
    , m_bAlwaysGenerateOGRId(ALWAYS_GENERATE_OGR_ID_DEFAULT)
{
    // A few hardcoded namespace uri->prefix mappings
    m_oMapURIToPrefix[ szXMLNS_URI ] = szXMLNS_PREFIX;
    m_oMapURIToPrefix[ szXSI_URI ] = szXSI_PREFIX;
}

/************************************************************************/
/*                               GetPrefix()                            */
/************************************************************************/

CPLString GMLASSchemaAnalyzer::GetPrefix( const CPLString& osNamespaceURI )
{
    if( osNamespaceURI.empty() )
        return "";
    const auto oIter = m_oMapURIToPrefix.find(osNamespaceURI);
    if( oIter != m_oMapURIToPrefix.end() )
        return oIter->second;
    else if( !osNamespaceURI.empty() )
    {
        // If the schema doesn't define a xmlns:MYPREFIX=myuri, then forge a
        // fake prefix for conveniency
        CPLString osPrefix;
        if( osNamespaceURI.find(szOPENGIS_URL) == 0 )
            osPrefix = osNamespaceURI.substr( strlen(szOPENGIS_URL) );
        else if( osNamespaceURI.find("http://") == 0 )
            osPrefix = osNamespaceURI.substr( strlen("http://") );
        else
            osPrefix = osNamespaceURI;
        for(size_t i = 0; i < osPrefix.size(); i++ )
        {
            if( !isalnum(osPrefix[i]) )
                osPrefix[i] = '_';
        }
        m_oMapURIToPrefix[osNamespaceURI] = osPrefix;
        CPLDebug("GMLAS",
                 "Cannot find prefix for ns='%s'. Forging %s",
                 osNamespaceURI.c_str(),
                 osPrefix.c_str());
        return osPrefix;
    }
    else
    {
        CPLDebug("GMLAS",
                 "Cannot find prefix for ns='%s'.",
                 osNamespaceURI.c_str());
        return "";
    }
}

/************************************************************************/
/*                               MakeXPath()                            */
/************************************************************************/

CPLString GMLASSchemaAnalyzer::MakeXPath( const CPLString& osNamespaceURI,
                                          const CPLString& osName )
{
    const CPLString osPrefix(GetPrefix(osNamespaceURI));
    if( osPrefix.empty() )
        return osName;
    return osPrefix + ":" + osName;
}

/************************************************************************/
/*                         GetNSOfLastXPathComponent()                  */
/************************************************************************/

// Return the namespace (if any) of the last component of the XPath
static CPLString GetNSOfLastXPathComponent(const CPLString& osXPath )
{
    size_t nPos = osXPath.rfind('@');
    if( nPos != std::string::npos )
        nPos ++;
    else
    {
        nPos = osXPath.rfind('/');
        if( nPos != std::string::npos )
            nPos ++;
        else
            nPos = 0;
    }
    size_t nPosColumn = osXPath.find(':', nPos);
    if( nPosColumn == std::string::npos )
        return CPLString();
    return CPLString(osXPath.substr(nPos, nPosColumn - nPos));
}

/************************************************************************/
/*                         LaunderFieldNames()                          */
/************************************************************************/

// Make sure that field names are unique within the class
void GMLASSchemaAnalyzer::LaunderFieldNames( GMLASFeatureClass& oClass )
{
    std::vector<GMLASField>& aoFields = oClass.GetFields();

    // Duplicates can happen if a class has both an element and an attribute
    // with same name, and/or attributes/elements with same name in different
    // namespaces.
    bool bHasDoneSomeRenaming = false;
    do
    {
        bHasDoneSomeRenaming = false;

        // Detect duplicated field names
        std::map<CPLString, std::vector<int> > oSetNames;
        for(int i=0; i< static_cast<int>(aoFields.size());i++)
        {
            if( aoFields[i].GetCategory() == GMLASField::REGULAR )
            {
                oSetNames[ aoFields[i].GetName() ].push_back(i ) ;
            }
        }

        // Iterate over the unique names
        for( const auto& oIter: oSetNames )
        {
            // Has it duplicates ?
            const size_t nOccurrences = oIter.second.size();
            if( nOccurrences > 1 )
            {
                const CPLString oClassNS =
                        GetNSOfLastXPathComponent(oClass.GetXPath());
                bool bHasDoneRenamingForThatCase = false;

                for(size_t i=0; i<nOccurrences;i++)
                {
                    GMLASField& oField = aoFields[oIter.second[i]];
                    // CPLDebug("GMLAS", "%s", oField.GetXPath().c_str() );
                    const CPLString oNS(
                                GetNSOfLastXPathComponent(oField.GetXPath()));
                    // If the field has a namespace that is not the one of its
                    // class, then prefix its name with its namespace
                    if( !oNS.empty() && oNS != oClassNS &&
                        !STARTS_WITH(oField.GetName(), (oNS + "_").c_str() ) )
                    {
                        bHasDoneSomeRenaming = true;
                        bHasDoneRenamingForThatCase = true;
                        oField.SetName( oNS + "_" + oField.GetName() );
                        break;
                    }
                    // If it is an attribute without a particular namespace,
                    // then suffix with _attr
                    else if( oNS.empty() &&
                             oField.GetXPath().find('@') != std::string::npos &&
                             oField.GetName().find("_attr") == std::string::npos )
                    {
                        bHasDoneSomeRenaming = true;
                        bHasDoneRenamingForThatCase = true;
                        oField.SetName( oField.GetName() + "_attr" );
                        break;
                    }
                }

                // If none of the above renaming strategies have worked, then
                // append a counter to the duplicates.
                if( !bHasDoneRenamingForThatCase )
                {
                    for(size_t i=0; i<nOccurrences;i++)
                    {
                        GMLASField& oField = aoFields[oIter.second[i]];
                        if( i > 0 )
                        {
                            bHasDoneSomeRenaming = true;
                            oField.SetName( oField.GetName() +
                                CPLSPrintf("%d", static_cast<int>(i)+1) );
                        }
                    }
                }
            }
        }
    }
    // As renaming could have created new duplicates (hopefully not!), loop
    // until no renaming has been done.
    while( bHasDoneSomeRenaming );

    // Now check if we must truncate names
    if( m_nIdentifierMaxLength >= MIN_VALUE_OF_MAX_IDENTIFIER_LENGTH )
    {
        for(size_t i=0; i< aoFields.size();i++)
        {
            int nNameSize = static_cast<int>(aoFields[i].GetName().size());
            if( nNameSize > m_nIdentifierMaxLength )
            {
                aoFields[i].SetName(
                    OGRGMLASTruncateIdentifier(aoFields[i].GetName(),
                                               m_nIdentifierMaxLength));
            }
        }
    }

    if( m_bPGIdentifierLaundering )
    {
        for(size_t i=0; i< aoFields.size();i++)
        {
            char* pszLaundered = OGRPGCommonLaunderName( aoFields[i].GetName(),
                                                         "GMLAS" );
            aoFields[i].SetName( pszLaundered );
            CPLFree( pszLaundered );
        }
    }

    // Detect duplicated field names
    std::map<CPLString, std::vector<int> > oSetNames;
    for(int i=0; i< static_cast<int>(aoFields.size());i++)
    {
        if( aoFields[i].GetCategory() == GMLASField::REGULAR )
        {
            CPLString osName( aoFields[i].GetName());
            if( m_bCaseInsensitiveIdentifier )
                osName.toupper();
            oSetNames[ osName ].push_back(i ) ;
        }
    }

    // Iterate over the unique names
    for( const auto& oIter: oSetNames )
    {
        // Has it duplicates ?
        const size_t nOccurrences = oIter.second.size();
        if( nOccurrences > 1 )
        {
            for(size_t i=0; i<nOccurrences;i++)
            {
                GMLASField& oField = aoFields[oIter.second[i]];
                oField.SetName( OGRGMLASAddSerialNumber( oField.GetName(),
                                                    static_cast<int>(i+1),
                                                    nOccurrences,
                                                    m_nIdentifierMaxLength) );
            }
        }
    }

    // Recursively process nested classes
    std::vector<GMLASFeatureClass>& aoNestedClasses = oClass.GetNestedClasses();
    for(size_t i=0; i<aoNestedClasses.size();i++)
    {
        LaunderFieldNames( aoNestedClasses[i] );
    }
}

/************************************************************************/
/*                       CollectClassesReferences()                     */
/************************************************************************/

void GMLASSchemaAnalyzer::CollectClassesReferences(
                                GMLASFeatureClass& oClass,
                                std::vector<GMLASFeatureClass*>& aoClasses )
{
    aoClasses.push_back(&oClass);
    std::vector<GMLASFeatureClass>& aoNestedClasses = oClass.GetNestedClasses();
    for(size_t i=0; i<aoNestedClasses.size();i++)
    {
        CollectClassesReferences( aoNestedClasses[i], aoClasses );
    }
}

/************************************************************************/
/*                         LaunderClassNames()                          */
/************************************************************************/

void GMLASSchemaAnalyzer::LaunderClassNames()
{
    std::vector<GMLASFeatureClass*> aoClasses;
    for(size_t i=0; i< m_aoClasses.size();i++)
    {
        CollectClassesReferences( m_aoClasses[i], aoClasses );
    }

    if( m_nIdentifierMaxLength >= MIN_VALUE_OF_MAX_IDENTIFIER_LENGTH )
    {
        for(size_t i=0; i< aoClasses.size();i++)
        {
            int nNameSize = static_cast<int>(aoClasses[i]->GetName().size());
            if( nNameSize > m_nIdentifierMaxLength )
            {
                aoClasses[i]->SetName(OGRGMLASTruncateIdentifier(
                                            aoClasses[i]->GetName(),
                                            m_nIdentifierMaxLength));
            }
        }
    }

    if( m_bPGIdentifierLaundering )
    {
        for(size_t i=0; i< aoClasses.size();i++)
        {
            char* pszLaundered = OGRPGCommonLaunderName( aoClasses[i]->GetName(),
                                                         "GMLAS" );
            aoClasses[i]->SetName( pszLaundered );
            CPLFree( pszLaundered );
        }
    }

    // Detect duplicated names. This should normally not happen in normal
    // conditions except if you have classes like
    // prefix_foo, prefix:foo, other_prefix:foo
    // or if names have been truncated in the previous step
    std::map<CPLString, std::vector<int> > oSetNames;
    for(int i=0; i< static_cast<int>(aoClasses.size());i++)
    {
        CPLString osName( aoClasses[i]->GetName() );
        if( m_bCaseInsensitiveIdentifier )
            osName.toupper();
        oSetNames[ osName ].push_back(i ) ;
    }

    // Iterate over the unique names
    for( const auto& oIter: oSetNames )
    {
        // Has it duplicates ?
        const size_t nOccurrences = oIter.second.size();
        if( nOccurrences > 1 )
        {
            for(size_t i=0; i<nOccurrences;i++)
            {
                GMLASFeatureClass* poClass = aoClasses[oIter.second[i]];
                poClass->SetName( OGRGMLASAddSerialNumber(poClass->GetName(),
                                                  static_cast<int>(i+1),
                                                  nOccurrences,
                                                  m_nIdentifierMaxLength) );
            }
        }
    }
}

/************************************************************************/
/*                       GMLASUniquePtr()                               */
/************************************************************************/

// Poor-man std::unique_ptr
template<class T> class GMLASUniquePtr
{
        T* m_p;

        GMLASUniquePtr(const GMLASUniquePtr&);
        GMLASUniquePtr& operator=(const GMLASUniquePtr&);

    public:
        explicit GMLASUniquePtr(T* p): m_p(p) {}
       ~GMLASUniquePtr() { delete m_p; }

       T* operator->() const { CPLAssert(m_p); return m_p; }

       T* get () const { return m_p; }
       T* release() { T* ret = m_p; m_p = NULL; return ret; }
};

/************************************************************************/
/*                   GetTopElementDeclarationFromXPath()                */
/************************************************************************/

XSElementDeclaration* GMLASSchemaAnalyzer::GetTopElementDeclarationFromXPath(
                                                    const CPLString& osXPath,
                                                    XSModel* poModel)
{
    const char* pszTypename = osXPath.c_str();
    const char* pszName = strrchr(pszTypename, ':');
    if( pszName )
        pszName ++;
    XSElementDeclaration* poEltDecl = nullptr;
    if( pszName != nullptr )
    {
        CPLString osNSPrefix = pszTypename;
        osNSPrefix.resize( pszName - 1 - pszTypename );
        CPLString osName = pszName;
        CPLString osNSURI;

        for( const auto& oIterNS: m_oMapURIToPrefix )
        {
            const CPLString& osIterNSURI(oIterNS.first);
            const CPLString& osIterNSPrefix(oIterNS.second);
            if( osNSPrefix == osIterNSPrefix )
            {
                osNSURI = osIterNSURI;
                break;
            }
        }
        XMLCh* xmlNS = XMLString::transcode(osNSURI);
        XMLCh* xmlName = XMLString::transcode(osName);
        poEltDecl = poModel->getElementDeclaration(xmlName, xmlNS);
        XMLString::release( &xmlNS );
        XMLString::release( &xmlName );
    }
    else
    {
        XMLCh* xmlName = XMLString::transcode(pszTypename);
        poEltDecl = poModel->getElementDeclaration(xmlName, nullptr);
        XMLString::release( &xmlName );
    }
    return poEltDecl;
}

/************************************************************************/
/*                        IsEltCompatibleOfFC()                         */
/************************************************************************/

static XSComplexTypeDefinition* IsEltCompatibleOfFC(
                                            XSElementDeclaration* poEltDecl)
{
    XSTypeDefinition* poTypeDef = poEltDecl->getTypeDefinition();
    if( poTypeDef->getTypeCategory() == XSTypeDefinition::COMPLEX_TYPE &&
        transcode(poEltDecl->getName()) != szFEATURE_COLLECTION )
    {
        XSComplexTypeDefinition* poCT =
                    reinterpret_cast<XSComplexTypeDefinition*>(poTypeDef);
        XSComplexTypeDefinition::CONTENT_TYPE eContentType(
                                                poCT->getContentType());
        if( eContentType == XSComplexTypeDefinition::CONTENTTYPE_ELEMENT ||
            eContentType == XSComplexTypeDefinition::CONTENTTYPE_MIXED )
        {
            return poCT;
        }
    }
    return nullptr;
}

/************************************************************************/
/*                          DerivesFromGMLFeature()                     */
/************************************************************************/

bool GMLASSchemaAnalyzer::DerivesFromGMLFeature(XSElementDeclaration* poEltDecl)
{
    XSElementDeclaration* poIter = poEltDecl;
    while( true )
    {
        XSElementDeclaration* poSubstGroup =
            poIter->getSubstitutionGroupAffiliation();
        if( poSubstGroup == nullptr )
            break;
        const CPLString osSubstNS(
                    transcode(poSubstGroup->getNamespace()) );
        const CPLString osSubstName(
                    transcode(poSubstGroup->getName()) );
        if( IsGMLNamespace(osSubstNS) &&
            osSubstName == "_FeatureCollection" )
        {
            return false;
        }
        if( IsGMLNamespace(osSubstNS) &&
            (osSubstName == "AbstractFeature" ||
                osSubstName == "_Feature") )
        {
            return true;
        }
        poIter = poSubstGroup;
    }
    return false;
}

/************************************************************************/
/*                               Analyze()                              */
/************************************************************************/

bool GMLASSchemaAnalyzer::Analyze(GMLASXSDCache& oCache,
                                  const CPLString& osBaseDirname,
                                  std::vector<PairURIFilename>& aoXSDs,
                                  bool bSchemaFullChecking,
                                  bool bHandleMultipleImports)
{
    GMLASUniquePtr<XMLGrammarPool> poGrammarPool(
         (new XMLGrammarPoolImpl(XMLPlatformUtils::fgMemoryManager)));

    std::vector<CPLString> aoNamespaces;
    GMLASAnalyzerEntityResolver oXSDEntityResolver( CPLString(),
                                                    m_oMapURIToPrefix,
                                                    m_oMapDocNSURIToPrefix,
                                                    oCache );

    // In this first pass we load the schemas that are directly pointed by
    // the user with the XSD open option, or that we found in the xsi:schemaLocation
    // attribute
    // The namespaces of those schemas are the "first choice" namespaces from
    // which we will try to find elements to turn them into layers
    aoNamespaces.push_back("");
    for( size_t i = 0; i < aoXSDs.size(); i++ )
    {
        const CPLString osURI(aoXSDs[i].first);
        const CPLString osXSDFilename(aoXSDs[i].second);

        GMLASUniquePtr<SAX2XMLReader> poParser(
                    XMLReaderFactory::createXMLReader(
                                    XMLPlatformUtils::fgMemoryManager,
                                    poGrammarPool.get()));

        // Commonly useful configuration.
        //
        poParser->setFeature (XMLUni::fgSAX2CoreNameSpaces, true);
        poParser->setFeature (XMLUni::fgSAX2CoreNameSpacePrefixes, true);
        poParser->setFeature (XMLUni::fgSAX2CoreValidation, true);

        // Enable validation.
        //
        poParser->setFeature (XMLUni::fgXercesSchema, true);

        poParser->setFeature (XMLUni::fgXercesValidationErrorAsFatal, false);

        // Use the loaded grammar during parsing.
        //
        poParser->setFeature (XMLUni::fgXercesUseCachedGrammarInParse, true);

        // Don't load schemas from any other source (e.g., from XML document's
        // xsi:schemaLocation attributes).
        //
        poParser->setFeature (XMLUni::fgXercesLoadSchema, false);

        Grammar* poGrammar = nullptr;
        if( !GMLASReader::LoadXSDInParser( poParser.get(),
                                           oCache,
                                           oXSDEntityResolver,
                                           osBaseDirname,
                                           osXSDFilename,
                                           &poGrammar,
                                           bSchemaFullChecking,
                                           bHandleMultipleImports ) )
        {
            return false;
        }

        // Some .xsd like
        // http://www.opengis.net/gwml-main/2.1 -> https://wfspoc.brgm-rec.fr/constellation/WS/wfs/BRGM:GWML2?request=DescribeFeatureType&version=2.0.0&service=WFS&namespace=xmlns(ns1=http://www.opengis.net/gwml-main/2.1)&typenames=ns1:GW_Aquifer
        // do not have a declared targetNamespace, so use the one of the
        // schemaLocation if the grammar returns an empty namespace.
        CPLString osGrammarURI( transcode(poGrammar->getTargetNamespace()) );
        if( osGrammarURI.empty() )
        {
            if( !osURI.empty() )
                osGrammarURI = osURI;
        }
        if( !osGrammarURI.empty() )
        {
            // Patch back the aoXSDs element in case we didn't know the
            // namespace URI initially
            if( osURI.empty() )
                aoXSDs[i].first = osGrammarURI;
            aoNamespaces.push_back( osGrammarURI );
        }
    }

    m_osGMLVersionFound = oXSDEntityResolver.GetGMLVersionFound();
    m_oSetSchemaURLs = oXSDEntityResolver.GetSchemaURLS();

    m_oIgnoredXPathMatcher.SetDocumentMapURIToPrefix( m_oMapURIToPrefix );
    m_oChildrenElementsConstraintsXPathMatcher.SetDocumentMapURIToPrefix( m_oMapURIToPrefix );
    m_oForcedFlattenedXPathMatcher.SetDocumentMapURIToPrefix( m_oMapURIToPrefix );
    m_oDisabledFlattenedXPathMatcher.SetDocumentMapURIToPrefix( m_oMapURIToPrefix );

    XSModel* poModel = getGrammarPool(poGrammarPool.get());
    CPLAssert(poModel); // should not be null according to doc

#if 0
    XSNamespaceItem* nsItem = poModel->getNamespaceItem(
                                        loadedGrammar->getTargetNamespace());
    if( nsItem == NULL )
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "getNamespaceItem(%s) failed",
                 transcode(loadedGrammar->getTargetNamespace()).c_str());
        return false;
    }
#endif

    bool bFoundGMLFeature = false;

    // Second pass, in all namespaces, to figure out inheritance relationships
    // and group models that have names
    std::map<CPLString, CPLString> oMapURIToPrefixWithEmpty(m_oMapURIToPrefix);
    oMapURIToPrefixWithEmpty[""] = "";
    for( const auto& oIterNS: oMapURIToPrefixWithEmpty )
    {
        const CPLString& osNSURI(oIterNS.first);
        if( osNSURI == szXS_URI ||
            osNSURI == szXSI_URI ||
            osNSURI == szXMLNS_URI ||
            osNSURI == szXLINK_URI )
        {
            continue;
        }

        XMLCh* xmlNamespace = XMLString::transcode(osNSURI.c_str());

        XSNamedMap<XSObject>* poMapModelGroupDefinition =
            poModel->getComponentsByNamespace(XSConstants::MODEL_GROUP_DEFINITION,
                                            xmlNamespace);

        // Remember group models that have names
        for(XMLSize_t i = 0; poMapModelGroupDefinition != nullptr &&
                             i <  poMapModelGroupDefinition->getLength(); i++ )
        {
            XSModelGroupDefinition* modelGroupDefinition =
                reinterpret_cast<XSModelGroupDefinition*>(
                                            poMapModelGroupDefinition->item(i));
            m_oMapModelGroupToMGD[modelGroupDefinition->getModelGroup()]
                            = modelGroupDefinition;
        }

        CPLDebug("GMLAS", "Discovering substitutions of %s (%s)",
                 oIterNS.second.c_str(), osNSURI.c_str());

        XSNamedMap<XSObject>* poMapElements = poModel->getComponentsByNamespace(
                            XSConstants::ELEMENT_DECLARATION, xmlNamespace);

        for(XMLSize_t i = 0; poMapElements != nullptr &&
                             i < poMapElements->getLength(); i++ )
        {
            XSElementDeclaration* poEltDecl =
                reinterpret_cast<XSElementDeclaration*>(poMapElements->item(i));
            XSElementDeclaration* poSubstGroup =
                            poEltDecl->getSubstitutionGroupAffiliation();
            const CPLString osEltXPath(
                            MakeXPath(transcode(poEltDecl->getNamespace()),
                                      transcode(poEltDecl->getName())));
            m_oMapXPathToEltDecl[ osEltXPath ] = poEltDecl;
            if( poSubstGroup )
            {
                m_oMapParentEltToChildElt[poSubstGroup].push_back(poEltDecl);
#ifdef DEBUG_VERBOSE
                CPLString osParentType(MakeXPath(
                            transcode(poSubstGroup->getNamespace()),
                            transcode(poSubstGroup->getName())));
                CPLDebug("GMLAS", "%s is a substitution for %s",
                        osEltXPath.c_str(),
                        osParentType.c_str());
#endif

                // Check if this element derives from gml:_Feature/AbstractFeature
                if( !bFoundGMLFeature &&
                    m_bInstantiateGMLFeaturesOnly &&
                    !IsGMLNamespace(osNSURI) &&
                    DerivesFromGMLFeature(poEltDecl) )
                {
                    CPLDebug("GMLAS",
                                "Restricting (in first pass) top level "
                                "elements to those deriving from "
                                "gml:_Feature/gml:AbstractFeature (due "
                                "to %s found)",
                                osEltXPath.c_str());
                    bFoundGMLFeature = true;
                }
            }
        }

        XMLString::release(&xmlNamespace);
    }

    // Check that we can find elements in the namespaces pointed in the
    // xsi:schemaLocation of the document, then fallback to namespaces
    // that might be indirectly imported by those first level namespaces
    bool bFoundElementsInFirstChoiceNamespaces = false;
    for( size_t iNS = 0; !bFoundElementsInFirstChoiceNamespaces &&
                         iNS < aoNamespaces.size(); iNS++ )
    {
        XMLCh* xmlNamespace = XMLString::transcode(aoNamespaces[iNS].c_str());

        XSNamedMap<XSObject>* poMapElements = poModel->getComponentsByNamespace(
            XSConstants::ELEMENT_DECLARATION, xmlNamespace);
        bFoundElementsInFirstChoiceNamespaces = 
            poMapElements != nullptr && poMapElements->getLength() > 0;
        XMLString::release(&xmlNamespace);
    }
    if( !bFoundElementsInFirstChoiceNamespaces )
    {
        CPLDebug("GMLAS", "Did not find element in 'first choice' namespaces. "
                 "Falling back to the namespaces they import");
        aoNamespaces.clear();
        for( const auto& oIterNS: oMapURIToPrefixWithEmpty )
        {
            const CPLString& osNSURI(oIterNS.first);
            if( osNSURI == szXS_URI ||
                osNSURI == szXSI_URI ||
                osNSURI == szXMLNS_URI ||
                osNSURI == szXLINK_URI ||
                osNSURI == szWFS_URI ||
                osNSURI == szWFS20_URI ||
                osNSURI == szGML_URI ||
                osNSURI == szGML32_URI )
            {
                // Skip all boring namespaces
                continue;
            }
            aoNamespaces.push_back(osNSURI);
        }
    }

    // Find which elements must be top levels (because referenced several
    // times)
    std::set<XSElementDeclaration*> oSetVisitedEltDecl;
    std::set<XSModelGroup*> oSetVisitedModelGroups;
    std::vector<XSElementDeclaration*> oVectorEltsForTopClass;

    // For some reason, different XSElementDeclaration* can point to the
    // same element, but we only want to instantiate a single class.
    // This is the case for base:SpatialDataSet in
    // inspire/geologicalunit/geologicalunit.gml test dataset.
    std::set<CPLString> aoSetXPathEltsForTopClass;

    // Third and fourth passes
    for( int iPass = 0; iPass < 2; ++iPass )
    {
        for( size_t iNS = 0; iNS < aoNamespaces.size(); iNS++ )
        {
            XMLCh* xmlNamespace = XMLString::transcode(aoNamespaces[iNS].c_str());

            XSNamedMap<XSObject>* poMapElements = poModel->getComponentsByNamespace(
                XSConstants::ELEMENT_DECLARATION, xmlNamespace);

            for(XMLSize_t i = 0; poMapElements != nullptr &&
                                i < poMapElements->getLength(); i++ )
            {
                XSElementDeclaration* poEltDecl =
                    reinterpret_cast<XSElementDeclaration*>(poMapElements->item(i));
                XSComplexTypeDefinition* poCT = IsEltCompatibleOfFC(poEltDecl);
                if( !poEltDecl->getAbstract() && poCT != nullptr  )
                {
                    const CPLString osXPath(MakeXPath(
                                    transcode(poEltDecl->getNamespace()),
                                    transcode(poEltDecl->getName())));
                    if( !IsIgnoredXPath(osXPath ) )
                    {
                        if( bFoundGMLFeature &&
                            m_bInstantiateGMLFeaturesOnly &&
                            !DerivesFromGMLFeature(poEltDecl) )
                        {
                            // Do nothing
                        }
                        else if( iPass == 0)
                        {
#ifdef DEBUG_VERBOSE
                        CPLDebug("GMLAS", "%s (%s) must be exposed as "
                                 "top-level (is top level in imported schemas)",
                                osXPath.c_str(),
                                transcode(poEltDecl->getTypeDefinition()->
                                                            getName()).c_str());
#endif
                            oSetVisitedEltDecl.insert( poEltDecl );
                            if( aoSetXPathEltsForTopClass.find( osXPath ) ==
                                aoSetXPathEltsForTopClass.end() )
                            {
                                m_oSetEltsForTopClass.insert( poEltDecl );
                                oVectorEltsForTopClass.push_back( poEltDecl );
                                aoSetXPathEltsForTopClass.insert( osXPath );
                            }
                        }
                        else
                        {
                            bool bSimpleEnoughOut = true;
                            int nSubCountSubEltOut = 0;
                            auto poParticle = poCT->getParticle();
                            if( poParticle )
                            {
                                FindElementsWithMustBeToLevel(
                                        osXPath,
                                        poParticle->getModelGroupTerm(),
                                        0,
                                        oSetVisitedEltDecl,
                                        oSetVisitedModelGroups,
                                        oVectorEltsForTopClass,
                                        aoSetXPathEltsForTopClass,
                                        poModel,
                                        bSimpleEnoughOut,
                                        nSubCountSubEltOut );
                            }
                        }
                    }
                }
            }

            XMLString::release(&xmlNamespace);
        }
    }

    // Find ambiguous class names
    {
        for( const auto& oIter: m_oSetEltsForTopClass )
        {
            CPLString osName(transcode(oIter->getName()));
            m_oMapEltNamesToInstanceCount[osName] ++;
        }
    }

    // Instantiate all needed typenames
    for( const auto& poEltDecl: oVectorEltsForTopClass )
    {
        const CPLString osXPath(MakeXPath(
                            transcode(poEltDecl->getNamespace()),
                            transcode(poEltDecl->getName())));

        bool bError = false;
        bool bResolvedType = InstantiateClassFromEltDeclaration(poEltDecl,
                                                                poModel,
                                                                bError);
        if( bError )
        {
            return false;
        }
        if( !bResolvedType )
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                        "Couldn't resolve %s (%s)",
                        osXPath.c_str(),
                        transcode(poEltDecl->getTypeDefinition()->getName()).c_str()
                        );
            return false;
        }
    }

    LaunderClassNames();

    return true;
}

/************************************************************************/
/*                            GetAnnotationDoc()                        */
/************************************************************************/

static CPLString GetAnnotationDoc( const XSAnnotation* annotation )
{
    if( !annotation )
        return CPLString();
    CPLString osAnnot(transcode(annotation->getAnnotationString()));
    CPLXMLNode* psRoot = CPLParseXMLString(osAnnot);
    CPLStripXMLNamespace(psRoot, nullptr, TRUE);
    CPLString osDoc( CPLGetXMLValue(psRoot, "=annotation.documentation", "") );
    CPLDestroyXMLNode(psRoot);
    return osDoc.Trim();
}

/************************************************************************/
/*                            GetAnnotationDoc()                        */
/************************************************************************/

static CPLString GetAnnotationDoc( const XSAnnotationList* annotationList )
{
    if( !annotationList )
        return CPLString();
    CPLString osRet;
    for( size_t i = 0; i < annotationList->size(); ++i )
    {
        CPLString osDoc( GetAnnotationDoc( annotationList->elementAt(i) ) );
        if( !osDoc.empty() )
        {
            if( !osRet.empty() )
                osRet += "\n";
            osRet += osDoc;
        }
    }
    return osRet;
}

/************************************************************************/
/*                            GetAnnotationDoc()                        */
/************************************************************************/

static CPLString GetAnnotationDoc( const XSElementDeclaration* poEltDecl )
{
    XSTypeDefinition* poTypeDef = poEltDecl->getTypeDefinition();
    CPLString osDoc = GetAnnotationDoc( poEltDecl->getAnnotation() );
    XSAnnotationList* list = nullptr;
    while( poTypeDef != nullptr )
    {
        if( poTypeDef->getTypeCategory() == XSTypeDefinition::COMPLEX_TYPE )
        {
            XSComplexTypeDefinition* poCT =
                        reinterpret_cast<XSComplexTypeDefinition*>(poTypeDef);
            list = poCT->getAnnotations();
        }
        else if( poTypeDef->getTypeCategory() == XSTypeDefinition::SIMPLE_TYPE )
        {
            XSSimpleTypeDefinition* poST =
                        reinterpret_cast<XSSimpleTypeDefinition*>(poTypeDef);
            list = poST->getAnnotations();
        }
        if( list != nullptr )
            break;
        XSTypeDefinition* poNewTypeDef = poTypeDef->getBaseType();
        if( poNewTypeDef == poTypeDef )
            break;
        poTypeDef = poNewTypeDef;
    }
    CPLString osDoc2 = GetAnnotationDoc( list );
    if( !osDoc.empty() && !osDoc2.empty() )
    {
        osDoc += "\n";
        osDoc += osDoc2;
    }
    else if( !osDoc2.empty() )
        osDoc = osDoc2;
    return osDoc;
}

/************************************************************************/
/*                  InstantiateClassFromEltDeclaration()                */
/************************************************************************/

bool GMLASSchemaAnalyzer::InstantiateClassFromEltDeclaration(
                                                XSElementDeclaration* poEltDecl,
                                                XSModel* poModel,
                                                bool& bError)
{
    bError = false;
    XSComplexTypeDefinition* poCT = IsEltCompatibleOfFC(poEltDecl);
    if( !poEltDecl->getAbstract() && poCT != nullptr )
    {
        GMLASFeatureClass oClass;
        const CPLString osEltName( transcode(poEltDecl->getName()) );
        const CPLString osXPath( MakeXPath(
                                transcode(poEltDecl->getNamespace()),
                                osEltName ) );

        if( IsIgnoredXPath( osXPath ) )
        {
#ifdef DEBUG_VERBOSE
            CPLDebug("GMLAS", "%s is in ignored xpaths",
                        osXPath.c_str());
#endif
            return false;
        }

        if( m_oMapEltNamesToInstanceCount[osEltName] > 1 )
        {
            CPLString osLaunderedXPath(osXPath);
            osLaunderedXPath.replaceAll(':', '_');
            oClass.SetName( osLaunderedXPath );
        }
        else
            oClass.SetName( osEltName );

#ifdef DEBUG_VERBOSE
        CPLDebug("GMLAS", "Instantiating element %s", osXPath.c_str());
#endif
        oClass.SetXPath( osXPath );
        oClass.SetIsTopLevelElt(
            GetTopElementDeclarationFromXPath(osXPath, poModel) != nullptr );

        std::set<XSModelGroup*> oSetVisitedModelGroups;

        oClass.SetDocumentation( GetAnnotationDoc(poEltDecl) );

        // might be NULL on swe:values for example
        if( poCT->getParticle() != nullptr )
        {
            std::map< CPLString, int > oMapCountOccurrencesOfSameName;
            BuildMapCountOccurrencesOfSameName(
                poCT->getParticle()->getModelGroupTerm(),
                oMapCountOccurrencesOfSameName);

            OGRwkbGeometryType eGeomType = wkbUnknown;
            if( IsGMLNamespace(transcode(poCT->getNamespace())) &&
                (eGeomType = GetOGRGeometryType(poCT)) != wkbNone )
            {
                GMLASField oField;
                oField.SetName( "geometry" );
                oField.SetMinOccurs( 1 );
                oField.SetMaxOccurs( 1 );
                oField.SetType( GMLAS_FT_GEOMETRY, szFAKEXS_GEOMETRY );
                oField.SetGeomType( eGeomType );
                oField.SetXPath( osXPath + szMATCH_ALL );
                oField.SetIncludeThisEltInBlob( true );

                oClass.AddField( oField );
            }
            else if( !ExploreModelGroup(
                                poCT->getParticle()->getModelGroupTerm(),
                                poCT->getAttributeUses(),
                                oClass,
                                0,
                                oSetVisitedModelGroups,
                                poModel,
                                oMapCountOccurrencesOfSameName) )
            {
                bError = true;
                return false;
            }
        }
        else
        {
            // TODO ?
        }

        LaunderFieldNames( oClass );

        m_aoClasses.push_back(oClass);
        return true;
    }
    return false;
}

/************************************************************************/
/*                 SetFieldTypeAndWidthFromDefinition()                 */
/************************************************************************/

void GMLASSchemaAnalyzer::SetFieldTypeAndWidthFromDefinition(
                                                 XSSimpleTypeDefinition* poST,
                                                 GMLASField& oField )
{
    int nMaxLength = 0;
    while( poST->getBaseType() != poST &&
            poST->getBaseType()->getTypeCategory() ==
                                        XSTypeDefinition::SIMPLE_TYPE &&
            !XMLString::equals(poST->getNamespace(),
                               PSVIUni::fgNamespaceXmlSchema) )
    {
        const XMLCh* maxLength = poST->getLexicalFacetValue(
                                    XSSimpleTypeDefinition::FACET_LENGTH );
        if( maxLength == nullptr )
        {
            maxLength = poST->getLexicalFacetValue(
                                XSSimpleTypeDefinition::FACET_MAXLENGTH );
        }
        if( maxLength != nullptr )
            nMaxLength = MAX(nMaxLength, atoi( transcode(maxLength) ) );
        poST = reinterpret_cast<XSSimpleTypeDefinition*>(poST->getBaseType());
    }

    if( XMLString::equals(poST->getNamespace(), PSVIUni::fgNamespaceXmlSchema) )
    {
        CPLString osType( transcode(poST->getName()) );
        oField.SetType( GMLASField::GetTypeFromString(osType), osType );
    }
    else
    {
        CPLError(CE_Warning, CPLE_AppDefined, "Base type is not a xs: one ???");
    }

    oField.SetWidth( nMaxLength );
}

/************************************************************************/
/*                              IsSame()                                */
/*                                                                      */
/* The objects returned by different PSVI API are not always the same   */
/* so do content inspection to figure out if they are equivalent.       */
/************************************************************************/

bool GMLASSchemaAnalyzer::IsSame( const XSModelGroup* poModelGroup1,
                                  const XSModelGroup* poModelGroup2 )
{
    if( poModelGroup1->getCompositor() != poModelGroup2->getCompositor() )
        return false;

    const XSParticleList* poParticleList1 = poModelGroup1->getParticles();
    const XSParticleList* poParticleList2 = poModelGroup2->getParticles();
    if( poParticleList1->size() != poParticleList2->size() )
        return false;

    for(size_t i = 0; i < poParticleList1->size(); ++i )
    {
        const XSParticle* poParticle1 = poParticleList1->elementAt(i);
        const XSParticle* poParticle2 = poParticleList2->elementAt(i);
        if( poParticle1->getTermType() != poParticle2->getTermType() ||
            poParticle1->getMinOccurs() != poParticle2->getMinOccurs() ||
            poParticle1->getMaxOccurs() != poParticle2->getMaxOccurs() ||
            poParticle1->getMaxOccursUnbounded() !=
                                        poParticle2->getMaxOccursUnbounded() )
        {
            return false;
        }
        switch( poParticle1->getTermType() )
        {
            case XSParticle::TERM_EMPTY:
                break;

            case XSParticle::TERM_ELEMENT:
            {
                const XSElementDeclaration* poElt1 =
                    const_cast<XSParticle*>(poParticle1)->getElementTerm();
                const XSElementDeclaration* poElt2 =
                    const_cast<XSParticle*>(poParticle2)->getElementTerm();
                // Pointer comparison works here
                if( poElt1 != poElt2 )
                    return false;
                break;
            }

            case XSParticle::TERM_MODELGROUP:
            {
                const XSModelGroup* psSubGroup1 =
                    const_cast<XSParticle*>(poParticle1)->getModelGroupTerm();
                const XSModelGroup* psSubGroup2 =
                    const_cast<XSParticle*>(poParticle2)->getModelGroupTerm();
                if( !IsSame(psSubGroup1, psSubGroup2) )
                    return false;
                break;
            }

            case XSParticle::TERM_WILDCARD:
            {
                // TODO: check that pointer comparison works
                const XSWildcard* psWildcard1 =
                    const_cast<XSParticle*>(poParticle1)->getWildcardTerm();
                const XSWildcard* psWildcard2 =
                    const_cast<XSParticle*>(poParticle2)->getWildcardTerm();
                if( psWildcard1 != psWildcard2 )
                    return false;
                break;
            }

            default:
            {
                CPLAssert(FALSE);
                return false;
            }
        }
    }

    return true;
}

/************************************************************************/
/*                           GetGroupName()                             */
/*                                                                      */
/*  The model group object returned when exploring a high level model   */
/*  group isn't the same object as the one returned by model group      */
/*  definitions and has no name. So we have to investigate the content  */
/*  of model groups to figure out if they are the same.                 */
/************************************************************************/

XSModelGroupDefinition* GMLASSchemaAnalyzer::GetGroupDefinition( const XSModelGroup* poModelGroup )
{
    for( const auto& oIter: m_oMapModelGroupToMGD )
    {
        if( IsSame(poModelGroup, oIter.first) )
        {
            return oIter.second;
        }
    }

    return nullptr;
}

/************************************************************************/
/*                              IsAnyType()                             */
/************************************************************************/

static bool IsAnyType(XSComplexTypeDefinition* poType)
{
    if( XMLString::equals(poType->getBaseType()->getNamespace(),
                             PSVIUni::fgNamespaceXmlSchema) &&
        transcode( poType->getBaseType()->getName() ) == szXS_ANY_TYPE )
    {
        XSParticle* poParticle = poType->getParticle();
        if( poParticle != nullptr )
        {
            XSModelGroup* poGroupTerm = poParticle->getModelGroupTerm();
            if( poGroupTerm != nullptr )
            {
                XSParticleList* poParticles =  poGroupTerm->getParticles();
                if( poParticles != nullptr )
                {
                    return poParticles->size() == 1 &&
                           poParticles->elementAt(0)->getTermType() ==
                                                    XSParticle::TERM_WILDCARD;
                }
            }
        }
        else if( poType->getDerivationMethod() ==
                                            XSConstants::DERIVATION_EXTENSION )
        {
            // swe:values case
            return true;
        }
    }
    return false;
}

/************************************************************************/
/*                       SetFieldFromAttribute()                        */
/************************************************************************/

void GMLASSchemaAnalyzer::SetFieldFromAttribute(
                                  GMLASField& oField,
                                  XSAttributeUse* poAttr,
                                  const CPLString& osXPathPrefix,
                                  const CPLString& osNamePrefix)
{
    XSAttributeDeclaration* poAttrDecl = poAttr->getAttrDeclaration();
    XSSimpleTypeDefinition* poAttrType = poAttrDecl->getTypeDefinition();

    SetFieldTypeAndWidthFromDefinition(poAttrType, oField);

    CPLString osNS(transcode(poAttrDecl->getNamespace()));
    CPLString osName(transcode(poAttrDecl->getName()));

    if( osNamePrefix.empty() )
        oField.SetName( osName );
    else
        oField.SetName( osNamePrefix + "_" + osName );

    oField.SetXPath( osXPathPrefix + "/@" +
                        MakeXPath( osNS, osName) );
    if( poAttr->getRequired() )
    {
        oField.SetNotNullable( true );
    }
    oField.SetMinOccurs( oField.IsNotNullable() ? 1 : 0 );
    oField.SetMaxOccurs( 1 );
    if( poAttr->getConstraintType() ==
                            XSConstants::VALUE_CONSTRAINT_FIXED )
    {
        oField.SetFixedValue(
                    transcode(poAttr->getConstraintValue()) );
    }
    else if( poAttr->getConstraintType() ==
                            XSConstants::VALUE_CONSTRAINT_DEFAULT )
    {
        oField.SetDefaultValue(
                    transcode(poAttr->getConstraintValue()) );
    }

    const bool bIsList =
        ( poAttrType->getVariety() == XSSimpleTypeDefinition::VARIETY_LIST );
    if( bIsList )
    {
        SetFieldTypeAndWidthFromDefinition(poAttrType->getItemType(), oField);
        if( m_bUseArrays && IsCompatibleOfArray(oField.GetType()) )
        {
            oField.SetList( true );
            oField.SetArray( true );
        }
        else
        {
            // We should probably create an auxiliary table here, but this
            // is too corner case for now...
            oField.SetType( GMLAS_FT_STRING, szXS_STRING );
        }
    }

    oField.SetDocumentation( GetAnnotationDoc( poAttrDecl->getAnnotation() ) );
}

/************************************************************************/
/*                      GetConcreteImplementationTypes()                */
/************************************************************************/

void GMLASSchemaAnalyzer::GetConcreteImplementationTypes(
                                XSElementDeclaration* poParentElt,
                                std::vector<XSElementDeclaration*>& apoImplEltList)
{
    const auto oIter = m_oMapParentEltToChildElt.find( poParentElt );
    if( oIter != m_oMapParentEltToChildElt.end() )
    {
        for( size_t j = 0; j < oIter->second.size(); j++ )
        {
            XSElementDeclaration* poSubElt = oIter->second[j];
            if( IsEltCompatibleOfFC(poSubElt) )
            {
                if( !poSubElt->getAbstract() )
                {
                    apoImplEltList.push_back(poSubElt);
                }
            }
            GetConcreteImplementationTypes(poSubElt, apoImplEltList);
        }
    }
}

/************************************************************************/
/*                       GetConstraintChildrenElements()                */
/************************************************************************/

std::vector<XSElementDeclaration*>
    GMLASSchemaAnalyzer::GetConstraintChildrenElements(const CPLString& osFullXPath)
{

    std::vector<XSElementDeclaration*> oVectorRes;
    CPLString osMatched;
    if( m_oChildrenElementsConstraintsXPathMatcher.MatchesRefXPath(
                                                    osFullXPath, osMatched) )
    {
        const std::vector<CPLString>& oVector =
                                m_oMapChildrenElementsConstraints[osMatched];
        const std::map<CPLString, CPLString>& oMapPrefixToURI =
                m_oChildrenElementsConstraintsXPathMatcher.GetMapPrefixToURI();
        for( size_t j = 0; j < oVector.size(); ++j )
        {
            const CPLString& osSubElt(oVector[j]);
            CPLString osSubEltPrefix;
            CPLString osSubEltURI;
            CPLString osSubEltType(osSubElt);
            size_t nPos = osSubElt.find(":");
            if( nPos != std::string::npos )
            {
                osSubEltPrefix = osSubElt.substr(0, nPos);
                osSubEltType = osSubElt.substr(nPos+1);

                const auto oIter2 = oMapPrefixToURI.find(osSubEltPrefix);
                if( oIter2 != oMapPrefixToURI.end() )
                {
                    osSubEltURI = oIter2->second;
                }
                else
                {
                    CPLError(CE_Warning, CPLE_AppDefined,
                            "Cannot find prefix of type constraint %s",
                            osSubElt.c_str());
                }
            }

            const CPLString osSubEltXPath(MakeXPath(osSubEltURI, osSubEltType));
            const auto oIter2 = m_oMapXPathToEltDecl.find(osSubEltXPath);
            if( oIter2 != m_oMapXPathToEltDecl.end() )
            {
                XSElementDeclaration* poSubElt = oIter2->second;
                if( IsEltCompatibleOfFC(poSubElt) )
                {
                    oVectorRes.push_back(poSubElt);
                }
            }
            else
            {
                CPLError(CE_Warning, CPLE_AppDefined,
                         "Cannot find element declaration of type constraint %s",
                         osSubElt.c_str());
            }
        }
    }
    return oVectorRes;
}

/************************************************************************/
/*                        GetOGRGeometryType()                          */
/************************************************************************/

static OGRwkbGeometryType GetOGRGeometryType( XSTypeDefinition* poTypeDef )
{
    const struct MyStruct
    {
        const char* pszName;
        OGRwkbGeometryType eType;
    } asArray[] = {
        { "GeometryPropertyType", wkbUnknown },
        { "PointPropertyType", wkbPoint },
        { "PolygonPropertyType", wkbPolygon },
        { "LineStringPropertyType", wkbLineString },
        { "MultiPointPropertyType", wkbMultiPoint },
        { "MultiPolygonPropertyType", wkbMultiPolygon },
        { "MultiLineStringPropertyType", wkbMultiLineString },
        { "MultiGeometryPropertyType", wkbGeometryCollection },
        { "MultiCurvePropertyType", wkbMultiCurve },
        { "MultiSurfacePropertyType", wkbMultiSurface },
        { "MultiSolidPropertyType", wkbUnknown },
        // GeometryArrayPropertyType ?
        // GeometricPrimitivePropertyType ?
        { "CurvePropertyType", wkbCurve },
        { "SurfacePropertyType", wkbSurface },
        // SurfaceArrayPropertyType ?
        // AbstractRingPropertyType ?
        // LinearRingPropertyType ?
        { "CompositeCurvePropertyType", wkbCurve },
        { "CompositeSurfacePropertyType", wkbSurface },
        { "CompositeSolidPropertyType", wkbUnknown },
        { "GeometricComplexPropertyType", wkbUnknown },
        { "SolidPropertyType", wkbPolyhedralSurface }
    };

    CPLString osName(transcode(poTypeDef->getName()));
    for( size_t i = 0; i < CPL_ARRAYSIZE(asArray); ++i )
    {
        if( osName == asArray[i].pszName )
            return asArray[i].eType;
    }
    return wkbNone;

#if 0
  <complexType name="CurveSegmentArrayPropertyType">
  <complexType name="KnotPropertyType">
  <complexType name="SurfacePatchArrayPropertyType">
  <complexType name="RingPropertyType">
  <complexType name="PolygonPatchArrayPropertyType">
  <complexType name="TrianglePatchArrayPropertyType">
  <complexType name="LineStringSegmentArrayPropertyType">
  <complexType name="SolidArrayPropertyType">
#endif
}


/************************************************************************/
/*                 GetOGRGeometryTypeFromGMLEltName()                   */
/************************************************************************/

static OGRwkbGeometryType GetOGRGeometryTypeFromGMLEltName(
                                                    const CPLString& osEltName )
{
    const struct MyStruct
    {
        const char* pszName;
        OGRwkbGeometryType eType;
    } asArray[] = {
        { "Point", wkbPoint },
        { "Polygon", wkbPolygon },
        { "Envelope", wkbPolygon },
        { "LineString", wkbLineString },
        { "MultiPoint", wkbMultiPoint },
        { "MultiPolygon", wkbMultiPolygon },
        { "MultiLineString", wkbMultiLineString },
        { "MultiGeometry", wkbGeometryCollection },
        { "MultiCurve", wkbMultiCurve },
        { "MultiSurface", wkbMultiSurface },
        { "MultiSolid", wkbUnknown },
        { "Curve", wkbCurve },
        { "Surface", wkbSurface },
        { "CompositeCurve", wkbCurve },
        { "CompositeSurface", wkbSurface },
        { "CompositeSolid", wkbUnknown },
        { "GeometricComplex", wkbUnknown },
    };

    for( size_t i = 0; i < CPL_ARRAYSIZE(asArray); ++i )
    {
        if( osEltName == asArray[i].pszName )
            return asArray[i].eType;
    }
    return wkbNone;
}

/************************************************************************/
/*                      CreateNonNestedRelationship()                  */
/************************************************************************/

void GMLASSchemaAnalyzer::CreateNonNestedRelationship(
                        XSElementDeclaration* poElt,
                        std::vector<XSElementDeclaration*>& apoImplEltList,
                        GMLASFeatureClass& oClass,
                        int nMaxOccurs,
                        bool bEltNameWillNeedPrefix,
                        bool bForceJunctionTable,
                        bool bCaseOfConstraintChildren )
{
    const CPLString osEltPrefix(
                GetPrefix(transcode(poElt->getNamespace())));
    const CPLString osEltName(transcode(poElt->getName()));
    const CPLString osOnlyElementXPath(
                    MakeXPath(transcode(poElt->getNamespace()),
                              osEltName) );
    const CPLString osElementXPath( oClass.GetXPath() + "/" +
                                    osOnlyElementXPath );

    if( !poElt->getAbstract() && !bCaseOfConstraintChildren )
    {
        apoImplEltList.insert(apoImplEltList.begin(), poElt);
    }

    std::set<CPLString> aoSetSubEltXPath;
    if( nMaxOccurs == 1 && !bForceJunctionTable )
    {
        // If the field isn't repeated, then we can link to each
        // potential realization types with a field

        for( size_t j = 0; j < apoImplEltList.size(); j++ )
        {
            XSElementDeclaration* poSubElt = apoImplEltList[j];
            const CPLString osSubEltName(transcode(poSubElt->getName()));
            const CPLString osSubEltXPath(
                MakeXPath(transcode(poSubElt->getNamespace()),
                          osSubEltName) );

            // For AbstractFeature_SpatialDataSet_pkid in SpatialDataSet_member
            if( aoSetSubEltXPath.find(osSubEltXPath) !=
                                            aoSetSubEltXPath.end() )
            {
                continue;
            }
            aoSetSubEltXPath.insert(osSubEltXPath);

            const CPLString osRealFullXPath( oClass.GetXPath() + "/" +
                    ((bCaseOfConstraintChildren) ?
                            osOnlyElementXPath + "/" : CPLString("")) +
                                             osSubEltXPath );

            if( IsIgnoredXPath( osRealFullXPath ) )
            {
#ifdef DEBUG_VERBOSE
                CPLDebug("GMLAS", "%s is in ignored xpaths",
                         osRealFullXPath.c_str());
#endif
                continue;
            }

            GMLASField oField;
            if( apoImplEltList.size() > 1 || bCaseOfConstraintChildren )
            {
                if( m_oMapEltNamesToInstanceCount[osSubEltName] > 1  )
                {
                    CPLString osLaunderedXPath(osSubEltXPath);
                    osLaunderedXPath.replaceAll(':', '_');
                    oField.SetName(
                        ((bEltNameWillNeedPrefix) ? osEltPrefix + "_" :
                                                                CPLString()) +
                                    transcode(poElt->getName()) + "_" +
                                    osLaunderedXPath + "_pkid" );
                }
                else
                {
                    oField.SetName(
                        ((bEltNameWillNeedPrefix) ? osEltPrefix + "_" :
                                                                CPLString()) +
                            transcode(poElt->getName()) + "_" +
                                    osSubEltName + "_pkid" );
                }
            }
            else
            {
                oField.SetName( transcode(poElt->getName()) + "_pkid" );
            }
            oField.SetXPath( osRealFullXPath );
            oField.SetMinOccurs( 0 );
            oField.SetMaxOccurs( nMaxOccurs );
            oField.SetCategory( GMLASField::PATH_TO_CHILD_ELEMENT_WITH_LINK );
            oField.SetRelatedClassXPath(osSubEltXPath);
            oField.SetType( GMLAS_FT_STRING, szXS_STRING );
            oClass.AddField( oField );
        }
    }
    else
    {
        // If the field is repeated, we need to use junction
        // tables
        for( size_t j = 0; j < apoImplEltList.size(); j++ )
        {
            XSElementDeclaration* poSubElt = apoImplEltList[j];
            const CPLString osSubEltName( transcode(poSubElt->getName()) );
            const CPLString osSubEltXPath(
                MakeXPath(transcode(poSubElt->getNamespace()), osSubEltName) );

            // For AbstractFeature_SpatialDataSet_pkid in SpatialDataSet_member
            if( aoSetSubEltXPath.find(osSubEltXPath) !=
                                            aoSetSubEltXPath.end() )
            {
                continue;
            }
            aoSetSubEltXPath.insert(osSubEltXPath);

            // Instantiate a junction table
            GMLASFeatureClass oJunctionTable;

            if( m_oMapEltNamesToInstanceCount[osSubEltName] > 1 )
            {
                CPLString osLaunderedXPath(osSubEltXPath);
                osLaunderedXPath.replaceAll(':', '_');
                oJunctionTable.SetName( oClass.GetName() + "_" +
                                        transcode(poElt->getName()) + "_" +
                                        osLaunderedXPath );
            }
            else
            {
                oJunctionTable.SetName( oClass.GetName() + "_" +
                                        transcode(poElt->getName()) + "_" +
                                        osSubEltName );
            }
            // Create a fake XPath binding the parent xpath (to an abstract
            // element) to the child element
            oJunctionTable.SetXPath(
                BuildJunctionTableXPath(osElementXPath, osSubEltXPath) );
            oJunctionTable.SetParentXPath( oClass.GetXPath() );
            oJunctionTable.SetChildXPath( osSubEltXPath );
            m_aoClasses.push_back(oJunctionTable);

            // Add an abstract field
            GMLASField oField;
            oField.SetName(
                ((bEltNameWillNeedPrefix) ? osEltPrefix + "_" :
                                                                CPLString()) +
                osEltName + "_" + osSubEltName );
            oField.SetXPath( oClass.GetXPath() + "/" +
                    ((bCaseOfConstraintChildren) ?
                            osOnlyElementXPath + "/" : CPLString("")) +
                                                osSubEltXPath);
            oField.SetMinOccurs( 0 );
            oField.SetMaxOccurs( nMaxOccurs );
            oField.SetAbstractElementXPath(osElementXPath);
            oField.SetRelatedClassXPath(osSubEltXPath);
            oField.SetCategory(
                    GMLASField::PATH_TO_CHILD_ELEMENT_WITH_JUNCTION_TABLE );
            oClass.AddField( oField );
        }
    }

#if 0
    GMLASField oField;
    oField.SetName( transcode(poElt->getName()) );
    oField.SetXPath( osElementXPath );
    oField.SetMinOccurs( poParticle->getMinOccurs() );
    oField.SetMaxOccurs( poParticle->getMaxOccursUnbounded() ?
        MAXOCCURS_UNLIMITED : poParticle->getMaxOccurs() );

    for( size_t j = 0; j < apoImplEltList.size(); j++ )
    {
        XSElementDeclaration* poSubElt = apoImplEltList[j];
        XSTypeDefinition* poSubEltType =
                                    poSubElt->getTypeDefinition();
        XSComplexTypeDefinition* poCT =
            reinterpret_cast<XSComplexTypeDefinition*>(poSubEltType);

        GMLASFeatureClass oNestedClass;
        oNestedClass.SetName( oClass.GetName() + "_" +
                    transcode(poSubElt->getName()) );
        oNestedClass.SetXPath( oClass.GetXPath() + "/" +
            MakeXPath(transcode(poSubElt->getNamespace()),
                        transcode(poSubElt->getName())) );

        std::set<XSModelGroup*>
            oSetNewVisitedModelGroups(oSetVisitedModelGroups);
        if( !ExploreModelGroup(
                poCT->getParticle()->getModelGroupTerm(),
                NULL,
                oNestedClass,
                nRecursionCounter + 1,
                oSetNewVisitedModelGroups ) )
        {
            return false;
        }

        oClass.AddNestedClass( oNestedClass );
    }

    if( !apoImplEltList.empty() )
    {
        oField.SetAbstract(true);
    }
    else
    {
        oField.SetType( GMLAS_FT_ANYTYPE, "anyType" );
        oField.SetXPath( oClass.GetXPath() + "/" + "*" );
        oField.SetIncludeThisEltInBlob( true );
    }
    oClass.AddField( oField );
#endif
}

/************************************************************************/
/*                          IsIgnoredXPath()                            */
/************************************************************************/

bool GMLASSchemaAnalyzer::IsIgnoredXPath(const CPLString& osXPath)
{
    CPLString osIgnored;
    return m_oIgnoredXPathMatcher.MatchesRefXPath(osXPath, osIgnored);
}

/************************************************************************/
/*                     FindElementsWithMustBeToLevel()                  */
/************************************************************************/

bool GMLASSchemaAnalyzer::FindElementsWithMustBeToLevel(
                            const CPLString& osParentXPath,
                            XSModelGroup* poModelGroup,
                            int nRecursionCounter,
                            std::set<XSElementDeclaration*>& oSetVisitedEltDecl,
                            std::set<XSModelGroup*>& oSetVisitedModelGroups,
                            std::vector<XSElementDeclaration*>&
                                                        oVectorEltsForTopClass,
                            std::set<CPLString>& aoSetXPathEltsForTopClass,
                            XSModel* poModel,
                            bool& bSimpleEnoughOut,
                            int& nCountSubEltsOut )
{
    const bool bAlreadyVisitedMG =
            ( oSetVisitedModelGroups.find(poModelGroup) !=
                                                oSetVisitedModelGroups.end() );

    oSetVisitedModelGroups.insert(poModelGroup);

    if( nRecursionCounter == 100 )
    {
        // Presumably an hostile schema
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Schema analysis failed due to too deeply nested model");
        return false;
    }

    {
        CPLString osIgnored;
        if( m_oDisabledFlattenedXPathMatcher.MatchesRefXPath(
                                            osParentXPath, osIgnored))
        {
            bSimpleEnoughOut = false;
        }
    }

    XSParticleList* poParticles = poModelGroup->getParticles();
    for(size_t i = 0; i < poParticles->size(); ++i )
    {
        XSParticle* poParticle = poParticles->elementAt(i);

        const bool bRepeatedParticle = poParticle->getMaxOccursUnbounded() ||
                                        poParticle->getMaxOccurs() > 1;

        if( poParticle->getTermType() == XSParticle::TERM_ELEMENT )
        {
            XSElementDeclaration* poElt = poParticle->getElementTerm();
            XSTypeDefinition* poTypeDef = poElt->getTypeDefinition();
            const CPLString osEltName(transcode(poElt->getName()));
            const CPLString osEltNS(transcode(poElt->getNamespace()));
            const CPLString osXPath( MakeXPath(osEltNS, osEltName) );
            const CPLString osFullXPath( osParentXPath + "/" + osXPath );

#ifdef DEBUG_SUPER_VERBOSE
            CPLDebug("GMLAS", "FindElementsWithMustBeToLevel: %s",
                     osFullXPath.c_str());
#endif

            if( IsIgnoredXPath( osFullXPath ) )
            {
#ifdef DEBUG_VERBOSE
                CPLDebug("GMLAS", "%s is in ignored xpaths",
                         osFullXPath.c_str());
#endif
                continue;
            }

            // This could be refined to detect if the repeated element might not
            // be simplifiable as an array
            if( bSimpleEnoughOut && bRepeatedParticle )
            {
#ifdef DEBUG_VERBOSE
                CPLDebug("GMLAS",
                         "%s not inlinable because %s is repeated",
                         osParentXPath.c_str(),
                         osXPath.c_str()
                        );
#endif
                bSimpleEnoughOut = false;
            }

            // We don't want to inline
            // sub-classes with hundereds of attributes
            nCountSubEltsOut ++;

            std::vector<XSElementDeclaration*> apoImplEltList;
            GetConcreteImplementationTypes(poElt, apoImplEltList);

            std::vector<XSElementDeclaration*> apoChildrenElements =
                GetConstraintChildrenElements(osFullXPath);

            // Special case for a GML geometry property
            if( IsGMLNamespace(transcode(poTypeDef->getNamespace())) &&
                GetOGRGeometryType(poTypeDef) != wkbNone )
            {
                // Do nothing
            }
            else if( IsGMLNamespace(osEltNS) &&
                     GetOGRGeometryTypeFromGMLEltName(osEltName) != wkbNone )
            {
                // Do nothing
            }
            // Any GML abstract type
            else if( poElt->getAbstract() &&
                     IsGMLNamespace(osEltNS) &&
                     osEltName != "_Feature" &&
                     osEltName != "AbstractFeature" &&
                     osEltName != "AbstractTimeObject" )
            {
                // Do nothing
            }
            // Are there substitution groups for this element ?
            else if( !apoImplEltList.empty() || !apoChildrenElements.empty() )
            {
                if( !apoChildrenElements.empty() )
                {
                    apoImplEltList = apoChildrenElements;
                }
                else if( !poElt->getAbstract() )
                {
                    apoImplEltList.insert(apoImplEltList.begin(), poElt);
                }
                for( size_t j = 0; j < apoImplEltList.size(); j++ )
                {
                    XSElementDeclaration* poSubElt = apoImplEltList[j];
                    const CPLString osSubEltXPath(
                        MakeXPath(transcode(poSubElt->getNamespace()),
                                    transcode(poSubElt->getName())) );

                    if( IsIgnoredXPath( osParentXPath + "/" + osSubEltXPath ) )
                    {
#ifdef DEBUG_VERBOSE
                        CPLDebug("GMLAS", "%s is in ignored xpaths",
                                 (osParentXPath + "/" + osSubEltXPath).c_str());
#endif
                        continue;
                    }

                    // Make sure we will instantiate the referenced element
                    if( m_oSetEltsForTopClass.find( poSubElt ) ==
                                m_oSetEltsForTopClass.end() &&
                        aoSetXPathEltsForTopClass.find( osSubEltXPath )
                                == aoSetXPathEltsForTopClass.end() )
                    {
#ifdef DEBUG_VERBOSE
                        CPLDebug("GMLAS", "%s (%s) must be exposed as "
                                     "top-level (%s of %s)",
                                osSubEltXPath.c_str(),
                                transcode(poSubElt->getTypeDefinition()->
                                                            getName()).c_str(),
                                apoChildrenElements.empty() ?
                                                "derived class" : "child",
                                osParentXPath.c_str()
                                );
#endif

                        oSetVisitedEltDecl.insert(poSubElt);
                        m_oSetEltsForTopClass.insert(poSubElt);
                        oVectorEltsForTopClass.push_back(poSubElt);
                        aoSetXPathEltsForTopClass.insert( osSubEltXPath );

                        XSComplexTypeDefinition* poSubEltCT =
                                            IsEltCompatibleOfFC(poSubElt);
                        if( !bAlreadyVisitedMG &&
                            poSubEltCT != nullptr &&
                            poSubEltCT->getParticle() != nullptr )
                        {
                            bool bSubSimpleEnoughOut = true;
                            int nSubCountSubElt = 0;
                            if( !FindElementsWithMustBeToLevel(
                                            osSubEltXPath,
                                            poSubEltCT->getParticle()->
                                                        getModelGroupTerm(),
                                            nRecursionCounter + 1,
                                            oSetVisitedEltDecl,
                                            oSetVisitedModelGroups,
                                            oVectorEltsForTopClass,
                                            aoSetXPathEltsForTopClass,
                                            poModel,
                                            bSubSimpleEnoughOut,
                                            nSubCountSubElt ) )
                            {
                                return false;
                            }
                        }
                    }
                }
            }

            else if( !poElt->getAbstract() &&
                poTypeDef->getTypeCategory() == XSTypeDefinition::COMPLEX_TYPE )
            {
                nCountSubEltsOut --;

                XSComplexTypeDefinition* poEltCT = IsEltCompatibleOfFC(poElt);
                if( poEltCT )
                {
                    // Might be a bit extreme, but for now we don't inline
                    // classes that have subclasses.
                    if( bSimpleEnoughOut )
                    {
#ifdef DEBUG_VERBOSE
                        CPLDebug("GMLAS",
                                  "%s not inlinable because %s field is complex",
                                  osParentXPath.c_str(),
                                 osXPath.c_str());
#endif
                        bSimpleEnoughOut = false;
                    }

                    if( oSetVisitedEltDecl.find(poElt) !=
                                    oSetVisitedEltDecl.end() )
                    {
                        if( m_oSetEltsForTopClass.find(poElt) ==
                                                m_oSetEltsForTopClass.end() &&
                            m_oSetSimpleEnoughElts.find(poElt) ==
                                        m_oSetSimpleEnoughElts.end() &&
                            aoSetXPathEltsForTopClass.find( osXPath )
                                == aoSetXPathEltsForTopClass.end() )
                        {
                            CPLString osIgnored;
                            if( !m_oForcedFlattenedXPathMatcher.MatchesRefXPath(
                                                        osXPath, osIgnored))
                            {
#ifdef DEBUG_VERBOSE
                                CPLDebug("GMLAS", "%s (%s) must be exposed as "
                                        "top-level (multiple time referenced)",
                                        osXPath.c_str(),
                                        transcode(
                                            poTypeDef->getNamespace()).c_str());
#endif
                                m_oSetEltsForTopClass.insert(poElt);
                                oVectorEltsForTopClass.push_back(poElt);
                                aoSetXPathEltsForTopClass.insert( osXPath );
                            }
                        }
                    }
                    else
                    {
                        oSetVisitedEltDecl.insert(poElt);

                        if( !bAlreadyVisitedMG &&
                            poEltCT->getParticle() != nullptr )
                        {
                            bool bSubSimpleEnoughOut = true;
                            int nSubCountSubElt = 0;

                            // Process attributes
                            XSAttributeUseList* poAttrList =
                                                    poEltCT->getAttributeUses();
                            const size_t nAttrListSize = (poAttrList != nullptr) ?
                                                                poAttrList->size(): 0;
                            for(size_t j=0; bSubSimpleEnoughOut && j< nAttrListSize; ++j )
                            {
                                XSAttributeUse* poAttr = poAttrList->elementAt(j);
                                GMLASField oField;
                                SetFieldFromAttribute(oField, poAttr, osFullXPath);
                                if( !IsIgnoredXPath( oField.GetXPath() ) &&
                                    oField.GetFixedValue().empty() )
                                {
#ifdef DEBUG_SUPER_VERBOSE
                                    CPLDebug("GMLAS", "FindElementsWithMustBeToLevel: %s",
                                             oField.GetXPath().c_str());
#endif
                                    nSubCountSubElt ++;
                                }
                            }

                            if( !FindElementsWithMustBeToLevel(
                                            osFullXPath,
                                            poEltCT->getParticle()->
                                                            getModelGroupTerm(),
                                            nRecursionCounter + 1,
                                            oSetVisitedEltDecl,
                                            oSetVisitedModelGroups,
                                            oVectorEltsForTopClass,
                                            aoSetXPathEltsForTopClass,
                                            poModel,
                                            bSubSimpleEnoughOut,
                                            nSubCountSubElt ) )
                            {
                                return false;
                            }
                            if( bSubSimpleEnoughOut )
                            {
#ifdef DEBUG_VERBOSE
                                CPLDebug("GMLAS",
                                    "%s is inlinable: %d fields",
                                    osXPath.c_str(),
                                    nSubCountSubElt
                                    );
#endif
                                m_oSetSimpleEnoughElts.insert(poElt);

                                nCountSubEltsOut += nSubCountSubElt;
                            }
                            else if( bSimpleEnoughOut )
                            {
#ifdef DEBUG_VERBOSE
                                CPLDebug("GMLAS",
                                    "%s not inlinable because %s is not inlinable",
                                    osParentXPath.c_str(),
                                    osXPath.c_str()
                                    );
#endif
                                bSimpleEnoughOut = false;
                            }
                        }
                    }
                }
                else
                {
                    if( transcode(poElt->getName()) != szFEATURE_COLLECTION )
                    {
                        poEltCT =
                            reinterpret_cast<XSComplexTypeDefinition*>(poTypeDef);
// Process attributes
                        XSAttributeUseList* poAttrList =
                                                poEltCT->getAttributeUses();
                        const size_t nAttrListSize = (poAttrList != nullptr) ?
                                                            poAttrList->size(): 0;
                        for(size_t j=0; bSimpleEnoughOut && j< nAttrListSize; ++j )
                        {
                            XSAttributeUse* poAttr = poAttrList->elementAt(j);
                            GMLASField oField;
                            SetFieldFromAttribute(oField, poAttr, osFullXPath);
                            if( !IsIgnoredXPath( oField.GetXPath() ) &&
                                oField.GetFixedValue().empty() )
                            {
#ifdef DEBUG_SUPER_VERBOSE
                                CPLDebug("GMLAS", "FindElementsWithMustBeToLevel: %s",
                                            oField.GetXPath().c_str());
#endif
                                nCountSubEltsOut ++;
                            }
                        }
                    }
                }

                CPLString osTargetElement;
                if( poElt->getAnnotation() != nullptr )
                {
                    CPLString osAnnot(transcode(
                        poElt->getAnnotation()->getAnnotationString()));

#ifdef DEBUG_SUPER_VERBOSE
                    CPLDebug("GMLAS", "Annot: %s", osAnnot.c_str());
#endif
                    CPLXMLNode* psRoot = CPLParseXMLString(osAnnot);
                    CPLStripXMLNamespace(psRoot, nullptr, TRUE);
                    osTargetElement =
                        CPLGetXMLValue(psRoot,
                                       "=annotation.appinfo.targetElement", "");
                    CPLDestroyXMLNode(psRoot);
#ifdef DEBUG_VERBOSE
                    if( !osTargetElement.empty() )
                        CPLDebug("GMLAS", "targetElement: %s",
                                osTargetElement.c_str());
#endif
                }

                // If we have a element of type gml:ReferenceType that has
                // a targetElement in its annotation.appinfo, then create
                // a dedicated field to have cross-layer relationships.
                if( IsGMLNamespace(transcode(poTypeDef->getNamespace())) &&
                    transcode(poTypeDef->getName()) == "ReferenceType" &&
                    !osTargetElement.empty() )
                {
                    XSElementDeclaration* poTargetElt =
                        GetTopElementDeclarationFromXPath(osTargetElement,
                                                            poModel);
                    // TODO: even for non abstract we should probably
                    // handle substitutions
                    if( poTargetElt != nullptr && !poTargetElt->getAbstract() )
                    {
                        const CPLString osTargetEltXPath( MakeXPath(
                                    transcode(poTargetElt->getNamespace()),
                                    transcode(poTargetElt->getName())) );

                        if( IsIgnoredXPath( osTargetEltXPath ) )
                        {
#ifdef DEBUG_VERBOSE
                            CPLDebug("GMLAS", "%s is in ignored xpaths",
                                    osTargetEltXPath.c_str());
#endif
                            continue;
                        }

                        // Make sure we will instantiate the referenced
                        //element
                        if( m_oSetEltsForTopClass.find( poTargetElt ) ==
                                    m_oSetEltsForTopClass.end() &&
                            aoSetXPathEltsForTopClass.find( osTargetEltXPath )
                                == aoSetXPathEltsForTopClass.end() )
                        {
#ifdef DEBUG_VERBOSE
                            CPLDebug("GMLAS",
                                        "%d: Adding %s as (%s) needed type",
                                        __LINE__,
                                    osTargetElement.c_str(),
                                    transcode(poTargetElt->
                                                getTypeDefinition()->
                                                    getName()).c_str());
#endif
                            oSetVisitedEltDecl.insert(poTargetElt);
                            m_oSetEltsForTopClass.insert( poTargetElt );
                            oVectorEltsForTopClass.push_back(poTargetElt);
                            aoSetXPathEltsForTopClass.insert( osTargetEltXPath );
                        }

                        XSComplexTypeDefinition* poTargetEltCT =
                                    IsEltCompatibleOfFC(poTargetElt);
                        if( !bAlreadyVisitedMG &&
                            poTargetEltCT &&
                            poTargetEltCT->getParticle() != nullptr )
                        {
                            bool bSubSimpleEnoughOut = true;
                            int nSubCountSubElt = 0;
                            if( !FindElementsWithMustBeToLevel(
                                            osTargetEltXPath,
                                            poTargetEltCT->getParticle()->
                                                            getModelGroupTerm(),
                                            nRecursionCounter + 1,
                                            oSetVisitedEltDecl,
                                            oSetVisitedModelGroups,
                                            oVectorEltsForTopClass,
                                            aoSetXPathEltsForTopClass,
                                            poModel,
                                            bSubSimpleEnoughOut,
                                            nSubCountSubElt) )
                            {
                                return false;
                            }
                        }
                    }
                }
            }
        }
        else if( !bAlreadyVisitedMG &&
                 poParticle->getTermType() == XSParticle::TERM_MODELGROUP )
        {
            // This could be refined to detect if the repeated element might not
            // be simplifiable as an array
            if( bSimpleEnoughOut && bRepeatedParticle )
            {
#ifdef DEBUG_VERBOSE
                CPLDebug("GMLAS",
                        "%s not inlinable because there is a repeated particle",
                        osParentXPath.c_str());
#endif
                bSimpleEnoughOut = false;
            }

            XSModelGroup* psSubModelGroup = poParticle->getModelGroupTerm();
            if( !FindElementsWithMustBeToLevel(
                                    osParentXPath,
                                    psSubModelGroup,
                                    nRecursionCounter + 1,
                                    oSetVisitedEltDecl,
                                    oSetVisitedModelGroups,
                                    oVectorEltsForTopClass,
                                    aoSetXPathEltsForTopClass,
                                    poModel,
                                    bSimpleEnoughOut,
                                    nCountSubEltsOut) )
            {
                return false;
            }
        }
        else
        {
            // This could be refined to detect if the repeated element might not
            // be simplifiable as an array
            if( bSimpleEnoughOut && bRepeatedParticle )
            {
#ifdef DEBUG_VERBOSE
                CPLDebug("GMLAS",
                        "%s not inlinable because there is a repeated particle",
                        osParentXPath.c_str());
#endif
                bSimpleEnoughOut = false;
            }
        }
    }

    if( bSimpleEnoughOut &&
        nCountSubEltsOut > m_nMaximumFieldsForFlattening )
    {
        CPLString osIgnored;
        if( !m_oForcedFlattenedXPathMatcher.MatchesRefXPath(
                                                osParentXPath, osIgnored))
        {
#ifdef DEBUG_VERBOSE
            CPLDebug("GMLAS",
                        "%s not inlinable because it has more than %d fields",
                        osParentXPath.c_str(),
                        m_nMaximumFieldsForFlattening);
#endif
            bSimpleEnoughOut = false;
        }
    }

    return true;
}

/************************************************************************/
/*                           IsGMLNamespace()                           */
/************************************************************************/

bool GMLASSchemaAnalyzer::IsGMLNamespace(const CPLString& osURI)
{
    if( osURI.find(szGML_URI) == 0 )
        return true;
    // Below is mostly for unit tests were we use xmlns:gml="http://fake_gml"
    const auto oIter = m_oMapURIToPrefix.find(osURI);
    return oIter != m_oMapURIToPrefix.end() && oIter->second == szGML_PREFIX;
}

/************************************************************************/
/*                    BuildMapCountOccurrencesOfSameName()               */
/************************************************************************/

void GMLASSchemaAnalyzer::BuildMapCountOccurrencesOfSameName(
                    XSModelGroup* poModelGroup,
                    std::map< CPLString, int >& oMapCountOccurrencesOfSameName)
{
    XSParticleList* poParticles = poModelGroup->getParticles();
    for(size_t i = 0; i < poParticles->size(); ++i )
    {
        XSParticle* poParticle = poParticles->elementAt(i);
        if( poParticle->getTermType() == XSParticle::TERM_ELEMENT )
        {
            XSElementDeclaration* poElt = poParticle->getElementTerm();
            const CPLString osEltName(transcode(poElt->getName()));
            oMapCountOccurrencesOfSameName[ osEltName ] ++;
        }
        else if( poParticle->getTermType() == XSParticle::TERM_MODELGROUP )
        {
            XSModelGroup* psSubModelGroup = poParticle->getModelGroupTerm();
            BuildMapCountOccurrencesOfSameName(psSubModelGroup,
                                              oMapCountOccurrencesOfSameName);
        }
    }
}

/************************************************************************/
/*                         ComposeMinOccurs()                           */
/************************************************************************/

static int ComposeMinOccurs(int nVal1, int nVal2)
{
    return nVal1 * nVal2;
}

/************************************************************************/
/*                         ComposeMaxOccurs()                           */
/************************************************************************/

static int ComposeMaxOccurs(int nVal1, int nVal2)
{
    if( nVal1 == MAXOCCURS_UNLIMITED || nVal2 == MAXOCCURS_UNLIMITED )
        return MAXOCCURS_UNLIMITED;
    return nVal1 * nVal2;
}

/************************************************************************/
/*                         ExploreModelGroup()                          */
/************************************************************************/

bool GMLASSchemaAnalyzer::ExploreModelGroup(
                            XSModelGroup* poModelGroup,
                            XSAttributeUseList* poMainAttrList,
                            GMLASFeatureClass& oClass,
                            int nRecursionCounter,
                            std::set<XSModelGroup*>& oSetVisitedModelGroups,
                            XSModel* poModel,
                            const std::map< CPLString, int >& oMapCountOccurrencesOfSameName)
{
    if( oSetVisitedModelGroups.find(poModelGroup) !=
                                                oSetVisitedModelGroups.end() )
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "%s already visited",
                 oClass.GetXPath().c_str());
        return false;
    }
    oSetVisitedModelGroups.insert(poModelGroup);

    if( nRecursionCounter == 100 )
    {
        // Presumably an hostile schema
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Schema analysis failed due to too deeply nested model");
        return false;
    }

    if( poMainAttrList != nullptr )
    {
        const size_t nMainAttrListSize = poMainAttrList->size();
        for(size_t j=0; j < nMainAttrListSize; ++j )
        {
            GMLASField oField;
            XSAttributeUse* poAttr = poMainAttrList->elementAt(j);
            SetFieldFromAttribute(oField, poAttr, oClass.GetXPath());

            if( IsIgnoredXPath( oField.GetXPath() ) )
            {
#ifdef DEBUG_VERBOSE
                CPLDebug("GMLAS", "%s is in ignored xpaths",
                        oField.GetXPath().c_str());
#endif
                if( !oField.GetFixedValue().empty() ||
                    !oField.GetDefaultValue().empty() )
                {
                    oField.SetIgnored();
                }
                else
                {
                    continue;
                }
            }

            oClass.AddField(oField);
        }
    }

    XSParticleList* poParticles = poModelGroup->getParticles();

    // Special case for GML 3.1.1 where gml:metaDataProperty should be
    // a sequence of gml:_Metadata but for some reason they have used
    // a sequence of any.
    if( oClass.GetXPath() == "gml:metaDataProperty" &&
        poModelGroup->getCompositor() ==
                                XSModelGroup::COMPOSITOR_SEQUENCE &&
        poParticles->size() == 1 &&
        poParticles->elementAt(0)->
                        getTermType() == XSParticle::TERM_WILDCARD )
    {
        XSElementDeclaration* poGMLMetadata =
            GetTopElementDeclarationFromXPath("gml:_MetaData", poModel);
        if( poGMLMetadata != nullptr )
        {
            std::vector<XSElementDeclaration*> apoImplEltList;
            GetConcreteImplementationTypes(poGMLMetadata, apoImplEltList);
            CreateNonNestedRelationship(poGMLMetadata,
                                        apoImplEltList,
                                        oClass,
                                        1,
                                        false, // doesn't need prefix
                                        true, // force junction table
                                        false // regular case
                                       );

            return true;
        }
    }

    const bool bIsChoice = (poModelGroup->getCompositor() ==
                                            XSModelGroup::COMPOSITOR_CHOICE);
    int nGroup = 0;

    for(size_t i = 0; i < poParticles->size(); ++i )
    {
        XSParticle* poParticle = poParticles->elementAt(i);
        const bool bRepeatedParticle = poParticle->getMaxOccursUnbounded() ||
                                       poParticle->getMaxOccurs() > 1;
        const int nMinOccurs = static_cast<int>(poParticle->getMinOccurs());
        const int nMaxOccurs =
                        poParticle->getMaxOccursUnbounded() ?
                            MAXOCCURS_UNLIMITED :
                            static_cast<int>(poParticle->getMaxOccurs());

        if( poParticle->getTermType() == XSParticle::TERM_ELEMENT )
        {
            XSElementDeclaration* poElt = poParticle->getElementTerm();
            const CPLString osEltName(transcode(poElt->getName()));

            const auto oIter = oMapCountOccurrencesOfSameName.find(osEltName);
            const bool bEltNameWillNeedPrefix =
                oIter != oMapCountOccurrencesOfSameName.end() &&
                oIter->second > 1;
            const CPLString osEltNS(transcode(poElt->getNamespace()));
            const CPLString osPrefixedEltName(
                (bEltNameWillNeedPrefix ? GetPrefix(osEltNS) + "_" : CPLString()) +
                osEltName);
            const CPLString osOnlyElementXPath(MakeXPath(osEltNS, osEltName));
            const CPLString osElementXPath( oClass.GetXPath() + "/" +
                                            osOnlyElementXPath );
#ifdef DEBUG_VERBOSE
            CPLDebug("GMLAS", "Iterating through %s", osElementXPath.c_str());
#endif

            if( IsIgnoredXPath( osElementXPath ) )
            {
#ifdef DEBUG_VERBOSE
                CPLDebug("GMLAS", "%s is in ignored xpaths",
                         osElementXPath.c_str());
#endif
                continue;
            }

            CPLString osTargetElement;
            if( poElt->getAnnotation() != nullptr )
            {
                CPLString osAnnot(transcode(
                    poElt->getAnnotation()->getAnnotationString()));

#ifdef DEBUG_SUPER_VERBOSE
                CPLDebug("GMLAS", "Annot: %s", osAnnot.c_str());
#endif
                CPLXMLNode* psRoot = CPLParseXMLString(osAnnot);
                CPLStripXMLNamespace(psRoot, nullptr, TRUE);
                osTargetElement =
                    CPLGetXMLValue(psRoot, "=annotation.appinfo.targetElement", "");
                CPLDestroyXMLNode(psRoot);
#ifdef DEBUG_VERBOSE
                if( !osTargetElement.empty() )
                    CPLDebug("GMLAS", "targetElement: %s",
                             osTargetElement.c_str());
#endif
            }

            XSTypeDefinition* poTypeDef = poElt->getTypeDefinition();

            std::vector<XSElementDeclaration*> apoImplEltList;
            GetConcreteImplementationTypes(poElt, apoImplEltList);

            std::vector<XSElementDeclaration*> apoChildrenElements =
                GetConstraintChildrenElements(osElementXPath);

            // Special case for a GML geometry property
            OGRwkbGeometryType eGeomType = wkbNone;

            if( !apoChildrenElements.empty() )
            {
                CreateNonNestedRelationship(poElt,
                                            apoChildrenElements,
                                            oClass,
                                            nMaxOccurs,
                                            bEltNameWillNeedPrefix,
                                            false, // do not force junction table
                                            true // special case for children elements
                                           );
            }

            else if( IsGMLNamespace(transcode(poTypeDef->getNamespace())) &&
                (eGeomType = GetOGRGeometryType(poTypeDef)) != wkbNone )
            {
                GMLASField oField;
                oField.SetName( osPrefixedEltName );
                oField.SetMinOccurs( nMinOccurs );
                oField.SetMaxOccurs( nMaxOccurs );
                oField.SetType( GMLAS_FT_GEOMETRY, szFAKEXS_GEOMETRY );
                if( nMaxOccurs > 1 || nMaxOccurs == MAXOCCURS_UNLIMITED )
                {
                    // Repeated geometry property can happen in some schemas
                    // like inspire.ec.europa.eu/schemas/ge_gp/4.0/GeophysicsCore.xsd
                    // or http://ngwd-bdnes.cits.nrcan.gc.ca/service/gwml/schemas/2.1/gwml2-flow.xsd
                    oField.SetGeomType( wkbUnknown );
                    oField.SetArray( true );
                }
                else
                    oField.SetGeomType( eGeomType );
                oField.SetXPath( osElementXPath );
                oField.SetDocumentation( GetAnnotationDoc( poElt ) );

                oClass.AddField( oField );
            }

            else if( IsGMLNamespace(osEltNS) &&
                     (eGeomType = GetOGRGeometryTypeFromGMLEltName(osEltName))
                                                                    != wkbNone )
            {
                GMLASField oField;
                oField.SetName( osPrefixedEltName );
                oField.SetMinOccurs( nMinOccurs );
                oField.SetMaxOccurs( nMaxOccurs );

                oField.SetType( GMLAS_FT_GEOMETRY, szFAKEXS_GEOMETRY );
                oField.SetGeomType( eGeomType );
                oField.SetArray( nMaxOccurs > 1 ||
                                    nMaxOccurs == MAXOCCURS_UNLIMITED );

                oField.SetXPath( osElementXPath );
                oField.SetIncludeThisEltInBlob( true );
                oField.SetDocumentation( GetAnnotationDoc( poElt ) );

                oClass.AddField( oField );
            }

            // Any GML abstract type
            else if( poElt->getAbstract() &&
                     IsGMLNamespace(osEltNS) &&
                     osEltName != "_Feature" &&
                     osEltName != "AbstractFeature" &&
                     osEltName != "AbstractTimeObject" )
            {
                GMLASField oField;
                oField.SetName( osPrefixedEltName );
                oField.SetMinOccurs( nMinOccurs );
                oField.SetMaxOccurs( nMaxOccurs );
                if( osEltName == "AbstractGeometry" )
                {
                    oField.SetType( GMLAS_FT_GEOMETRY, szFAKEXS_GEOMETRY );
                    oField.SetGeomType( wkbUnknown );
                    oField.SetArray( nMaxOccurs > 1 ||
                                     nMaxOccurs == MAXOCCURS_UNLIMITED );
                }
                else
                {
                    oField.SetType( GMLAS_FT_ANYTYPE, szXS_ANY_TYPE );
                }
                oField.SetIncludeThisEltInBlob( true );
                oField.SetDocumentation( GetAnnotationDoc( poElt ) );

                for( size_t j = 0; j < apoImplEltList.size(); j++ )
                {
                    XSElementDeclaration* poSubElt = apoImplEltList[j];
                    oField.AddAlternateXPath( oClass.GetXPath() + "/" +
                         MakeXPath(transcode(poSubElt->getNamespace()),
                                   transcode(poSubElt->getName())) );
                }

                oClass.AddField( oField );
            }

            // Are there substitution groups for this element ?
            // or is this element already identified as being a top-level one ?
            else if( !apoImplEltList.empty() ||
                     (m_oSetEltsForTopClass.find(poElt) !=
                                                m_oSetEltsForTopClass.end() &&
                      m_oSetSimpleEnoughElts.find(poElt) ==
                                                m_oSetSimpleEnoughElts.end()) )
            {
                CreateNonNestedRelationship(poElt,
                                            apoImplEltList,
                                            oClass,
                                            nMaxOccurs,
                                            bEltNameWillNeedPrefix,
                                            false, // do not force junction table
                                            false // regular case
                                           );
            }

            // Abstract element without realizations !
            else if ( poElt->getAbstract() )
            {
                // Do nothing with it since it cannot be instantiated
                // in a valid way.
                CPLDebug("GMLAS",
                         "Ignoring %s that is abstract without realizations",
                         osElementXPath.c_str());
            }

            // Simple type like string, int, etc...
            else
            if( poTypeDef->getTypeCategory() == XSTypeDefinition::SIMPLE_TYPE )
            {
                XSSimpleTypeDefinition* poST =
                            reinterpret_cast<XSSimpleTypeDefinition*>(poTypeDef);
                GMLASField oField;
                SetFieldTypeAndWidthFromDefinition(poST, oField);
                oField.SetMinOccurs( (bIsChoice) ? 0 : nMinOccurs );
                oField.SetMaxOccurs( nMaxOccurs );
                oField.SetDocumentation( GetAnnotationDoc( poElt ) );

                bool bNeedAuxTable = false;
                const bool bIsList =
                    ( poST->getVariety() == XSSimpleTypeDefinition::VARIETY_LIST );
                if( bIsList )
                {
                    SetFieldTypeAndWidthFromDefinition(poST->getItemType(),
                                                       oField);
                    if( bRepeatedParticle || !m_bUseArrays ||
                        !IsCompatibleOfArray(oField.GetType()) )
                    {
                        // Really particular case. This is a workaround
                        oField.SetType( GMLAS_FT_STRING, szXS_STRING );
                    }
                    else
                    {
                        oField.SetList( true );
                        oField.SetArray( true );
                    }
                }

                if( m_bUseArrays && bRepeatedParticle &&
                    IsCompatibleOfArray(oField.GetType()) )
                {
                    oField.SetArray( true );
                }
                else if( bRepeatedParticle )
                {
                    bNeedAuxTable = true;
                }
                if( bNeedAuxTable )
                {
                    GMLASFeatureClass oNestedClass;
                    oNestedClass.SetName( oClass.GetName() + "_" +
                                          osPrefixedEltName );
                    oNestedClass.SetXPath( osElementXPath );
                    GMLASField oUniqueField;
                    oUniqueField.SetName("value");
                    oUniqueField.SetMinOccurs( 1 );
                    oUniqueField.SetMaxOccurs( 1 );
                    oUniqueField.SetXPath( osElementXPath );
                    oUniqueField.SetType( oField.GetType(),
                                          oField.GetTypeName() );
                    oNestedClass.AddField(oUniqueField);
                    oNestedClass.SetDocumentation( GetAnnotationDoc( poElt ) );

                    oClass.AddNestedClass( oNestedClass );

                    oField.SetType( GMLAS_FT_STRING, "" );
                    oField.SetName( osPrefixedEltName );
                    oField.SetXPath( osElementXPath );
                    oField.SetCategory(
                                    GMLASField::PATH_TO_CHILD_ELEMENT_NO_LINK);
                    oField.SetRelatedClassXPath( oField.GetXPath() );
                    oClass.AddField(oField);
                }
                else
                {
                    oField.SetName( osPrefixedEltName );
                    oField.SetXPath( osElementXPath );
                    if( !bIsChoice && nMinOccurs > 0 &&
                        !poElt->getNillable() )
                    {
                        oField.SetNotNullable( true );
                    }
                    oClass.AddField(oField);

                    // If the element has minOccurs=0 and is nillable, then we
                    // need an extra field to be able to distinguish between the
                    // case of the missing element or the element with
                    // xsi:nil="true"
                    if( nMinOccurs == 0 && poElt->getNillable() && !m_bUseNullState )
                    {
                        GMLASField oFieldNil;
                        oFieldNil.SetName( osPrefixedEltName + "_" + szNIL );
                        oFieldNil.SetXPath( osElementXPath + "/" +
                                            szAT_XSI_NIL );
                        oFieldNil.SetType( GMLAS_FT_BOOLEAN, "boolean" );
                        oFieldNil.SetMinOccurs( 0 );
                        oFieldNil.SetMaxOccurs( 1 );
                        oClass.AddField(oFieldNil);
                    }
                }
            }

            // Complex type (element with attributes, composed element, etc...)
            else if( poTypeDef->getTypeCategory() == XSTypeDefinition::COMPLEX_TYPE )
            {
                XSComplexTypeDefinition* poEltCT =
                        reinterpret_cast<XSComplexTypeDefinition*>(poTypeDef);
                std::vector< GMLASField > aoFields;
                bool bNothingMoreToDo = false;
                std::vector<GMLASFeatureClass> aoNestedClasses;

                const int nMinOccursEltParticle = poEltCT->getParticle() ?
                  static_cast<int>(poEltCT->getParticle()->getMinOccurs()) : -1;
                const int nMaxOccursEltParticle = poEltCT->getParticle() ?
                    (poEltCT->getParticle()->getMaxOccursUnbounded() ?
                        MAXOCCURS_UNLIMITED :
                        static_cast<int>(poEltCT->getParticle()->getMaxOccurs()))
                    : -1;

                const bool bEltRepeatedParticle = nMaxOccursEltParticle > 1 ||
                                nMaxOccursEltParticle == MAXOCCURS_UNLIMITED;
                const bool bMoveNestedClassToTop =
                        !bRepeatedParticle && !bEltRepeatedParticle;

                // Process attributes
                XSAttributeUseList* poAttrList =
                                        poEltCT->getAttributeUses();
                const size_t nAttrListSize = (poAttrList != nullptr) ?
                                                    poAttrList->size(): 0;
                for(size_t j=0; j< nAttrListSize; ++j )
                {
                    XSAttributeUse* poAttr = poAttrList->elementAt(j);
                    GMLASField oField;
                    CPLString osNamePrefix( bMoveNestedClassToTop ?
                        osPrefixedEltName : CPLString() );
                    SetFieldFromAttribute(oField, poAttr,
                                          osElementXPath,
                                          osNamePrefix);
                    if( nMinOccurs == 0 || bIsChoice )
                    {
                        oField.SetMinOccurs(0);
                        oField.SetNotNullable(false);
                    }

                    if( IsIgnoredXPath( oField.GetXPath() ) )
                    {
#ifdef DEBUG_VERBOSE
                        CPLDebug("GMLAS", "%s is in ignored xpaths",
                                 oField.GetXPath().c_str());
#endif
                        if( !oField.GetFixedValue().empty() ||
                            !oField.GetDefaultValue().empty() )
                        {
                            oField.SetIgnored();
                        }
                        else
                        {
                            continue;
                        }
                    }

                    aoFields.push_back(oField);
                }

                // Deal with anyAttributes (or any element that also imply it)
                XSWildcard* poAttrWildcard = poEltCT->getAttributeWildcard();
                if( poAttrWildcard != nullptr )
                {
                    GMLASField oField;
                    oField.SetType( GMLASField::GetTypeFromString(szXS_STRING),
                                    szFAKEXS_JSON_DICT );
                    if( !bMoveNestedClassToTop )
                    {
                        oField.SetName( "anyAttributes" );
                    }
                    else
                    {
                        oField.SetName( osPrefixedEltName + "_anyAttributes" );
                    }
                    oField.SetXPath(  osElementXPath + "/" + szAT_ANY_ATTR );
                    oField.SetDocumentation( GetAnnotationDoc(
                                            poAttrWildcard->getAnnotation() ) );

                    aoFields.push_back(oField);
                }

                XSSimpleTypeDefinition* poST = poEltCT->getSimpleType();
                if( poST != nullptr )
                {
                    /* Case of an element, generally with attributes */

                    GMLASField oField;
                    SetFieldTypeAndWidthFromDefinition(poST, oField);
                    if( bRepeatedParticle && nAttrListSize == 0 &&
                        m_bUseArrays &&
                        IsCompatibleOfArray(oField.GetType()) &&
                        oField.GetCategory() !=
                                GMLASField::PATH_TO_CHILD_ELEMENT_WITH_LINK )
                    {
                        /* We have a complex type, but no attributes, and */
                        /* compatible of arrays, so move it to top level! */
                        oField.SetName( osPrefixedEltName );
                        oField.SetArray( true );
                        oField.SetMinOccurs( nMinOccurs );
                        oField.SetMaxOccurs( nMaxOccurs );
                    }
                    else if( bRepeatedParticle )
                    {
                        oField.SetName( "value" );
                        oField.SetMinOccurs( 1 );
                        oField.SetMaxOccurs( 1 );
                        oField.SetNotNullable( true );
                    }
                    else
                    {
                        if( nMinOccurs == 0 )
                        {
                            for(size_t j=0; j<aoFields.size();j++)
                            {
                                aoFields[j].SetMinOccurs( 0 );
                                aoFields[j].SetNotNullable( false );
                            }
                        }

                        oField.SetName( osPrefixedEltName );
                        oField.SetMinOccurs( (bIsChoice) ? 0 : nMinOccurs );
                        oField.SetMaxOccurs( nMaxOccurs );

                        // If the element has minOccurs=0 and is nillable, then we
                        // need an extra field to be able to distinguish between the
                        // case of the missing element or the element with
                        // xsi:nil="true"
                        if( nMinOccurs == 0 && poElt->getNillable() && !m_bUseNullState )
                        {
                            GMLASField oFieldNil;
                            oFieldNil.SetName( osPrefixedEltName + "_" + szNIL );
                            oFieldNil.SetXPath( osElementXPath + "/" +
                                                szAT_XSI_NIL );
                            oFieldNil.SetType( GMLAS_FT_BOOLEAN, "boolean" );
                            oFieldNil.SetMinOccurs( 0 );
                            oFieldNil.SetMaxOccurs( 1 );
                            aoFields.push_back(oFieldNil);
                        }

                    }
                    oField.SetXPath( osElementXPath );
                    oField.SetDocumentation( GetAnnotationDoc( poElt ) );

                    aoFields.push_back(oField);
                    if( oField.IsArray() )
                    {
                        oClass.AddField( oField );
                        bNothingMoreToDo = true;
                    }
                }
                else if( IsAnyType(poEltCT) )
                {
                    GMLASField oField;
                    oField.SetType( GMLAS_FT_ANYTYPE, szXS_ANY_TYPE );
                    if( bRepeatedParticle )
                    {
                        oField.SetName( "value" );
                        oField.SetMinOccurs( 1 );
                        oField.SetMaxOccurs( 1 );
                        oField.SetNotNullable( true );
                    }
                    else
                    {
                        if( nMinOccurs == 0 )
                        {
                            for(size_t j=0; j<aoFields.size();j++)
                            {
                                aoFields[j].SetMinOccurs( 0 );
                                aoFields[j].SetNotNullable( false );
                            }
                        }

                        oField.SetName( osPrefixedEltName );
                        oField.SetMinOccurs( nMinOccurs );
                        oField.SetMaxOccurs( nMaxOccurs );
                    }
                    oField.SetXPath( osElementXPath );
                    oField.SetDocumentation( GetAnnotationDoc( poElt ) );

                    aoFields.push_back(oField);
                }

                // Is it an element that we already visited ? (cycle)
                else if( poEltCT->getParticle() != nullptr &&
                         oSetVisitedModelGroups.find(
                            poEltCT->getParticle()->getModelGroupTerm()) !=
                                                oSetVisitedModelGroups.end() )
                {
                    CreateNonNestedRelationship(poElt,
                                                apoImplEltList,
                                                oClass,
                                                bMoveNestedClassToTop ? 1 :
                                                        MAXOCCURS_UNLIMITED,
                                                bEltNameWillNeedPrefix,
                                                true, // force junction table
                                                false // regular case
                                               );

                    bNothingMoreToDo = true;
                }

                else
                {
                    GMLASFeatureClass oNestedClass;
                    oNestedClass.SetName( oClass.GetName() + "_" +
                                          osPrefixedEltName );
                    oNestedClass.SetXPath( osElementXPath );
                    oNestedClass.SetDocumentation( GetAnnotationDoc( poElt ) );

                    // NULL can happen, for example for gml:ReferenceType
                    // that is an empty sequence with just attributes
                    if( poEltCT->getParticle() != nullptr )
                    {
#ifdef DEBUG_VERBOSE
                        CPLDebug("GMLAS", "Exploring %s",
                                 osElementXPath.c_str());
#endif
                        std::set<XSModelGroup*>
                            oSetNewVisitedModelGroups(oSetVisitedModelGroups);

                        std::map< CPLString, int > oMapCountOccurrencesOfSameNameSub;
                        BuildMapCountOccurrencesOfSameName(
                            poEltCT->getParticle()->getModelGroupTerm(),
                            oMapCountOccurrencesOfSameNameSub);

                        if( !ExploreModelGroup(
                                           poEltCT->getParticle()->
                                                            getModelGroupTerm(),
                                           nullptr,
                                           oNestedClass,
                                           nRecursionCounter + 1,
                                           oSetNewVisitedModelGroups,
                                           poModel,
                                           oMapCountOccurrencesOfSameNameSub) )
                        {
                            return false;
                        }
                    }

                    // If we have a element of type gml:ReferenceType that has
                    // a targetElement in its annotation.appinfo, then create
                    // a dedicated field to have cross-layer relationships.
                    if( IsGMLNamespace(transcode(poTypeDef->getNamespace())) &&
                        transcode(poTypeDef->getName()) == "ReferenceType" &&
                        !osTargetElement.empty() )
                    {
                        XSElementDeclaration* poTargetElt =
                            GetTopElementDeclarationFromXPath(osTargetElement,
                                                              poModel);
                        // TODO: even for non abstract we should probably
                        // handle substitutions
                        if( poTargetElt != nullptr && !poTargetElt->getAbstract() )
                        {
                            bool bHasRequiredId = false;
                            XSComplexTypeDefinition* poTargetEltCT =
                                IsEltCompatibleOfFC(poTargetElt);
                            if( poTargetEltCT )
                            {
                                XSAttributeUseList* poTargetEltAttrList =
                                        poTargetEltCT->getAttributeUses();
                                const size_t nTEAttrListSize = (poTargetEltAttrList != nullptr) ?
                                                                poTargetEltAttrList->size(): 0;
                                for(size_t j=0; j< nTEAttrListSize; ++j )
                                {
                                    XSAttributeUse* poTEAttr = poTargetEltAttrList->elementAt(j);
                                    XSAttributeDeclaration* poTEAttrDecl = poTEAttr->getAttrDeclaration();
                                    XSSimpleTypeDefinition* poTEAttrType = poTEAttrDecl->getTypeDefinition();
                                    if( transcode(poTEAttrType->getName()) == szXS_ID &&
                                        poTEAttr->getRequired() )
                                    {
                                        bHasRequiredId = true;
                                        break;
                                    }
                                }
                            }
                            if( bHasRequiredId && !m_bAlwaysGenerateOGRId )
                            {
                                // If the element is nillable, then we
                                // need an extra field to be able to distinguish between the
                                // case of the missing element or the element with
                                // xsi:nil="true"
                                if( poElt->getNillable() && !m_bUseNullState )
                                {
                                    GMLASField oFieldNil;
                                    oFieldNil.SetName( osPrefixedEltName + "_" + szNIL );
                                    oFieldNil.SetXPath( osElementXPath + "/" +
                                                        szAT_XSI_NIL );
                                    oFieldNil.SetType( GMLAS_FT_BOOLEAN, "boolean" );
                                    oFieldNil.SetMinOccurs( 0 );
                                    oFieldNil.SetMaxOccurs( 1 );
                                    aoFields.push_back(oFieldNil);
                                }

                                GMLASField oField;
                                // Fake xpath
                                oField.SetXPath(
                                    GMLASField::MakePKIDFieldXPathFromXLinkHrefXPath(
                                                osElementXPath + "/" + szAT_XLINK_HREF));
                                oField.SetName( osPrefixedEltName + szPKID_SUFFIX );
                                oField.SetMinOccurs(0);
                                oField.SetMaxOccurs(1);
                                oField.SetType( GMLAS_FT_STRING, szXS_STRING );
                                oField.SetCategory(
                                    GMLASField::PATH_TO_CHILD_ELEMENT_WITH_LINK );
                                oField.SetRelatedClassXPath(osTargetElement);
                                aoFields.push_back( oField );
                            }
                        }
                        else if( poTargetElt != nullptr && poTargetElt->getAbstract() )
                        {
                            // If the element is nillable, then we
                            // need an extra field to be able to distinguish between the
                            // case of the missing element or the element with
                            // xsi:nil="true"
                            if( poElt->getNillable() && !m_bUseNullState )
                            {
                                GMLASField oFieldNil;
                                oFieldNil.SetName( osPrefixedEltName + "_" + szNIL );
                                oFieldNil.SetXPath( osElementXPath + "/" +
                                                    szAT_XSI_NIL );
                                oFieldNil.SetType( GMLAS_FT_BOOLEAN, "boolean" );
                                oFieldNil.SetMinOccurs( 0 );
                                oFieldNil.SetMaxOccurs( 1 );
                                aoFields.push_back(oFieldNil);
                            }

                            // e.g importing http://inspire.ec.europa.eu/schemas/ad/4.0
                            // references bu-base:AbstractConstruction, but sometimes
                            // there are no realization available for it, so no
                            // need to be verbose about that.
                            std::vector<XSElementDeclaration*>
                                                        apoImplTargetEltList;
                            GetConcreteImplementationTypes(poTargetElt,
                                                        apoImplTargetEltList);
                            if( !apoImplTargetEltList.empty() )
                            {
                                CPLDebug("GMLAS",
                                         "Not handled: targetElement %s of %s "
                                         "is abstract but has substitutions",
                                         osTargetElement.c_str(),
                                         osElementXPath.c_str());
                            }
                        }
                        else
                        {
                            // This shouldn't happen with consistent schemas
                            // but as targetElement is in <annotation>, no
                            // general-purpose XSD validator can ensure this
                            CPLDebug("GMLAS", "%s is a targetElement of %s, "
                                     "but cannot be found",
                                     osTargetElement.c_str(),
                                     osElementXPath.c_str());
                        }
                    }

                    // Can we move the nested class(es) one level up ?
                    if( bMoveNestedClassToTop )
                    {
                        // Case of an element like
                        //   <xs:element name="foo">
                        //      <xs:complexType>
                        //          <xs:sequence>

                        const std::vector<GMLASField>& osNestedClassFields =
                                                    oNestedClass.GetFields();
                        for(size_t j = 0; j < osNestedClassFields.size(); j++ )
                        {
                            GMLASField oField(osNestedClassFields[j]);
                            oField.SetName( 
                                osPrefixedEltName + "_" + oField.GetName() );
                            if( nMinOccurs == 0 ||
                                (poEltCT->getParticle() != nullptr &&
                                 poEltCT->getParticle()->getMinOccurs() == 0) )
                            {
                                oField.SetMinOccurs(0);
                                oField.SetNotNullable(false);
                            }
                            aoFields.push_back( oField );
                        }

                        aoNestedClasses = oNestedClass.GetNestedClasses();
                    }
                    else
                    {
                        // Case of an element like
                        //   <xs:element name="foo">
                        //      <xs:complexType>
                        //          <xs:sequence maxOccurs="unbounded">
                        // or
                        //   <xs:element name="foo" maxOccurs="unbounded">
                        //      <xs:complexType>
                        //          <xs:sequence>
                        // or even
                        //   <xs:element name="foo" maxOccurs="unbounded">
                        //      <xs:complexType>
                        //          <xs:sequence maxOccurs="unbounded">
                        if( m_bUseArrays && nAttrListSize == 0 &&
                            oNestedClass.GetNestedClasses().empty() &&
                            oNestedClass.GetFields().size() == 1 &&
                            IsCompatibleOfArray(
                                    oNestedClass.GetFields()[0].GetType()) &&
                            oNestedClass.GetFields()[0].GetCategory() !=
                                GMLASField::PATH_TO_CHILD_ELEMENT_WITH_LINK )
                        {
                            // In the case the sequence has a single element,
                            // compatible of array type, and no attribute and
                            // no nested classes, then add an array attribute
                            // at the top-level
                            GMLASField oField (oNestedClass.GetFields()[0] );
                            oField.SetName( osPrefixedEltName + "_" +
                                            oField.GetName() );
                            if( oField.GetMaxOccurs() == 1 &&
                                bEltRepeatedParticle &&
                                poEltCT->getParticle() != nullptr )
                            {
                                oField.SetMaxOccurs( nMaxOccursEltParticle );
                            }
                            oField.SetArray( true );
                            oClass.AddField( oField );
                        }
                        else
                        {
                            if( !aoFields.empty() && bEltRepeatedParticle)
                            {
                                // We have attributes and the sequence is
                                // repeated
                                //   <xs:element name="foo" maxOccurs="unbounded">
                                //      <xs:complexType>
                                //          <xs:sequence maxOccurs="unbounded">
                                //              ...
                                //          </xs:sequence>
                                //          <xs:attribute .../>
                                //      </xs:complexType>
                                //   </xs:element>
                                // So we need to create an
                                // intermediate class to store them
                                GMLASFeatureClass oIntermediateNestedClass;
                                oIntermediateNestedClass.SetName(
                                        oClass.GetName() + "_" +
                                        osPrefixedEltName );
                                oIntermediateNestedClass.SetXPath( osElementXPath );

                                oIntermediateNestedClass.PrependFields( aoFields );

                                oNestedClass.SetName( oClass.GetName() + "_" +
                                        osPrefixedEltName + "_sequence" );
                                oNestedClass.SetXPath( oNestedClass.GetXPath() +
                                        szEXTRA_SUFFIX + "sequence");
                                oNestedClass.SetIsRepeatedSequence( true );

                                GMLASField oField;
                                oField.SetXPath( osElementXPath );
                                oField.SetCategory(
                                    GMLASField::PATH_TO_CHILD_ELEMENT_NO_LINK);
                                if( nMaxOccursEltParticle != 1 )
                                    oField.SetRepetitionOnSequence( true );
                                oField.SetMinOccurs( nMinOccursEltParticle );
                                oField.SetMaxOccurs( nMaxOccursEltParticle );
                                oField.SetRelatedClassXPath( oNestedClass.GetXPath() );
                                oIntermediateNestedClass.AddField(oField);

                                oIntermediateNestedClass.AddNestedClass( oNestedClass );

                                oClass.AddNestedClass( oIntermediateNestedClass );
                            }
                            else
                            {
                                oNestedClass.SetIsRepeatedSequence(
                                                        bEltRepeatedParticle );
                                oNestedClass.PrependFields( aoFields );

                                oClass.AddNestedClass( oNestedClass );
                            }

                            GMLASField oField;
                            oField.SetName( osPrefixedEltName );
                            oField.SetXPath( osElementXPath );
                            if( bRepeatedParticle )
                            {
                                if( poEltCT->getParticle() != nullptr )
                                {
                                    oField.SetMinOccurs( ComposeMinOccurs(
                                        nMinOccurs, nMinOccursEltParticle) );
                                    oField.SetMaxOccurs( ComposeMaxOccurs(
                                        nMaxOccurs, nMaxOccursEltParticle) );
                                }
                                else
                                {
                                    oField.SetMinOccurs( nMinOccurs );
                                    oField.SetMaxOccurs( nMaxOccurs );
                                }
                            }
                            else if( poEltCT->getParticle() != nullptr )
                            {
                                if( nMaxOccursEltParticle != 1 )
                                    oField.SetRepetitionOnSequence( true );
                                oField.SetMinOccurs( nMinOccursEltParticle );
                                oField.SetMaxOccurs( nMaxOccursEltParticle );
                            }
                            oField.SetCategory(
                                    GMLASField::PATH_TO_CHILD_ELEMENT_NO_LINK);
                            oField.SetRelatedClassXPath( oField.GetXPath() );
                            oClass.AddField(oField);
                        }

                        bNothingMoreToDo = true;
                    }
                }

                if( bNothingMoreToDo )
                {
                    // Nothing to do
                }
                else if( bRepeatedParticle )
                {
                    GMLASFeatureClass oNestedClass;
                    oNestedClass.SetName( oClass.GetName() + "_" +
                                          osPrefixedEltName );
                    oNestedClass.SetXPath( osElementXPath );
                    oNestedClass.AppendFields( aoFields );
                    oNestedClass.SetDocumentation( GetAnnotationDoc( poElt ) );
                    oClass.AddNestedClass( oNestedClass );

                    GMLASField oField;
                    oField.SetName( osPrefixedEltName );
                    oField.SetXPath( osElementXPath );
                    oField.SetMinOccurs( (bIsChoice) ? 0 : nMinOccurs );
                    oField.SetMaxOccurs( nMaxOccurs );
                    oField.SetCategory(
                                    GMLASField::PATH_TO_CHILD_ELEMENT_NO_LINK);
                    oField.SetRelatedClassXPath( oField.GetXPath() );
                    oClass.AddField(oField);
                }
                else
                {
                    oClass.AppendFields( aoFields );
                    for(size_t j = 0; j < aoNestedClasses.size(); j++ )
                    {
                        oClass.AddNestedClass( aoNestedClasses[j] );
                    }
                }
            }
        }
        else if( poParticle->getTermType() == XSParticle::TERM_MODELGROUP )
        {
            XSModelGroup* psSubModelGroup = poParticle->getModelGroupTerm();
            if( bRepeatedParticle )
            {
                GMLASFeatureClass oNestedClass;
                CPLString osGroupName;
                XSModelGroupDefinition* psGroupDefinition =
                                            GetGroupDefinition(psSubModelGroup);
                if( psGroupDefinition != nullptr )
                {
                    osGroupName = transcode( psGroupDefinition->getName() );
                    oNestedClass.SetDocumentation(
                        GetAnnotationDoc( psGroupDefinition->getAnnotation() ) );
                }
                else
                {
                    // Is it a <xs:choice maxOccurs=">1|unbounded"
                    if (psSubModelGroup->getCompositor() ==
                                            XSModelGroup::COMPOSITOR_CHOICE)
                    {
                        std::set<XSModelGroup*>
                            oSetNewVisitedModelGroups(oSetVisitedModelGroups);
                        GMLASFeatureClass oTmpClass;
                        oTmpClass.SetName( oClass.GetName() );
                        oTmpClass.SetXPath( oClass.GetXPath() );
                        if( !ExploreModelGroup( psSubModelGroup,
                                                nullptr,
                                                oTmpClass,
                                                nRecursionCounter + 1,
                                                oSetNewVisitedModelGroups,
                                                poModel,
                                                oMapCountOccurrencesOfSameName ) )
                        {
                            return false;
                        }
                        bool bHasArray = false;
                        std::vector<GMLASField>& oTmpFields =
                                                        oTmpClass.GetFields();
                        for( size_t j = 0; j < oTmpFields.size(); ++j )
                        {
                            if( oTmpFields[j].IsArray() )
                            {
                                bHasArray = true;
                                break;
                            }
                        }
                        if( !bHasArray )
                        {
                            for( size_t j = 0; j < oTmpFields.size(); ++j )
                            {
                                oTmpFields[j].SetMayAppearOutOfOrder( true );
                                oClass.AddField( oTmpFields[j] );
                            }
                            return true;
                        }
                    }

                    nGroup ++;
                    osGroupName = CPLSPrintf("_group%d", nGroup);
                }
                oNestedClass.SetName( oClass.GetName() + "_" + osGroupName);
                oNestedClass.SetIsGroup(true);
                oNestedClass.SetIsRepeatedSequence(true);
                // Caution: we will change it afterwards !
                oNestedClass.SetXPath( oClass.GetXPath() );
                std::set<XSModelGroup*>
                    oSetNewVisitedModelGroups(oSetVisitedModelGroups);
                if( !ExploreModelGroup( psSubModelGroup,
                                        nullptr,
                                        oNestedClass,
                                        nRecursionCounter + 1,
                                        oSetNewVisitedModelGroups,
                                        poModel,
                                        oMapCountOccurrencesOfSameName) )
                {
                    return false;
                }
                // This is a nasty hack. We set a unique fake xpath *AFTER*
                // processing the group, so that we can add a fake GROUP field
                // pointing to the nested class
                oNestedClass.SetXPath( oClass.GetXPath() + szEXTRA_SUFFIX + osGroupName );

                if( m_bUseArrays &&
                    oNestedClass.GetFields().size() == 1 &&
                    IsCompatibleOfArray(oNestedClass.GetFields()[0].GetType()) )
                {
                    GMLASField oField(oNestedClass.GetFields()[0]);
                    oField.SetMinOccurs( ComposeMinOccurs(oField.GetMinOccurs(), nMinOccurs) );
                    oField.SetMaxOccurs( ComposeMaxOccurs(oField.GetMaxOccurs(), nMaxOccurs) );
                    oField.SetArray( true );
                    oClass.AddField( oField );
                }
                else
                {
                    oClass.AddNestedClass( oNestedClass );

                    GMLASField oField;
                    oField.SetCategory( GMLASField::GROUP );
                    oField.SetMinOccurs( nMinOccurs );
                    oField.SetMaxOccurs( nMaxOccurs );
                    oField.SetRelatedClassXPath( oNestedClass.GetXPath() );
                    oClass.AddField(oField);
                }
            }
            else
            {
                std::set<XSModelGroup*>
                    oSetNewVisitedModelGroups(oSetVisitedModelGroups);
                if( !ExploreModelGroup( psSubModelGroup,
                                        nullptr,
                                        oClass,
                                        nRecursionCounter + 1,
                                        oSetNewVisitedModelGroups,
                                        poModel,
                                        oMapCountOccurrencesOfSameName ) )
                {
                    return false;
                }
            }
        }
        else if( poParticle->getTermType() == XSParticle::TERM_WILDCARD )
        {
            /* Special case for a layer that matches everything, as found */
            /* in swe:extension */
            XSWildcard* poWildcard = poParticle->getWildcardTerm();
            GMLASField oField;
            oField.SetXPath( oClass.GetXPath() + szMATCH_ALL );
            oField.SetName( "value" );
            oField.SetType( GMLAS_FT_ANYTYPE, szXS_ANY_TYPE );
            oField.SetIncludeThisEltInBlob( true );
            oField.SetMinOccurs( nMinOccurs );
            oField.SetMaxOccurs( 1 );
            oField.SetDocumentation(
                        GetAnnotationDoc( poWildcard->getAnnotation() ) );
            oClass.AddField(oField);
        }
    }

    return true;
}
