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