1 : /**********************************************************************
2 : * $Id: mitab_datfile.cpp,v 1.19 2008/01/29 20:46:32 dmorissette Exp $
3 : *
4 : * Name: mitab_datfile.cpp
5 : * Project: MapInfo TAB Read/Write library
6 : * Language: C++
7 : * Purpose: Implementation of the TABIDFile class used to handle
8 : * reading/writing of the .DAT file
9 : * Author: Daniel Morissette, dmorissette@dmsolutions.ca
10 : *
11 : **********************************************************************
12 : * Copyright (c) 1999-2001, Daniel Morissette
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 : * $Log: mitab_datfile.cpp,v $
34 : * Revision 1.19 2008/01/29 20:46:32 dmorissette
35 : * Added support for v9 Time and DateTime fields (byg 1754)
36 : *
37 : * Revision 1.18 2007/10/09 17:43:16 fwarmerdam
38 : * Remove static variables that interfere with reentrancy. (GDAL #1883)
39 : *
40 : * Revision 1.17 2004/06/30 20:29:03 dmorissette
41 : * Fixed refs to old address danmo@videotron.ca
42 : *
43 : * Revision 1.16 2001/05/01 12:32:03 daniel
44 : * Get rid of leading spaces in WriteDateField().
45 : *
46 : * Revision 1.15 2000/04/27 15:42:03 daniel
47 : * Map variable field length (width=0) coming from OGR to acceptable default
48 : *
49 : * Revision 1.14 2000/02/28 16:52:52 daniel
50 : * Added support for writing indexes, removed validation on field name in
51 : * NATIVE tables, and remove trailing spaces in DBF char field values
52 : *
53 : * Revision 1.13 2000/01/28 07:31:49 daniel
54 : * Validate char field width (must be <= 254 chars)
55 : *
56 : * Revision 1.12 2000/01/16 19:08:48 daniel
57 : * Added support for reading 'Table Type DBF' tables
58 : *
59 : * Revision 1.11 2000/01/15 22:30:43 daniel
60 : * Switch to MIT/X-Consortium OpenSource license
61 : *
62 : * Revision 1.10 1999/12/20 18:59:20 daniel
63 : * Dates again... now returned as "YYYYMMDD"
64 : *
65 : * Revision 1.9 1999/12/16 17:11:45 daniel
66 : * Date fields: return as "YYYY/MM/DD", and accept 3 diff. formats as input
67 : *
68 : * Revision 1.8 1999/12/14 03:58:29 daniel
69 : * Fixed date read/write (bytes were reversed)
70 : *
71 : * Revision 1.7 1999/11/09 07:34:35 daniel
72 : * Return default values when deleted attribute records are encountered
73 : *
74 : * Revision 1.6 1999/10/19 06:09:25 daniel
75 : * Removed obsolete GetFieldDef() method
76 : *
77 : * Revision 1.5 1999/10/01 03:56:28 daniel
78 : * Avoid multiple InitWriteHeader() calls (caused a leak) and added a fix
79 : * in WriteCharField() to prevent reading bytes past end of string buffer
80 : *
81 : * Revision 1.4 1999/10/01 02:02:36 warmerda
82 : * Added assertions to try and track TABRawBinBlock leak.
83 : *
84 : * Revision 1.3 1999/09/26 14:59:36 daniel
85 : * Implemented write support
86 : *
87 : * Revision 1.2 1999/09/20 18:43:20 daniel
88 : * Use binary access to open file.
89 : *
90 : * Revision 1.1 1999/07/12 04:18:23 daniel
91 : * Initial checkin
92 : *
93 : **********************************************************************/
94 :
95 : #include "mitab.h"
96 :
97 : /*=====================================================================
98 : * class TABDATFile
99 : *
100 : * Note that the .DAT files are .DBF files with some exceptions:
101 : *
102 : * All fields in the DBF header are defined as 'C' type (strings),
103 : * even for binary integers. So we have to look in the associated .TAB
104 : * file to find the real field definition.
105 : *
106 : * Even though binary integers are defined as 'C' type, they are stored
107 : * in binary form inside a 4 bytes string field.
108 : *====================================================================*/
109 :
110 :
111 : /**********************************************************************
112 : * TABDATFile::TABDATFile()
113 : *
114 : * Constructor.
115 : **********************************************************************/
116 4 : TABDATFile::TABDATFile()
117 : {
118 4 : m_fp = NULL;
119 4 : m_pszFname = NULL;
120 4 : m_eTableType = TABTableNative;
121 :
122 4 : m_poHeaderBlock = NULL;
123 4 : m_poRecordBlock = NULL;
124 4 : m_pasFieldDef = NULL;
125 :
126 4 : m_numFields = -1;
127 4 : m_numRecords = -1;
128 4 : m_nFirstRecordPtr = 0;
129 4 : m_nBlockSize = 0;
130 4 : m_nRecordSize = -1;
131 4 : m_nCurRecordId = -1;
132 4 : m_bCurRecordDeletedFlag = FALSE;
133 4 : m_bWriteHeaderInitialized = FALSE;
134 4 : }
135 :
136 : /**********************************************************************
137 : * TABDATFile::~TABDATFile()
138 : *
139 : * Destructor.
140 : **********************************************************************/
141 4 : TABDATFile::~TABDATFile()
142 : {
143 4 : Close();
144 4 : }
145 :
146 : /**********************************************************************
147 : * TABDATFile::Open()
148 : *
149 : * Open a .DAT file, and initialize the structures to be ready to read
150 : * records from it.
151 : *
152 : * We currently support NATIVE and DBF tables for reading, and only
153 : * NATIVE tables for writing.
154 : *
155 : * Returns 0 on success, -1 on error.
156 : **********************************************************************/
157 : int TABDATFile::Open(const char *pszFname, const char *pszAccess,
158 4 : TABTableType eTableType /*=TABNativeTable*/)
159 : {
160 : int i;
161 :
162 4 : if (m_fp)
163 : {
164 : CPLError(CE_Failure, CPLE_FileIO,
165 0 : "Open() failed: object already contains an open file");
166 0 : return -1;
167 : }
168 :
169 : /*-----------------------------------------------------------------
170 : * Validate access mode and make sure we use binary access.
171 : *----------------------------------------------------------------*/
172 6 : if (EQUALN(pszAccess, "r", 1) && (eTableType==TABTableNative ||
173 : eTableType==TABTableDBF) )
174 : {
175 2 : m_eAccessMode = TABRead;
176 2 : pszAccess = "rb";
177 : }
178 4 : else if (EQUALN(pszAccess, "w", 1) && eTableType==TABTableNative)
179 : {
180 2 : m_eAccessMode = TABWrite;
181 2 : pszAccess = "wb";
182 : }
183 : else
184 : {
185 : CPLError(CE_Failure, CPLE_FileIO,
186 0 : "Open() failed: access mode \"%s\" not supported", pszAccess);
187 0 : return -1;
188 : }
189 :
190 : /*-----------------------------------------------------------------
191 : * Open file for reading
192 : *----------------------------------------------------------------*/
193 4 : m_pszFname = CPLStrdup(pszFname);
194 4 : m_fp = VSIFOpen(m_pszFname, pszAccess);
195 4 : m_eTableType = eTableType;
196 :
197 4 : if (m_fp == NULL)
198 : {
199 : CPLError(CE_Failure, CPLE_FileIO,
200 0 : "Open() failed for %s", m_pszFname);
201 0 : CPLFree(m_pszFname);
202 0 : m_pszFname = NULL;
203 0 : return -1;
204 : }
205 :
206 4 : if (m_eAccessMode == TABRead)
207 : {
208 : /*------------------------------------------------------------
209 : * READ ACCESS:
210 : * Read .DAT file header (record size, num records, etc...)
211 : * m_poHeaderBlock will be reused later to read field definition
212 : *-----------------------------------------------------------*/
213 2 : m_poHeaderBlock = new TABRawBinBlock(m_eAccessMode, TRUE);
214 2 : m_poHeaderBlock->ReadFromFile(m_fp, 0, 32);
215 :
216 2 : m_poHeaderBlock->ReadByte(); // Table type ??? 0x03
217 2 : m_poHeaderBlock->ReadByte(); // Last update year
218 2 : m_poHeaderBlock->ReadByte(); // Last update month
219 2 : m_poHeaderBlock->ReadByte(); // Last update day
220 :
221 2 : m_numRecords = m_poHeaderBlock->ReadInt32();
222 2 : m_nFirstRecordPtr = m_poHeaderBlock->ReadInt16();
223 2 : m_nRecordSize = m_poHeaderBlock->ReadInt16();
224 :
225 2 : m_numFields = m_nFirstRecordPtr/32 - 1;
226 :
227 : /*-------------------------------------------------------------
228 : * Read the field definitions
229 : * First 32 bytes field definition starts at byte 32 in file
230 : *------------------------------------------------------------*/
231 : m_pasFieldDef = (TABDATFieldDef*)CPLCalloc(m_numFields,
232 2 : sizeof(TABDATFieldDef));
233 :
234 6 : for(i=0; i<m_numFields; i++)
235 : {
236 4 : m_poHeaderBlock->GotoByteInFile((i+1)*32);
237 4 : m_poHeaderBlock->ReadBytes(11, (GByte*)m_pasFieldDef[i].szName);
238 4 : m_pasFieldDef[i].szName[10] = '\0';
239 4 : m_pasFieldDef[i].cType = (char)m_poHeaderBlock->ReadByte();
240 :
241 4 : m_poHeaderBlock->ReadInt32(); // Skip Bytes 12-15
242 4 : m_pasFieldDef[i].byLength = m_poHeaderBlock->ReadByte();
243 4 : m_pasFieldDef[i].byDecimals = m_poHeaderBlock->ReadByte();
244 :
245 4 : m_pasFieldDef[i].eTABType = TABFUnknown;
246 : }
247 :
248 : /*-------------------------------------------------------------
249 : * Establish a good record block size to use based on record size, and
250 : * then create m_poRecordBlock
251 : * Record block size has to be a multiple of record size.
252 : *------------------------------------------------------------*/
253 2 : m_nBlockSize = ((1024/m_nRecordSize)+1)*m_nRecordSize;
254 2 : m_nBlockSize = MIN(m_nBlockSize, (m_numRecords*m_nRecordSize));
255 :
256 2 : CPLAssert( m_poRecordBlock == NULL );
257 2 : m_poRecordBlock = new TABRawBinBlock(m_eAccessMode, FALSE);
258 2 : m_poRecordBlock->InitNewBlock(m_fp, m_nBlockSize);
259 2 : m_poRecordBlock->SetFirstBlockPtr(m_nFirstRecordPtr);
260 : }
261 : else
262 : {
263 : /*------------------------------------------------------------
264 : * WRITE ACCESS:
265 : * Set acceptable defaults for all class members.
266 : * The real header initialization will be done when the first
267 : * record is written
268 : *-----------------------------------------------------------*/
269 2 : m_poHeaderBlock = NULL;
270 :
271 2 : m_numRecords = 0;
272 2 : m_nFirstRecordPtr = 0;
273 2 : m_nRecordSize = 0;
274 2 : m_numFields = 0;
275 2 : m_pasFieldDef = NULL;
276 2 : m_bWriteHeaderInitialized = FALSE;
277 : }
278 :
279 4 : return 0;
280 : }
281 :
282 : /**********************************************************************
283 : * TABDATFile::Close()
284 : *
285 : * Close current file, and release all memory used.
286 : *
287 : * Returns 0 on success, -1 on error.
288 : **********************************************************************/
289 8 : int TABDATFile::Close()
290 : {
291 8 : if (m_fp == NULL)
292 4 : return 0;
293 :
294 : /*----------------------------------------------------------------
295 : * Write access: Update the header with number of records, etc.
296 : * and add a CTRL-Z char at the end of the file.
297 : *---------------------------------------------------------------*/
298 4 : if (m_eAccessMode == TABWrite)
299 : {
300 2 : WriteHeader();
301 :
302 2 : char cEOF = 26;
303 2 : if (VSIFSeek(m_fp, 0L, SEEK_END) == 0)
304 2 : VSIFWrite(&cEOF, 1, 1, m_fp);
305 : }
306 :
307 : // Delete all structures
308 4 : if (m_poHeaderBlock)
309 : {
310 4 : delete m_poHeaderBlock;
311 4 : m_poHeaderBlock = NULL;
312 : }
313 :
314 4 : if (m_poRecordBlock)
315 : {
316 4 : delete m_poRecordBlock;
317 4 : m_poRecordBlock = NULL;
318 : }
319 :
320 : // Close file
321 4 : VSIFClose(m_fp);
322 4 : m_fp = NULL;
323 :
324 4 : CPLFree(m_pszFname);
325 4 : m_pszFname = NULL;
326 :
327 4 : CPLFree(m_pasFieldDef);
328 4 : m_pasFieldDef = NULL;
329 :
330 4 : m_numFields = -1;
331 4 : m_numRecords = -1;
332 4 : m_nFirstRecordPtr = 0;
333 4 : m_nBlockSize = 0;
334 4 : m_nRecordSize = -1;
335 4 : m_nCurRecordId = -1;
336 4 : m_bWriteHeaderInitialized = FALSE;
337 :
338 4 : return 0;
339 : }
340 :
341 :
342 : /**********************************************************************
343 : * TABDATFile::InitWriteHeader()
344 : *
345 : * Init the header members to be ready to write the header and data records
346 : * to a newly created data file.
347 : *
348 : * Returns 0 on success, -1 on error.
349 : **********************************************************************/
350 2 : int TABDATFile::InitWriteHeader()
351 : {
352 : int i;
353 :
354 2 : if (m_eAccessMode != TABWrite || m_bWriteHeaderInitialized)
355 0 : return 0;
356 :
357 : /*------------------------------------------------------------
358 : * Compute values for Record size, header size, etc.
359 : *-----------------------------------------------------------*/
360 2 : m_nFirstRecordPtr = (m_numFields+1)*32 + 1;
361 :
362 2 : m_nRecordSize = 1;
363 6 : for(i=0; i<m_numFields; i++)
364 : {
365 4 : m_nRecordSize += m_pasFieldDef[i].byLength;
366 : }
367 :
368 : /*-------------------------------------------------------------
369 : * Create m_poRecordBlock the size of a data record.
370 : *------------------------------------------------------------*/
371 2 : m_nBlockSize = m_nRecordSize;
372 :
373 2 : CPLAssert( m_poRecordBlock == NULL );
374 2 : m_poRecordBlock = new TABRawBinBlock(m_eAccessMode, FALSE);
375 2 : m_poRecordBlock->InitNewBlock(m_fp, m_nBlockSize);
376 2 : m_poRecordBlock->SetFirstBlockPtr(m_nFirstRecordPtr);
377 :
378 : /*-------------------------------------------------------------
379 : * Make sure this init. will be performed only once
380 : *------------------------------------------------------------*/
381 2 : m_bWriteHeaderInitialized = TRUE;
382 :
383 2 : return 0;
384 : }
385 :
386 : /**********************************************************************
387 : * TABDATFile::WriteHeader()
388 : *
389 : * Init the header members to be ready to write the header and data records
390 : * to a newly created data file.
391 : *
392 : * Returns 0 on success, -1 on error.
393 : **********************************************************************/
394 4 : int TABDATFile::WriteHeader()
395 : {
396 : int i;
397 :
398 4 : if (m_eAccessMode != TABWrite)
399 : {
400 : CPLError(CE_Failure, CPLE_NotSupported,
401 0 : "WriteHeader() can be used only with Write access.");
402 0 : return -1;
403 : }
404 :
405 4 : if (!m_bWriteHeaderInitialized)
406 2 : InitWriteHeader();
407 :
408 : /*------------------------------------------------------------
409 : * Create a single block that will be used to generate the whole header.
410 : *-----------------------------------------------------------*/
411 4 : if (m_poHeaderBlock == NULL)
412 2 : m_poHeaderBlock = new TABRawBinBlock(m_eAccessMode, TRUE);
413 4 : m_poHeaderBlock->InitNewBlock(m_fp, m_nFirstRecordPtr, 0);
414 :
415 : /*------------------------------------------------------------
416 : * First 32 bytes: main header block
417 : *-----------------------------------------------------------*/
418 4 : m_poHeaderBlock->WriteByte(0x03); // Table type ??? 0x03
419 :
420 : // __TODO__ Write the correct update date value
421 4 : m_poHeaderBlock->WriteByte(99); // Last update year
422 4 : m_poHeaderBlock->WriteByte(9); // Last update month
423 4 : m_poHeaderBlock->WriteByte(9); // Last update day
424 :
425 4 : m_poHeaderBlock->WriteInt32(m_numRecords);
426 4 : m_poHeaderBlock->WriteInt16(m_nFirstRecordPtr);
427 4 : m_poHeaderBlock->WriteInt16(m_nRecordSize);
428 :
429 4 : m_poHeaderBlock->WriteZeros(20); // Pad rest with zeros
430 :
431 : /*-------------------------------------------------------------
432 : * Field definitions follow. Each field def is 32 bytes.
433 : *------------------------------------------------------------*/
434 12 : for(i=0; i<m_numFields; i++)
435 : {
436 8 : m_poHeaderBlock->WriteBytes(11, (GByte*)m_pasFieldDef[i].szName);
437 8 : m_poHeaderBlock->WriteByte(m_pasFieldDef[i].cType);
438 :
439 8 : m_poHeaderBlock->WriteInt32(0); // Skip Bytes 12-15
440 :
441 8 : m_poHeaderBlock->WriteByte(m_pasFieldDef[i].byLength);
442 8 : m_poHeaderBlock->WriteByte(m_pasFieldDef[i].byDecimals);
443 :
444 8 : m_poHeaderBlock->WriteZeros(14); // Pad rest with zeros
445 : }
446 :
447 : /*-------------------------------------------------------------
448 : * Header ends with a 0x0d character.
449 : *------------------------------------------------------------*/
450 4 : m_poHeaderBlock->WriteByte(0x0d);
451 :
452 : /*-------------------------------------------------------------
453 : * Write the block to the file and return.
454 : *------------------------------------------------------------*/
455 4 : return m_poHeaderBlock->CommitToFile();
456 : }
457 :
458 :
459 :
460 : /**********************************************************************
461 : * TABDATFile::GetNumFields()
462 : *
463 : * Return the number of fields in this table.
464 : *
465 : * Returns a value >= 0 on success, -1 on error.
466 : **********************************************************************/
467 72 : int TABDATFile::GetNumFields()
468 : {
469 72 : return m_numFields;
470 : }
471 :
472 : /**********************************************************************
473 : * TABDATFile::GetNumRecords()
474 : *
475 : * Return the number of records in this table.
476 : *
477 : * Returns a value >= 0 on success, -1 on error.
478 : **********************************************************************/
479 4 : int TABDATFile::GetNumRecords()
480 : {
481 4 : return m_numRecords;
482 : }
483 :
484 : /**********************************************************************
485 : * TABDATFile::GetRecordBlock()
486 : *
487 : * Return a TABRawBinBlock reference positioned at the beginning of the
488 : * specified record and ready to read (or write) field values from/to it.
489 : * In read access, the returned block is guaranteed to contain at least one
490 : * full record of data, and in write access, it is at least big enough to
491 : * hold one full record.
492 : *
493 : * Note that record ids are positive and start at 1.
494 : *
495 : * In Write access, CommitRecordToFile() MUST be called after the
496 : * data items have been written to the record, otherwise the record
497 : * will never make it to the file.
498 : *
499 : * Returns a reference to the TABRawBinBlock on success or NULL on error.
500 : * The returned pointer is a reference to a block object owned by this
501 : * TABDATFile object and should not be freed by the caller.
502 : **********************************************************************/
503 109 : TABRawBinBlock *TABDATFile::GetRecordBlock(int nRecordId)
504 : {
505 109 : m_bCurRecordDeletedFlag = FALSE;
506 :
507 109 : if (m_eAccessMode == TABRead)
508 : {
509 : /*-------------------------------------------------------------
510 : * READ ACCESS
511 : *------------------------------------------------------------*/
512 : int nFileOffset;
513 :
514 96 : nFileOffset = m_nFirstRecordPtr+(nRecordId-1)*m_nRecordSize;
515 :
516 : /*-------------------------------------------------------------
517 : * Move record block pointer to the right location
518 : *------------------------------------------------------------*/
519 96 : if ( m_poRecordBlock == NULL ||
520 : nRecordId < 1 || nRecordId > m_numRecords ||
521 : m_poRecordBlock->GotoByteInFile(nFileOffset) != 0 )
522 : {
523 : CPLError(CE_Failure, CPLE_FileIO,
524 : "Failed reading .DAT record block for record #%d in %s",
525 0 : nRecordId, m_pszFname);
526 0 : return NULL;
527 : }
528 :
529 : /*-------------------------------------------------------------
530 : * The first char of the record is a ' ' for an active record, or
531 : * '*' for a deleted one.
532 : * In the case of a deleted record, we simply return default
533 : * values for each attribute... this is what MapInfo seems to do
534 : * when it takes a .TAB with deleted records and exports it to .MIF
535 : *------------------------------------------------------------*/
536 96 : if (m_poRecordBlock->ReadByte() != ' ')
537 : {
538 0 : m_bCurRecordDeletedFlag = TRUE;
539 : }
540 : }
541 13 : else if (m_eAccessMode == TABWrite && nRecordId > 0)
542 : {
543 : /*-------------------------------------------------------------
544 : * WRITE ACCESS
545 : *------------------------------------------------------------*/
546 : int nFileOffset;
547 :
548 : /*-------------------------------------------------------------
549 : * Before writing the first record, we must generate the file
550 : * header. We will also initialize class members such as record
551 : * size, etc. and will create m_poRecordBlock.
552 : *------------------------------------------------------------*/
553 13 : if (!m_bWriteHeaderInitialized)
554 : {
555 2 : WriteHeader();
556 : }
557 :
558 13 : m_numRecords = MAX(nRecordId, m_numRecords);
559 :
560 13 : nFileOffset = m_nFirstRecordPtr+(nRecordId-1)*m_nRecordSize;
561 :
562 13 : m_poRecordBlock->InitNewBlock(m_fp, m_nRecordSize, nFileOffset);
563 :
564 : /*-------------------------------------------------------------
565 : * The first char of the record is the active/deleted flag.
566 : * Automatically set it to ' ' (active).
567 : *------------------------------------------------------------*/
568 13 : m_poRecordBlock->WriteByte(' ');
569 :
570 : }
571 :
572 109 : m_nCurRecordId = nRecordId;
573 :
574 109 : return m_poRecordBlock;
575 : }
576 :
577 : /**********************************************************************
578 : * TABDATFile::CommitRecordToFile()
579 : *
580 : * Commit the data record previously initialized with GetRecordBlock()
581 : * to the file. This function must be called after writing the data
582 : * values to a record otherwise the record will never make it to the
583 : * file.
584 : *
585 : * Returns 0 on success, -1 on error.
586 : **********************************************************************/
587 13 : int TABDATFile::CommitRecordToFile()
588 : {
589 13 : if (m_eAccessMode != TABWrite || m_poRecordBlock == NULL)
590 0 : return -1;
591 :
592 13 : return m_poRecordBlock->CommitToFile();
593 : }
594 :
595 :
596 : /**********************************************************************
597 : * TABDATFile::ValidateFieldInfoFromTAB()
598 : *
599 : * Check that the value read from the .TAB file by the caller are
600 : * consistent with what is found in the .DAT header.
601 : *
602 : * Note that field ids are positive and start at 0.
603 : *
604 : * We have to use this function when opening a file for reading since
605 : * the .DAT file does not contain the full field types information...
606 : * a .DAT file is actually a .DBF file in which the .DBF types are
607 : * handled in a special way... type 'C' fields are used to store binary
608 : * values for most MapInfo types.
609 : *
610 : * For TABTableDBF, we actually have no validation to do since all types
611 : * are stored as strings internally, so we'll just convert from string.
612 : *
613 : * Returns a value >= 0 if OK, -1 on error.
614 : **********************************************************************/
615 : int TABDATFile::ValidateFieldInfoFromTAB(int iField, const char *pszName,
616 : TABFieldType eType,
617 4 : int nWidth, int nPrecision)
618 : {
619 4 : int i = iField; // Just to make things shorter
620 :
621 4 : CPLAssert(m_pasFieldDef);
622 :
623 4 : if (m_pasFieldDef == NULL || iField < 0 || iField >= m_numFields)
624 : {
625 : CPLError(CE_Failure, CPLE_FileIO,
626 : "Invalid field %d (%s) in .TAB header. %s contains only %d fields.",
627 0 : iField+1, pszName, m_pszFname, m_pasFieldDef? m_numFields:0);
628 0 : return -1;
629 : }
630 :
631 : /*-----------------------------------------------------------------
632 : * We used to check that the .TAB field name matched the .DAT
633 : * name stored internally, but apparently some tools that rename table
634 : * field names only update the .TAB file and not the .DAT, so we won't
635 : * do that name validation any more... we'll just check the type.
636 : *
637 : * With TABTableNative, we have to validate the field sizes as well
638 : * because .DAT files use char fields to store binary values.
639 : * With TABTableDBF, no need to validate field type since all
640 : * fields are stored as strings internally.
641 : *----------------------------------------------------------------*/
642 4 : if ((m_eTableType == TABTableNative &&
643 : ((eType == TABFChar && (m_pasFieldDef[i].cType != 'C' ||
644 : m_pasFieldDef[i].byLength != nWidth )) ||
645 : (eType == TABFDecimal && (m_pasFieldDef[i].cType != 'N' ||
646 : m_pasFieldDef[i].byLength != nWidth||
647 : m_pasFieldDef[i].byDecimals!=nPrecision)) ||
648 : (eType == TABFInteger && (m_pasFieldDef[i].cType != 'C' ||
649 : m_pasFieldDef[i].byLength != 4 )) ||
650 : (eType == TABFSmallInt && (m_pasFieldDef[i].cType != 'C' ||
651 : m_pasFieldDef[i].byLength != 2 )) ||
652 : (eType == TABFFloat && (m_pasFieldDef[i].cType != 'C' ||
653 : m_pasFieldDef[i].byLength != 8 )) ||
654 : (eType == TABFDate && (m_pasFieldDef[i].cType != 'C' ||
655 : m_pasFieldDef[i].byLength != 4 )) ||
656 : (eType == TABFTime && (m_pasFieldDef[i].cType != 'C' ||
657 : m_pasFieldDef[i].byLength != 4 )) ||
658 : (eType == TABFDateTime && (m_pasFieldDef[i].cType != 'C' ||
659 : m_pasFieldDef[i].byLength != 8 )) ||
660 : (eType == TABFLogical && (m_pasFieldDef[i].cType != 'L' ||
661 : m_pasFieldDef[i].byLength != 1 )) ) ))
662 : {
663 : CPLError(CE_Failure, CPLE_FileIO,
664 : "Definition of field %d (%s) from .TAB file does not match "
665 : "what is found in %s (name=%s, type=%c, width=%d, prec=%d)",
666 : iField+1, pszName, m_pszFname,
667 : m_pasFieldDef[i].szName, m_pasFieldDef[i].cType,
668 0 : m_pasFieldDef[i].byLength, m_pasFieldDef[i].byDecimals);
669 0 : return -1;
670 : }
671 :
672 4 : m_pasFieldDef[i].eTABType = eType;
673 :
674 4 : return 0;
675 : }
676 :
677 : /**********************************************************************
678 : * TABDATFile::AddField()
679 : *
680 : * Create a new field (column) in a newly created table. This function
681 : * must be called after the file has been opened, but before writing the
682 : * first record.
683 : *
684 : * Returns the new field index (a value >= 0) if OK, -1 on error.
685 : **********************************************************************/
686 : int TABDATFile::AddField(const char *pszName, TABFieldType eType,
687 4 : int nWidth, int nPrecision /*=0*/)
688 : {
689 4 : if (m_eAccessMode != TABWrite || m_bWriteHeaderInitialized ||
690 : m_eTableType != TABTableNative)
691 : {
692 : CPLError(CE_Failure, CPLE_NotSupported,
693 : "Addition of new table fields is not supported after the "
694 0 : "first data item has been written.");
695 0 : return -1;
696 : }
697 :
698 : /*-----------------------------------------------------------------
699 : * Validate field width... must be <= 254
700 : *----------------------------------------------------------------*/
701 4 : if (nWidth > 254)
702 : {
703 : CPLError(CE_Failure, CPLE_IllegalArg,
704 : "Invalid size (%d) for field '%s'. "
705 0 : "Size must be 254 or less.", nWidth, pszName);
706 0 : return -1;
707 : }
708 :
709 : /*-----------------------------------------------------------------
710 : * Map fields with width=0 (variable length in OGR) to a valid default
711 : *----------------------------------------------------------------*/
712 4 : if (eType == TABFDecimal && nWidth == 0)
713 0 : nWidth=20;
714 4 : else if (nWidth == 0)
715 0 : nWidth=254; /* char fields */
716 :
717 4 : if (m_numFields < 0)
718 0 : m_numFields = 0;
719 :
720 4 : m_numFields++;
721 : m_pasFieldDef = (TABDATFieldDef*)CPLRealloc(m_pasFieldDef,
722 4 : m_numFields*sizeof(TABDATFieldDef));
723 :
724 4 : strncpy(m_pasFieldDef[m_numFields-1].szName, pszName, 10);
725 4 : m_pasFieldDef[m_numFields-1].szName[10] = '\0';
726 4 : m_pasFieldDef[m_numFields-1].eTABType = eType;
727 4 : m_pasFieldDef[m_numFields-1].byLength = (GByte)nWidth;
728 4 : m_pasFieldDef[m_numFields-1].byDecimals = (GByte)nPrecision;
729 :
730 4 : switch(eType)
731 : {
732 : case TABFChar:
733 2 : m_pasFieldDef[m_numFields-1].cType = 'C';
734 2 : break;
735 : case TABFDecimal:
736 0 : m_pasFieldDef[m_numFields-1].cType = 'N';
737 0 : break;
738 : case TABFInteger:
739 1 : m_pasFieldDef[m_numFields-1].cType = 'C';
740 1 : m_pasFieldDef[m_numFields-1].byLength = 4;
741 1 : break;
742 : case TABFSmallInt:
743 0 : m_pasFieldDef[m_numFields-1].cType = 'C';
744 0 : m_pasFieldDef[m_numFields-1].byLength = 2;
745 0 : break;
746 : case TABFFloat:
747 1 : m_pasFieldDef[m_numFields-1].cType = 'C';
748 1 : m_pasFieldDef[m_numFields-1].byLength = 8;
749 1 : break;
750 : case TABFDate:
751 0 : m_pasFieldDef[m_numFields-1].cType = 'C';
752 0 : m_pasFieldDef[m_numFields-1].byLength = 4;
753 0 : break;
754 : case TABFTime:
755 0 : m_pasFieldDef[m_numFields-1].cType = 'C';
756 0 : m_pasFieldDef[m_numFields-1].byLength = 4;
757 0 : break;
758 : case TABFDateTime:
759 0 : m_pasFieldDef[m_numFields-1].cType = 'C';
760 0 : m_pasFieldDef[m_numFields-1].byLength = 8;
761 0 : break;
762 : case TABFLogical:
763 0 : m_pasFieldDef[m_numFields-1].cType = 'L';
764 0 : m_pasFieldDef[m_numFields-1].byLength = 1;
765 0 : break;
766 : default:
767 : CPLError(CE_Failure, CPLE_NotSupported,
768 0 : "Unsupported field type for field `%s'", pszName);
769 0 : return -1;
770 : }
771 :
772 4 : return 0;
773 : }
774 :
775 : /**********************************************************************
776 : * TABDATFile::GetFieldType()
777 : *
778 : * Returns the native field type for field # nFieldId as previously set
779 : * by ValidateFieldInfoFromTAB().
780 : *
781 : * Note that field ids are positive and start at 0.
782 : **********************************************************************/
783 190 : TABFieldType TABDATFile::GetFieldType(int nFieldId)
784 : {
785 190 : if (m_pasFieldDef == NULL || nFieldId < 0 || nFieldId >= m_numFields)
786 0 : return TABFUnknown;
787 :
788 190 : return m_pasFieldDef[nFieldId].eTABType;
789 : }
790 :
791 : /**********************************************************************
792 : * TABDATFile::GetFieldWidth()
793 : *
794 : * Returns the width for field # nFieldId as previously read from the
795 : * .DAT header.
796 : *
797 : * Note that field ids are positive and start at 0.
798 : **********************************************************************/
799 166 : int TABDATFile::GetFieldWidth(int nFieldId)
800 : {
801 166 : if (m_pasFieldDef == NULL || nFieldId < 0 || nFieldId >= m_numFields)
802 0 : return 0;
803 :
804 166 : return m_pasFieldDef[nFieldId].byLength;
805 : }
806 :
807 : /**********************************************************************
808 : * TABDATFile::GetFieldPrecision()
809 : *
810 : * Returns the precision for field # nFieldId as previously read from the
811 : * .DAT header.
812 : *
813 : * Note that field ids are positive and start at 0.
814 : **********************************************************************/
815 0 : int TABDATFile::GetFieldPrecision(int nFieldId)
816 : {
817 0 : if (m_pasFieldDef == NULL || nFieldId < 0 || nFieldId >= m_numFields)
818 0 : return 0;
819 :
820 0 : return m_pasFieldDef[nFieldId].byDecimals;
821 : }
822 :
823 : /**********************************************************************
824 : * TABDATFile::ReadCharField()
825 : *
826 : * Read the character field value at the current position in the data
827 : * block.
828 : *
829 : * Use GetRecordBlock() to position the data block to the beginning of
830 : * a record before attempting to read values.
831 : *
832 : * nWidth is the field length, as defined in the .DAT header.
833 : *
834 : * Returns a reference to an internal buffer that will be valid only until
835 : * the next field is read, or "" if the operation failed, in which case
836 : * CPLError() will have been called.
837 : **********************************************************************/
838 53 : const char *TABDATFile::ReadCharField(int nWidth)
839 : {
840 : // If current record has been deleted, then return an acceptable
841 : // default value.
842 53 : if (m_bCurRecordDeletedFlag)
843 0 : return "";
844 :
845 53 : if (m_poRecordBlock == NULL)
846 : {
847 : CPLError(CE_Failure, CPLE_AssertionFailed,
848 0 : "Can't read field value: file is not opened.");
849 0 : return "";
850 : }
851 :
852 53 : if (nWidth < 1 || nWidth > 255)
853 : {
854 : CPLError(CE_Failure, CPLE_AssertionFailed,
855 0 : "Illegal width for a char field: %d", nWidth);
856 0 : return "";
857 : }
858 :
859 53 : if (m_poRecordBlock->ReadBytes(nWidth, (GByte*)m_szBuffer) != 0)
860 0 : return "";
861 :
862 53 : m_szBuffer[nWidth] = '\0';
863 :
864 : // NATIVE tables are padded with '\0' chars, but DBF tables are padded
865 : // with spaces... get rid of the trailing spaces.
866 53 : if (m_eTableType == TABTableDBF)
867 : {
868 0 : int nLen = strlen(m_szBuffer)-1;
869 0 : while(nLen>=0 && m_szBuffer[nLen] == ' ')
870 0 : m_szBuffer[nLen--] = '\0';
871 : }
872 :
873 53 : return m_szBuffer;
874 : }
875 :
876 : /**********************************************************************
877 : * TABDATFile::ReadIntegerField()
878 : *
879 : * Read the integer field value at the current position in the data
880 : * block.
881 : *
882 : * Note: nWidth is used only with TABTableDBF types.
883 : *
884 : * CPLError() will have been called if something fails.
885 : **********************************************************************/
886 50 : GInt32 TABDATFile::ReadIntegerField(int nWidth)
887 : {
888 : // If current record has been deleted, then return an acceptable
889 : // default value.
890 50 : if (m_bCurRecordDeletedFlag)
891 0 : return 0;
892 :
893 50 : if (m_poRecordBlock == NULL)
894 : {
895 : CPLError(CE_Failure, CPLE_AssertionFailed,
896 0 : "Can't read field value: file is not opened.");
897 0 : return 0;
898 : }
899 :
900 50 : if (m_eTableType == TABTableDBF)
901 0 : return atoi(ReadCharField(nWidth));
902 :
903 50 : return m_poRecordBlock->ReadInt32();
904 : }
905 :
906 : /**********************************************************************
907 : * TABDATFile::ReadSmallIntField()
908 : *
909 : * Read the smallint field value at the current position in the data
910 : * block.
911 : *
912 : * Note: nWidth is used only with TABTableDBF types.
913 : *
914 : * CPLError() will have been called if something fails.
915 : **********************************************************************/
916 0 : GInt16 TABDATFile::ReadSmallIntField(int nWidth)
917 : {
918 : // If current record has been deleted, then return an acceptable
919 : // default value.
920 0 : if (m_bCurRecordDeletedFlag)
921 0 : return 0;
922 :
923 0 : if (m_poRecordBlock == NULL)
924 : {
925 : CPLError(CE_Failure, CPLE_AssertionFailed,
926 0 : "Can't read field value: file is not opened.");
927 0 : return 0;
928 : }
929 :
930 0 : if (m_eTableType == TABTableDBF)
931 0 : return atoi(ReadCharField(nWidth));
932 :
933 0 : return m_poRecordBlock->ReadInt16();
934 : }
935 :
936 : /**********************************************************************
937 : * TABDATFile::ReadFloatField()
938 : *
939 : * Read the float field value at the current position in the data
940 : * block.
941 : *
942 : * Note: nWidth is used only with TABTableDBF types.
943 : *
944 : * CPLError() will have been called if something fails.
945 : **********************************************************************/
946 50 : double TABDATFile::ReadFloatField(int nWidth)
947 : {
948 : // If current record has been deleted, then return an acceptable
949 : // default value.
950 50 : if (m_bCurRecordDeletedFlag)
951 0 : return 0.0;
952 :
953 50 : if (m_poRecordBlock == NULL)
954 : {
955 : CPLError(CE_Failure, CPLE_AssertionFailed,
956 0 : "Can't read field value: file is not opened.");
957 0 : return 0.0;
958 : }
959 :
960 50 : if (m_eTableType == TABTableDBF)
961 0 : return atof(ReadCharField(nWidth));
962 :
963 50 : return m_poRecordBlock->ReadDouble();
964 : }
965 :
966 : /**********************************************************************
967 : * TABDATFile::ReadLogicalField()
968 : *
969 : * Read the logical field value at the current position in the data
970 : * block.
971 : *
972 : * The file contains either 0 or 1, and we return a string with
973 : * "F" (false) or "T" (true)
974 : *
975 : * Note: nWidth is used only with TABTableDBF types.
976 : *
977 : * CPLError() will have been called if something fails.
978 : **********************************************************************/
979 0 : const char *TABDATFile::ReadLogicalField(int nWidth)
980 : {
981 : GByte bValue;
982 :
983 : // If current record has been deleted, then return an acceptable
984 : // default value.
985 0 : if (m_bCurRecordDeletedFlag)
986 0 : return "F";
987 :
988 0 : if (m_poRecordBlock == NULL)
989 : {
990 : CPLError(CE_Failure, CPLE_AssertionFailed,
991 0 : "Can't read field value: file is not opened.");
992 0 : return "";
993 : }
994 :
995 0 : if (m_eTableType == TABTableDBF)
996 : {
997 0 : const char *pszVal = ReadCharField(nWidth);
998 0 : bValue = (pszVal && strchr("1YyTt", pszVal[0]) != NULL);
999 : }
1000 : else
1001 : {
1002 : // In Native tables, we are guaranteed it is 1 byte with 0/1 value
1003 0 : bValue = m_poRecordBlock->ReadByte();
1004 : }
1005 :
1006 0 : return bValue? "T":"F";
1007 : }
1008 :
1009 : /**********************************************************************
1010 : * TABDATFile::ReadDateField()
1011 : *
1012 : * Read the logical field value at the current position in the data
1013 : * block.
1014 : *
1015 : * A date field is a 4 bytes binary value in which the first byte is
1016 : * the day, followed by 1 byte for the month, and 2 bytes for the year.
1017 : *
1018 : * We return an 8 chars string in the format "YYYYMMDD"
1019 : *
1020 : * Note: nWidth is used only with TABTableDBF types.
1021 : *
1022 : * Returns a reference to an internal buffer that will be valid only until
1023 : * the next field is read, or "" if the operation failed, in which case
1024 : * CPLError() will have been called.
1025 : **********************************************************************/
1026 0 : const char *TABDATFile::ReadDateField(int nWidth)
1027 : {
1028 : int nDay, nMonth, nYear;
1029 :
1030 : // If current record has been deleted, then return an acceptable
1031 : // default value.
1032 0 : if (m_bCurRecordDeletedFlag)
1033 0 : return "";
1034 :
1035 0 : if (m_poRecordBlock == NULL)
1036 : {
1037 : CPLError(CE_Failure, CPLE_AssertionFailed,
1038 0 : "Can't read field value: file is not opened.");
1039 0 : return "";
1040 : }
1041 :
1042 : // With .DBF files, there is nothing to do... the value should already
1043 : // be stored in YYYYMMDD format according to DBF specs.
1044 0 : if (m_eTableType == TABTableDBF)
1045 0 : return ReadCharField(nWidth);
1046 :
1047 :
1048 0 : nYear = m_poRecordBlock->ReadInt16();
1049 0 : nMonth = m_poRecordBlock->ReadByte();
1050 0 : nDay = m_poRecordBlock->ReadByte();
1051 :
1052 0 : if (CPLGetLastErrorNo() != 0 || (nYear==0 && nMonth==0 && nDay==0))
1053 0 : return "";
1054 :
1055 0 : sprintf(m_szBuffer, "%4.4d%2.2d%2.2d", nYear, nMonth, nDay);
1056 :
1057 0 : return m_szBuffer;
1058 : }
1059 :
1060 : /**********************************************************************
1061 : * TABDATFile::ReadTimeField()
1062 : *
1063 : * Read the Time field value at the current position in the data
1064 : * block.
1065 : *
1066 : * A time field is a 4 bytes binary value which represents the number
1067 : * of milliseconds since midnight.
1068 : *
1069 : * We return a 9 char string in the format "HHMMSSMMM"
1070 : *
1071 : * Note: nWidth is used only with TABTableDBF types.
1072 : *
1073 : * Returns a reference to an internal buffer that will be valid only until
1074 : * the next field is read, or "" if the operation failed, in which case
1075 : * CPLError() will have been called.
1076 : **********************************************************************/
1077 0 : const char *TABDATFile::ReadTimeField(int nWidth)
1078 : {
1079 : GInt32 nS;
1080 : static char szBuf[20];
1081 :
1082 : // If current record has been deleted, then return an acceptable
1083 : // default value.
1084 0 : if (m_bCurRecordDeletedFlag)
1085 0 : return "";
1086 :
1087 0 : if (m_poRecordBlock == NULL)
1088 : {
1089 : CPLError(CE_Failure, CPLE_AssertionFailed,
1090 0 : "Can't read field value: file is not opened.");
1091 0 : return "";
1092 : }
1093 :
1094 : // With .DBF files, there is nothing to do... the value should already
1095 : // be stored in HHMMSSMMM format according to DBF specs.
1096 0 : if (m_eTableType == TABTableDBF)
1097 0 : return ReadCharField(nWidth);
1098 :
1099 :
1100 0 : nS = m_poRecordBlock->ReadInt32(); // Convert time from ms to sec
1101 :
1102 : // nS is set to -1 when the value is 'not set'
1103 0 : if (CPLGetLastErrorNo() != 0 || nS < 0 || (nS>86400000))
1104 0 : return "";
1105 :
1106 0 : int nHour = int(nS/3600000);
1107 0 : int nMin = int((nS/1000 - nHour*3600)/60);
1108 0 : int nSec = int(nS/1000 - nHour*3600 - nMin*60);
1109 0 : int nMS = int(nS-nHour*3600000-nMin*60000-nSec*1000);
1110 0 : sprintf(szBuf, "%2.2d%2.2d%2.2d%3.3d", nHour, nMin, nSec, nMS);
1111 :
1112 0 : return szBuf;
1113 : }
1114 :
1115 : /**********************************************************************
1116 : * TABDATFile::ReadDateTimeField()
1117 : *
1118 : * Read the DateTime field value at the current position in the data
1119 : * block.
1120 : *
1121 : * A datetime field is an 8 bytes binary value in which the first byte is
1122 : * the day, followed by 1 byte for the month, and 2 bytes for the year. After
1123 : * this is 4 bytes which represents the number of milliseconds since midnight.
1124 : *
1125 : * We return an 17 chars string in the format "YYYYMMDDhhmmssmmm"
1126 : *
1127 : * Note: nWidth is used only with TABTableDBF types.
1128 : *
1129 : * Returns a reference to an internal buffer that will be valid only until
1130 : * the next field is read, or "" if the operation failed, in which case
1131 : * CPLError() will have been called.
1132 : **********************************************************************/
1133 0 : const char *TABDATFile::ReadDateTimeField(int nWidth)
1134 : {
1135 : int nDay, nMonth, nYear;
1136 : GInt32 nS;
1137 : static char szBuf[20];
1138 :
1139 : // If current record has been deleted, then return an acceptable
1140 : // default value.
1141 0 : if (m_bCurRecordDeletedFlag)
1142 0 : return "";
1143 :
1144 0 : if (m_poRecordBlock == NULL)
1145 : {
1146 : CPLError(CE_Failure, CPLE_AssertionFailed,
1147 0 : "Can't read field value: file is not opened.");
1148 0 : return "";
1149 : }
1150 :
1151 : // With .DBF files, there is nothing to do... the value should already
1152 : // be stored in YYYYMMDD format according to DBF specs.
1153 0 : if (m_eTableType == TABTableDBF)
1154 0 : return ReadCharField(nWidth);
1155 :
1156 :
1157 0 : nYear = m_poRecordBlock->ReadInt16();
1158 0 : nMonth = m_poRecordBlock->ReadByte();
1159 0 : nDay = m_poRecordBlock->ReadByte();
1160 0 : nS = m_poRecordBlock->ReadInt32();
1161 :
1162 0 : if (CPLGetLastErrorNo() != 0 ||
1163 : (nYear==0 && nMonth==0 && nDay==0) || (nS>86400000))
1164 0 : return "";
1165 :
1166 0 : int nHour = int(nS/3600000);
1167 0 : int nMin = int((nS/1000 - nHour*3600)/60);
1168 0 : int nSec = int(nS/1000 - nHour*3600 - nMin*60);
1169 0 : int nMS = int(nS-nHour*3600000-nMin*60000-nSec*1000);
1170 :
1171 : sprintf(szBuf, "%4.4d%2.2d%2.2d%2.2d%2.2d%2.2d%3.3d",
1172 0 : nYear, nMonth, nDay, nHour, nMin, nSec, nMS);
1173 :
1174 0 : return szBuf;
1175 : }
1176 :
1177 : /**********************************************************************
1178 : * TABDATFile::ReadDecimalField()
1179 : *
1180 : * Read the decimal field value at the current position in the data
1181 : * block.
1182 : *
1183 : * A decimal field is a floating point value with a fixed number of digits
1184 : * stored as a character string.
1185 : *
1186 : * nWidth is the field length, as defined in the .DAT header.
1187 : *
1188 : * We return the value as a binary double.
1189 : *
1190 : * CPLError() will have been called if something fails.
1191 : **********************************************************************/
1192 0 : double TABDATFile::ReadDecimalField(int nWidth)
1193 : {
1194 : const char *pszVal;
1195 :
1196 : // If current record has been deleted, then return an acceptable
1197 : // default value.
1198 0 : if (m_bCurRecordDeletedFlag)
1199 0 : return 0.0;
1200 :
1201 0 : pszVal = ReadCharField(nWidth);
1202 :
1203 0 : return atof(pszVal);
1204 : }
1205 :
1206 :
1207 : /**********************************************************************
1208 : * TABDATFile::WriteCharField()
1209 : *
1210 : * Write the character field value at the current position in the data
1211 : * block.
1212 : *
1213 : * Use GetRecordBlock() to position the data block to the beginning of
1214 : * a record before attempting to write values.
1215 : *
1216 : * nWidth is the field length, as defined in the .DAT header.
1217 : *
1218 : * Returns 0 on success, or -1 if the operation failed, in which case
1219 : * CPLError() will have been called.
1220 : **********************************************************************/
1221 : int TABDATFile::WriteCharField(const char *pszStr, int nWidth,
1222 13 : TABINDFile *poINDFile, int nIndexNo)
1223 : {
1224 13 : if (m_poRecordBlock == NULL)
1225 : {
1226 : CPLError(CE_Failure, CPLE_AssertionFailed,
1227 0 : "Can't write field value: GetRecordBlock() has not been called.");
1228 0 : return -1;
1229 : }
1230 :
1231 13 : if (nWidth < 1 || nWidth > 255)
1232 : {
1233 : CPLError(CE_Failure, CPLE_AssertionFailed,
1234 0 : "Illegal width for a char field: %d", nWidth);
1235 0 : return -1;
1236 : }
1237 :
1238 : //
1239 : // Write the buffer after making sure that we don't try to read
1240 : // past the end of the source buffer. The rest of the field will
1241 : // be padded with zeros if source string is shorter than specified
1242 : // field width.
1243 : //
1244 13 : int nLen = strlen(pszStr);
1245 13 : nLen = MIN(nLen, nWidth);
1246 :
1247 13 : if ((nLen>0 && m_poRecordBlock->WriteBytes(nLen, (GByte*)pszStr) != 0) ||
1248 : (nWidth-nLen > 0 && m_poRecordBlock->WriteZeros(nWidth-nLen)!=0) )
1249 0 : return -1;
1250 :
1251 : // Update Index
1252 13 : if (poINDFile && nIndexNo > 0)
1253 : {
1254 0 : GByte *pKey = poINDFile->BuildKey(nIndexNo, pszStr);
1255 0 : if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0)
1256 0 : return -1;
1257 : }
1258 :
1259 13 : return 0;
1260 : }
1261 :
1262 : /**********************************************************************
1263 : * TABDATFile::WriteIntegerField()
1264 : *
1265 : * Write the integer field value at the current position in the data
1266 : * block.
1267 : *
1268 : * CPLError() will have been called if something fails.
1269 : **********************************************************************/
1270 : int TABDATFile::WriteIntegerField(GInt32 nValue,
1271 10 : TABINDFile *poINDFile, int nIndexNo)
1272 : {
1273 10 : if (m_poRecordBlock == NULL)
1274 : {
1275 : CPLError(CE_Failure, CPLE_AssertionFailed,
1276 0 : "Can't write field value: GetRecordBlock() has not been called.");
1277 0 : return -1;
1278 : }
1279 :
1280 : // Update Index
1281 10 : if (poINDFile && nIndexNo > 0)
1282 : {
1283 0 : GByte *pKey = poINDFile->BuildKey(nIndexNo, nValue);
1284 0 : if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0)
1285 0 : return -1;
1286 : }
1287 :
1288 10 : return m_poRecordBlock->WriteInt32(nValue);
1289 : }
1290 :
1291 : /**********************************************************************
1292 : * TABDATFile::WriteSmallIntField()
1293 : *
1294 : * Write the smallint field value at the current position in the data
1295 : * block.
1296 : *
1297 : * CPLError() will have been called if something fails.
1298 : **********************************************************************/
1299 : int TABDATFile::WriteSmallIntField(GInt16 nValue,
1300 0 : TABINDFile *poINDFile, int nIndexNo)
1301 : {
1302 0 : if (m_poRecordBlock == NULL)
1303 : {
1304 : CPLError(CE_Failure, CPLE_AssertionFailed,
1305 0 : "Can't write field value: GetRecordBlock() has not been called.");
1306 0 : return -1;
1307 : }
1308 :
1309 : // Update Index
1310 0 : if (poINDFile && nIndexNo > 0)
1311 : {
1312 0 : GByte *pKey = poINDFile->BuildKey(nIndexNo, nValue);
1313 0 : if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0)
1314 0 : return -1;
1315 : }
1316 :
1317 0 : return m_poRecordBlock->WriteInt16(nValue);
1318 : }
1319 :
1320 : /**********************************************************************
1321 : * TABDATFile::WriteFloatField()
1322 : *
1323 : * Write the float field value at the current position in the data
1324 : * block.
1325 : *
1326 : * CPLError() will have been called if something fails.
1327 : **********************************************************************/
1328 : int TABDATFile::WriteFloatField(double dValue,
1329 10 : TABINDFile *poINDFile, int nIndexNo)
1330 : {
1331 10 : if (m_poRecordBlock == NULL)
1332 : {
1333 : CPLError(CE_Failure, CPLE_AssertionFailed,
1334 0 : "Can't write field value: GetRecordBlock() has not been called.");
1335 0 : return -1;
1336 : }
1337 :
1338 : // Update Index
1339 10 : if (poINDFile && nIndexNo > 0)
1340 : {
1341 0 : GByte *pKey = poINDFile->BuildKey(nIndexNo, dValue);
1342 0 : if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0)
1343 0 : return -1;
1344 : }
1345 :
1346 10 : return m_poRecordBlock->WriteDouble(dValue);
1347 : }
1348 :
1349 : /**********************************************************************
1350 : * TABDATFile::WriteLogicalField()
1351 : *
1352 : * Write the logical field value at the current position in the data
1353 : * block.
1354 : *
1355 : * The value written to the file is either 0 or 1, but this function
1356 : * takes as input a string with "F" (false) or "T" (true)
1357 : *
1358 : * CPLError() will have been called if something fails.
1359 : **********************************************************************/
1360 : int TABDATFile::WriteLogicalField(const char *pszValue,
1361 0 : TABINDFile *poINDFile, int nIndexNo)
1362 : {
1363 : GByte bValue;
1364 :
1365 0 : if (m_poRecordBlock == NULL)
1366 : {
1367 : CPLError(CE_Failure, CPLE_AssertionFailed,
1368 0 : "Can't write field value: GetRecordBlock() has not been called.");
1369 0 : return -1;
1370 : }
1371 :
1372 0 : if (EQUALN(pszValue, "T", 1))
1373 0 : bValue = 1;
1374 : else
1375 0 : bValue = 0;
1376 :
1377 : // Update Index
1378 0 : if (poINDFile && nIndexNo > 0)
1379 : {
1380 0 : GByte *pKey = poINDFile->BuildKey(nIndexNo, (int)bValue);
1381 0 : if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0)
1382 0 : return -1;
1383 : }
1384 :
1385 0 : return m_poRecordBlock->WriteByte(bValue);
1386 : }
1387 :
1388 : /**********************************************************************
1389 : * TABDATFile::WriteDateField()
1390 : *
1391 : * Write the date field value at the current position in the data
1392 : * block.
1393 : *
1394 : * A date field is a 4 bytes binary value in which the first byte is
1395 : * the day, followed by 1 byte for the month, and 2 bytes for the year.
1396 : *
1397 : * The expected input is a 10 chars string in the format "YYYY/MM/DD"
1398 : * or "DD/MM/YYYY" or "YYYYMMDD"
1399 : *
1400 : * Returns 0 on success, or -1 if the operation failed, in which case
1401 : * CPLError() will have been called.
1402 : **********************************************************************/
1403 : int TABDATFile::WriteDateField(const char *pszValue,
1404 0 : TABINDFile *poINDFile, int nIndexNo)
1405 : {
1406 : int nDay, nMonth, nYear;
1407 0 : char **papszTok = NULL;
1408 :
1409 0 : if (m_poRecordBlock == NULL)
1410 : {
1411 : CPLError(CE_Failure, CPLE_AssertionFailed,
1412 0 : "Can't write field value: GetRecordBlock() has not been called.");
1413 0 : return -1;
1414 : }
1415 :
1416 : /*-----------------------------------------------------------------
1417 : * Get rid of leading spaces.
1418 : *----------------------------------------------------------------*/
1419 0 : while ( *pszValue == ' ' ) { pszValue++; }
1420 :
1421 : /*-----------------------------------------------------------------
1422 : * Try to automagically detect date format, one of:
1423 : * "YYYY/MM/DD", "DD/MM/YYYY", or "YYYYMMDD"
1424 : *----------------------------------------------------------------*/
1425 :
1426 0 : if (strlen(pszValue) == 8)
1427 : {
1428 : /*-------------------------------------------------------------
1429 : * "YYYYMMDD"
1430 : *------------------------------------------------------------*/
1431 : char szBuf[9];
1432 0 : strcpy(szBuf, pszValue);
1433 0 : nDay = atoi(szBuf+6);
1434 0 : szBuf[6] = '\0';
1435 0 : nMonth = atoi(szBuf+4);
1436 0 : szBuf[4] = '\0';
1437 0 : nYear = atoi(szBuf);
1438 : }
1439 0 : else if (strlen(pszValue) == 10 &&
1440 : (papszTok = CSLTokenizeStringComplex(pszValue, "/",
1441 : FALSE, FALSE)) != NULL &&
1442 : CSLCount(papszTok) == 3 &&
1443 : (strlen(papszTok[0]) == 4 || strlen(papszTok[2]) == 4) )
1444 : {
1445 : /*-------------------------------------------------------------
1446 : * Either "YYYY/MM/DD" or "DD/MM/YYYY"
1447 : *------------------------------------------------------------*/
1448 0 : if (strlen(papszTok[0]) == 4)
1449 : {
1450 0 : nYear = atoi(papszTok[0]);
1451 0 : nMonth = atoi(papszTok[1]);
1452 0 : nDay = atoi(papszTok[2]);
1453 : }
1454 : else
1455 : {
1456 0 : nYear = atoi(papszTok[2]);
1457 0 : nMonth = atoi(papszTok[1]);
1458 0 : nDay = atoi(papszTok[0]);
1459 : }
1460 : }
1461 0 : else if (strlen(pszValue) == 0)
1462 : {
1463 0 : nYear = nMonth = nDay = 0;
1464 : }
1465 : else
1466 : {
1467 : CPLError(CE_Failure, CPLE_AppDefined,
1468 : "Invalid date field value `%s'. Date field values must "
1469 : "be in the format `YYYY/MM/DD', `MM/DD/YYYY' or `YYYYMMDD'",
1470 0 : pszValue);
1471 0 : CSLDestroy(papszTok);
1472 0 : return -1;
1473 : }
1474 0 : CSLDestroy(papszTok);
1475 :
1476 0 : m_poRecordBlock->WriteInt16(nYear);
1477 0 : m_poRecordBlock->WriteByte(nMonth);
1478 0 : m_poRecordBlock->WriteByte(nDay);
1479 :
1480 0 : if (CPLGetLastErrorNo() != 0)
1481 0 : return -1;
1482 :
1483 : // Update Index
1484 0 : if (poINDFile && nIndexNo > 0)
1485 : {
1486 : GByte *pKey = poINDFile->BuildKey(nIndexNo, (nYear*0x10000 +
1487 0 : nMonth * 0x100 + nDay));
1488 0 : if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0)
1489 0 : return -1;
1490 : }
1491 :
1492 0 : return 0;
1493 : }
1494 :
1495 : /**********************************************************************
1496 : * TABDATFile::WriteTimeField()
1497 : *
1498 : * Write the date field value at the current position in the data
1499 : * block.
1500 : *
1501 : * A time field is a 4 byte binary value which represents the number
1502 : * of milliseconds since midnight.
1503 : *
1504 : * The expected input is a 10 chars string in the format "HH:MM:SS"
1505 : * or "HHMMSSmmm"
1506 : *
1507 : * Returns 0 on success, or -1 if the operation failed, in which case
1508 : * CPLError() will have been called.
1509 : **********************************************************************/
1510 : int TABDATFile::WriteTimeField(const char *pszValue,
1511 0 : TABINDFile *poINDFile, int nIndexNo)
1512 : {
1513 : int nHour, nMin, nSec, nMS;
1514 0 : GInt32 nS = -1;
1515 0 : char **papszTok = NULL;
1516 :
1517 0 : if (m_poRecordBlock == NULL)
1518 : {
1519 : CPLError(CE_Failure, CPLE_AssertionFailed,
1520 0 : "Can't write field value: GetRecordBlock() has not been called.");
1521 0 : return -1;
1522 : }
1523 :
1524 : /*-----------------------------------------------------------------
1525 : * Get rid of leading spaces.
1526 : *----------------------------------------------------------------*/
1527 0 : while ( *pszValue == ' ' ) { pszValue++; }
1528 :
1529 : /*-----------------------------------------------------------------
1530 : * Try to automagically detect time format, one of:
1531 : * "HH:MM:SS", or "HHMMSSmmm"
1532 : *----------------------------------------------------------------*/
1533 :
1534 0 : if (strlen(pszValue) == 8)
1535 : {
1536 : /*-------------------------------------------------------------
1537 : * "HH:MM:SS"
1538 : *------------------------------------------------------------*/
1539 : char szBuf[9];
1540 0 : strcpy(szBuf, pszValue);
1541 0 : szBuf[2]=0;
1542 0 : szBuf[5]=0;
1543 0 : nHour = atoi(szBuf);
1544 0 : nMin = atoi(szBuf+3);
1545 0 : nSec = atoi(szBuf+6);
1546 0 : nMS = 0;
1547 :
1548 0 : nS = (nHour*3600+nMin*60+nSec)*1000+nMS;
1549 : }
1550 0 : else if (strlen(pszValue) == 9)
1551 : {
1552 : /*-------------------------------------------------------------
1553 : * "HHMMSSmmm"
1554 : *------------------------------------------------------------*/
1555 : char szBuf[4];
1556 0 : strncpy(szBuf,pszValue,2);
1557 0 : szBuf[2]=0;
1558 0 : nHour = atoi(szBuf);
1559 :
1560 0 : strncpy(szBuf,pszValue+2,2);
1561 0 : szBuf[2]=0;
1562 0 : nMin = atoi(szBuf);
1563 :
1564 0 : strncpy(szBuf,pszValue+4,2);
1565 0 : szBuf[2]=0;
1566 0 : nSec = atoi(szBuf);
1567 :
1568 0 : strncpy(szBuf,pszValue+6,3);
1569 0 : szBuf[3]=0;
1570 0 : nMS = atoi(szBuf);
1571 :
1572 0 : nS = (nHour*3600+nMin*60+nSec)*1000+nMS;
1573 : }
1574 0 : else if (strlen(pszValue) == 0)
1575 : {
1576 0 : nS = -1; // Write -1 to .DAT file if value is not set
1577 : }
1578 : else
1579 : {
1580 : CPLError(CE_Failure, CPLE_AppDefined,
1581 : "Invalid time field value `%s'. Time field values must "
1582 : "be in the format `HH:MM:SS', or `HHMMSSmmm'",
1583 0 : pszValue);
1584 0 : CSLDestroy(papszTok);
1585 0 : return -1;
1586 : }
1587 0 : CSLDestroy(papszTok);
1588 :
1589 0 : m_poRecordBlock->WriteInt32(nS);
1590 :
1591 0 : if (CPLGetLastErrorNo() != 0)
1592 0 : return -1;
1593 :
1594 : // Update Index
1595 0 : if (poINDFile && nIndexNo > 0)
1596 : {
1597 0 : GByte *pKey = poINDFile->BuildKey(nIndexNo, (nS));
1598 0 : if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0)
1599 0 : return -1;
1600 : }
1601 :
1602 0 : return 0;
1603 : }
1604 :
1605 : /**********************************************************************
1606 : * TABDATFile::WriteDateTimeField()
1607 : *
1608 : * Write the DateTime field value at the current position in the data
1609 : * block.
1610 : *
1611 : * A datetime field is a 8 bytes binary value in which the first byte is
1612 : * the day, followed by 1 byte for the month, and 2 bytes for the year.
1613 : * After this the time value is stored as a 4 byte integer
1614 : * (milliseconds since midnight)
1615 : *
1616 : * The expected input is a 10 chars string in the format "YYYY/MM/DD HH:MM:SS"
1617 : * or "DD/MM/YYYY HH:MM:SS" or "YYYYMMDDhhmmssmmm"
1618 : *
1619 : * Returns 0 on success, or -1 if the operation failed, in which case
1620 : * CPLError() will have been called.
1621 : **********************************************************************/
1622 : int TABDATFile::WriteDateTimeField(const char *pszValue,
1623 0 : TABINDFile *poINDFile, int nIndexNo)
1624 : {
1625 : int nDay, nMonth, nYear, nHour, nMin, nSec, nMS;
1626 0 : char **papszTok = NULL;
1627 :
1628 0 : if (m_poRecordBlock == NULL)
1629 : {
1630 : CPLError(CE_Failure, CPLE_AssertionFailed,
1631 0 : "Can't write field value: GetRecordBlock() has not been called.");
1632 0 : return -1;
1633 : }
1634 :
1635 : /*-----------------------------------------------------------------
1636 : * Get rid of leading spaces.
1637 : *----------------------------------------------------------------*/
1638 0 : while ( *pszValue == ' ' ) { pszValue++; }
1639 :
1640 : /*-----------------------------------------------------------------
1641 : * Try to automagically detect date format, one of:
1642 : * "YYYY/MM/DD HH:MM:SS", "DD/MM/YYYY HH:MM:SS", or "YYYYMMDDhhmmssmmm"
1643 : *----------------------------------------------------------------*/
1644 :
1645 0 : if (strlen(pszValue) == 17)
1646 : {
1647 : /*-------------------------------------------------------------
1648 : * "YYYYMMDDhhmmssmmm"
1649 : *------------------------------------------------------------*/
1650 : char szBuf[18];
1651 0 : strcpy(szBuf, pszValue);
1652 0 : nMS = atoi(szBuf+14);
1653 0 : szBuf[14]=0;
1654 0 : nSec = atoi(szBuf+12);
1655 0 : szBuf[12]=0;
1656 0 : nMin = atoi(szBuf+10);
1657 0 : szBuf[10]=0;
1658 0 : nHour = atoi(szBuf+8);
1659 0 : szBuf[8]=0;
1660 0 : nDay = atoi(szBuf+6);
1661 0 : szBuf[6] = 0;
1662 0 : nMonth = atoi(szBuf+4);
1663 0 : szBuf[4] = 0;
1664 0 : nYear = atoi(szBuf);
1665 : }
1666 0 : else if (strlen(pszValue) == 19 &&
1667 : (papszTok = CSLTokenizeStringComplex(pszValue, "/ :",
1668 : FALSE, FALSE)) != NULL &&
1669 : CSLCount(papszTok) == 6 &&
1670 : (strlen(papszTok[0]) == 4 || strlen(papszTok[2]) == 4) )
1671 : {
1672 : /*-------------------------------------------------------------
1673 : * Either "YYYY/MM/DD HH:MM:SS" or "DD/MM/YYYY HH:MM:SS"
1674 : *------------------------------------------------------------*/
1675 0 : if (strlen(papszTok[0]) == 4)
1676 : {
1677 0 : nYear = atoi(papszTok[0]);
1678 0 : nMonth= atoi(papszTok[1]);
1679 0 : nDay = atoi(papszTok[2]);
1680 0 : nHour = atoi(papszTok[3]);
1681 0 : nMin = atoi(papszTok[4]);
1682 0 : nSec = atoi(papszTok[5]);
1683 0 : nMS = 0;
1684 : }
1685 : else
1686 : {
1687 0 : nYear = atoi(papszTok[2]);
1688 0 : nMonth= atoi(papszTok[1]);
1689 0 : nDay = atoi(papszTok[0]);
1690 0 : nHour = atoi(papszTok[3]);
1691 0 : nMin = atoi(papszTok[4]);
1692 0 : nSec = atoi(papszTok[5]);
1693 0 : nMS = 0;
1694 : }
1695 : }
1696 0 : else if (strlen(pszValue) == 0)
1697 : {
1698 0 : nYear = nMonth = nDay = 0;
1699 0 : nHour = nMin = nSec = nMS = 0;
1700 : }
1701 : else
1702 : {
1703 : CPLError(CE_Failure, CPLE_AppDefined,
1704 : "Invalid date field value `%s'. Date field values must "
1705 : "be in the format `YYYY/MM/DD HH:MM:SS', "
1706 : "`MM/DD/YYYY HH:MM:SS' or `YYYYMMDDhhmmssmmm'",
1707 0 : pszValue);
1708 0 : CSLDestroy(papszTok);
1709 0 : return -1;
1710 : }
1711 0 : CSLDestroy(papszTok);
1712 :
1713 0 : GInt32 nS = (nHour*3600+nMin*60+nSec)*1000+nMS;
1714 :
1715 0 : m_poRecordBlock->WriteInt16(nYear);
1716 0 : m_poRecordBlock->WriteByte(nMonth);
1717 0 : m_poRecordBlock->WriteByte(nDay);
1718 0 : m_poRecordBlock->WriteInt32(nS);
1719 :
1720 0 : if (CPLGetLastErrorNo() != 0)
1721 0 : return -1;
1722 :
1723 : // Update Index
1724 0 : if (poINDFile && nIndexNo > 0)
1725 : {
1726 : // __TODO__ (see bug #1844)
1727 : // Indexing on DateTime Fields not currently supported, that will
1728 : // require passing the 8 bytes datetime value to BuildKey() here...
1729 0 : CPLAssert(FALSE);
1730 : GByte *pKey = poINDFile->BuildKey(nIndexNo, (nYear*0x10000 +
1731 0 : nMonth * 0x100 + nDay));
1732 0 : if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0)
1733 0 : return -1;
1734 : }
1735 :
1736 0 : return 0;
1737 : }
1738 :
1739 : /**********************************************************************
1740 : * TABDATFile::WriteDecimalField()
1741 : *
1742 : * Write the decimal field value at the current position in the data
1743 : * block.
1744 : *
1745 : * A decimal field is a floating point value with a fixed number of digits
1746 : * stored as a character string.
1747 : *
1748 : * nWidth is the field length, as defined in the .DAT header.
1749 : *
1750 : * CPLError() will have been called if something fails.
1751 : **********************************************************************/
1752 : int TABDATFile::WriteDecimalField(double dValue, int nWidth, int nPrec,
1753 0 : TABINDFile *poINDFile, int nIndexNo)
1754 : {
1755 : const char *pszVal;
1756 :
1757 0 : pszVal = CPLSPrintf("%*.*f", nWidth, nPrec, dValue);
1758 0 : if ((int)strlen(pszVal) > nWidth)
1759 0 : pszVal += strlen(pszVal) - nWidth;
1760 :
1761 : // Update Index
1762 0 : if (poINDFile && nIndexNo > 0)
1763 : {
1764 0 : GByte *pKey = poINDFile->BuildKey(nIndexNo, dValue);
1765 0 : if (poINDFile->AddEntry(nIndexNo, pKey, m_nCurRecordId) != 0)
1766 0 : return -1;
1767 : }
1768 :
1769 0 : return m_poRecordBlock->WriteBytes(nWidth, (GByte*)pszVal);
1770 : }
1771 :
1772 :
1773 :
1774 : /**********************************************************************
1775 : * TABDATFile::Dump()
1776 : *
1777 : * Dump block contents... available only in DEBUG mode.
1778 : **********************************************************************/
1779 : #ifdef DEBUG
1780 :
1781 0 : void TABDATFile::Dump(FILE *fpOut /*=NULL*/)
1782 : {
1783 0 : if (fpOut == NULL)
1784 0 : fpOut = stdout;
1785 :
1786 0 : fprintf(fpOut, "----- TABDATFile::Dump() -----\n");
1787 :
1788 0 : if (m_fp == NULL)
1789 : {
1790 0 : fprintf(fpOut, "File is not opened.\n");
1791 : }
1792 : else
1793 : {
1794 0 : fprintf(fpOut, "File is opened: %s\n", m_pszFname);
1795 0 : fprintf(fpOut, "m_numFields = %d\n", m_numFields);
1796 0 : fprintf(fpOut, "m_numRecords = %d\n", m_numRecords);
1797 : }
1798 :
1799 0 : fflush(fpOut);
1800 0 : }
1801 :
1802 : #endif // DEBUG
1803 :
1804 :
1805 :
1806 :
1807 :
|