Line data Source code
1 : /**********************************************************************
2 : * $Id$
3 : *
4 : * Name: avc_misc.c
5 : * Project: Arc/Info vector coverage (AVC) BIN<->E00 conversion library
6 : * Language: ANSI C
7 : * Purpose: Misc. functions used by several parts of the library
8 : * Author: Daniel Morissette, dmorissette@dmsolutions.ca
9 : *
10 : **********************************************************************
11 : * Copyright (c) 1999-2005, Daniel Morissette
12 : *
13 : * Permission is hereby granted, free of charge, to any person obtaining a
14 : * copy of this software and associated documentation files (the "Software"),
15 : * to deal in the Software without restriction, including without limitation
16 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
17 : * and/or sell copies of the Software, and to permit persons to whom the
18 : * Software is furnished to do so, subject to the following conditions:
19 : *
20 : * The above copyright notice and this permission notice shall be included
21 : * in all copies or substantial portions of the Software.
22 : *
23 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 : * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
26 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
29 : * DEALINGS IN THE SOFTWARE.
30 : **********************************************************************
31 : *
32 : * $Log: avc_misc.c,v $
33 : * Revision 1.9 2005/06/03 03:49:59 daniel
34 : * Update email address, website url, and copyright dates
35 : *
36 : * Revision 1.8 2004/08/31 21:00:20 warmerda
37 : * Applied Carl Anderson's patch to reduce the amount of stating while
38 : * trying to discover filename "case" on Unix in AVCAdjustCaseSensitiveFilename.
39 : * http://bugzilla.remotesensing.org/show_bug.cgi?id=314
40 : *
41 : * Revision 1.7 2001/11/25 21:38:01 daniel
42 : * Remap '\\' to '/' in AVCAdjustCaseSensitiveFilename() on Unix.
43 : *
44 : * Revision 1.6 2001/11/25 21:15:23 daniel
45 : * Added hack (AVC_MAP_TYPE40_TO_DOUBLE) to map type 40 fields bigger than 8
46 : * digits to double precision as we generate E00 output (bug599)
47 : *
48 : * Revision 1.5 2000/09/26 20:21:04 daniel
49 : * Added AVCCoverPC write
50 : *
51 : * Revision 1.4 2000/09/22 19:45:21 daniel
52 : * Switch to MIT-style license
53 : *
54 : * Revision 1.3 2000/01/10 02:53:21 daniel
55 : * Added AVCAdjustCaseSensitiveFilename() and AVCFileExists()
56 : *
57 : * Revision 1.2 1999/08/23 18:24:27 daniel
58 : * Fixed support for attribute fields of type 40
59 : *
60 : * Revision 1.1 1999/05/11 02:34:46 daniel
61 : * Initial revision
62 : *
63 : **********************************************************************/
64 :
65 : #include "avc.h"
66 :
67 : /**********************************************************************
68 : * AVCE00ComputeRecSize()
69 : *
70 : * Computes the number of chars required to generate a E00 attribute
71 : * table record.
72 : *
73 : * Returns -1 on error, i.e. if it encounters an unsupported field type.
74 : **********************************************************************/
75 55 : int _AVCE00ComputeRecSize(int numFields, AVCFieldInfo *pasDef,
76 : GBool bMapType40ToDouble)
77 : {
78 55 : int i, nType, nBufSize = 0;
79 :
80 : /*-------------------------------------------------------------
81 : * Add up the nbr of chars used by each field
82 : *------------------------------------------------------------*/
83 338 : for (i = 0; i < numFields; i++)
84 : {
85 283 : nType = pasDef[i].nType1 * 10;
86 283 : if (nType == AVC_FT_DATE || nType == AVC_FT_CHAR ||
87 : nType == AVC_FT_FIXINT)
88 : {
89 22 : nBufSize += pasDef[i].nSize;
90 : }
91 261 : else if (nType == AVC_FT_BININT && pasDef[i].nSize == 4)
92 87 : nBufSize += 11;
93 174 : else if (nType == AVC_FT_BININT && pasDef[i].nSize == 2)
94 0 : nBufSize += 6;
95 174 : else if (bMapType40ToDouble && nType == AVC_FT_FIXNUM &&
96 0 : pasDef[i].nSize > 8)
97 : {
98 : /* See explanation in AVCE00GenTableHdr() about this hack to remap
99 : * type 40 fields to double precision floats.
100 : */
101 0 : nBufSize += 24; /* Remap to double float */
102 : }
103 174 : else if ((nType == AVC_FT_BINFLOAT && pasDef[i].nSize == 4) ||
104 : nType == AVC_FT_FIXNUM)
105 174 : nBufSize += 14;
106 0 : else if (nType == AVC_FT_BINFLOAT && pasDef[i].nSize == 8)
107 0 : nBufSize += 24;
108 : else
109 : {
110 : /*-----------------------------------------------------
111 : * Hummm... unsupported field type...
112 : *----------------------------------------------------*/
113 0 : CPLError(CE_Failure, CPLE_NotSupported,
114 : "_AVCE00ComputeRecSize(): Unsupported field type: "
115 : "(type=%d, size=%d)",
116 0 : nType, pasDef[i].nSize);
117 0 : return -1;
118 : }
119 : }
120 :
121 55 : return nBufSize;
122 : }
123 :
124 : /**********************************************************************
125 : * _AVCDestroyTableFields()
126 : *
127 : * Release all memory associated with an array of AVCField structures.
128 : **********************************************************************/
129 62 : void _AVCDestroyTableFields(AVCTableDef *psTableDef, AVCField *pasFields)
130 : {
131 : int i, nFieldType;
132 :
133 62 : if (pasFields)
134 : {
135 376 : for (i = 0; i < psTableDef->numFields; i++)
136 : {
137 314 : nFieldType = psTableDef->pasFieldDef[i].nType1 * 10;
138 314 : if (nFieldType == AVC_FT_DATE || nFieldType == AVC_FT_CHAR ||
139 289 : nFieldType == AVC_FT_FIXINT || nFieldType == AVC_FT_FIXNUM)
140 : {
141 25 : CPLFree(pasFields[i].pszStr);
142 : }
143 : }
144 62 : CPLFree(pasFields);
145 : }
146 62 : }
147 :
148 : /**********************************************************************
149 : * _AVCDestroyTableDef()
150 : *
151 : * Release all memory associated with a AVCTableDef structure.
152 : *
153 : **********************************************************************/
154 62 : void _AVCDestroyTableDef(AVCTableDef *psTableDef)
155 : {
156 62 : if (psTableDef)
157 : {
158 62 : CPLFree(psTableDef->pasFieldDef);
159 62 : CPLFree(psTableDef);
160 : }
161 62 : }
162 :
163 : /**********************************************************************
164 : * _AVCDupTableDef()
165 : *
166 : * Create a new copy of a AVCTableDef structure.
167 : **********************************************************************/
168 0 : AVCTableDef *_AVCDupTableDef(AVCTableDef *psSrcDef)
169 : {
170 : AVCTableDef *psNewDef;
171 :
172 0 : if (psSrcDef == nullptr)
173 0 : return nullptr;
174 :
175 0 : psNewDef = (AVCTableDef *)CPLMalloc(1 * sizeof(AVCTableDef));
176 :
177 0 : memcpy(psNewDef, psSrcDef, sizeof(AVCTableDef));
178 :
179 0 : psNewDef->pasFieldDef =
180 0 : (AVCFieldInfo *)CPLMalloc(psSrcDef->numFields * sizeof(AVCFieldInfo));
181 :
182 0 : memcpy(psNewDef->pasFieldDef, psSrcDef->pasFieldDef,
183 0 : psSrcDef->numFields * sizeof(AVCFieldInfo));
184 :
185 0 : return psNewDef;
186 : }
187 :
188 : /**********************************************************************
189 : * AVCFileExists()
190 : *
191 : * Returns TRUE if a file with the specified name exists in the
192 : * specified directory.
193 : *
194 : * For now I simply try to fopen() the file ... would it be more
195 : * efficient to use stat() ???
196 : **********************************************************************/
197 10 : GBool AVCFileExists(const char *pszPath, const char *pszName)
198 : {
199 : char *pszBuf;
200 10 : GBool bFileExists = FALSE;
201 : VSILFILE *fp;
202 :
203 10 : pszBuf = (char *)CPLMalloc(strlen(pszPath) + strlen(pszName) + 1);
204 10 : snprintf(pszBuf, strlen(pszPath) + strlen(pszName) + 1, "%s%s", pszPath,
205 : pszName);
206 :
207 10 : AVCAdjustCaseSensitiveFilename(pszBuf);
208 :
209 10 : if ((fp = VSIFOpenL(pszBuf, "rb")) != nullptr)
210 : {
211 10 : bFileExists = TRUE;
212 10 : VSIFCloseL(fp);
213 : }
214 :
215 10 : CPLFree(pszBuf);
216 :
217 10 : return bFileExists;
218 : }
219 :
220 : /**********************************************************************
221 : * AVCAdjustCaseSensitiveFilename()
222 : *
223 : * Scan a filename and its path, adjust uppercase/lowercases if
224 : * necessary, and return a reference to that filename.
225 : *
226 : * This function works on the original buffer and returns a reference to it.
227 : *
228 : * NFW: It seems like this could be made somewhat more efficient by
229 : * getting a directory listing and doing a case insensitive search in
230 : * that list rather than all this stating that can be very expensive
231 : * in some circumstances. However, at least with Carl's fix this is
232 : * somewhat faster.
233 : * see: http://bugzilla.remotesensing.org/show_bug.cgi?id=314
234 : **********************************************************************/
235 105 : char *AVCAdjustCaseSensitiveFilename(char *pszFname)
236 : {
237 : VSIStatBufL sStatBuf;
238 105 : char *pszTmpPath = nullptr;
239 : int nTotalLen, iTmpPtr;
240 : GBool bValidPath;
241 :
242 : /*-----------------------------------------------------------------
243 : * First check if the filename is OK as is.
244 : *----------------------------------------------------------------*/
245 105 : if (VSIStatL(pszFname, &sStatBuf) == 0)
246 : {
247 61 : return pszFname;
248 : }
249 :
250 44 : pszTmpPath = CPLStrdup(pszFname);
251 44 : nTotalLen = (int)strlen(pszTmpPath);
252 :
253 : /*-----------------------------------------------------------------
254 : * Remap '\\' to '/'
255 : *----------------------------------------------------------------*/
256 2336 : for (iTmpPtr = 0; iTmpPtr < nTotalLen; iTmpPtr++)
257 : {
258 2292 : if (pszTmpPath[iTmpPtr] == '\\')
259 0 : pszTmpPath[iTmpPtr] = '/';
260 : }
261 :
262 : /*-----------------------------------------------------------------
263 : * Try all lower case, check if the filename is OK as that.
264 : *----------------------------------------------------------------*/
265 2336 : for (iTmpPtr = 0; iTmpPtr < nTotalLen; iTmpPtr++)
266 : {
267 2292 : if (pszTmpPath[iTmpPtr] >= 'A' && pszTmpPath[iTmpPtr] <= 'Z')
268 132 : pszTmpPath[iTmpPtr] += 32;
269 : }
270 :
271 44 : if (VSIStatL(pszTmpPath, &sStatBuf) == 0)
272 : {
273 44 : strcpy(pszFname, pszTmpPath);
274 44 : CPLFree(pszTmpPath);
275 44 : return pszFname;
276 : }
277 :
278 : /*-----------------------------------------------------------------
279 : * Try all upper case, check if the filename is OK as that.
280 : *----------------------------------------------------------------*/
281 0 : for (iTmpPtr = 0; iTmpPtr < nTotalLen; iTmpPtr++)
282 : {
283 0 : if (pszTmpPath[iTmpPtr] >= 'a' && pszTmpPath[iTmpPtr] <= 'z')
284 0 : pszTmpPath[iTmpPtr] -= 32;
285 : }
286 :
287 0 : if (VSIStatL(pszTmpPath, &sStatBuf) == 0)
288 : {
289 0 : strcpy(pszFname, pszTmpPath);
290 0 : CPLFree(pszTmpPath);
291 0 : return pszFname;
292 : }
293 :
294 : /*-----------------------------------------------------------------
295 : * OK, file either does not exist or has the wrong cases... we'll
296 : * go backwards until we find a portion of the path that is valid.
297 : *----------------------------------------------------------------*/
298 0 : strcpy(pszTmpPath, pszFname);
299 :
300 : /*-----------------------------------------------------------------
301 : * Remap '\\' to '/'
302 : *----------------------------------------------------------------*/
303 0 : for (iTmpPtr = 0; iTmpPtr < nTotalLen; iTmpPtr++)
304 : {
305 0 : if (pszTmpPath[iTmpPtr] == '\\')
306 0 : pszTmpPath[iTmpPtr] = '/';
307 : }
308 :
309 0 : bValidPath = FALSE;
310 0 : while (iTmpPtr > 0 && !bValidPath)
311 : {
312 : /*-------------------------------------------------------------
313 : * Move back to the previous '/' separator
314 : *------------------------------------------------------------*/
315 0 : pszTmpPath[--iTmpPtr] = '\0';
316 0 : while (iTmpPtr > 0 && pszTmpPath[iTmpPtr - 1] != '/')
317 : {
318 0 : pszTmpPath[--iTmpPtr] = '\0';
319 : }
320 :
321 0 : if (iTmpPtr > 0 && VSIStatL(pszTmpPath, &sStatBuf) == 0)
322 0 : bValidPath = TRUE;
323 : }
324 :
325 0 : CPLAssert(iTmpPtr >= 0);
326 :
327 : /*-----------------------------------------------------------------
328 : * Assume that CWD is valid... so an empty path is a valid path
329 : *----------------------------------------------------------------*/
330 0 : if (iTmpPtr == 0)
331 0 : bValidPath = TRUE;
332 :
333 : /*-----------------------------------------------------------------
334 : * OK, now that we have a valid base, reconstruct the whole path
335 : * by scanning all the sub-directories.
336 : * If we get to a point where a path component does not exist then
337 : * we simply return the rest of the path as is.
338 : *----------------------------------------------------------------*/
339 0 : while (bValidPath && strlen(pszTmpPath) < (size_t)nTotalLen)
340 : {
341 0 : char **papszDir = VSIReadDir(pszTmpPath);
342 : int iEntry, iLastPartStart;
343 :
344 0 : iLastPartStart = iTmpPtr;
345 :
346 : /*-------------------------------------------------------------
347 : * Add one component to the current path
348 : *------------------------------------------------------------*/
349 0 : pszTmpPath[iTmpPtr] = pszFname[iTmpPtr];
350 0 : iTmpPtr++;
351 0 : for (; pszFname[iTmpPtr] != '\0' && pszFname[iTmpPtr] != '/'; iTmpPtr++)
352 : {
353 0 : pszTmpPath[iTmpPtr] = pszFname[iTmpPtr];
354 : }
355 :
356 0 : while (iLastPartStart < iTmpPtr && pszTmpPath[iLastPartStart] == '/')
357 0 : iLastPartStart++;
358 :
359 : /*-------------------------------------------------------------
360 : * And do a case insensitive search in the current dir...
361 : *------------------------------------------------------------*/
362 0 : for (iEntry = 0; papszDir && papszDir[iEntry]; iEntry++)
363 : {
364 0 : if (EQUAL(pszTmpPath + iLastPartStart, papszDir[iEntry]))
365 : {
366 : /* Fount it! */
367 : #ifdef CSA_BUILD
368 : // Silence false positive warning about overlapping buffers
369 : memmove(pszTmpPath + iLastPartStart, papszDir[iEntry],
370 : strlen(papszDir[iEntry]) + 1);
371 : #else
372 0 : strcpy(pszTmpPath + iLastPartStart, papszDir[iEntry]);
373 : #endif
374 0 : break;
375 : }
376 : }
377 :
378 0 : if (iTmpPtr > 0 && VSIStatL(pszTmpPath, &sStatBuf) != 0)
379 0 : bValidPath = FALSE;
380 :
381 0 : CSLDestroy(papszDir);
382 : }
383 :
384 : /*-----------------------------------------------------------------
385 : * We reached the last valid path component... just copy the rest
386 : * of the path as is.
387 : *----------------------------------------------------------------*/
388 0 : if (iTmpPtr < nTotalLen - 1)
389 : {
390 0 : strncpy(pszTmpPath + iTmpPtr, pszFname + iTmpPtr, nTotalLen - iTmpPtr);
391 : }
392 :
393 : /*-----------------------------------------------------------------
394 : * Update the source buffer and return.
395 : *----------------------------------------------------------------*/
396 0 : strcpy(pszFname, pszTmpPath);
397 0 : CPLFree(pszTmpPath);
398 :
399 0 : return pszFname;
400 : }
401 :
402 : /**********************************************************************
403 : * AVCPrintRealValue()
404 : *
405 : * Format a floating point value according to the specified coverage
406 : * precision (AVC_SINGLE/DOUBLE_PREC), and append the formatted value
407 : * to the end of the pszBuf buffer.
408 : *
409 : * The function returns the number of characters added to the buffer.
410 : **********************************************************************/
411 0 : int AVCPrintRealValue(char *pszBuf, size_t nBufLen, int nPrecision,
412 : AVCFileType eType, double dValue)
413 : {
414 : static int numExpDigits = -1;
415 0 : int nLen = 0;
416 :
417 : /* WIN32 systems' printf() for floating point output generates 3
418 : * digits exponents (ex: 1.23E+012), but E00 files must have 2 digits
419 : * exponents (ex: 1.23E+12).
420 : * Run a test (only once per prg execution) to establish the number
421 : * of exponent digits on the current platform.
422 : */
423 0 : if (numExpDigits == -1)
424 : {
425 : char szBuf[50];
426 : int i;
427 :
428 0 : CPLsnprintf(szBuf, sizeof(szBuf), "%10.7E", 123.45);
429 0 : numExpDigits = 0;
430 0 : for (i = (int)strlen(szBuf) - 1; i > 0; i--)
431 : {
432 0 : if (szBuf[i] == '+' || szBuf[i] == '-')
433 : break;
434 0 : numExpDigits++;
435 : }
436 : }
437 :
438 : /* We will append the value at the end of the current buffer contents.
439 : */
440 0 : nBufLen -= strlen(pszBuf);
441 0 : pszBuf = pszBuf + strlen(pszBuf);
442 :
443 0 : if (dValue < 0.0)
444 : {
445 0 : *pszBuf = '-';
446 0 : dValue = -1.0 * dValue;
447 : }
448 : else
449 0 : *pszBuf = ' ';
450 :
451 : /* Just to make things more complicated, double values are
452 : * output in a different format in attribute tables than in
453 : * the other files!
454 : */
455 0 : if (nPrecision == AVC_FORMAT_DBF_FLOAT)
456 : {
457 : /* Float stored in DBF table in PC coverages */
458 0 : CPLsnprintf(pszBuf + 1, nBufLen - 1, "%9.6E", dValue);
459 0 : nLen = 13;
460 : }
461 0 : else if (nPrecision == AVC_DOUBLE_PREC && eType == AVCFileTABLE)
462 : {
463 0 : CPLsnprintf(pszBuf + 1, nBufLen - 1, "%20.17E", dValue);
464 0 : nLen = 24;
465 : }
466 0 : else if (nPrecision == AVC_DOUBLE_PREC)
467 : {
468 0 : CPLsnprintf(pszBuf + 1, nBufLen - 1, "%17.14E", dValue);
469 0 : nLen = 21;
470 : }
471 : else
472 : {
473 0 : CPLsnprintf(pszBuf + 1, nBufLen - 1, "%10.7E", dValue);
474 0 : nLen = 14;
475 : }
476 :
477 : /* Adjust number of exponent digits if necessary
478 : */
479 0 : if (numExpDigits > 2)
480 : {
481 : int n;
482 0 : n = (int)strlen(pszBuf);
483 :
484 0 : pszBuf[n - numExpDigits] = pszBuf[n - 2];
485 0 : pszBuf[n - numExpDigits + 1] = pszBuf[n - 1];
486 0 : pszBuf[n - numExpDigits + 2] = '\0';
487 : }
488 :
489 : /* Just make sure that the actual output length is what we expected.
490 : */
491 0 : CPLAssert(strlen(pszBuf) == (size_t)nLen);
492 :
493 0 : return nLen;
494 : }
|