/******************************************************************************
 *
 * Project:  JML Translator
 * Purpose:  Implements OGRJMLLayer class.
 *
 ******************************************************************************
 * Copyright (c) 2014, Even Rouault <even dot rouault at spatialys dot com>
 *
 * SPDX-License-Identifier: MIT
 ****************************************************************************/

#include "cpl_conv.h"
#include "ogr_jml.h"
#include "ogr_p.h"

#ifdef HAVE_EXPAT

constexpr int PARSER_BUF_SIZE = 8192;

/************************************************************************/
/*                            OGRJMLLayer()                             */
/************************************************************************/

OGRJMLLayer::OGRJMLLayer(const char *pszLayerName, OGRJMLDataset *poDSIn,
                         VSILFILE *fpIn)
    : m_poDS(poDSIn), poFeatureDefn(new OGRFeatureDefn(pszLayerName)),
      nNextFID(0), fp(fpIn), bHasReadSchema(false), currentDepth(0),
      bStopParsing(false), nWithoutEventCounter(0), nDataHandlerCounter(0),
      bAccumulateElementValue(false),
      pszElementValue(static_cast<char *>(CPLCalloc(1024, 1))),
      nElementValueLen(0), nElementValueAlloc(1024), poFeature(nullptr),
      ppoFeatureTab(nullptr), nFeatureTabLength(0), nFeatureTabIndex(0),
      bSchemaFinished(false), nJCSGMLInputTemplateDepth(0),
      nCollectionElementDepth(0), nFeatureCollectionDepth(0),
      nFeatureElementDepth(0), nGeometryElementDepth(0), nColumnDepth(0),
      nNameDepth(0), nTypeDepth(0), nAttributeElementDepth(0), iAttr(-1),
      iRGBField(-1)
{
    SetDescription(poFeatureDefn->GetName());
    poFeatureDefn->Reference();
}

/************************************************************************/
/*                            ~OGRJMLLayer()                            */
/************************************************************************/

OGRJMLLayer::~OGRJMLLayer()

{
    poFeatureDefn->Release();

    CPLFree(pszElementValue);

    for (int i = nFeatureTabIndex; i < nFeatureTabLength; i++)
        delete ppoFeatureTab[i];
    CPLFree(ppoFeatureTab);

    if (poFeature)
        delete poFeature;
}

/************************************************************************/
/*                            GetLayerDefn()                            */
/************************************************************************/

const OGRFeatureDefn *OGRJMLLayer::GetLayerDefn() const
{
    if (!bHasReadSchema)
        const_cast<OGRJMLLayer *>(this)->LoadSchema();

    return poFeatureDefn;
}

static void XMLCALL startElementCbk(void *pUserData, const char *pszName,
                                    const char **ppszAttr)
{
    static_cast<OGRJMLLayer *>(pUserData)->startElementCbk(pszName, ppszAttr);
}

static void XMLCALL endElementCbk(void *pUserData, const char *pszName)
{
    static_cast<OGRJMLLayer *>(pUserData)->endElementCbk(pszName);
}

static void XMLCALL dataHandlerCbk(void *pUserData, const char *data, int nLen)
{
    static_cast<OGRJMLLayer *>(pUserData)->dataHandlerCbk(data, nLen);
}

/************************************************************************/
/*                            ResetReading()                            */
/************************************************************************/

void OGRJMLLayer::ResetReading()

{
    nNextFID = 0;

    VSIFSeekL(fp, 0, SEEK_SET);
    VSIFClearErrL(fp);

    poParser.reset(OGRCreateExpatXMLParser());
    XML_SetElementHandler(poParser.get(), ::startElementCbk, ::endElementCbk);
    XML_SetCharacterDataHandler(poParser.get(), ::dataHandlerCbk);
    XML_SetUserData(poParser.get(), this);

    for (int i = nFeatureTabIndex; i < nFeatureTabLength; i++)
        delete ppoFeatureTab[i];
    nFeatureTabIndex = 0;
    nFeatureTabLength = 0;
    delete poFeature;
    poFeature = nullptr;

    currentDepth = 0;

    nCollectionElementDepth = 0;
    nFeatureElementDepth = 0;
    nGeometryElementDepth = 0;
    nAttributeElementDepth = 0;
    iAttr = -1;

    bAccumulateElementValue = false;
    nElementValueLen = 0;
    pszElementValue[0] = '\0';
}

/************************************************************************/
/*                          startElementCbk()                           */
/************************************************************************/

void OGRJMLLayer::startElementCbk(const char *pszName, const char **ppszAttr)
{
    if (bStopParsing)
        return;

    nWithoutEventCounter = 0;

    if (nFeatureElementDepth > 0 && nAttributeElementDepth == 0 &&
        nGeometryElementDepth == 0 && osGeometryElement.compare(pszName) == 0)
    {
        nGeometryElementDepth = currentDepth;
        bAccumulateElementValue = true;
    }
    else if (nFeatureElementDepth > 0 && nAttributeElementDepth == 0 &&
             nGeometryElementDepth == 0)
    {
        /* We assume that attributes are present in the order they are */
        /* declared, so as a first guess, we can try the aoColumns[iAttr + 1] */
        int i = (iAttr + 1 < poFeatureDefn->GetFieldCount()) ? -1 : 0;
        for (; i < static_cast<int>(aoColumns.size()); i++)
        {
            const OGRJMLColumn &oColumn =
                (i < 0) ? aoColumns[iAttr + 1] : aoColumns[i];
            if (oColumn.osElementName != pszName)
                continue;

            if (oColumn.bIsBody)
            {
                if (!oColumn.osAttributeName.empty() && ppszAttr != nullptr &&
                    ppszAttr[0] != nullptr && ppszAttr[1] != nullptr &&
                    oColumn.osAttributeName.compare(ppszAttr[0]) == 0 &&
                    oColumn.osAttributeValue.compare(ppszAttr[1]) == 0)
                {
                    /* <osElementName
                     * osAttributeName="osAttributeValue">value</osElementName>
                     */

                    bAccumulateElementValue = true;
                    nAttributeElementDepth = currentDepth;
                    iAttr = (i < 0) ? iAttr + 1 : i;
                    break;
                }
                else if (oColumn.osAttributeName.empty())
                {
                    /* <osElementName>value</osElementName> */

                    bAccumulateElementValue = true;
                    nAttributeElementDepth = currentDepth;
                    iAttr = (i < 0) ? iAttr + 1 : i;
                    break;
                }
            }
            else if (!oColumn.osAttributeName.empty() && ppszAttr != nullptr &&
                     ppszAttr[0] != nullptr && ppszAttr[1] != nullptr &&
                     oColumn.osAttributeName.compare(ppszAttr[0]) == 0)
            {
                /* <osElementName osAttributeName="value"></osElementName> */

                AddStringToElementValue(ppszAttr[1], (int)strlen(ppszAttr[1]));

                nAttributeElementDepth = currentDepth;
                iAttr = (i < 0) ? iAttr + 1 : i;
                break;
            }
        }
    }
    else if (nGeometryElementDepth > 0)
    {
        AddStringToElementValue("<", 1);
        AddStringToElementValue(pszName, (int)strlen(pszName));

        const char **papszIter = ppszAttr;
        while (papszIter && *papszIter != nullptr)
        {
            AddStringToElementValue(" ", 1);
            AddStringToElementValue(papszIter[0], (int)strlen(papszIter[0]));
            AddStringToElementValue("=\"", 2);
            AddStringToElementValue(papszIter[1], (int)strlen(papszIter[1]));
            AddStringToElementValue("\"", 1);
            papszIter += 2;
        }

        AddStringToElementValue(">", 1);
    }
    else if (nFeatureCollectionDepth > 0 && nFeatureElementDepth == 0 &&
             osFeatureElement.compare(pszName) == 0)
    {
        nFeatureElementDepth = currentDepth;
        poFeature = new OGRFeature(poFeatureDefn);
    }
    else if (nFeatureCollectionDepth == 0 &&
             osCollectionElement.compare(pszName) == 0)
    {
        nFeatureCollectionDepth = currentDepth;
    }

    currentDepth++;
}

/************************************************************************/
/*                           StopAccumulate()                           */
/************************************************************************/

void OGRJMLLayer::StopAccumulate()
{
    bAccumulateElementValue = false;
    nElementValueLen = 0;
    pszElementValue[0] = '\0';
}

/************************************************************************/
/*                           endElementCbk()                            */
/************************************************************************/

void OGRJMLLayer::endElementCbk(const char *pszName)
{
    if (bStopParsing)
        return;

    nWithoutEventCounter = 0;

    currentDepth--;

    if (nAttributeElementDepth == currentDepth)
    {
        if (nElementValueLen)
            poFeature->SetField(iAttr, pszElementValue);
        else if (iAttr >= 0)
            poFeature->SetFieldNull(iAttr);
        nAttributeElementDepth = 0;
        StopAccumulate();
    }
    else if (nGeometryElementDepth > 0 && currentDepth > nGeometryElementDepth)
    {
        AddStringToElementValue("</", 2);
        AddStringToElementValue(pszName, static_cast<int>(strlen(pszName)));
        AddStringToElementValue(">", 1);
    }
    else if (nGeometryElementDepth == currentDepth)
    {
        if (nElementValueLen)
        {
            OGRGeometry *poGeom =
                OGRGeometry::FromHandle(OGR_G_CreateFromGML(pszElementValue));
            if (poGeom != nullptr &&
                poGeom->getGeometryType() == wkbGeometryCollection &&
                poGeom->IsEmpty())
            {
                delete poGeom;
            }
            else
                poFeature->SetGeometryDirectly(poGeom);
        }

        nGeometryElementDepth = 0;
        StopAccumulate();
    }
    else if (nFeatureElementDepth == currentDepth)
    {
        /* Builds a style string from R_G_B if we don't already have a */
        /* style string */
        OGRGeometry *poGeom = poFeature->GetGeometryRef();
        unsigned int R = 0;
        unsigned int G = 0;
        unsigned int B = 0;
        if (iRGBField >= 0 && poFeature->IsFieldSetAndNotNull(iRGBField) &&
            poFeature->GetStyleString() == nullptr && poGeom != nullptr &&
            sscanf(poFeature->GetFieldAsString(iRGBField), "%02X%02X%02X", &R,
                   &G, &B) == 3)
        {
            const OGRwkbGeometryType eGeomType =
                wkbFlatten(poGeom->getGeometryType());
            if (eGeomType == wkbPoint || eGeomType == wkbMultiPoint ||
                eGeomType == wkbLineString || eGeomType == wkbMultiLineString)
            {
                poFeature->SetStyleString(
                    CPLSPrintf("PEN(c:#%02X%02X%02X)", R, G, B));
            }
            else if (eGeomType == wkbPolygon || eGeomType == wkbMultiPolygon)
            {
                poFeature->SetStyleString(
                    CPLSPrintf("BRUSH(fc:#%02X%02X%02X)", R, G, B));
            }
        }

        poFeature->SetFID(nNextFID++);

        if ((m_poFilterGeom == nullptr || FilterGeometry(poGeom)) &&
            (m_poAttrQuery == nullptr || m_poAttrQuery->Evaluate(poFeature)))
        {
            ppoFeatureTab = static_cast<OGRFeature **>(CPLRealloc(
                ppoFeatureTab, sizeof(OGRFeature *) * (nFeatureTabLength + 1)));
            ppoFeatureTab[nFeatureTabLength] = poFeature;
            nFeatureTabLength++;
        }
        else
        {
            delete poFeature;
        }
        poFeature = nullptr;
        iAttr = -1;

        nFeatureElementDepth = 0;
    }
    else if (nFeatureCollectionDepth == currentDepth)
    {
        nFeatureCollectionDepth = 0;
    }
}

/************************************************************************/
/*                      AddStringToElementValue()                       */
/************************************************************************/

void OGRJMLLayer::AddStringToElementValue(const char *data, int nLen)
{
    if (nLen > INT_MAX - nElementValueLen - 1 - 1000)
    {
        CPLError(CE_Failure, CPLE_OutOfMemory,
                 "Too much data in a single element");
        XML_StopParser(poParser.get(), XML_FALSE);
        bStopParsing = true;
        return;
    }
    if (nElementValueLen + nLen + 1 > nElementValueAlloc)
    {
        char *pszNewElementValue = static_cast<char *>(VSI_REALLOC_VERBOSE(
            pszElementValue, nElementValueLen + nLen + 1 + 1000));
        if (pszNewElementValue == nullptr)
        {
            XML_StopParser(poParser.get(), XML_FALSE);
            bStopParsing = true;
            return;
        }
        nElementValueAlloc = nElementValueLen + nLen + 1 + 1000;
        pszElementValue = pszNewElementValue;
    }
    memcpy(pszElementValue + nElementValueLen, data, nLen);
    nElementValueLen += nLen;
    pszElementValue[nElementValueLen] = '\0';
}

/************************************************************************/
/*                           dataHandlerCbk()                           */
/************************************************************************/

void OGRJMLLayer::dataHandlerCbk(const char *data, int nLen)
{
    if (bStopParsing)
        return;

    nDataHandlerCounter++;
    if (nDataHandlerCounter >= PARSER_BUF_SIZE)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "File probably corrupted (million laugh pattern)");
        XML_StopParser(poParser.get(), XML_FALSE);
        bStopParsing = true;
        return;
    }

    nWithoutEventCounter = 0;

    if (bAccumulateElementValue)
    {
        AddStringToElementValue(data, nLen);
    }
}

/************************************************************************/
/*                           GetNextFeature()                           */
/************************************************************************/

OGRFeature *OGRJMLLayer::GetNextFeature()
{
    if (!bHasReadSchema)
        LoadSchema();

    if (bStopParsing)
        return nullptr;

    if (nFeatureTabIndex < nFeatureTabLength)
    {
        return ppoFeatureTab[nFeatureTabIndex++];
    }

    if (VSIFEofL(fp) || VSIFErrorL(fp))
        return nullptr;

    std::vector<char> aBuf(PARSER_BUF_SIZE);

    this->nFeatureTabLength = 0;
    nFeatureTabIndex = 0;

    nWithoutEventCounter = 0;

    int nDone = 0;
    do
    {
        nDataHandlerCounter = 0;
        unsigned int nLen =
            (unsigned int)VSIFReadL(aBuf.data(), 1, aBuf.size(), fp);
        nDone = (nLen < aBuf.size());
        if (XML_Parse(poParser.get(), aBuf.data(), nLen, nDone) ==
            XML_STATUS_ERROR)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "XML parsing of JML file failed : %s "
                     "at line %d, column %d",
                     XML_ErrorString(XML_GetErrorCode(poParser.get())),
                     (int)XML_GetCurrentLineNumber(poParser.get()),
                     (int)XML_GetCurrentColumnNumber(poParser.get()));
            bStopParsing = true;
        }
        nWithoutEventCounter++;
    } while (!nDone && !bStopParsing && this->nFeatureTabLength == 0 &&
             nWithoutEventCounter < 10);

    if (nWithoutEventCounter == 10)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Too much data inside one element. File probably corrupted");
        bStopParsing = true;
    }

    return (this->nFeatureTabLength) ? ppoFeatureTab[nFeatureTabIndex++]
                                     : nullptr;
}

static void XMLCALL startElementLoadSchemaCbk(void *pUserData,
                                              const char *pszName,
                                              const char **ppszAttr)
{
    static_cast<OGRJMLLayer *>(pUserData)->startElementLoadSchemaCbk(pszName,
                                                                     ppszAttr);
}

static void XMLCALL endElementLoadSchemaCbk(void *pUserData,
                                            const char *pszName)
{
    static_cast<OGRJMLLayer *>(pUserData)->endElementLoadSchemaCbk(pszName);
}

/************************************************************************/
/*                             LoadSchema()                             */
/************************************************************************/

/** This function parses the beginning of the file to detect the fields */
void OGRJMLLayer::LoadSchema()
{
    if (bHasReadSchema)
        return;

    bHasReadSchema = true;

    poParser.reset(OGRCreateExpatXMLParser());
    XML_SetElementHandler(poParser.get(), ::startElementLoadSchemaCbk,
                          ::endElementLoadSchemaCbk);
    XML_SetCharacterDataHandler(poParser.get(), ::dataHandlerCbk);
    XML_SetUserData(poParser.get(), this);

    VSIFSeekL(fp, 0, SEEK_SET);

    std::vector<char> aBuf(PARSER_BUF_SIZE);
    int nDone = 0;
    do
    {
        nDataHandlerCounter = 0;
        const unsigned int nLen = static_cast<unsigned int>(
            VSIFReadL(aBuf.data(), 1, aBuf.size(), fp));
        nDone = (nLen < aBuf.size());
        if (XML_Parse(poParser.get(), aBuf.data(), nLen, nDone) ==
            XML_STATUS_ERROR)
        {
            CPLError(
                CE_Failure, CPLE_AppDefined,
                "XML parsing of JML file failed : %s at line %d, "
                "column %d",
                XML_ErrorString(XML_GetErrorCode(poParser.get())),
                static_cast<int>(XML_GetCurrentLineNumber(poParser.get())),
                static_cast<int>(XML_GetCurrentColumnNumber(poParser.get())));
            bStopParsing = true;
        }
        nWithoutEventCounter++;
    } while (!nDone && !bStopParsing && !bSchemaFinished &&
             nWithoutEventCounter < 10);

    poParser.reset();

    if (nWithoutEventCounter == 10)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Too much data inside one element. File probably corrupted");
        bStopParsing = true;
    }

    if (osCollectionElement.empty() || osFeatureElement.empty() ||
        osGeometryElement.empty())
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Missing CollectionElement, FeatureElement or "
                 "GeometryElement");
        bStopParsing = true;
    }

    if (!osSRSName.empty())
    {
        if (osSRSName.find("http://www.opengis.net/gml/srs/epsg.xml#") == 0)
        {
            OGRSpatialReference *poSRS = new OGRSpatialReference();
            poSRS->importFromEPSG(atoi(
                osSRSName
                    .substr(strlen("http://www.opengis.net/gml/srs/epsg.xml#"))
                    .c_str()));
            poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
            poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poSRS);
            poSRS->Release();
        }
    }

    nJCSGMLInputTemplateDepth = 0;
    nCollectionElementDepth = 0;
    nFeatureCollectionDepth = 0;
    nFeatureElementDepth = 0;
    nGeometryElementDepth = 0;
    nColumnDepth = 0;
    nNameDepth = 0;
    nTypeDepth = 0;
    nAttributeElementDepth = 0;

    ResetReading();
}

/************************************************************************/
/*                     startElementLoadSchemaCbk()                      */
/************************************************************************/

void OGRJMLLayer::startElementLoadSchemaCbk(const char *pszName,
                                            const char **ppszAttr)
{
    if (bStopParsing)
        return;

    nWithoutEventCounter = 0;

    if (nJCSGMLInputTemplateDepth == 0 &&
        strcmp(pszName, "JCSGMLInputTemplate") == 0)
        nJCSGMLInputTemplateDepth = currentDepth;
    else if (nJCSGMLInputTemplateDepth > 0)
    {
        if (nCollectionElementDepth == 0 &&
            strcmp(pszName, "CollectionElement") == 0)
        {
            nCollectionElementDepth = currentDepth;
            bAccumulateElementValue = true;
        }
        else if (nFeatureElementDepth == 0 &&
                 strcmp(pszName, "FeatureElement") == 0)
        {
            nFeatureElementDepth = currentDepth;
            bAccumulateElementValue = true;
        }
        else if (nGeometryElementDepth == 0 &&
                 strcmp(pszName, "GeometryElement") == 0)
        {
            nGeometryElementDepth = currentDepth;
            bAccumulateElementValue = true;
        }
        else if (nColumnDepth == 0 && strcmp(pszName, "column") == 0)
        {
            nColumnDepth = currentDepth;
            oCurColumn.osName = "";
            oCurColumn.osType = "";
            oCurColumn.osElementName = "";
            oCurColumn.osAttributeName = "";
            oCurColumn.osAttributeValue = "";
            oCurColumn.bIsBody = false;
        }
        else if (nColumnDepth > 0)
        {
            if (nNameDepth == 0 && strcmp(pszName, "name") == 0)
            {
                nNameDepth = currentDepth;
                bAccumulateElementValue = true;
            }
            else if (nTypeDepth == 0 && strcmp(pszName, "type") == 0)
            {
                nTypeDepth = currentDepth;
                bAccumulateElementValue = true;
            }
            else if (strcmp(pszName, "valueElement") == 0)
            {
                const char **papszIter = ppszAttr;
                while (papszIter && *papszIter != nullptr)
                {
                    if (strcmp(*papszIter, "elementName") == 0)
                        oCurColumn.osElementName = papszIter[1];
                    else if (strcmp(*papszIter, "attributeName") == 0)
                        oCurColumn.osAttributeName = papszIter[1];
                    else if (strcmp(*papszIter, "attributeValue") == 0)
                        oCurColumn.osAttributeValue = papszIter[1];
                    papszIter += 2;
                }
            }
            else if (strcmp(pszName, "valueLocation") == 0)
            {
                const char **papszIter = ppszAttr;
                while (papszIter && *papszIter != nullptr)
                {
                    if (strcmp(*papszIter, "position") == 0)
                        oCurColumn.bIsBody = strcmp(papszIter[1], "body") == 0;
                    else if (strcmp(*papszIter, "attributeName") == 0)
                        oCurColumn.osAttributeName = papszIter[1];
                    papszIter += 2;
                }
            }
        }
    }
    else if (nFeatureCollectionDepth == 0 &&
             osCollectionElement.compare(pszName) == 0)
    {
        nFeatureCollectionDepth = currentDepth;
    }
    else if (nFeatureCollectionDepth > 0 &&
             currentDepth == nFeatureCollectionDepth + 2 &&
             strcmp(pszName, "gml:Box") == 0)
    {
        const char **papszIter = ppszAttr;
        while (papszIter && *papszIter != nullptr)
        {
            if (strcmp(*papszIter, "srsName") == 0)
                osSRSName = papszIter[1];
            papszIter += 2;
        }
        bSchemaFinished = true;
    }
    else if (nFeatureCollectionDepth >= 0 &&
             currentDepth >= nFeatureCollectionDepth + 1 &&
             osFeatureElement.compare(pszName) == 0)
    {
        bSchemaFinished = true;
    }

    currentDepth++;
}

/************************************************************************/
/*                      endElementLoadSchemaCbk()                       */
/************************************************************************/

void OGRJMLLayer::endElementLoadSchemaCbk(const char * /* pszName */)
{
    if (bStopParsing)
        return;

    nWithoutEventCounter = 0;

    currentDepth--;

    if (nJCSGMLInputTemplateDepth == currentDepth)
    {
        nJCSGMLInputTemplateDepth = 0;
    }
    else if (nCollectionElementDepth == currentDepth)
    {
        nCollectionElementDepth = 0;
        osCollectionElement = pszElementValue;
#ifdef DEBUG_VERBOSE
        CPLDebug("JML", "osCollectionElement = %s",
                 osCollectionElement.c_str());
#endif
        StopAccumulate();
    }
    else if (nFeatureElementDepth == currentDepth)
    {
        nFeatureElementDepth = 0;
        osFeatureElement = pszElementValue;
#ifdef DEBUG_VERBOSE
        CPLDebug("JML", "osFeatureElement = %s", osFeatureElement.c_str());
#endif
        StopAccumulate();
    }
    else if (nGeometryElementDepth == currentDepth)
    {
        nGeometryElementDepth = 0;
        osGeometryElement = pszElementValue;
#ifdef DEBUG_VERBOSE
        CPLDebug("JML", "osGeometryElement = %s", osGeometryElement.c_str());
#endif
        StopAccumulate();
    }
    else if (nColumnDepth == currentDepth)
    {
        bool bIsOK = true;
        if (oCurColumn.osName.empty())
            bIsOK = false;
        if (oCurColumn.osType.empty())
            bIsOK = false;
        if (oCurColumn.osElementName.empty())
            bIsOK = false;
        if (oCurColumn.bIsBody)
        {
            if (oCurColumn.osAttributeName.empty() &&
                !oCurColumn.osAttributeValue.empty())
                bIsOK = false;
            if (!oCurColumn.osAttributeName.empty() &&
                oCurColumn.osAttributeValue.empty())
                bIsOK = false;
            /* Only 2 valid possibilities : */
            /* <osElementName
             * osAttributeName="osAttributeValue">value</osElementName> */
            /* <osElementName>value</osElementName> */
        }
        else
        {
            /* <osElementName osAttributeName="value"></osElementName> */
            if (oCurColumn.osAttributeName.empty())
                bIsOK = false;
            if (!oCurColumn.osAttributeValue.empty())
                bIsOK = false;
        }

        if (bIsOK)
        {
            OGRFieldType eType = OFTString;
            if (EQUAL(oCurColumn.osType, "INTEGER"))
                eType = OFTInteger;
            else if (EQUAL(oCurColumn.osType, "DOUBLE"))
                eType = OFTReal;
            else if (EQUAL(oCurColumn.osType, "DATE"))
                eType = OFTDateTime;
            OGRFieldDefn oField(oCurColumn.osName, eType);

            if (oCurColumn.osName == "R_G_B" && eType == OFTString)
                iRGBField = poFeatureDefn->GetFieldCount();

            poFeatureDefn->AddFieldDefn(&oField);
            aoColumns.push_back(oCurColumn);
        }
        else
        {
            CPLDebug("JML",
                     "Invalid column definition: name = %s, type = %s, "
                     "elementName = %s, attributeName = %s, "
                     "attributeValue = %s, bIsBody = %d",
                     oCurColumn.osName.c_str(), oCurColumn.osType.c_str(),
                     oCurColumn.osElementName.c_str(),
                     oCurColumn.osAttributeName.c_str(),
                     oCurColumn.osAttributeValue.c_str(),
                     static_cast<int>(oCurColumn.bIsBody));
        }

        nColumnDepth = 0;
    }
    else if (nNameDepth == currentDepth)
    {
        nNameDepth = 0;
        oCurColumn.osName = pszElementValue;
#ifdef DEBUG_VERBOSE
        CPLDebug("JML", "oCurColumn.osName = %s", oCurColumn.osName.c_str());
#endif
        StopAccumulate();
    }
    else if (nTypeDepth == currentDepth)
    {
        nTypeDepth = 0;
        oCurColumn.osType = pszElementValue;
#ifdef DEBUG_VERBOSE
        CPLDebug("JML", "oCurColumn.osType = %s", oCurColumn.osType.c_str());
#endif
        StopAccumulate();
    }
}

/************************************************************************/
/*                           TestCapability()                           */
/************************************************************************/

int OGRJMLLayer::TestCapability(const char *pszCap) const

{
    if (EQUAL(pszCap, OLCStringsAsUTF8))
        return true;
    else if (EQUAL(pszCap, OLCZGeometries))
        return true;

    return false;
}

#endif /* HAVE_EXPAT */
