/src/MapServer/src/mapxbase.c
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * $Id$ |
3 | | * |
4 | | * Project: MapServer |
5 | | * Purpose: .dbf access API. Derived from shapelib, and relicensed with |
6 | | * permission of Frank Warmerdam (shapelib author). |
7 | | * Author: Steve Lime and the MapServer team. |
8 | | * |
9 | | ****************************************************************************** |
10 | | * Copyright (c) 1996-2005 Regents of the University of Minnesota. |
11 | | * |
12 | | * Permission is hereby granted, free of charge, to any person obtaining a |
13 | | * copy of this software and associated documentation files (the "Software"), |
14 | | * to deal in the Software without restriction, including without limitation |
15 | | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
16 | | * and/or sell copies of the Software, and to permit persons to whom the |
17 | | * Software is furnished to do so, subject to the following conditions: |
18 | | * |
19 | | * The above copyright notice and this permission notice shall be included in |
20 | | * all copies of this Software or works derived from this Software. |
21 | | * |
22 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
23 | | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
24 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
25 | | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
26 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
27 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
28 | | * DEALINGS IN THE SOFTWARE. |
29 | | ****************************************************************************/ |
30 | | |
31 | | #include "mapserver.h" |
32 | | #include <stdlib.h> /* for atof() and atoi() */ |
33 | | #include <math.h> |
34 | | |
35 | | #include "cpl_vsi.h" |
36 | | |
37 | 0 | static inline void IGUR_sizet(size_t ignored) { |
38 | 0 | (void)ignored; |
39 | 0 | } /* Ignore GCC Unused Result */ |
40 | | |
41 | | /************************************************************************/ |
42 | | /* SfRealloc() */ |
43 | | /* */ |
44 | | /* A realloc cover function that will access a NULL pointer as */ |
45 | | /* a valid input. */ |
46 | | /************************************************************************/ |
47 | | static void *SfRealloc(void *pMem, int nNewSize) |
48 | | |
49 | 1.33k | { |
50 | 1.33k | return ((void *)realloc(pMem, nNewSize)); |
51 | 1.33k | } |
52 | | |
53 | | /************************************************************************/ |
54 | | /* writeHeader() */ |
55 | | /* */ |
56 | | /* This is called to write out the file header, and field */ |
57 | | /* descriptions before writing any actual data records. This */ |
58 | | /* also computes all the DBFDataSet field offset/size/decimals */ |
59 | | /* and so forth values. */ |
60 | | /************************************************************************/ |
61 | | static void writeHeader(DBFHandle psDBF) |
62 | | |
63 | 0 | { |
64 | 0 | uchar abyHeader[32]; |
65 | 0 | int i; |
66 | |
|
67 | 0 | if (!psDBF->bNoHeader) |
68 | 0 | return; |
69 | | |
70 | 0 | psDBF->bNoHeader = MS_FALSE; |
71 | | |
72 | | /* -------------------------------------------------------------------- */ |
73 | | /* Initialize the file header information. */ |
74 | | /* -------------------------------------------------------------------- */ |
75 | 0 | for (i = 0; i < 32; i++) |
76 | 0 | abyHeader[i] = 0; |
77 | |
|
78 | 0 | abyHeader[0] = 0x03; /* memo field? - just copying */ |
79 | | |
80 | | /* date updated on close, record count preset at zero */ |
81 | |
|
82 | 0 | abyHeader[8] = psDBF->nHeaderLength % 256; |
83 | 0 | abyHeader[9] = psDBF->nHeaderLength / 256; |
84 | |
|
85 | 0 | abyHeader[10] = psDBF->nRecordLength % 256; |
86 | 0 | abyHeader[11] = psDBF->nRecordLength / 256; |
87 | | |
88 | | /* -------------------------------------------------------------------- */ |
89 | | /* Write the initial 32 byte file header, and all the field */ |
90 | | /* descriptions. */ |
91 | | /* -------------------------------------------------------------------- */ |
92 | 0 | VSIFSeekL(psDBF->fp, 0, 0); |
93 | 0 | VSIFWriteL(abyHeader, 32, 1, psDBF->fp); |
94 | 0 | VSIFWriteL(psDBF->pszHeader, 32, psDBF->nFields, psDBF->fp); |
95 | | |
96 | | /* -------------------------------------------------------------------- */ |
97 | | /* Write out the newline character if there is room for it. */ |
98 | | /* -------------------------------------------------------------------- */ |
99 | 0 | if (psDBF->nHeaderLength > 32 * psDBF->nFields + 32) { |
100 | 0 | char cNewline; |
101 | |
|
102 | 0 | cNewline = 0x0d; |
103 | 0 | VSIFWriteL(&cNewline, 1, 1, psDBF->fp); |
104 | 0 | } |
105 | 0 | } |
106 | | |
107 | | /************************************************************************/ |
108 | | /* flushRecord() */ |
109 | | /* */ |
110 | | /* Write out the current record if there is one. */ |
111 | | /************************************************************************/ |
112 | | static void flushRecord(DBFHandle psDBF) |
113 | | |
114 | 1.28k | { |
115 | 1.28k | unsigned int nRecordOffset; |
116 | | |
117 | 1.28k | if (psDBF->bCurrentRecordModified && psDBF->nCurrentRecord > -1) { |
118 | 0 | psDBF->bCurrentRecordModified = MS_FALSE; |
119 | |
|
120 | 0 | nRecordOffset = |
121 | 0 | psDBF->nRecordLength * psDBF->nCurrentRecord + psDBF->nHeaderLength; |
122 | |
|
123 | 0 | VSIFSeekL(psDBF->fp, nRecordOffset, 0); |
124 | 0 | VSIFWriteL(psDBF->pszCurrentRecord, psDBF->nRecordLength, 1, psDBF->fp); |
125 | 0 | } |
126 | 1.28k | } |
127 | | |
128 | 1.38k | DBFHandle msDBFOpenVirtualFile(VSILFILE *fp) { |
129 | 1.38k | DBFHandle psDBF; |
130 | 1.38k | uchar *pabyBuf; |
131 | 1.38k | int nFields, nHeadLen, nRecLen, iField; |
132 | | |
133 | | /* -------------------------------------------------------------------- */ |
134 | | /* Open the file. */ |
135 | | /* -------------------------------------------------------------------- */ |
136 | 1.38k | psDBF = (DBFHandle)calloc(1, sizeof(DBFInfo)); |
137 | 1.38k | MS_CHECK_ALLOC(psDBF, sizeof(DBFInfo), NULL); |
138 | 1.38k | psDBF->fp = fp; |
139 | | |
140 | 1.38k | psDBF->bNoHeader = MS_FALSE; |
141 | 1.38k | psDBF->nCurrentRecord = -1; |
142 | 1.38k | psDBF->bCurrentRecordModified = MS_FALSE; |
143 | | |
144 | 1.38k | psDBF->pszStringField = NULL; |
145 | 1.38k | psDBF->nStringFieldLen = 0; |
146 | | |
147 | 1.38k | psDBF->pszEncoding = NULL; |
148 | | |
149 | | /* -------------------------------------------------------------------- */ |
150 | | /* Read Table Header info */ |
151 | | /* -------------------------------------------------------------------- */ |
152 | 1.38k | pabyBuf = (uchar *)msSmallMalloc(500); |
153 | 1.38k | if (VSIFReadL(pabyBuf, 32, 1, psDBF->fp) != 1) { |
154 | 46 | VSIFCloseL(psDBF->fp); |
155 | 46 | msFree(psDBF); |
156 | 46 | msFree(pabyBuf); |
157 | 46 | return (NULL); |
158 | 46 | } |
159 | | |
160 | 1.34k | if (pabyBuf[7] < 128) |
161 | 1.16k | psDBF->nRecords = pabyBuf[4] + pabyBuf[5] * 256 + pabyBuf[6] * 256 * 256 + |
162 | 1.16k | pabyBuf[7] * 256 * 256 * 256; |
163 | 182 | else |
164 | 182 | psDBF->nRecords = 0; |
165 | | |
166 | 1.34k | psDBF->nHeaderLength = nHeadLen = pabyBuf[8] + pabyBuf[9] * 256; |
167 | 1.34k | psDBF->nRecordLength = nRecLen = pabyBuf[10] + pabyBuf[11] * 256; |
168 | | |
169 | 1.34k | if (nHeadLen <= 32) { |
170 | 10 | VSIFCloseL(psDBF->fp); |
171 | 10 | msFree(psDBF); |
172 | 10 | msFree(pabyBuf); |
173 | 10 | return (NULL); |
174 | 10 | } |
175 | | |
176 | 1.33k | psDBF->nFields = nFields = (nHeadLen - 32) / 32; |
177 | | |
178 | 1.33k | psDBF->pszCurrentRecord = (char *)msSmallMalloc(nRecLen); |
179 | | |
180 | | /* -------------------------------------------------------------------- */ |
181 | | /* Read in Field Definitions */ |
182 | | /* -------------------------------------------------------------------- */ |
183 | 1.33k | pabyBuf = (uchar *)SfRealloc(pabyBuf, nHeadLen); |
184 | 1.33k | psDBF->pszHeader = (char *)pabyBuf; |
185 | | |
186 | 1.33k | VSIFSeekL(psDBF->fp, 32, 0); |
187 | 1.33k | if (VSIFReadL(pabyBuf, nHeadLen - 32, 1, psDBF->fp) != 1) { |
188 | 50 | msFree(psDBF->pszCurrentRecord); |
189 | 50 | VSIFCloseL(psDBF->fp); |
190 | 50 | msFree(psDBF); |
191 | 50 | msFree(pabyBuf); |
192 | 50 | return (NULL); |
193 | 50 | } |
194 | | |
195 | 1.28k | psDBF->panFieldOffset = (int *)msSmallMalloc(sizeof(int) * nFields); |
196 | 1.28k | psDBF->panFieldSize = (int *)msSmallMalloc(sizeof(int) * nFields); |
197 | 1.28k | psDBF->panFieldDecimals = (int *)msSmallMalloc(sizeof(int) * nFields); |
198 | 1.28k | psDBF->pachFieldType = (char *)msSmallMalloc(sizeof(char) * nFields); |
199 | | |
200 | 10.1k | for (iField = 0; iField < nFields; iField++) { |
201 | 8.88k | uchar *pabyFInfo; |
202 | | |
203 | 8.88k | pabyFInfo = pabyBuf + iField * 32; |
204 | | |
205 | 8.88k | if (pabyFInfo[11] == 'N' || pabyFInfo[11] == 'F') { |
206 | 947 | psDBF->panFieldSize[iField] = pabyFInfo[16]; |
207 | 947 | psDBF->panFieldDecimals[iField] = pabyFInfo[17]; |
208 | 7.94k | } else { |
209 | 7.94k | psDBF->panFieldSize[iField] = pabyFInfo[16] + pabyFInfo[17] * 256; |
210 | 7.94k | psDBF->panFieldDecimals[iField] = 0; |
211 | 7.94k | } |
212 | | |
213 | 8.88k | psDBF->pachFieldType[iField] = (char)pabyFInfo[11]; |
214 | 8.88k | if (iField == 0) |
215 | 62 | psDBF->panFieldOffset[iField] = 1; |
216 | 8.82k | else |
217 | 8.82k | psDBF->panFieldOffset[iField] = |
218 | 8.82k | psDBF->panFieldOffset[iField - 1] + psDBF->panFieldSize[iField - 1]; |
219 | 8.88k | } |
220 | | |
221 | 1.28k | return (psDBF); |
222 | 1.33k | } |
223 | | |
224 | | /** |
225 | | * Attempt to read character encoding from a .CPG-file |
226 | | */ |
227 | 0 | char *msReadCPGEncoding(char *cpgFilename) { |
228 | 0 | VSILFILE *fpCPG = VSIFOpenL(cpgFilename, "rb"); |
229 | 0 | if (fpCPG == NULL) |
230 | 0 | return NULL; |
231 | | |
232 | 0 | char szEncoding[100] = ""; |
233 | 0 | size_t nRead = VSIFReadL(szEncoding, 1, sizeof(szEncoding) - 1, fpCPG); |
234 | 0 | VSIFCloseL(fpCPG); |
235 | |
|
236 | 0 | if (nRead == 0) |
237 | 0 | return NULL; |
238 | | |
239 | | // Null terminate and remove any line break |
240 | 0 | szEncoding[nRead] = '\0'; |
241 | 0 | szEncoding[strcspn(szEncoding, "\r\n")] = 0; |
242 | |
|
243 | 0 | if (szEncoding[0] == '\0') { |
244 | 0 | return NULL; |
245 | 0 | } |
246 | | |
247 | | /* See |
248 | | * https://github.com/OSGeo/gdal/blob/release/3.11/ogr/ogrsf_frmts/shape/ogrshapelayer.cpp#L503 |
249 | | */ |
250 | 0 | const int nCPG = atoi(szEncoding); |
251 | 0 | if ((nCPG >= 437 && nCPG <= 950) || (nCPG >= 1250 && nCPG <= 1258)) { |
252 | 0 | char szResult[20]; |
253 | 0 | snprintf(szResult, sizeof(szResult), "CP%d", nCPG); |
254 | 0 | return msStrdup(szResult); |
255 | 0 | } |
256 | | |
257 | 0 | if (strncasecmp(szEncoding, "8859", 4) == 0) { |
258 | 0 | const char *suffix = szEncoding + 4; |
259 | 0 | if (*suffix == '-' || *suffix == '_') |
260 | 0 | suffix++; |
261 | |
|
262 | 0 | if (!isdigit((unsigned char)*suffix)) |
263 | 0 | return msStrdup(szEncoding); |
264 | | |
265 | 0 | char szResult[40]; |
266 | 0 | snprintf(szResult, sizeof(szResult), "ISO-8859-%d", atoi(suffix)); |
267 | 0 | return msStrdup(szResult); |
268 | 0 | } |
269 | | |
270 | 0 | if (strncasecmp(szEncoding, "ANSI 1251", 9) == 0) { |
271 | 0 | return msStrdup("CP1251"); |
272 | 0 | } |
273 | | |
274 | 0 | return msStrdup(szEncoding); |
275 | 0 | } |
276 | | |
277 | | /************************************************************************/ |
278 | | /* msDBFOpen() */ |
279 | | /* */ |
280 | | /* Open a .dbf file. */ |
281 | | /************************************************************************/ |
282 | | DBFHandle msDBFOpen(const char *pszFilename, const char *pszAccess) |
283 | | |
284 | 0 | { |
285 | | /* -------------------------------------------------------------------- */ |
286 | | /* We only allow the access strings "rb" and "r+". */ |
287 | | /* -------------------------------------------------------------------- */ |
288 | 0 | if (strcmp(pszAccess, "r") != 0 && strcmp(pszAccess, "r+") != 0 && |
289 | 0 | strcmp(pszAccess, "rb") != 0 && strcmp(pszAccess, "r+b") != 0) |
290 | 0 | return (NULL); |
291 | | |
292 | | /* -------------------------------------------------------------------- */ |
293 | | /* Ensure the extension is converted to dbf or DBF if it is */ |
294 | | /* currently .shp or .shx. */ |
295 | | /* -------------------------------------------------------------------- */ |
296 | 0 | char *pszDBFFilename = (char *)msSmallMalloc(strlen(pszFilename) + 1); |
297 | 0 | strcpy(pszDBFFilename, pszFilename); |
298 | |
|
299 | 0 | if (strcmp(pszFilename + strlen(pszFilename) - 4, ".shp") == 0 || |
300 | 0 | strcmp(pszFilename + strlen(pszFilename) - 4, ".shx") == 0) { |
301 | 0 | strcpy(pszDBFFilename + strlen(pszDBFFilename) - 4, ".dbf"); |
302 | 0 | } else if (strcmp(pszFilename + strlen(pszFilename) - 4, ".SHP") == 0 || |
303 | 0 | strcmp(pszFilename + strlen(pszFilename) - 4, ".SHX") == 0) { |
304 | 0 | strcpy(pszDBFFilename + strlen(pszDBFFilename) - 4, ".DBF"); |
305 | 0 | } |
306 | | |
307 | | /* -------------------------------------------------------------------- */ |
308 | | /* Open the file. */ |
309 | | /* -------------------------------------------------------------------- */ |
310 | 0 | VSILFILE *fp = VSIFOpenL(pszDBFFilename, pszAccess); |
311 | 0 | if (fp == NULL) { |
312 | 0 | if (strcmp(pszDBFFilename + strlen(pszDBFFilename) - 4, ".dbf") == 0) { |
313 | 0 | strcpy(pszDBFFilename + strlen(pszDBFFilename) - 4, ".DBF"); |
314 | 0 | fp = VSIFOpenL(pszDBFFilename, pszAccess); |
315 | 0 | } |
316 | 0 | } |
317 | |
|
318 | 0 | if (fp == NULL) { |
319 | 0 | msFree(pszDBFFilename); |
320 | 0 | return (NULL); |
321 | 0 | } |
322 | | |
323 | 0 | DBFHandle dbfHandle = msDBFOpenVirtualFile(fp); |
324 | 0 | if (dbfHandle) { |
325 | 0 | char *pszCPGFilename = (char *)msSmallMalloc(strlen(pszDBFFilename) + 1); |
326 | 0 | strcpy(pszCPGFilename, pszDBFFilename); |
327 | |
|
328 | 0 | if (strcmp(pszDBFFilename + strlen(pszDBFFilename) - 4, ".dbf") == 0) { |
329 | 0 | strcpy(pszCPGFilename + strlen(pszCPGFilename) - 4, ".cpg"); |
330 | 0 | } else { |
331 | 0 | strcpy(pszCPGFilename + strlen(pszCPGFilename) - 4, ".CPG"); |
332 | 0 | } |
333 | |
|
334 | 0 | dbfHandle->pszEncoding = msReadCPGEncoding(pszCPGFilename); |
335 | |
|
336 | 0 | msFree(pszCPGFilename); |
337 | 0 | } |
338 | |
|
339 | 0 | msFree(pszDBFFilename); |
340 | 0 | return dbfHandle; |
341 | 0 | } |
342 | | |
343 | | /************************************************************************/ |
344 | | /* msDBFClose() */ |
345 | | /************************************************************************/ |
346 | | |
347 | 1.28k | void msDBFClose(DBFHandle psDBF) { |
348 | | /* -------------------------------------------------------------------- */ |
349 | | /* Write out header if not already written. */ |
350 | | /* -------------------------------------------------------------------- */ |
351 | 1.28k | if (psDBF->bNoHeader) |
352 | 0 | writeHeader(psDBF); |
353 | | |
354 | 1.28k | flushRecord(psDBF); |
355 | | |
356 | | /* -------------------------------------------------------------------- */ |
357 | | /* Update last access date, and number of records if we have */ |
358 | | /* write access. */ |
359 | | /* -------------------------------------------------------------------- */ |
360 | 1.28k | if (psDBF->bUpdated) { |
361 | 0 | uchar abyFileHeader[32]; |
362 | |
|
363 | 0 | VSIFSeekL(psDBF->fp, 0, 0); |
364 | 0 | IGUR_sizet(VSIFReadL(abyFileHeader, 32, 1, psDBF->fp)); |
365 | |
|
366 | 0 | abyFileHeader[1] = 95; /* YY */ |
367 | 0 | abyFileHeader[2] = 7; /* MM */ |
368 | 0 | abyFileHeader[3] = 26; /* DD */ |
369 | |
|
370 | 0 | abyFileHeader[4] = psDBF->nRecords % 256; |
371 | 0 | abyFileHeader[5] = (psDBF->nRecords / 256) % 256; |
372 | 0 | abyFileHeader[6] = (psDBF->nRecords / (256 * 256)) % 256; |
373 | 0 | abyFileHeader[7] = (psDBF->nRecords / (256 * 256 * 256)) % 256; |
374 | |
|
375 | 0 | VSIFSeekL(psDBF->fp, 0, 0); |
376 | 0 | VSIFWriteL(abyFileHeader, 32, 1, psDBF->fp); |
377 | 0 | } |
378 | | |
379 | | /* -------------------------------------------------------------------- */ |
380 | | /* Close, and free resources. */ |
381 | | /* -------------------------------------------------------------------- */ |
382 | 1.28k | VSIFCloseL(psDBF->fp); |
383 | | |
384 | 1.28k | if (psDBF->panFieldOffset != NULL) { |
385 | 62 | free(psDBF->panFieldOffset); |
386 | 62 | free(psDBF->panFieldSize); |
387 | 62 | free(psDBF->panFieldDecimals); |
388 | 62 | free(psDBF->pachFieldType); |
389 | 62 | } |
390 | | |
391 | 1.28k | free(psDBF->pszHeader); |
392 | 1.28k | free(psDBF->pszCurrentRecord); |
393 | | |
394 | 1.28k | free(psDBF->pszStringField); |
395 | 1.28k | free(psDBF->pszEncoding); |
396 | | |
397 | 1.28k | free(psDBF); |
398 | 1.28k | } |
399 | | |
400 | | /************************************************************************/ |
401 | | /* msDBFCreate() */ |
402 | | /* */ |
403 | | /* Create a new .dbf file. */ |
404 | | /************************************************************************/ |
405 | | DBFHandle msDBFCreate(const char *pszFilename) |
406 | | |
407 | 0 | { |
408 | 0 | DBFHandle psDBF; |
409 | 0 | VSILFILE *fp; |
410 | | |
411 | | /* -------------------------------------------------------------------- */ |
412 | | /* Create the file. */ |
413 | | /* -------------------------------------------------------------------- */ |
414 | 0 | fp = VSIFOpenL(pszFilename, "wb"); |
415 | 0 | if (fp == NULL) |
416 | 0 | return (NULL); |
417 | | |
418 | 0 | { |
419 | 0 | char ch = 0; |
420 | 0 | VSIFWriteL(&ch, 1, 1, fp); |
421 | 0 | } |
422 | 0 | VSIFCloseL(fp); |
423 | |
|
424 | 0 | fp = VSIFOpenL(pszFilename, "rb+"); |
425 | 0 | if (fp == NULL) |
426 | 0 | return (NULL); |
427 | | |
428 | | /* -------------------------------------------------------------------- */ |
429 | | /* Create the info structure. */ |
430 | | /* -------------------------------------------------------------------- */ |
431 | 0 | psDBF = (DBFHandle)malloc(sizeof(DBFInfo)); |
432 | 0 | if (psDBF == NULL) { |
433 | 0 | msSetError(MS_MEMERR, "%s: %d: Out of memory allocating %u bytes.\n", |
434 | 0 | "msDBFCreate()", __FILE__, __LINE__, |
435 | 0 | (unsigned int)sizeof(DBFInfo)); |
436 | 0 | VSIFCloseL(fp); |
437 | 0 | return NULL; |
438 | 0 | } |
439 | | |
440 | 0 | psDBF->fp = fp; |
441 | 0 | psDBF->nRecords = 0; |
442 | 0 | psDBF->nFields = 0; |
443 | 0 | psDBF->nRecordLength = 1; |
444 | 0 | psDBF->nHeaderLength = 33; |
445 | |
|
446 | 0 | psDBF->panFieldOffset = NULL; |
447 | 0 | psDBF->panFieldSize = NULL; |
448 | 0 | psDBF->panFieldDecimals = NULL; |
449 | 0 | psDBF->pachFieldType = NULL; |
450 | 0 | psDBF->pszHeader = NULL; |
451 | |
|
452 | 0 | psDBF->nCurrentRecord = -1; |
453 | 0 | psDBF->bCurrentRecordModified = MS_FALSE; |
454 | 0 | psDBF->pszCurrentRecord = NULL; |
455 | |
|
456 | 0 | psDBF->pszStringField = NULL; |
457 | 0 | psDBF->nStringFieldLen = 0; |
458 | |
|
459 | 0 | psDBF->bNoHeader = MS_TRUE; |
460 | 0 | psDBF->bUpdated = MS_FALSE; |
461 | |
|
462 | 0 | return (psDBF); |
463 | 0 | } |
464 | | |
465 | | /************************************************************************/ |
466 | | /* msDBFAddField() */ |
467 | | /* */ |
468 | | /* Add a field to a newly created .dbf file before any records */ |
469 | | /* are written. */ |
470 | | /************************************************************************/ |
471 | | int msDBFAddField(DBFHandle psDBF, const char *pszFieldName, DBFFieldType eType, |
472 | 0 | int nWidth, int nDecimals) { |
473 | 0 | char *pszFInfo; |
474 | 0 | int i; |
475 | | |
476 | | /* -------------------------------------------------------------------- */ |
477 | | /* Do some checking to ensure we can add records to this file. */ |
478 | | /* -------------------------------------------------------------------- */ |
479 | 0 | if (psDBF->nRecords > 0) |
480 | 0 | return (MS_FALSE); |
481 | | |
482 | 0 | if (!psDBF->bNoHeader) |
483 | 0 | return (MS_FALSE); |
484 | | |
485 | 0 | if (eType != FTDouble && nDecimals != 0) |
486 | 0 | return (MS_FALSE); |
487 | | |
488 | | /* -------------------------------------------------------------------- */ |
489 | | /* SfRealloc all the arrays larger to hold the additional field */ |
490 | | /* information. */ |
491 | | /* -------------------------------------------------------------------- */ |
492 | 0 | psDBF->nFields++; |
493 | |
|
494 | 0 | psDBF->panFieldOffset = |
495 | 0 | (int *)SfRealloc(psDBF->panFieldOffset, sizeof(int) * psDBF->nFields); |
496 | |
|
497 | 0 | psDBF->panFieldSize = |
498 | 0 | (int *)SfRealloc(psDBF->panFieldSize, sizeof(int) * psDBF->nFields); |
499 | |
|
500 | 0 | psDBF->panFieldDecimals = |
501 | 0 | (int *)SfRealloc(psDBF->panFieldDecimals, sizeof(int) * psDBF->nFields); |
502 | |
|
503 | 0 | psDBF->pachFieldType = |
504 | 0 | (char *)SfRealloc(psDBF->pachFieldType, sizeof(char) * psDBF->nFields); |
505 | | |
506 | | /* -------------------------------------------------------------------- */ |
507 | | /* Assign the new field information fields. */ |
508 | | /* -------------------------------------------------------------------- */ |
509 | 0 | psDBF->panFieldOffset[psDBF->nFields - 1] = psDBF->nRecordLength; |
510 | 0 | psDBF->nRecordLength += nWidth; |
511 | 0 | psDBF->panFieldSize[psDBF->nFields - 1] = nWidth; |
512 | 0 | psDBF->panFieldDecimals[psDBF->nFields - 1] = nDecimals; |
513 | |
|
514 | 0 | if (eType == FTString) |
515 | 0 | psDBF->pachFieldType[psDBF->nFields - 1] = 'C'; |
516 | 0 | else |
517 | 0 | psDBF->pachFieldType[psDBF->nFields - 1] = 'N'; |
518 | | |
519 | | /* -------------------------------------------------------------------- */ |
520 | | /* Extend the required header information. */ |
521 | | /* -------------------------------------------------------------------- */ |
522 | 0 | psDBF->nHeaderLength += 32; |
523 | 0 | psDBF->bUpdated = MS_FALSE; |
524 | |
|
525 | 0 | psDBF->pszHeader = (char *)SfRealloc(psDBF->pszHeader, psDBF->nFields * 32); |
526 | |
|
527 | 0 | pszFInfo = psDBF->pszHeader + 32 * (psDBF->nFields - 1); |
528 | |
|
529 | 0 | for (i = 0; i < 32; i++) |
530 | 0 | pszFInfo[i] = '\0'; |
531 | |
|
532 | 0 | strncpy(pszFInfo, pszFieldName, 10); |
533 | |
|
534 | 0 | pszFInfo[11] = psDBF->pachFieldType[psDBF->nFields - 1]; |
535 | |
|
536 | 0 | if (eType == FTString) { |
537 | 0 | pszFInfo[16] = nWidth % 256; |
538 | 0 | pszFInfo[17] = nWidth / 256; |
539 | 0 | } else { |
540 | 0 | pszFInfo[16] = nWidth; |
541 | 0 | pszFInfo[17] = nDecimals; |
542 | 0 | } |
543 | | |
544 | | /* -------------------------------------------------------------------- */ |
545 | | /* Make the current record buffer appropriately larger. */ |
546 | | /* -------------------------------------------------------------------- */ |
547 | 0 | psDBF->pszCurrentRecord = |
548 | 0 | (char *)SfRealloc(psDBF->pszCurrentRecord, psDBF->nRecordLength); |
549 | |
|
550 | 0 | return (psDBF->nFields - 1); |
551 | 0 | } |
552 | | |
553 | | /************************************************************************/ |
554 | | /* DBFIsValueNULL() */ |
555 | | /* */ |
556 | | /* Return TRUE if value is NULL (in DBF terms). */ |
557 | | /* */ |
558 | | /* Based on DBFIsAttributeNULL of shapelib */ |
559 | | /************************************************************************/ |
560 | | |
561 | | static int DBFIsValueNULL(const char *pszValue, char type) |
562 | | |
563 | 0 | { |
564 | 0 | switch (type) { |
565 | 0 | case 'N': |
566 | 0 | case 'F': |
567 | | /* NULL numeric fields have value "****************" */ |
568 | 0 | return pszValue[0] == '*'; |
569 | | |
570 | 0 | case 'D': |
571 | | /* NULL date fields have value "00000000" */ |
572 | 0 | return strncmp(pszValue, "00000000", 8) == 0; |
573 | | |
574 | 0 | case 'L': |
575 | | /* NULL boolean fields have value "?" */ |
576 | 0 | return pszValue[0] == '?'; |
577 | | |
578 | 0 | default: |
579 | | /* empty string fields are considered NULL */ |
580 | 0 | return strlen(pszValue) == 0; |
581 | 0 | } |
582 | 0 | } |
583 | | |
584 | | /************************************************************************/ |
585 | | /* msDBFReadAttribute() */ |
586 | | /* */ |
587 | | /* Read one of the attribute fields of a record. */ |
588 | | /************************************************************************/ |
589 | | static const char *msDBFReadAttribute(DBFHandle psDBF, int hEntity, int iField) |
590 | | |
591 | 0 | { |
592 | 0 | int i; |
593 | 0 | unsigned int nRecordOffset; |
594 | 0 | const uchar *pabyRec; |
595 | 0 | const char *pReturnField = NULL; |
596 | | |
597 | | /* -------------------------------------------------------------------- */ |
598 | | /* Is the request valid? */ |
599 | | /* -------------------------------------------------------------------- */ |
600 | 0 | if (iField < 0 || iField >= psDBF->nFields) { |
601 | 0 | msSetError(MS_DBFERR, "Invalid field index %d.", "msDBFReadAttribute()", |
602 | 0 | iField); |
603 | 0 | return (NULL); |
604 | 0 | } |
605 | | |
606 | 0 | if (hEntity < 0 || hEntity >= psDBF->nRecords) { |
607 | 0 | msSetError(MS_DBFERR, "Invalid record number %d.", "msDBFReadAttribute()", |
608 | 0 | hEntity); |
609 | 0 | return (NULL); |
610 | 0 | } |
611 | | |
612 | | /* -------------------------------------------------------------------- */ |
613 | | /* Have we read the record? */ |
614 | | /* -------------------------------------------------------------------- */ |
615 | 0 | if (psDBF->nCurrentRecord != hEntity) { |
616 | 0 | flushRecord(psDBF); |
617 | |
|
618 | 0 | nRecordOffset = psDBF->nRecordLength * hEntity + psDBF->nHeaderLength; |
619 | |
|
620 | 0 | VSIFSeekL(psDBF->fp, nRecordOffset, 0); |
621 | 0 | if (VSIFReadL(psDBF->pszCurrentRecord, psDBF->nRecordLength, 1, |
622 | 0 | psDBF->fp) != 1) { |
623 | 0 | msSetError(MS_DBFERR, "Cannot read record %d.", "msDBFReadAttribute()", |
624 | 0 | hEntity); |
625 | 0 | return (NULL); |
626 | 0 | } |
627 | | |
628 | 0 | psDBF->nCurrentRecord = hEntity; |
629 | 0 | } |
630 | | |
631 | 0 | pabyRec = (const uchar *)psDBF->pszCurrentRecord; |
632 | | /* DEBUG */ |
633 | | /* printf("CurrentRecord(%c):%s\n", psDBF->pachFieldType[iField], pabyRec); */ |
634 | | |
635 | | /* -------------------------------------------------------------------- */ |
636 | | /* Ensure our field buffer is large enough to hold this buffer. */ |
637 | | /* -------------------------------------------------------------------- */ |
638 | 0 | if (psDBF->panFieldSize[iField] + 1 > psDBF->nStringFieldLen) { |
639 | 0 | psDBF->nStringFieldLen = psDBF->panFieldSize[iField] * 2 + 10; |
640 | 0 | psDBF->pszStringField = |
641 | 0 | (char *)SfRealloc(psDBF->pszStringField, psDBF->nStringFieldLen); |
642 | 0 | } |
643 | | |
644 | | /* -------------------------------------------------------------------- */ |
645 | | /* Extract the requested field. */ |
646 | | /* -------------------------------------------------------------------- */ |
647 | 0 | strncpy(psDBF->pszStringField, |
648 | 0 | (const char *)pabyRec + psDBF->panFieldOffset[iField], |
649 | 0 | psDBF->panFieldSize[iField]); |
650 | 0 | psDBF->pszStringField[psDBF->panFieldSize[iField]] = '\0'; |
651 | | |
652 | | /* |
653 | | ** Trim trailing blanks (SDL Modification) |
654 | | */ |
655 | 0 | for (i = strlen(psDBF->pszStringField) - 1; i >= 0; i--) { |
656 | 0 | if (psDBF->pszStringField[i] != ' ') { |
657 | 0 | psDBF->pszStringField[i + 1] = '\0'; |
658 | 0 | break; |
659 | 0 | } |
660 | 0 | } |
661 | |
|
662 | 0 | if (i == -1) |
663 | 0 | psDBF->pszStringField[0] = '\0'; /* whole string is blank (SDL fix) */ |
664 | | |
665 | | /* |
666 | | ** Trim/skip leading blanks (SDL/DM Modification - only on numeric types) |
667 | | */ |
668 | 0 | if (psDBF->pachFieldType[iField] == 'N' || |
669 | 0 | psDBF->pachFieldType[iField] == 'F' || |
670 | 0 | psDBF->pachFieldType[iField] == 'D') { |
671 | 0 | for (i = 0; psDBF->pszStringField[i] != '\0'; i++) { |
672 | 0 | if (psDBF->pszStringField[i] != ' ') |
673 | 0 | break; |
674 | 0 | } |
675 | 0 | pReturnField = psDBF->pszStringField + i; |
676 | 0 | } else |
677 | 0 | pReturnField = psDBF->pszStringField; |
678 | | |
679 | | /* detect null values */ |
680 | 0 | if (DBFIsValueNULL(pReturnField, psDBF->pachFieldType[iField])) { |
681 | 0 | if (psDBF->pachFieldType[iField] == 'N' || |
682 | 0 | psDBF->pachFieldType[iField] == 'F' || |
683 | 0 | psDBF->pachFieldType[iField] == 'D') |
684 | 0 | pReturnField = "0"; |
685 | 0 | } |
686 | 0 | return (pReturnField); |
687 | 0 | } |
688 | | |
689 | | /************************************************************************/ |
690 | | /* msDBFReadIntAttribute() */ |
691 | | /* */ |
692 | | /* Read an integer attribute. */ |
693 | | /************************************************************************/ |
694 | | int msDBFReadIntegerAttribute(DBFHandle psDBF, int iRecord, int iField) |
695 | | |
696 | 0 | { |
697 | 0 | return (atoi(msDBFReadAttribute(psDBF, iRecord, iField))); |
698 | 0 | } |
699 | | |
700 | | /************************************************************************/ |
701 | | /* msDBFReadDoubleAttribute() */ |
702 | | /* */ |
703 | | /* Read a double attribute. */ |
704 | | /************************************************************************/ |
705 | 0 | double msDBFReadDoubleAttribute(DBFHandle psDBF, int iRecord, int iField) { |
706 | 0 | return (atof(msDBFReadAttribute(psDBF, iRecord, iField))); |
707 | 0 | } |
708 | | |
709 | | /************************************************************************/ |
710 | | /* msDBFReadStringAttribute() */ |
711 | | /* */ |
712 | | /* Read a string attribute. */ |
713 | | /************************************************************************/ |
714 | 0 | const char *msDBFReadStringAttribute(DBFHandle psDBF, int iRecord, int iField) { |
715 | 0 | return (msDBFReadAttribute(psDBF, iRecord, iField)); |
716 | 0 | } |
717 | | |
718 | | /************************************************************************/ |
719 | | /* msDBFGetFieldCount() */ |
720 | | /* */ |
721 | | /* Return the number of fields in this table. */ |
722 | | /************************************************************************/ |
723 | 0 | int msDBFGetFieldCount(DBFHandle psDBF) { return (psDBF->nFields); } |
724 | | |
725 | | /************************************************************************/ |
726 | | /* msDBFGetRecordCount() */ |
727 | | /* */ |
728 | | /* Return the number of records in this table. */ |
729 | | /************************************************************************/ |
730 | 0 | int msDBFGetRecordCount(DBFHandle psDBF) { return (psDBF->nRecords); } |
731 | | |
732 | | /************************************************************************/ |
733 | | /* msDBFGetFieldInfo() */ |
734 | | /* */ |
735 | | /* Return any requested information about the field. */ |
736 | | /************************************************************************/ |
737 | | DBFFieldType msDBFGetFieldInfo(DBFHandle psDBF, int iField, char *pszFieldName, |
738 | 0 | int *pnWidth, int *pnDecimals) { |
739 | 0 | if (iField < 0 || iField >= psDBF->nFields) |
740 | 0 | return (FTInvalid); |
741 | | |
742 | 0 | if (pnWidth != NULL) |
743 | 0 | *pnWidth = psDBF->panFieldSize[iField]; |
744 | |
|
745 | 0 | if (pnDecimals != NULL) |
746 | 0 | *pnDecimals = psDBF->panFieldDecimals[iField]; |
747 | |
|
748 | 0 | if (pszFieldName != NULL) { |
749 | 0 | int i; |
750 | |
|
751 | 0 | strncpy(pszFieldName, (char *)psDBF->pszHeader + iField * 32, 11); |
752 | 0 | pszFieldName[11] = '\0'; |
753 | 0 | for (i = 10; i > 0 && pszFieldName[i] == ' '; i--) |
754 | 0 | pszFieldName[i] = '\0'; |
755 | 0 | } |
756 | |
|
757 | 0 | if (psDBF->pachFieldType[iField] == 'N' || |
758 | 0 | psDBF->pachFieldType[iField] == 'F' || |
759 | 0 | psDBF->pachFieldType[iField] == 'D') { |
760 | 0 | if (psDBF->panFieldDecimals[iField] > 0) |
761 | 0 | return (FTDouble); |
762 | 0 | else |
763 | 0 | return (FTInteger); |
764 | 0 | } else { |
765 | 0 | return (FTString); |
766 | 0 | } |
767 | 0 | } |
768 | | |
769 | | /************************************************************************/ |
770 | | /* msDBFWriteAttribute() */ |
771 | | /* */ |
772 | | /* Write an attribute record to the file. */ |
773 | | /************************************************************************/ |
774 | | static int msDBFWriteAttribute(DBFHandle psDBF, int hEntity, int iField, |
775 | 0 | void *pValue) { |
776 | 0 | unsigned int nRecordOffset; |
777 | 0 | int len; |
778 | 0 | uchar *pabyRec; |
779 | 0 | char szSField[40]; |
780 | | |
781 | | /* -------------------------------------------------------------------- */ |
782 | | /* Is this a valid record? */ |
783 | | /* -------------------------------------------------------------------- */ |
784 | 0 | if (hEntity < 0 || hEntity > psDBF->nRecords) |
785 | 0 | return (MS_FALSE); |
786 | | |
787 | 0 | if (psDBF->bNoHeader) |
788 | 0 | writeHeader(psDBF); |
789 | | |
790 | | /* -------------------------------------------------------------------- */ |
791 | | /* Is this a brand new record? */ |
792 | | /* -------------------------------------------------------------------- */ |
793 | 0 | if (hEntity == psDBF->nRecords) { |
794 | 0 | flushRecord(psDBF); |
795 | |
|
796 | 0 | psDBF->nRecords++; |
797 | 0 | for (unsigned i = 0; i < psDBF->nRecordLength; i++) |
798 | 0 | psDBF->pszCurrentRecord[i] = ' '; |
799 | |
|
800 | 0 | psDBF->nCurrentRecord = hEntity; |
801 | 0 | } |
802 | | |
803 | | /* -------------------------------------------------------------------- */ |
804 | | /* Is this an existing record, but different than the last one */ |
805 | | /* we accessed? */ |
806 | | /* -------------------------------------------------------------------- */ |
807 | 0 | if (psDBF->nCurrentRecord != hEntity) { |
808 | 0 | flushRecord(psDBF); |
809 | |
|
810 | 0 | nRecordOffset = psDBF->nRecordLength * hEntity + psDBF->nHeaderLength; |
811 | |
|
812 | 0 | VSIFSeekL(psDBF->fp, nRecordOffset, 0); |
813 | 0 | if (VSIFReadL(psDBF->pszCurrentRecord, psDBF->nRecordLength, 1, |
814 | 0 | psDBF->fp) != 1) |
815 | 0 | return MS_FALSE; |
816 | | |
817 | 0 | psDBF->nCurrentRecord = hEntity; |
818 | 0 | } |
819 | | |
820 | 0 | pabyRec = (uchar *)psDBF->pszCurrentRecord; |
821 | | |
822 | | /* -------------------------------------------------------------------- */ |
823 | | /* Assign all the record fields. */ |
824 | | /* -------------------------------------------------------------------- */ |
825 | 0 | switch (psDBF->pachFieldType[iField]) { |
826 | 0 | case 'D': |
827 | 0 | case 'N': |
828 | 0 | case 'F': |
829 | 0 | snprintf(szSField, sizeof(szSField), "%*.*f", psDBF->panFieldSize[iField], |
830 | 0 | psDBF->panFieldDecimals[iField], *(double *)pValue); |
831 | 0 | len = strlen((char *)szSField); |
832 | 0 | memcpy(pabyRec + psDBF->panFieldOffset[iField], szSField, |
833 | 0 | MS_MIN(len, psDBF->panFieldSize[iField])); |
834 | 0 | break; |
835 | | |
836 | 0 | default: |
837 | 0 | len = strlen((char *)pValue); |
838 | 0 | memcpy(pabyRec + psDBF->panFieldOffset[iField], pValue, |
839 | 0 | MS_MIN(len, psDBF->panFieldSize[iField])); |
840 | 0 | break; |
841 | 0 | } |
842 | | |
843 | 0 | psDBF->bCurrentRecordModified = MS_TRUE; |
844 | 0 | psDBF->bUpdated = MS_TRUE; |
845 | |
|
846 | 0 | return (MS_TRUE); |
847 | 0 | } |
848 | | |
849 | | /************************************************************************/ |
850 | | /* msDBFWriteDoubleAttribute() */ |
851 | | /* */ |
852 | | /* Write a double attribute. */ |
853 | | /************************************************************************/ |
854 | | int msDBFWriteDoubleAttribute(DBFHandle psDBF, int iRecord, int iField, |
855 | 0 | double dValue) { |
856 | 0 | return (msDBFWriteAttribute(psDBF, iRecord, iField, (void *)&dValue)); |
857 | 0 | } |
858 | | |
859 | | /************************************************************************/ |
860 | | /* msDBFWriteIntegerAttribute() */ |
861 | | /* */ |
862 | | /* Write a integer attribute. */ |
863 | | /************************************************************************/ |
864 | | |
865 | | int msDBFWriteIntegerAttribute(DBFHandle psDBF, int iRecord, int iField, |
866 | 0 | int nValue) { |
867 | 0 | double dValue = nValue; |
868 | |
|
869 | 0 | return (msDBFWriteAttribute(psDBF, iRecord, iField, (void *)&dValue)); |
870 | 0 | } |
871 | | |
872 | | /************************************************************************/ |
873 | | /* msDBFWriteStringAttribute() */ |
874 | | /* */ |
875 | | /* Write a string attribute. */ |
876 | | /************************************************************************/ |
877 | | int msDBFWriteStringAttribute(DBFHandle psDBF, int iRecord, int iField, |
878 | 0 | const char *pszValue) { |
879 | 0 | return (msDBFWriteAttribute(psDBF, iRecord, iField, (void *)pszValue)); |
880 | 0 | } |
881 | | |
882 | | /* |
883 | | ** Which column number in the .DBF file does the item correspond to |
884 | | */ |
885 | 0 | int msDBFGetItemIndex(DBFHandle dbffile, char *name) { |
886 | 0 | int i; |
887 | 0 | int fWidth, fnDecimals; /* field width and number of decimals */ |
888 | 0 | char fName[32]; /* field name */ |
889 | |
|
890 | 0 | if (!name) { |
891 | 0 | msSetError(MS_MISCERR, "NULL item name passed.", "msGetItemIndex()"); |
892 | 0 | return (-1); |
893 | 0 | } |
894 | | |
895 | | /* does name exist as a field? */ |
896 | 0 | for (i = 0; i < msDBFGetFieldCount(dbffile); i++) { |
897 | 0 | msDBFGetFieldInfo(dbffile, i, fName, &fWidth, &fnDecimals); |
898 | 0 | if (strcasecmp(name, fName) == 0) /* found it */ |
899 | 0 | return (i); |
900 | 0 | } |
901 | | |
902 | 0 | msSetError(MS_DBFERR, "Item '%s' not found.", "msDBFGetItemIndex()", name); |
903 | 0 | return (-1); /* item not found */ |
904 | 0 | } |
905 | | |
906 | | /* |
907 | | ** Load item names into a character array |
908 | | */ |
909 | 0 | char **msDBFGetItems(DBFHandle dbffile) { |
910 | 0 | char **items; |
911 | 0 | int i, nFields; |
912 | 0 | char fName[32]; |
913 | |
|
914 | 0 | if ((nFields = msDBFGetFieldCount(dbffile)) == 0) { |
915 | 0 | msSetError(MS_DBFERR, "File contains no data.", "msGetDBFItems()"); |
916 | 0 | return (NULL); |
917 | 0 | } |
918 | | |
919 | 0 | items = (char **)malloc(sizeof(char *) * nFields); |
920 | 0 | MS_CHECK_ALLOC(items, sizeof(char *) * nFields, NULL); |
921 | |
|
922 | 0 | for (i = 0; i < nFields; i++) { |
923 | 0 | msDBFGetFieldInfo(dbffile, i, fName, NULL, NULL); |
924 | 0 | items[i] = msStrdup(fName); |
925 | 0 | } |
926 | |
|
927 | 0 | return (items); |
928 | 0 | } |
929 | | |
930 | | /* |
931 | | ** Load item values into a character array |
932 | | */ |
933 | 0 | char **msDBFGetValues(DBFHandle dbffile, int record) { |
934 | 0 | char **values; |
935 | 0 | int i, nFields; |
936 | |
|
937 | 0 | if ((nFields = msDBFGetFieldCount(dbffile)) == 0) { |
938 | 0 | msSetError(MS_DBFERR, "File contains no data.", "msGetDBFValues()"); |
939 | 0 | return (NULL); |
940 | 0 | } |
941 | | |
942 | 0 | values = (char **)malloc(sizeof(char *) * nFields); |
943 | 0 | MS_CHECK_ALLOC(values, sizeof(char *) * nFields, NULL); |
944 | |
|
945 | 0 | for (i = 0; i < nFields; i++) |
946 | 0 | values[i] = msStrdup(msDBFReadStringAttribute(dbffile, record, i)); |
947 | |
|
948 | 0 | return (values); |
949 | 0 | } |
950 | | |
951 | 0 | int *msDBFGetItemIndexes(DBFHandle dbffile, char **items, int numitems) { |
952 | 0 | int *itemindexes = NULL, i; |
953 | |
|
954 | 0 | if (numitems == 0) |
955 | 0 | return (NULL); |
956 | | |
957 | 0 | itemindexes = (int *)malloc(sizeof(int) * numitems); |
958 | 0 | MS_CHECK_ALLOC(itemindexes, sizeof(int) * numitems, NULL); |
959 | |
|
960 | 0 | for (i = 0; i < numitems; i++) { |
961 | 0 | itemindexes[i] = msDBFGetItemIndex(dbffile, items[i]); |
962 | 0 | if (itemindexes[i] == -1) { |
963 | 0 | free(itemindexes); |
964 | 0 | return (NULL); /* item not found */ |
965 | 0 | } |
966 | 0 | } |
967 | | |
968 | 0 | return (itemindexes); |
969 | 0 | } |
970 | | |
971 | | char **msDBFGetValueList(DBFHandle dbffile, int record, int *itemindexes, |
972 | 0 | int numitems) { |
973 | 0 | const char *value; |
974 | 0 | char **values = NULL; |
975 | 0 | int i; |
976 | |
|
977 | 0 | if (numitems == 0) |
978 | 0 | return (NULL); |
979 | | |
980 | 0 | values = (char **)malloc(sizeof(char *) * numitems); |
981 | 0 | MS_CHECK_ALLOC(values, sizeof(char *) * numitems, NULL); |
982 | |
|
983 | 0 | for (i = 0; i < numitems; i++) { |
984 | 0 | value = msDBFReadStringAttribute(dbffile, record, itemindexes[i]); |
985 | 0 | if (value == NULL) { |
986 | 0 | free(values); |
987 | 0 | return NULL; /* Error already reported by msDBFReadStringAttribute() */ |
988 | 0 | } |
989 | 0 | values[i] = msStrdup(value); |
990 | 0 | } |
991 | | |
992 | 0 | return (values); |
993 | 0 | } |