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