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

#include "gmlreaderp.h"
#include "gmlreader.h"
#include "cpl_error.h"
#include "cpl_string.h"
#include "gmlutils.h"
#include "cpl_multiproc.h"

#include "ogr_geometry.h"

#include <algorithm>

CPL_CVSID("$Id: nasreader.cpp 38566 2017-05-21 16:59:47Z rouault $");

/************************************************************************/
/* ==================================================================== */
/*                  With XERCES Library                                 */
/* ==================================================================== */
/************************************************************************/

#include "nasreaderp.h"
#include "cpl_conv.h"

/************************************************************************/
/*                          CreateNASReader()                           */
/************************************************************************/

IGMLReader *CreateNASReader()

{
    return new NASReader();
}

/************************************************************************/
/*                             NASReader()                              */
/************************************************************************/

NASReader::NASReader() :
    m_bClassListLocked(false),
    m_nClassCount(0),
    m_papoClass(NULL),
    m_pszFilename(NULL),
    m_poNASHandler(NULL),
    m_poSAXReader(NULL),
    m_bReadStarted(false),
    m_bXercesInitialized(false),
    m_poState(NULL),
    m_poCompleteFeature(NULL),
    m_pszFilteredClassName(NULL)
{}

/************************************************************************/
/*                             ~NASReader()                             */
/************************************************************************/

NASReader::~NASReader()

{
    ClearClasses();

    CPLFree( m_pszFilename );

    CleanupParser();

    if( m_bXercesInitialized )
        OGRDeinitializeXerces();

    CPLFree( m_pszFilteredClassName );
}

/************************************************************************/
/*                          SetSourceFile()                             */
/************************************************************************/

void NASReader::SetSourceFile( const char *pszFilename )

{
    CPLFree( m_pszFilename );
    m_pszFilename = CPLStrdup( pszFilename );
}

/************************************************************************/
/*                       GetSourceFileName()                            */
/************************************************************************/

const char* NASReader::GetSourceFileName()

{
    return m_pszFilename;
}

/************************************************************************/
/*                            SetupParser()                             */
/************************************************************************/

bool NASReader::SetupParser()

{
    if( !m_bXercesInitialized )
    {
        if( !OGRInitializeXerces() )
            return false;
        m_bXercesInitialized = true;
    }

    // Cleanup any old parser.
    if( m_poSAXReader != NULL )
        CleanupParser();

    // Create and initialize parser.
    XMLCh* xmlUriValid = NULL;
    XMLCh* xmlUriNS = NULL;

    try{
        m_poSAXReader = XMLReaderFactory::createXMLReader();

        m_poNASHandler = new NASHandler( this );

        m_poSAXReader->setContentHandler( m_poNASHandler );
        m_poSAXReader->setErrorHandler( m_poNASHandler );
        m_poSAXReader->setLexicalHandler( m_poNASHandler );
        m_poSAXReader->setEntityResolver( m_poNASHandler );
        m_poSAXReader->setDTDHandler( m_poNASHandler );

        xmlUriValid =
            XMLString::transcode("http://xml.org/sax/features/validation");
        xmlUriNS =
            XMLString::transcode("http://xml.org/sax/features/namespaces");

#if (OGR_GML_VALIDATION)
        m_poSAXReader->setFeature( xmlUriValid, true);
        m_poSAXReader->setFeature( xmlUriNS, true);

        m_poSAXReader->setFeature( XMLUni::fgSAX2CoreNameSpaces, true );
        m_poSAXReader->setFeature( XMLUni::fgXercesSchema, true );

//    m_poSAXReader->setDoSchema(true);
//    m_poSAXReader->setValidationSchemaFullChecking(true);
#else
        m_poSAXReader->setFeature( XMLUni::fgSAX2CoreValidation, false);

        m_poSAXReader->setFeature( XMLUni::fgXercesSchema, false);

#endif
        XMLString::release( &xmlUriValid );
        XMLString::release( &xmlUriNS );
    }
    catch (...)
    {
        XMLString::release( &xmlUriValid );
        XMLString::release( &xmlUriNS );

        CPLError( CE_Warning, CPLE_AppDefined,
                  "Exception initializing Xerces based GML reader.\n" );
        return false;
    }

    m_bReadStarted = false;

    // Push an empty state.
    PushState( new GMLReadState() );

    return true;
}

/************************************************************************/
/*                           CleanupParser()                            */
/************************************************************************/

void NASReader::CleanupParser()

{
    if( m_poSAXReader == NULL )
        return;

    while( m_poState )
        PopState();

    delete m_poSAXReader;
    m_poSAXReader = NULL;

    delete m_poNASHandler;
    m_poNASHandler = NULL;

    delete m_poCompleteFeature;
    m_poCompleteFeature = NULL;

    m_bReadStarted = false;
}

/************************************************************************/
/*                            NextFeature()                             */
/************************************************************************/

GMLFeature *NASReader::NextFeature()

{
    GMLFeature *poReturn = NULL;

    try
    {
        if( !m_bReadStarted )
        {
            if( m_poSAXReader == NULL )
                SetupParser();
            if( m_poSAXReader == NULL )
                return NULL;

            if( !m_poSAXReader->parseFirst( m_pszFilename, m_oToFill ) )
                return NULL;
            m_bReadStarted = true;
        }

        while( m_poCompleteFeature == NULL
               && m_poSAXReader->parseNext( m_oToFill ) ) {}

        poReturn = m_poCompleteFeature;
        m_poCompleteFeature = NULL;
    }
    catch (const XMLException& toCatch)
    {
        CPLDebug( "NAS",
                  "Error during NextFeature()! Message:\n%s",
                  transcode( toCatch.getMessage() ).c_str() );
    }

    return poReturn;
}

/************************************************************************/
/*                            PushFeature()                             */
/*                                                                      */
/*      Create a feature based on the named element.  If the            */
/*      corresponding feature class doesn't exist yet, then create      */
/*      it now.  A new GMLReadState will be created for the feature,    */
/*      and it will be placed within that state.  The state is          */
/*      pushed onto the readstate stack.                                */
/************************************************************************/

void NASReader::PushFeature( const char *pszElement,
                             const Attributes &attrs )

{
/* -------------------------------------------------------------------- */
/*      Find the class of this element.                                 */
/* -------------------------------------------------------------------- */
    int iClass = 0;
    for( ; iClass < GetClassCount(); iClass++ )
    {
        if( EQUAL(pszElement,GetClass(iClass)->GetElementName()) )
            break;
    }

/* -------------------------------------------------------------------- */
/*      Create a new feature class for this element, if there is no     */
/*      existing class for it.                                          */
/* -------------------------------------------------------------------- */
    if( iClass == GetClassCount() )
    {
        CPLAssert( !IsClassListLocked() );

        GMLFeatureClass *poNewClass = new GMLFeatureClass( pszElement );

        iClass = AddClass( poNewClass );
    }

/* -------------------------------------------------------------------- */
/*      Create a feature of this feature class.                         */
/* -------------------------------------------------------------------- */
    GMLFeature *poFeature = new GMLFeature( GetClass( iClass ) );

/* -------------------------------------------------------------------- */
/*      Create and push a new read state.                               */
/* -------------------------------------------------------------------- */
    GMLReadState *poState = new GMLReadState();
    poState->m_poFeature = poFeature;
    PushState( poState );

/* -------------------------------------------------------------------- */
/*      Check for gml:id, and if found push it as an attribute named    */
/*      gml_id.                                                         */
/* -------------------------------------------------------------------- */
    const XMLCh achFID[] = { 'g', 'm', 'l', ':', 'i', 'd', '\0' };
    int nFIDIndex = attrs.getIndex( achFID );
    if( nFIDIndex != -1 )
    {
        char *pszFID = CPLStrdup( transcode( attrs.getValue( nFIDIndex ) ) );
        SetFeaturePropertyDirectly( "gml_id", pszFID );
    }
}

/************************************************************************/
/*                          IsFeatureElement()                          */
/*                                                                      */
/*      Based on context and the element name, is this element a new    */
/*      GML feature element?                                            */
/************************************************************************/

bool NASReader::IsFeatureElement( const char *pszElement )

{
    CPLAssert( m_poState != NULL );

    const char *pszLast = m_poState->GetLastComponent();
    const int nLen = static_cast<int>(strlen(pszLast));

    // There seem to be two major NAS classes of feature identifiers
    // -- either a wfs:Insert or a gml:featureMember.

    if( (nLen < 6 || !EQUAL(pszLast+nLen-6,"Insert"))
        && (nLen < 13 || !EQUAL(pszLast+nLen-13,"featureMember"))
        && (nLen < 7 || !EQUAL(pszLast+nLen-7,"Replace")) )
        return false;

    // If the class list isn't locked, any element that is a featureMember
    // will do.
    if( !IsClassListLocked() )
        return true;

    // otherwise, find a class with the desired element name.
    for( int i = 0; i < GetClassCount(); i++ )
    {
        if( EQUAL(pszElement,GetClass(i)->GetElementName()) )
            return true;
    }

    return false;
}

/************************************************************************/
/*                         IsAttributeElement()                         */
/************************************************************************/

bool NASReader::IsAttributeElement( const char *pszElement )

{
    if( m_poState->m_poFeature == NULL )
        return false;

    GMLFeatureClass *poClass = m_poState->m_poFeature->GetClass();

    // If the schema is not yet locked, then any simple element
    // is potentially an attribute.
    if( !poClass->IsSchemaLocked() )
        return true;

    // Otherwise build the path to this element into a single string
    // and compare against known attributes.
    CPLString osElemPath;

    if( m_poState->m_nPathLength == 0 )
        osElemPath = pszElement;
    else
    {
        osElemPath = m_poState->osPath;
        osElemPath += "|";
        osElemPath += pszElement;
    }

    for( int i = 0; i < poClass->GetPropertyCount(); i++ )
        if( EQUAL(poClass->GetProperty(i)->GetSrcElement(),osElemPath) )
            return true;

    return false;
}

/************************************************************************/
/*                              PopState()                              */
/************************************************************************/

void NASReader::PopState()

{
    if( m_poState != NULL )
    {
        if( m_poState->m_poFeature != NULL && m_poCompleteFeature == NULL )
        {
            m_poCompleteFeature = m_poState->m_poFeature;
            m_poState->m_poFeature = NULL;
        }
        else if( m_poState->m_poFeature != NULL )
        {
            delete m_poState->m_poFeature;
            m_poState->m_poFeature = NULL;
        }

        GMLReadState *poParent = m_poState->m_poParentState;

        delete m_poState;
        m_poState = poParent;
    }
}

/************************************************************************/
/*                             PushState()                              */
/************************************************************************/

void NASReader::PushState( GMLReadState *poState )

{
    poState->m_poParentState = m_poState;
    m_poState = poState;
}

/************************************************************************/
/*                              GetClass()                              */
/************************************************************************/

GMLFeatureClass *NASReader::GetClass( int iClass ) const

{
    if( iClass < 0 || iClass >= m_nClassCount )
        return NULL;

    return m_papoClass[iClass];
}

/************************************************************************/
/*                              GetClass()                              */
/************************************************************************/

GMLFeatureClass *NASReader::GetClass( const char *pszName ) const

{
    for( int iClass = 0; iClass < m_nClassCount; iClass++ )
    {
        if( EQUAL(GetClass(iClass)->GetName(),pszName) )
            return GetClass(iClass);
    }

    return NULL;
}

/************************************************************************/
/*                              AddClass()                              */
/************************************************************************/

int NASReader::AddClass( GMLFeatureClass *poNewClass )

{
    CPLAssert( GetClass( poNewClass->GetName() ) == NULL );

    m_nClassCount++;
    m_papoClass = (GMLFeatureClass **)
        CPLRealloc( m_papoClass, sizeof(void*) * m_nClassCount );

    // keep delete the last entry
    if( m_nClassCount > 1 &&
        EQUAL( m_papoClass[m_nClassCount-2]->GetName(), "Delete" ) )
    {
      m_papoClass[m_nClassCount-1] = m_papoClass[m_nClassCount-2];
      m_papoClass[m_nClassCount-2] = poNewClass;
      return m_nClassCount-2;
    }
    else
    {
      m_papoClass[m_nClassCount-1] = poNewClass;
      return m_nClassCount-1;
    }
}

/************************************************************************/
/*                            ClearClasses()                            */
/************************************************************************/

void NASReader::ClearClasses()

{
    for( int i = 0; i < m_nClassCount; i++ )
        delete m_papoClass[i];
    CPLFree( m_papoClass );

    m_nClassCount = 0;
    m_papoClass = NULL;
}

/************************************************************************/
/*                         SetFeatureProperty()                         */
/*                                                                      */
/*      Set the property value on the current feature, adding the       */
/*      property name to the GMLFeatureClass if required.               */
/*      The pszValue ownership is passed to this function.              */
/************************************************************************/

void NASReader::SetFeaturePropertyDirectly( const char *pszElement,
                                            char *pszValue )

{
    GMLFeature *poFeature = GetState()->m_poFeature;

    CPLAssert( poFeature  != NULL );

/* -------------------------------------------------------------------- */
/*      Does this property exist in the feature class?  If not, add     */
/*      it.                                                             */
/* -------------------------------------------------------------------- */
    GMLFeatureClass *poClass = poFeature->GetClass();
    int iProperty = 0;

    for( ; iProperty < poClass->GetPropertyCount(); iProperty++ )
    {
        if( EQUAL(poClass->GetProperty( iProperty )->GetSrcElement(),
                  pszElement ) )
            break;
    }

    if( iProperty == poClass->GetPropertyCount() )
    {
        if( poClass->IsSchemaLocked() )
        {
            CPLDebug("NAS", "Encountered property missing from class schema.");
            CPLFree(pszValue);
            return;
        }

        CPLString osFieldName;

        if( strchr(pszElement,'|') == NULL )
            osFieldName = pszElement;
        else
        {
            osFieldName = strrchr(pszElement,'|') + 1;
            if( poClass->GetPropertyIndex(osFieldName) != -1 )
                osFieldName = pszElement;
        }

        // Does this conflict with an existing property name?
        while( poClass->GetProperty(osFieldName) != NULL )
        {
            osFieldName += "_";
        }

        GMLPropertyDefn *poPDefn = new GMLPropertyDefn(osFieldName,pszElement);

        if( EQUAL(CPLGetConfigOption( "GML_FIELDTYPES", ""), "ALWAYS_STRING") )
            poPDefn->SetType( GMLPT_String );

        poClass->AddProperty( poPDefn );
    }

    if ( GMLPropertyDefn::IsSimpleType(
             poClass->GetProperty( iProperty )->GetType() ) )
    {
        const GMLProperty *poProp = poFeature->GetProperty(iProperty);
        if ( poProp && poProp->nSubProperties > 0 )
        {
            int iId = poClass->GetPropertyIndex( "gml_id" );
            const GMLProperty *poIdProp = poFeature->GetProperty(iId);

            CPLDebug( "NAS",
                      "Overwriting existing property %s.%s of value '%s' "
                      "with '%s' (gml_id: %s).",
                      poClass->GetName(), pszElement,
                      poProp->papszSubProperties[0], pszValue,
                      poIdProp && poIdProp->nSubProperties>0 &&
                      poIdProp->papszSubProperties &&
                      poIdProp->papszSubProperties[0] ?
                      poIdProp->papszSubProperties[0] : "(null)" );
        }
    }

/* -------------------------------------------------------------------- */
/*      We want to handle <lage> specially to ensure it is zero         */
/*      filled, and treated as a string despite the numeric             */
/*      content. https://trac.wheregroup.com/PostNAS/ticket/9           */
/* -------------------------------------------------------------------- */
    if( strcmp(poClass->GetProperty(iProperty)->GetName(),"lage") == 0 )
    {
        if( strlen(pszValue) < 5 )
        {
            CPLString osValue = "00000";
            osValue += pszValue;
            poFeature->SetPropertyDirectly(
                iProperty, CPLStrdup(osValue + osValue.size() - 5) );
            CPLFree(pszValue);
        }
        else
            poFeature->SetPropertyDirectly( iProperty, pszValue );

        if( !poClass->IsSchemaLocked() )
        {
            poClass->GetProperty(iProperty)->SetWidth( 5 );
            poClass->GetProperty(iProperty)->SetType( GMLPT_String );
        }
        return;
    }
    else if( strcmp( poClass->GetProperty(iProperty)->GetName(),
                     "kartendarstellung") == 0 ||
             strcmp( poClass->GetProperty(iProperty)->GetName(),
                     "rechtsbehelfsverfahren") == 0 )
    {
        poFeature->SetPropertyDirectly(
            iProperty, CPLStrdup( EQUAL( pszValue, "true" ) ? "1" : "0" ) );
        CPLFree(pszValue);

        if( !poClass->IsSchemaLocked() )
        {
            poClass->GetProperty(iProperty)->SetType( GMLPT_Integer );
        }
        return;
    }

/* -------------------------------------------------------------------- */
/*      Set the property                                                */
/* -------------------------------------------------------------------- */
    poFeature->SetPropertyDirectly( iProperty, pszValue );

/* -------------------------------------------------------------------- */
/*      Do we need to update the property type?                         */
/* -------------------------------------------------------------------- */
    if( !poClass->IsSchemaLocked() )
    {
        // Special handling for punktkennung per NAS #12
        if( strcmp(poClass->GetProperty(iProperty)->GetName(),
                   "punktkennung") == 0)
        {
            poClass->GetProperty(iProperty)->SetWidth( 15 );
            poClass->GetProperty(iProperty)->SetType( GMLPT_String );
        }
        // Special handling for artDerFlurstuecksgrenze per
        // http://trac.osgeo.org/gdal/ticket/4255
        else if( strcmp(poClass->GetProperty(iProperty)->GetName(),
                   "artDerFlurstuecksgrenze") == 0)
        {
            poClass->GetProperty(iProperty)->SetType( GMLPT_IntegerList );
        }
        else
            poClass->GetProperty(iProperty)->AnalysePropertyValue(
                poFeature->GetProperty(iProperty));
    }
}

/************************************************************************/
/*                            LoadClasses()                             */
/************************************************************************/

bool NASReader::LoadClasses( const char *pszFile )

{
    // Add logic later to determine reasonable default schema file.
    if( pszFile == NULL )
        return false;

/* -------------------------------------------------------------------- */
/*      Load the raw XML file.                                          */
/* -------------------------------------------------------------------- */
    VSILFILE   *fp = VSIFOpenL( pszFile, "rb" );

    if( fp == NULL )
    {
        CPLError( CE_Failure, CPLE_OpenFailed,
                  "Failed to open file %s.", pszFile );
        return false;
    }

    VSIFSeekL( fp, 0, SEEK_END );
    const int nLength = static_cast<int>(VSIFTellL( fp ));
    VSIFSeekL( fp, 0, SEEK_SET );

    char *pszWholeText = static_cast<char *>( VSIMalloc(nLength+1) );
    if( pszWholeText == NULL )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Failed to allocate %d byte buffer for %s,\n"
                  "is this really a GMLFeatureClassList file?",
                  nLength, pszFile );
        VSIFCloseL( fp );
        return false;
    }

    if( VSIFReadL( pszWholeText, nLength, 1, fp ) != 1 )
    {
        VSIFree( pszWholeText );
        VSIFCloseL( fp );
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Read failed on %s.", pszFile );
        return false;
    }
    pszWholeText[nLength] = '\0';

    VSIFCloseL( fp );

    if( strstr( pszWholeText, "<GMLFeatureClassList>" ) == NULL )
    {
        VSIFree( pszWholeText );
        CPLError( CE_Failure, CPLE_AppDefined,
                  "File %s does not contain a GMLFeatureClassList tree.",
                  pszFile );
        return false;
    }

/* -------------------------------------------------------------------- */
/*      Convert to XML parse tree.                                      */
/* -------------------------------------------------------------------- */
    CPLXMLNode *psRoot = CPLParseXMLString( pszWholeText );
    VSIFree( pszWholeText );

    // We assume parser will report errors via CPL.
    if( psRoot == NULL )
        return false;

    if( psRoot->eType != CXT_Element
        || !EQUAL(psRoot->pszValue,"GMLFeatureClassList") )
    {
        CPLDestroyXMLNode(psRoot);
        CPLError( CE_Failure, CPLE_AppDefined,
                  "File %s is not a GMLFeatureClassList document.",
                  pszFile );
        return false;
    }

/* -------------------------------------------------------------------- */
/*      Extract feature classes for all definitions found.              */
/* -------------------------------------------------------------------- */
    for( CPLXMLNode *psThis = psRoot->psChild;
         psThis != NULL;
         psThis = psThis->psNext )
    {
        if( psThis->eType == CXT_Element
            && EQUAL(psThis->pszValue,"GMLFeatureClass") )
        {
            GMLFeatureClass *poClass = new GMLFeatureClass();

            if( !poClass->InitializeFromXML( psThis ) )
            {
                delete poClass;
                CPLDestroyXMLNode( psRoot );
                return false;
            }

            poClass->SetSchemaLocked( true );

            AddClass( poClass );
        }
    }

    CPLDestroyXMLNode( psRoot );

    SetClassListLocked( true );

    return true;
}

/************************************************************************/
/*                            SaveClasses()                             */
/************************************************************************/

bool NASReader::SaveClasses( const char *pszFile )

{
    // Add logic later to determine reasonable default schema file.
    if( pszFile == NULL )
        return false;

/* -------------------------------------------------------------------- */
/*      Create in memory schema tree.                                   */
/* -------------------------------------------------------------------- */
    CPLXMLNode *psRoot =
        CPLCreateXMLNode( NULL, CXT_Element, "GMLFeatureClassList" );

    for( int iClass = 0; iClass < GetClassCount(); iClass++ )
    {
        GMLFeatureClass *poClass = GetClass( iClass );

        CPLAddXMLChild( psRoot, poClass->SerializeToXML() );
    }

/* -------------------------------------------------------------------- */
/*      Serialize to disk.                                              */
/* -------------------------------------------------------------------- */
    bool         bSuccess = true;
    char        *pszWholeText = CPLSerializeXMLTree( psRoot );

    CPLDestroyXMLNode( psRoot );

    FILE *fp = VSIFOpen( pszFile, "wb" );

    if( fp == NULL )
        bSuccess = false;
    else
    {
        if( VSIFWrite( pszWholeText, strlen(pszWholeText), 1, fp ) != 1 )
            bSuccess = false;
        VSIFClose( fp );
    }

    CPLFree( pszWholeText );

    return bSuccess;
}

/************************************************************************/
/*                          PrescanForSchema()                          */
/*                                                                      */
/*      For now we use a pretty dumb approach of just doing a normal    */
/*      scan of the whole file, building up the schema information.     */
/*      Eventually we hope to do a more efficient scan when just        */
/*      looking for schema information.                                 */
/************************************************************************/

bool NASReader::PrescanForSchema( bool bGetExtents,
                                  bool /*bAnalyzeSRSPerFeature*/,
                                  bool /*bOnlyDetectSRS*/ )
{
    if( m_pszFilename == NULL )
        return false;

    SetClassListLocked( false );

    ClearClasses();
    if( !SetupParser() )
        return false;

    std::string osWork;
    GMLFeature  *poFeature = NULL;

    while( (poFeature = NextFeature()) != NULL )
    {
        GMLFeatureClass *poClass = poFeature->GetClass();

        if( poClass->GetFeatureCount() == -1 )
            poClass->SetFeatureCount( 1 );
        else
            poClass->SetFeatureCount( poClass->GetFeatureCount() + 1 );

        if( bGetExtents )
        {
            OGRGeometry *poGeometry = NULL;

            const CPLXMLNode* const * papsGeometry = poFeature->GetGeometryList();
            if( papsGeometry[0] != NULL )
            {
                poGeometry = (OGRGeometry*) OGR_G_CreateFromGMLTree(papsGeometry[0]);
                poGeometry = ConvertGeometry(poGeometry);
            }

            if( poGeometry != NULL )
            {
                OGREnvelope sEnvelope;

                if( poClass->GetGeometryPropertyCount() == 0 )
                    poClass->AddGeometryProperty(
                        new GMLGeometryPropertyDefn( "", "", wkbUnknown, -1, true ) );

                OGRwkbGeometryType eGType = (OGRwkbGeometryType)
                    poClass->GetGeometryProperty(0)->GetType();

                // Merge SRSName into layer.
                const char* pszSRSName = GML_ExtractSrsNameFromGeometry(papsGeometry, osWork, false);
//                if (pszSRSName != NULL)
//                    m_bCanUseGlobalSRSName = FALSE;
                poClass->MergeSRSName(pszSRSName);

                // Merge geometry type into layer.
                if( poClass->GetFeatureCount() == 1 && eGType == wkbUnknown )
                    eGType = wkbNone;

                poClass->GetGeometryProperty(0)->SetType(
                    (int) OGRMergeGeometryTypesEx(
                        eGType, poGeometry->getGeometryType(), TRUE ) );

                // merge extents.
                poGeometry->getEnvelope( &sEnvelope );
                delete poGeometry;
                double dfXMin = 0.0;
                double dfXMax = 0.0;
                double dfYMin = 0.0;
                double dfYMax = 0.0;
                if( poClass->GetExtents(&dfXMin, &dfXMax, &dfYMin, &dfYMax) )
                {
                    dfXMin = std::min(dfXMin, sEnvelope.MinX);
                    dfXMax = std::max(dfXMax, sEnvelope.MaxX);
                    dfYMin = std::min(dfYMin, sEnvelope.MinY);
                    dfYMax = std::max(dfYMax, sEnvelope.MaxY);
                }
                else
                {
                    dfXMin = sEnvelope.MinX;
                    dfXMax = sEnvelope.MaxX;
                    dfYMin = sEnvelope.MinY;
                    dfYMax = sEnvelope.MaxY;
                }

                poClass->SetExtents( dfXMin, dfXMax, dfYMin, dfYMax );
            }
            else
            {
                if( poClass->GetGeometryPropertyCount() == 1 &&
                    poClass->GetGeometryProperty(0)->GetType() == (int) wkbUnknown
                    && poClass->GetFeatureCount() == 1 )
                {
                    poClass->ClearGeometryProperties();
                }
            }
        }

        delete poFeature;
    }

    CleanupParser();

    return GetClassCount() > 0;
}

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

void NASReader::ResetReading()

{
    CleanupParser();
    SetFilteredClassName(NULL);
}

/************************************************************************/
/*                            CheckForFID()                             */
/*                                                                      */
/*      Merge the fid attribute into the current field text.            */
/************************************************************************/

void NASReader::CheckForFID( const Attributes &attrs,
                             char **ppszCurField )

{
    const XMLCh  Name[] = { 'f', 'i', 'd', '\0' };
    int nIndex = attrs.getIndex( Name );

    if( nIndex != -1 )
    {
        CPLString osCurField = *ppszCurField;

        osCurField += transcode( attrs.getValue( nIndex ) );

        CPLFree( *ppszCurField );
        *ppszCurField = CPLStrdup(osCurField);
    }
}

/************************************************************************/
/*                         CheckForRelations()                          */
/************************************************************************/

void NASReader::CheckForRelations( const char *pszElement,
                                   const Attributes &attrs,
                                   char **ppszCurField )

{
    GMLFeature *poFeature = GetState()->m_poFeature;

    CPLAssert( poFeature  != NULL );

    const XMLCh  Name[] = { 'x', 'l', 'i', 'n', 'k', ':', 'h', 'r', 'e', 'f', '\0' };
    const int nIndex = attrs.getIndex( Name );

    if( nIndex != -1 )
    {
        CPLString osVal( transcode( attrs.getValue( nIndex ) ) );

        if( STARTS_WITH_CI(osVal, "urn:adv:oid:") )
        {
            poFeature->AddOBProperty( pszElement, osVal );
            CPLFree( *ppszCurField );
            *ppszCurField = CPLStrdup( osVal.c_str() + 12 );
        }
    }
}

/************************************************************************/
/*                         HugeFileResolver()                           */
/*      Returns true for success                                        */
/************************************************************************/

bool NASReader::HugeFileResolver( const char * /*pszFile */,
                                  bool /* bSqliteIsTempFile */,
                                  int /* iSqliteCacheMB */ )
{
    CPLDebug( "NAS", "HugeFileResolver() not currently implemented for NAS." );
    return false;
}

/************************************************************************/
/*                         PrescanForTemplate()                         */
/*      Returns true for success                                        */
/************************************************************************/

bool NASReader::PrescanForTemplate( void )

{
    CPLDebug( "NAS",
              "PrescanForTemplate() not currently implemented for NAS." );
    return false;
}

/************************************************************************/
/*                           ResolveXlinks()                            */
/*      Returns true for success                                        */
/************************************************************************/

bool NASReader::ResolveXlinks( const char * /*pszFile */,
                               bool* /*pbOutIsTempFile */,
                               char ** /*papszSkip */,
                               const bool /*bStrict */ )
{
    CPLDebug( "NAS", "ResolveXlinks() not currently implemented for NAS." );
    return false;
}

/************************************************************************/
/*                       SetFilteredClassName()                         */
/************************************************************************/

bool NASReader::SetFilteredClassName(const char* pszClassName)
{
    CPLFree(m_pszFilteredClassName);
    m_pszFilteredClassName = (pszClassName) ? CPLStrdup(pszClassName) : NULL;
    return true;
}

/************************************************************************/
/*                         ConvertGeometry()                            */
/************************************************************************/

OGRGeometry*  NASReader::ConvertGeometry(OGRGeometry* poGeom)
{
    //poGeom = OGRGeometryFactory::forceToLineString( poGeom, false );
    if( poGeom != NULL )
    {
        if( wkbFlatten(poGeom->getGeometryType()) == wkbMultiLineString )
        {
            poGeom = OGRGeometryFactory::forceTo(poGeom, wkbLineString);
        }
    }
    return poGeom;
}
