/******************************************************************************
 * 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>
 *
 * SPDX-License-Identifier: MIT
 ****************************************************************************/

#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"

#include <list>

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;
        std::string osVersion;
        for (unsigned int i = 0; i < attrs.getLength(); i++)
        {
            const std::string 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 = std::move(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, const std::shared_ptr<VSIVirtualHandle> &fpXSD,
    std::map<CPLString, CPLString> &oMapURIToPrefix,
    const std::map<CPLString, CPLString> &oMapDocNSURIToPrefix,
    CPLString &osGMLVersionFound)
{
    GMLASInputSource oSource(pszXSDFilename, fpXSD);
    // 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->setFeature(XMLUni::fgXercesDisableDefaultEntityResolution, true);

    std::string osErrorMsg;
    try
    {
        poReader->parse(oSource);
    }
    catch (const SAXException &e)
    {
        osErrorMsg += transcode(e.getMessage());
    }
    catch (const XMLException &e)
    {
        osErrorMsg += transcode(e.getMessage());
    }
    catch (const OutOfMemoryException &e)
    {
        if (strstr(CPLGetLastErrorMsg(), "configuration option") == nullptr)
        {
            osErrorMsg += transcode(e.getMessage());
        }
    }
    catch (const DOMException &e)
    {
        osErrorMsg += transcode(e.getMessage());
    }
    if (!osErrorMsg.empty())
    {
        CPLError(CE_Failure, CPLE_AppDefined, "%s", osErrorMsg.c_str());
    }
    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,
        const std::shared_ptr<VSIVirtualHandle> &fp) override;
};

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

void GMLASAnalyzerEntityResolver::DoExtraSchemaProcessing(
    const CPLString &osFilename, const std::shared_ptr<VSIVirtualHandle> &fp)
{
    CollectNamespacePrefixes(osFilename, fp, m_oMapURIToPrefix,
                             m_oMapDocNSURIToPrefix, m_osGMLVersionFound);
    fp->Seek(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(static_cast<unsigned char>(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
bool 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.

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

    std::set<CPLString> oSetDuplicates;
    for (const auto &oIter : oMapNameToFieldIndex)
    {
        // Has it duplicates ?
        const size_t nOccurrences = oIter.second.size();
        if (nOccurrences > 1)
        {
            oSetDuplicates.insert(oIter.first);
        }
    }

    while (!oSetDuplicates.empty())
    {
        // Iterate over the unique names
        auto oIterSet = oSetDuplicates.begin();
        while (oIterSet != oSetDuplicates.end())
        {
            auto oIterSetNext = oIterSet;
            ++oIterSetNext;

            auto oIterMap = oMapNameToFieldIndex.find(*oIterSet);
            CPLAssert(oIterMap != oMapNameToFieldIndex.end());
            auto &list = oIterMap->second;

            const CPLString oClassNS =
                GetNSOfLastXPathComponent(oClass.GetXPath());
            bool bHasDoneRenamingForThatCase = false;

            auto oIterList = list.begin();

            // Update oMapNameToFieldIndex and oSetDuplicates with the
            // new field name, and removing the old one.
            const auto updateSetAndMapWithNewName =
                [&oIterList, &list, &oMapNameToFieldIndex,
                 &oSetDuplicates](int nFieldIdx, const std::string &osNewName)
            {
                list.erase(oIterList);
                auto &newList = oMapNameToFieldIndex[osNewName];
                newList.push_back(nFieldIdx);
                if (newList.size() > 1)
                    oSetDuplicates.insert(osNewName);
            };

            while (oIterList != list.end())
            {
                auto oIterListNext = oIterList;
                ++oIterListNext;

                const int nFieldIdx = *oIterList;
                GMLASField &oField = aoFields[nFieldIdx];
                // 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()))
                {
                    bHasDoneRenamingForThatCase = true;
                    const auto osNewName = oNS + "_" + oField.GetName();
                    oField.SetName(osNewName);
                    updateSetAndMapWithNewName(nFieldIdx, osNewName);
                    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)
                {
                    bHasDoneRenamingForThatCase = true;
                    const auto osNewName = oField.GetName() + "_attr";
                    oField.SetName(osNewName);
                    updateSetAndMapWithNewName(nFieldIdx, osNewName);
                    break;
                }

                oIterList = oIterListNext;
            }

            // If none of the above renaming strategies have worked, then
            // append a counter to the duplicates.
            if (!bHasDoneRenamingForThatCase)
            {
                int i = 0;
                oIterList = list.begin();
                while (oIterList != list.end())
                {
                    auto oIterListNext = oIterList;
                    ++oIterListNext;

                    const int nFieldIdx = *oIterList;
                    GMLASField &oField = aoFields[nFieldIdx];
                    if (i > 0)
                    {
                        const auto osNewName =
                            oField.GetName() +
                            CPLSPrintf("%d", static_cast<int>(i) + 1);
                        oField.SetName(osNewName);
                        updateSetAndMapWithNewName(nFieldIdx, osNewName);
                    }

                    ++i;
                    oIterList = oIterListNext;
                }
            }

            // Update oSetDuplicates and oMapNameToFieldIndex if we have
            // no longer duplicates for the current name
            if (list.size() <= 1)
            {
                if (list.empty())
                {
                    oMapNameToFieldIndex.erase(oIterMap);
                }
                oSetDuplicates.erase(oIterSet);
            }

            oIterSet = oIterSetNext;
        }
    }

#ifdef DEBUG
    {
        // Check that the above algorithm managed to deduplicate names
        std::set<CPLString> oSetNames;
        for (const auto &oField : aoFields)
        {
            if (oField.GetCategory() == GMLASField::REGULAR)
            {
                const auto &osName = oField.GetName();
                CPLAssert(oSetNames.find(osName) == oSetNames.end());
                oSetNames.insert(osName);
            }
        }
    }
#endif

    // 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());
            /* Somewhat arbitrary limitation to avoid performance issues in */
            /* OGRGMLASTruncateIdentifier() */
            if (nNameSize > 1024)
            {
                CPLError(CE_Failure, CPLE_NotSupported,
                         "Field name with excessive length (%d) found",
                         nNameSize);
                return false;
            }
            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", false);
            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++)
    {
        if (!LaunderFieldNames(aoNestedClasses[i]))
            return false;
    }
    return true;
}

/************************************************************************/
/*                       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", false);
            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 *pszColon = strrchr(pszTypename, ':');
    XSElementDeclaration *poEltDecl = nullptr;
    if (pszColon != nullptr)
    {
        CPLString osNSPrefix = pszTypename;
        osNSPrefix.resize(pszColon - pszTypename);
        CPLString osName = pszColon + 1;
        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 = nullptr;
        XMLCh *xmlName = nullptr;
        try
        {
            xmlNS = XMLString::transcode(osNSURI);
            xmlName = XMLString::transcode(osName);
            poEltDecl = poModel->getElementDeclaration(xmlName, xmlNS);
        }
        catch (const TranscodingException &e)
        {
            CPLError(CE_Failure, CPLE_AppDefined, "TranscodingException: %s",
                     transcode(e.getMessage()).c_str());
        }
        XMLString::release(&xmlNS);
        XMLString::release(&xmlName);
    }
    else
    {
        try
        {
            XMLCh *xmlName = XMLString::transcode(pszTypename);
            poEltDecl = poModel->getElementDeclaration(xmlName, nullptr);
            XMLString::release(&xmlName);
        }
        catch (const TranscodingException &e)
        {
            CPLError(CE_Failure, CPLE_AppDefined, "TranscodingException: %s",
                     transcode(e.getMessage()).c_str());
        }
    }
    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);

        // coverity[unsafe_xml_parse_config]
        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);

        poParser->setFeature(XMLUni::fgXercesDisableDefaultEntityResolution,
                             true);

        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 = nullptr;
        try
        {
            xmlNamespace = XMLString::transcode(osNSURI.c_str());
        }
        catch (const TranscodingException &e)
        {
            CPLError(CE_Failure, CPLE_AppDefined, "TranscodingException: %s",
                     transcode(e.getMessage()).c_str());
            return false;
        }

        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 = nullptr;
        try
        {
            xmlNamespace = XMLString::transcode(aoNamespaces[iNS].c_str());
        }
        catch (const TranscodingException &e)
        {
            CPLError(CE_Failure, CPLE_AppDefined, "TranscodingException: %s",
                     transcode(e.getMessage()).c_str());
            return false;
        }

        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 = nullptr;
            try
            {
                xmlNamespace = XMLString::transcode(aoNamespaces[iNS].c_str());
            }
            catch (const TranscodingException &e)
            {
                CPLError(CE_Failure, CPLE_AppDefined,
                         "TranscodingException: %s",
                         transcode(e.getMessage()).c_str());
                return false;
            }

            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)
                            {
                                CPL_IGNORE_RET_VAL(
                                    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;
    }
    const 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 ?
        }

        if (!LaunderFieldNames(oClass))
            return false;

        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()                        */
/************************************************************************/

bool GMLASSchemaAnalyzer::SetFieldFromAttribute(GMLASField &oField,
                                                XSAttributeUse *poAttr,
                                                const CPLString &osXPathPrefix,
                                                const CPLString &osNamePrefix)
{
    const XSAttributeDeclaration *poAttrDecl = poAttr->getAttrDeclaration();
    const CPLString osNS(transcode(poAttrDecl->getNamespace()));
    const CPLString osName(transcode(poAttrDecl->getName()));

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

    XSSimpleTypeDefinition *poAttrType = poAttrDecl->getTypeDefinition();
    if (!poAttrType)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Cannot get type definition for attribute %s",
                 oField.GetName().c_str());
        return false;
    }

    SetFieldTypeAndWidthFromDefinition(poAttrType, oField);

    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()));

    return true;
}

/************************************************************************/
/*                      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},
                   {"BoundingShapeType", wkbPolygon},
                   {"PolygonPropertyType", wkbPolygon},
                   {"LineStringPropertyType", wkbLineString},
                   {"MultiPointPropertyType", wkbMultiPoint},
                   {"MultiPolygonPropertyType", wkbMultiPolygon},
                   {"MultiLineStringPropertyType", wkbMultiLineString},
                   {"MultiGeometryPropertyType", wkbGeometryCollection},
                   {"MultiCurvePropertyType", wkbMultiCurve},
                   {"MultiSurfacePropertyType", wkbMultiSurface},
                   {"MultiSolidPropertyType", wkbUnknown},
                   // GeometryArrayPropertyType ?
                   {"GeometricPrimitivePropertyType", wkbUnknown},
                   {"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 = std::move(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)
                        {
                            int nSubCountSubElt = 0;

                            // 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;
                                if (!SetFieldFromAttribute(oField, poAttr,
                                                           osFullXPath))
                                {
                                    return false;
                                }
                                if (!IsIgnoredXPath(oField.GetXPath()) &&
                                    oField.GetFixedValue().empty())
                                {
#ifdef DEBUG_SUPER_VERBOSE
                                    CPLDebug(
                                        "GMLAS",
                                        "FindElementsWithMustBeToLevel: %s",
                                        oField.GetXPath().c_str());
#endif
                                    nSubCountSubElt++;
                                }
                            }

                            bool bSubSimpleEnoughOut = true;
                            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;
                            if (!SetFieldFromAttribute(oField, poAttr,
                                                       osFullXPath))
                            {
                                return false;
                            }
                            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);
            if (!SetFieldFromAttribute(oField, poAttr, oClass.GetXPath()))
            {
                return 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;
                }
            }

            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;                    // to make Visual Studio happy
            CPL_IGNORE_RET_VAL(eGeomType);  // to make cppcheck happy

            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());
                    if (!SetFieldFromAttribute(oField, poAttr, osElementXPath,
                                               osNamePrefix))
                    {
                        return false;
                    }
                    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;
}
