Line data Source code
1 : /**********************************************************************
2 : *
3 : * Name: mitab_datfile.cpp
4 : * Project: MapInfo TAB Read/Write library
5 : * Language: C++
6 : * Purpose: Implementation of the TABIDFile class used to handle
7 : * reading/writing of the .DAT file
8 : * Author: Daniel Morissette, dmorissette@dmsolutions.ca
9 : *
10 : **********************************************************************
11 : * Copyright (c) 1999-2001, Daniel Morissette
12 : * Copyright (c) 2014, Even Rouault <even.rouault at spatialys.com>
13 : *
14 : * SPDX-License-Identifier: MIT
15 : **********************************************************************/
16 :
17 : #include "cpl_port.h"
18 : #include "mitab.h"
19 :
20 : #include <climits>
21 : #include <cstdio>
22 : #include <cstdlib>
23 : #include <cstring>
24 : #if HAVE_FCNTL_H
25 : #include <fcntl.h>
26 : #endif
27 : #include <algorithm>
28 : #include <string>
29 :
30 : #include "cpl_conv.h"
31 : #include "cpl_error.h"
32 : #include "cpl_string.h"
33 : #include "cpl_vsi.h"
34 : #include "mitab_priv.h"
35 : #include "ogr_core.h"
36 : #include "ogr_feature.h"
37 : #include "ogr_p.h"
38 :
39 : /*=====================================================================
40 : * class TABDATFile
41 : *
42 : * Note that the .DAT files are .DBF files with some exceptions:
43 : *
44 : * All fields in the DBF header are defined as 'C' type (strings),
45 : * even for binary integers. So we have to look in the associated .TAB
46 : * file to find the real field definition.
47 : *
48 : * Even though binary integers are defined as 'C' type, they are stored
49 : * in binary form inside a 4 bytes string field.
50 : *====================================================================*/
51 :
52 : /**********************************************************************
53 : * TABDATFile::TABDATFile()
54 : *
55 : * Constructor.
56 : **********************************************************************/
57 1463 : TABDATFile::TABDATFile(const char *pszEncoding)
58 : : m_pszFname(nullptr), m_fp(nullptr), m_eAccessMode(TABRead),
59 : m_eTableType(TABTableNative), m_poHeaderBlock(nullptr), m_numFields(-1),
60 : m_pasFieldDef(nullptr), m_poRecordBlock(nullptr), m_nBlockSize(0),
61 : m_nRecordSize(-1), m_nCurRecordId(-1), m_bCurRecordDeletedFlag(FALSE),
62 : m_numRecords(-1), m_nFirstRecordPtr(0), m_bWriteHeaderInitialized(FALSE),
63 : m_bWriteEOF(FALSE), m_bUpdated(FALSE),
64 1463 : m_osEncoding(pszEncoding), m_szBuffer{}
65 : {
66 1463 : }
67 :
68 : /**********************************************************************
69 : * TABDATFile::~TABDATFile()
70 : *
71 : * Destructor.
72 : **********************************************************************/
73 1463 : TABDATFile::~TABDATFile()
74 : {
75 1463 : Close();
76 1463 : }
77 :
78 : /**********************************************************************
79 : * TABDATFile::Open()
80 : *
81 : * Compatibility layer with new interface.
82 : * Return 0 on success, -1 in case of failure.
83 : **********************************************************************/
84 :
85 0 : int TABDATFile::Open(const char *pszFname, const char *pszAccess,
86 : TABTableType eTableType)
87 : {
88 : // cppcheck-suppress nullPointer
89 0 : if (STARTS_WITH_CI(pszAccess, "r"))
90 : {
91 0 : return Open(pszFname, TABRead, eTableType);
92 : }
93 0 : else if (STARTS_WITH_CI(pszAccess, "w"))
94 : {
95 0 : return Open(pszFname, TABWrite, eTableType);
96 : }
97 : else
98 : {
99 0 : CPLError(CE_Failure, CPLE_FileIO,
100 : "Open() failed: access mode \"%s\" not supported", pszAccess);
101 0 : return -1;
102 : }
103 : }
104 :
105 : /**********************************************************************
106 : * TABDATFile::Open()
107 : *
108 : * Open a .DAT file, and initialize the structures to be ready to read
109 : * records from it.
110 : *
111 : * We currently support NATIVE and DBF tables for reading, and only
112 : * NATIVE tables for writing.
113 : *
114 : * Returns 0 on success, -1 on error.
115 : **********************************************************************/
116 1494 : int TABDATFile::Open(const char *pszFname, TABAccess eAccess,
117 : TABTableType eTableType /*=TABNativeTable*/)
118 : {
119 1494 : if (m_fp)
120 : {
121 0 : CPLError(CE_Failure, CPLE_FileIO,
122 : "Open() failed: object already contains an open file");
123 0 : return -1;
124 : }
125 :
126 : // Validate access mode and make sure we use binary access.
127 1494 : const char *pszAccess = nullptr;
128 1494 : if (eAccess == TABRead &&
129 0 : (eTableType == TABTableNative || eTableType == TABTableDBF))
130 : {
131 260 : pszAccess = "rb";
132 : }
133 1234 : else if (eAccess == TABWrite && eTableType == TABTableNative)
134 : {
135 154 : pszAccess = "wb+";
136 : }
137 1080 : else if (eAccess == TABReadWrite && eTableType == TABTableNative)
138 : {
139 1080 : pszAccess = "rb+";
140 : }
141 : else
142 : {
143 0 : CPLError(CE_Failure, CPLE_FileIO,
144 : "Open() failed: access mode \"%d\" "
145 : "not supported with eTableType=%d",
146 : eAccess, eTableType);
147 0 : return -1;
148 : }
149 1494 : m_eAccessMode = eAccess;
150 :
151 : // Open file for reading.
152 1494 : m_pszFname = CPLStrdup(pszFname);
153 1494 : m_fp = VSIFOpenL(m_pszFname, pszAccess);
154 1494 : m_eTableType = eTableType;
155 :
156 1494 : if (m_fp == nullptr)
157 : {
158 0 : CPLError(CE_Failure, CPLE_FileIO, "Open() failed for %s", m_pszFname);
159 0 : CPLFree(m_pszFname);
160 0 : m_pszFname = nullptr;
161 0 : return -1;
162 : }
163 :
164 1494 : if (m_eAccessMode == TABRead || m_eAccessMode == TABReadWrite)
165 : {
166 : // READ ACCESS:
167 : // Read .DAT file header (record size, num records, etc...)
168 : // m_poHeaderBlock will be reused later to read field definition
169 1340 : m_poHeaderBlock = new TABRawBinBlock(m_eAccessMode, TRUE);
170 1340 : CPL_IGNORE_RET_VAL(m_poHeaderBlock->ReadFromFile(m_fp, 0, 32));
171 :
172 1340 : m_poHeaderBlock->ReadByte(); // Table type ??? 0x03
173 1340 : m_poHeaderBlock->ReadByte(); // Last update year
174 1340 : m_poHeaderBlock->ReadByte(); // Last update month
175 1340 : m_poHeaderBlock->ReadByte(); // Last update day
176 :
177 1340 : m_numRecords = m_poHeaderBlock->ReadInt32();
178 1340 : m_nFirstRecordPtr = m_poHeaderBlock->ReadInt16();
179 1340 : m_nRecordSize = m_poHeaderBlock->ReadInt16();
180 1340 : if (m_nFirstRecordPtr < 32 || m_nRecordSize <= 0 || m_numRecords < 0)
181 : {
182 0 : VSIFCloseL(m_fp);
183 0 : m_fp = nullptr;
184 0 : CPLFree(m_pszFname);
185 0 : m_pszFname = nullptr;
186 0 : delete m_poHeaderBlock;
187 0 : m_poHeaderBlock = nullptr;
188 0 : return -1;
189 : }
190 :
191 : // Limit number of records to avoid int overflow
192 1340 : if (m_numRecords > INT_MAX / m_nRecordSize ||
193 1340 : m_nFirstRecordPtr > INT_MAX - m_numRecords * m_nRecordSize)
194 : {
195 0 : m_numRecords = (INT_MAX - m_nFirstRecordPtr) / m_nRecordSize;
196 : }
197 :
198 1340 : m_numFields = m_nFirstRecordPtr / 32 - 1;
199 :
200 : // Read the field definitions.
201 : // First 32 bytes field definition starts at byte 32 in file.
202 1340 : m_pasFieldDef = static_cast<TABDATFieldDef *>(
203 1340 : CPLCalloc(m_numFields, sizeof(TABDATFieldDef)));
204 :
205 2975 : for (int i = 0; i < m_numFields; i++)
206 : {
207 1635 : m_poHeaderBlock->GotoByteInFile((i + 1) * 32);
208 1635 : m_poHeaderBlock->ReadBytes(
209 1635 : 11, reinterpret_cast<GByte *>(m_pasFieldDef[i].szName));
210 1635 : m_pasFieldDef[i].szName[10] = '\0';
211 1635 : m_pasFieldDef[i].cType =
212 1635 : static_cast<char>(m_poHeaderBlock->ReadByte());
213 :
214 1635 : m_poHeaderBlock->ReadInt32(); // Skip Bytes 12-15
215 1635 : m_pasFieldDef[i].byLength = m_poHeaderBlock->ReadByte();
216 1635 : m_pasFieldDef[i].byDecimals = m_poHeaderBlock->ReadByte();
217 :
218 1635 : m_pasFieldDef[i].eTABType = TABFUnknown;
219 : }
220 :
221 : // Establish a good record block size to use based on record size, and
222 : // then create m_poRecordBlock.
223 : // Record block size has to be a multiple of record size.
224 1340 : m_nBlockSize = ((1024 / m_nRecordSize) + 1) * m_nRecordSize;
225 1340 : m_nBlockSize = std::min(m_nBlockSize, (m_numRecords * m_nRecordSize));
226 :
227 1340 : CPLAssert(m_poRecordBlock == nullptr);
228 1340 : m_poRecordBlock = new TABRawBinBlock(m_eAccessMode, FALSE);
229 1340 : m_poRecordBlock->InitNewBlock(m_fp, m_nBlockSize);
230 1340 : m_poRecordBlock->SetFirstBlockPtr(m_nFirstRecordPtr);
231 :
232 1340 : m_bWriteHeaderInitialized = TRUE;
233 : }
234 : else
235 : {
236 : // WRITE ACCESS:
237 : // Set acceptable defaults for all class members.
238 : // The real header initialization will be done when the first
239 : // record is written.
240 154 : m_poHeaderBlock = nullptr;
241 :
242 154 : m_numRecords = 0;
243 154 : m_nFirstRecordPtr = 0;
244 154 : m_nRecordSize = 0;
245 154 : m_numFields = 0;
246 154 : m_pasFieldDef = nullptr;
247 154 : m_bWriteHeaderInitialized = FALSE;
248 : }
249 :
250 1494 : return 0;
251 : }
252 :
253 : /**********************************************************************
254 : * TABDATFile::Close()
255 : *
256 : * Close current file, and release all memory used.
257 : *
258 : * Returns 0 on success, -1 on error.
259 : **********************************************************************/
260 2957 : int TABDATFile::Close()
261 : {
262 2957 : if (m_fp == nullptr)
263 1463 : return 0;
264 :
265 : // Write access: Update the header with number of records, etc.
266 : // and add a CTRL-Z char at the end of the file.
267 1494 : if (m_eAccessMode != TABRead)
268 : {
269 1234 : SyncToDisk();
270 : }
271 :
272 : // Delete all structures
273 1494 : if (m_poHeaderBlock)
274 : {
275 1494 : delete m_poHeaderBlock;
276 1494 : m_poHeaderBlock = nullptr;
277 : }
278 :
279 1494 : if (m_poRecordBlock)
280 : {
281 1494 : delete m_poRecordBlock;
282 1494 : m_poRecordBlock = nullptr;
283 : }
284 :
285 : // Close file
286 1494 : VSIFCloseL(m_fp);
287 1494 : m_fp = nullptr;
288 :
289 1494 : CPLFree(m_pszFname);
290 1494 : m_pszFname = nullptr;
291 :
292 1494 : CPLFree(m_pasFieldDef);
293 1494 : m_pasFieldDef = nullptr;
294 :
295 1494 : m_numFields = -1;
296 1494 : m_numRecords = -1;
297 1494 : m_nFirstRecordPtr = 0;
298 1494 : m_nBlockSize = 0;
299 1494 : m_nRecordSize = -1;
300 1494 : m_nCurRecordId = -1;
301 1494 : m_bWriteHeaderInitialized = FALSE;
302 1494 : m_bWriteEOF = FALSE;
303 1494 : m_bUpdated = FALSE;
304 :
305 1494 : return 0;
306 : }
307 :
308 : /************************************************************************/
309 : /* SyncToDisk() */
310 : /************************************************************************/
311 :
312 1353 : int TABDATFile::SyncToDisk()
313 : {
314 1353 : if (m_fp == nullptr)
315 0 : return 0;
316 :
317 1353 : if (m_eAccessMode == TABRead)
318 : {
319 0 : CPLError(CE_Failure, CPLE_NotSupported,
320 : "SyncToDisk() can be used only with Write access.");
321 0 : return -1;
322 : }
323 :
324 1353 : if (!m_bUpdated && m_bWriteHeaderInitialized)
325 114 : return 0;
326 :
327 : // No need to call. CommitRecordToFile(). It is normally called by
328 : // TABFeature::WriteRecordToDATFile()
329 1239 : if (WriteHeader() != 0)
330 0 : return -1;
331 :
332 1239 : m_bUpdated = FALSE;
333 1239 : return 0;
334 : }
335 :
336 : /**********************************************************************
337 : * TABDATFile::InitWriteHeader()
338 : *
339 : * Init the header members to be ready to write the header and data records
340 : * to a newly created data file.
341 : *
342 : * Returns 0 on success, -1 on error.
343 : **********************************************************************/
344 154 : int TABDATFile::InitWriteHeader()
345 : {
346 154 : if (m_eAccessMode == TABRead || m_bWriteHeaderInitialized)
347 0 : return 0;
348 :
349 : // Compute values for Record size, header size, etc.
350 154 : m_nFirstRecordPtr = (m_numFields + 1) * 32 + 1;
351 :
352 154 : m_nRecordSize = 1;
353 469 : for (int i = 0; i < m_numFields; i++)
354 : {
355 315 : m_nRecordSize += m_pasFieldDef[i].byLength;
356 : }
357 :
358 : // Create m_poRecordBlock the size of a data record.
359 154 : m_nBlockSize = m_nRecordSize;
360 :
361 154 : CPLAssert(m_poRecordBlock == nullptr);
362 154 : m_poRecordBlock = new TABRawBinBlock(TABReadWrite, FALSE);
363 154 : m_poRecordBlock->InitNewBlock(m_fp, m_nBlockSize);
364 154 : m_poRecordBlock->SetFirstBlockPtr(m_nFirstRecordPtr);
365 :
366 : // Make sure this init. will be performed only once.
367 154 : m_bWriteHeaderInitialized = TRUE;
368 :
369 154 : return 0;
370 : }
371 :
372 : /**********************************************************************
373 : * TABDATFile::WriteHeader()
374 : *
375 : * Init the header members to be ready to write the header and data records
376 : * to a newly created data file.
377 : *
378 : * Returns 0 on success, -1 on error.
379 : **********************************************************************/
380 1369 : int TABDATFile::WriteHeader()
381 : {
382 1369 : if (m_eAccessMode == TABRead)
383 : {
384 0 : CPLError(CE_Failure, CPLE_NotSupported,
385 : "WriteHeader() can be used only with Write access.");
386 0 : return -1;
387 : }
388 :
389 1369 : if (!m_bWriteHeaderInitialized)
390 154 : InitWriteHeader();
391 :
392 : // Create a single block that will be used to generate the whole header.
393 1369 : if (m_poHeaderBlock == nullptr)
394 154 : m_poHeaderBlock = new TABRawBinBlock(m_eAccessMode, TRUE);
395 1369 : m_poHeaderBlock->InitNewBlock(m_fp, m_nFirstRecordPtr, 0);
396 :
397 : // First 32 bytes: main header block.
398 1369 : m_poHeaderBlock->WriteByte(0x03); // Table type ??? 0x03
399 :
400 : // __TODO__ Write the correct update date value
401 1369 : m_poHeaderBlock->WriteByte(99); // Last update year
402 1369 : m_poHeaderBlock->WriteByte(9); // Last update month
403 1369 : m_poHeaderBlock->WriteByte(9); // Last update day
404 :
405 1369 : m_poHeaderBlock->WriteInt32(m_numRecords);
406 1369 : m_poHeaderBlock->WriteInt16(static_cast<GInt16>(m_nFirstRecordPtr));
407 1369 : m_poHeaderBlock->WriteInt16(static_cast<GInt16>(m_nRecordSize));
408 :
409 1369 : m_poHeaderBlock->WriteZeros(20); // Pad rest with zeros.
410 :
411 : // Field definitions follow. Each field def is 32 bytes.
412 3126 : for (int i = 0; i < m_numFields; i++)
413 : {
414 1757 : m_poHeaderBlock->WriteBytes(
415 1757 : 11, reinterpret_cast<GByte *>(m_pasFieldDef[i].szName));
416 1757 : m_poHeaderBlock->WriteByte(m_pasFieldDef[i].cType);
417 :
418 1757 : m_poHeaderBlock->WriteInt32(0); // Skip Bytes 12-15
419 :
420 1757 : m_poHeaderBlock->WriteByte(m_pasFieldDef[i].byLength);
421 1757 : m_poHeaderBlock->WriteByte(m_pasFieldDef[i].byDecimals);
422 :
423 1757 : m_poHeaderBlock->WriteZeros(14); // Pad rest with zeros
424 : }
425 :
426 : // Header ends with a 0x0d character.
427 1369 : m_poHeaderBlock->WriteByte(0x0d);
428 :
429 : // Write the block to the file and return.
430 1369 : return m_poHeaderBlock->CommitToFile();
431 : }
432 :
433 : /**********************************************************************
434 : * TABDATFile::GetNumFields()
435 : *
436 : * Return the number of fields in this table.
437 : *
438 : * Returns a value >= 0 on success, -1 on error.
439 : **********************************************************************/
440 544622 : int TABDATFile::GetNumFields()
441 : {
442 544622 : return m_numFields;
443 : }
444 :
445 : /**********************************************************************
446 : * TABDATFile::GetNumRecords()
447 : *
448 : * Return the number of records in this table.
449 : *
450 : * Returns a value >= 0 on success, -1 on error.
451 : **********************************************************************/
452 1432 : int TABDATFile::GetNumRecords()
453 : {
454 1432 : return m_numRecords;
455 : }
456 :
457 : /**********************************************************************
458 : * TABDATFile::GetRecordBlock()
459 : *
460 : * Return a TABRawBinBlock reference positioned at the beginning of the
461 : * specified record and ready to read (or write) field values from/to it.
462 : * In read access, the returned block is guaranteed to contain at least one
463 : * full record of data, and in write access, it is at least big enough to
464 : * hold one full record.
465 : *
466 : * Note that record ids are positive and start at 1.
467 : *
468 : * In Write access, CommitRecordToFile() MUST be called after the
469 : * data items have been written to the record, otherwise the record
470 : * will never make it to the file.
471 : *
472 : * Returns a reference to the TABRawBinBlock on success or NULL on error.
473 : * The returned pointer is a reference to a block object owned by this
474 : * TABDATFile object and should not be freed by the caller.
475 : **********************************************************************/
476 698128 : TABRawBinBlock *TABDATFile::GetRecordBlock(int nRecordId)
477 : {
478 698128 : if (m_fp == nullptr)
479 : {
480 0 : CPLError(CE_Failure, CPLE_NotSupported,
481 : "Operation not supported on closed table.");
482 0 : return nullptr;
483 : }
484 :
485 698128 : m_bCurRecordDeletedFlag = FALSE;
486 698128 : m_bWriteEOF = FALSE;
487 :
488 698128 : if (m_eAccessMode == TABRead || nRecordId <= m_numRecords)
489 : {
490 : // READ ACCESS
491 683170 : const int nFileOffset =
492 683170 : m_nFirstRecordPtr + (nRecordId - 1) * m_nRecordSize;
493 :
494 : // Move record block pointer to the right location.
495 683170 : if (m_poRecordBlock == nullptr || nRecordId < 1 ||
496 2049510 : nRecordId > m_numRecords ||
497 683170 : m_poRecordBlock->GotoByteInFile(nFileOffset) != 0)
498 : {
499 0 : CPLError(CE_Failure, CPLE_FileIO,
500 : "Failed reading .DAT record block for record #%d in %s",
501 : nRecordId, m_pszFname);
502 0 : return nullptr;
503 : }
504 :
505 : // The first char of the record is a ' ' for an active record, or
506 : // '*' for a deleted one.
507 : // In the case of a deleted record, we simply return default
508 : // values for each attribute... this is what MapInfo seems to do
509 : // when it takes a .TAB with deleted records and exports it to .MIF
510 683170 : if (m_poRecordBlock->ReadByte() != ' ')
511 : {
512 1320 : m_bCurRecordDeletedFlag = TRUE;
513 683170 : }
514 : }
515 14958 : else if (nRecordId > 0)
516 : {
517 : // WRITE ACCESS
518 :
519 : // Before writing the first record, we must generate the file
520 : // header. We will also initialize class members such as record
521 : // size, etc. and will create m_poRecordBlock.
522 14958 : if (!m_bWriteHeaderInitialized)
523 : {
524 130 : WriteHeader();
525 : }
526 :
527 14958 : m_bUpdated = TRUE;
528 :
529 14958 : m_numRecords = std::max(nRecordId, m_numRecords);
530 14958 : if (nRecordId == m_numRecords)
531 14958 : m_bWriteEOF = TRUE;
532 :
533 14958 : const int nFileOffset =
534 14958 : m_nFirstRecordPtr + (nRecordId - 1) * m_nRecordSize;
535 :
536 14958 : m_poRecordBlock->InitNewBlock(m_fp, m_nRecordSize, nFileOffset);
537 :
538 : // The first char of the record is the active/deleted flag.
539 : // Automatically set it to ' ' (active).
540 14958 : m_poRecordBlock->WriteByte(' ');
541 : }
542 :
543 698128 : m_nCurRecordId = nRecordId;
544 :
545 698128 : return m_poRecordBlock;
546 : }
547 :
548 : /**********************************************************************
549 : * TABDATFile::CommitRecordToFile()
550 : *
551 : * Commit the data record previously initialized with GetRecordBlock()
552 : * to the file. This function must be called after writing the data
553 : * values to a record otherwise the record will never make it to the
554 : * file.
555 : *
556 : * Returns 0 on success, -1 on error.
557 : **********************************************************************/
558 15173 : int TABDATFile::CommitRecordToFile()
559 : {
560 15173 : if (m_eAccessMode == TABRead || m_poRecordBlock == nullptr)
561 0 : return -1;
562 :
563 15173 : if (m_poRecordBlock->CommitToFile() != 0)
564 0 : return -1;
565 :
566 : // If this is the end of file, write EOF character.
567 15173 : if (m_bWriteEOF)
568 : {
569 14957 : m_bWriteEOF = FALSE;
570 14957 : char cEOF = 26;
571 14957 : if (VSIFSeekL(m_fp, 0L, SEEK_END) == 0)
572 14957 : VSIFWriteL(&cEOF, 1, 1, m_fp);
573 : }
574 :
575 15173 : return 0;
576 : }
577 :
578 : /**********************************************************************
579 : * TABDATFile::MarkAsDeleted()
580 : *
581 : * Returns 0 on success, -1 on error.
582 : **********************************************************************/
583 715 : int TABDATFile::MarkAsDeleted()
584 : {
585 715 : if (m_eAccessMode == TABRead || m_poRecordBlock == nullptr)
586 0 : return -1;
587 :
588 715 : const int nFileOffset =
589 715 : m_nFirstRecordPtr + (m_nCurRecordId - 1) * m_nRecordSize;
590 :
591 715 : if (m_poRecordBlock->GotoByteInFile(nFileOffset) != 0)
592 0 : return -1;
593 :
594 715 : m_poRecordBlock->WriteByte('*');
595 :
596 715 : if (m_poRecordBlock->CommitToFile() != 0)
597 0 : return -1;
598 :
599 715 : m_bCurRecordDeletedFlag = TRUE;
600 715 : m_bUpdated = TRUE;
601 :
602 715 : return 0;
603 : }
604 :
605 : /**********************************************************************
606 : * TABDATFile::MarkRecordAsExisting()
607 : *
608 : * Returns 0 on success, -1 on error.
609 : **********************************************************************/
610 15087 : int TABDATFile::MarkRecordAsExisting()
611 : {
612 15087 : if (m_eAccessMode == TABRead || m_poRecordBlock == nullptr)
613 0 : return -1;
614 :
615 15087 : const int nFileOffset =
616 15087 : m_nFirstRecordPtr + (m_nCurRecordId - 1) * m_nRecordSize;
617 :
618 15087 : if (m_poRecordBlock->GotoByteInFile(nFileOffset) != 0)
619 0 : return -1;
620 :
621 15087 : m_poRecordBlock->WriteByte(' ');
622 :
623 15087 : m_bCurRecordDeletedFlag = FALSE;
624 15087 : m_bUpdated = TRUE;
625 :
626 15087 : return 0;
627 : }
628 :
629 : /**********************************************************************
630 : * TABDATFile::ValidateFieldInfoFromTAB()
631 : *
632 : * Check that the value read from the .TAB file by the caller are
633 : * consistent with what is found in the .DAT header.
634 : *
635 : * Note that field ids are positive and start at 0.
636 : *
637 : * We have to use this function when opening a file for reading since
638 : * the .DAT file does not contain the full field types information...
639 : * a .DAT file is actually a .DBF file in which the .DBF types are
640 : * handled in a special way... type 'C' fields are used to store binary
641 : * values for most MapInfo types.
642 : *
643 : * For TABTableDBF, we actually have no validation to do since all types
644 : * are stored as strings internally, so we'll just convert from string.
645 : *
646 : * Returns a value >= 0 if OK, -1 on error.
647 : **********************************************************************/
648 1537 : int TABDATFile::ValidateFieldInfoFromTAB(int iField, const char *pszName,
649 : TABFieldType eType, int nWidth,
650 : int nPrecision)
651 : {
652 1537 : int i = iField; // Just to make things shorter
653 :
654 1537 : if (m_pasFieldDef == nullptr || iField < 0 || iField >= m_numFields)
655 : {
656 16 : CPLError(
657 : CE_Failure, CPLE_FileIO,
658 : "Invalid field %d (%s) in .TAB header. %s contains only %d fields.",
659 16 : iField + 1, pszName, m_pszFname, m_pasFieldDef ? m_numFields : 0);
660 16 : return -1;
661 : }
662 :
663 : // We used to check that the .TAB field name matched the .DAT
664 : // name stored internally, but apparently some tools that rename table
665 : // field names only update the .TAB file and not the .DAT, so we won't
666 : // do that name validation any more... we'll just check the type.
667 : //
668 : // With TABTableNative, we have to validate the field sizes as well
669 : // because .DAT files use char fields to store binary values.
670 : // With TABTableDBF, no need to validate field type since all
671 : // fields are stored as strings internally.
672 :
673 1521 : if ((m_eTableType == TABTableNative &&
674 201 : ((eType == TABFChar && (m_pasFieldDef[i].cType != 'C' ||
675 1521 : m_pasFieldDef[i].byLength != nWidth)) ||
676 8 : (eType == TABFDecimal &&
677 8 : (m_pasFieldDef[i].cType != 'N' ||
678 8 : m_pasFieldDef[i].byLength != nWidth ||
679 1521 : m_pasFieldDef[i].byDecimals != nPrecision)) ||
680 1233 : (eType == TABFInteger &&
681 1521 : (m_pasFieldDef[i].cType != 'C' || m_pasFieldDef[i].byLength != 4)) ||
682 1 : (eType == TABFSmallInt &&
683 1521 : (m_pasFieldDef[i].cType != 'C' || m_pasFieldDef[i].byLength != 2)) ||
684 2 : (eType == TABFLargeInt &&
685 1521 : (m_pasFieldDef[i].cType != 'C' || m_pasFieldDef[i].byLength != 8)) ||
686 31 : (eType == TABFFloat &&
687 1521 : (m_pasFieldDef[i].cType != 'C' || m_pasFieldDef[i].byLength != 8)) ||
688 21 : (eType == TABFDate &&
689 1521 : (m_pasFieldDef[i].cType != 'C' || m_pasFieldDef[i].byLength != 4)) ||
690 3 : (eType == TABFTime &&
691 1521 : (m_pasFieldDef[i].cType != 'C' || m_pasFieldDef[i].byLength != 4)) ||
692 19 : (eType == TABFDateTime &&
693 1521 : (m_pasFieldDef[i].cType != 'C' || m_pasFieldDef[i].byLength != 8)) ||
694 2 : (eType == TABFLogical &&
695 2 : (m_pasFieldDef[i].cType != 'L' || m_pasFieldDef[i].byLength != 1)))))
696 : {
697 0 : CPLError(CE_Failure, CPLE_FileIO,
698 : "Definition of field %d (%s) from .TAB file does not match "
699 : "what is found in %s (name=%s, type=%c, width=%d, prec=%d)",
700 0 : iField + 1, pszName, m_pszFname, m_pasFieldDef[i].szName,
701 0 : m_pasFieldDef[i].cType, m_pasFieldDef[i].byLength,
702 0 : m_pasFieldDef[i].byDecimals);
703 0 : return -1;
704 : }
705 :
706 1521 : m_pasFieldDef[i].eTABType = eType;
707 :
708 1521 : return 0;
709 : }
710 :
711 : /**********************************************************************
712 : * TABDATFileSetFieldDefinition()
713 : *
714 : **********************************************************************/
715 336 : static int TABDATFileSetFieldDefinition(TABDATFieldDef *psFieldDef,
716 : const char *pszName, TABFieldType eType,
717 : int nWidth, int nPrecision)
718 : {
719 : // Validate field width.
720 336 : if (nWidth > 254)
721 : {
722 0 : CPLError(CE_Failure, CPLE_IllegalArg,
723 : "Invalid size (%d) for field '%s'. "
724 : "Size must be 254 or less.",
725 : nWidth, pszName);
726 0 : return -1;
727 : }
728 :
729 : // Map fields with width=0 (variable length in OGR) to a valid default.
730 336 : if (eType == TABFDecimal && nWidth == 0)
731 0 : nWidth = 20;
732 336 : else if (nWidth == 0)
733 0 : nWidth = 254; // char fields.
734 :
735 336 : snprintf(psFieldDef->szName, sizeof(psFieldDef->szName), "%s", pszName);
736 336 : psFieldDef->eTABType = eType;
737 336 : psFieldDef->byDecimals = 0;
738 :
739 336 : switch (eType)
740 : {
741 189 : case TABFChar:
742 189 : psFieldDef->cType = 'C';
743 189 : psFieldDef->byLength = static_cast<GByte>(nWidth);
744 189 : break;
745 4 : case TABFDecimal:
746 4 : psFieldDef->cType = 'N';
747 4 : psFieldDef->byLength = static_cast<GByte>(nWidth);
748 4 : psFieldDef->byDecimals = static_cast<GByte>(nPrecision);
749 4 : break;
750 79 : case TABFInteger:
751 79 : psFieldDef->cType = 'C';
752 79 : psFieldDef->byLength = 4;
753 79 : break;
754 0 : case TABFSmallInt:
755 0 : psFieldDef->cType = 'C';
756 0 : psFieldDef->byLength = 2;
757 0 : break;
758 1 : case TABFLargeInt:
759 1 : psFieldDef->cType = 'C';
760 1 : psFieldDef->byLength = 8;
761 1 : break;
762 24 : case TABFFloat:
763 24 : psFieldDef->cType = 'C';
764 24 : psFieldDef->byLength = 8;
765 24 : break;
766 18 : case TABFDate:
767 18 : psFieldDef->cType = 'C';
768 18 : psFieldDef->byLength = 4;
769 18 : break;
770 2 : case TABFTime:
771 2 : psFieldDef->cType = 'C';
772 2 : psFieldDef->byLength = 4;
773 2 : break;
774 18 : case TABFDateTime:
775 18 : psFieldDef->cType = 'C';
776 18 : psFieldDef->byLength = 8;
777 18 : break;
778 1 : case TABFLogical:
779 1 : psFieldDef->cType = 'L';
780 1 : psFieldDef->byLength = 1;
781 1 : break;
782 0 : default:
783 0 : CPLError(CE_Failure, CPLE_NotSupported,
784 : "Unsupported field type for field `%s'", pszName);
785 0 : return -1;
786 : }
787 :
788 336 : return 0;
789 : }
790 :
791 : /**********************************************************************
792 : * TABDATFile::AddField()
793 : *
794 : * Create a new field (column) in a newly created table. This function
795 : * must be called after the file has been opened, but before writing the
796 : * first record.
797 : *
798 : * Returns the new field index (a value >= 0) if OK, -1 on error.
799 : **********************************************************************/
800 329 : int TABDATFile::AddField(const char *pszName, TABFieldType eType, int nWidth,
801 : int nPrecision /* =0 */)
802 : {
803 329 : if (m_fp == nullptr)
804 : {
805 0 : CPLError(CE_Failure, CPLE_NotSupported,
806 : "Operation not supported on closed table.");
807 0 : return -1;
808 : }
809 329 : if (m_eAccessMode == TABRead || m_eTableType != TABTableNative)
810 : {
811 0 : CPLError(CE_Failure, CPLE_NotSupported,
812 : "Operation not supported on read-only files or "
813 : "on non-native table.");
814 0 : return -1;
815 : }
816 :
817 : TABDATFieldDef sFieldDef;
818 329 : if (TABDATFileSetFieldDefinition(&sFieldDef, pszName, eType, nWidth,
819 329 : nPrecision) < 0)
820 0 : return -1;
821 :
822 329 : if (m_numFields < 0)
823 0 : m_numFields = 0;
824 :
825 329 : m_numFields++;
826 329 : m_pasFieldDef = static_cast<TABDATFieldDef *>(
827 329 : CPLRealloc(m_pasFieldDef, m_numFields * sizeof(TABDATFieldDef)));
828 329 : memcpy(&m_pasFieldDef[m_numFields - 1], &sFieldDef, sizeof(sFieldDef));
829 :
830 : // If there are already records, we cannot update in place.
831 : // Create a temporary .dat.tmp in which we create the new structure
832 : // and then copy the widen records.
833 329 : if (m_numRecords > 0)
834 : {
835 14 : TABDATFile oTempFile(GetEncoding());
836 14 : CPLString osOriginalFile(m_pszFname);
837 14 : CPLString osTmpFile(m_pszFname);
838 14 : osTmpFile += ".tmp";
839 14 : if (oTempFile.Open(osTmpFile.c_str(), TABWrite) != 0)
840 0 : return -1;
841 :
842 : // Create field structure.
843 60 : for (int i = 0; i < m_numFields; i++)
844 : {
845 46 : oTempFile.AddField(
846 46 : m_pasFieldDef[i].szName, m_pasFieldDef[i].eTABType,
847 46 : m_pasFieldDef[i].byLength, m_pasFieldDef[i].byDecimals);
848 : }
849 :
850 14 : GByte *pabyRecord = static_cast<GByte *>(CPLMalloc(m_nRecordSize));
851 :
852 : // Copy records.
853 44 : for (int j = 0; j < m_numRecords; j++)
854 : {
855 60 : if (GetRecordBlock(1 + j) == nullptr ||
856 30 : oTempFile.GetRecordBlock(1 + j) == nullptr)
857 : {
858 0 : CPLFree(pabyRecord);
859 0 : oTempFile.Close();
860 0 : VSIUnlink(osTmpFile);
861 0 : return -1;
862 : }
863 30 : if (m_bCurRecordDeletedFlag)
864 : {
865 0 : oTempFile.MarkAsDeleted();
866 : }
867 : else
868 : {
869 30 : if (m_poRecordBlock->ReadBytes(m_nRecordSize - 1, pabyRecord) !=
870 30 : 0 ||
871 30 : oTempFile.m_poRecordBlock->WriteBytes(m_nRecordSize - 1,
872 60 : pabyRecord) != 0 ||
873 30 : oTempFile.m_poRecordBlock->WriteZeros(
874 30 : m_pasFieldDef[m_numFields - 1].byLength) != 0)
875 : {
876 0 : CPLFree(pabyRecord);
877 0 : oTempFile.Close();
878 0 : VSIUnlink(osTmpFile);
879 0 : return -1;
880 : }
881 30 : oTempFile.CommitRecordToFile();
882 : }
883 : }
884 :
885 14 : CPLFree(pabyRecord);
886 :
887 : // Close temporary file.
888 14 : oTempFile.Close();
889 :
890 : // Backup field definitions as we will need to set the TABFieldType.
891 : TABDATFieldDef *pasFieldDefTmp = static_cast<TABDATFieldDef *>(
892 14 : CPLMalloc(m_numFields * sizeof(TABDATFieldDef)));
893 14 : memcpy(pasFieldDefTmp, m_pasFieldDef,
894 14 : m_numFields * sizeof(TABDATFieldDef));
895 :
896 14 : m_numFields--; // So that Close() doesn't see the new field.
897 14 : Close();
898 :
899 : // Move temporary file as main .data file and reopen it.
900 14 : VSIUnlink(osOriginalFile);
901 14 : VSIRename(osTmpFile, osOriginalFile);
902 14 : if (Open(osOriginalFile, TABReadWrite) < 0)
903 : {
904 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot reopen %s",
905 : osOriginalFile.c_str());
906 0 : CPLFree(pasFieldDefTmp);
907 0 : return -1;
908 : }
909 :
910 : // Restore saved TABFieldType.
911 60 : for (int i = 0; i < m_numFields; i++)
912 : {
913 46 : m_pasFieldDef[i].eTABType = pasFieldDefTmp[i].eTABType;
914 : }
915 14 : CPLFree(pasFieldDefTmp);
916 : }
917 :
918 329 : return 0;
919 : }
920 :
921 : /************************************************************************/
922 : /* DeleteField() */
923 : /************************************************************************/
924 :
925 2 : int TABDATFile::DeleteField(int iField)
926 : {
927 2 : if (m_fp == nullptr)
928 : {
929 0 : CPLError(CE_Failure, CPLE_NotSupported,
930 : "Operation not supported on closed table.");
931 0 : return -1;
932 : }
933 2 : if (m_eAccessMode == TABRead || m_eTableType != TABTableNative)
934 : {
935 0 : CPLError(CE_Failure, CPLE_NotSupported,
936 : "Operation not supported on read-only files or "
937 : "on non-native table.");
938 0 : return -1;
939 : }
940 :
941 2 : if (iField < 0 || iField >= m_numFields)
942 : {
943 0 : CPLError(CE_Failure, CPLE_IllegalArg, "Invalid field index: %d",
944 : iField);
945 0 : return -1;
946 : }
947 :
948 : // If no records have been written, then just remove from the field
949 : // definition array.
950 2 : if (m_numRecords <= 0)
951 : {
952 0 : if (iField < m_numFields - 1)
953 : {
954 0 : memmove(m_pasFieldDef + iField, m_pasFieldDef + iField + 1,
955 0 : (m_numFields - 1 - iField) * sizeof(TABDATFieldDef));
956 : }
957 0 : m_numFields--;
958 0 : return 0;
959 : }
960 :
961 2 : if (m_numFields == 1)
962 : {
963 0 : CPLError(CE_Failure, CPLE_IllegalArg,
964 : "Cannot delete the single remaining field.");
965 0 : return -1;
966 : }
967 :
968 : // Otherwise we need to do a temporary file.
969 4 : TABDATFile oTempFile(GetEncoding());
970 4 : CPLString osOriginalFile(m_pszFname);
971 4 : CPLString osTmpFile(m_pszFname);
972 2 : osTmpFile += ".tmp";
973 2 : if (oTempFile.Open(osTmpFile.c_str(), TABWrite) != 0)
974 0 : return -1;
975 :
976 : // Create field structure.
977 2 : int nRecordSizeBefore = 0;
978 2 : int nRecordSizeAfter = 0;
979 11 : for (int i = 0; i < m_numFields; i++)
980 : {
981 9 : if (i != iField)
982 : {
983 7 : if (i < iField)
984 3 : nRecordSizeBefore += m_pasFieldDef[i].byLength;
985 : else /* if( i > iField ) */
986 4 : nRecordSizeAfter += m_pasFieldDef[i].byLength;
987 7 : oTempFile.AddField(
988 7 : m_pasFieldDef[i].szName, m_pasFieldDef[i].eTABType,
989 7 : m_pasFieldDef[i].byLength, m_pasFieldDef[i].byDecimals);
990 : }
991 : }
992 :
993 2 : CPLAssert(nRecordSizeBefore + m_pasFieldDef[iField].byLength +
994 : nRecordSizeAfter ==
995 : m_nRecordSize - 1);
996 :
997 2 : GByte *pabyRecord = static_cast<GByte *>(CPLMalloc(m_nRecordSize));
998 :
999 : // Copy records.
1000 8 : for (int j = 0; j < m_numRecords; j++)
1001 : {
1002 12 : if (GetRecordBlock(1 + j) == nullptr ||
1003 6 : oTempFile.GetRecordBlock(1 + j) == nullptr)
1004 : {
1005 0 : CPLFree(pabyRecord);
1006 0 : oTempFile.Close();
1007 0 : VSIUnlink(osTmpFile);
1008 0 : return -1;
1009 : }
1010 6 : if (m_bCurRecordDeletedFlag)
1011 : {
1012 0 : oTempFile.MarkAsDeleted();
1013 : }
1014 : else
1015 : {
1016 6 : if (m_poRecordBlock->ReadBytes(m_nRecordSize - 1, pabyRecord) !=
1017 6 : 0 ||
1018 3 : (nRecordSizeBefore > 0 &&
1019 3 : oTempFile.m_poRecordBlock->WriteBytes(nRecordSizeBefore,
1020 18 : pabyRecord) != 0) ||
1021 3 : (nRecordSizeAfter > 0 &&
1022 3 : oTempFile.m_poRecordBlock->WriteBytes(
1023 3 : nRecordSizeAfter, pabyRecord + nRecordSizeBefore +
1024 3 : m_pasFieldDef[iField].byLength) !=
1025 : 0))
1026 : {
1027 0 : CPLFree(pabyRecord);
1028 0 : oTempFile.Close();
1029 0 : VSIUnlink(osTmpFile);
1030 0 : return -1;
1031 : }
1032 6 : oTempFile.CommitRecordToFile();
1033 : }
1034 : }
1035 :
1036 2 : CPLFree(pabyRecord);
1037 :
1038 : // Close temporary file.
1039 2 : oTempFile.Close();
1040 :
1041 : // Backup field definitions as we will need to set the TABFieldType.
1042 : TABDATFieldDef *pasFieldDefTmp = static_cast<TABDATFieldDef *>(
1043 2 : CPLMalloc(m_numFields * sizeof(TABDATFieldDef)));
1044 2 : memcpy(pasFieldDefTmp, m_pasFieldDef, m_numFields * sizeof(TABDATFieldDef));
1045 :
1046 2 : Close();
1047 :
1048 : // Move temporary file as main .data file and reopen it.
1049 2 : VSIUnlink(osOriginalFile);
1050 2 : VSIRename(osTmpFile, osOriginalFile);
1051 2 : if (Open(osOriginalFile, TABReadWrite) < 0)
1052 : {
1053 0 : CPLFree(pasFieldDefTmp);
1054 0 : return -1;
1055 : }
1056 :
1057 : // Restore saved TABFieldType.
1058 9 : for (int i = 0; i < m_numFields; i++)
1059 : {
1060 7 : if (i < iField)
1061 3 : m_pasFieldDef[i].eTABType = pasFieldDefTmp[i].eTABType;
1062 : else
1063 4 : m_pasFieldDef[i].eTABType = pasFieldDefTmp[i + 1].eTABType;
1064 : }
1065 2 : CPLFree(pasFieldDefTmp);
1066 :
1067 2 : return 0;
1068 : }
1069 :
1070 : /************************************************************************/
1071 : /* ReorderFields() */
1072 : /************************************************************************/
1073 :
1074 10 : int TABDATFile::ReorderFields(int *panMap)
1075 : {
1076 10 : if (m_fp == nullptr)
1077 : {
1078 0 : CPLError(CE_Failure, CPLE_NotSupported,
1079 : "Operation not supported on closed table.");
1080 0 : return -1;
1081 : }
1082 10 : if (m_eAccessMode == TABRead || m_eTableType != TABTableNative)
1083 : {
1084 0 : CPLError(CE_Failure, CPLE_NotSupported,
1085 : "Operation not supported on read-only files or "
1086 : "on non-native table.");
1087 0 : return -1;
1088 : }
1089 :
1090 10 : if (m_numFields == 0)
1091 0 : return 0;
1092 :
1093 10 : OGRErr eErr = OGRCheckPermutation(panMap, m_numFields);
1094 10 : if (eErr != OGRERR_NONE)
1095 0 : return -1;
1096 :
1097 : // If no records have been written, then just reorder the field
1098 : // definition array.
1099 10 : if (m_numRecords <= 0)
1100 : {
1101 : TABDATFieldDef *pasFieldDefTmp = static_cast<TABDATFieldDef *>(
1102 0 : CPLMalloc(m_numFields * sizeof(TABDATFieldDef)));
1103 0 : memcpy(pasFieldDefTmp, m_pasFieldDef,
1104 0 : m_numFields * sizeof(TABDATFieldDef));
1105 0 : for (int i = 0; i < m_numFields; i++)
1106 : {
1107 0 : memcpy(m_pasFieldDef + i, pasFieldDefTmp + panMap[i],
1108 : sizeof(TABDATFieldDef));
1109 : }
1110 0 : CPLFree(pasFieldDefTmp);
1111 0 : return 0;
1112 : }
1113 :
1114 : // We could theoretically update in place, but a sudden interruption
1115 : // would leave the file in a undefined state.
1116 :
1117 20 : TABDATFile oTempFile(GetEncoding());
1118 20 : CPLString osOriginalFile(m_pszFname);
1119 20 : CPLString osTmpFile(m_pszFname);
1120 10 : osTmpFile += ".tmp";
1121 10 : if (oTempFile.Open(osTmpFile.c_str(), TABWrite) != 0)
1122 0 : return -1;
1123 :
1124 : // Create field structure.
1125 : int *panOldOffset =
1126 10 : static_cast<int *>(CPLMalloc(m_numFields * sizeof(int)));
1127 52 : for (int i = 0; i < m_numFields; i++)
1128 : {
1129 42 : int iBefore = panMap[i];
1130 42 : if (i == 0)
1131 10 : panOldOffset[i] = 0;
1132 : else
1133 32 : panOldOffset[i] =
1134 32 : panOldOffset[i - 1] + m_pasFieldDef[i - 1].byLength;
1135 42 : oTempFile.AddField(
1136 42 : m_pasFieldDef[iBefore].szName, m_pasFieldDef[iBefore].eTABType,
1137 42 : m_pasFieldDef[iBefore].byLength, m_pasFieldDef[iBefore].byDecimals);
1138 : }
1139 :
1140 10 : GByte *pabyRecord = static_cast<GByte *>(CPLMalloc(m_nRecordSize));
1141 :
1142 : // Copy records.
1143 48 : for (int j = 0; j < m_numRecords; j++)
1144 : {
1145 76 : if (GetRecordBlock(1 + j) == nullptr ||
1146 38 : oTempFile.GetRecordBlock(1 + j) == nullptr)
1147 : {
1148 0 : CPLFree(pabyRecord);
1149 0 : CPLFree(panOldOffset);
1150 0 : oTempFile.Close();
1151 0 : VSIUnlink(osTmpFile);
1152 0 : return -1;
1153 : }
1154 38 : if (m_bCurRecordDeletedFlag)
1155 : {
1156 0 : oTempFile.MarkAsDeleted();
1157 : }
1158 : else
1159 : {
1160 38 : if (m_poRecordBlock->ReadBytes(m_nRecordSize - 1, pabyRecord) != 0)
1161 : {
1162 0 : CPLFree(pabyRecord);
1163 0 : CPLFree(panOldOffset);
1164 0 : oTempFile.Close();
1165 0 : VSIUnlink(osTmpFile);
1166 0 : return -1;
1167 : }
1168 196 : for (int i = 0; i < m_numFields; i++)
1169 : {
1170 158 : int iBefore = panMap[i];
1171 316 : if (oTempFile.m_poRecordBlock->WriteBytes(
1172 158 : m_pasFieldDef[iBefore].byLength,
1173 158 : pabyRecord + panOldOffset[iBefore]) != 0)
1174 : {
1175 0 : CPLFree(pabyRecord);
1176 0 : CPLFree(panOldOffset);
1177 0 : oTempFile.Close();
1178 0 : VSIUnlink(osTmpFile);
1179 0 : return -1;
1180 : }
1181 : }
1182 :
1183 38 : oTempFile.CommitRecordToFile();
1184 : }
1185 : }
1186 :
1187 10 : CPLFree(pabyRecord);
1188 10 : CPLFree(panOldOffset);
1189 :
1190 10 : oTempFile.Close();
1191 :
1192 : // Backup field definitions as we will need to set the TABFieldType.
1193 : TABDATFieldDef *pasFieldDefTmp = static_cast<TABDATFieldDef *>(
1194 10 : CPLMalloc(m_numFields * sizeof(TABDATFieldDef)));
1195 10 : memcpy(pasFieldDefTmp, m_pasFieldDef, m_numFields * sizeof(TABDATFieldDef));
1196 :
1197 : // Close ourselves.
1198 10 : Close();
1199 :
1200 : // Move temporary file as main .data file and reopen it.
1201 10 : VSIUnlink(osOriginalFile);
1202 10 : VSIRename(osTmpFile, osOriginalFile);
1203 10 : if (Open(osOriginalFile, TABReadWrite) < 0)
1204 : {
1205 0 : CPLFree(pasFieldDefTmp);
1206 0 : return -1;
1207 : }
1208 :
1209 : // Restore saved TABFieldType.
1210 52 : for (int i = 0; i < m_numFields; i++)
1211 : {
1212 42 : int iBefore = panMap[i];
1213 42 : m_pasFieldDef[i].eTABType = pasFieldDefTmp[iBefore].eTABType;
1214 : }
1215 10 : CPLFree(pasFieldDefTmp);
1216 :
1217 10 : return 0;
1218 : }
1219 :
1220 : /************************************************************************/
1221 : /* AlterFieldDefn() */
1222 : /************************************************************************/
1223 :
1224 11 : int TABDATFile::AlterFieldDefn(int iField, const OGRFieldDefn *poSrcFieldDefn,
1225 : OGRFieldDefn *poNewFieldDefn, int nFlags)
1226 : {
1227 11 : if (m_fp == nullptr)
1228 : {
1229 0 : CPLError(CE_Failure, CPLE_NotSupported,
1230 : "Operation not supported on closed table.");
1231 0 : return -1;
1232 : }
1233 11 : if (m_eAccessMode == TABRead || m_eTableType != TABTableNative)
1234 : {
1235 0 : CPLError(CE_Failure, CPLE_NotSupported,
1236 : "Operation not supported on read-only files or "
1237 : "on non-native table.");
1238 0 : return -1;
1239 : }
1240 :
1241 11 : if (iField < 0 || iField >= m_numFields)
1242 : {
1243 0 : CPLError(CE_Failure, CPLE_IllegalArg, "Invalid field index: %d",
1244 : iField);
1245 0 : return -1;
1246 : }
1247 :
1248 11 : TABFieldType eTABType = m_pasFieldDef[iField].eTABType;
1249 11 : int nWidth = poSrcFieldDefn->GetWidth();
1250 11 : int nPrecision = poSrcFieldDefn->GetPrecision();
1251 11 : if (nFlags & ALTER_TYPE_FLAG)
1252 : {
1253 11 : if (IMapInfoFile::GetTABType(poNewFieldDefn, &eTABType, nullptr,
1254 11 : nullptr) < 0)
1255 0 : return -1;
1256 : }
1257 11 : if (nFlags & ALTER_WIDTH_PRECISION_FLAG)
1258 : {
1259 : // Instead of taking directly poNewFieldDefn->GetWidth()/GetPrecision(),
1260 : // use GetTABType() to take into account .dat limitations on
1261 : // width & precision to clamp what user might have specify
1262 10 : if (IMapInfoFile::GetTABType(poNewFieldDefn, nullptr, &nWidth,
1263 10 : &nPrecision) < 0)
1264 0 : return -1;
1265 : }
1266 :
1267 11 : if ((nFlags & ALTER_TYPE_FLAG) &&
1268 11 : eTABType != m_pasFieldDef[iField].eTABType)
1269 : {
1270 6 : if (eTABType != TABFChar && m_numRecords > 0)
1271 : {
1272 1 : CPLError(CE_Failure, CPLE_NotSupported,
1273 : "Can only convert to OFTString");
1274 1 : return -1;
1275 : }
1276 5 : if (eTABType == TABFChar && (nFlags & ALTER_WIDTH_PRECISION_FLAG) == 0)
1277 1 : nWidth = 254;
1278 : }
1279 :
1280 10 : if (nFlags & ALTER_WIDTH_PRECISION_FLAG)
1281 : {
1282 13 : if (eTABType != TABFChar && nWidth != poSrcFieldDefn->GetWidth() &&
1283 4 : m_numRecords > 0)
1284 : {
1285 1 : CPLError(
1286 : CE_Failure, CPLE_NotSupported,
1287 : "Resizing only supported on String fields on non-empty layer");
1288 1 : return -1;
1289 : }
1290 : }
1291 :
1292 9 : if (nFlags & ALTER_NAME_FLAG)
1293 : {
1294 9 : strncpy(m_pasFieldDef[iField].szName, poNewFieldDefn->GetNameRef(),
1295 : sizeof(m_pasFieldDef[iField].szName) - 1);
1296 9 : m_pasFieldDef[iField].szName[sizeof(m_pasFieldDef[iField].szName) - 1] =
1297 : '\0';
1298 : // If renaming is the only operation, then nothing more to do.
1299 9 : if (nFlags == ALTER_NAME_FLAG)
1300 : {
1301 0 : m_bUpdated = TRUE;
1302 0 : return 0;
1303 : }
1304 : }
1305 :
1306 9 : if (m_numRecords <= 0)
1307 : {
1308 3 : if ((nFlags & ALTER_TYPE_FLAG) &&
1309 3 : eTABType != m_pasFieldDef[iField].eTABType)
1310 : {
1311 : TABDATFieldDef sFieldDef;
1312 2 : TABDATFileSetFieldDefinition(&sFieldDef,
1313 2 : m_pasFieldDef[iField].szName, eTABType,
1314 2 : m_pasFieldDef[iField].byLength,
1315 2 : m_pasFieldDef[iField].byDecimals);
1316 2 : memcpy(&m_pasFieldDef[iField], &sFieldDef, sizeof(sFieldDef));
1317 : }
1318 3 : if (nFlags & ALTER_WIDTH_PRECISION_FLAG)
1319 : {
1320 3 : if (eTABType == TABFChar || eTABType == TABFDecimal)
1321 2 : m_pasFieldDef[iField].byLength = static_cast<GByte>(nWidth);
1322 3 : if (eTABType == TABFDecimal)
1323 2 : m_pasFieldDef[iField].byDecimals =
1324 : static_cast<GByte>(nPrecision);
1325 : }
1326 3 : return 0;
1327 : }
1328 :
1329 : const bool bWidthPrecisionPreserved =
1330 7 : (nWidth == poSrcFieldDefn->GetWidth() &&
1331 1 : nPrecision == poSrcFieldDefn->GetPrecision());
1332 6 : if (eTABType == m_pasFieldDef[iField].eTABType && bWidthPrecisionPreserved)
1333 : {
1334 1 : return 0;
1335 : }
1336 :
1337 5 : if (eTABType != TABFChar)
1338 : {
1339 : // should hopefully not happen given all above checks
1340 0 : CPLError(CE_Failure, CPLE_NotSupported,
1341 : "Unsupported AlterFieldDefn() operation");
1342 0 : return -1;
1343 : }
1344 :
1345 : // Otherwise we need to do a temporary file.
1346 10 : TABDATFile oTempFile(GetEncoding());
1347 10 : CPLString osOriginalFile(m_pszFname);
1348 10 : CPLString osTmpFile(m_pszFname);
1349 5 : osTmpFile += ".tmp";
1350 5 : if (oTempFile.Open(osTmpFile.c_str(), TABWrite) != 0)
1351 0 : return -1;
1352 :
1353 : // Create field structure.
1354 5 : int nRecordSizeBefore = 0;
1355 5 : int nRecordSizeAfter = 0;
1356 : TABDATFieldDef sFieldDef;
1357 5 : sFieldDef.eTABType = TABFUnknown;
1358 5 : sFieldDef.byLength = 0;
1359 5 : sFieldDef.byDecimals = 0;
1360 5 : TABDATFileSetFieldDefinition(&sFieldDef, m_pasFieldDef[iField].szName,
1361 : eTABType, nWidth, nPrecision);
1362 :
1363 24 : for (int i = 0; i < m_numFields; i++)
1364 : {
1365 19 : if (i != iField)
1366 : {
1367 14 : if (i < iField)
1368 4 : nRecordSizeBefore += m_pasFieldDef[i].byLength;
1369 : else /*if( i > iField )*/
1370 10 : nRecordSizeAfter += m_pasFieldDef[i].byLength;
1371 14 : oTempFile.AddField(
1372 14 : m_pasFieldDef[i].szName, m_pasFieldDef[i].eTABType,
1373 14 : m_pasFieldDef[i].byLength, m_pasFieldDef[i].byDecimals);
1374 : }
1375 : else
1376 : {
1377 5 : oTempFile.AddField(sFieldDef.szName, sFieldDef.eTABType,
1378 5 : sFieldDef.byLength, sFieldDef.byDecimals);
1379 : }
1380 : }
1381 :
1382 5 : GByte *pabyRecord = static_cast<GByte *>(CPLMalloc(m_nRecordSize));
1383 5 : char *pabyNewField = static_cast<char *>(CPLMalloc(sFieldDef.byLength + 1));
1384 :
1385 : // Copy records.
1386 18 : for (int j = 0; j < m_numRecords; j++)
1387 : {
1388 26 : if (GetRecordBlock(1 + j) == nullptr ||
1389 13 : oTempFile.GetRecordBlock(1 + j) == nullptr)
1390 : {
1391 0 : CPLFree(pabyRecord);
1392 0 : CPLFree(pabyNewField);
1393 0 : oTempFile.Close();
1394 0 : VSIUnlink(osTmpFile);
1395 0 : return -1;
1396 : }
1397 13 : if (m_bCurRecordDeletedFlag)
1398 : {
1399 0 : oTempFile.MarkAsDeleted();
1400 : }
1401 : else
1402 : {
1403 19 : if (nRecordSizeBefore > 0 &&
1404 6 : (m_poRecordBlock->ReadBytes(nRecordSizeBefore, pabyRecord) !=
1405 6 : 0 ||
1406 6 : oTempFile.m_poRecordBlock->WriteBytes(nRecordSizeBefore,
1407 6 : pabyRecord) != 0))
1408 : {
1409 0 : CPLFree(pabyRecord);
1410 0 : CPLFree(pabyNewField);
1411 0 : oTempFile.Close();
1412 0 : VSIUnlink(osTmpFile);
1413 0 : return -1;
1414 : }
1415 :
1416 13 : memset(pabyNewField, 0, sFieldDef.byLength + 1);
1417 13 : if (m_pasFieldDef[iField].eTABType == TABFChar)
1418 : {
1419 6 : strncpy(pabyNewField,
1420 6 : ReadCharField(m_pasFieldDef[iField].byLength),
1421 6 : sFieldDef.byLength);
1422 : }
1423 7 : else if (m_pasFieldDef[iField].eTABType == TABFInteger)
1424 : {
1425 7 : snprintf(pabyNewField, sFieldDef.byLength, "%d",
1426 7 : ReadIntegerField(m_pasFieldDef[iField].byLength));
1427 : }
1428 0 : else if (m_pasFieldDef[iField].eTABType == TABFSmallInt)
1429 : {
1430 0 : snprintf(pabyNewField, sFieldDef.byLength, "%d",
1431 0 : ReadSmallIntField(m_pasFieldDef[iField].byLength));
1432 : }
1433 0 : else if (m_pasFieldDef[iField].eTABType == TABFLargeInt)
1434 : {
1435 0 : snprintf(pabyNewField, sFieldDef.byLength, CPL_FRMT_GIB,
1436 0 : ReadLargeIntField(m_pasFieldDef[iField].byLength));
1437 : }
1438 0 : else if (m_pasFieldDef[iField].eTABType == TABFFloat)
1439 : {
1440 0 : CPLsnprintf(pabyNewField, sFieldDef.byLength, "%.18f",
1441 0 : ReadFloatField(m_pasFieldDef[iField].byLength));
1442 : }
1443 0 : else if (m_pasFieldDef[iField].eTABType == TABFDecimal)
1444 : {
1445 0 : CPLsnprintf(pabyNewField, sFieldDef.byLength, "%.18f",
1446 0 : ReadFloatField(m_pasFieldDef[iField].byLength));
1447 : }
1448 0 : else if (m_pasFieldDef[iField].eTABType == TABFLogical)
1449 : {
1450 0 : strncpy(pabyNewField,
1451 0 : ReadLogicalField(m_pasFieldDef[iField].byLength) ? "T"
1452 : : "F",
1453 0 : sFieldDef.byLength);
1454 : }
1455 0 : else if (m_pasFieldDef[iField].eTABType == TABFDate)
1456 : {
1457 0 : strncpy(pabyNewField,
1458 0 : ReadDateField(m_pasFieldDef[iField].byLength),
1459 0 : sFieldDef.byLength);
1460 : }
1461 0 : else if (m_pasFieldDef[iField].eTABType == TABFTime)
1462 : {
1463 0 : strncpy(pabyNewField,
1464 0 : ReadTimeField(m_pasFieldDef[iField].byLength),
1465 0 : sFieldDef.byLength);
1466 : }
1467 0 : else if (m_pasFieldDef[iField].eTABType == TABFDateTime)
1468 : {
1469 0 : strncpy(pabyNewField,
1470 0 : ReadDateTimeField(m_pasFieldDef[iField].byLength),
1471 0 : sFieldDef.byLength);
1472 : }
1473 :
1474 39 : if (oTempFile.m_poRecordBlock->WriteBytes(
1475 13 : sFieldDef.byLength,
1476 25 : reinterpret_cast<GByte *>(pabyNewField)) != 0 ||
1477 12 : (nRecordSizeAfter > 0 &&
1478 12 : (m_poRecordBlock->ReadBytes(nRecordSizeAfter, pabyRecord) !=
1479 12 : 0 ||
1480 12 : oTempFile.m_poRecordBlock->WriteBytes(nRecordSizeAfter,
1481 12 : pabyRecord) != 0)))
1482 : {
1483 0 : CPLFree(pabyRecord);
1484 0 : CPLFree(pabyNewField);
1485 0 : oTempFile.Close();
1486 0 : VSIUnlink(osTmpFile);
1487 0 : return -1;
1488 : }
1489 13 : oTempFile.CommitRecordToFile();
1490 : }
1491 : }
1492 :
1493 5 : CPLFree(pabyRecord);
1494 5 : CPLFree(pabyNewField);
1495 :
1496 5 : oTempFile.Close();
1497 :
1498 : // Backup field definitions as we will need to set the TABFieldType.
1499 : TABDATFieldDef *pasFieldDefTmp = static_cast<TABDATFieldDef *>(
1500 5 : CPLMalloc(m_numFields * sizeof(TABDATFieldDef)));
1501 5 : memcpy(pasFieldDefTmp, m_pasFieldDef, m_numFields * sizeof(TABDATFieldDef));
1502 :
1503 5 : Close();
1504 :
1505 : // Move temporary file as main .data file and reopen it.
1506 5 : VSIUnlink(osOriginalFile);
1507 5 : VSIRename(osTmpFile, osOriginalFile);
1508 5 : if (Open(osOriginalFile, TABReadWrite) < 0)
1509 : {
1510 0 : CPLFree(pasFieldDefTmp);
1511 0 : return -1;
1512 : }
1513 :
1514 : // Restore saved TABFieldType.
1515 24 : for (int i = 0; i < m_numFields; i++)
1516 : {
1517 19 : if (i != iField)
1518 14 : m_pasFieldDef[i].eTABType = pasFieldDefTmp[i].eTABType;
1519 : else
1520 5 : m_pasFieldDef[i].eTABType = eTABType;
1521 : }
1522 5 : CPLFree(pasFieldDefTmp);
1523 :
1524 5 : return 0;
1525 : }
1526 :
1527 : /**********************************************************************
1528 : * TABDATFile::GetFieldType()
1529 : *
1530 : * Returns the native field type for field # nFieldId as previously set
1531 : * by ValidateFieldInfoFromTAB().
1532 : *
1533 : * Note that field ids are positive and start at 0.
1534 : **********************************************************************/
1535 547292 : TABFieldType TABDATFile::GetFieldType(int nFieldId)
1536 : {
1537 547292 : if (m_pasFieldDef == nullptr || nFieldId < 0 || nFieldId >= m_numFields)
1538 0 : return TABFUnknown;
1539 :
1540 547292 : return m_pasFieldDef[nFieldId].eTABType;
1541 : }
1542 :
1543 : /**********************************************************************
1544 : * TABDATFile::GetFieldWidth()
1545 : *
1546 : * Returns the width for field # nFieldId as previously read from the
1547 : * .DAT header.
1548 : *
1549 : * Note that field ids are positive and start at 0.
1550 : **********************************************************************/
1551 531608 : int TABDATFile::GetFieldWidth(int nFieldId)
1552 : {
1553 531608 : if (m_pasFieldDef == nullptr || nFieldId < 0 || nFieldId >= m_numFields)
1554 0 : return 0;
1555 :
1556 531608 : return m_pasFieldDef[nFieldId].byLength;
1557 : }
1558 :
1559 : /**********************************************************************
1560 : * TABDATFile::GetFieldPrecision()
1561 : *
1562 : * Returns the precision for field # nFieldId as previously read from the
1563 : * .DAT header.
1564 : *
1565 : * Note that field ids are positive and start at 0.
1566 : **********************************************************************/
1567 5 : int TABDATFile::GetFieldPrecision(int nFieldId)
1568 : {
1569 5 : if (m_pasFieldDef == nullptr || nFieldId < 0 || nFieldId >= m_numFields)
1570 0 : return 0;
1571 :
1572 5 : return m_pasFieldDef[nFieldId].byDecimals;
1573 : }
1574 :
1575 : /**********************************************************************
1576 : * TABDATFile::ReadCharField()
1577 : *
1578 : * Read the character field value at the current position in the data
1579 : * block.
1580 : *
1581 : * Use GetRecordBlock() to position the data block to the beginning of
1582 : * a record before attempting to read values.
1583 : *
1584 : * nWidth is the field length, as defined in the .DAT header.
1585 : *
1586 : * Returns a reference to an internal buffer that will be valid only until
1587 : * the next field is read, or "" if the operation failed, in which case
1588 : * CPLError() will have been called.
1589 : **********************************************************************/
1590 1342 : const char *TABDATFile::ReadCharField(int nWidth)
1591 : {
1592 : // If current record has been deleted, then return an acceptable
1593 : // default value.
1594 1342 : if (m_bCurRecordDeletedFlag)
1595 0 : return "";
1596 :
1597 1342 : if (m_poRecordBlock == nullptr)
1598 : {
1599 0 : CPLError(CE_Failure, CPLE_AssertionFailed,
1600 : "Can't read field value: file is not opened.");
1601 0 : return "";
1602 : }
1603 :
1604 1342 : if (nWidth < 1 || nWidth > 255)
1605 : {
1606 0 : CPLError(CE_Failure, CPLE_AssertionFailed,
1607 : "Illegal width for a char field: %d", nWidth);
1608 0 : return "";
1609 : }
1610 :
1611 2684 : if (m_poRecordBlock->ReadBytes(nWidth,
1612 1342 : reinterpret_cast<GByte *>(m_szBuffer)) != 0)
1613 0 : return "";
1614 :
1615 1342 : m_szBuffer[nWidth] = '\0';
1616 :
1617 : // NATIVE tables are padded with '\0' chars, but DBF tables are padded
1618 : // with spaces... get rid of the trailing spaces.
1619 1342 : if (m_eTableType == TABTableDBF)
1620 : {
1621 0 : int nLen = static_cast<int>(strlen(m_szBuffer)) - 1;
1622 0 : while (nLen >= 0 && m_szBuffer[nLen] == ' ')
1623 0 : m_szBuffer[nLen--] = '\0';
1624 : }
1625 :
1626 1342 : return m_szBuffer;
1627 : }
1628 :
1629 : /**********************************************************************
1630 : * TABDATFile::ReadIntegerField()
1631 : *
1632 : * Read the integer field value at the current position in the data
1633 : * block.
1634 : *
1635 : * Note: nWidth is used only with TABTableDBF types.
1636 : *
1637 : * CPLError() will have been called if something fails.
1638 : **********************************************************************/
1639 529219 : GInt32 TABDATFile::ReadIntegerField(int nWidth)
1640 : {
1641 : // If current record has been deleted, then return an acceptable
1642 : // default value.
1643 529219 : if (m_bCurRecordDeletedFlag)
1644 0 : return 0;
1645 :
1646 529219 : if (m_poRecordBlock == nullptr)
1647 : {
1648 0 : CPLError(CE_Failure, CPLE_AssertionFailed,
1649 : "Can't read field value: file is not opened.");
1650 0 : return 0;
1651 : }
1652 :
1653 529219 : if (m_eTableType == TABTableDBF)
1654 0 : return atoi(ReadCharField(nWidth));
1655 :
1656 529219 : return m_poRecordBlock->ReadInt32();
1657 : }
1658 :
1659 : /**********************************************************************
1660 : * TABDATFile::ReadSmallIntField()
1661 : *
1662 : * Read the smallint field value at the current position in the data
1663 : * block.
1664 : *
1665 : * Note: nWidth is used only with TABTableDBF types.
1666 : *
1667 : * CPLError() will have been called if something fails.
1668 : **********************************************************************/
1669 4 : GInt16 TABDATFile::ReadSmallIntField(int nWidth)
1670 : {
1671 : // If current record has been deleted, then return an acceptable
1672 : // default value.
1673 4 : if (m_bCurRecordDeletedFlag)
1674 0 : return 0;
1675 :
1676 4 : if (m_poRecordBlock == nullptr)
1677 : {
1678 0 : CPLError(CE_Failure, CPLE_AssertionFailed,
1679 : "Can't read field value: file is not opened.");
1680 0 : return 0;
1681 : }
1682 :
1683 4 : if (m_eTableType == TABTableDBF)
1684 0 : return static_cast<GInt16>(atoi(ReadCharField(nWidth)));
1685 :
1686 4 : return m_poRecordBlock->ReadInt16();
1687 : }
1688 :
1689 : /**********************************************************************
1690 : * TABDATFile::ReadLargeIntField()
1691 : *
1692 : * Read the largeint field value at the current position in the data
1693 : * block.
1694 : *
1695 : * Note: nWidth is used only with TABTableDBF types.
1696 : *
1697 : * CPLError() will have been called if something fails.
1698 : **********************************************************************/
1699 6 : GInt64 TABDATFile::ReadLargeIntField(int nWidth)
1700 : {
1701 : // If current record has been deleted, then return an acceptable
1702 : // default value.
1703 6 : if (m_bCurRecordDeletedFlag)
1704 0 : return 0;
1705 :
1706 6 : if (m_poRecordBlock == nullptr)
1707 : {
1708 0 : CPLError(CE_Failure, CPLE_AssertionFailed,
1709 : "Can't read field value: file is not opened.");
1710 0 : return 0;
1711 : }
1712 :
1713 6 : if (m_eTableType == TABTableDBF)
1714 0 : return static_cast<GIntBig>(CPLAtoGIntBig(ReadCharField(nWidth)));
1715 :
1716 6 : return m_poRecordBlock->ReadInt64();
1717 : }
1718 :
1719 : /**********************************************************************
1720 : * TABDATFile::ReadFloatField()
1721 : *
1722 : * Read the float field value at the current position in the data
1723 : * block.
1724 : *
1725 : * Note: nWidth is used only with TABTableDBF types.
1726 : *
1727 : * CPLError() will have been called if something fails.
1728 : **********************************************************************/
1729 598 : double TABDATFile::ReadFloatField(int nWidth)
1730 : {
1731 : // If current record has been deleted, then return an acceptable
1732 : // default value.
1733 598 : if (m_bCurRecordDeletedFlag)
1734 0 : return 0.0;
1735 :
1736 598 : if (m_poRecordBlock == nullptr)
1737 : {
1738 0 : CPLError(CE_Failure, CPLE_AssertionFailed,
1739 : "Can't read field value: file is not opened.");
1740 0 : return 0.0;
1741 : }
1742 :
1743 598 : if (m_eTableType == TABTableDBF)
1744 0 : return CPLAtof(ReadCharField(nWidth));
1745 :
1746 598 : return m_poRecordBlock->ReadDouble();
1747 : }
1748 :
1749 : /**********************************************************************
1750 : * TABDATFile::ReadLogicalField()
1751 : *
1752 : * Read the logical field value at the current position in the data
1753 : * block.
1754 : *
1755 : * Note: nWidth is used only with TABTableDBF types.
1756 : *
1757 : * CPLError() will have been called if something fails.
1758 : **********************************************************************/
1759 6 : bool TABDATFile::ReadLogicalField(int nWidth)
1760 : {
1761 : // If current record has been deleted, then return an acceptable
1762 : // default value.
1763 6 : if (m_bCurRecordDeletedFlag)
1764 0 : return false;
1765 :
1766 6 : if (m_poRecordBlock == nullptr)
1767 : {
1768 0 : CPLError(CE_Failure, CPLE_AssertionFailed,
1769 : "Can't read field value: file is not opened.");
1770 0 : return false;
1771 : }
1772 :
1773 6 : bool bValue = false;
1774 6 : if (m_eTableType == TABTableDBF)
1775 : {
1776 0 : const char *pszVal = ReadCharField(nWidth);
1777 0 : bValue = pszVal && strchr("1YyTt", pszVal[0]) != nullptr;
1778 : }
1779 : else
1780 : {
1781 : // In Native tables, we are guaranteed it is 1 byte with 0/1 value
1782 6 : bValue = CPL_TO_BOOL(m_poRecordBlock->ReadByte());
1783 : }
1784 :
1785 6 : return bValue;
1786 : }
1787 :
1788 : /**********************************************************************
1789 : * TABDATFile::ReadDateField()
1790 : *
1791 : * Read the logical field value at the current position in the data
1792 : * block.
1793 : *
1794 : * A date field is a 4 bytes binary value in which the first byte is
1795 : * the day, followed by 1 byte for the month, and 2 bytes for the year.
1796 : *
1797 : * We return an 8 chars string in the format "YYYYMMDD"
1798 : *
1799 : * Note: nWidth is used only with TABTableDBF types.
1800 : *
1801 : * Returns a reference to an internal buffer that will be valid only until
1802 : * the next field is read, or "" if the operation failed, in which case
1803 : * CPLError() will have been called.
1804 : **********************************************************************/
1805 0 : const char *TABDATFile::ReadDateField(int nWidth)
1806 : {
1807 0 : int nDay = 0;
1808 0 : int nMonth = 0;
1809 0 : int nYear = 0;
1810 0 : int status = ReadDateField(nWidth, &nYear, &nMonth, &nDay);
1811 :
1812 0 : if (status == -1)
1813 0 : return "";
1814 :
1815 0 : snprintf(m_szBuffer, sizeof(m_szBuffer), "%4.4d%2.2d%2.2d", nYear, nMonth,
1816 : nDay);
1817 :
1818 0 : return m_szBuffer;
1819 : }
1820 :
1821 29 : int TABDATFile::ReadDateField(int nWidth, int *nYear, int *nMonth, int *nDay)
1822 : {
1823 : // If current record has been deleted, then return an acceptable
1824 : // default value.
1825 29 : if (m_bCurRecordDeletedFlag)
1826 0 : return -1;
1827 :
1828 29 : if (m_poRecordBlock == nullptr)
1829 : {
1830 0 : CPLError(CE_Failure, CPLE_AssertionFailed,
1831 : "Can't read field value: file is not opened.");
1832 0 : return -1;
1833 : }
1834 :
1835 : // With .DBF files, the value should already be
1836 : // stored in YYYYMMDD format according to DBF specs.
1837 29 : if (m_eTableType == TABTableDBF)
1838 : {
1839 0 : strcpy(m_szBuffer, ReadCharField(nWidth));
1840 0 : sscanf(m_szBuffer, "%4d%2d%2d", nYear, nMonth, nDay);
1841 : }
1842 : else
1843 : {
1844 29 : *nYear = m_poRecordBlock->ReadInt16();
1845 29 : *nMonth = m_poRecordBlock->ReadByte();
1846 29 : *nDay = m_poRecordBlock->ReadByte();
1847 : }
1848 :
1849 58 : if (CPLGetLastErrorType() == CE_Failure ||
1850 29 : (*nYear == 0 && *nMonth == 0 && *nDay == 0))
1851 17 : return -1;
1852 :
1853 12 : return 0;
1854 : }
1855 :
1856 : /**********************************************************************
1857 : * TABDATFile::ReadTimeField()
1858 : *
1859 : * Read the Time field value at the current position in the data
1860 : * block.
1861 : *
1862 : * A time field is a 4 bytes binary value which represents the number
1863 : * of milliseconds since midnight.
1864 : *
1865 : * We return a 9 char string in the format "HHMMSSMMM"
1866 : *
1867 : * Note: nWidth is used only with TABTableDBF types.
1868 : *
1869 : * Returns a reference to an internal buffer that will be valid only until
1870 : * the next field is read, or "" if the operation failed, in which case
1871 : * CPLError() will have been called.
1872 : **********************************************************************/
1873 0 : const char *TABDATFile::ReadTimeField(int nWidth)
1874 : {
1875 0 : int nHour = 0;
1876 0 : int nMinute = 0;
1877 0 : int nSecond = 0;
1878 0 : int nMS = 0;
1879 0 : int status = ReadTimeField(nWidth, &nHour, &nMinute, &nSecond, &nMS);
1880 :
1881 0 : if (status == -1)
1882 0 : return "";
1883 :
1884 0 : snprintf(m_szBuffer, sizeof(m_szBuffer), "%2.2d%2.2d%2.2d%3.3d", nHour,
1885 : nMinute, nSecond, nMS);
1886 :
1887 0 : return m_szBuffer;
1888 : }
1889 :
1890 7 : int TABDATFile::ReadTimeField(int nWidth, int *nHour, int *nMinute,
1891 : int *nSecond, int *nMS)
1892 : {
1893 7 : GInt32 nS = 0;
1894 : // If current record has been deleted, then return an acceptable
1895 : // default value.
1896 7 : if (m_bCurRecordDeletedFlag)
1897 0 : return -1;
1898 :
1899 7 : if (m_poRecordBlock == nullptr)
1900 : {
1901 0 : CPLError(CE_Failure, CPLE_AssertionFailed,
1902 : "Can't read field value: file is not opened.");
1903 0 : return -1;
1904 : }
1905 :
1906 : // With .DBF files, the value should already be stored in
1907 : // HHMMSSMMM format according to DBF specs.
1908 7 : if (m_eTableType == TABTableDBF)
1909 : {
1910 0 : strcpy(m_szBuffer, ReadCharField(nWidth));
1911 0 : sscanf(m_szBuffer, "%2d%2d%2d%3d", nHour, nMinute, nSecond, nMS);
1912 : }
1913 : else
1914 : {
1915 7 : nS = m_poRecordBlock->ReadInt32(); // Convert time from ms to sec
1916 : }
1917 :
1918 : // nS is set to -1 when the value is 'not set'
1919 7 : if (CPLGetLastErrorType() == CE_Failure || nS < 0 || (nS > 86400000))
1920 1 : return -1;
1921 :
1922 6 : *nHour = int(nS / 3600000);
1923 6 : *nMinute = int((nS / 1000 - *nHour * 3600) / 60);
1924 6 : *nSecond = int(nS / 1000 - *nHour * 3600 - *nMinute * 60);
1925 6 : *nMS = int(nS - *nHour * 3600000 - *nMinute * 60000 - *nSecond * 1000);
1926 :
1927 6 : return 0;
1928 : }
1929 :
1930 : /**********************************************************************
1931 : * TABDATFile::ReadDateTimeField()
1932 : *
1933 : * Read the DateTime field value at the current position in the data
1934 : * block.
1935 : *
1936 : * A datetime field is an 8 bytes binary value in which the first byte is
1937 : * the day, followed by 1 byte for the month, and 2 bytes for the year. After
1938 : * this is 4 bytes which represents the number of milliseconds since midnight.
1939 : *
1940 : * We return an 17 chars string in the format "YYYYMMDDhhmmssmmm"
1941 : *
1942 : * Note: nWidth is used only with TABTableDBF types.
1943 : *
1944 : * Returns a reference to an internal buffer that will be valid only until
1945 : * the next field is read, or "" if the operation failed, in which case
1946 : * CPLError() will have been called.
1947 : **********************************************************************/
1948 0 : const char *TABDATFile::ReadDateTimeField(int nWidth)
1949 : {
1950 0 : int nDay = 0;
1951 0 : int nMonth = 0;
1952 0 : int nYear = 0;
1953 0 : int nHour = 0;
1954 0 : int nMinute = 0;
1955 0 : int nSecond = 0;
1956 0 : int nMS = 0;
1957 0 : int status = ReadDateTimeField(nWidth, &nYear, &nMonth, &nDay, &nHour,
1958 : &nMinute, &nSecond, &nMS);
1959 :
1960 0 : if (status == -1)
1961 0 : return "";
1962 :
1963 0 : snprintf(m_szBuffer, sizeof(m_szBuffer),
1964 : "%4.4d%2.2d%2.2d%2.2d%2.2d%2.2d%3.3d", nYear, nMonth, nDay, nHour,
1965 : nMinute, nSecond, nMS);
1966 :
1967 0 : return m_szBuffer;
1968 : }
1969 :
1970 23 : int TABDATFile::ReadDateTimeField(int nWidth, int *nYear, int *nMonth,
1971 : int *nDay, int *nHour, int *nMinute,
1972 : int *nSecond, int *nMS)
1973 : {
1974 23 : GInt32 nS = 0;
1975 : // If current record has been deleted, then return an acceptable
1976 : // default value.
1977 23 : if (m_bCurRecordDeletedFlag)
1978 0 : return -1;
1979 :
1980 23 : if (m_poRecordBlock == nullptr)
1981 : {
1982 0 : CPLError(CE_Failure, CPLE_AssertionFailed,
1983 : "Can't read field value: file is not opened.");
1984 0 : return -1;
1985 : }
1986 :
1987 : // With .DBF files, the value should already be stored in
1988 : // YYYYMMDD format according to DBF specs.
1989 23 : if (m_eTableType == TABTableDBF)
1990 : {
1991 0 : strcpy(m_szBuffer, ReadCharField(nWidth));
1992 0 : sscanf(m_szBuffer, "%4d%2d%2d%2d%2d%2d%3d", nYear, nMonth, nDay, nHour,
1993 : nMinute, nSecond, nMS);
1994 : }
1995 : else
1996 : {
1997 23 : *nYear = m_poRecordBlock->ReadInt16();
1998 23 : *nMonth = m_poRecordBlock->ReadByte();
1999 23 : *nDay = m_poRecordBlock->ReadByte();
2000 23 : nS = m_poRecordBlock->ReadInt32();
2001 : }
2002 :
2003 23 : if (CPLGetLastErrorType() == CE_Failure ||
2004 23 : (*nYear == 0 && *nMonth == 0 && *nDay == 0) || (nS > 86400000))
2005 17 : return -1;
2006 :
2007 6 : *nHour = int(nS / 3600000);
2008 6 : *nMinute = int((nS / 1000 - *nHour * 3600) / 60);
2009 6 : *nSecond = int(nS / 1000 - *nHour * 3600 - *nMinute * 60);
2010 6 : *nMS = int(nS - *nHour * 3600000 - *nMinute * 60000 - *nSecond * 1000);
2011 :
2012 6 : return 0;
2013 : }
2014 :
2015 : /**********************************************************************
2016 : * TABDATFile::ReadDecimalField()
2017 : *
2018 : * Read the decimal field value at the current position in the data
2019 : * block.
2020 : *
2021 : * A decimal field is a floating point value with a fixed number of digits
2022 : * stored as a character string.
2023 : *
2024 : * nWidth is the field length, as defined in the .DAT header.
2025 : *
2026 : * We return the value as a binary double.
2027 : *
2028 : * CPLError() will have been called if something fails.
2029 : **********************************************************************/
2030 28 : double TABDATFile::ReadDecimalField(int nWidth)
2031 : {
2032 : // If current record has been deleted, then return an acceptable
2033 : // default value.
2034 28 : if (m_bCurRecordDeletedFlag)
2035 0 : return 0.0;
2036 :
2037 28 : const char *pszVal = ReadCharField(nWidth);
2038 :
2039 28 : return CPLAtof(pszVal);
2040 : }
2041 :
2042 : /**********************************************************************
2043 : * TABDATFile::WriteCharField()
2044 : *
2045 : * Write the character field value at the current position in the data
2046 : * block.
2047 : *
2048 : * Use GetRecordBlock() to position the data block to the beginning of
2049 : * a record before attempting to write values.
2050 : *
2051 : * nWidth is the field length, as defined in the .DAT header.
2052 : *
2053 : * Returns 0 on success, or -1 if the operation failed, in which case
2054 : * CPLError() will have been called.
2055 : **********************************************************************/
2056 377 : int TABDATFile::WriteCharField(const char *pszStr, int nWidth,
2057 : TABINDFile *poINDFile, int nIndexNo)
2058 : {
2059 377 : if (m_poRecordBlock == nullptr)
2060 : {
2061 0 : CPLError(
2062 : CE_Failure, CPLE_AssertionFailed,
2063 : "Can't write field value: GetRecordBlock() has not been called.");
2064 0 : return -1;
2065 : }
2066 :
2067 377 : if (nWidth < 1 || nWidth > 255)
2068 : {
2069 0 : CPLError(CE_Failure, CPLE_AssertionFailed,
2070 : "Illegal width for a char field: %d", nWidth);
2071 0 : return -1;
2072 : }
2073 :
2074 : //
2075 : // Write the buffer after making sure that we don't try to read
2076 : // past the end of the source buffer. The rest of the field will
2077 : // be padded with zeros if source string is shorter than specified
2078 : // field width.
2079 : //
2080 377 : const int nLen = std::min(static_cast<int>(strlen(pszStr)), nWidth);
2081 :
2082 277 : if ((nLen > 0 && m_poRecordBlock->WriteBytes(
2083 754 : nLen, reinterpret_cast<const GByte *>(pszStr)) != 0) ||
2084 377 : (nWidth - nLen > 0 && m_poRecordBlock->WriteZeros(nWidth - nLen) != 0))
2085 0 : return -1;
2086 :
2087 : // Update Index
2088 377 : if (poINDFile && nIndexNo > 0)
2089 : {
2090 0 : GByte *pKey = poINDFile->BuildKey(nIndexNo, pszStr);
2091 0 : if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0)
2092 0 : return -1;
2093 : }
2094 :
2095 377 : return 0;
2096 : }
2097 :
2098 : /**********************************************************************
2099 : * TABDATFile::WriteIntegerField()
2100 : *
2101 : * Write the integer field value at the current position in the data
2102 : * block.
2103 : *
2104 : * CPLError() will have been called if something fails.
2105 : **********************************************************************/
2106 14994 : int TABDATFile::WriteIntegerField(GInt32 nValue, TABINDFile *poINDFile,
2107 : int nIndexNo)
2108 : {
2109 14994 : if (m_poRecordBlock == nullptr)
2110 : {
2111 0 : CPLError(
2112 : CE_Failure, CPLE_AssertionFailed,
2113 : "Can't write field value: GetRecordBlock() has not been called.");
2114 0 : return -1;
2115 : }
2116 :
2117 : // Update Index
2118 14994 : if (poINDFile && nIndexNo > 0)
2119 : {
2120 2 : GByte *pKey = poINDFile->BuildKey(nIndexNo, nValue);
2121 2 : if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0)
2122 0 : return -1;
2123 : }
2124 :
2125 14994 : return m_poRecordBlock->WriteInt32(nValue);
2126 : }
2127 :
2128 : /**********************************************************************
2129 : * TABDATFile::WriteSmallIntField()
2130 : *
2131 : * Write the smallint field value at the current position in the data
2132 : * block.
2133 : *
2134 : * CPLError() will have been called if something fails.
2135 : **********************************************************************/
2136 0 : int TABDATFile::WriteSmallIntField(GInt16 nValue, TABINDFile *poINDFile,
2137 : int nIndexNo)
2138 : {
2139 0 : if (m_poRecordBlock == nullptr)
2140 : {
2141 0 : CPLError(
2142 : CE_Failure, CPLE_AssertionFailed,
2143 : "Can't write field value: GetRecordBlock() has not been called.");
2144 0 : return -1;
2145 : }
2146 :
2147 : // Update Index
2148 0 : if (poINDFile && nIndexNo > 0)
2149 : {
2150 0 : GByte *pKey = poINDFile->BuildKey(nIndexNo, nValue);
2151 0 : if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0)
2152 0 : return -1;
2153 : }
2154 :
2155 0 : return m_poRecordBlock->WriteInt16(nValue);
2156 : }
2157 :
2158 : /**********************************************************************
2159 : * TABDATFile::WriteLargeIntField()
2160 : *
2161 : * Write the smallint field value at the current position in the data
2162 : * block.
2163 : *
2164 : * CPLError() will have been called if something fails.
2165 : **********************************************************************/
2166 2 : int TABDATFile::WriteLargeIntField(GInt64 nValue, TABINDFile *poINDFile,
2167 : int nIndexNo)
2168 : {
2169 2 : if (m_poRecordBlock == nullptr)
2170 : {
2171 0 : CPLError(
2172 : CE_Failure, CPLE_AssertionFailed,
2173 : "Can't write field value: GetRecordBlock() has not been called.");
2174 0 : return -1;
2175 : }
2176 :
2177 : // Update Index
2178 2 : if (poINDFile && nIndexNo > 0)
2179 : {
2180 0 : GByte *pKey = poINDFile->BuildKey(nIndexNo, nValue);
2181 0 : if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0)
2182 0 : return -1;
2183 : }
2184 :
2185 2 : return m_poRecordBlock->WriteInt64(nValue);
2186 : }
2187 :
2188 : /**********************************************************************
2189 : * TABDATFile::WriteFloatField()
2190 : *
2191 : * Write the float field value at the current position in the data
2192 : * block.
2193 : *
2194 : * CPLError() will have been called if something fails.
2195 : **********************************************************************/
2196 237 : int TABDATFile::WriteFloatField(double dValue, TABINDFile *poINDFile,
2197 : int nIndexNo)
2198 : {
2199 237 : if (m_poRecordBlock == nullptr)
2200 : {
2201 0 : CPLError(
2202 : CE_Failure, CPLE_AssertionFailed,
2203 : "Can't write field value: GetRecordBlock() has not been called.");
2204 0 : return -1;
2205 : }
2206 :
2207 : // Update Index
2208 237 : if (poINDFile && nIndexNo > 0)
2209 : {
2210 0 : GByte *pKey = poINDFile->BuildKey(nIndexNo, dValue);
2211 0 : if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0)
2212 0 : return -1;
2213 : }
2214 :
2215 237 : return m_poRecordBlock->WriteDouble(dValue);
2216 : }
2217 :
2218 : /**********************************************************************
2219 : * TABDATFile::WriteLogicalField()
2220 : *
2221 : * Write the logical field value at the current position in the data
2222 : * block.
2223 : *
2224 : * The value written to the file is either 0 or 1.
2225 : *
2226 : * CPLError() will have been called if something fails.
2227 : **********************************************************************/
2228 2 : int TABDATFile::WriteLogicalField(bool bValue, TABINDFile *poINDFile,
2229 : int nIndexNo)
2230 : {
2231 2 : if (m_poRecordBlock == nullptr)
2232 : {
2233 0 : CPLError(
2234 : CE_Failure, CPLE_AssertionFailed,
2235 : "Can't write field value: GetRecordBlock() has not been called.");
2236 0 : return -1;
2237 : }
2238 :
2239 2 : const GByte byValue = bValue ? 1 : 0;
2240 :
2241 : // Update Index
2242 2 : if (poINDFile && nIndexNo > 0)
2243 : {
2244 0 : GByte *pKey = poINDFile->BuildKey(nIndexNo, static_cast<int>(byValue));
2245 0 : if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0)
2246 0 : return -1;
2247 : }
2248 :
2249 2 : return m_poRecordBlock->WriteByte(byValue);
2250 : }
2251 :
2252 : /**********************************************************************
2253 : * TABDATFile::WriteDateField()
2254 : *
2255 : * Write the date field value at the current position in the data
2256 : * block.
2257 : *
2258 : * A date field is a 4 bytes binary value in which the first byte is
2259 : * the day, followed by 1 byte for the month, and 2 bytes for the year.
2260 : *
2261 : * The expected input is a 10 chars string in the format "YYYY/MM/DD"
2262 : * or "DD/MM/YYYY" or "YYYYMMDD"
2263 : *
2264 : * Returns 0 on success, or -1 if the operation failed, in which case
2265 : * CPLError() will have been called.
2266 : **********************************************************************/
2267 0 : int TABDATFile::WriteDateField(const char *pszValue, TABINDFile *poINDFile,
2268 : int nIndexNo)
2269 : {
2270 0 : char **papszTok = nullptr;
2271 :
2272 : // Get rid of leading spaces.
2273 0 : while (*pszValue == ' ')
2274 : {
2275 0 : pszValue++;
2276 : }
2277 :
2278 : // Try to automagically detect date format, one of:
2279 : // "YYYY/MM/DD", "DD/MM/YYYY", or "YYYYMMDD"
2280 0 : int nDay = 0;
2281 0 : int nMonth = 0;
2282 0 : int nYear = 0;
2283 :
2284 0 : if (strlen(pszValue) == 8)
2285 : {
2286 : // "YYYYMMDD"
2287 0 : char szBuf[9] = {};
2288 0 : strcpy(szBuf, pszValue);
2289 0 : nDay = atoi(szBuf + 6);
2290 0 : szBuf[6] = '\0';
2291 0 : nMonth = atoi(szBuf + 4);
2292 0 : szBuf[4] = '\0';
2293 0 : nYear = atoi(szBuf);
2294 : }
2295 0 : else if (strlen(pszValue) == 10 &&
2296 0 : (papszTok = CSLTokenizeStringComplex(pszValue, "/", FALSE,
2297 0 : FALSE)) != nullptr &&
2298 0 : CSLCount(papszTok) == 3 &&
2299 0 : (strlen(papszTok[0]) == 4 || strlen(papszTok[2]) == 4))
2300 : {
2301 : // Either "YYYY/MM/DD" or "DD/MM/YYYY"
2302 0 : if (strlen(papszTok[0]) == 4)
2303 : {
2304 0 : nYear = atoi(papszTok[0]);
2305 0 : nMonth = atoi(papszTok[1]);
2306 0 : nDay = atoi(papszTok[2]);
2307 : }
2308 : else
2309 : {
2310 0 : nYear = atoi(papszTok[2]);
2311 0 : nMonth = atoi(papszTok[1]);
2312 0 : nDay = atoi(papszTok[0]);
2313 : }
2314 : }
2315 0 : else if (strlen(pszValue) == 0)
2316 : {
2317 0 : nYear = 0;
2318 0 : nMonth = 0;
2319 0 : nDay = 0;
2320 : }
2321 : else
2322 : {
2323 0 : CPLError(CE_Failure, CPLE_AppDefined,
2324 : "Invalid date field value `%s'. Date field values must "
2325 : "be in the format `YYYY/MM/DD', `MM/DD/YYYY' or `YYYYMMDD'",
2326 : pszValue);
2327 0 : CSLDestroy(papszTok);
2328 0 : return -1;
2329 : }
2330 0 : CSLDestroy(papszTok);
2331 :
2332 0 : return WriteDateField(nYear, nMonth, nDay, poINDFile, nIndexNo);
2333 : }
2334 :
2335 76 : int TABDATFile::WriteDateField(int nYear, int nMonth, int nDay,
2336 : TABINDFile *poINDFile, int nIndexNo)
2337 : {
2338 76 : if (m_poRecordBlock == nullptr)
2339 : {
2340 0 : CPLError(
2341 : CE_Failure, CPLE_AssertionFailed,
2342 : "Can't write field value: GetRecordBlock() has not been called.");
2343 0 : return -1;
2344 : }
2345 :
2346 76 : m_poRecordBlock->WriteInt16(static_cast<GInt16>(nYear));
2347 76 : m_poRecordBlock->WriteByte(static_cast<GByte>(nMonth));
2348 76 : m_poRecordBlock->WriteByte(static_cast<GByte>(nDay));
2349 :
2350 76 : if (CPLGetLastErrorType() == CE_Failure)
2351 0 : return -1;
2352 :
2353 : // Update Index
2354 76 : if (poINDFile && nIndexNo > 0)
2355 : {
2356 0 : GByte *pKey = poINDFile->BuildKey(
2357 0 : nIndexNo, (nYear * 0x10000 + nMonth * 0x100 + nDay));
2358 0 : if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0)
2359 0 : return -1;
2360 : }
2361 :
2362 76 : return 0;
2363 : }
2364 :
2365 : /**********************************************************************
2366 : * TABDATFile::WriteTimeField()
2367 : *
2368 : * Write the date field value at the current position in the data
2369 : * block.
2370 : *
2371 : * A time field is a 4 byte binary value which represents the number
2372 : * of milliseconds since midnight.
2373 : *
2374 : * The expected input is a 10 chars string in the format "HH:MM:SS"
2375 : * or "HHMMSSmmm"
2376 : *
2377 : * Returns 0 on success, or -1 if the operation failed, in which case
2378 : * CPLError() will have been called.
2379 : **********************************************************************/
2380 0 : int TABDATFile::WriteTimeField(const char *pszValue, TABINDFile *poINDFile,
2381 : int nIndexNo)
2382 : {
2383 : // Get rid of leading spaces.
2384 0 : while (*pszValue == ' ')
2385 : {
2386 0 : pszValue++;
2387 : }
2388 :
2389 : // Try to automagically detect time format, one of:
2390 : // "HH:MM:SS", or "HHMMSSmmm"
2391 0 : int nHour = 0;
2392 0 : int nMin = 0;
2393 0 : int nSec = 0;
2394 0 : int nMS = 0;
2395 :
2396 0 : if (strlen(pszValue) == 8)
2397 : {
2398 : // "HH:MM:SS"
2399 0 : char szBuf[9] = {};
2400 0 : strcpy(szBuf, pszValue);
2401 0 : szBuf[2] = 0;
2402 0 : szBuf[5] = 0;
2403 0 : nHour = atoi(szBuf);
2404 0 : nMin = atoi(szBuf + 3);
2405 0 : nSec = atoi(szBuf + 6);
2406 0 : nMS = 0;
2407 : }
2408 0 : else if (strlen(pszValue) == 9)
2409 : {
2410 : // "HHMMSSmmm"
2411 0 : char szBuf[4] = {};
2412 0 : const int HHLength = 2;
2413 0 : strncpy(szBuf, pszValue, HHLength);
2414 0 : szBuf[HHLength] = 0;
2415 0 : nHour = atoi(szBuf);
2416 :
2417 0 : const int MMLength = 2;
2418 0 : strncpy(szBuf, pszValue + HHLength, MMLength);
2419 0 : szBuf[MMLength] = 0;
2420 0 : nMin = atoi(szBuf);
2421 :
2422 0 : const int SSLength = 2;
2423 0 : strncpy(szBuf, pszValue + HHLength + MMLength, SSLength);
2424 0 : szBuf[SSLength] = 0;
2425 0 : nSec = atoi(szBuf);
2426 :
2427 0 : const int mmmLength = 3;
2428 0 : strncpy(szBuf, pszValue + HHLength + MMLength + SSLength, mmmLength);
2429 0 : szBuf[mmmLength] = 0;
2430 0 : nMS = atoi(szBuf);
2431 : }
2432 0 : else if (strlen(pszValue) == 0)
2433 : {
2434 : // Write -1 to .DAT file if value is not set
2435 0 : nHour = -1;
2436 0 : nMin = -1;
2437 0 : nSec = -1;
2438 0 : nMS = -1;
2439 : }
2440 : else
2441 : {
2442 0 : CPLError(CE_Failure, CPLE_AppDefined,
2443 : "Invalid time field value `%s'. Time field values must "
2444 : "be in the format `HH:MM:SS', or `HHMMSSmmm'",
2445 : pszValue);
2446 0 : return -1;
2447 : }
2448 :
2449 0 : return WriteTimeField(nHour, nMin, nSec, nMS, poINDFile, nIndexNo);
2450 : }
2451 :
2452 3 : int TABDATFile::WriteTimeField(int nHour, int nMinute, int nSecond, int nMS,
2453 : TABINDFile *poINDFile, int nIndexNo)
2454 : {
2455 3 : GInt32 nS = -1;
2456 :
2457 3 : if (m_poRecordBlock == nullptr)
2458 : {
2459 0 : CPLError(
2460 : CE_Failure, CPLE_AssertionFailed,
2461 : "Can't write field value: GetRecordBlock() has not been called.");
2462 0 : return -1;
2463 : }
2464 :
2465 3 : nS = (nHour * 3600 + nMinute * 60 + nSecond) * 1000 + nMS;
2466 3 : if (nS < 0)
2467 1 : nS = -1;
2468 3 : m_poRecordBlock->WriteInt32(nS);
2469 :
2470 3 : if (CPLGetLastErrorType() == CE_Failure)
2471 0 : return -1;
2472 :
2473 : // Update Index
2474 3 : if (poINDFile && nIndexNo > 0)
2475 : {
2476 0 : GByte *pKey = poINDFile->BuildKey(nIndexNo, (nS));
2477 0 : if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0)
2478 0 : return -1;
2479 : }
2480 :
2481 3 : return 0;
2482 : }
2483 :
2484 : /**********************************************************************
2485 : * TABDATFile::WriteDateTimeField()
2486 : *
2487 : * Write the DateTime field value at the current position in the data
2488 : * block.
2489 : *
2490 : * A datetime field is a 8 bytes binary value in which the first byte is
2491 : * the day, followe
2492 : d by 1 byte for the month, and 2 bytes for the year.
2493 : * After this the time value is stored as a 4 byte integer
2494 : * (milliseconds since midnight)
2495 : *
2496 : * The expected input is a 10 chars string in the format "YYYY/MM/DD HH:MM:SS"
2497 : * or "DD/MM/YYYY HH:MM:SS" or "YYYYMMDDhhmmssmmm"
2498 : *
2499 : * Returns 0 on success, or -1 if the operation failed, in which case
2500 : * CPLError() will have been called.
2501 : **********************************************************************/
2502 0 : int TABDATFile::WriteDateTimeField(const char *pszValue, TABINDFile *poINDFile,
2503 : int nIndexNo)
2504 : {
2505 : // Get rid of leading spaces.
2506 0 : while (*pszValue == ' ')
2507 : {
2508 0 : pszValue++;
2509 : }
2510 :
2511 : /*-----------------------------------------------------------------
2512 : * Try to automagically detect date format, one of:
2513 : * "YYYY/MM/DD HH:MM:SS", "DD/MM/YYYY HH:MM:SS", or "YYYYMMDDhhmmssmmm"
2514 : *----------------------------------------------------------------*/
2515 0 : int nDay = 0;
2516 0 : int nMonth = 0;
2517 0 : int nYear = 0;
2518 0 : int nHour = 0;
2519 0 : int nMin = 0;
2520 0 : int nSec = 0;
2521 0 : int nMS = 0;
2522 0 : char **papszTok = nullptr;
2523 :
2524 0 : if (strlen(pszValue) == 17)
2525 : {
2526 : // "YYYYMMDDhhmmssmmm"
2527 0 : char szBuf[18] = {};
2528 0 : strcpy(szBuf, pszValue);
2529 0 : nMS = atoi(szBuf + 14);
2530 0 : szBuf[14] = 0;
2531 0 : nSec = atoi(szBuf + 12);
2532 0 : szBuf[12] = 0;
2533 0 : nMin = atoi(szBuf + 10);
2534 0 : szBuf[10] = 0;
2535 0 : nHour = atoi(szBuf + 8);
2536 0 : szBuf[8] = 0;
2537 0 : nDay = atoi(szBuf + 6);
2538 0 : szBuf[6] = 0;
2539 0 : nMonth = atoi(szBuf + 4);
2540 0 : szBuf[4] = 0;
2541 0 : nYear = atoi(szBuf);
2542 : }
2543 0 : else if (strlen(pszValue) == 19 &&
2544 0 : (papszTok = CSLTokenizeStringComplex(pszValue, "/ :", FALSE,
2545 0 : FALSE)) != nullptr &&
2546 0 : CSLCount(papszTok) == 6 &&
2547 0 : (strlen(papszTok[0]) == 4 || strlen(papszTok[2]) == 4))
2548 : {
2549 : // Either "YYYY/MM/DD HH:MM:SS" or "DD/MM/YYYY HH:MM:SS"
2550 0 : if (strlen(papszTok[0]) == 4)
2551 : {
2552 0 : nYear = atoi(papszTok[0]);
2553 0 : nMonth = atoi(papszTok[1]);
2554 0 : nDay = atoi(papszTok[2]);
2555 0 : nHour = atoi(papszTok[3]);
2556 0 : nMin = atoi(papszTok[4]);
2557 0 : nSec = atoi(papszTok[5]);
2558 0 : nMS = 0;
2559 : }
2560 : else
2561 : {
2562 0 : nYear = atoi(papszTok[2]);
2563 0 : nMonth = atoi(papszTok[1]);
2564 0 : nDay = atoi(papszTok[0]);
2565 0 : nHour = atoi(papszTok[3]);
2566 0 : nMin = atoi(papszTok[4]);
2567 0 : nSec = atoi(papszTok[5]);
2568 0 : nMS = 0;
2569 : }
2570 : }
2571 0 : else if (strlen(pszValue) == 0)
2572 : {
2573 0 : nYear = 0;
2574 0 : nMonth = 0;
2575 0 : nDay = 0;
2576 0 : nHour = 0;
2577 0 : nMin = 0;
2578 0 : nSec = 0;
2579 0 : nMS = 0;
2580 : }
2581 : else
2582 : {
2583 0 : CPLError(CE_Failure, CPLE_AppDefined,
2584 : "Invalid date field value `%s'. Date field values must "
2585 : "be in the format `YYYY/MM/DD HH:MM:SS', "
2586 : "`MM/DD/YYYY HH:MM:SS' or `YYYYMMDDhhmmssmmm'",
2587 : pszValue);
2588 0 : CSLDestroy(papszTok);
2589 0 : return -1;
2590 : }
2591 0 : CSLDestroy(papszTok);
2592 :
2593 0 : return WriteDateTimeField(nYear, nMonth, nDay, nHour, nMin, nSec, nMS,
2594 0 : poINDFile, nIndexNo);
2595 : }
2596 :
2597 75 : int TABDATFile::WriteDateTimeField(int nYear, int nMonth, int nDay, int nHour,
2598 : int nMinute, int nSecond, int nMS,
2599 : TABINDFile *poINDFile, int nIndexNo)
2600 : {
2601 75 : GInt32 nS = (nHour * 3600 + nMinute * 60 + nSecond) * 1000 + nMS;
2602 :
2603 75 : if (m_poRecordBlock == nullptr)
2604 : {
2605 0 : CPLError(
2606 : CE_Failure, CPLE_AssertionFailed,
2607 : "Can't write field value: GetRecordBlock() has not been called.");
2608 0 : return -1;
2609 : }
2610 :
2611 75 : m_poRecordBlock->WriteInt16(static_cast<GInt16>(nYear));
2612 75 : m_poRecordBlock->WriteByte(static_cast<GByte>(nMonth));
2613 75 : m_poRecordBlock->WriteByte(static_cast<GByte>(nDay));
2614 75 : m_poRecordBlock->WriteInt32(nS);
2615 :
2616 75 : if (CPLGetLastErrorType() == CE_Failure)
2617 0 : return -1;
2618 :
2619 : // Update Index
2620 75 : if (poINDFile && nIndexNo > 0)
2621 : {
2622 : // __TODO__ (see bug #1844)
2623 : // Indexing on DateTime Fields not currently supported, that will
2624 : // require passing the 8 bytes datetime value to BuildKey() here...
2625 0 : CPLAssert(false);
2626 : GByte *pKey = poINDFile->BuildKey(
2627 : nIndexNo, (nYear * 0x10000 + nMonth * 0x100 + nDay));
2628 : if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0)
2629 : return -1;
2630 : }
2631 :
2632 75 : return 0;
2633 : }
2634 :
2635 : /**********************************************************************
2636 : * TABDATFile::WriteDecimalField()
2637 : *
2638 : * Write the decimal field value at the current position in the data
2639 : * block.
2640 : *
2641 : * A decimal field is a floating point value with a fixed number of digits
2642 : * stored as a character string.
2643 : *
2644 : * nWidth is the field length, as defined in the .DAT header.
2645 : *
2646 : * CPLError() will have been called if something fails.
2647 : **********************************************************************/
2648 5 : int TABDATFile::WriteDecimalField(double dValue, int nWidth, int nPrec,
2649 : TABINDFile *poINDFile, int nIndexNo)
2650 : {
2651 5 : char szFormat[10] = {};
2652 :
2653 5 : snprintf(szFormat, sizeof(szFormat), "%%%d.%df", nWidth, nPrec);
2654 5 : const char *pszVal = CPLSPrintf(szFormat, dValue);
2655 5 : if (static_cast<int>(strlen(pszVal)) > nWidth)
2656 : {
2657 1 : CPLError(CE_Failure, CPLE_AppDefined,
2658 : "Cannot format %g as a %d.%d field", dValue, nWidth, nPrec);
2659 1 : return -1;
2660 : }
2661 :
2662 : // Update Index
2663 4 : if (poINDFile && nIndexNo > 0)
2664 : {
2665 0 : GByte *pKey = poINDFile->BuildKey(nIndexNo, dValue);
2666 0 : if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0)
2667 0 : return -1;
2668 : }
2669 :
2670 4 : return m_poRecordBlock->WriteBytes(nWidth,
2671 4 : reinterpret_cast<const GByte *>(pszVal));
2672 : }
2673 :
2674 1856 : const CPLString &TABDATFile::GetEncoding() const
2675 : {
2676 1856 : return m_osEncoding;
2677 : }
2678 :
2679 2 : void TABDATFile::SetEncoding(const CPLString &osEncoding)
2680 : {
2681 2 : m_osEncoding = osEncoding;
2682 2 : }
2683 :
2684 : /**********************************************************************
2685 : * TABDATFile::Dump()
2686 : *
2687 : * Dump block contents... available only in DEBUG mode.
2688 : **********************************************************************/
2689 : #ifdef DEBUG
2690 :
2691 0 : void TABDATFile::Dump(FILE *fpOut /* =NULL */)
2692 : {
2693 0 : if (fpOut == nullptr)
2694 0 : fpOut = stdout;
2695 :
2696 0 : fprintf(fpOut, "----- TABDATFile::Dump() -----\n");
2697 :
2698 0 : if (m_fp == nullptr)
2699 : {
2700 0 : fprintf(fpOut, "File is not opened.\n");
2701 : }
2702 : else
2703 : {
2704 0 : fprintf(fpOut, "File is opened: %s\n", m_pszFname);
2705 0 : fprintf(fpOut, "m_numFields = %d\n", m_numFields);
2706 0 : fprintf(fpOut, "m_numRecords = %d\n", m_numRecords);
2707 : }
2708 :
2709 0 : fflush(fpOut);
2710 0 : }
2711 :
2712 : #endif // DEBUG
|