GRASS 8 Programmer's Manual 8.6.0dev(2026)-f6f2c534ea
Loading...
Searching...
No Matches
dbfopen.c
Go to the documentation of this file.
1/******************************************************************************
2 *
3 * Project: Shapelib
4 * Purpose: Implementation of .dbf access API documented in dbf_api.html.
5 * Author: Frank Warmerdam, warmerdam@pobox.com
6 *
7 ******************************************************************************
8 * Copyright (c) 1999, Frank Warmerdam
9 * Copyright (c) 2012-2024, Even Rouault <even dot rouault at spatialys.com>
10 *
11 * SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
12 ******************************************************************************/
13
14#include "shapefil_private.h"
15
16#include <assert.h>
17#include <errno.h>
18#include <math.h>
19#include <stdbool.h>
20#include <stdio.h>
21#include <stdlib.h>
22#include <ctype.h>
23#include <string.h>
24
25#ifdef USE_CPL
26#include "cpl_string.h"
27#else
28
29#ifndef STRCASECMP
30#if defined(_MSC_VER)
31#define STRCASECMP(a, b) (_stricmp(a, b))
32#elif defined(_WIN32)
33#define STRCASECMP(a, b) (stricmp(a, b))
34#else
35#include <strings.h>
36#define STRCASECMP(a, b) (strcasecmp(a, b))
37#endif
38#endif
39
40#if defined(_MSC_VER)
41#if _MSC_VER < 1900
42#define snprintf _snprintf
43#endif
44#elif defined(_WIN32)
45#ifndef snprintf
46#define snprintf _snprintf
47#endif
48#endif
49
50#define CPLsprintf sprintf
51#define CPLsnprintf snprintf
52#endif
53
54#ifndef FALSE
55#define FALSE 0
56#define TRUE 1
57#endif
58
59/* File header size */
60#define XBASE_FILEHDR_SZ 32
61
62#define HEADER_RECORD_TERMINATOR 0x0D
63
64/* See http://www.manmrk.net/tutorials/database/xbase/dbf.html */
65#define END_OF_FILE_CHARACTER 0x1A
66
67#ifdef USE_CPL
69{
70}
71#else
72#define CPL_IGNORE_RET_VAL_INT(x) x
73#endif
74
75/************************************************************************/
76/* DBFWriteHeader() */
77/* */
78/* This is called to write out the file header, and field */
79/* descriptions before writing any actual data records. This */
80/* also computes all the DBFDataSet field offset/size/decimals */
81/* and so forth values. */
82/************************************************************************/
83
84static void DBFWriteHeader(DBFHandle psDBF)
85{
86 unsigned char abyHeader[XBASE_FILEHDR_SZ] = {0};
87
88 if (!psDBF->bNoHeader)
89 return;
90
91 psDBF->bNoHeader = FALSE;
92
93 /* -------------------------------------------------------------------- */
94 /* Initialize the file header information. */
95 /* -------------------------------------------------------------------- */
96 abyHeader[0] = 0x03; /* memo field? - just copying */
97
98 /* write out update date */
99 abyHeader[1] = STATIC_CAST(unsigned char, psDBF->nUpdateYearSince1900);
100 abyHeader[2] = STATIC_CAST(unsigned char, psDBF->nUpdateMonth);
101 abyHeader[3] = STATIC_CAST(unsigned char, psDBF->nUpdateDay);
102
103 /* record count preset at zero */
104
105 abyHeader[8] = STATIC_CAST(unsigned char, psDBF->nHeaderLength % 256);
106 abyHeader[9] = STATIC_CAST(unsigned char, psDBF->nHeaderLength / 256);
107
108 abyHeader[10] = STATIC_CAST(unsigned char, psDBF->nRecordLength % 256);
109 abyHeader[11] = STATIC_CAST(unsigned char, psDBF->nRecordLength / 256);
110
111 abyHeader[29] = STATIC_CAST(unsigned char, psDBF->iLanguageDriver);
112
113 /* -------------------------------------------------------------------- */
114 /* Write the initial 32 byte file header, and all the field */
115 /* descriptions. */
116 /* -------------------------------------------------------------------- */
117 psDBF->sHooks.FSeek(psDBF->fp, 0, 0);
118 psDBF->sHooks.FWrite(abyHeader, XBASE_FILEHDR_SZ, 1, psDBF->fp);
119 psDBF->sHooks.FWrite(psDBF->pszHeader, XBASE_FLDHDR_SZ, psDBF->nFields,
120 psDBF->fp);
121
122 /* -------------------------------------------------------------------- */
123 /* Write out the newline character if there is room for it. */
124 /* -------------------------------------------------------------------- */
125 if (psDBF->nHeaderLength >
128 psDBF->sHooks.FWrite(&cNewline, 1, 1, psDBF->fp);
129 }
130
131 /* -------------------------------------------------------------------- */
132 /* If the file is new, add a EOF character. */
133 /* -------------------------------------------------------------------- */
134 if (psDBF->nRecords == 0 && psDBF->bWriteEndOfFileChar) {
136
137 psDBF->sHooks.FWrite(&ch, 1, 1, psDBF->fp);
138 }
139}
140
141/************************************************************************/
142/* DBFFlushRecord() */
143/* */
144/* Write out the current record if there is one. */
145/************************************************************************/
146
147static bool DBFFlushRecord(DBFHandle psDBF)
148{
149 if (psDBF->bCurrentRecordModified && psDBF->nCurrentRecord > -1) {
150 psDBF->bCurrentRecordModified = FALSE;
151
152 const SAOffset nRecordOffset =
153 psDBF->nRecordLength *
154 STATIC_CAST(SAOffset, psDBF->nCurrentRecord) +
155 psDBF->nHeaderLength;
156
157 /* --------------------------------------------------------------------
158 */
159 /* Guard FSeek with check for whether we're already at position; */
160 /* no-op FSeeks defeat network filesystems' write buffering. */
161 /* --------------------------------------------------------------------
162 */
163 if (psDBF->bRequireNextWriteSeek ||
164 psDBF->sHooks.FTell(psDBF->fp) != nRecordOffset) {
165 if (psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0) != 0) {
166 char szMessage[128];
167 snprintf(
168 szMessage, sizeof(szMessage),
169 "Failure seeking to position before writing DBF record %d.",
170 psDBF->nCurrentRecord);
171 psDBF->sHooks.Error(szMessage);
172 return false;
173 }
174 }
175
176 if (psDBF->sHooks.FWrite(psDBF->pszCurrentRecord, psDBF->nRecordLength,
177 1, psDBF->fp) != 1) {
178 char szMessage[128];
180 "Failure writing DBF record %d.", psDBF->nCurrentRecord);
181 psDBF->sHooks.Error(szMessage);
182 return false;
183 }
184
185 /* --------------------------------------------------------------------
186 */
187 /* If next op is also a write, allow possible skipping of FSeek. */
188 /* --------------------------------------------------------------------
189 */
190 psDBF->bRequireNextWriteSeek = FALSE;
191
192 if (psDBF->nCurrentRecord == psDBF->nRecords - 1) {
193 if (psDBF->bWriteEndOfFileChar) {
195 psDBF->sHooks.FWrite(&ch, 1, 1, psDBF->fp);
196 }
197 }
198 }
199
200 return true;
201}
202
203/************************************************************************/
204/* DBFLoadRecord() */
205/************************************************************************/
206
207static bool DBFLoadRecord(DBFHandle psDBF, int iRecord)
208{
209 if (psDBF->nCurrentRecord != iRecord) {
210 if (!DBFFlushRecord(psDBF))
211 return false;
212
213 const SAOffset nRecordOffset =
214 psDBF->nRecordLength * STATIC_CAST(SAOffset, iRecord) +
215 psDBF->nHeaderLength;
216
217 if (psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, SEEK_SET) != 0) {
218 char szMessage[128];
220 "fseek(%ld) failed on DBF file.",
222 psDBF->sHooks.Error(szMessage);
223 return false;
224 }
225
226 if (psDBF->sHooks.FRead(psDBF->pszCurrentRecord, psDBF->nRecordLength,
227 1, psDBF->fp) != 1) {
228 char szMessage[128];
230 "fread(%d) failed on DBF file.", psDBF->nRecordLength);
231 psDBF->sHooks.Error(szMessage);
232 return false;
233 }
234
235 psDBF->nCurrentRecord = iRecord;
236 /* --------------------------------------------------------------------
237 */
238 /* Require a seek for next write in case of mixed R/W operations.
239 */
240 /* --------------------------------------------------------------------
241 */
242 psDBF->bRequireNextWriteSeek = TRUE;
243 }
244
245 return true;
246}
247
248/************************************************************************/
249/* DBFUpdateHeader() */
250/************************************************************************/
251
253{
254 if (psDBF->bNoHeader)
255 DBFWriteHeader(psDBF);
256
257 if (!DBFFlushRecord(psDBF))
258 return;
259
260 psDBF->sHooks.FSeek(psDBF->fp, 0, 0);
261
262 unsigned char abyFileHeader[XBASE_FILEHDR_SZ] = {0};
263 psDBF->sHooks.FRead(abyFileHeader, 1, sizeof(abyFileHeader), psDBF->fp);
264
265 abyFileHeader[1] = STATIC_CAST(unsigned char, psDBF->nUpdateYearSince1900);
266 abyFileHeader[2] = STATIC_CAST(unsigned char, psDBF->nUpdateMonth);
267 abyFileHeader[3] = STATIC_CAST(unsigned char, psDBF->nUpdateDay);
268 abyFileHeader[4] = STATIC_CAST(unsigned char, psDBF->nRecords & 0xFF);
269 abyFileHeader[5] =
270 STATIC_CAST(unsigned char, (psDBF->nRecords >> 8) & 0xFF);
271 abyFileHeader[6] =
272 STATIC_CAST(unsigned char, (psDBF->nRecords >> 16) & 0xFF);
273 abyFileHeader[7] =
274 STATIC_CAST(unsigned char, (psDBF->nRecords >> 24) & 0xFF);
275
276 psDBF->sHooks.FSeek(psDBF->fp, 0, 0);
277 psDBF->sHooks.FWrite(abyFileHeader, sizeof(abyFileHeader), 1, psDBF->fp);
278
279 psDBF->sHooks.FFlush(psDBF->fp);
280}
281
282/************************************************************************/
283/* DBFSetLastModifiedDate() */
284/************************************************************************/
285
287 int nMM, int nDD)
288{
289 psDBF->nUpdateYearSince1900 = nYYSince1900;
290 psDBF->nUpdateMonth = nMM;
291 psDBF->nUpdateDay = nDD;
292}
293
294/************************************************************************/
295/* DBFOpen() */
296/* */
297/* Open a .dbf file. */
298/************************************************************************/
299
301{
302 SAHooks sHooks;
303
304 SASetupDefaultHooks(&sHooks);
305
306 return DBFOpenLL(pszFilename, pszAccess, &sHooks);
307}
308
309/************************************************************************/
310/* DBFGetLenWithoutExtension() */
311/************************************************************************/
312
313static int DBFGetLenWithoutExtension(const char *pszBasename)
314{
315 const int nLen = STATIC_CAST(int, strlen(pszBasename));
316 for (int i = nLen - 1;
317 i > 0 && pszBasename[i] != '/' && pszBasename[i] != '\\'; i--) {
318 if (pszBasename[i] == '.') {
319 return i;
320 }
321 }
322 return nLen;
323}
324
325/************************************************************************/
326/* DBFOpen() */
327/* */
328/* Open a .dbf file. */
329/************************************************************************/
330
332 const SAHooks *psHooks)
333{
334 /* -------------------------------------------------------------------- */
335 /* We only allow the access strings "rb" and "r+". */
336 /* -------------------------------------------------------------------- */
337 if (strcmp(pszAccess, "r") != 0 && strcmp(pszAccess, "r+") != 0 &&
338 strcmp(pszAccess, "rb") != 0 && strcmp(pszAccess, "rb+") != 0 &&
339 strcmp(pszAccess, "r+b") != 0)
340 return SHPLIB_NULLPTR;
341
342 if (strcmp(pszAccess, "r") == 0)
343 pszAccess = "rb";
344
345 if (strcmp(pszAccess, "r+") == 0)
346 pszAccess = "rb+";
347
348 /* -------------------------------------------------------------------- */
349 /* Compute the base (layer) name. If there is any extension */
350 /* on the passed in filename we will strip it off. */
351 /* -------------------------------------------------------------------- */
352 const int nLenWithoutExtension = DBFGetLenWithoutExtension(pszFilename);
354 if (!pszFullname) {
355 return SHPLIB_NULLPTR;
356 }
359
361 if (!psDBF) {
363 return SHPLIB_NULLPTR;
364 }
365 psDBF->fp = psHooks->FOpen(pszFullname, pszAccess, psHooks->pvUserData);
366 memcpy(&(psDBF->sHooks), psHooks, sizeof(SAHooks));
367
368 if (psDBF->fp == SHPLIB_NULLPTR) {
370 psDBF->fp =
371 psDBF->sHooks.FOpen(pszFullname, pszAccess, psHooks->pvUserData);
372 }
373
375 SAFile pfCPG = psHooks->FOpen(pszFullname, "r", psHooks->pvUserData);
376 if (pfCPG == SHPLIB_NULLPTR) {
378 pfCPG = psHooks->FOpen(pszFullname, "r", psHooks->pvUserData);
379 }
380
382
383 if (psDBF->fp == SHPLIB_NULLPTR) {
384 free(psDBF);
385 if (pfCPG)
386 psHooks->FClose(pfCPG);
387 return SHPLIB_NULLPTR;
388 }
389
390 psDBF->bNoHeader = FALSE;
391 psDBF->nCurrentRecord = -1;
392 psDBF->bCurrentRecordModified = FALSE;
393
394 /* -------------------------------------------------------------------- */
395 /* Read Table Header info */
396 /* -------------------------------------------------------------------- */
397 const int nBufSize = 500;
398 unsigned char *pabyBuf = STATIC_CAST(unsigned char *, malloc(nBufSize));
399 if (!pabyBuf) {
400 psDBF->sHooks.FClose(psDBF->fp);
401 if (pfCPG)
402 psHooks->FClose(pfCPG);
403 free(psDBF);
404 return SHPLIB_NULLPTR;
405 }
406 if (psDBF->sHooks.FRead(pabyBuf, XBASE_FILEHDR_SZ, 1, psDBF->fp) != 1) {
407 psDBF->sHooks.FClose(psDBF->fp);
408 if (pfCPG)
409 psDBF->sHooks.FClose(pfCPG);
410 free(pabyBuf);
411 free(psDBF);
412 return SHPLIB_NULLPTR;
413 }
414
416
417 psDBF->nRecords = pabyBuf[4] | (pabyBuf[5] << 8) | (pabyBuf[6] << 16) |
418 ((pabyBuf[7] & 0x7f) << 24);
419
420 const int nHeadLen = pabyBuf[8] | (pabyBuf[9] << 8);
421 psDBF->nHeaderLength = nHeadLen;
422 psDBF->nRecordLength = pabyBuf[10] | (pabyBuf[11] << 8);
423 psDBF->iLanguageDriver = pabyBuf[29];
424
425 if (psDBF->nRecordLength == 0 || nHeadLen < XBASE_FILEHDR_SZ) {
426 psDBF->sHooks.FClose(psDBF->fp);
427 if (pfCPG)
428 psDBF->sHooks.FClose(pfCPG);
429 free(pabyBuf);
430 free(psDBF);
431 return SHPLIB_NULLPTR;
432 }
433
434 const int nFields = (nHeadLen - XBASE_FILEHDR_SZ) / XBASE_FLDHDR_SZ;
435 psDBF->nFields = nFields;
436
437 assert(psDBF->nRecordLength < 65536);
438 psDBF->pszCurrentRecord = STATIC_CAST(char *, malloc(psDBF->nRecordLength));
439 if (!psDBF->pszCurrentRecord) {
440 psDBF->sHooks.FClose(psDBF->fp);
441 if (pfCPG)
442 psDBF->sHooks.FClose(pfCPG);
443 free(pabyBuf);
444 free(psDBF);
445 return SHPLIB_NULLPTR;
446 }
447
448 /* -------------------------------------------------------------------- */
449 /* Figure out the code page from the LDID and CPG */
450 /* -------------------------------------------------------------------- */
451 psDBF->pszCodePage = SHPLIB_NULLPTR;
452 if (pfCPG) {
453 memset(pabyBuf, 0, nBufSize);
454 psDBF->sHooks.FRead(pabyBuf, 1, nBufSize - 1, pfCPG);
455 const size_t n = strcspn(REINTERPRET_CAST(char *, pabyBuf), "\n\r");
456 if (n > 0) {
457 pabyBuf[n] = '\0';
458 psDBF->pszCodePage = STATIC_CAST(char *, malloc(n + 1));
459 if (psDBF->pszCodePage)
460 memcpy(psDBF->pszCodePage, pabyBuf, n + 1);
461 }
462 psDBF->sHooks.FClose(pfCPG);
463 }
464 if (psDBF->pszCodePage == SHPLIB_NULLPTR && pabyBuf[29] != 0) {
465 snprintf(REINTERPRET_CAST(char *, pabyBuf), nBufSize, "LDID/%d",
466 psDBF->iLanguageDriver);
467 psDBF->pszCodePage = STATIC_CAST(
468 char *, malloc(strlen(REINTERPRET_CAST(char *, pabyBuf)) + 1));
469 if (psDBF->pszCodePage)
470 strcpy(psDBF->pszCodePage, REINTERPRET_CAST(char *, pabyBuf));
471 }
472
473 /* -------------------------------------------------------------------- */
474 /* Read in Field Definitions */
475 /* -------------------------------------------------------------------- */
476
477 // To please Coverity Scan
478 assert(nHeadLen < 65536);
479 unsigned char *pabyBufNew =
480 STATIC_CAST(unsigned char *, realloc(pabyBuf, nHeadLen));
481 if (!pabyBufNew) {
482 psDBF->sHooks.FClose(psDBF->fp);
483 free(pabyBuf);
484 free(psDBF->pszCurrentRecord);
485 free(psDBF->pszCodePage);
486 free(psDBF);
487 return SHPLIB_NULLPTR;
488 }
490 psDBF->pszHeader = REINTERPRET_CAST(char *, pabyBuf);
491
492 psDBF->sHooks.FSeek(psDBF->fp, XBASE_FILEHDR_SZ, 0);
493 if (psDBF->sHooks.FRead(pabyBuf, nHeadLen - XBASE_FILEHDR_SZ, 1,
494 psDBF->fp) != 1) {
495 psDBF->sHooks.FClose(psDBF->fp);
496 free(pabyBuf);
497 free(psDBF->pszCurrentRecord);
498 free(psDBF->pszCodePage);
499 free(psDBF);
500 return SHPLIB_NULLPTR;
501 }
502
503 psDBF->panFieldOffset = STATIC_CAST(int *, malloc(sizeof(int) * nFields));
504 psDBF->panFieldSize = STATIC_CAST(int *, malloc(sizeof(int) * nFields));
505 psDBF->panFieldDecimals = STATIC_CAST(int *, malloc(sizeof(int) * nFields));
506 psDBF->pachFieldType = STATIC_CAST(char *, malloc(sizeof(char) * nFields));
507 if (!psDBF->panFieldOffset || !psDBF->panFieldSize ||
508 !psDBF->panFieldDecimals || !psDBF->pachFieldType) {
510 return SHPLIB_NULLPTR;
511 }
512
513 for (int iField = 0; iField < nFields; iField++) {
514 const unsigned char *pabyFInfo = pabyBuf + iField * XBASE_FLDHDR_SZ;
516 psDBF->nFields = iField;
517 break;
518 }
519
520 if (pabyFInfo[11] == 'N' || pabyFInfo[11] == 'F') {
521 psDBF->panFieldSize[iField] = pabyFInfo[16];
522 psDBF->panFieldDecimals[iField] = pabyFInfo[17];
523 }
524 else {
525 psDBF->panFieldSize[iField] = pabyFInfo[16];
526 psDBF->panFieldDecimals[iField] = 0;
527
528 /*
529 ** The following seemed to be used sometimes to handle files with
530 long
531 ** string fields, but in other cases (such as bug 1202) the decimals
532 field
533 ** just seems to indicate some sort of preferred formatting, not
534 very
535 ** wide fields. So I have disabled this code. FrankW.
536 psDBF->panFieldSize[iField] = pabyFInfo[16] +
537 pabyFInfo[17]*256; psDBF->panFieldDecimals[iField] = 0;
538 */
539 }
540
541 psDBF->pachFieldType[iField] = STATIC_CAST(char, pabyFInfo[11]);
542 if (iField == 0)
543 psDBF->panFieldOffset[iField] = 1;
544 else
545 psDBF->panFieldOffset[iField] = psDBF->panFieldOffset[iField - 1] +
546 psDBF->panFieldSize[iField - 1];
547 }
548
549 /* Check that the total width of fields does not exceed the record width */
550 if (psDBF->nFields > 0 && psDBF->panFieldOffset[psDBF->nFields - 1] +
551 psDBF->panFieldSize[psDBF->nFields - 1] >
552 psDBF->nRecordLength) {
554 return SHPLIB_NULLPTR;
555 }
556
558
559 psDBF->bRequireNextWriteSeek = TRUE;
560
561 return (psDBF);
562}
563
564/************************************************************************/
565/* DBFClose() */
566/************************************************************************/
567
569{
570 if (psDBF == SHPLIB_NULLPTR)
571 return;
572
573 /* -------------------------------------------------------------------- */
574 /* Write out header if not already written. */
575 /* -------------------------------------------------------------------- */
576 if (psDBF->bNoHeader)
577 DBFWriteHeader(psDBF);
578
579 CPL_IGNORE_RET_VAL_INT(DBFFlushRecord(psDBF));
580
581 /* -------------------------------------------------------------------- */
582 /* Update last access date, and number of records if we have */
583 /* write access. */
584 /* -------------------------------------------------------------------- */
585 if (psDBF->bUpdated)
587
588 /* -------------------------------------------------------------------- */
589 /* Close, and free resources. */
590 /* -------------------------------------------------------------------- */
591 psDBF->sHooks.FClose(psDBF->fp);
592
593 if (psDBF->panFieldOffset != SHPLIB_NULLPTR) {
594 free(psDBF->panFieldOffset);
595 free(psDBF->panFieldSize);
596 free(psDBF->panFieldDecimals);
597 free(psDBF->pachFieldType);
598 }
599
600 if (psDBF->pszWorkField != SHPLIB_NULLPTR)
601 free(psDBF->pszWorkField);
602
603 free(psDBF->pszHeader);
604 free(psDBF->pszCurrentRecord);
605 free(psDBF->pszCodePage);
606
607 free(psDBF);
608}
609
610/************************************************************************/
611/* DBFCreate() */
612/* */
613/* Create a new .dbf file with default code page LDID/87 (0x57) */
614/************************************************************************/
615
617{
618 return DBFCreateEx(pszFilename, "LDID/87"); // 0x57
619}
620
621/************************************************************************/
622/* DBFCreateEx() */
623/* */
624/* Create a new .dbf file. */
625/************************************************************************/
626
628 const char *pszCodePage)
629{
630 SAHooks sHooks;
631
632 SASetupDefaultHooks(&sHooks);
633
634 return DBFCreateLL(pszFilename, pszCodePage, &sHooks);
635}
636
637/************************************************************************/
638/* DBFCreate() */
639/* */
640/* Create a new .dbf file. */
641/************************************************************************/
642
644 const char *pszCodePage,
645 const SAHooks *psHooks)
646{
647 /* -------------------------------------------------------------------- */
648 /* Compute the base (layer) name. If there is any extension */
649 /* on the passed in filename we will strip it off. */
650 /* -------------------------------------------------------------------- */
651 const int nLenWithoutExtension = DBFGetLenWithoutExtension(pszFilename);
653 if (!pszFullname)
654 return SHPLIB_NULLPTR;
657
658 /* -------------------------------------------------------------------- */
659 /* Create the file. */
660 /* -------------------------------------------------------------------- */
661 SAFile fp = psHooks->FOpen(pszFullname, "wb+", psHooks->pvUserData);
662 if (fp == SHPLIB_NULLPTR) {
663 const size_t nMessageLen = strlen(pszFullname) + 256;
664 char *pszMessage = STATIC_CAST(char *, malloc(nMessageLen));
665 if (pszMessage) {
666 snprintf(pszMessage, nMessageLen, "Failed to create file %s: %s",
668 psHooks->Error(pszMessage);
670 }
671
673 return SHPLIB_NULLPTR;
674 }
675
677 int ldid = -1;
678 if (pszCodePage != SHPLIB_NULLPTR) {
679 if (strncmp(pszCodePage, "LDID/", 5) == 0) {
680 ldid = atoi(pszCodePage + 5);
681 if (ldid > 255)
682 ldid = -1; // don't use 0 to indicate out of range as LDID/0 is
683 // a valid one
684 }
685 if (ldid < 0) {
686 SAFile fpCPG =
687 psHooks->FOpen(pszFullname, "w", psHooks->pvUserData);
688 psHooks->FWrite(
689 CONST_CAST(void *, STATIC_CAST(const void *, pszCodePage)),
690 strlen(pszCodePage), 1, fpCPG);
691 psHooks->FClose(fpCPG);
692 }
693 }
694 if (pszCodePage == SHPLIB_NULLPTR || ldid >= 0) {
695 psHooks->Remove(pszFullname, psHooks->pvUserData);
696 }
697
699
700 /* -------------------------------------------------------------------- */
701 /* Create the info structure. */
702 /* -------------------------------------------------------------------- */
704 if (!psDBF) {
705 return SHPLIB_NULLPTR;
706 }
707
708 memcpy(&(psDBF->sHooks), psHooks, sizeof(SAHooks));
709 psDBF->fp = fp;
710 psDBF->nRecords = 0;
711 psDBF->nFields = 0;
712 psDBF->nRecordLength = 1;
713 psDBF->nHeaderLength =
714 XBASE_FILEHDR_SZ + 1; /* + 1 for HEADER_RECORD_TERMINATOR */
715
716 psDBF->panFieldOffset = SHPLIB_NULLPTR;
717 psDBF->panFieldSize = SHPLIB_NULLPTR;
718 psDBF->panFieldDecimals = SHPLIB_NULLPTR;
719 psDBF->pachFieldType = SHPLIB_NULLPTR;
720 psDBF->pszHeader = SHPLIB_NULLPTR;
721
722 psDBF->nCurrentRecord = -1;
723 psDBF->bCurrentRecordModified = FALSE;
724 psDBF->pszCurrentRecord = SHPLIB_NULLPTR;
725
726 psDBF->bNoHeader = TRUE;
727
728 psDBF->iLanguageDriver = ldid > 0 ? ldid : 0;
729 psDBF->pszCodePage = SHPLIB_NULLPTR;
730 if (pszCodePage) {
731 psDBF->pszCodePage =
732 STATIC_CAST(char *, malloc(strlen(pszCodePage) + 1));
733 if (psDBF->pszCodePage)
734 strcpy(psDBF->pszCodePage, pszCodePage);
735 }
736 DBFSetLastModifiedDate(psDBF, 95, 7, 26); /* dummy date */
737
739
740 psDBF->bRequireNextWriteSeek = TRUE;
741
742 return (psDBF);
743}
744
745/************************************************************************/
746/* DBFAddField() */
747/* */
748/* Add a field to a newly created .dbf or to an existing one */
749/************************************************************************/
750
753{
754 char chNativeType;
755
756 if (eType == FTLogical)
757 chNativeType = 'L';
758 else if (eType == FTDate)
759 chNativeType = 'D';
760 else if (eType == FTString)
761 chNativeType = 'C';
762 else
763 chNativeType = 'N';
764
766 nDecimals);
767}
768
769/************************************************************************/
770/* DBFGetNullCharacter() */
771/************************************************************************/
772
773static char DBFGetNullCharacter(char chType)
774{
775 switch (chType) {
776 case 'N':
777 case 'F':
778 return '*';
779 case 'D':
780 return '0';
781 case 'L':
782 return '?';
783 default:
784 return ' ';
785 }
786}
787
788/************************************************************************/
789/* DBFAddField() */
790/* */
791/* Add a field to a newly created .dbf file before any records */
792/* are written. */
793/************************************************************************/
794
796 char chType, int nWidth, int nDecimals)
797{
798 /* make sure that everything is written in .dbf */
799 if (!DBFFlushRecord(psDBF))
800 return -1;
801
802 if (psDBF->nHeaderLength + XBASE_FLDHDR_SZ > 65535) {
803 char szMessage[128];
805 "Cannot add field %s. Header length limit reached "
806 "(max 65535 bytes, 2046 fields).",
808 psDBF->sHooks.Error(szMessage);
809 return -1;
810 }
811
812 /* -------------------------------------------------------------------- */
813 /* Do some checking to ensure we can add records to this file. */
814 /* -------------------------------------------------------------------- */
815 if (nWidth < 1)
816 return -1;
817
820
821 if (psDBF->nRecordLength + nWidth > 65535) {
822 char szMessage[128];
824 "Cannot add field %s. Record length limit reached "
825 "(max 65535 bytes).",
827 psDBF->sHooks.Error(szMessage);
828 return -1;
829 }
830
831 const int nOldRecordLength = psDBF->nRecordLength;
832 const int nOldHeaderLength = psDBF->nHeaderLength;
833
834 /* -------------------------------------------------------------------- */
835 /* realloc all the arrays larger to hold the additional field */
836 /* information. */
837 /* -------------------------------------------------------------------- */
838
839 int *panFieldOffsetNew =
840 STATIC_CAST(int *, realloc(psDBF->panFieldOffset,
841 sizeof(int) * (psDBF->nFields + 1)));
842
843 int *panFieldSizeNew =
844 STATIC_CAST(int *, realloc(psDBF->panFieldSize,
845 sizeof(int) * (psDBF->nFields + 1)));
846
848 STATIC_CAST(int *, realloc(psDBF->panFieldDecimals,
849 sizeof(int) * (psDBF->nFields + 1)));
850
851 char *pachFieldTypeNew =
852 STATIC_CAST(char *, realloc(psDBF->pachFieldType,
853 sizeof(char) * (psDBF->nFields + 1)));
854
855 char *pszHeaderNew =
856 STATIC_CAST(char *, realloc(psDBF->pszHeader,
857 (psDBF->nFields + 1) * XBASE_FLDHDR_SZ));
858
859 /* -------------------------------------------------------------------- */
860 /* Make the current record buffer appropriately larger. */
861 /* -------------------------------------------------------------------- */
862 char *pszCurrentRecordNew =
863 STATIC_CAST(char *, realloc(psDBF->pszCurrentRecord,
864 psDBF->nRecordLength + nWidth));
865
867 psDBF->panFieldOffset = panFieldOffsetNew;
868 if (panFieldSizeNew)
869 psDBF->panFieldSize = panFieldSizeNew;
871 psDBF->panFieldDecimals = panFieldDecimalsNew;
873 psDBF->pachFieldType = pachFieldTypeNew;
874 if (pszHeaderNew)
875 psDBF->pszHeader = pszHeaderNew;
877 psDBF->pszCurrentRecord = pszCurrentRecordNew;
878
881 psDBF->sHooks.Error("Out of memory");
882 return -1;
883 }
884
885 /* alloc record */
887 if (!psDBF->bNoHeader) {
888 pszRecord = STATIC_CAST(char *, malloc(psDBF->nRecordLength + nWidth));
889 if (!pszRecord) {
890 psDBF->sHooks.Error("Out of memory");
891 return -1;
892 }
893 }
894
895 psDBF->nFields++;
896
897 /* -------------------------------------------------------------------- */
898 /* Assign the new field information fields. */
899 /* -------------------------------------------------------------------- */
900 psDBF->panFieldOffset[psDBF->nFields - 1] = psDBF->nRecordLength;
901 psDBF->nRecordLength += nWidth;
902 psDBF->panFieldSize[psDBF->nFields - 1] = nWidth;
903 psDBF->panFieldDecimals[psDBF->nFields - 1] = nDecimals;
904 psDBF->pachFieldType[psDBF->nFields - 1] = chType;
905
906 /* -------------------------------------------------------------------- */
907 /* Extend the required header information. */
908 /* -------------------------------------------------------------------- */
909 psDBF->nHeaderLength += XBASE_FLDHDR_SZ;
910 psDBF->bUpdated = FALSE;
911
912 char *pszFInfo = psDBF->pszHeader + XBASE_FLDHDR_SZ * (psDBF->nFields - 1);
913
914 for (int i = 0; i < XBASE_FLDHDR_SZ; i++)
915 pszFInfo[i] = '\0';
916
918
919 pszFInfo[11] = psDBF->pachFieldType[psDBF->nFields - 1];
920
921 if (chType == 'C') {
922 pszFInfo[16] = STATIC_CAST(unsigned char, nWidth % 256);
923 pszFInfo[17] = STATIC_CAST(unsigned char, nWidth / 256);
924 }
925 else {
926 pszFInfo[16] = STATIC_CAST(unsigned char, nWidth);
927 pszFInfo[17] = STATIC_CAST(unsigned char, nDecimals);
928 }
929
930 /* we're done if dealing with new .dbf */
931 if (psDBF->bNoHeader)
932 return (psDBF->nFields - 1);
933
934 /* -------------------------------------------------------------------- */
935 /* For existing .dbf file, shift records */
936 /* -------------------------------------------------------------------- */
937
938 const char chFieldFill = DBFGetNullCharacter(chType);
939
941 for (int i = psDBF->nRecords - 1; i >= 0; --i) {
944
945 /* load record */
946 psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
947 if (psDBF->sHooks.FRead(pszRecord, nOldRecordLength, 1, psDBF->fp) !=
948 1) {
950 return -1;
951 }
952
953 /* set new field's value to NULL */
955
956 nRecordOffset = psDBF->nRecordLength * STATIC_CAST(SAOffset, i) +
957 psDBF->nHeaderLength;
958
959 /* move record to the new place*/
960 psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
961 psDBF->sHooks.FWrite(pszRecord, psDBF->nRecordLength, 1, psDBF->fp);
962 }
963
964 if (psDBF->bWriteEndOfFileChar) {
966
968 psDBF->nRecordLength * STATIC_CAST(SAOffset, psDBF->nRecords) +
969 psDBF->nHeaderLength;
970
971 psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
972 psDBF->sHooks.FWrite(&ch, 1, 1, psDBF->fp);
973 }
974
975 /* free record */
977
978 /* force update of header with new header, record length and new field */
979 psDBF->bNoHeader = TRUE;
981
982 psDBF->nCurrentRecord = -1;
983 psDBF->bCurrentRecordModified = FALSE;
984 psDBF->bUpdated = TRUE;
985
986 return (psDBF->nFields - 1);
987}
988
989/************************************************************************/
990/* DBFReadAttribute() */
991/* */
992/* Read one of the attribute fields of a record. */
993/************************************************************************/
994
995static void *DBFReadAttribute(DBFHandle psDBF, int hEntity, int iField,
996 char chReqType)
997{
998 /* -------------------------------------------------------------------- */
999 /* Verify selection. */
1000 /* -------------------------------------------------------------------- */
1001 if (hEntity < 0 || hEntity >= psDBF->nRecords)
1002 return SHPLIB_NULLPTR;
1003
1004 if (iField < 0 || iField >= psDBF->nFields)
1005 return SHPLIB_NULLPTR;
1006
1007 /* -------------------------------------------------------------------- */
1008 /* Have we read the record? */
1009 /* -------------------------------------------------------------------- */
1010 if (!DBFLoadRecord(psDBF, hEntity))
1011 return SHPLIB_NULLPTR;
1012
1013 const unsigned char *pabyRec =
1014 REINTERPRET_CAST(const unsigned char *, psDBF->pszCurrentRecord);
1015
1016 /* -------------------------------------------------------------------- */
1017 /* Ensure we have room to extract the target field. */
1018 /* -------------------------------------------------------------------- */
1019 if (psDBF->panFieldSize[iField] >= psDBF->nWorkFieldLength) {
1020 psDBF->nWorkFieldLength = psDBF->panFieldSize[iField] + 100;
1021 if (psDBF->pszWorkField == SHPLIB_NULLPTR)
1022 psDBF->pszWorkField =
1023 STATIC_CAST(char *, malloc(psDBF->nWorkFieldLength));
1024 else
1025 psDBF->pszWorkField = STATIC_CAST(
1026 char *, realloc(psDBF->pszWorkField, psDBF->nWorkFieldLength));
1027 }
1028
1029 /* -------------------------------------------------------------------- */
1030 /* Extract the requested field. */
1031 /* -------------------------------------------------------------------- */
1032 memcpy(psDBF->pszWorkField,
1033 REINTERPRET_CAST(const char *, pabyRec) +
1034 psDBF->panFieldOffset[iField],
1035 psDBF->panFieldSize[iField]);
1036 psDBF->pszWorkField[psDBF->panFieldSize[iField]] = '\0';
1037
1038 void *pReturnField = psDBF->pszWorkField;
1039
1040 /* -------------------------------------------------------------------- */
1041 /* Decode the field. */
1042 /* -------------------------------------------------------------------- */
1043 if (chReqType == 'I') {
1044 psDBF->fieldValue.nIntField = atoi(psDBF->pszWorkField);
1045
1046 pReturnField = &(psDBF->fieldValue.nIntField);
1047 }
1048 else if (chReqType == 'N') {
1049 psDBF->fieldValue.dfDoubleField =
1050 psDBF->sHooks.Atof(psDBF->pszWorkField);
1051
1052 pReturnField = &(psDBF->fieldValue.dfDoubleField);
1053 }
1054
1055/* -------------------------------------------------------------------- */
1056/* Should we trim white space off the string attribute value? */
1057/* -------------------------------------------------------------------- */
1058#ifdef TRIM_DBF_WHITESPACE
1059 else {
1060 char *pchSrc = psDBF->pszWorkField;
1061 char *pchDst = pchSrc;
1062
1063 while (*pchSrc == ' ')
1064 pchSrc++;
1065
1066 while (*pchSrc != '\0')
1067 *(pchDst++) = *(pchSrc++);
1068 *pchDst = '\0';
1069
1070 while (pchDst != psDBF->pszWorkField && *(--pchDst) == ' ')
1071 *pchDst = '\0';
1072 }
1073#endif
1074
1075 return pReturnField;
1076}
1077
1078/************************************************************************/
1079/* DBFReadIntAttribute() */
1080/* */
1081/* Read an integer attribute. */
1082/************************************************************************/
1083
1085 int iField)
1086{
1087 int *pnValue =
1088 STATIC_CAST(int *, DBFReadAttribute(psDBF, iRecord, iField, 'I'));
1089
1090 if (pnValue == SHPLIB_NULLPTR)
1091 return 0;
1092 else
1093 return *pnValue;
1094}
1095
1096/************************************************************************/
1097/* DBFReadDoubleAttribute() */
1098/* */
1099/* Read a double attribute. */
1100/************************************************************************/
1101
1103 int iField)
1104{
1105 double *pdValue =
1106 STATIC_CAST(double *, DBFReadAttribute(psDBF, iRecord, iField, 'N'));
1107
1108 if (pdValue == SHPLIB_NULLPTR)
1109 return 0.0;
1110 else
1111 return *pdValue;
1112}
1113
1114/************************************************************************/
1115/* DBFReadStringAttribute() */
1116/* */
1117/* Read a string attribute. */
1118/************************************************************************/
1119
1120const char SHPAPI_CALL1(*)
1122{
1123 return STATIC_CAST(const char *,
1124 DBFReadAttribute(psDBF, iRecord, iField, 'C'));
1125}
1126
1127/************************************************************************/
1128/* DBFReadLogicalAttribute() */
1129/* */
1130/* Read a logical attribute. */
1131/************************************************************************/
1132
1133const char SHPAPI_CALL1(*)
1135{
1136 return STATIC_CAST(const char *,
1137 DBFReadAttribute(psDBF, iRecord, iField, 'L'));
1138}
1139
1140/************************************************************************/
1141/* DBFReadDateAttribute() */
1142/* */
1143/* Read a date attribute. */
1144/************************************************************************/
1145
1147 int iField)
1148{
1149 const char *pdateValue = STATIC_CAST(
1150 const char *, DBFReadAttribute(psDBF, iRecord, iField, 'D'));
1151
1152 SHPDate date;
1153
1154 if (pdateValue == SHPLIB_NULLPTR) {
1155 date.year = 0;
1156 date.month = 0;
1157 date.day = 0;
1158 }
1159 else if (3 != sscanf(pdateValue, "%4d%2d%2d", &date.year, &date.month,
1160 &date.day)) {
1161 date.year = 0;
1162 date.month = 0;
1163 date.day = 0;
1164 }
1165
1166 return date;
1167}
1168
1169/************************************************************************/
1170/* DBFIsValueNULL() */
1171/* */
1172/* Return TRUE if the passed string is NULL. */
1173/************************************************************************/
1174
1175static bool DBFIsValueNULL(char chType, const char *pszValue, int size)
1176{
1177 if (pszValue == SHPLIB_NULLPTR)
1178 return true;
1179
1180 switch (chType) {
1181 case 'N':
1182 case 'F':
1183 /*
1184 ** We accept all asterisks or all blanks as NULL
1185 ** though according to the spec I think it should be all
1186 ** asterisks.
1187 */
1188 if (pszValue[0] == '*')
1189 return true;
1190
1191 for (int i = 0; pszValue[i] != '\0'; i++) {
1192 if (pszValue[i] != ' ')
1193 return false;
1194 }
1195 return true;
1196
1197 case 'D': {
1198 const char DIGIT_ZERO = '0';
1199 /* NULL date fields have value "00000000" or "0"*size */
1200 /* Some DBF files have fields filled with spaces */
1201 /* (trimmed by DBFReadStringAttribute) to indicate null */
1202 /* values for dates (#4265). */
1203 /* And others have ' 0':
1204 * https://lists.osgeo.org/pipermail/gdal-dev/2023-November/058010.html
1205 */
1206 /* And others just empty string:
1207 * https://github.com/OSGeo/gdal/issues/10405 */
1208 if (pszValue[0] == 0 || strncmp(pszValue, "00000000", 8) == 0 ||
1209 strcmp(pszValue, " ") == 0 || strcmp(pszValue, "0") == 0)
1210 return true;
1211 for (int i = 0; i < size; i++)
1212 if (pszValue[i] != DIGIT_ZERO)
1213 return false;
1214 return true;
1215 }
1216
1217 case 'L':
1218 /* NULL boolean fields have value "?" */
1219 return pszValue[0] == '?';
1220
1221 default:
1222 /* empty string fields are considered NULL */
1223 return strlen(pszValue) == 0;
1224 }
1225}
1226
1227/************************************************************************/
1228/* DBFIsAttributeNULL() */
1229/* */
1230/* Return TRUE if value for field is NULL. */
1231/* */
1232/* Contributed by Jim Matthews. */
1233/************************************************************************/
1234
1236 int iField)
1237{
1239
1240 if (pszValue == SHPLIB_NULLPTR)
1241 return TRUE;
1242
1243 return DBFIsValueNULL(psDBF->pachFieldType[iField], pszValue,
1244 psDBF->panFieldSize[iField]);
1245}
1246
1247/************************************************************************/
1248/* DBFGetFieldCount() */
1249/* */
1250/* Return the number of fields in this table. */
1251/************************************************************************/
1252
1254{
1255 return (psDBF->nFields);
1256}
1257
1258/************************************************************************/
1259/* DBFGetRecordCount() */
1260/* */
1261/* Return the number of records in this table. */
1262/************************************************************************/
1263
1265{
1266 return (psDBF->nRecords);
1267}
1268
1269/************************************************************************/
1270/* DBFGetFieldInfo() */
1271/* */
1272/* Return any requested information about the field. */
1273/* pszFieldName must be at least XBASE_FLDNAME_LEN_READ+1 (=12) */
1274/* bytes long. */
1275/************************************************************************/
1276
1278 char *pszFieldName, int *pnWidth,
1279 int *pnDecimals)
1280{
1281 if (iField < 0 || iField >= psDBF->nFields)
1282 return (FTInvalid);
1283
1284 if (pnWidth != SHPLIB_NULLPTR)
1285 *pnWidth = psDBF->panFieldSize[iField];
1286
1288 *pnDecimals = psDBF->panFieldDecimals[iField];
1289
1292 STATIC_CAST(char *, psDBF->pszHeader) +
1296 for (int i = XBASE_FLDNAME_LEN_READ - 1;
1297 i > 0 && pszFieldName[i] == ' '; i--)
1298 pszFieldName[i] = '\0';
1299 }
1300
1301 if (psDBF->pachFieldType[iField] == 'L')
1302 return (FTLogical);
1303
1304 else if (psDBF->pachFieldType[iField] == 'D')
1305 return (FTDate);
1306
1307 else if (psDBF->pachFieldType[iField] == 'N' ||
1308 psDBF->pachFieldType[iField] == 'F') {
1309 if (psDBF->panFieldDecimals[iField] > 0 ||
1310 psDBF->panFieldSize[iField] >= 10)
1311 return (FTDouble);
1312 else
1313 return (FTInteger);
1314 }
1315 else {
1316 return (FTString);
1317 }
1318}
1319
1320/************************************************************************/
1321/* DBFWriteAttribute() */
1322/* */
1323/* Write an attribute record to the file. */
1324/************************************************************************/
1325
1326static bool DBFWriteAttribute(DBFHandle psDBF, int hEntity, int iField,
1327 void *pValue)
1328{
1329 /* -------------------------------------------------------------------- */
1330 /* Is this a valid record? */
1331 /* -------------------------------------------------------------------- */
1332 if (hEntity < 0 || hEntity > psDBF->nRecords)
1333 return false;
1334
1335 if (psDBF->bNoHeader)
1336 DBFWriteHeader(psDBF);
1337
1338 /* -------------------------------------------------------------------- */
1339 /* Is this a brand new record? */
1340 /* -------------------------------------------------------------------- */
1341 if (hEntity == psDBF->nRecords) {
1342 if (!DBFFlushRecord(psDBF))
1343 return false;
1344
1345 psDBF->nRecords++;
1346 for (int i = 0; i < psDBF->nRecordLength; i++)
1347 psDBF->pszCurrentRecord[i] = ' ';
1348
1349 psDBF->nCurrentRecord = hEntity;
1350 }
1351
1352 /* -------------------------------------------------------------------- */
1353 /* Is this an existing record, but different than the last one */
1354 /* we accessed? */
1355 /* -------------------------------------------------------------------- */
1356 if (!DBFLoadRecord(psDBF, hEntity))
1357 return false;
1358
1359 unsigned char *pabyRec =
1360 REINTERPRET_CAST(unsigned char *, psDBF->pszCurrentRecord);
1361
1362 psDBF->bCurrentRecordModified = TRUE;
1363 psDBF->bUpdated = TRUE;
1364
1365 /* -------------------------------------------------------------------- */
1366 /* Translate NULL value to valid DBF file representation. */
1367 /* */
1368 /* Contributed by Jim Matthews. */
1369 /* -------------------------------------------------------------------- */
1370 if (pValue == SHPLIB_NULLPTR) {
1371 memset(pabyRec + psDBF->panFieldOffset[iField],
1372 DBFGetNullCharacter(psDBF->pachFieldType[iField]),
1373 psDBF->panFieldSize[iField]);
1374 return true;
1375 }
1376
1377 /* -------------------------------------------------------------------- */
1378 /* Assign all the record fields. */
1379 /* -------------------------------------------------------------------- */
1380 bool nRetResult = true;
1381
1382 switch (psDBF->pachFieldType[iField]) {
1383 case 'D':
1384 case 'N':
1385 case 'F': {
1386 int nWidth = psDBF->panFieldSize[iField];
1387
1388 char szSField[XBASE_FLD_MAX_WIDTH + 1];
1389 if (STATIC_CAST(int, sizeof(szSField)) - 2 < nWidth)
1390 nWidth = sizeof(szSField) - 2;
1391
1392 char szFormat[20];
1393 snprintf(szFormat, sizeof(szFormat), "%%%d.%df", nWidth,
1394 psDBF->panFieldDecimals[iField]);
1395 const double value = *STATIC_CAST(double *, pValue);
1396 CPLsnprintf(szSField, sizeof(szSField), szFormat, value);
1397 szSField[sizeof(szSField) - 1] = '\0';
1398 if (STATIC_CAST(int, strlen(szSField)) > psDBF->panFieldSize[iField]) {
1399 szSField[psDBF->panFieldSize[iField]] = '\0';
1400 nRetResult = psDBF->sHooks.Atof(szSField) == value;
1401 }
1402 memcpy(
1403 REINTERPRET_CAST(char *, pabyRec + psDBF->panFieldOffset[iField]),
1405 break;
1406 }
1407
1408 case 'L':
1409 if (psDBF->panFieldSize[iField] >= 1 &&
1410 (*STATIC_CAST(char *, pValue) == 'F' ||
1411 *STATIC_CAST(char *, pValue) == 'T')) {
1412 *(pabyRec + psDBF->panFieldOffset[iField]) =
1413 *STATIC_CAST(char *, pValue);
1414 }
1415 else {
1416 nRetResult = false;
1417 }
1418 break;
1419
1420 default: {
1421 int j;
1422 if (STATIC_CAST(int, strlen(STATIC_CAST(char *, pValue))) >
1423 psDBF->panFieldSize[iField]) {
1424 j = psDBF->panFieldSize[iField];
1425 nRetResult = false;
1426 }
1427 else {
1428 memset(pabyRec + psDBF->panFieldOffset[iField], ' ',
1429 psDBF->panFieldSize[iField]);
1430 j = STATIC_CAST(int, strlen(STATIC_CAST(char *, pValue)));
1431 }
1432
1433 strncpy(
1434 REINTERPRET_CAST(char *, pabyRec + psDBF->panFieldOffset[iField]),
1435 STATIC_CAST(const char *, pValue), j);
1436 break;
1437 }
1438 }
1439
1440 return nRetResult;
1441}
1442
1443/************************************************************************/
1444/* DBFWriteAttributeDirectly() */
1445/* */
1446/* Write an attribute record to the file, but without any */
1447/* reformatting based on type. The provided buffer is written */
1448/* as is to the field position in the record. */
1449/************************************************************************/
1450
1452 int iField, const void *pValue)
1453{
1454 /* -------------------------------------------------------------------- */
1455 /* Is this a valid record? */
1456 /* -------------------------------------------------------------------- */
1457 if (hEntity < 0 || hEntity > psDBF->nRecords)
1458 return (FALSE);
1459
1460 if (psDBF->bNoHeader)
1461 DBFWriteHeader(psDBF);
1462
1463 /* -------------------------------------------------------------------- */
1464 /* Is this a brand new record? */
1465 /* -------------------------------------------------------------------- */
1466 if (hEntity == psDBF->nRecords) {
1467 if (!DBFFlushRecord(psDBF))
1468 return FALSE;
1469
1470 psDBF->nRecords++;
1471 for (int i = 0; i < psDBF->nRecordLength; i++)
1472 psDBF->pszCurrentRecord[i] = ' ';
1473
1474 psDBF->nCurrentRecord = hEntity;
1475 }
1476
1477 /* -------------------------------------------------------------------- */
1478 /* Is this an existing record, but different than the last one */
1479 /* we accessed? */
1480 /* -------------------------------------------------------------------- */
1481 if (!DBFLoadRecord(psDBF, hEntity))
1482 return FALSE;
1483
1484 if (iField >= 0) {
1485 unsigned char *pabyRec =
1486 REINTERPRET_CAST(unsigned char *, psDBF->pszCurrentRecord);
1487
1488 /* --------------------------------------------------------------------
1489 */
1490 /* Assign all the record fields. */
1491 /* --------------------------------------------------------------------
1492 */
1493 int j;
1494 if (STATIC_CAST(int, strlen(STATIC_CAST(const char *, pValue))) >
1495 psDBF->panFieldSize[iField])
1496 j = psDBF->panFieldSize[iField];
1497 else {
1498 memset(pabyRec + psDBF->panFieldOffset[iField], ' ',
1499 psDBF->panFieldSize[iField]);
1500 j = STATIC_CAST(int, strlen(STATIC_CAST(const char *, pValue)));
1501 }
1502
1503 memcpy(
1504 REINTERPRET_CAST(char *, pabyRec + psDBF->panFieldOffset[iField]),
1505 STATIC_CAST(const char *, pValue), j);
1506 }
1507
1508 psDBF->bCurrentRecordModified = TRUE;
1509 psDBF->bUpdated = TRUE;
1510
1511 return (TRUE);
1512}
1513
1514/************************************************************************/
1515/* DBFWriteDoubleAttribute() */
1516/* */
1517/* Write a double attribute. */
1518/************************************************************************/
1519
1521 int iField, double dValue)
1522{
1523 return (DBFWriteAttribute(psDBF, iRecord, iField,
1524 STATIC_CAST(void *, &dValue)));
1525}
1526
1527/************************************************************************/
1528/* DBFWriteIntegerAttribute() */
1529/* */
1530/* Write an integer attribute. */
1531/************************************************************************/
1532
1534 int iField, int nValue)
1535{
1536 double dValue = nValue;
1537
1538 return (DBFWriteAttribute(psDBF, iRecord, iField,
1539 STATIC_CAST(void *, &dValue)));
1540}
1541
1542/************************************************************************/
1543/* DBFWriteStringAttribute() */
1544/* */
1545/* Write a string attribute. */
1546/************************************************************************/
1547
1549 int iField, const char *pszValue)
1550{
1551 return (
1552 DBFWriteAttribute(psDBF, iRecord, iField,
1553 STATIC_CAST(void *, CONST_CAST(char *, pszValue))));
1554}
1555
1556/************************************************************************/
1557/* DBFWriteNULLAttribute() */
1558/* */
1559/* Write a NULL attribute. */
1560/************************************************************************/
1561
1563{
1564 return (DBFWriteAttribute(psDBF, iRecord, iField, SHPLIB_NULLPTR));
1565}
1566
1567/************************************************************************/
1568/* DBFWriteLogicalAttribute() */
1569/* */
1570/* Write a logical attribute. */
1571/************************************************************************/
1572
1574 int iField, const char lValue)
1575{
1576 return (
1577 DBFWriteAttribute(psDBF, iRecord, iField,
1578 STATIC_CAST(void *, CONST_CAST(char *, &lValue))));
1579}
1580
1581/************************************************************************/
1582/* DBFWriteDateAttribute() */
1583/* */
1584/* Write a date attribute. */
1585/************************************************************************/
1586
1588 const SHPDate *lValue)
1589{
1590 if (SHPLIB_NULLPTR == lValue)
1591 return false;
1592 /* check for supported digit range, but do not check for valid date */
1593 if (lValue->year < 0 || lValue->year > 9999)
1594 return false;
1595 if (lValue->month < 0 || lValue->month > 99)
1596 return false;
1597 if (lValue->day < 0 || lValue->day > 99)
1598 return false;
1599 char dateValue[9]; /* "yyyyMMdd\0" */
1600 snprintf(dateValue, sizeof(dateValue), "%04d%02d%02d", lValue->year,
1601 lValue->month, lValue->day);
1603}
1604
1605/************************************************************************/
1606/* DBFWriteTuple() */
1607/* */
1608/* Write an attribute record to the file. */
1609/************************************************************************/
1610
1612 const void *pRawTuple)
1613{
1614 /* -------------------------------------------------------------------- */
1615 /* Is this a valid record? */
1616 /* -------------------------------------------------------------------- */
1617 if (hEntity < 0 || hEntity > psDBF->nRecords)
1618 return (FALSE);
1619
1620 if (psDBF->bNoHeader)
1621 DBFWriteHeader(psDBF);
1622
1623 /* -------------------------------------------------------------------- */
1624 /* Is this a brand new record? */
1625 /* -------------------------------------------------------------------- */
1626 if (hEntity == psDBF->nRecords) {
1627 if (!DBFFlushRecord(psDBF))
1628 return FALSE;
1629
1630 psDBF->nRecords++;
1631 for (int i = 0; i < psDBF->nRecordLength; i++)
1632 psDBF->pszCurrentRecord[i] = ' ';
1633
1634 psDBF->nCurrentRecord = hEntity;
1635 }
1636
1637 /* -------------------------------------------------------------------- */
1638 /* Is this an existing record, but different than the last one */
1639 /* we accessed? */
1640 /* -------------------------------------------------------------------- */
1641 if (!DBFLoadRecord(psDBF, hEntity))
1642 return FALSE;
1643
1644 unsigned char *pabyRec =
1645 REINTERPRET_CAST(unsigned char *, psDBF->pszCurrentRecord);
1646
1647 memcpy(pabyRec, pRawTuple, psDBF->nRecordLength);
1648
1649 psDBF->bCurrentRecordModified = TRUE;
1650 psDBF->bUpdated = TRUE;
1651
1652 return (TRUE);
1653}
1654
1655/************************************************************************/
1656/* DBFReadTuple() */
1657/* */
1658/* Read a complete record. Note that the result is only valid */
1659/* till the next record read for any reason. */
1660/************************************************************************/
1661
1663{
1664 if (hEntity < 0 || hEntity >= psDBF->nRecords)
1665 return SHPLIB_NULLPTR;
1666
1667 if (!DBFLoadRecord(psDBF, hEntity))
1668 return SHPLIB_NULLPTR;
1669
1670 return STATIC_CAST(const char *, psDBF->pszCurrentRecord);
1671}
1672
1673/************************************************************************/
1674/* DBFCloneEmpty() */
1675/* */
1676/* Create a new .dbf file with same code page and field */
1677/* definitions as the given handle. */
1678/************************************************************************/
1679
1681 const char *pszFilename)
1682{
1684 DBFCreateLL(pszFilename, psDBF->pszCodePage, &psDBF->sHooks);
1685 if (newDBF == SHPLIB_NULLPTR)
1686 return SHPLIB_NULLPTR;
1687
1688 newDBF->nFields = psDBF->nFields;
1689 newDBF->nRecordLength = psDBF->nRecordLength;
1690 newDBF->nHeaderLength = psDBF->nHeaderLength;
1691
1692 if (psDBF->pszHeader) {
1693 newDBF->pszHeader =
1694 STATIC_CAST(char *, malloc(XBASE_FLDHDR_SZ * psDBF->nFields));
1695 memcpy(newDBF->pszHeader, psDBF->pszHeader,
1696 XBASE_FLDHDR_SZ * psDBF->nFields);
1697 }
1698
1699 newDBF->panFieldOffset =
1700 STATIC_CAST(int *, malloc(sizeof(int) * psDBF->nFields));
1701 memcpy(newDBF->panFieldOffset, psDBF->panFieldOffset,
1702 sizeof(int) * psDBF->nFields);
1703 newDBF->panFieldSize =
1704 STATIC_CAST(int *, malloc(sizeof(int) * psDBF->nFields));
1705 memcpy(newDBF->panFieldSize, psDBF->panFieldSize,
1706 sizeof(int) * psDBF->nFields);
1707 newDBF->panFieldDecimals =
1708 STATIC_CAST(int *, malloc(sizeof(int) * psDBF->nFields));
1709 memcpy(newDBF->panFieldDecimals, psDBF->panFieldDecimals,
1710 sizeof(int) * psDBF->nFields);
1711 newDBF->pachFieldType =
1712 STATIC_CAST(char *, malloc(sizeof(char) * psDBF->nFields));
1713 memcpy(newDBF->pachFieldType, psDBF->pachFieldType,
1714 sizeof(char) * psDBF->nFields);
1715
1716 newDBF->bNoHeader = TRUE;
1717 newDBF->bUpdated = TRUE;
1718 newDBF->bWriteEndOfFileChar = psDBF->bWriteEndOfFileChar;
1719
1720 DBFWriteHeader(newDBF);
1722
1723 newDBF = DBFOpen(pszFilename, "rb+");
1724 newDBF->bWriteEndOfFileChar = psDBF->bWriteEndOfFileChar;
1725
1726 return (newDBF);
1727}
1728
1729/************************************************************************/
1730/* DBFGetNativeFieldType() */
1731/* */
1732/* Return the DBase field type for the specified field. */
1733/* */
1734/* Value can be one of: 'C' (String), 'D' (Date), 'F' (Float), */
1735/* 'N' (Numeric, with or without decimal), */
1736/* 'L' (Logical), */
1737/* 'M' (Memo: 10 digits .DBT block ptr) */
1738/************************************************************************/
1739
1741{
1742 if (iField >= 0 && iField < psDBF->nFields)
1743 return psDBF->pachFieldType[iField];
1744
1745 return ' ';
1746}
1747
1748/************************************************************************/
1749/* DBFGetFieldIndex() */
1750/* */
1751/* Get the index number for a field in a .dbf file. */
1752/* */
1753/* Contributed by Jim Matthews. */
1754/************************************************************************/
1755
1757 const char *pszFieldName)
1758{
1759 char name[XBASE_FLDNAME_LEN_READ + 1];
1760
1761 for (int i = 0; i < DBFGetFieldCount(psDBF); i++) {
1764 return (i);
1765 }
1766 return (-1);
1767}
1768
1769/************************************************************************/
1770/* DBFIsRecordDeleted() */
1771/* */
1772/* Returns TRUE if the indicated record is deleted, otherwise */
1773/* it returns FALSE. */
1774/************************************************************************/
1775
1777{
1778 /* -------------------------------------------------------------------- */
1779 /* Verify selection. */
1780 /* -------------------------------------------------------------------- */
1781 if (iShape < 0 || iShape >= psDBF->nRecords)
1782 return TRUE;
1783
1784 /* -------------------------------------------------------------------- */
1785 /* Have we read the record? */
1786 /* -------------------------------------------------------------------- */
1787 if (!DBFLoadRecord(psDBF, iShape))
1788 return FALSE;
1789
1790 /* -------------------------------------------------------------------- */
1791 /* '*' means deleted. */
1792 /* -------------------------------------------------------------------- */
1793 return psDBF->pszCurrentRecord[0] == '*';
1794}
1795
1796/************************************************************************/
1797/* DBFMarkRecordDeleted() */
1798/************************************************************************/
1799
1801 int bIsDeleted)
1802{
1803 /* -------------------------------------------------------------------- */
1804 /* Verify selection. */
1805 /* -------------------------------------------------------------------- */
1806 if (iShape < 0 || iShape >= psDBF->nRecords)
1807 return FALSE;
1808
1809 /* -------------------------------------------------------------------- */
1810 /* Is this an existing record, but different than the last one */
1811 /* we accessed? */
1812 /* -------------------------------------------------------------------- */
1813 if (!DBFLoadRecord(psDBF, iShape))
1814 return FALSE;
1815
1816 /* -------------------------------------------------------------------- */
1817 /* Assign value, marking record as dirty if it changes. */
1818 /* -------------------------------------------------------------------- */
1819 const char chNewFlag = bIsDeleted ? '*' : ' ';
1820
1821 if (psDBF->pszCurrentRecord[0] != chNewFlag) {
1822 psDBF->bCurrentRecordModified = TRUE;
1823 psDBF->bUpdated = TRUE;
1824 psDBF->pszCurrentRecord[0] = chNewFlag;
1825 }
1826
1827 return TRUE;
1828}
1829
1830/************************************************************************/
1831/* DBFGetCodePage */
1832/************************************************************************/
1833
1835{
1836 if (psDBF == SHPLIB_NULLPTR)
1837 return SHPLIB_NULLPTR;
1838 return psDBF->pszCodePage;
1839}
1840
1841/************************************************************************/
1842/* DBFDeleteField() */
1843/* */
1844/* Remove a field from a .dbf file */
1845/************************************************************************/
1846
1848{
1849 if (iField < 0 || iField >= psDBF->nFields)
1850 return FALSE;
1851
1852 /* make sure that everything is written in .dbf */
1853 if (!DBFFlushRecord(psDBF))
1854 return FALSE;
1855
1856 /* get information about field to be deleted */
1857 int nOldRecordLength = psDBF->nRecordLength;
1858 int nOldHeaderLength = psDBF->nHeaderLength;
1859 int nDeletedFieldOffset = psDBF->panFieldOffset[iField];
1860 int nDeletedFieldSize = psDBF->panFieldSize[iField];
1861
1862 /* update fields info */
1863 for (int i = iField + 1; i < psDBF->nFields; i++) {
1864 psDBF->panFieldOffset[i - 1] =
1865 psDBF->panFieldOffset[i] - nDeletedFieldSize;
1866 psDBF->panFieldSize[i - 1] = psDBF->panFieldSize[i];
1867 psDBF->panFieldDecimals[i - 1] = psDBF->panFieldDecimals[i];
1868 psDBF->pachFieldType[i - 1] = psDBF->pachFieldType[i];
1869 }
1870
1871 /* resize fields arrays */
1872 psDBF->nFields--;
1873
1874 psDBF->panFieldOffset = STATIC_CAST(
1875 int *, realloc(psDBF->panFieldOffset, sizeof(int) * psDBF->nFields));
1876
1877 psDBF->panFieldSize = STATIC_CAST(
1878 int *, realloc(psDBF->panFieldSize, sizeof(int) * psDBF->nFields));
1879
1880 psDBF->panFieldDecimals = STATIC_CAST(
1881 int *, realloc(psDBF->panFieldDecimals, sizeof(int) * psDBF->nFields));
1882
1883 psDBF->pachFieldType = STATIC_CAST(
1884 char *, realloc(psDBF->pachFieldType, sizeof(char) * psDBF->nFields));
1885
1886 /* update header information */
1887 psDBF->nHeaderLength -= XBASE_FLDHDR_SZ;
1888 psDBF->nRecordLength -= nDeletedFieldSize;
1889
1890 /* overwrite field information in header */
1891 memmove(psDBF->pszHeader + iField * XBASE_FLDHDR_SZ,
1892 psDBF->pszHeader + (iField + 1) * XBASE_FLDHDR_SZ,
1893 sizeof(char) * (psDBF->nFields - iField) * XBASE_FLDHDR_SZ);
1894
1895 psDBF->pszHeader = STATIC_CAST(
1896 char *, realloc(psDBF->pszHeader, psDBF->nFields * XBASE_FLDHDR_SZ));
1897
1898 /* update size of current record appropriately */
1899 psDBF->pszCurrentRecord = STATIC_CAST(
1900 char *, realloc(psDBF->pszCurrentRecord, psDBF->nRecordLength));
1901
1902 /* we're done if we're dealing with not yet created .dbf */
1903 if (psDBF->bNoHeader && psDBF->nRecords == 0)
1904 return TRUE;
1905
1906 /* force update of header with new header and record length */
1907 psDBF->bNoHeader = TRUE;
1909
1910 /* alloc record */
1911 char *pszRecord =
1912 STATIC_CAST(char *, malloc(sizeof(char) * nOldRecordLength));
1913
1914 /* shift records to their new positions */
1915 for (int iRecord = 0; iRecord < psDBF->nRecords; iRecord++) {
1919
1920 /* load record */
1921 psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
1922 if (psDBF->sHooks.FRead(pszRecord, nOldRecordLength, 1, psDBF->fp) !=
1923 1) {
1924 free(pszRecord);
1925 return FALSE;
1926 }
1927
1928 nRecordOffset = psDBF->nRecordLength * STATIC_CAST(SAOffset, iRecord) +
1929 psDBF->nHeaderLength;
1930
1931 /* move record in two steps */
1932 psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
1933 psDBF->sHooks.FWrite(pszRecord, nDeletedFieldOffset, 1, psDBF->fp);
1934 psDBF->sHooks.FWrite(
1937 psDBF->fp);
1938 }
1939
1940 if (psDBF->bWriteEndOfFileChar) {
1943 psDBF->nRecordLength * STATIC_CAST(SAOffset, psDBF->nRecords) +
1944 psDBF->nHeaderLength;
1945
1946 psDBF->sHooks.FSeek(psDBF->fp, nEOFOffset, 0);
1947 psDBF->sHooks.FWrite(&ch, 1, 1, psDBF->fp);
1948 }
1949
1950 /* TODO: truncate file */
1951
1952 /* free record */
1953 free(pszRecord);
1954
1955 psDBF->nCurrentRecord = -1;
1956 psDBF->bCurrentRecordModified = FALSE;
1957 psDBF->bUpdated = TRUE;
1958
1959 return TRUE;
1960}
1961
1962/************************************************************************/
1963/* DBFReorderFields() */
1964/* */
1965/* Reorder the fields of a .dbf file */
1966/* */
1967/* panMap must be exactly psDBF->nFields long and be a permutation */
1968/* of [0, psDBF->nFields-1]. This assumption will not be asserted in the*/
1969/* code of DBFReorderFields. */
1970/************************************************************************/
1971
1973{
1974 if (psDBF->nFields == 0)
1975 return TRUE;
1976
1977 /* make sure that everything is written in .dbf */
1978 if (!DBFFlushRecord(psDBF))
1979 return FALSE;
1980
1981 /* a simple malloc() would be enough, but calloc() helps clang static
1982 * analyzer */
1983 int *panFieldOffsetNew =
1984 STATIC_CAST(int *, calloc(psDBF->nFields, sizeof(int)));
1985 int *panFieldSizeNew =
1986 STATIC_CAST(int *, calloc(psDBF->nFields, sizeof(int)));
1987 int *panFieldDecimalsNew =
1988 STATIC_CAST(int *, calloc(psDBF->nFields, sizeof(int)));
1989 char *pachFieldTypeNew =
1990 STATIC_CAST(char *, calloc(psDBF->nFields, sizeof(char)));
1991 char *pszHeaderNew = STATIC_CAST(
1992 char *, malloc(sizeof(char) * XBASE_FLDHDR_SZ * psDBF->nFields));
1993 char *pszRecord = SHPLIB_NULLPTR;
1995 if (!(psDBF->bNoHeader && psDBF->nRecords == 0)) {
1996 /* alloc record */
1997 pszRecord =
1998 STATIC_CAST(char *, malloc(sizeof(char) * psDBF->nRecordLength));
1999 pszRecordNew =
2000 STATIC_CAST(char *, malloc(sizeof(char) * psDBF->nRecordLength));
2001 }
2004 (!(psDBF->bNoHeader && psDBF->nRecords == 0) &&
2005 (!pszRecord || !pszRecordNew))) {
2011 free(pszRecord);
2013 psDBF->sHooks.Error("Out of memory");
2014 return FALSE;
2015 }
2016
2017 /* shuffle fields definitions */
2018 for (int i = 0; i < psDBF->nFields; i++) {
2019 panFieldSizeNew[i] = psDBF->panFieldSize[panMap[i]];
2020 panFieldDecimalsNew[i] = psDBF->panFieldDecimals[panMap[i]];
2021 pachFieldTypeNew[i] = psDBF->pachFieldType[panMap[i]];
2023 psDBF->pszHeader + panMap[i] * XBASE_FLDHDR_SZ, XBASE_FLDHDR_SZ);
2024 }
2025 panFieldOffsetNew[0] = 1;
2026 for (int i = 1; i < psDBF->nFields; i++) {
2028 panFieldOffsetNew[i - 1] + panFieldSizeNew[i - 1];
2029 }
2030
2031 free(psDBF->pszHeader);
2032 psDBF->pszHeader = pszHeaderNew;
2033
2034 bool errorAbort = false;
2035
2036 /* we're done if we're dealing with not yet created .dbf */
2037 if (!(psDBF->bNoHeader && psDBF->nRecords == 0)) {
2038 /* force update of header with new header and record length */
2039 psDBF->bNoHeader = TRUE;
2041
2042 /* shuffle fields in records */
2043 for (int iRecord = 0; iRecord < psDBF->nRecords; iRecord++) {
2044 const SAOffset nRecordOffset =
2045 psDBF->nRecordLength * STATIC_CAST(SAOffset, iRecord) +
2046 psDBF->nHeaderLength;
2047
2048 /* load record */
2049 psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
2050 if (psDBF->sHooks.FRead(pszRecord, psDBF->nRecordLength, 1,
2051 psDBF->fp) != 1) {
2052 errorAbort = true;
2053 break;
2054 }
2055
2056 pszRecordNew[0] = pszRecord[0];
2057
2058 for (int i = 0; i < psDBF->nFields; i++) {
2060 pszRecord + psDBF->panFieldOffset[panMap[i]],
2061 psDBF->panFieldSize[panMap[i]]);
2062 }
2063
2064 /* write record */
2065 psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
2066 psDBF->sHooks.FWrite(pszRecordNew, psDBF->nRecordLength, 1,
2067 psDBF->fp);
2068 }
2069 }
2070
2071 /* free record */
2072 free(pszRecord);
2074
2075 if (errorAbort) {
2080 psDBF->nCurrentRecord = -1;
2081 psDBF->bCurrentRecordModified = FALSE;
2082 psDBF->bUpdated = FALSE;
2083 return FALSE;
2084 }
2085
2086 free(psDBF->panFieldOffset);
2087 free(psDBF->panFieldSize);
2088 free(psDBF->panFieldDecimals);
2089 free(psDBF->pachFieldType);
2090
2091 psDBF->panFieldOffset = panFieldOffsetNew;
2092 psDBF->panFieldSize = panFieldSizeNew;
2093 psDBF->panFieldDecimals = panFieldDecimalsNew;
2094 psDBF->pachFieldType = pachFieldTypeNew;
2095
2096 psDBF->nCurrentRecord = -1;
2097 psDBF->bCurrentRecordModified = FALSE;
2098 psDBF->bUpdated = TRUE;
2099
2100 return TRUE;
2101}
2102
2103/************************************************************************/
2104/* DBFAlterFieldDefn() */
2105/* */
2106/* Alter a field definition in a .dbf file */
2107/************************************************************************/
2108
2110 const char *pszFieldName, char chType,
2111 int nWidth, int nDecimals)
2112{
2113 if (iField < 0 || iField >= psDBF->nFields)
2114 return FALSE;
2115
2116 /* make sure that everything is written in .dbf */
2117 if (!DBFFlushRecord(psDBF))
2118 return FALSE;
2119
2120 const char chFieldFill = DBFGetNullCharacter(chType);
2121
2122 const char chOldType = psDBF->pachFieldType[iField];
2123 const int nOffset = psDBF->panFieldOffset[iField];
2124 const int nOldWidth = psDBF->panFieldSize[iField];
2125 const int nOldRecordLength = psDBF->nRecordLength;
2126
2127 /* -------------------------------------------------------------------- */
2128 /* Do some checking to ensure we can add records to this file. */
2129 /* -------------------------------------------------------------------- */
2130 if (nWidth < 1)
2131 return -1;
2132
2135
2136 char *pszRecord = STATIC_CAST(
2137 char *, malloc(nOldRecordLength +
2138 ((nWidth > nOldWidth) ? nWidth - nOldWidth : 0)));
2139 char *pszOldField = STATIC_CAST(char *, malloc(nOldWidth + 1));
2140 if (!pszRecord || !pszOldField) {
2141 free(pszRecord);
2143 return FALSE;
2144 }
2145
2146 if (nWidth != nOldWidth) {
2148 char *, realloc(psDBF->pszCurrentRecord,
2149 psDBF->nRecordLength + nWidth - nOldWidth));
2150 if (!pszCurrentRecordNew) {
2151 free(pszRecord);
2153 return FALSE;
2154 }
2155 psDBF->pszCurrentRecord = pszCurrentRecordNew;
2156 }
2157
2158 /* -------------------------------------------------------------------- */
2159 /* Assign the new field information fields. */
2160 /* -------------------------------------------------------------------- */
2161 psDBF->panFieldSize[iField] = nWidth;
2162 psDBF->panFieldDecimals[iField] = nDecimals;
2163 psDBF->pachFieldType[iField] = chType;
2164
2165 /* -------------------------------------------------------------------- */
2166 /* Update the header information. */
2167 /* -------------------------------------------------------------------- */
2168 char *pszFInfo = psDBF->pszHeader + XBASE_FLDHDR_SZ * iField;
2169
2170 for (int i = 0; i < XBASE_FLDHDR_SZ; i++)
2171 pszFInfo[i] = '\0';
2172
2174
2175 pszFInfo[11] = psDBF->pachFieldType[iField];
2176
2177 if (chType == 'C') {
2178 pszFInfo[16] = STATIC_CAST(unsigned char, nWidth % 256);
2179 pszFInfo[17] = STATIC_CAST(unsigned char, nWidth / 256);
2180 }
2181 else {
2182 pszFInfo[16] = STATIC_CAST(unsigned char, nWidth);
2183 pszFInfo[17] = STATIC_CAST(unsigned char, nDecimals);
2184 }
2185
2186 /* -------------------------------------------------------------------- */
2187 /* Update offsets */
2188 /* -------------------------------------------------------------------- */
2189 if (nWidth != nOldWidth) {
2190 for (int i = iField + 1; i < psDBF->nFields; i++)
2191 psDBF->panFieldOffset[i] += nWidth - nOldWidth;
2192 psDBF->nRecordLength += nWidth - nOldWidth;
2193 }
2194
2195 /* we're done if we're dealing with not yet created .dbf */
2196 if (psDBF->bNoHeader && psDBF->nRecords == 0) {
2197 free(pszRecord);
2199 return TRUE;
2200 }
2201
2202 /* force update of header with new header and record length */
2203 psDBF->bNoHeader = TRUE;
2205
2206 bool errorAbort = false;
2207
2208 if (nWidth < nOldWidth || (nWidth == nOldWidth && chType != chOldType)) {
2210
2211 /* move records to their new positions */
2212 for (int iRecord = 0; iRecord < psDBF->nRecords; iRecord++) {
2215 psDBF->nHeaderLength;
2216
2217 /* load record */
2218 psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
2219 if (psDBF->sHooks.FRead(pszRecord, nOldRecordLength, 1,
2220 psDBF->fp) != 1) {
2221 errorAbort = true;
2222 break;
2223 }
2224
2226 const bool bIsNULL =
2227 DBFIsValueNULL(chOldType, pszOldField, nOldWidth);
2228
2229 if (nWidth != nOldWidth) {
2230 if ((chOldType == 'N' || chOldType == 'F' ||
2231 chOldType == 'D') &&
2232 pszOldField[0] == ' ') {
2233 /* Strip leading spaces when truncating a numeric field */
2236 }
2241 }
2242 }
2243
2244 /* Convert null value to the appropriate value of the new type */
2245 if (bIsNULL) {
2247 }
2248
2250 psDBF->nRecordLength * STATIC_CAST(SAOffset, iRecord) +
2251 psDBF->nHeaderLength;
2252
2253 /* write record */
2254 psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
2255 psDBF->sHooks.FWrite(pszRecord, psDBF->nRecordLength, 1, psDBF->fp);
2256 }
2257
2258 if (!errorAbort && psDBF->bWriteEndOfFileChar) {
2260
2262 psDBF->nRecordLength * STATIC_CAST(SAOffset, psDBF->nRecords) +
2263 psDBF->nHeaderLength;
2264
2265 psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
2266 psDBF->sHooks.FWrite(&ch, 1, 1, psDBF->fp);
2267 }
2268 /* TODO: truncate file */
2269 }
2270 else if (nWidth > nOldWidth) {
2272
2273 /* move records to their new positions */
2274 for (int iRecord = psDBF->nRecords - 1; iRecord >= 0; iRecord--) {
2277 psDBF->nHeaderLength;
2278
2279 /* load record */
2280 psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
2281 if (psDBF->sHooks.FRead(pszRecord, nOldRecordLength, 1,
2282 psDBF->fp) != 1) {
2283 errorAbort = true;
2284 break;
2285 }
2286
2288 const bool bIsNULL =
2289 DBFIsValueNULL(chOldType, pszOldField, nOldWidth);
2290
2295 }
2296
2297 /* Convert null value to the appropriate value of the new type */
2298 if (bIsNULL) {
2300 }
2301 else {
2302 if ((chOldType == 'N' || chOldType == 'F')) {
2303 /* Add leading spaces when expanding a numeric field */
2307 }
2308 else {
2309 /* Add trailing spaces */
2311 nWidth - nOldWidth);
2312 }
2313 }
2314
2316 psDBF->nRecordLength * STATIC_CAST(SAOffset, iRecord) +
2317 psDBF->nHeaderLength;
2318
2319 /* write record */
2320 psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
2321 psDBF->sHooks.FWrite(pszRecord, psDBF->nRecordLength, 1, psDBF->fp);
2322 }
2323
2324 if (!errorAbort && psDBF->bWriteEndOfFileChar) {
2325 const char ch = END_OF_FILE_CHARACTER;
2326
2328 psDBF->nRecordLength * STATIC_CAST(SAOffset, psDBF->nRecords) +
2329 psDBF->nHeaderLength;
2330
2331 psDBF->sHooks.FSeek(psDBF->fp, nRecordOffset, 0);
2332 psDBF->sHooks.FWrite(&ch, 1, 1, psDBF->fp);
2333 }
2334 }
2335
2336 free(pszRecord);
2338
2339 if (errorAbort) {
2340 psDBF->nCurrentRecord = -1;
2341 psDBF->bCurrentRecordModified = TRUE;
2342 psDBF->bUpdated = FALSE;
2343
2344 return FALSE;
2345 }
2346 psDBF->nCurrentRecord = -1;
2347 psDBF->bCurrentRecordModified = FALSE;
2348 psDBF->bUpdated = TRUE;
2349
2350 return TRUE;
2351}
2352
2353/************************************************************************/
2354/* DBFSetWriteEndOfFileChar() */
2355/************************************************************************/
2356
2358{
2359 psDBF->bWriteEndOfFileChar = bWriteFlag;
2360}
DBFHandle DBFCloneEmpty(const DBFHandle psDBF, const char *pszFilename)
Definition dbfopen.c:1680
const char * DBFGetCodePage(const DBFHandle psDBF)
Definition dbfopen.c:1834
int DBFWriteIntegerAttribute(DBFHandle psDBF, int iRecord, int iField, int nValue)
Definition dbfopen.c:1533
const char * DBFReadTuple(DBFHandle psDBF, int hEntity)
Definition dbfopen.c:1662
DBFHandle DBFCreate(const char *pszFilename)
Definition dbfopen.c:616
int DBFWriteStringAttribute(DBFHandle psDBF, int iRecord, int iField, const char *pszValue)
Definition dbfopen.c:1548
#define STRCASECMP(a, b)
Definition dbfopen.c:36
int DBFGetFieldCount(const DBFHandle psDBF)
Definition dbfopen.c:1253
int DBFMarkRecordDeleted(DBFHandle psDBF, int iShape, int bIsDeleted)
Definition dbfopen.c:1800
int DBFWriteLogicalAttribute(DBFHandle psDBF, int iRecord, int iField, const char lValue)
Definition dbfopen.c:1573
DBFHandle DBFOpenLL(const char *pszFilename, const char *pszAccess, const SAHooks *psHooks)
Definition dbfopen.c:331
int DBFDeleteField(DBFHandle psDBF, int iField)
Definition dbfopen.c:1847
void DBFClose(DBFHandle psDBF)
Definition dbfopen.c:568
int DBFAddField(DBFHandle psDBF, const char *pszFieldName, DBFFieldType eType, int nWidth, int nDecimals)
Definition dbfopen.c:751
int DBFAlterFieldDefn(DBFHandle psDBF, int iField, const char *pszFieldName, char chType, int nWidth, int nDecimals)
Definition dbfopen.c:2109
int DBFWriteNULLAttribute(DBFHandle psDBF, int iRecord, int iField)
Definition dbfopen.c:1562
SHPDate DBFReadDateAttribute(DBFHandle psDBF, int iRecord, int iField)
Definition dbfopen.c:1146
int DBFIsRecordDeleted(const DBFHandle psDBF, int iShape)
Definition dbfopen.c:1776
void DBFSetWriteEndOfFileChar(DBFHandle psDBF, int bWriteFlag)
Definition dbfopen.c:2357
int DBFReadIntegerAttribute(DBFHandle psDBF, int iRecord, int iField)
Definition dbfopen.c:1084
DBFHandle DBFCreateEx(const char *pszFilename, const char *pszCodePage)
Definition dbfopen.c:627
int DBFIsAttributeNULL(const DBFHandle psDBF, int iRecord, int iField)
Definition dbfopen.c:1235
DBFFieldType DBFGetFieldInfo(const DBFHandle psDBF, int iField, char *pszFieldName, int *pnWidth, int *pnDecimals)
Definition dbfopen.c:1277
int DBFReorderFields(DBFHandle psDBF, const int *panMap)
Definition dbfopen.c:1972
const char * DBFReadLogicalAttribute(DBFHandle psDBF, int iRecord, int iField)
Definition dbfopen.c:1134
int DBFWriteDoubleAttribute(DBFHandle psDBF, int iRecord, int iField, double dValue)
Definition dbfopen.c:1520
double DBFReadDoubleAttribute(DBFHandle psDBF, int iRecord, int iField)
Definition dbfopen.c:1102
#define HEADER_RECORD_TERMINATOR
Definition dbfopen.c:62
void DBFSetLastModifiedDate(DBFHandle psDBF, int nYYSince1900, int nMM, int nDD)
Definition dbfopen.c:286
void DBFUpdateHeader(DBFHandle psDBF)
Definition dbfopen.c:252
char DBFGetNativeFieldType(const DBFHandle psDBF, int iField)
Definition dbfopen.c:1740
DBFHandle DBFCreateLL(const char *pszFilename, const char *pszCodePage, const SAHooks *psHooks)
Definition dbfopen.c:643
#define XBASE_FILEHDR_SZ
Definition dbfopen.c:60
#define TRUE
Definition dbfopen.c:56
#define FALSE
Definition dbfopen.c:55
int DBFAddNativeFieldType(DBFHandle psDBF, const char *pszFieldName, char chType, int nWidth, int nDecimals)
Definition dbfopen.c:795
#define END_OF_FILE_CHARACTER
Definition dbfopen.c:65
int DBFWriteAttributeDirectly(DBFHandle psDBF, int hEntity, int iField, const void *pValue)
Definition dbfopen.c:1451
int DBFGetRecordCount(const DBFHandle psDBF)
Definition dbfopen.c:1264
const char * DBFReadStringAttribute(DBFHandle psDBF, int iRecord, int iField)
Definition dbfopen.c:1121
int DBFWriteDateAttribute(DBFHandle psDBF, int iRecord, int iField, const SHPDate *lValue)
Definition dbfopen.c:1587
int DBFWriteTuple(DBFHandle psDBF, int hEntity, const void *pRawTuple)
Definition dbfopen.c:1611
DBFHandle DBFOpen(const char *pszFilename, const char *pszAccess)
Definition dbfopen.c:300
int DBFGetFieldIndex(const DBFHandle psDBF, const char *pszFieldName)
Definition dbfopen.c:1756
#define CPLsnprintf
Definition dbfopen.c:51
#define CPL_IGNORE_RET_VAL_INT(x)
Definition dbfopen.c:72
#define assert(condition)
Definition lz4.c:291
const char * name
Definition named_colr.c:6
#define strcpy
Definition parson.c:66
void SASetupDefaultHooks(SAHooks *psHooks)
Definition safileio.c:91
DBFFieldType
Definition shapefil.h:459
@ FTDouble
Definition shapefil.h:462
@ FTString
Definition shapefil.h:460
@ FTInvalid
Definition shapefil.h:465
@ FTLogical
Definition shapefil.h:463
@ FTDate
Definition shapefil.h:464
@ FTInteger
Definition shapefil.h:461
#define XBASE_FLDNAME_LEN_WRITE
Definition shapefil.h:473
#define XBASE_FLDHDR_SZ
Definition shapefil.h:469
#define SHPAPI_CALL
Definition shapefil.h:105
#define XBASE_FLDNAME_LEN_READ
Definition shapefil.h:471
#define SHPAPI_CALL1(x)
Definition shapefil.h:110
#define XBASE_FLD_MAX_WIDTH
Definition shapefil.h:475
#define STATIC_CAST(type, x)
#define REINTERPRET_CAST(type, x)
#define SHPLIB_NULLPTR
#define CONST_CAST(type, x)
void * malloc(unsigned)
void free(void *)
int day
Definition shapefil.h:195
int month
Definition shapefil.h:194
int year
Definition shapefil.h:193