Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: ISO 8211 Access
4 : * Purpose: Implements the DDFModule class.
5 : * Author: Frank Warmerdam, warmerdam@pobox.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 1999, Frank Warmerdam
9 : * Copyright (c) 2011-2013, Even Rouault <even dot rouault at spatialys.com>
10 : *
11 : * SPDX-License-Identifier: MIT
12 : ****************************************************************************/
13 :
14 : #include "cpl_port.h"
15 : #include "iso8211.h"
16 :
17 : #include <algorithm>
18 : #include <array>
19 : #include <cstdio>
20 : #include <cstring>
21 :
22 : #include "cpl_conv.h"
23 : #include "cpl_error.h"
24 : #include "cpl_vsi.h"
25 :
26 : /************************************************************************/
27 : /* DDFModule() */
28 : /************************************************************************/
29 :
30 : /**
31 : * The constructor.
32 : */
33 :
34 : DDFModule::DDFModule() = default;
35 :
36 : /************************************************************************/
37 : /* ~DDFModule() */
38 : /************************************************************************/
39 :
40 : /**
41 : * The destructor.
42 : */
43 :
44 181 : DDFModule::~DDFModule()
45 :
46 : {
47 181 : Close();
48 181 : }
49 :
50 : /************************************************************************/
51 : /* Close() */
52 : /* */
53 : /* Note that closing a file also destroys essentially all other */
54 : /* module datastructures. */
55 : /************************************************************************/
56 :
57 : /**
58 : * Close an ISO 8211 file.
59 : */
60 :
61 202 : void DDFModule::Close()
62 :
63 : {
64 : /* -------------------------------------------------------------------- */
65 : /* Close the file. */
66 : /* -------------------------------------------------------------------- */
67 202 : if (fpDDF != nullptr)
68 : {
69 123 : CPL_IGNORE_RET_VAL(VSIFCloseL(fpDDF));
70 123 : fpDDF = nullptr;
71 : }
72 :
73 202 : poRecord.reset();
74 :
75 202 : apoFieldDefns.clear();
76 202 : }
77 :
78 : /************************************************************************/
79 : /* Open() */
80 : /* */
81 : /* Open an ISO 8211 file, and read the DDR record to build the */
82 : /* field definitions. */
83 : /************************************************************************/
84 :
85 : /**
86 : * Open a ISO 8211 (DDF) file for reading.
87 : *
88 : * If the open succeeds the data descriptive record (DDR) will have been
89 : * read, and all the field and subfield definitions will be available.
90 : *
91 : * @param pszFilename The name of the file to open.
92 : * @param bFailQuietly If FALSE a CPL Error is issued for non-8211 files,
93 : * otherwise quietly return NULL.
94 : *
95 : * @return FALSE if the open fails or TRUE if it succeeds. Errors messages
96 : * are issued internally with CPLError().
97 : */
98 :
99 143 : int DDFModule::Open(const char *pszFilename, int bFailQuietly)
100 :
101 : {
102 143 : constexpr int nLeaderSize = 24;
103 :
104 : /* -------------------------------------------------------------------- */
105 : /* Close the existing file if there is one. */
106 : /* -------------------------------------------------------------------- */
107 143 : if (fpDDF != nullptr)
108 0 : Close();
109 :
110 : /* -------------------------------------------------------------------- */
111 : /* Open the file. */
112 : /* -------------------------------------------------------------------- */
113 : VSIStatBufL sStat;
114 143 : if (VSIStatL(pszFilename, &sStat) == 0 && !VSI_ISDIR(sStat.st_mode))
115 104 : fpDDF = VSIFOpenL(pszFilename, "rb");
116 :
117 143 : if (fpDDF == nullptr)
118 : {
119 39 : if (!bFailQuietly)
120 0 : CPLError(CE_Failure, CPLE_OpenFailed,
121 : "Unable to open DDF file `%s'.", pszFilename);
122 39 : return FALSE;
123 : }
124 :
125 : /* -------------------------------------------------------------------- */
126 : /* Read the 24 byte leader. */
127 : /* -------------------------------------------------------------------- */
128 : char achLeader[nLeaderSize];
129 :
130 104 : if (static_cast<int>(VSIFReadL(achLeader, 1, nLeaderSize, fpDDF)) !=
131 : nLeaderSize)
132 : {
133 0 : CPL_IGNORE_RET_VAL(VSIFCloseL(fpDDF));
134 0 : fpDDF = nullptr;
135 :
136 0 : if (!bFailQuietly)
137 0 : CPLError(CE_Failure, CPLE_FileIO,
138 : "Leader is short on DDF file `%s'.", pszFilename);
139 :
140 0 : return FALSE;
141 : }
142 :
143 : /* -------------------------------------------------------------------- */
144 : /* Verify that this appears to be a valid DDF file. */
145 : /* -------------------------------------------------------------------- */
146 104 : int i, bValid = TRUE;
147 :
148 2600 : for (i = 0; i < nLeaderSize; i++)
149 : {
150 2496 : if (achLeader[i] < 32 || achLeader[i] > 126)
151 0 : bValid = FALSE;
152 : }
153 :
154 104 : if (achLeader[5] != '1' && achLeader[5] != '2' && achLeader[5] != '3')
155 0 : bValid = FALSE;
156 :
157 104 : if (achLeader[6] != 'L')
158 0 : bValid = FALSE;
159 104 : if (achLeader[8] != '1' && achLeader[8] != ' ')
160 0 : bValid = FALSE;
161 :
162 : /* -------------------------------------------------------------------- */
163 : /* Extract information from leader. */
164 : /* -------------------------------------------------------------------- */
165 :
166 104 : if (bValid)
167 : {
168 104 : _recLength = DDFScanInt(achLeader + 0, 5);
169 104 : _interchangeLevel = achLeader[5];
170 104 : _leaderIden = achLeader[6];
171 104 : _inlineCodeExtensionIndicator = achLeader[7];
172 104 : _versionNumber = achLeader[8];
173 104 : _appIndicator = achLeader[9];
174 104 : _fieldControlLength = DDFScanInt(achLeader + 10, 2);
175 104 : _fieldAreaStart = DDFScanInt(achLeader + 12, 5);
176 104 : _extendedCharSet[0] = achLeader[17];
177 104 : _extendedCharSet[1] = achLeader[18];
178 104 : _extendedCharSet[2] = achLeader[19];
179 104 : _sizeFieldLength = DDFScanInt(achLeader + 20, 1);
180 104 : _sizeFieldPos = DDFScanInt(achLeader + 21, 1);
181 104 : _sizeFieldTag = DDFScanInt(achLeader + 23, 1);
182 :
183 104 : if (_recLength < nLeaderSize || _fieldControlLength <= 0 ||
184 104 : _fieldAreaStart < 24 || _sizeFieldLength <= 0 ||
185 104 : _sizeFieldPos <= 0 || _sizeFieldTag <= 0)
186 : {
187 0 : bValid = FALSE;
188 : }
189 : }
190 :
191 : /* -------------------------------------------------------------------- */
192 : /* If the header is invalid, then clean up, report the error */
193 : /* and return. */
194 : /* -------------------------------------------------------------------- */
195 104 : if (!bValid)
196 : {
197 0 : CPL_IGNORE_RET_VAL(VSIFCloseL(fpDDF));
198 0 : fpDDF = nullptr;
199 :
200 0 : if (!bFailQuietly)
201 0 : CPLError(CE_Failure, CPLE_AppDefined,
202 : "File `%s' does not appear to have\n"
203 : "a valid ISO 8211 header.\n",
204 : pszFilename);
205 0 : return FALSE;
206 : }
207 :
208 : /* -------------------------------------------------------------------- */
209 : /* Read the whole record info memory. */
210 : /* -------------------------------------------------------------------- */
211 208 : std::string osRecord;
212 104 : osRecord.assign(achLeader, nLeaderSize);
213 104 : osRecord.resize(_recLength);
214 :
215 104 : if (static_cast<int>(VSIFReadL(osRecord.data() + nLeaderSize, 1,
216 104 : _recLength - nLeaderSize, fpDDF)) !=
217 104 : _recLength - nLeaderSize)
218 : {
219 0 : if (!bFailQuietly)
220 0 : CPLError(CE_Failure, CPLE_FileIO,
221 : "Header record is short on DDF file `%s'.", pszFilename);
222 :
223 0 : return FALSE;
224 : }
225 :
226 : /* -------------------------------------------------------------------- */
227 : /* First make a pass counting the directory entries. */
228 : /* -------------------------------------------------------------------- */
229 104 : int nFieldEntryWidth, nFDCount = 0;
230 :
231 104 : nFieldEntryWidth = _sizeFieldLength + _sizeFieldPos + _sizeFieldTag;
232 :
233 1458 : for (i = nLeaderSize; i + nFieldEntryWidth <= _recLength;
234 1354 : i += nFieldEntryWidth)
235 : {
236 1458 : if (osRecord[i] == DDF_FIELD_TERMINATOR)
237 104 : break;
238 :
239 1354 : nFDCount++;
240 : }
241 :
242 : /* -------------------------------------------------------------------- */
243 : /* Allocate, and read field definitions. */
244 : /* -------------------------------------------------------------------- */
245 1458 : for (i = 0; i < nFDCount; i++)
246 : {
247 : char szTag[128];
248 1354 : int nEntryOffset = nLeaderSize + i * nFieldEntryWidth;
249 : int nFieldLength, nFieldPos;
250 :
251 1354 : strncpy(szTag, osRecord.c_str() + nEntryOffset, _sizeFieldTag);
252 1354 : szTag[_sizeFieldTag] = '\0';
253 :
254 1354 : nEntryOffset += _sizeFieldTag;
255 : nFieldLength =
256 1354 : DDFScanInt(osRecord.c_str() + nEntryOffset, _sizeFieldLength);
257 :
258 1354 : nEntryOffset += _sizeFieldLength;
259 1354 : nFieldPos = DDFScanInt(osRecord.c_str() + nEntryOffset, _sizeFieldPos);
260 :
261 1354 : if (nFieldPos < 0 || nFieldPos > INT_MAX - _fieldAreaStart ||
262 : nFieldLength <
263 1354 : 2 || // DDFFieldDefn::Initialize() assumes at least 2 bytes
264 1354 : _recLength - (_fieldAreaStart + nFieldPos) < nFieldLength)
265 : {
266 0 : if (!bFailQuietly)
267 0 : CPLError(CE_Failure, CPLE_FileIO,
268 : "Header record invalid on DDF file `%s'.",
269 : pszFilename);
270 :
271 0 : return FALSE;
272 : }
273 :
274 2708 : auto poFDefn = std::make_unique<DDFFieldDefn>();
275 1354 : if (poFDefn->Initialize(this, szTag, nFieldLength,
276 1354 : osRecord.c_str() + _fieldAreaStart + nFieldPos))
277 1354 : AddField(std::move(poFDefn));
278 : }
279 :
280 : /* -------------------------------------------------------------------- */
281 : /* Record the current file offset, the beginning of the first */
282 : /* data record. */
283 : /* -------------------------------------------------------------------- */
284 104 : nFirstRecordOffset = static_cast<long>(VSIFTellL(fpDDF));
285 :
286 104 : return TRUE;
287 : }
288 :
289 : /************************************************************************/
290 : /* Initialize() */
291 : /************************************************************************/
292 :
293 20 : int DDFModule::Initialize(char chInterchangeLevel, char chLeaderIden,
294 : char chCodeExtensionIndicator, char chVersionNumber,
295 : char chAppIndicator,
296 : const std::array<char, 3> &achExtendedCharSet,
297 : int nSizeFieldLength, int nSizeFieldPos,
298 : int nSizeFieldTag)
299 :
300 : {
301 20 : _interchangeLevel = chInterchangeLevel;
302 20 : _leaderIden = chLeaderIden;
303 20 : _inlineCodeExtensionIndicator = chCodeExtensionIndicator;
304 20 : _versionNumber = chVersionNumber;
305 20 : _appIndicator = chAppIndicator;
306 20 : _extendedCharSet = achExtendedCharSet;
307 20 : _sizeFieldLength = nSizeFieldLength;
308 20 : _sizeFieldPos = nSizeFieldPos;
309 20 : _sizeFieldTag = nSizeFieldTag;
310 :
311 20 : return TRUE;
312 : }
313 :
314 : /************************************************************************/
315 : /* Create() */
316 : /************************************************************************/
317 :
318 20 : int DDFModule::Create(const char *pszFilename)
319 :
320 : {
321 20 : CPLAssert(fpDDF == nullptr);
322 :
323 : /* -------------------------------------------------------------------- */
324 : /* Create the file on disk. */
325 : /* -------------------------------------------------------------------- */
326 20 : fpDDF = VSIFOpenL(pszFilename, "wb+");
327 20 : if (fpDDF == nullptr)
328 : {
329 1 : CPLError(CE_Failure, CPLE_OpenFailed,
330 : "Failed to create file %s, check path and permissions.",
331 : pszFilename);
332 1 : return FALSE;
333 : }
334 :
335 19 : bReadOnly = FALSE;
336 :
337 : /* -------------------------------------------------------------------- */
338 : /* Prepare all the field definition information. */
339 : /* -------------------------------------------------------------------- */
340 19 : _recLength =
341 : 24 +
342 19 : GetFieldCount() * (_sizeFieldLength + _sizeFieldPos + _sizeFieldTag) +
343 : 1;
344 :
345 19 : _fieldAreaStart = _recLength;
346 :
347 399 : for (auto &poFieldDefn : apoFieldDefns)
348 : {
349 : int nLength;
350 :
351 380 : poFieldDefn->GenerateDDREntry(this, nullptr, &nLength);
352 380 : _recLength += nLength;
353 : }
354 :
355 : /* -------------------------------------------------------------------- */
356 : /* Setup 24 byte leader. */
357 : /* -------------------------------------------------------------------- */
358 : char achLeader[25];
359 :
360 19 : snprintf(achLeader + 0, sizeof(achLeader) - 0, "%05d", _recLength);
361 19 : achLeader[5] = _interchangeLevel;
362 19 : achLeader[6] = _leaderIden;
363 19 : achLeader[7] = _inlineCodeExtensionIndicator;
364 19 : achLeader[8] = _versionNumber;
365 19 : achLeader[9] = _appIndicator;
366 19 : snprintf(achLeader + 10, sizeof(achLeader) - 10, "%02d",
367 : _fieldControlLength);
368 19 : snprintf(achLeader + 12, sizeof(achLeader) - 12, "%05d", _fieldAreaStart);
369 19 : memcpy(achLeader + 17, _extendedCharSet.data(), 3);
370 19 : snprintf(achLeader + 20, sizeof(achLeader) - 20, "%1d", _sizeFieldLength);
371 19 : snprintf(achLeader + 21, sizeof(achLeader) - 21, "%1d", _sizeFieldPos);
372 19 : achLeader[22] = '0';
373 19 : snprintf(achLeader + 23, sizeof(achLeader) - 23, "%1d", _sizeFieldTag);
374 19 : int bRet = VSIFWriteL(achLeader, 24, 1, fpDDF) > 0;
375 :
376 : /* -------------------------------------------------------------------- */
377 : /* Write out directory entries. */
378 : /* -------------------------------------------------------------------- */
379 19 : int nOffset = 0;
380 399 : for (auto &poFieldDefn : apoFieldDefns)
381 : {
382 : char achDirEntry[255];
383 : char szFormat[32];
384 : int nLength;
385 :
386 380 : CPLAssert(_sizeFieldLength + _sizeFieldPos + _sizeFieldTag <
387 : static_cast<int>(sizeof(achDirEntry)));
388 :
389 380 : poFieldDefn->GenerateDDREntry(this, nullptr, &nLength);
390 :
391 380 : CPLAssert(static_cast<int>(strlen(poFieldDefn->GetName())) ==
392 : _sizeFieldTag);
393 380 : snprintf(achDirEntry, sizeof(achDirEntry), "%s",
394 : poFieldDefn->GetName());
395 380 : snprintf(szFormat, sizeof(szFormat), "%%0%dd", _sizeFieldLength);
396 380 : snprintf(achDirEntry + _sizeFieldTag,
397 380 : sizeof(achDirEntry) - _sizeFieldTag, szFormat, nLength);
398 380 : snprintf(szFormat, sizeof(szFormat), "%%0%dd", _sizeFieldPos);
399 380 : snprintf(achDirEntry + _sizeFieldTag + _sizeFieldLength,
400 380 : sizeof(achDirEntry) - _sizeFieldTag - _sizeFieldLength,
401 : szFormat, nOffset);
402 380 : nOffset += nLength;
403 :
404 760 : bRet &= VSIFWriteL(achDirEntry,
405 380 : _sizeFieldLength + _sizeFieldPos + _sizeFieldTag, 1,
406 380 : fpDDF) > 0;
407 : }
408 :
409 19 : char chUT = DDF_FIELD_TERMINATOR;
410 19 : bRet &= VSIFWriteL(&chUT, 1, 1, fpDDF) > 0;
411 :
412 : /* -------------------------------------------------------------------- */
413 : /* Write out the field descriptions themselves. */
414 : /* -------------------------------------------------------------------- */
415 399 : for (auto &poFieldDefn : apoFieldDefns)
416 : {
417 380 : char *pachData = nullptr;
418 380 : int nLength = 0;
419 :
420 380 : poFieldDefn->GenerateDDREntry(this, &pachData, &nLength);
421 380 : bRet &= VSIFWriteL(pachData, nLength, 1, fpDDF) > 0;
422 380 : CPLFree(pachData);
423 : }
424 :
425 19 : return bRet ? TRUE : FALSE;
426 : }
427 :
428 : /************************************************************************/
429 : /* Dump() */
430 : /************************************************************************/
431 :
432 : /**
433 : * Write out module info to debugging file.
434 : *
435 : * A variety of information about the module is written to the debugging
436 : * file. This includes all the field and subfield definitions read from
437 : * the header.
438 : *
439 : * @param fp The standard IO file handle to write to. i.e. stderr.
440 : */
441 :
442 0 : void DDFModule::Dump(FILE *fp) const
443 :
444 : {
445 0 : fprintf(fp, "DDFModule:\n");
446 0 : fprintf(fp, " _recLength = %d\n", _recLength);
447 0 : fprintf(fp, " _interchangeLevel = %c\n", _interchangeLevel);
448 0 : fprintf(fp, " _leaderIden = %c\n", _leaderIden);
449 0 : fprintf(fp, " _inlineCodeExtensionIndicator = %c\n",
450 0 : _inlineCodeExtensionIndicator);
451 0 : fprintf(fp, " _versionNumber = %c\n", _versionNumber);
452 0 : fprintf(fp, " _appIndicator = %c\n", _appIndicator);
453 0 : fprintf(fp, " _extendedCharSet = `%c%c%c'\n", _extendedCharSet[0],
454 0 : _extendedCharSet[1], _extendedCharSet[2]);
455 0 : fprintf(fp, " _fieldControlLength = %d\n", _fieldControlLength);
456 0 : fprintf(fp, " _fieldAreaStart = %d\n", _fieldAreaStart);
457 0 : fprintf(fp, " _sizeFieldLength = %d\n", _sizeFieldLength);
458 0 : fprintf(fp, " _sizeFieldPos = %d\n", _sizeFieldPos);
459 0 : fprintf(fp, " _sizeFieldTag = %d\n", _sizeFieldTag);
460 :
461 0 : for (const auto &poFieldDefn : apoFieldDefns)
462 : {
463 0 : poFieldDefn->Dump(fp);
464 : }
465 0 : }
466 :
467 : /************************************************************************/
468 : /* FindFieldDefn() */
469 : /************************************************************************/
470 :
471 : /**
472 : * Fetch the definition of the named field.
473 : *
474 : * This function will scan the DDFFieldDefn's on this module, to find
475 : * one with the indicated field name.
476 : *
477 : * @param pszFieldName The name of the field to search for. The comparison is
478 : * case insensitive.
479 : *
480 : * @return A pointer to the request DDFFieldDefn object is returned, or NULL
481 : * if none matching the name are found. The return object remains owned by
482 : * the DDFModule, and should not be deleted by application code.
483 : */
484 :
485 111555 : const DDFFieldDefn *DDFModule::FindFieldDefn(const char *pszFieldName) const
486 :
487 : {
488 : /* -------------------------------------------------------------------- */
489 : /* This pass tries to reduce the cost of comparing strings by */
490 : /* first checking the first character, and by using strcmp() */
491 : /* -------------------------------------------------------------------- */
492 888316 : for (const auto &poFieldDefn : apoFieldDefns)
493 : {
494 888313 : const char *pszThisName = poFieldDefn->GetName();
495 :
496 888313 : if (*pszThisName == *pszFieldName && *pszFieldName != '\0' &&
497 200032 : strcmp(pszFieldName + 1, pszThisName + 1) == 0)
498 111552 : return poFieldDefn.get();
499 : }
500 :
501 : /* -------------------------------------------------------------------- */
502 : /* Now do a more general check. Application code may not */
503 : /* always use the correct name case. */
504 : /* -------------------------------------------------------------------- */
505 9 : for (const auto &poFieldDefn : apoFieldDefns)
506 : {
507 6 : if (EQUAL(pszFieldName, poFieldDefn->GetName()))
508 0 : return poFieldDefn.get();
509 : }
510 :
511 3 : return nullptr;
512 : }
513 :
514 : /************************************************************************/
515 : /* ReadRecord() */
516 : /* */
517 : /* Read one record from the file, and return to the */
518 : /* application. The returned record is owned by the module, */
519 : /* and is reused from call to call in order to preserve headers */
520 : /* when they aren't being re-read from record to record. */
521 : /************************************************************************/
522 :
523 : /**
524 : * Read one record from the file.
525 : *
526 : * @return A pointer to a DDFRecord object is returned, or NULL if a read
527 : * error, or end of file occurs. The returned record is owned by the
528 : * module, and should not be deleted by the application. The record is
529 : * only valid until the next ReadRecord() at which point it is overwritten.
530 : */
531 :
532 28036 : DDFRecord *DDFModule::ReadRecord()
533 :
534 : {
535 28036 : if (poRecord == nullptr)
536 102 : poRecord = std::make_unique<DDFRecord>(this);
537 :
538 28036 : if (poRecord->Read())
539 27954 : return poRecord.get();
540 : else
541 82 : return nullptr;
542 : }
543 :
544 : /************************************************************************/
545 : /* AddField() */
546 : /************************************************************************/
547 :
548 : /**
549 : * Add new field definition.
550 : *
551 : * Field definitions may only be added to DDFModules being used for
552 : * writing, not those being used for reading. Ownership of the
553 : * DDFFieldDefn object is taken by the DDFModule.
554 : *
555 : * @param poNewFDefn definition to be added to the module.
556 : */
557 :
558 1754 : void DDFModule::AddField(std::unique_ptr<DDFFieldDefn> poNewFDefn)
559 :
560 : {
561 1754 : apoFieldDefns.push_back(std::move(poNewFDefn));
562 1754 : }
563 :
564 : /************************************************************************/
565 : /* GetField() */
566 : /************************************************************************/
567 :
568 : /**
569 : * Fetch a field definition by index.
570 : *
571 : * @param i (from 0 to GetFieldCount() - 1.
572 : * @return the returned field pointer or NULL if the index is out of range.
573 : */
574 :
575 40 : DDFFieldDefn *DDFModule::GetField(int i)
576 :
577 : {
578 40 : if (i < 0 || static_cast<size_t>(i) >= apoFieldDefns.size())
579 0 : return nullptr;
580 : else
581 40 : return apoFieldDefns[i].get();
582 : }
583 :
584 : /************************************************************************/
585 : /* Rewind() */
586 : /************************************************************************/
587 :
588 : /**
589 : * Return to first record.
590 : *
591 : * The next call to ReadRecord() will read the first data record in the file.
592 : *
593 : * @param nOffset the offset in the file to return to. By default this is
594 : * -1, a special value indicating that reading should return to the first
595 : * data record. Otherwise it is an absolute byte offset in the file.
596 : */
597 :
598 0 : void DDFModule::Rewind(vsi_l_offset nOffset)
599 :
600 : {
601 0 : if (nOffset == static_cast<vsi_l_offset>(-1))
602 0 : nOffset = nFirstRecordOffset;
603 :
604 0 : if (fpDDF == nullptr)
605 0 : return;
606 :
607 0 : if (VSIFSeekL(fpDDF, nOffset, SEEK_SET) < 0)
608 0 : return;
609 :
610 0 : if (nOffset == nFirstRecordOffset && poRecord != nullptr)
611 0 : poRecord->Clear();
612 : }
|