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