/******************************************************************************
 *
 * Project:  JML Translator
 * Purpose:  Implements OGRJMLLayer class.
 *
 ******************************************************************************
 * Copyright (c) 2014, Even Rouault <even dot rouault at spatialys dot com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

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

CPL_CVSID("$Id$")

#ifdef HAVE_EXPAT

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

OGRJMLLayer::OGRJMLLayer( const char* pszLayerName,
                          OGRJMLDataset * /* poDSIn */,
                          VSILFILE* fpIn ) :
    poFeatureDefn(new OGRFeatureDefn( pszLayerName )),
    nNextFID(0),
    fp(fpIn),
    bHasReadSchema(false),
    oParser(nullptr),
    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()

{
    if (oParser)
        XML_ParserFree(oParser);
    poFeatureDefn->Release();

    CPLFree(pszElementValue);

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

    if (poFeature)
        delete poFeature;
}

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

OGRFeatureDefn * OGRJMLLayer::GetLayerDefn()
{
    if (!bHasReadSchema)
        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 );
    if (oParser)
        XML_ParserFree(oParser);

    oParser = OGRCreateExpatXMLParser();
    XML_SetElementHandler(oParser, ::startElementCbk, ::endElementCbk);
    XML_SetCharacterDataHandler(oParser, ::dataHandlerCbk);
    XML_SetUserData(oParser, 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 = reinterpret_cast<OGRGeometry *>(
                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(oParser, 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(oParser, 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 >= BUFSIZ)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "File probably corrupted (million laugh pattern)");
        XML_StopParser(oParser, 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))
        return nullptr;

    char aBuf[BUFSIZ];

    nFeatureTabLength = 0;
    nFeatureTabIndex = 0;

    nWithoutEventCounter = 0;

    int nDone = 0;
    do
    {
        nDataHandlerCounter = 0;
        unsigned int nLen =
                (unsigned int)VSIFReadL( aBuf, 1, sizeof(aBuf), fp );
        nDone = VSIFEofL(fp);
        if (XML_Parse(oParser, aBuf, 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(oParser)),
                     (int)XML_GetCurrentLineNumber(oParser),
                     (int)XML_GetCurrentColumnNumber(oParser));
            bStopParsing = true;
        }
        nWithoutEventCounter ++;
    } while (!nDone && !bStopParsing && nFeatureTabLength == 0 &&
             nWithoutEventCounter < 10);

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

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

    oParser = OGRCreateExpatXMLParser();
    XML_SetElementHandler(oParser, ::startElementLoadSchemaCbk,
                          ::endElementLoadSchemaCbk);
    XML_SetCharacterDataHandler(oParser, ::dataHandlerCbk);
    XML_SetUserData(oParser, this);

    VSIFSeekL( fp, 0, SEEK_SET );

    char aBuf[BUFSIZ];
    int nDone = 0;
    do
    {
        nDataHandlerCounter = 0;
        const unsigned int nLen = static_cast<unsigned int>(
            VSIFReadL( aBuf, 1, sizeof(aBuf), fp ) );
        nDone = VSIFEofL(fp);
        if (XML_Parse(oParser, aBuf, 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(oParser)),
                      static_cast<int>(XML_GetCurrentLineNumber(oParser)),
                      static_cast<int>(XML_GetCurrentColumnNumber(oParser)) );
            bStopParsing = true;
        }
        nWithoutEventCounter ++;
    } while ( !nDone && !bStopParsing && !bSchemaFinished &&
              nWithoutEventCounter < 10 );

    XML_ParserFree(oParser);
    oParser = nullptr;

    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 )

{
    return EQUAL(pszCap,OLCStringsAsUTF8);
}

#endif /* HAVE_EXPAT */
