/**********************************************************************
 * $Id: avc_e00read.cpp 8ca42e1b9c2e54b75d35e49885df9789a2643aa4 2020-05-17 21:43:40 +0200 Even Rouault $
 *
 * Name:     avc_e00read.c
 * Project:  Arc/Info vector coverage (AVC)  BIN->E00 conversion library
 * Language: ANSI C
 * Purpose:  Functions to open a binary coverage and read it as if it
 *           was an ASCII E00 file.  This file is the main entry point
 *           for the library.
 * Author:   Daniel Morissette, dmorissette@dmsolutions.ca
 *
 **********************************************************************
 * Copyright (c) 1999-2005, Daniel Morissette
 *
 * 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.
 **********************************************************************
 *
 * $Log: avc_e00read.c,v $
 * Revision 1.28  2008/07/30 19:22:18  dmorissette
 * Move detection of EXP header directly in AVCE00ReadOpenE00() and use
 * VSIFGets() instead of CPLReadLine() to avoid problem with huge one line
 * files (GDAL/OGR ticket #1989)
 *
 * Revision 1.27  2008/07/30 18:35:53  dmorissette
 * Avoid scanning the whole E00 input file in AVCE00ReadOpenE00() if the
 * file does not start with an EXP line (GDAL/OGR ticket 1989)
 *
 * Revision 1.26  2008/07/30 16:17:46  dmorissette
 * Detect compressed E00 input files and refuse to open them instead of
 * crashing (bug 1928, GDAL/OGR ticket 2513)
 *
 * Revision 1.25  2008/07/24 20:34:12  dmorissette
 * Fixed VC++ WIN32 build problems in GDAL/OGR environment
 * (GDAL/OGR ticket http://trac.osgeo.org/gdal/ticket/2500)
 *
 * Revision 1.24  2008/07/24 13:49:20  dmorissette
 * Fixed GCC compiler warning (GDAL ticket #2495)
 *
 * Revision 1.23  2006/08/17 19:51:01  dmorissette
 * #include <unistd.h> to solve warning on 64 bit platforms (bug 1461)
 *
 * Revision 1.22  2006/08/17 18:56:42  dmorissette
 * Support for reading standalone info tables (just tables, no coverage
 * data) by pointing AVCE00ReadOpen() to the info directory (bug 1549).
 *
 * Revision 1.21  2006/06/27 18:38:43  dmorissette
 * Cleaned up E00 reading (bug 1497, patch from James F.)
 *
 * Revision 1.20  2006/06/27 18:06:34  dmorissette
 * Applied patch for EOP processing from James F. (bug 1497)
 *
 * Revision 1.19  2006/06/16 11:48:11  daniel
 * New functions to read E00 files directly as opposed to translating to
 * binary coverage. Used in the implementation of E00 read support in OGR.
 * Contributed by James E. Flemer. (bug 1497)
 *
 * Revision 1.18  2006/06/14 16:31:28  daniel
 * Added support for AVCCoverPC2 type (bug 1491)
 *
 * Revision 1.17  2005/06/03 03:49:58  daniel
 * Update email address, website url, and copyright dates
 *
 * Revision 1.16  2004/07/14 18:49:50  daniel
 * Fixed leak when trying to open something that's not a coverage (bug513)
 *
 * Revision 1.15  2002/08/27 15:46:15  daniel
 * Applied fix made in GDAL/OGR by 'aubin' (moved include ctype.h after avc.h)
 *
 * Revision 1.14  2000/09/22 19:45:21  daniel
 * Switch to MIT-style license
 *
 * Revision 1.13  2000/05/29 15:31:31  daniel
 * Added Japanese DBCS support
 *
 * Revision 1.12  2000/02/14 17:21:01  daniel
 * Made more robust for corrupted or invalid files in cover directory
 *
 * Revision 1.11  2000/02/02 04:26:04  daniel
 * Support reading TX6/TX7/RXP/RPL files in weird coverages
 *
 * Revision 1.10  2000/01/10 02:56:30  daniel
 * Added read support for "weird" coverages
 *
 * Revision 1.9  2000/01/07 07:12:49  daniel
 * Added support for reading PC Coverage TXT files
 *
 * Revision 1.8  1999/12/24 07:41:08  daniel
 * Check fname length before testing for extension in AVCE00ReadFindCoverType()
 *
 * Revision 1.7  1999/12/24 07:18:34  daniel
 * Added PC Arc/Info coverages support
 *
 * Revision 1.6  1999/08/26 17:22:18  daniel
 * Use VSIFopen() instead of fopen() directly
 *
 * Revision 1.5  1999/08/23 18:21:41  daniel
 * New syntax for AVCBinReadListTables()
 *
 * Revision 1.4  1999/05/11 02:10:01  daniel
 * Free psInfo struct inside AVCE00ReadClose()
 *
 * Revision 1.3  1999/04/06 19:43:26  daniel
 * Added E00 coverage path in EXP 0  header line
 *
 * Revision 1.2  1999/02/25 04:19:01  daniel
 * Added TXT, TX6/TX7, RXP and RPL support + other minor changes
 *
 * Revision 1.1  1999/01/29 16:28:52  daniel
 * Initial revision
 *
 **********************************************************************/

#include "avc.h"

#ifdef WIN32
#  include <direct.h>   /* getcwd() */
#else
#  include <unistd.h>   /* getcwd() */
#endif

#include <ctype.h>      /* toupper() */

// Should be 80 but let's be laxer
constexpr int knMAX_CHARS_PER_LINE = 1024;

static void _AVCE00ReadScanE00(AVCE00ReadE00Ptr psRead);
static int _AVCE00ReadBuildSqueleton(AVCE00ReadPtr psInfo,
                                     char **papszCoverDir);
static AVCCoverType _AVCE00ReadFindCoverType(char **papszCoverDir);


/**********************************************************************
 *                          AVCE00ReadOpen()
 *
 * Open a Arc/Info coverage to read it as if it was an E00 file.
 *
 * You can either pass the name of the coverage directory, or the path
 * to one of the files in the coverage directory.  The name of the
 * coverage MUST be included in pszCoverPath... this means that
 * passing "." is invalid.
 * The following are all valid values for pszCoverPath:
 *               /home/data/country
 *               /home/data/country/
 *               /home/data/country/arc.adf
 * (Of course you should replace the '/' with '\\' on DOS systems!)
 *
 * Returns a new AVCE00ReadPtr handle or nullptr if the coverage could
 * not be opened or if it does not appear to be a valid Arc/Info coverage.
 *
 * The handle will eventually have to be released with AVCE00ReadClose().
 **********************************************************************/
AVCE00ReadPtr  AVCE00ReadOpen(const char *pszCoverPath)
{
    AVCE00ReadPtr   psInfo;
    int             i, nLen, nCoverPrecision;
    VSIStatBufL     sStatBuf;
    char            **papszCoverDir = nullptr;

    CPLErrorReset();

    /*-----------------------------------------------------------------
     * pszCoverPath must be either a valid directory name or a valid
     * file name.
     *----------------------------------------------------------------*/
    if (pszCoverPath == nullptr || strlen(pszCoverPath) == 0 ||
        VSIStatL(pszCoverPath, &sStatBuf) == -1)
    {
        CPLError(CE_Failure, CPLE_OpenFailed,
                 "Invalid coverage path: %s.",
                 pszCoverPath?pszCoverPath:"(nullptr)");
        return nullptr;
    }

    /*-----------------------------------------------------------------
     * Alloc the AVCE00ReadPtr handle
     *----------------------------------------------------------------*/
    psInfo = (AVCE00ReadPtr)CPLCalloc(1, sizeof(struct AVCE00ReadInfo_t));

    /*-----------------------------------------------------------------
     * 2 possibilities about the value passed in pszCoverPath:
     * - It can be the directory name of the coverage
     * - or it can be the path to one of the files in the coverage
     *
     * If the name passed in pszCoverPath is not a directory, then we
     * need to strip the last part of the filename to keep only the
     * path, terminated by a '/' (or a '\\').
     *----------------------------------------------------------------*/
    if (VSI_ISDIR(sStatBuf.st_mode))
    {
        /*-------------------------------------------------------------
         * OK, we have a valid directory name... make sure it is
         * terminated with a '/' (or '\\')
         *------------------------------------------------------------*/
        nLen = (int)strlen(pszCoverPath);

        if (pszCoverPath[nLen-1] == '/' || pszCoverPath[nLen-1] == '\\')
            psInfo->pszCoverPath = CPLStrdup(pszCoverPath);
        else
        {
#ifdef WIN32
            psInfo->pszCoverPath = CPLStrdup(CPLSPrintf("%s\\",pszCoverPath));
#else
            psInfo->pszCoverPath = CPLStrdup(CPLSPrintf("%s/",pszCoverPath));
#endif
        }
    }
    else
    {
        /*-------------------------------------------------------------
         * We are dealing with a filename.
         * Extract the coverage path component and store it.
         * The coverage path will remain terminated by a '/' or '\\' char.
         *------------------------------------------------------------*/
        psInfo->pszCoverPath = CPLStrdup(pszCoverPath);

        for( i = (int)strlen(psInfo->pszCoverPath)-1;
             i > 0 && psInfo->pszCoverPath[i] != '/' &&
                 psInfo->pszCoverPath[i] != '\\';
             i-- ) {}

        psInfo->pszCoverPath[i+1] = '\0';
    }


    /*-----------------------------------------------------------------
     * Extract the coverage name from the coverage path.  Note that
     * for this the coverage path must be in the form:
     * "dir1/dir2/dir3/covername/" ... if it is not the case, then
     * we would have to use getcwd() to find the current directory name...
     * but for now we'll just produce an error if this happens.
     *----------------------------------------------------------------*/
    nLen = 0;
    for( i = (int)strlen(psInfo->pszCoverPath)-1;
        i > 0 && psInfo->pszCoverPath[i-1] != '/' &&
                psInfo->pszCoverPath[i-1] != '\\'&&
                psInfo->pszCoverPath[i-1] != ':';
        i-- )
    {
        nLen++;
    }

    if (nLen > 0)
    {
        psInfo->pszCoverName = CPLStrdup(psInfo->pszCoverPath+i);
        psInfo->pszCoverName[nLen] = '\0';
    }
    else
    {
        CPLError(CE_Failure, CPLE_OpenFailed,
                 "Invalid coverage path (%s): "
                 "coverage name must be included in path.", pszCoverPath);

        CPLFree(psInfo->pszCoverPath);
        CPLFree(psInfo);
        return nullptr;
    }

    /*-----------------------------------------------------------------
     * Read the coverage directory listing and try to establish the cover type
     *----------------------------------------------------------------*/
    papszCoverDir = VSIReadDir(psInfo->pszCoverPath);

    psInfo->eCoverType = _AVCE00ReadFindCoverType(papszCoverDir);

    if (psInfo->eCoverType == AVCCoverTypeUnknown  )
    {
        CPLError(CE_Failure, CPLE_OpenFailed,
                 "Invalid coverage (%s): directory does not appear to "
                 "contain any supported vector coverage file.",  pszCoverPath);
        CPLFree(psInfo->pszCoverName);
        CPLFree(psInfo->pszCoverPath);
        CPLFree(psInfo->pszInfoPath);
        CPLFree(psInfo);
        CSLDestroy(papszCoverDir);
        return nullptr;
    }

    /*-----------------------------------------------------------------
     * INFO path: PC Coverages have all files in the same dir, and unix
     * covers have the INFO files in ../info
     *----------------------------------------------------------------*/
    if (psInfo->eCoverType == AVCCoverPC || psInfo->eCoverType == AVCCoverPC2)
    {
        psInfo->pszInfoPath = CPLStrdup(psInfo->pszCoverPath);
    }
    else
    {
        /*-------------------------------------------------------------
         * Lazy way to build the INFO path: simply add "../info/"...
         * this could probably be improved!
         *------------------------------------------------------------*/
        size_t nInfoPathLen = strlen(psInfo->pszCoverPath)+9;
        psInfo->pszInfoPath =(char*)CPLMalloc(nInfoPathLen);
#ifdef WIN32
#  define AVC_INFOPATH "..\\info\\"
#else
#  define AVC_INFOPATH "../info/"
#endif
        snprintf(psInfo->pszInfoPath, nInfoPathLen, "%s%s", psInfo->pszCoverPath,
                                             AVC_INFOPATH);

        AVCAdjustCaseSensitiveFilename(psInfo->pszInfoPath);
    }

    /*-----------------------------------------------------------------
     * For Unix coverages, check that the info directory exists and
     * contains the "arc.dir".  In AVCCoverWeird, the arc.dir is
     * called "../INFO/ARCDR9".
     * PC Coverages have their info tables in the same directory as
     * the coverage files.
     *----------------------------------------------------------------*/
    if (((psInfo->eCoverType == AVCCoverV7 ||
          psInfo->eCoverType == AVCCoverV7Tables) &&
         ! AVCFileExists(psInfo->pszInfoPath, "arc.dir") ) ||
         (psInfo->eCoverType == AVCCoverWeird &&
         ! AVCFileExists(psInfo->pszInfoPath, "arcdr9") ) )
    {
        CPLError(CE_Failure, CPLE_OpenFailed,
             "Invalid coverage (%s): 'info' directory not found or invalid.",
                                              pszCoverPath);
        CPLFree(psInfo->pszCoverName);
        CPLFree(psInfo->pszCoverPath);
        CPLFree(psInfo->pszInfoPath);
        CPLFree(psInfo);
        CSLDestroy(papszCoverDir);
        return nullptr;
    }

    /*-----------------------------------------------------------------
     * Make sure there was no error until now before we build skeleton.
     *----------------------------------------------------------------*/
    if (CPLGetLastErrorNo() != 0)
    {
        CPLFree(psInfo->pszCoverName);
        CPLFree(psInfo->pszCoverPath);
        CPLFree(psInfo->pszInfoPath);
        CPLFree(psInfo);
        CSLDestroy(papszCoverDir);
        return nullptr;
    }

    /*-----------------------------------------------------------------
     * Build the E00 file skeleton and be ready to return a E00 header...
     * We'll also read the coverage precision by the same way.
     *----------------------------------------------------------------*/
    nCoverPrecision = _AVCE00ReadBuildSqueleton(psInfo, papszCoverDir);

    /* Ignore warnings produced while building skeleton */
    CPLErrorReset();

    CSLDestroy(papszCoverDir);
    papszCoverDir = nullptr;

    psInfo->iCurSection = 0;
    psInfo->iCurStep = AVC_GEN_NOTSTARTED;
    psInfo->bReadAllSections = TRUE;

    /*-----------------------------------------------------------------
     * Init the E00 generator.
     *----------------------------------------------------------------*/
    psInfo->hGenInfo = AVCE00GenInfoAlloc(nCoverPrecision);

    /*-----------------------------------------------------------------
     * Init multibyte encoding info
     *----------------------------------------------------------------*/
    psInfo->psDBCSInfo = AVCAllocDBCSInfo();

    /*-----------------------------------------------------------------
     * If an error happened during the open call, cleanup and return nullptr.
     *----------------------------------------------------------------*/
    if (CPLGetLastErrorNo() != 0)
    {
        AVCE00ReadClose(psInfo);
        psInfo = nullptr;
    }

    return psInfo;
}

/**********************************************************************
 *                          AVCE00ReadOpenE00()
 *
 * Open a E00 file for reading.
 *
 * Returns a new AVCE00ReadE00Ptr handle or nullptr if the file could
 * not be opened or if it does not appear to be a valid E00 file.
 *
 * The handle will eventually have to be released with
 * AVCE00ReadCloseE00().
 **********************************************************************/
AVCE00ReadE00Ptr AVCE00ReadOpenE00(const char *pszE00FileName)
{
    AVCE00ReadE00Ptr psRead;
    VSIStatBufL      sStatBuf;
    VSILFILE        *fp;
    char             *p;
    char             szHeader[10];

    CPLErrorReset();

    /*-----------------------------------------------------------------
     * pszE00FileName must be a valid file that can be opened for
     * reading
     *----------------------------------------------------------------*/
    if (pszE00FileName == nullptr || strlen(pszE00FileName) == 0 ||
        VSIStatL(pszE00FileName, &sStatBuf) == -1 ||
        VSI_ISDIR(sStatBuf.st_mode))
    {
        CPLError(CE_Failure, CPLE_OpenFailed,
                 "Invalid E00 file path: %s.",
                 pszE00FileName?pszE00FileName:"(nullptr)");
        return nullptr;
    }

    if (nullptr == (fp = VSIFOpenL(pszE00FileName, "r")))
        return nullptr;

    /*-----------------------------------------------------------------
     * Make sure the file starts with a "EXP  0" or "EXP  1" header
     *----------------------------------------------------------------*/
    memset(szHeader, 0, sizeof(szHeader));
    if (VSIFReadL(szHeader, 5, 1, fp) == 0 || !STARTS_WITH_CI(szHeader, "EXP ") )
    {
        CPLError(CE_Failure, CPLE_OpenFailed,
                 "This does not look like a E00 file: does not start with "
                 "a EXP header." );
        VSIFCloseL(fp);
        return nullptr;
    }
    VSIRewindL(fp);

    /*-----------------------------------------------------------------
     * Alloc the AVCE00ReadE00Ptr handle
     *----------------------------------------------------------------*/
    psRead = (AVCE00ReadE00Ptr)CPLCalloc(1,
            sizeof(struct AVCE00ReadInfoE00_t));

    psRead->hFile = fp;
    psRead->pszCoverPath = CPLStrdup(pszE00FileName);
    psRead->eCurFileType = AVCFileUnknown;

    /*-----------------------------------------------------------------
     * Extract the coverage name from the coverage path.
     *----------------------------------------------------------------*/
    if (nullptr != (p = strrchr(psRead->pszCoverPath, '/')) ||
        nullptr != (p = strrchr(psRead->pszCoverPath, '\\')) ||
        nullptr != (p = strrchr(psRead->pszCoverPath, ':')))
    {
        psRead->pszCoverName = CPLStrdup(p + 1);
    }
    else
    {
        psRead->pszCoverName = CPLStrdup(psRead->pszCoverPath);
    }
    if (nullptr != (p = strrchr(psRead->pszCoverName, '.')))
    {
        *p = '\0';
    }

    /*-----------------------------------------------------------------
     * Make sure there was no error until now before we scan file.
     *----------------------------------------------------------------*/
    if (CPLGetLastErrorNo() != 0)
    {
        AVCE00ReadCloseE00(psRead);
        return nullptr;
    }

    psRead->hParseInfo = AVCE00ParseInfoAlloc();

    /*-----------------------------------------------------------------
     * Scan the E00 file for sections
     *----------------------------------------------------------------*/
    _AVCE00ReadScanE00(psRead);
    if (CPLGetLastErrorNo() != 0)
    {
        AVCE00ReadCloseE00(psRead);
        return nullptr;
    }

    AVCE00ReadRewindE00(psRead);
    CPLErrorReset();

    if (psRead->numSections < 1)
    {
        AVCE00ReadCloseE00(psRead);
        return nullptr;
    }

    psRead->bReadAllSections = TRUE;

    /*-----------------------------------------------------------------
     * If an error happened during the open call, cleanup and return nullptr.
     *----------------------------------------------------------------*/
    if (CPLGetLastErrorNo() != 0)
    {
        AVCE00ReadCloseE00(psRead);
        psRead = nullptr;
    }

    return psRead;
}

/**********************************************************************
 *                          AVCE00ReadClose()
 *
 * Close a coverage and release all memory used by the AVCE00ReadPtr
 * handle.
 **********************************************************************/
void AVCE00ReadClose(AVCE00ReadPtr psInfo)
{
    CPLErrorReset();

    if (psInfo == nullptr)
        return;

    CPLFree(psInfo->pszCoverPath);
    CPLFree(psInfo->pszInfoPath);
    CPLFree(psInfo->pszCoverName);

    if (psInfo->hFile)
        AVCBinReadClose(psInfo->hFile);

    if (psInfo->hGenInfo)
        AVCE00GenInfoFree(psInfo->hGenInfo);

    if (psInfo->pasSections)
    {
        int i;
        for(i=0; i<psInfo->numSections; i++)
        {
            CPLFree(psInfo->pasSections[i].pszName);
            CPLFree(psInfo->pasSections[i].pszFilename);
        }
        CPLFree(psInfo->pasSections);
    }

    AVCFreeDBCSInfo(psInfo->psDBCSInfo);

    CPLFree(psInfo);
}

/**********************************************************************
 *                          AVCE00ReadCloseE00()
 *
 * Close a coverage and release all memory used by the AVCE00ReadE00Ptr
 * handle.
 **********************************************************************/
void AVCE00ReadCloseE00(AVCE00ReadE00Ptr psRead)
{
    if (psRead == nullptr)
        return;

    CPLFree(psRead->pszCoverPath);
    CPLFree(psRead->pszCoverName);

    if (psRead->hFile)
    {
        VSIFCloseL(psRead->hFile);
        psRead->hFile = nullptr;
    }

    if (psRead->pasSections)
    {
        int i;
        for(i=0; i<psRead->numSections; i++)
        {
            CPLFree(psRead->pasSections[i].pszName);
            CPLFree(psRead->pasSections[i].pszFilename);
        }
        CPLFree(psRead->pasSections);
    }

    /* These Free calls handle nullptr's */
    AVCE00ParseInfoFree(psRead->hParseInfo);
    psRead->hParseInfo = nullptr;

    CPLFree(psRead);
}


/**********************************************************************
 *                          _AVCIncreaseSectionsArray()
 *
 * Add a number of structures to the Sections array and return the
 * index of the first one that was added.  Note that the address of the
 * original array (*pasArray) is quite likely to change!
 *
 * The value of *pnumItems will be updated to reflect the new array size.
 **********************************************************************/
static int _AVCIncreaseSectionsArray(AVCE00Section **pasArray, int *pnumItems,
                                    int numToAdd)
{
    int i;

    *pasArray = (AVCE00Section*)CPLRealloc(*pasArray,
                                           (*pnumItems+numToAdd)*
                                                    sizeof(AVCE00Section));

    for(i=0; i<numToAdd; i++)
    {
        (*pasArray)[*pnumItems+i].eType = AVCFileUnknown;
        (*pasArray)[*pnumItems+i].pszName = nullptr;
        (*pasArray)[*pnumItems+i].pszFilename = nullptr;
        (*pasArray)[*pnumItems+i].nLineNum = 0;
        (*pasArray)[*pnumItems+i].nFeatureCount = -1;
    }

    i = *pnumItems;
    (*pnumItems) += numToAdd;

    return i;
}

/**********************************************************************
 *                         _AVCE00ReadFindCoverType()
 *
 * This functions tries to establish the coverage type by looking
 * at the coverage directory listing passed as argument.
 *
 * Returns one of AVCCoverV7 for Arc/Info V7 (Unix) coverages, or
 *                AVCCoverPC for PC Arc/Info coverages.
 *                AVCCoverWeird for an hybrid between V7 and PC
 *
 * If coverage type cannot be established then AVCCoverTypeUnknown is
 * returned.
 **********************************************************************/
static AVCCoverType _AVCE00ReadFindCoverType(char **papszCoverDir)
{
    int         i, nLen;
    GBool       bFoundAdfFile=FALSE, bFoundArcFile=FALSE,
                bFoundTableFile=FALSE, bFoundDbfFile=FALSE,
                bFoundArcDirFile=FALSE;

    /*-----------------------------------------------------------------
     * Scan the list of files, looking for well known filenames.
     * Start with the funky types first...
     *----------------------------------------------------------------*/
    for(i=0; papszCoverDir && papszCoverDir[i]; i++)
    {
        nLen = (int)strlen(papszCoverDir[i]);
        if (nLen > 4 && EQUAL(papszCoverDir[i]+nLen-4, ".adf") )
        {
            bFoundAdfFile = TRUE;
        }
        else if (nLen > 4 && EQUAL(papszCoverDir[i]+nLen-4, ".dbf") )
        {
            bFoundDbfFile = TRUE;
        }
        else if (EQUAL(papszCoverDir[i], "arc") ||
                 EQUAL(papszCoverDir[i], "cnt") ||
                 EQUAL(papszCoverDir[i], "pal") ||
                 EQUAL(papszCoverDir[i], "lab") ||
                 EQUAL(papszCoverDir[i], "prj") ||
                 EQUAL(papszCoverDir[i], "tol") )
        {
            bFoundArcFile = TRUE;
        }
        else if (EQUAL(papszCoverDir[i], "aat") ||
                 EQUAL(papszCoverDir[i], "pat") ||
                 EQUAL(papszCoverDir[i], "bnd") ||
                 EQUAL(papszCoverDir[i], "tic") )
        {
            bFoundTableFile = TRUE;
        }
        else if (EQUAL(papszCoverDir[i], "arc.dir") )
        {
            bFoundArcDirFile = TRUE;
        }

    }

    /*-----------------------------------------------------------------
     * Check for PC Arc/Info coverage - variant 1.
     * These PC coverages have files with no extension (e.g. "ARC","PAL",...)
     * and their tables filenames are in the form "???.dbf"
     *----------------------------------------------------------------*/
    if (bFoundArcFile && bFoundDbfFile)
        return AVCCoverPC;

    /*-----------------------------------------------------------------
     * Check for PC Arc/Info coverage - variant 2.
     * looks like a hybrid between AVCCoverPC and AVCCoverV7
     * These PC coverages have files with .adf extension (e.g."ARC.ADF"),
     * and their tables filenames are in the form "???.dbf"
     *----------------------------------------------------------------*/
    if (bFoundAdfFile && bFoundDbfFile)
        return AVCCoverPC2;

    /*-----------------------------------------------------------------
     * Check for the weird coverages.
     * Their coverage files have no extension just like PC Coverages,
     * and their tables have 3 letters filenames with no extension
     * either (e.g. "AAT", "PAT", etc.)
     * They also have a ../info directory, but we don't really need
     * to check that (not yet!).
     *----------------------------------------------------------------*/
    if (bFoundArcFile && bFoundTableFile)
        return AVCCoverWeird;

    /*-----------------------------------------------------------------
     * V7 Coverages... they are the easiest to recognize
     * because of the ".adf" file extension
     *----------------------------------------------------------------*/
    if (bFoundAdfFile)
        return AVCCoverV7;

    /*-----------------------------------------------------------------
     * Standalone info tables.
     * We were pointed at the "info" directory. We'll treat this as
     * a coverage with just info tables.
     *----------------------------------------------------------------*/
    if (bFoundArcDirFile)
        return AVCCoverV7Tables;

    return AVCCoverTypeUnknown;
}


/**********************************************************************
 *                         _AVCE00ReadAddJabberwockySection()
 *
 * Add to the skeleton a section that contains subsections
 * for all the files with a given extension.
 *
 * Returns Updated Coverage precision
 **********************************************************************/
static int _AVCE00ReadAddJabberwockySection(AVCE00ReadPtr psInfo,
                                            AVCFileType   eFileType,
                                            const char   *pszSectionName,
                                            int           nCoverPrecision,
                                            const char   *pszFileExtension,
                                            char        **papszCoverDir )
{
    int         iSect, iDirEntry, nLen, nExtLen;
    GBool       bFoundFiles = FALSE;
    AVCBinFile *psFile=nullptr;

    nExtLen = (int)strlen(pszFileExtension);

    /*-----------------------------------------------------------------
     * Scan the directory for files with a ".txt" extension.
     *----------------------------------------------------------------*/

    for (iDirEntry=0; papszCoverDir && papszCoverDir[iDirEntry]; iDirEntry++)
    {
        nLen = (int)strlen(papszCoverDir[iDirEntry]);

        if (nLen > nExtLen && EQUAL(papszCoverDir[iDirEntry] + nLen-nExtLen,
                                    pszFileExtension) &&
            (psFile = AVCBinReadOpen(psInfo->pszCoverPath,
                                     papszCoverDir[iDirEntry],
                                     psInfo->eCoverType, eFileType,
                                     psInfo->psDBCSInfo)) != nullptr)
        {
            if (nCoverPrecision == AVC_DEFAULT_PREC)
                nCoverPrecision = psFile->nPrecision;
            AVCBinReadClose(psFile);

            if (bFoundFiles == FALSE)
            {
                /* Insert a "TX6 #" header before the first TX6 file
                 */
                iSect = _AVCIncreaseSectionsArray(&(psInfo->pasSections),
                                                  &(psInfo->numSections), 1);
                psInfo->pasSections[iSect].eType = AVCFileUnknown;

                psInfo->pasSections[iSect].pszName =
                            CPLStrdup(CPLSPrintf("%s  %c", pszSectionName,
                                  (nCoverPrecision==AVC_DOUBLE_PREC)?'3':'2'));

                bFoundFiles = TRUE;
            }

            /* Add this file to the skeleton
             */
            iSect = _AVCIncreaseSectionsArray(&(psInfo->pasSections),
                                              &(psInfo->numSections), 1);

            psInfo->pasSections[iSect].eType = eFileType;
            psInfo->pasSections[iSect].pszFilename=
                                   CPLStrdup(papszCoverDir[iDirEntry]);

            /* pszName will contain only the classname without the file
             * extension */
            psInfo->pasSections[iSect].pszName =
                                   CPLStrdup(papszCoverDir[iDirEntry]);
            psInfo->pasSections[iSect].pszName[nLen-nExtLen] = '\0';
        }
    }

    if (bFoundFiles)
    {
        /* Add a line to close the TX6 section.
         */
        iSect = _AVCIncreaseSectionsArray(&(psInfo->pasSections),
                                          &(psInfo->numSections), 1);
        psInfo->pasSections[iSect].eType = AVCFileUnknown;
        psInfo->pasSections[iSect].pszName = CPLStrdup("JABBERWOCKY");
    }

    return nCoverPrecision;
}

/**********************************************************************
 *                     _AVCE00ReadNextLineE00()
 *
 * Processes the next line of input from the E00 file.
 * (See AVCE00WriteNextLine() for similar processing.)
 *
 * Returns the next object from the E00 file, or nullptr.
 **********************************************************************/
static void *_AVCE00ReadNextLineE00(AVCE00ReadE00Ptr psRead,
        const char *pszLine)
{
    /* int nStatus = 0; */
    void *psObj = nullptr;

    AVCE00ParseInfo *psInfo = psRead->hParseInfo;

    CPLErrorReset();

    ++psInfo->nCurLineNum;

    if (psInfo->bForceEndOfSection)
    {
        /*-------------------------------------------------------------
         * The last call encountered an implicit end of section, so
         * we close the section now without waiting for an end-of-section
         * line (there won't be any!)... and get ready to proceed with
         * the next section.
         * This is used for TABLEs.
         *------------------------------------------------------------*/
        AVCE00ParseSectionEnd(psInfo, pszLine, TRUE);
        psRead->eCurFileType = AVCFileUnknown;
    }

    /*-----------------------------------------------------------------
     * If we're at the top level inside a supersection... check if this
     * supersection ends here.
     *----------------------------------------------------------------*/
    if (AVCE00ParseSuperSectionEnd(psInfo, pszLine) == TRUE)
    {
        /* Nothing to do... it is all been done by the call to
         * AVCE00ParseSuperSectionEnd()
         */
    }
    else if (psRead->eCurFileType == AVCFileUnknown)
    {
        /*-------------------------------------------------------------
         * We're at the top level or inside a supersection... waiting
         * to encounter a valid section or supersection header
         * (i.e. "ARC  2", etc...)
         *------------------------------------------------------------*/

        /*-------------------------------------------------------------
         * First check for a supersection header (TX6, RXP, IFO, ...)
         *------------------------------------------------------------*/
        if ( AVCE00ParseSuperSectionHeader(psInfo,
                                           pszLine) == AVCFileUnknown )
        {
            /*---------------------------------------------------------
             * This was not a supersection header... check if it is a simple
             * section header
             *--------------------------------------------------------*/
            psRead->eCurFileType = AVCE00ParseSectionHeader(psInfo,
                    pszLine);
        }
        else
        {
            /* got supersection */
        }

        if (psRead->eCurFileType == AVCFileTABLE)
        {
            /*---------------------------------------------------------
             * send the first header line to the parser and wait until
             * the whole header has been read.
             *--------------------------------------------------------*/
            AVCE00ParseNextLine(psInfo, pszLine);
        }
        else if (psRead->eCurFileType != AVCFileUnknown)
        {
            /*---------------------------------------------------------
             * found a valid section header
             *--------------------------------------------------------*/
        }
    }
    else if (psRead->eCurFileType == AVCFileTABLE &&
             ! psInfo->bTableHdrComplete )
    {
        /*-------------------------------------------------------------
         * We're reading a TABLE header... continue reading lines
         * from the header
         *
         * Note: When parsing a TABLE, the first object returned will
         * be the AVCTableDef, then data records will follow.
         *------------------------------------------------------------*/
        psObj = AVCE00ParseNextLine(psInfo, pszLine);
        if (psObj)
        {
            /* got table header */
            /* TODO: Enable return of table definition? */
            psObj = nullptr;
        }
    }
    else
    {
        /*-------------------------------------------------------------
         * We're are in the middle of a section... first check if we
         * have reached the end.
         *
         * note: The first call to AVCE00ParseSectionEnd() with FALSE will
         *       not reset the parser until we close the file... and then
         *       we call the function again to reset the parser.
         *------------------------------------------------------------*/
        if (AVCE00ParseSectionEnd(psInfo, pszLine, FALSE))
        {
            psRead->eCurFileType = AVCFileUnknown;
            AVCE00ParseSectionEnd(psInfo, pszLine, TRUE);
        }
        else
        /*-------------------------------------------------------------
         * ... not at the end yet, so continue reading objects.
         *------------------------------------------------------------*/
        {
            psObj = AVCE00ParseNextLine(psInfo, pszLine);

            if (psObj)
            {
                /* got object */
            }
        }
    }

#if 0
    if (CPLGetLastErrorNo() != 0)
        nStatus = -1;
#endif

    return psObj;
}

/**********************************************************************
 *                         _AVCE00ReadBuildSqueleton()
 *
 * Build the skeleton of the E00 file corresponding to the specified
 * coverage and set the appropriate fields in the AVCE00ReadPtr struct.
 *
 * Note that the order of the sections in the skeleton is important
 * since some software may rely on this ordering when they read E00 files.
 *
 * The function returns the coverage precision that it will read from one
 * of the file headers.
 **********************************************************************/
static int _AVCE00ReadBuildSqueleton(AVCE00ReadPtr psInfo,
                                     char **papszCoverDir)
{
    int         iTable, numTables, iFile, nLen;
    char      **papszTables, **papszFiles, szCWD[75]="", *pcTmp;
    char       *pszEXPPath=nullptr;
    int         nCoverPrecision = AVC_DEFAULT_PREC;
    char        cPrecisionCode = '2';
    const char *szFname = nullptr;
    AVCBinFile *psFile=nullptr;

    psInfo->numSections = 0;
    psInfo->pasSections = nullptr;

    /*-----------------------------------------------------------------
     * Build the absolute coverage path to include in the EXP  0 line
     * This line usually contains the full path of the E00 file that
     * is being created, but since the lib does not write the output
     * file directly, there is no simple way to get that value.  Instead,
     * we will use the absolute coverage path to which we add a .E00
     * extension.
     * We need also make sure cover path is all in uppercase.
     *----------------------------------------------------------------*/
#ifdef WIN32
    if (psInfo->pszCoverPath[0] != '\\' &&
        !(isalpha(psInfo->pszCoverPath[0]) && psInfo->pszCoverPath[1] == ':'))
#else
    if (psInfo->pszCoverPath[0] != '/')
#endif
    {
        if (getcwd(szCWD, 74) == nullptr)
            szCWD[0] = '\0';    /* Failed: buffer may be too small */

        nLen = (int)strlen(szCWD);

#ifdef WIN32
        if (nLen > 0 && szCWD[nLen -1] != '\\')
            strcat(szCWD, "\\");
#else
        if (nLen > 0 && szCWD[nLen -1] != '/')
            strcat(szCWD, "/");
#endif
    }

    CPLString osCoverPathTruncated(psInfo->pszCoverPath);
    osCoverPathTruncated.resize(osCoverPathTruncated.size()-1);
    pszEXPPath = CPLStrdup(CPLSPrintf("EXP  0 %s%s.E00", szCWD,
                                      osCoverPathTruncated.c_str()));
    pcTmp = pszEXPPath;
    for( ; *pcTmp != '\0'; pcTmp++)
        *pcTmp = (char) toupper(*pcTmp);

    /*-----------------------------------------------------------------
     * EXP Header
     *----------------------------------------------------------------*/
    {
    const int iSect = _AVCIncreaseSectionsArray(&(psInfo->pasSections),
                                  &(psInfo->numSections), 1);
    psInfo->pasSections[iSect].eType = AVCFileUnknown;
    psInfo->pasSections[iSect].pszName = pszEXPPath;
    }

    /*-----------------------------------------------------------------
     * We have to try to open each file as we go for 2 reasons:
     * - To validate the file's signature in order to detect cases like a user
     *   that places files such as "mystuff.txt" in the cover directory...
     *   this has already happened and obviously lead to problems!)
     * - We also need to find the coverage's precision from the headers
     *----------------------------------------------------------------*/

    /*-----------------------------------------------------------------
     * ARC section (arc.adf)
     *----------------------------------------------------------------*/
    szFname = (psInfo->eCoverType==AVCCoverV7 ||
               psInfo->eCoverType==AVCCoverPC2 ) ? "arc.adf": "arc";
    if ( (iFile=CSLFindString(papszCoverDir, szFname)) != -1 &&
         (psFile = AVCBinReadOpen(psInfo->pszCoverPath, szFname,
                                  psInfo->eCoverType, AVCFileARC,
                                  psInfo->psDBCSInfo)) != nullptr)
    {
        if (nCoverPrecision == AVC_DEFAULT_PREC)
            nCoverPrecision = psFile->nPrecision;
        AVCBinReadClose(psFile);
        const int iSect = _AVCIncreaseSectionsArray(&(psInfo->pasSections),
                                      &(psInfo->numSections), 1);

        psInfo->pasSections[iSect].eType = AVCFileARC;
        psInfo->pasSections[iSect].pszName = CPLStrdup("ARC");
        psInfo->pasSections[iSect].pszFilename=CPLStrdup(papszCoverDir[iFile]);
    }

    /*-----------------------------------------------------------------
     * CNT section (cnt.adf)
     *----------------------------------------------------------------*/
    szFname = (psInfo->eCoverType==AVCCoverV7 ||
               psInfo->eCoverType==AVCCoverPC2 ) ? "cnt.adf": "cnt";
    if ( (iFile=CSLFindString(papszCoverDir, szFname)) != -1 &&
         (psFile = AVCBinReadOpen(psInfo->pszCoverPath, szFname,
                                  psInfo->eCoverType, AVCFileCNT,
                                  psInfo->psDBCSInfo)) != nullptr)
    {
        if (nCoverPrecision == AVC_DEFAULT_PREC)
            nCoverPrecision = psFile->nPrecision;
        AVCBinReadClose(psFile);
        const int iSect = _AVCIncreaseSectionsArray(&(psInfo->pasSections),
                                      &(psInfo->numSections), 1);

        psInfo->pasSections[iSect].eType = AVCFileCNT;
        psInfo->pasSections[iSect].pszName = CPLStrdup("CNT");
        psInfo->pasSections[iSect].pszFilename=CPLStrdup(papszCoverDir[iFile]);
    }

    /*-----------------------------------------------------------------
     * LAB section (lab.adf)
     *----------------------------------------------------------------*/
    szFname = (psInfo->eCoverType==AVCCoverV7 ||
               psInfo->eCoverType==AVCCoverPC2 ) ? "lab.adf": "lab";
    if ( (iFile=CSLFindString(papszCoverDir, szFname)) != -1 &&
         (psFile = AVCBinReadOpen(psInfo->pszCoverPath, szFname,
                                  psInfo->eCoverType, AVCFileLAB,
                                  psInfo->psDBCSInfo)) != nullptr)
    {
        if (nCoverPrecision == AVC_DEFAULT_PREC)
            nCoverPrecision = psFile->nPrecision;
        AVCBinReadClose(psFile);
        const int iSect = _AVCIncreaseSectionsArray(&(psInfo->pasSections),
                                      &(psInfo->numSections), 1);

        psInfo->pasSections[iSect].eType = AVCFileLAB;
        psInfo->pasSections[iSect].pszName = CPLStrdup("LAB");
        psInfo->pasSections[iSect].pszFilename=CPLStrdup(papszCoverDir[iFile]);
    }

    /*-----------------------------------------------------------------
     * PAL section (pal.adf)
     *----------------------------------------------------------------*/
    szFname = (psInfo->eCoverType==AVCCoverV7 ||
               psInfo->eCoverType==AVCCoverPC2 ) ? "pal.adf": "pal";
    if ( (iFile=CSLFindString(papszCoverDir, szFname)) != -1 &&
         (psFile = AVCBinReadOpen(psInfo->pszCoverPath, szFname,
                                  psInfo->eCoverType, AVCFilePAL,
                                  psInfo->psDBCSInfo)) != nullptr)
    {
        if (nCoverPrecision == AVC_DEFAULT_PREC)
            nCoverPrecision = psFile->nPrecision;
        AVCBinReadClose(psFile);
        const int iSect = _AVCIncreaseSectionsArray(&(psInfo->pasSections),
                                      &(psInfo->numSections), 1);

        psInfo->pasSections[iSect].eType = AVCFilePAL;
        psInfo->pasSections[iSect].pszName = CPLStrdup("PAL");
        psInfo->pasSections[iSect].pszFilename=CPLStrdup(papszCoverDir[iFile]);
    }

    /*-----------------------------------------------------------------
     * TOL section (tol.adf for single precision, par.adf for double)
     *----------------------------------------------------------------*/
    szFname = (psInfo->eCoverType==AVCCoverV7 ||
               psInfo->eCoverType==AVCCoverPC2 ) ? "tol.adf": "tol";
    if ( (iFile=CSLFindString(papszCoverDir, szFname)) != -1 &&
         (psFile = AVCBinReadOpen(psInfo->pszCoverPath, szFname,
                                  psInfo->eCoverType, AVCFileTOL,
                                  psInfo->psDBCSInfo)) != nullptr)
    {
        if (nCoverPrecision == AVC_DEFAULT_PREC)
            nCoverPrecision = psFile->nPrecision;
        AVCBinReadClose(psFile);
        const int iSect = _AVCIncreaseSectionsArray(&(psInfo->pasSections),
                                      &(psInfo->numSections), 1);

        psInfo->pasSections[iSect].eType = AVCFileTOL;
        psInfo->pasSections[iSect].pszName = CPLStrdup("TOL");
        psInfo->pasSections[iSect].pszFilename=CPLStrdup(papszCoverDir[iFile]);
    }

    szFname = (psInfo->eCoverType==AVCCoverV7 ||
               psInfo->eCoverType==AVCCoverPC2 ) ? "par.adf": "par";
    if ( (iFile=CSLFindString(papszCoverDir, szFname)) != -1 &&
         (psFile = AVCBinReadOpen(psInfo->pszCoverPath, szFname,
                                  psInfo->eCoverType, AVCFileTOL,
                                  psInfo->psDBCSInfo)) != nullptr)
    {
        if (nCoverPrecision == AVC_DEFAULT_PREC)
            nCoverPrecision = psFile->nPrecision;
        AVCBinReadClose(psFile);
        const int iSect = _AVCIncreaseSectionsArray(&(psInfo->pasSections),
                                      &(psInfo->numSections), 1);

        psInfo->pasSections[iSect].eType = AVCFileTOL;
        psInfo->pasSections[iSect].pszName = CPLStrdup("TOL");
        psInfo->pasSections[iSect].pszFilename=CPLStrdup(papszCoverDir[iFile]);
    }

    /*-----------------------------------------------------------------
     * TXT section (txt.adf)
     *----------------------------------------------------------------*/
    szFname = (psInfo->eCoverType==AVCCoverV7 ||
               psInfo->eCoverType==AVCCoverPC2 ) ? "txt.adf": "txt";
    if ( (iFile=CSLFindString(papszCoverDir, szFname)) != -1 &&
         (psFile = AVCBinReadOpen(psInfo->pszCoverPath, szFname,
                                  psInfo->eCoverType, AVCFileTXT,
                                  psInfo->psDBCSInfo)) != nullptr)
    {
        if (nCoverPrecision == AVC_DEFAULT_PREC)
            nCoverPrecision = psFile->nPrecision;
        AVCBinReadClose(psFile);
        const int iSect = _AVCIncreaseSectionsArray(&(psInfo->pasSections),
                                      &(psInfo->numSections), 1);

        psInfo->pasSections[iSect].eType = AVCFileTXT;
        psInfo->pasSections[iSect].pszName = CPLStrdup("TXT");
        psInfo->pasSections[iSect].pszFilename=CPLStrdup(papszCoverDir[iFile]);
    }

    /*-----------------------------------------------------------------
     * TX6 section (*.txt)
     * Scan the directory for files with a ".txt" extension.
     * Note: Never seen those in a PC Arc/Info coverage!
     * In weird coverages, the filename ends with "txt" but there is no "."
     *----------------------------------------------------------------*/
    if (psInfo->eCoverType == AVCCoverV7)
        nCoverPrecision = _AVCE00ReadAddJabberwockySection(psInfo, AVCFileTX6,
                                                           "TX6",
                                                           nCoverPrecision,
                                                           ".txt",
                                                           papszCoverDir);
    else if (psInfo->eCoverType == AVCCoverWeird)
        nCoverPrecision = _AVCE00ReadAddJabberwockySection(psInfo, AVCFileTX6,
                                                           "TX6",
                                                           nCoverPrecision,
                                                           "txt",
                                                           papszCoverDir);

    /*-----------------------------------------------------------------
     * At this point, we should have read the coverage precision... and if
     * we haven't yet then we'll just use single by default.
     * We'll need cPrecisionCode for some of the sections that follow.
     *----------------------------------------------------------------*/
    if (nCoverPrecision == AVC_DOUBLE_PREC)
        cPrecisionCode = '3';
    else
        cPrecisionCode = '2';

    /*-----------------------------------------------------------------
     * SIN  2/3 and EOX lines ... ???
     *----------------------------------------------------------------*/
    {
    int iSect = _AVCIncreaseSectionsArray(&(psInfo->pasSections),
                                  &(psInfo->numSections), 2);
    psInfo->pasSections[iSect].eType = AVCFileUnknown;
    psInfo->pasSections[iSect].pszName = CPLStrdup("SIN  X");
    psInfo->pasSections[iSect].pszName[5] = cPrecisionCode;
    iSect++;
    psInfo->pasSections[iSect].eType = AVCFileUnknown;
    psInfo->pasSections[iSect].pszName = CPLStrdup("EOX");
    }

    /*-----------------------------------------------------------------
     * LOG section (log.adf) (ends with EOL)
     *----------------------------------------------------------------*/

    /*-----------------------------------------------------------------
     * PRJ section (prj.adf) (ends with EOP)
     *----------------------------------------------------------------*/
    szFname = (psInfo->eCoverType==AVCCoverV7 ||
               psInfo->eCoverType==AVCCoverPC2 ) ? "prj.adf": "prj";
    if ( (iFile=CSLFindString(papszCoverDir, szFname)) != -1 )
    {
        const int iSect = _AVCIncreaseSectionsArray(&(psInfo->pasSections),
                                      &(psInfo->numSections), 1);

        psInfo->pasSections[iSect].eType = AVCFilePRJ;
        psInfo->pasSections[iSect].pszName = CPLStrdup("PRJ");
        psInfo->pasSections[iSect].pszFilename=CPLStrdup(papszCoverDir[iFile]);
    }

    /*-----------------------------------------------------------------
     * RXP section (*.rxp)
     * Scan the directory for files with a ".rxp" extension.
     *----------------------------------------------------------------*/
    if (psInfo->eCoverType == AVCCoverV7)
        _AVCE00ReadAddJabberwockySection(psInfo, AVCFileRXP, "RXP",
                                         nCoverPrecision,".rxp",papszCoverDir);
    else if (psInfo->eCoverType == AVCCoverWeird)
        _AVCE00ReadAddJabberwockySection(psInfo, AVCFileRXP, "RXP",
                                         nCoverPrecision,"rxp",papszCoverDir);


    /*-----------------------------------------------------------------
     * RPL section (*.pal)
     * Scan the directory for files with a ".rpl" extension.
     *----------------------------------------------------------------*/
    if (psInfo->eCoverType == AVCCoverV7)
        _AVCE00ReadAddJabberwockySection(psInfo, AVCFileRPL, "RPL",
                                         nCoverPrecision,".pal",papszCoverDir);
    else if (psInfo->eCoverType == AVCCoverWeird)
        _AVCE00ReadAddJabberwockySection(psInfo, AVCFileRPL, "RPL",
                                         nCoverPrecision,"rpl",papszCoverDir);

    /*-----------------------------------------------------------------
     * IFO section (tables)
     *----------------------------------------------------------------*/
    papszTables = papszFiles = nullptr;
    if (psInfo->eCoverType == AVCCoverV7 ||
        psInfo->eCoverType == AVCCoverV7Tables ||
        psInfo->eCoverType == AVCCoverWeird)
    {
        /*-------------------------------------------------------------
         * Unix coverages: get tables from the ../info/arc.dir
         * Weird coverages: the arc.dir is similar but called "arcdr9"
         *------------------------------------------------------------*/
        papszTables = AVCBinReadListTables(psInfo->pszInfoPath,
                                           psInfo->pszCoverName,
                                           &papszFiles, psInfo->eCoverType,
                                           psInfo->psDBCSInfo);
    }
    else if (psInfo->eCoverType == AVCCoverPC ||
             psInfo->eCoverType == AVCCoverPC2)
    {
        /*-------------------------------------------------------------
         * PC coverages: look for "???.dbf" in the coverage directory
         *               and build the table name using the coverage name
         *               as the table basename, and the dbf file basename
         *               as the table extension.
         *------------------------------------------------------------*/
        for(iFile=0; papszCoverDir && papszCoverDir[iFile]; iFile++)
        {
            if ((nLen = (int)strlen(papszCoverDir[iFile])) == 7 &&
                EQUAL(papszCoverDir[iFile] + nLen -4, ".dbf"))
            {
                papszCoverDir[iFile][nLen - 4] = '\0';
                szFname = CPLSPrintf("%s.%s", psInfo->pszCoverName,
                                              papszCoverDir[iFile]);
                pcTmp = (char*)szFname;
                for( ; *pcTmp != '\0'; pcTmp++)
                    *pcTmp = (char)toupper(*pcTmp);
                papszCoverDir[iFile][nLen - 4] = '.';

                papszTables = CSLAddString(papszTables, szFname);
                papszFiles = CSLAddString(papszFiles, papszCoverDir[iFile]);
            }
        }
    }

    if (papszTables != nullptr && (numTables = CSLCount(papszTables)) > 0)
    {
        int iSect = _AVCIncreaseSectionsArray(&(psInfo->pasSections),
                                      &(psInfo->numSections), numTables+2);

        psInfo->pasSections[iSect].eType = AVCFileUnknown;
        psInfo->pasSections[iSect].pszName = CPLStrdup("IFO  X");
        psInfo->pasSections[iSect].pszName[5] = cPrecisionCode;
        iSect++;

        for(iTable=0; iTable<numTables; iTable++)
        {
            psInfo->pasSections[iSect].eType = AVCFileTABLE;
            psInfo->pasSections[iSect].pszName=CPLStrdup(papszTables[iTable]);
            if (papszFiles)
            {
                psInfo->pasSections[iSect].pszFilename=
                                              CPLStrdup(papszFiles[iTable]);
            }
            iSect++;
        }

        psInfo->pasSections[iSect].eType = AVCFileUnknown;
        psInfo->pasSections[iSect].pszName = CPLStrdup("EOI");

    }
    CSLDestroy(papszTables);
    CSLDestroy(papszFiles);

    /*-----------------------------------------------------------------
     * File ends with EOS
     *----------------------------------------------------------------*/
    const int iSect = _AVCIncreaseSectionsArray(&(psInfo->pasSections),
                                  &(psInfo->numSections), 1);
    psInfo->pasSections[iSect].eType = AVCFileUnknown;
    psInfo->pasSections[iSect].pszName = CPLStrdup("EOS");


    return nCoverPrecision;
}


/**********************************************************************
 *                     _AVCE00ReadScanE00()
 *
 * Processes an entire E00 file to find all the interesting sections.
 **********************************************************************/
static void _AVCE00ReadScanE00(AVCE00ReadE00Ptr psRead)
{
    AVCE00ParseInfo *psInfo = psRead->hParseInfo;

    const char *pszLine;
    const char *pszName = nullptr;
    void       *obj;
    int        iSect = 0;
    GBool      bFirstLine = TRUE;

    while (CPLGetLastErrorNo() == 0 &&
            (pszLine = CPLReadLine2L(psRead->hFile, knMAX_CHARS_PER_LINE, nullptr) ) != nullptr )
    {
        if (bFirstLine)
        {
            /* Look for the first non-empty line, after the EXP header,
             * trying to detect compressed E00 files. If the file is
             * compressed, the first line of data should be 79 or 80 chars
             * long and contain several '~' characters.
             */
            int nLen = (int)strlen(pszLine);
            if (nLen == 0 || STARTS_WITH_CI(pszLine, "EXP "))
                continue;  /* Skip empty and EXP header lines */
            else if ( (nLen == 79 || nLen == 80) &&
                      strchr(pszLine, '~') != nullptr )
            {
                /* Looks like a compressed file. Just log an error and return.
                 * The caller should reject the file because it contains 0
                 * sections
                 */
                CPLError(CE_Failure, CPLE_OpenFailed,
                         "This looks like a compressed E00 file and cannot be "
                         "processed directly. You may need to uncompress it "
                         "first using the E00compr library or the e00conv "
                         "program." );
                return;
            }

            /* All seems fine. Continue with normal processing */
            bFirstLine = FALSE;
        }

        /* coverity[tainted_data] */
        obj = _AVCE00ReadNextLineE00(psRead, pszLine);

        if (obj)
        {
            pszName = nullptr;
            switch (psInfo->eFileType)
            {
            case AVCFileARC:
                pszName = "ARC";
                break;

            case AVCFilePAL:
                pszName = "PAL";
                break;

            case AVCFileCNT:
                pszName = "CNT";
                break;

            case AVCFileLAB:
                pszName = "LAB";
                break;

            case AVCFileRPL:
                pszName = "RPL";
                break;

            case AVCFileTXT:
                pszName = "TXT";
                break;

            case AVCFileTX6:
                pszName = "TX6";
                break;

            case AVCFilePRJ:
                pszName = "PRJ";
                break;

            case AVCFileTABLE:
                pszName = psInfo->hdr.psTableDef->szTableName;
                break;

            default:
                break;
            }

            if (pszName && (psRead->numSections == 0 ||
                    psRead->pasSections[iSect].eType != psInfo->eFileType ||
                    !EQUAL(pszName, psRead->pasSections[iSect].pszName)))
            {
                iSect = _AVCIncreaseSectionsArray(&(psRead->pasSections),
                                      &(psRead->numSections), 1);

                psRead->pasSections[iSect].eType = psInfo->eFileType;
                /* psRead->pasSections[iSect].pszName = CPLStrdup(psRead->pszCoverName); */
                psRead->pasSections[iSect].pszName = CPLStrdup(pszName);
                psRead->pasSections[iSect].pszFilename = CPLStrdup(psRead->pszCoverPath);
                psRead->pasSections[iSect].nLineNum = psInfo->nStartLineNum;
                psRead->pasSections[iSect].nFeatureCount = 0;
            }

            if (pszName && psRead->numSections)
            {
                /* increase feature count for current layer */
                ++psRead->pasSections[iSect].nFeatureCount;
            }
        }
    }
}

/**********************************************************************
 *                         _AVCE00ReadNextTableLine()
 *
 * Return the next line of the E00 representation of a info table.
 *
 * This function is used by AVCE00ReadNextLine() to generate table
 * output... it should never be called directly.
 **********************************************************************/
static const char *_AVCE00ReadNextTableLine(AVCE00ReadPtr psInfo)
{
    const char *pszLine = nullptr;
    AVCE00Section *psSect;

    psSect = &(psInfo->pasSections[psInfo->iCurSection]);

    CPLAssert(psSect->eType == AVCFileTABLE);

    if (psInfo->iCurStep == AVC_GEN_NOTSTARTED)
    {
        /*---------------------------------------------------------
         * Open table and start returning header
         *--------------------------------------------------------*/
        if (psInfo->eCoverType == AVCCoverPC ||
            psInfo->eCoverType == AVCCoverPC2)
        {
            /*---------------------------------------------------------
             * PC Arc/Info: We pass the DBF table's full filename + the
             * Arc/Info table name (for E00 header)
             *--------------------------------------------------------*/
            char *pszFname;
            pszFname = CPLStrdup(CPLSPrintf("%s%s", psInfo->pszInfoPath,
                                                    psSect->pszFilename ));
            psInfo->hFile = AVCBinReadOpen(pszFname, psSect->pszName,
                                           psInfo->eCoverType, psSect->eType,
                                           psInfo->psDBCSInfo);
            CPLFree(pszFname);
        }
        else
        {
            /*---------------------------------------------------------
             * AVCCoverV7 and AVCCoverWeird:
             * We pass the INFO dir's path, and the Arc/Info table name
             * will be searched in the arc.dir
             *--------------------------------------------------------*/
            psInfo->hFile = AVCBinReadOpen(psInfo->pszInfoPath,
                                           psSect->pszName,
                                           psInfo->eCoverType, psSect->eType,
                                           psInfo->psDBCSInfo);
        }


        /* For some reason the file could not be opened... abort now.
         * An error message should have already been produced by
         * AVCBinReadOpen()
         */
        if (psInfo->hFile == nullptr)
            return nullptr;

        psInfo->iCurStep = AVC_GEN_TABLEHEADER;

        pszLine = AVCE00GenTableHdr(psInfo->hGenInfo,
                                    psInfo->hFile->hdr.psTableDef,
                                    FALSE);
    }

    if (pszLine == nullptr &&
        psInfo->iCurStep == AVC_GEN_TABLEHEADER)
    {
        /*---------------------------------------------------------
         * Continue table header
         *--------------------------------------------------------*/
        pszLine = AVCE00GenTableHdr(psInfo->hGenInfo,
                                    psInfo->hFile->hdr.psTableDef,
                                    TRUE);

        if (pszLine == nullptr)
        {
            /* Finished with table header... time to proceed with the
             * table data.
             * Reset the AVCE00GenInfo struct. so that it returns nullptr,
             * which will force reading of the first record from the
             * file on the next call to AVCE00ReadNextLine()
             */
            AVCE00GenReset(psInfo->hGenInfo);
            psInfo->iCurStep = AVC_GEN_TABLEDATA;
        }

    }

    if (pszLine == nullptr &&
        psInfo->iCurStep == AVC_GEN_TABLEDATA)
    {
        /*---------------------------------------------------------
         * Continue with records of data
         *--------------------------------------------------------*/

        pszLine = AVCE00GenTableRec(psInfo->hGenInfo,
                                    psInfo->hFile->hdr.psTableDef->numFields,
                                    psInfo->hFile->hdr.psTableDef->pasFieldDef,
                                    psInfo->hFile->cur.pasFields,
                                    TRUE);

        if (pszLine == nullptr)
        {
            /* Current record is finished generating... we need to read
             * a new one from the file.
             */
            if (AVCBinReadNextObject(psInfo->hFile) != nullptr)
            {
                pszLine = AVCE00GenTableRec(psInfo->hGenInfo,
                                    psInfo->hFile->hdr.psTableDef->numFields,
                                    psInfo->hFile->hdr.psTableDef->pasFieldDef,
                                    psInfo->hFile->cur.pasFields,
                                    FALSE);
            }
        }
    }

    if (pszLine == nullptr)
    {
        /*---------------------------------------------------------
         * No more lines to output for this table ... Close it.
         *--------------------------------------------------------*/
        AVCBinReadClose(psInfo->hFile);
        psInfo->hFile = nullptr;

        /*---------------------------------------------------------
         * And now proceed to the next section...
         * OK, I don't really like recursion, but it was
         * the simplest way to do this, and anyways we should never
         * have more than one level of recursion.
         *--------------------------------------------------------*/
        if (psInfo->bReadAllSections)
            psInfo->iCurSection++;
        else
            psInfo->iCurSection = psInfo->numSections;
        psInfo->iCurStep = AVC_GEN_NOTSTARTED;

        pszLine = AVCE00ReadNextLine(psInfo);
    }

    /*-----------------------------------------------------------------
     * Check for errors... if any error happened, then return nullptr.
     *----------------------------------------------------------------*/
    if (CPLGetLastErrorNo() != 0)
    {
        pszLine = nullptr;
    }

    return pszLine;
}


/**********************************************************************
 *                          AVCE00ReadNextLine()
 *
 * Returns the next line of the E00 representation of the coverage
 * or nullptr when there are no more lines to generate, or if an error happened.
 * The returned line is a null-terminated string, and it does not
 * include a newline character.
 *
 * Call CPLGetLastErrorNo() after calling AVCE00ReadNextLine() to
 * make sure that the line was generated successfully.
 *
 * Note that AVCE00ReadNextLine() returns a reference to an
 * internal buffer whose contents will
 * be valid only until the next call to this function.  The caller should
 * not attempt to free() the returned pointer.
 **********************************************************************/
const char *AVCE00ReadNextLine(AVCE00ReadPtr psInfo)
{
    const char *pszLine = nullptr;
    AVCE00Section *psSect;

    CPLErrorReset();

    /*-----------------------------------------------------------------
     * Check if we have finished generating E00 output
     *----------------------------------------------------------------*/
    if (psInfo->iCurSection >= psInfo->numSections)
        return nullptr;

    psSect = &(psInfo->pasSections[psInfo->iCurSection]);

    /*-----------------------------------------------------------------
     * For simplicity, the generation of table output is in a separate
     * function.
     *----------------------------------------------------------------*/
    if (psSect->eType == AVCFileTABLE)
    {
        return _AVCE00ReadNextTableLine(psInfo);
    }

    if (psSect->eType == AVCFileUnknown)
    {
    /*-----------------------------------------------------------------
     * Section not attached to any file, used to hold header lines
     * or section separators, etc... just return the line directly and
     * move pointer to the next section.
     *----------------------------------------------------------------*/
        pszLine = psSect->pszName;
        if (psInfo->bReadAllSections)
            psInfo->iCurSection++;
        else
            psInfo->iCurSection = psInfo->numSections;
        psInfo->iCurStep = AVC_GEN_NOTSTARTED;
    }
    /*=================================================================
     *              ARC, PAL, CNT, LAB, TOL and TXT
     *================================================================*/
    else if (psInfo->iCurStep == AVC_GEN_NOTSTARTED &&
             (psSect->eType == AVCFileARC ||
              psSect->eType == AVCFilePAL ||
              psSect->eType == AVCFileRPL ||
              psSect->eType == AVCFileCNT ||
              psSect->eType == AVCFileLAB ||
              psSect->eType == AVCFileTOL ||
              psSect->eType == AVCFileTXT ||
              psSect->eType == AVCFileTX6 ||
              psSect->eType == AVCFileRXP   ) )
    {
    /*-----------------------------------------------------------------
     * Start processing of an ARC, PAL, CNT, LAB or TOL section:
     *   Open the file, get ready to read the first object from the
     *   file, and return the header line.
     *  If the file fails to open then we will return nullptr.
     *----------------------------------------------------------------*/
        psInfo->hFile = AVCBinReadOpen(psInfo->pszCoverPath,
                                       psSect->pszFilename,
                                       psInfo->eCoverType, psSect->eType,
                                       psInfo->psDBCSInfo);

        /*-------------------------------------------------------------
         * For some reason the file could not be opened... abort now.
         * An error message should have already been produced by
         * AVCBinReadOpen()
         *------------------------------------------------------------*/
        if (psInfo->hFile == nullptr)
            return nullptr;

        pszLine = AVCE00GenStartSection(psInfo->hGenInfo,
                                        psSect->eType, psSect->pszName);

        /*-------------------------------------------------------------
         * Reset the AVCE00GenInfo struct. so that it returns nullptr,
         * which will force reading of the first object from the
         * file on the next call to AVCE00ReadNextLine()
         *------------------------------------------------------------*/
        AVCE00GenReset(psInfo->hGenInfo);
        psInfo->iCurStep = AVC_GEN_DATA;
    }
    else if (psInfo->iCurStep == AVC_GEN_DATA &&
             (psSect->eType == AVCFileARC ||
              psSect->eType == AVCFilePAL ||
              psSect->eType == AVCFileRPL ||
              psSect->eType == AVCFileCNT ||
              psSect->eType == AVCFileLAB ||
              psSect->eType == AVCFileTOL ||
              psSect->eType == AVCFileTXT ||
              psSect->eType == AVCFileTX6 ||
              psSect->eType == AVCFileRXP    ) )
    {
    /*-----------------------------------------------------------------
     * Return the next line of an ARC/PAL/CNT/TOL/TXT object...
     * if necessary, read the next object from the binary file.
     *----------------------------------------------------------------*/
        pszLine = AVCE00GenObject(psInfo->hGenInfo,
                                  psSect->eType,
                  (psSect->eType==AVCFileARC?(void*)(psInfo->hFile->cur.psArc):
                   psSect->eType==AVCFilePAL?(void*)(psInfo->hFile->cur.psPal):
                   psSect->eType==AVCFileRPL?(void*)(psInfo->hFile->cur.psPal):
                   psSect->eType==AVCFileCNT?(void*)(psInfo->hFile->cur.psCnt):
                   psSect->eType==AVCFileLAB?(void*)(psInfo->hFile->cur.psLab):
                   psSect->eType==AVCFileTOL?(void*)(psInfo->hFile->cur.psTol):
                   psSect->eType==AVCFileTXT?(void*)(psInfo->hFile->cur.psTxt):
                   psSect->eType==AVCFileTX6?(void*)(psInfo->hFile->cur.psTxt):
                   psSect->eType==AVCFileRXP?(void*)(psInfo->hFile->cur.psRxp):
                   nullptr),
                                  TRUE);
        if (pszLine == nullptr)
        {
            /*---------------------------------------------------------
             * Current object is finished generating... we need to read
             * a new one from the file.
             *--------------------------------------------------------*/
            if (AVCBinReadNextObject(psInfo->hFile) != nullptr)
            {
                pszLine = AVCE00GenObject(psInfo->hGenInfo,
                                          psSect->eType,
                  (psSect->eType==AVCFileARC?(void*)(psInfo->hFile->cur.psArc):
                   psSect->eType==AVCFilePAL?(void*)(psInfo->hFile->cur.psPal):
                   psSect->eType==AVCFileRPL?(void*)(psInfo->hFile->cur.psPal):
                   psSect->eType==AVCFileCNT?(void*)(psInfo->hFile->cur.psCnt):
                   psSect->eType==AVCFileLAB?(void*)(psInfo->hFile->cur.psLab):
                   psSect->eType==AVCFileTOL?(void*)(psInfo->hFile->cur.psTol):
                   psSect->eType==AVCFileTXT?(void*)(psInfo->hFile->cur.psTxt):
                   psSect->eType==AVCFileTX6?(void*)(psInfo->hFile->cur.psTxt):
                   psSect->eType==AVCFileRXP?(void*)(psInfo->hFile->cur.psRxp):
                   nullptr),
                                          FALSE);
            }
        }
        if (pszLine == nullptr)
        {
            /*---------------------------------------------------------
             * Still nullptr ??? This means we finished reading this file...
             * Start returning the "end of section" line(s)...
             *--------------------------------------------------------*/
            AVCBinReadClose(psInfo->hFile);
            psInfo->hFile = nullptr;
            psInfo->iCurStep = AVC_GEN_ENDSECTION;
            pszLine = AVCE00GenEndSection(psInfo->hGenInfo, psSect->eType,
                                          FALSE);
        }
    }
    /*=================================================================
     *                          PRJ
     *================================================================*/
    else if (psInfo->iCurStep == AVC_GEN_NOTSTARTED &&
              psSect->eType == AVCFilePRJ   )
    {
        /*-------------------------------------------------------------
         * Start processing of PRJ section... return first header line.
         *------------------------------------------------------------*/
        pszLine = AVCE00GenStartSection(psInfo->hGenInfo,
                                        psSect->eType, nullptr);

        psInfo->hFile = nullptr;
        psInfo->iCurStep = AVC_GEN_DATA;
    }
    else if (psInfo->iCurStep == AVC_GEN_DATA &&
             psSect->eType == AVCFilePRJ  )
    {
        /*-------------------------------------------------------------
         * Return the next line of a PRJ section
         *------------------------------------------------------------*/
        if (psInfo->hFile == nullptr)
        {
            /*---------------------------------------------------------
             * File has not been read yet...
             * Read the PRJ file, and return the first PRJ line.
             *--------------------------------------------------------*/
            psInfo->hFile = AVCBinReadOpen(psInfo->pszCoverPath,
                                           psSect->pszFilename,
                                           psInfo->eCoverType, psSect->eType,
                                           psInfo->psDBCSInfo);

            /* For some reason the file could not be opened... abort now.
             * An error message should have already been produced by
             * AVCBinReadOpen()
             */
            if (psInfo->hFile == nullptr)
                return nullptr;

            pszLine = AVCE00GenPrj(psInfo->hGenInfo,
                                   psInfo->hFile->cur.papszPrj, FALSE);
        }
        else
        {
            /*---------------------------------------------------------
             * Generate the next line of output.
             *--------------------------------------------------------*/
            pszLine = AVCE00GenPrj(psInfo->hGenInfo,
                                   psInfo->hFile->cur.papszPrj, TRUE);
        }

        if (pszLine == nullptr)
        {
            /*---------------------------------------------------------
             * Still nullptr ??? This means we finished generating this PRJ
             * section...
             * Start returning the "end of section" line(s)...
             *--------------------------------------------------------*/
            AVCBinReadClose(psInfo->hFile);
            psInfo->hFile = nullptr;
            psInfo->iCurStep = AVC_GEN_ENDSECTION;
            pszLine = AVCE00GenEndSection(psInfo->hGenInfo, psSect->eType,
                                          FALSE);
        }
    }
    else if (psInfo->iCurStep != AVC_GEN_ENDSECTION)
    {
        /* We should never get here! */
        CPLAssert(FALSE);
    }


    /*=================================================================
     *                End of section, for all files
     *================================================================*/

    /*-----------------------------------------------------------------
     * Finished processing of an ARC, PAL, CNT, LAB, TOL, PRJ file ...
     * continue returning the "end of section" line(s), and move the pointer
     * to the next section once we're done.
     *----------------------------------------------------------------*/
    if (psInfo->iCurStep == AVC_GEN_ENDSECTION && pszLine == nullptr)
    {
        pszLine = AVCE00GenEndSection(psInfo->hGenInfo, psSect->eType, TRUE);

        if (pszLine == nullptr)
        {
            /*---------------------------------------------------------
             * Finished returning the last lines of the section...
             * proceed to the next section...
             * OK, I don't really like recursion, but it was
             * the simplest way to do this, and anyways we should never
             * have more than one level of recursion.
             *--------------------------------------------------------*/
            if (psInfo->bReadAllSections)
                psInfo->iCurSection++;
            else
                psInfo->iCurSection = psInfo->numSections;
            psInfo->iCurStep = AVC_GEN_NOTSTARTED;

            pszLine = AVCE00ReadNextLine(psInfo);
        }
    }

    return pszLine;
}



/**********************************************************************
 *                         AVCE00ReadSectionsList()
 *
 * Returns an array of AVCE00Section structures that describe the
 * skeleton of the whole coverage.  The value of *numSect will be
 * set to the number of sections in the array.
 *
 * You can scan the returned array, and use AVCE00ReadGotoSection() to move
 * the read pointer directly to the beginning of a given section
 * of the file.
 *
 * Sections of type AVCFileUnknown correspond to lines in the
 * E00 output that are not directly linked to any coverage file, like
 * the "EXP 0" line, the "IFO X", "SIN X", etc.
 *
 * THE RETURNED ARRAY IS AN INTERNAL STRUCTURE AND SHOULD NOT BE
 * MODIFIED OR FREED BY THE CALLER... its contents will be valid
 * for as long as the coverage will remain open.
 **********************************************************************/
AVCE00Section *AVCE00ReadSectionsList(AVCE00ReadPtr psInfo, int *numSect)
{
    CPLErrorReset();

    *numSect = psInfo->numSections;
    return psInfo->pasSections;
}

/**********************************************************************
 *                         AVCE00ReadGotoSection()
 *
 * Move the read pointer to the E00 section (coverage file) described in
 * the psSect structure.  Call AVCE00ReadSectionsList() to get the list of
 * sections for the current coverage.
 *
 * if bContinue=TRUE, then reading will automatically continue with the
 * next sections of the file once the requested section is finished.
 * Otherwise, if bContinue=FALSE then reading will stop at the end
 * of this section (i.e. AVCE00ReadNextLine() will return nullptr when
 * it reaches the end of this section)
 *
 * Sections of type AVCFileUnknown returned by AVCE00ReadSectionsList()
 * correspond to lines in the E00 output that are not directly linked
 * to any coverage file, like the "EXP 0" line, the "IFO X", "SIN X", etc.
 * You can jump to these sections or any other one without problems.
 *
 * This function returns 0 on success or -1 on error.
 **********************************************************************/
int AVCE00ReadGotoSection(AVCE00ReadPtr psInfo, AVCE00Section *psSect,
                          GBool bContinue)
{
    int     iSect;
    GBool   bFound = FALSE;

    CPLErrorReset();

    /*-----------------------------------------------------------------
     * Locate the requested section in the array.
     *----------------------------------------------------------------*/
    for(iSect=0; iSect<psInfo->numSections; iSect++)
    {
        if (psInfo->pasSections[iSect].eType == psSect->eType &&
            EQUAL(psInfo->pasSections[iSect].pszName, psSect->pszName))
        {
            bFound = TRUE;
            break;
        }
    }

    /*-----------------------------------------------------------------
     * Not found ... generate an error...
     *----------------------------------------------------------------*/
    if (!bFound)
    {
        CPLError(CE_Failure, CPLE_IllegalArg,
                 "Requested E00 section does not exist!");
        return -1;
    }

    /*-----------------------------------------------------------------
     * Found it ... close current section and get ready to read
     * the new one.
     *----------------------------------------------------------------*/
    if (psInfo->hFile)
    {
        AVCBinReadClose(psInfo->hFile);
        psInfo->hFile = nullptr;
    }

    psInfo->bReadAllSections = bContinue;
    psInfo->iCurSection = iSect;
    psInfo->iCurStep = AVC_GEN_NOTSTARTED;

    return 0;
}

/**********************************************************************
 *                         AVCE00ReadRewind()
 *
 * Rewinds the AVCE00ReadPtr just like the stdio rewind()
 * function would do if you were reading an ASCII E00 file.
 *
 * Returns 0 on success or -1 on error.
 **********************************************************************/
int  AVCE00ReadRewind(AVCE00ReadPtr psInfo)
{
    CPLErrorReset();

    return AVCE00ReadGotoSection(psInfo, &(psInfo->pasSections[0]), TRUE);
}

/**********************************************************************
 *                         AVCE00ReadRewindE00()
 *
 * Rewinds the AVCE00ReadE00Ptr just like the stdio rewind()
 * function would do if you were reading an ASCII E00 file.
 *
 * Returns 0 on success or -1 on error.
 **********************************************************************/
int  AVCE00ReadRewindE00(AVCE00ReadE00Ptr psRead)
{
    CPLErrorReset();

    psRead->bReadAllSections = TRUE;
    psRead->eCurFileType = AVCFileUnknown;

    psRead->hParseInfo->nCurLineNum = 0;
    psRead->hParseInfo->nStartLineNum = 0;
    psRead->hParseInfo->bForceEndOfSection = TRUE;
    psRead->hParseInfo->eSuperSectionType = AVCFileUnknown;
    AVCE00ParseSectionEnd(psRead->hParseInfo, nullptr, 1);

    return VSIFSeekL(psRead->hFile, 0, SEEK_SET);
}

/**********************************************************************
 *                        _AVCE00ReadSeekE00()
 *
 * Seeks to a new location in the E00 file, keeping parse state
 * appropriately.
 *
 * NOTE: This is a pretty slow implementation.
 * NOTE: The SEEK_END is not implemented.
 *
 * Returns 0 on success or -1 on error.
 **********************************************************************/
static int _AVCE00ReadSeekE00(AVCE00ReadE00Ptr psRead, int nOffset,
        int nWhence)
{
    const char *pszLine;
    /* void       *obj; */

    switch (nWhence)
    {
    case SEEK_CUR:
        break;

    case SEEK_SET:
        AVCE00ReadRewindE00(psRead);
        break;

    default:
        CPLAssert(nWhence == SEEK_CUR || nWhence == SEEK_SET);
        break;
    }

    while (nOffset-- &&
            CPLGetLastErrorNo() == 0 &&
            (pszLine = CPLReadLine2L(psRead->hFile, knMAX_CHARS_PER_LINE, nullptr) ) != nullptr )
    {
        /* obj = */
        /* coverity[tainted_data] */
        _AVCE00ReadNextLineE00(psRead, pszLine);
    }

    return nOffset ? -1 : 0;
}

/**********************************************************************
 *                      AVCE00ReadNextObjectE00()
 *
 * Returns the next object in an E00 file or nullptr when there are no
 * more objects, or if an error happened.  The object type can be
 * determined via the eCurFileType attribute of the
 * AVCE00ReadE00Ptr object.
 *
 * Note that AVCE00ReadNextLine() returns a reference to an internal
 * buffer whose contents will be valid only until the next call to
 * this function.  The caller should not attempt to free() the
 * returned pointer.
 **********************************************************************/
void *AVCE00ReadNextObjectE00(AVCE00ReadE00Ptr psRead)
{
    const char *pszLine;
    void       *obj = nullptr;

    do
    {
        pszLine = CPLReadLine2L(psRead->hFile, knMAX_CHARS_PER_LINE, nullptr);
        if (pszLine == nullptr)
            break;
        /* coverity[tainted_data] */
        obj = _AVCE00ReadNextLineE00(psRead, pszLine);
    }
    while (obj == nullptr &&
            (psRead->bReadAllSections ||
             psRead->eCurFileType != AVCFileUnknown) &&
            CPLGetLastErrorNo() == 0);
    return obj;
}

/**********************************************************************
 *                         AVCE00ReadSectionsListE00()
 *
 * Returns an array of AVCE00Section structures that describe the
 * sections in the E00 file.  The value of *numSect will be set to the
 * number of sections in the array.
 *
 * You can scan the returned array, and use AVCE00ReadGotoSectionE00()
 * to move the read pointer directly to the beginning of a given
 * section of the file.
 *
 * THE RETURNED ARRAY IS AN INTERNAL STRUCTURE AND SHOULD NOT BE
 * MODIFIED OR FREED BY THE CALLER... its contents will be valid
 * for as long as the coverage will remain open.
 **********************************************************************/
AVCE00Section *AVCE00ReadSectionsListE00(AVCE00ReadE00Ptr psRead,
        int *numSect)
{
    CPLErrorReset();

    *numSect = psRead->numSections;
    return psRead->pasSections;
}

/**********************************************************************
 *                         AVCE00ReadGotoSectionE00()
 *
 * Move the read pointer to the E00 section described in the psSect
 * structure.  Call AVCE00ReadSectionsListE00() to get the list of
 * sections for the current coverage.
 *
 * If bContinue is TRUE, then reading will automatically continue with
 * the next section of the file once the requested section is finished.
 * Otherwise, if bContinue is FALSE then reading will stop at the end
 * of this section (i.e. AVCE00ReadNextObjectE00() will return nullptr
 * when the end of this section is reached)
 *
 * This function returns 0 on success or -1 on error.
 **********************************************************************/
int AVCE00ReadGotoSectionE00(AVCE00ReadE00Ptr psRead,
        AVCE00Section *psSect, GBool bContinue)
{
    int     iSect;
    GBool   bFound = FALSE;

    CPLErrorReset();

    /*-----------------------------------------------------------------
     * Locate the requested section in the array.
     *----------------------------------------------------------------*/
    for(iSect=0; iSect<psRead->numSections; iSect++)
    {
        if (psRead->pasSections[iSect].eType == psSect->eType &&
            EQUAL(psRead->pasSections[iSect].pszName, psSect->pszName))
        {
            bFound = TRUE;
            break;
        }
    }

    /*-----------------------------------------------------------------
     * Not found ... generate an error...
     *----------------------------------------------------------------*/
    if (!bFound)
    {
        CPLError(CE_Failure, CPLE_IllegalArg,
                 "Requested E00 section does not exist!");
        return -1;
    }

    /*-----------------------------------------------------------------
     * Found it ... advance parser to line number of start of section
     *----------------------------------------------------------------*/
    _AVCE00ReadSeekE00(psRead, psRead->pasSections[iSect].nLineNum, SEEK_SET);

    psRead->bReadAllSections = bContinue;

    return 0;
}
