EVOLUTION-MANAGER
Edit File: ddfmodule.cpp
/****************************************************************************** * * Project: ISO 8211 Access * Purpose: Implements the DDFModule class. * Author: Frank Warmerdam, warmerdam@pobox.com * ****************************************************************************** * Copyright (c) 1999, Frank Warmerdam * Copyright (c) 2011-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 "cpl_port.h" #include "iso8211.h" #include <cstdio> #include <cstring> #if HAVE_FCNTL_H # include <fcntl.h> #endif #include "cpl_conv.h" #include "cpl_error.h" #include "cpl_vsi.h" CPL_CVSID("$Id: ddfmodule.cpp 36552 2016-11-29 06:01:45Z goatbar $"); /************************************************************************/ /* DDFModule() */ /************************************************************************/ /** * The constructor. */ DDFModule::DDFModule() : fpDDF(NULL), bReadOnly(TRUE), nFirstRecordOffset(0), _interchangeLevel('\0'), _inlineCodeExtensionIndicator('\0'), _versionNumber('\0'), _appIndicator('\0'), _fieldControlLength(9), _recLength(0), _leaderIden('L'), _fieldAreaStart(0), _sizeFieldLength(0), _sizeFieldPos(0), _sizeFieldTag(0), nFieldDefnCount(0), papoFieldDefns(NULL), poRecord(NULL), nCloneCount(0), nMaxCloneCount(0), papoClones(NULL) { strcpy( _extendedCharSet, " ! " ); } /************************************************************************/ /* ~DDFModule() */ /************************************************************************/ /** * The destructor. */ DDFModule::~DDFModule() { Close(); } /************************************************************************/ /* Close() */ /* */ /* Note that closing a file also destroys essentially all other */ /* module datastructures. */ /************************************************************************/ /** * Close an ISO 8211 file. */ void DDFModule::Close() { /* -------------------------------------------------------------------- */ /* Close the file. */ /* -------------------------------------------------------------------- */ if( fpDDF != NULL ) { CPL_IGNORE_RET_VAL(VSIFCloseL( fpDDF )); fpDDF = NULL; } /* -------------------------------------------------------------------- */ /* Cleanup the working record. */ /* -------------------------------------------------------------------- */ if( poRecord != NULL ) { delete poRecord; poRecord = NULL; } /* -------------------------------------------------------------------- */ /* Cleanup the clones. */ /* -------------------------------------------------------------------- */ for( int i = 0; i < nCloneCount; i++ ) { papoClones[i]->RemoveIsCloneFlag(); delete papoClones[i]; } nCloneCount = 0; nMaxCloneCount = 0; CPLFree( papoClones ); papoClones = NULL; /* -------------------------------------------------------------------- */ /* Cleanup the field definitions. */ /* -------------------------------------------------------------------- */ for( int i = 0; i < nFieldDefnCount; i++ ) delete papoFieldDefns[i]; CPLFree( papoFieldDefns ); papoFieldDefns = NULL; nFieldDefnCount = 0; } /************************************************************************/ /* Open() */ /* */ /* Open an ISO 8211 file, and read the DDR record to build the */ /* field definitions. */ /************************************************************************/ /** * Open a ISO 8211 (DDF) file for reading. * * If the open succeeds the data descriptive record (DDR) will have been * read, and all the field and subfield definitions will be available. * * @param pszFilename The name of the file to open. * @param bFailQuietly If FALSE a CPL Error is issued for non-8211 files, * otherwise quietly return NULL. * * @return FALSE if the open fails or TRUE if it succeeds. Errors messages * are issued internally with CPLError(). */ int DDFModule::Open( const char * pszFilename, int bFailQuietly ) { static const int nLeaderSize = 24; /* -------------------------------------------------------------------- */ /* Close the existing file if there is one. */ /* -------------------------------------------------------------------- */ if( fpDDF != NULL ) Close(); /* -------------------------------------------------------------------- */ /* Open the file. */ /* -------------------------------------------------------------------- */ fpDDF = VSIFOpenL( pszFilename, "rb" ); if( fpDDF == NULL ) { if( !bFailQuietly ) CPLError( CE_Failure, CPLE_OpenFailed, "Unable to open DDF file `%s'.", pszFilename ); return FALSE; } /* -------------------------------------------------------------------- */ /* Read the 24 byte leader. */ /* -------------------------------------------------------------------- */ char achLeader[nLeaderSize]; if( (int)VSIFReadL( achLeader, 1, nLeaderSize, fpDDF ) != nLeaderSize ) { CPL_IGNORE_RET_VAL(VSIFCloseL( fpDDF )); fpDDF = NULL; if( !bFailQuietly ) CPLError( CE_Failure, CPLE_FileIO, "Leader is short on DDF file `%s'.", pszFilename ); return FALSE; } /* -------------------------------------------------------------------- */ /* Verify that this appears to be a valid DDF file. */ /* -------------------------------------------------------------------- */ int i, bValid = TRUE; for( i = 0; i < nLeaderSize; i++ ) { if( achLeader[i] < 32 || achLeader[i] > 126 ) bValid = FALSE; } if( achLeader[5] != '1' && achLeader[5] != '2' && achLeader[5] != '3' ) bValid = FALSE; if( achLeader[6] != 'L' ) bValid = FALSE; if( achLeader[8] != '1' && achLeader[8] != ' ' ) bValid = FALSE; /* -------------------------------------------------------------------- */ /* Extract information from leader. */ /* -------------------------------------------------------------------- */ if( bValid ) { _recLength = DDFScanInt( achLeader+0, 5 ); _interchangeLevel = achLeader[5]; _leaderIden = achLeader[6]; _inlineCodeExtensionIndicator = achLeader[7]; _versionNumber = achLeader[8]; _appIndicator = achLeader[9]; _fieldControlLength = DDFScanInt(achLeader+10,2); _fieldAreaStart = DDFScanInt(achLeader+12,5); _extendedCharSet[0] = achLeader[17]; _extendedCharSet[1] = achLeader[18]; _extendedCharSet[2] = achLeader[19]; _extendedCharSet[3] = '\0'; _sizeFieldLength = DDFScanInt(achLeader+20,1); _sizeFieldPos = DDFScanInt(achLeader+21,1); _sizeFieldTag = DDFScanInt(achLeader+23,1); if( _recLength < nLeaderSize || _fieldControlLength == 0 || _fieldAreaStart < 24 || _sizeFieldLength == 0 || _sizeFieldPos == 0 || _sizeFieldTag == 0 ) { bValid = FALSE; } } /* -------------------------------------------------------------------- */ /* If the header is invalid, then clean up, report the error */ /* and return. */ /* -------------------------------------------------------------------- */ if( !bValid ) { CPL_IGNORE_RET_VAL(VSIFCloseL( fpDDF )); fpDDF = NULL; if( !bFailQuietly ) CPLError( CE_Failure, CPLE_AppDefined, "File `%s' does not appear to have\n" "a valid ISO 8211 header.\n", pszFilename ); return FALSE; } /* -------------------------------------------------------------------- */ /* Read the whole record info memory. */ /* -------------------------------------------------------------------- */ char *pachRecord = (char *) CPLMalloc(_recLength); memcpy( pachRecord, achLeader, nLeaderSize ); if( (int)VSIFReadL( pachRecord+nLeaderSize, 1, _recLength-nLeaderSize, fpDDF ) != _recLength - nLeaderSize ) { if( !bFailQuietly ) CPLError( CE_Failure, CPLE_FileIO, "Header record is short on DDF file `%s'.", pszFilename ); return FALSE; } /* -------------------------------------------------------------------- */ /* First make a pass counting the directory entries. */ /* -------------------------------------------------------------------- */ int nFieldEntryWidth, nFDCount = 0; nFieldEntryWidth = _sizeFieldLength + _sizeFieldPos + _sizeFieldTag; for( i = nLeaderSize; i < _recLength; i += nFieldEntryWidth ) { if( pachRecord[i] == DDF_FIELD_TERMINATOR ) break; nFDCount++; } /* -------------------------------------------------------------------- */ /* Allocate, and read field definitions. */ /* -------------------------------------------------------------------- */ for( i = 0; i < nFDCount; i++ ) { char szTag[128]; int nEntryOffset = nLeaderSize + i*nFieldEntryWidth; int nFieldLength, nFieldPos; strncpy( szTag, pachRecord+nEntryOffset, _sizeFieldTag ); szTag[_sizeFieldTag] = '\0'; nEntryOffset += _sizeFieldTag; nFieldLength = DDFScanInt( pachRecord+nEntryOffset, _sizeFieldLength ); nEntryOffset += _sizeFieldLength; nFieldPos = DDFScanInt( pachRecord+nEntryOffset, _sizeFieldPos ); if (_fieldAreaStart+nFieldPos < 0 || _recLength - (_fieldAreaStart+nFieldPos) < nFieldLength) { if( !bFailQuietly ) CPLError( CE_Failure, CPLE_FileIO, "Header record invalid on DDF file `%s'.", pszFilename ); CPLFree( pachRecord ); return FALSE; } DDFFieldDefn *poFDefn = new DDFFieldDefn(); if( poFDefn->Initialize( this, szTag, nFieldLength, pachRecord+_fieldAreaStart+nFieldPos ) ) AddField( poFDefn ); else delete poFDefn; } CPLFree( pachRecord ); /* -------------------------------------------------------------------- */ /* Record the current file offset, the beginning of the first */ /* data record. */ /* -------------------------------------------------------------------- */ nFirstRecordOffset = (long)VSIFTellL( fpDDF ); return TRUE; } /************************************************************************/ /* Initialize() */ /************************************************************************/ int DDFModule::Initialize( char chInterchangeLevel, char chLeaderIden, char chCodeExtensionIndicator, char chVersionNumber, char chAppIndicator, const char *pszExtendedCharSet, int nSizeFieldLength, int nSizeFieldPos, int nSizeFieldTag ) { _interchangeLevel = chInterchangeLevel; _leaderIden = chLeaderIden; _inlineCodeExtensionIndicator = chCodeExtensionIndicator; _versionNumber = chVersionNumber; _appIndicator = chAppIndicator; snprintf( _extendedCharSet, sizeof(_extendedCharSet), "%s", pszExtendedCharSet ); _sizeFieldLength = nSizeFieldLength; _sizeFieldPos = nSizeFieldPos; _sizeFieldTag = nSizeFieldTag; return TRUE; } /************************************************************************/ /* Create() */ /************************************************************************/ int DDFModule::Create( const char *pszFilename ) { CPLAssert( fpDDF == NULL ); /* -------------------------------------------------------------------- */ /* Create the file on disk. */ /* -------------------------------------------------------------------- */ fpDDF = VSIFOpenL( pszFilename, "wb+" ); if( fpDDF == NULL ) { CPLError( CE_Failure, CPLE_OpenFailed, "Failed to create file %s, check path and permissions.", pszFilename ); return FALSE; } bReadOnly = FALSE; /* -------------------------------------------------------------------- */ /* Prepare all the field definition information. */ /* -------------------------------------------------------------------- */ int iField; _recLength = 24 + nFieldDefnCount * (_sizeFieldLength+_sizeFieldPos+_sizeFieldTag) + 1; _fieldAreaStart = _recLength; for( iField=0; iField < nFieldDefnCount; iField++ ) { int nLength; papoFieldDefns[iField]->GenerateDDREntry( this, NULL, &nLength ); _recLength += nLength; } /* -------------------------------------------------------------------- */ /* Setup 24 byte leader. */ /* -------------------------------------------------------------------- */ char achLeader[25]; snprintf( achLeader+0, sizeof(achLeader)-0, "%05d", (int) _recLength ); achLeader[5] = _interchangeLevel; achLeader[6] = _leaderIden; achLeader[7] = _inlineCodeExtensionIndicator; achLeader[8] = _versionNumber; achLeader[9] = _appIndicator; snprintf( achLeader+10, sizeof(achLeader)-10, "%02d", (int) _fieldControlLength ); snprintf( achLeader+12, sizeof(achLeader)-12, "%05d", (int) _fieldAreaStart ); strncpy( achLeader+17, _extendedCharSet, 3 ); snprintf( achLeader+20, sizeof(achLeader)-20, "%1d", (int) _sizeFieldLength ); snprintf( achLeader+21, sizeof(achLeader)-21, "%1d", (int) _sizeFieldPos ); achLeader[22] = '0'; snprintf( achLeader+23, sizeof(achLeader)-23, "%1d", (int) _sizeFieldTag ); int bRet = VSIFWriteL( achLeader, 24, 1, fpDDF ) > 0; /* -------------------------------------------------------------------- */ /* Write out directory entries. */ /* -------------------------------------------------------------------- */ int nOffset = 0; for( iField=0; iField < nFieldDefnCount; iField++ ) { char achDirEntry[255]; char szFormat[32]; int nLength; CPLAssert(_sizeFieldLength + _sizeFieldPos + _sizeFieldTag < (int)sizeof(achDirEntry)); papoFieldDefns[iField]->GenerateDDREntry( this, NULL, &nLength ); CPLAssert( (int)strlen(papoFieldDefns[iField]->GetName()) == _sizeFieldTag ); strcpy( achDirEntry, papoFieldDefns[iField]->GetName() ); snprintf(szFormat, sizeof(szFormat), "%%0%dd", (int)_sizeFieldLength); snprintf( achDirEntry + _sizeFieldTag, sizeof(achDirEntry) - _sizeFieldTag, szFormat, nLength ); snprintf(szFormat, sizeof(szFormat), "%%0%dd", (int)_sizeFieldPos); snprintf( achDirEntry + _sizeFieldTag + _sizeFieldLength, sizeof(achDirEntry) - _sizeFieldTag - _sizeFieldLength, szFormat, nOffset ); nOffset += nLength; bRet &= VSIFWriteL( achDirEntry, _sizeFieldLength + _sizeFieldPos + _sizeFieldTag, 1, fpDDF ) > 0; } char chUT = DDF_FIELD_TERMINATOR; bRet &= VSIFWriteL( &chUT, 1, 1, fpDDF ) > 0; /* -------------------------------------------------------------------- */ /* Write out the field descriptions themselves. */ /* -------------------------------------------------------------------- */ for( iField = 0; iField < nFieldDefnCount; iField++ ) { char *pachData = NULL; int nLength = 0; papoFieldDefns[iField]->GenerateDDREntry( this, &pachData, &nLength ); bRet &= VSIFWriteL( pachData, nLength, 1, fpDDF ) > 0; CPLFree( pachData ); } return bRet ? TRUE : FALSE; } /************************************************************************/ /* Dump() */ /************************************************************************/ /** * Write out module info to debugging file. * * A variety of information about the module is written to the debugging * file. This includes all the field and subfield definitions read from * the header. * * @param fp The standard IO file handle to write to. i.e. stderr. */ void DDFModule::Dump( FILE * fp ) { fprintf( fp, "DDFModule:\n" ); fprintf( fp, " _recLength = %d\n", _recLength ); fprintf( fp, " _interchangeLevel = %c\n", _interchangeLevel ); fprintf( fp, " _leaderIden = %c\n", _leaderIden ); fprintf( fp, " _inlineCodeExtensionIndicator = %c\n", _inlineCodeExtensionIndicator ); fprintf( fp, " _versionNumber = %c\n", _versionNumber ); fprintf( fp, " _appIndicator = %c\n", _appIndicator ); fprintf( fp, " _extendedCharSet = `%s'\n", _extendedCharSet ); fprintf( fp, " _fieldControlLength = %d\n", _fieldControlLength ); fprintf( fp, " _fieldAreaStart = %d\n", _fieldAreaStart ); fprintf( fp, " _sizeFieldLength = %d\n", _sizeFieldLength ); fprintf( fp, " _sizeFieldPos = %d\n", _sizeFieldPos ); fprintf( fp, " _sizeFieldTag = %d\n", _sizeFieldTag ); for( int i = 0; i < nFieldDefnCount; i++ ) { papoFieldDefns[i]->Dump( fp ); } } /************************************************************************/ /* FindFieldDefn() */ /************************************************************************/ /** * Fetch the definition of the named field. * * This function will scan the DDFFieldDefn's on this module, to find * one with the indicated field name. * * @param pszFieldName The name of the field to search for. The comparison is * case insensitive. * * @return A pointer to the request DDFFieldDefn object is returned, or NULL * if none matching the name are found. The return object remains owned by * the DDFModule, and should not be deleted by application code. */ DDFFieldDefn *DDFModule::FindFieldDefn( const char *pszFieldName ) { int i; /* -------------------------------------------------------------------- */ /* This pass tries to reduce the cost of comparing strings by */ /* first checking the first character, and by using strcmp() */ /* -------------------------------------------------------------------- */ for( i = 0; i < nFieldDefnCount; i++ ) { const char *pszThisName = papoFieldDefns[i]->GetName(); if( *pszThisName == *pszFieldName && strcmp( pszFieldName+1, pszThisName+1) == 0 ) return papoFieldDefns[i]; } /* -------------------------------------------------------------------- */ /* Now do a more general check. Application code may not */ /* always use the correct name case. */ /* -------------------------------------------------------------------- */ for( i = 0; i < nFieldDefnCount; i++ ) { if( EQUAL(pszFieldName, papoFieldDefns[i]->GetName()) ) return papoFieldDefns[i]; } return NULL; } /************************************************************************/ /* ReadRecord() */ /* */ /* Read one record from the file, and return to the */ /* application. The returned record is owned by the module, */ /* and is reused from call to call in order to preserve headers */ /* when they aren't being re-read from record to record. */ /************************************************************************/ /** * Read one record from the file. * * @return A pointer to a DDFRecord object is returned, or NULL if a read * error, or end of file occurs. The returned record is owned by the * module, and should not be deleted by the application. The record is * only valid until the next ReadRecord() at which point it is overwritten. */ DDFRecord *DDFModule::ReadRecord() { if( poRecord == NULL ) poRecord = new DDFRecord( this ); if( poRecord->Read() ) return poRecord; else return NULL; } /************************************************************************/ /* AddField() */ /************************************************************************/ /** * Add new field definition. * * Field definitions may only be added to DDFModules being used for * writing, not those being used for reading. Ownership of the * DDFFieldDefn object is taken by the DDFModule. * * @param poNewFDefn definition to be added to the module. */ void DDFModule::AddField( DDFFieldDefn *poNewFDefn ) { nFieldDefnCount++; papoFieldDefns = (DDFFieldDefn **) CPLRealloc(papoFieldDefns, sizeof(void*)*nFieldDefnCount); papoFieldDefns[nFieldDefnCount-1] = poNewFDefn; } /************************************************************************/ /* GetField() */ /************************************************************************/ /** * Fetch a field definition by index. * * @param i (from 0 to GetFieldCount() - 1. * @return the returned field pointer or NULL if the index is out of range. */ DDFFieldDefn *DDFModule::GetField(int i) { if( i < 0 || i >= nFieldDefnCount ) return NULL; else return papoFieldDefns[i]; } /************************************************************************/ /* AddCloneRecord() */ /* */ /* We want to keep track of cloned records, so we can clean */ /* them up when the module is destroyed. */ /************************************************************************/ void DDFModule::AddCloneRecord( DDFRecord * poRecordIn ) { /* -------------------------------------------------------------------- */ /* Do we need to grow the container array? */ /* -------------------------------------------------------------------- */ if( nCloneCount == nMaxCloneCount ) { nMaxCloneCount = nCloneCount*2 + 20; papoClones = (DDFRecord **) CPLRealloc(papoClones, nMaxCloneCount * sizeof(void*)); } /* -------------------------------------------------------------------- */ /* Add to the list. */ /* -------------------------------------------------------------------- */ papoClones[nCloneCount++] = poRecordIn; } /************************************************************************/ /* RemoveCloneRecord() */ /************************************************************************/ void DDFModule::RemoveCloneRecord( DDFRecord * poRecordIn ) { int i; for( i = 0; i < nCloneCount; i++ ) { if( papoClones[i] == poRecordIn ) { papoClones[i] = papoClones[nCloneCount-1]; nCloneCount--; return; } } CPLAssert( false ); } /************************************************************************/ /* Rewind() */ /************************************************************************/ /** * Return to first record. * * The next call to ReadRecord() will read the first data record in the file. * * @param nOffset the offset in the file to return to. By default this is * -1, a special value indicating that reading should return to the first * data record. Otherwise it is an absolute byte offset in the file. */ void DDFModule::Rewind( long nOffset ) { if( nOffset == -1 ) nOffset = nFirstRecordOffset; if( fpDDF == NULL ) return; if( VSIFSeekL( fpDDF, nOffset, SEEK_SET ) < 0 ) return; if( nOffset == nFirstRecordOffset && poRecord != NULL ) poRecord->Clear(); }