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