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