1 /**********************************************************************
2 *
3 * Project: CPL - Common Portability Library
4 * Purpose: Portable filename/path parsing, and forming ala "Glob API".
5 * Author: Frank Warmerdam, warmerda@home.com
6 *
7 **********************************************************************
8 * Copyright (c) 1999, Frank Warmerdam
9 *
10 * Permission is hereby granted, free of charge, to any person obtaining a
11 * copy of this software and associated documentation files (the "Software"),
12 * to deal in the Software without restriction, including without limitation
13 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14 * and/or sell copies of the Software, and to permit persons to whom the
15 * Software is furnished to do so, subject to the following conditions:
16 *
17 * The above copyright notice and this permission notice shall be included
18 * in all copies or substantial portions of the Software.
19 *
20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 * DEALINGS IN THE SOFTWARE.
27 **********************************************************************
28 *
29 * $Log: cpl_path.cpp,v $
30 * Revision 1.1.1.1 2006/08/21 05:52:20 dsr
31 * Initial import as opencpn, GNU Automake compliant.
32 *
33 * Revision 1.1.1.1 2006/04/19 03:23:28 dsr
34 * Rename/Import to OpenCPN
35 *
36 * Revision 1.14 2003/05/28 19:22:38 warmerda
37 * fixed docs
38 *
39 * Revision 1.13 2003/04/04 09:11:35 dron
40 * strcpy() and strcat() replaced by strncpy() and strncat().
41 * A lot of assertion on string sizes added.
42 *
43 * Revision 1.12 2002/12/13 06:14:17 warmerda
44 * fixed bug with IsRelative function
45 *
46 * Revision 1.11 2002/12/13 06:00:54 warmerda
47 * added CPLProjectRelativeFilename() and CPLIsFilenameRelative()
48 *
49 * Revision 1.10 2002/08/15 09:23:24 dron
50 * Added CPLGetDirname() function
51 *
52 * Revision 1.9 2001/08/30 21:20:49 warmerda
53 * expand tabs
54 *
55 * Revision 1.8 2001/07/18 04:00:49 warmerda
56 *
57 * Revision 1.7 2001/05/12 19:20:55 warmerda
58 * Fixed documentation of CPLGetExtension().
59 *
60 * Revision 1.6 2001/03/16 22:15:08 warmerda
61 * added CPLResetExtension
62 *
63 * Revision 1.5 2001/02/24 01:53:57 warmerda
64 * Added CPLFormCIFilename()
65 *
66 * Revision 1.4 2001/01/19 21:18:25 warmerda
67 * expanded tabs
68 *
69 * Revision 1.3 2000/01/26 17:53:36 warmerda
70 * Fixed CPLGetExtension() for filenames with no extension.
71 *
72 * Revision 1.2 2000/01/24 19:32:59 warmerda
73 * Fixed CPLGetExtension() to not include the dot.
74 *
75 * Revision 1.1 1999/10/14 19:23:39 warmerda
76 * New
77 *
78 **********************************************************************/
79
80 #include "cpl_conv.h"
81 #include "cpl_string.h"
82
83
84 /* should be size of larged possible filename */
85 #define CPL_PATH_BUF_SIZE 2048
86 static char szStaticResult[CPL_PATH_BUF_SIZE];
87
88 #ifdef WIN32
89 #define SEP_CHAR '\\'
90 #define SEP_STRING "\\"
91 #else
92 #define SEP_CHAR '/'
93 #define SEP_STRING "/"
94 #endif
95
96 /************************************************************************/
97 /* CPLFindFilenameStart() */
98 /************************************************************************/
99
CPLFindFilenameStart(const char * pszFilename)100 static int CPLFindFilenameStart( const char * pszFilename )
101
102 {
103 int iFileStart;
104
105 for( iFileStart = strlen(pszFilename);
106 iFileStart > 0
107 && pszFilename[iFileStart-1] != '/'
108 && pszFilename[iFileStart-1] != '\\';
109 iFileStart-- ) {}
110
111 return iFileStart;
112 }
113
114 /************************************************************************/
115 /* CPLGetPath() */
116 /************************************************************************/
117
118 /**
119 * Extract directory path portion of filename.
120 *
121 * Returns a string containing the directory path portion of the passed
122 * filename. If there is no path in the passed filename an empty string
123 * will be returned (not NULL).
124 *
125 * <pre>
126 * CPLGetPath( "abc/def.xyz" ) == "abc"
127 * CPLGetPath( "/abc/def/" ) == "/abc/def"
128 * CPLGetPath( "/" ) == "/"
129 * CPLGetPath( "/abc/def" ) == "/abc"
130 * CPLGetPath( "abc" ) == ""
131 * </pre>
132 *
133 * @param pszFilename the filename potentially including a path.
134 *
135 * @return Path in an internal string which must not be freed. The string
136 * may be destroyed by the next CPL filename handling call. The returned
137 * will generally not contain a trailing path separator.
138 */
139
CPLGetPath(const char * pszFilename)140 const char *CPLGetPath( const char *pszFilename )
141
142 {
143 int iFileStart = CPLFindFilenameStart(pszFilename);
144
145 CPLAssert( iFileStart < CPL_PATH_BUF_SIZE );
146
147 if( iFileStart == 0 )
148 {
149 strcpy( szStaticResult, "" );
150 return szStaticResult;
151 }
152
153 strncpy( szStaticResult, pszFilename, iFileStart );
154 szStaticResult[iFileStart] = '\0';
155
156 if( iFileStart > 1
157 && (szStaticResult[iFileStart-1] == '/'
158 || szStaticResult[iFileStart-1] == '\\') )
159 szStaticResult[iFileStart-1] = '\0';
160
161 return szStaticResult;
162 }
163
164 /************************************************************************/
165 /* CPLGetDirname() */
166 /************************************************************************/
167
168 /**
169 * Extract directory path portion of filename.
170 *
171 * Returns a string containing the directory path portion of the passed
172 * filename. If there is no path in the passed filename the dot will be
173 * returned. It is the only difference from CPLGetPath().
174 *
175 * <pre>
176 * CPLGetDirname( "abc/def.xyz" ) == "abc"
177 * CPLGetDirname( "/abc/def/" ) == "/abc/def"
178 * CPLGetDirname( "/" ) == "/"
179 * CPLGetDirname( "/abc/def" ) == "/abc"
180 * CPLGetDirname( "abc" ) == "."
181 * </pre>
182 *
183 * @param pszFilename the filename potentially including a path.
184 *
185 * @return Path in an internal string which must not be freed. The string
186 * may be destroyed by the next CPL filename handling call. The returned
187 * will generally not contain a trailing path separator.
188 */
189
CPLGetDirname(const char * pszFilename)190 const char *CPLGetDirname( const char *pszFilename )
191
192 {
193 int iFileStart = CPLFindFilenameStart(pszFilename);
194
195 CPLAssert( iFileStart < CPL_PATH_BUF_SIZE );
196
197 if( iFileStart == 0 )
198 {
199 strcpy( szStaticResult, "." );
200 return szStaticResult;
201 }
202
203 strncpy( szStaticResult, pszFilename, iFileStart );
204 szStaticResult[iFileStart] = '\0';
205
206 if( iFileStart > 1
207 && (szStaticResult[iFileStart-1] == '/'
208 || szStaticResult[iFileStart-1] == '\\') )
209 szStaticResult[iFileStart-1] = '\0';
210
211 return szStaticResult;
212 }
213
214 /************************************************************************/
215 /* CPLGetFilename() */
216 /************************************************************************/
217
218 /**
219 * Extract non-directory portion of filename.
220 *
221 * Returns a string containing the bare filename portion of the passed
222 * filename. If there is no filename (passed value ends in trailing directory
223 * separator) an empty string is returned.
224 *
225 * <pre>
226 * CPLGetFilename( "abc/def.xyz" ) == "def.xyz"
227 * CPLGetFilename( "/abc/def/" ) == ""
228 * CPLGetFilename( "abc/def" ) == "def"
229 * </pre>
230 *
231 * @param pszFullFilename the full filename potentially including a path.
232 *
233 * @return just the non-directory portion of the path in an internal string
234 * which must not be freed. The string
235 * may be destroyed by the next CPL filename handling call.
236 */
237
CPLGetFilename(const char * pszFullFilename)238 const char *CPLGetFilename( const char *pszFullFilename )
239
240 {
241 int iFileStart = CPLFindFilenameStart( pszFullFilename );
242
243 strncpy( szStaticResult, pszFullFilename + iFileStart, CPL_PATH_BUF_SIZE );
244 szStaticResult[CPL_PATH_BUF_SIZE - 1] = '\0';
245
246 return szStaticResult;
247 }
248
249 /************************************************************************/
250 /* CPLGetBasename() */
251 /************************************************************************/
252
253 /**
254 * Extract basename (non-directory, non-extension) portion of filename.
255 *
256 * Returns a string containing the file basename portion of the passed
257 * name. If there is no basename (passed value ends in trailing directory
258 * separator, or filename starts with a dot) an empty string is returned.
259 *
260 * <pre>
261 * CPLGetBasename( "abc/def.xyz" ) == "def"
262 * CPLGetBasename( "abc/def" ) == "def"
263 * CPLGetBasename( "abc/def/" ) == ""
264 * </pre>
265 *
266 * @param pszFullFilename the full filename potentially including a path.
267 *
268 * @return just the non-directory, non-extension portion of the path in
269 * an internal string which must not be freed. The string
270 * may be destroyed by the next CPL filename handling call.
271 */
272
CPLGetBasename(const char * pszFullFilename)273 const char *CPLGetBasename( const char *pszFullFilename )
274
275 {
276 int iFileStart = CPLFindFilenameStart( pszFullFilename );
277 int iExtStart, nLength;
278
279 for( iExtStart = strlen(pszFullFilename);
280 iExtStart > iFileStart && pszFullFilename[iExtStart] != '.';
281 iExtStart-- ) {}
282
283 if( iExtStart == iFileStart )
284 iExtStart = strlen(pszFullFilename);
285
286 nLength = iExtStart - iFileStart;
287
288 CPLAssert( nLength < CPL_PATH_BUF_SIZE );
289
290 strncpy( szStaticResult, pszFullFilename + iFileStart, nLength );
291 szStaticResult[nLength] = '\0';
292
293 return szStaticResult;
294 }
295
296
297 /************************************************************************/
298 /* CPLGetExtension() */
299 /************************************************************************/
300
301 /**
302 * Extract filename extension from full filename.
303 *
304 * Returns a string containing the extention portion of the passed
305 * name. If there is no extension (the filename has no dot) an empty string
306 * is returned. The returned extension will not include the period.
307 *
308 * <pre>
309 * CPLGetExtension( "abc/def.xyz" ) == "xyz"
310 * CPLGetExtension( "abc/def" ) == ""
311 * </pre>
312 *
313 * @param pszFullFilename the full filename potentially including a path.
314 *
315 * @return just the extension portion of the path in
316 * an internal string which must not be freed. The string
317 * may be destroyed by the next CPL filename handling call.
318 */
319
CPLGetExtension(const char * pszFullFilename)320 const char *CPLGetExtension( const char *pszFullFilename )
321
322 {
323 int iFileStart = CPLFindFilenameStart( pszFullFilename );
324 int iExtStart;
325
326 for( iExtStart = strlen(pszFullFilename);
327 iExtStart > iFileStart && pszFullFilename[iExtStart] != '.';
328 iExtStart-- ) {}
329
330 if( iExtStart == iFileStart )
331 iExtStart = strlen(pszFullFilename)-1;
332
333 strncpy( szStaticResult, pszFullFilename+iExtStart+1, CPL_PATH_BUF_SIZE );
334 szStaticResult[CPL_PATH_BUF_SIZE - 1] = '\0';
335
336 return szStaticResult;
337 }
338
339 /************************************************************************/
340 /* CPLResetExtension() */
341 /************************************************************************/
342
343 /**
344 * Replace the extension with the provided one.
345 *
346 * @param pszPath the input path, this string is not altered.
347 * @param pszExt the new extension to apply to the given path.
348 *
349 * @return an altered filename with the new extension. Do not
350 * modify or free the returned string. The string may be destroyed by the
351 * next CPL call.
352 */
353
CPLResetExtension(const char * pszPath,const char * pszExt)354 const char *CPLResetExtension( const char *pszPath, const char *pszExt )
355
356 {
357 int i;
358
359 /* -------------------------------------------------------------------- */
360 /* First, try and strip off any existing extension. */
361 /* -------------------------------------------------------------------- */
362 strncpy( szStaticResult, pszPath, CPL_PATH_BUF_SIZE );
363 szStaticResult[CPL_PATH_BUF_SIZE - 1] = '\0';
364 for( i = strlen(szStaticResult) - 1; i > 0; i-- )
365 {
366 if( szStaticResult[i] == '.' )
367 {
368 szStaticResult[i] = '\0';
369 break;
370 }
371
372 if( szStaticResult[i] == '/' || szStaticResult[i] == '\\'
373 || szStaticResult[i] == ':' )
374 break;
375 }
376
377 /* -------------------------------------------------------------------- */
378 /* Append the new extension. */
379 /* -------------------------------------------------------------------- */
380 CPLAssert( strlen(pszExt) + 2 < CPL_PATH_BUF_SIZE );
381
382 strcat( szStaticResult, "." );
383 strcat( szStaticResult, pszExt );
384
385 return szStaticResult;
386 }
387
388 /************************************************************************/
389 /* CPLFormFilename() */
390 /************************************************************************/
391
392 /**
393 * Build a full file path from a passed path, file basename and extension.
394 *
395 * The path, and extension are optional. The basename may in fact contain
396 * an extension if desired.
397 *
398 * <pre>
399 * CPLFormFilename("abc/xyz","def", ".dat" ) == "abc/xyz/def.dat"
400 * CPLFormFilename(NULL,"def", NULL ) == "def"
401 * CPLFormFilename(NULL,"abc/def.dat", NULL ) == "abc/def.dat"
402 * CPLFormFilename("/abc/xyz/","def.dat", NULL ) == "/abc/xyz/def.dat"
403 * </pre>
404 *
405 * @param pszPath directory path to the directory containing the file. This
406 * may be relative or absolute, and may have a trailing path separator or
407 * not. May be NULL.
408 *
409 * @param pszBasename file basename. May optionally have path and/or
410 * extension. May not be NULL.
411 *
412 * @param pszExtension file extension, optionally including the period. May
413 * be NULL.
414 *
415 * @return a fully formed filename in an internal static string. Do not
416 * modify or free the returned string. The string may be destroyed by the
417 * next CPL call.
418 */
419
CPLFormFilename(const char * pszPath,const char * pszBasename,const char * pszExtension)420 const char *CPLFormFilename( const char * pszPath,
421 const char * pszBasename,
422 const char * pszExtension )
423
424 {
425 const char *pszAddedPathSep = "";
426 const char *pszAddedExtSep = "";
427
428 if( pszPath == NULL )
429 pszPath = "";
430 else if( strlen(pszPath) > 0
431 && pszPath[strlen(pszPath)-1] != '/'
432 && pszPath[strlen(pszPath)-1] != '\\' )
433 pszAddedPathSep = SEP_STRING;
434
435 if( pszExtension == NULL )
436 pszExtension = "";
437 else if( pszExtension[0] != '.' && strlen(pszExtension) > 0 )
438 pszAddedExtSep = ".";
439
440 CPLAssert( strlen(pszPath) + strlen(pszAddedPathSep) +
441 strlen(pszBasename) + strlen(pszAddedExtSep) +
442 strlen(pszExtension) + 1 < CPL_PATH_BUF_SIZE );
443
444 strncpy( szStaticResult, pszPath, CPL_PATH_BUF_SIZE - 1 );
445 strncat( szStaticResult, pszAddedPathSep, sizeof(szStaticResult)-strlen(szStaticResult)-1);
446 strncat( szStaticResult, pszBasename, sizeof(szStaticResult)-strlen(szStaticResult)-1);
447 strncat( szStaticResult, pszAddedExtSep, sizeof(szStaticResult)-strlen(szStaticResult)-1);
448 strncat( szStaticResult, pszExtension, sizeof(szStaticResult)-strlen(szStaticResult)-1);
449 szStaticResult[CPL_PATH_BUF_SIZE - 1] = '\0';
450
451 return szStaticResult;
452 }
453
454 /************************************************************************/
455 /* CPLFormCIFilename() */
456 /************************************************************************/
457
458 /**
459 * Case insensitive file searching, returing full path.
460 *
461 * This function tries to return the path to a file regardless of
462 * whether the file exactly matches the basename, and extension case, or
463 * is all upper case, or all lower case. The path is treated as case
464 * sensitive. This function is equivelent to CPLFormFilename() on
465 * case insensitive file systems (like Windows).
466 *
467 * @param pszPath directory path to the directory containing the file. This
468 * may be relative or absolute, and may have a trailing path separator or
469 * not. May be NULL.
470 *
471 * @param pszBasename file basename. May optionally have path and/or
472 * extension. May not be NULL.
473 *
474 * @param pszExtension file extension, optionally including the period. May
475 * be NULL.
476 *
477 * @return a fully formed filename in an internal static string. Do not
478 * modify or free the returned string. The string may be destroyed by the
479 * next CPL call.
480 */
481
CPLFormCIFilename(const char * pszPath,const char * pszBasename,const char * pszExtension)482 const char *CPLFormCIFilename( const char * pszPath,
483 const char * pszBasename,
484 const char * pszExtension )
485
486 {
487 #ifdef WIN32
488 return CPLFormFilename( pszPath, pszBasename, pszExtension );
489 #else
490 const char *pszAddedExtSep = "";
491 char *pszFilename;
492 const char *pszFullPath;
493 int nLen = strlen(pszBasename)+2, i;
494 FILE *fp;
495
496 if( pszExtension != NULL )
497 nLen += strlen(pszExtension);
498
499 pszFilename = (char *) CPLMalloc(nLen);
500
501 if( pszExtension == NULL )
502 pszExtension = "";
503 else if( pszExtension[0] != '.' && strlen(pszExtension) > 0 )
504 pszAddedExtSep = ".";
505
506 sprintf( pszFilename, "%s%s%s",
507 pszBasename, pszAddedExtSep, pszExtension );
508
509 pszFullPath = CPLFormFilename( pszPath, pszFilename, NULL );
510 fp = VSIFOpen( pszFullPath, "r" );
511 if( fp == NULL )
512 {
513 for( i = 0; pszFilename[i] != '\0'; i++ )
514 {
515 if( pszFilename[i] >= 'a' && pszFilename[i] <= 'z' )
516 pszFilename[i] = pszFilename[i] + 'A' - 'a';
517 }
518
519 pszFullPath = CPLFormFilename( pszPath, pszFilename, NULL );
520 fp = VSIFOpen( pszFullPath, "r" );
521 }
522
523 if( fp == NULL )
524 {
525 for( i = 0; pszFilename[i] != '\0'; i++ )
526 {
527 if( pszFilename[i] >= 'A' && pszFilename[i] <= 'Z' )
528 pszFilename[i] = pszFilename[i] + 'a' - 'A';
529 }
530
531 pszFullPath = CPLFormFilename( pszPath, pszFilename, NULL );
532 fp = VSIFOpen( pszFullPath, "r" );
533 }
534
535 if( fp != NULL )
536 VSIFClose( fp );
537 else
538 pszFullPath = CPLFormFilename( pszPath, pszBasename, pszExtension );
539
540 CPLFree( pszFilename );
541
542 return pszFullPath;
543 #endif
544 }
545
546 /************************************************************************/
547 /* CPLProjectRelativeFilename() */
548 /************************************************************************/
549
550 /**
551 * Find a file relative to a project file.
552 *
553 * Given the path to a "project" directory, and a path to a secondary file
554 * referenced from that project, build a path to the secondary file
555 * that the current application can use. If the secondary path is already
556 * absolute, rather than relative, then it will be returned unaltered.
557 *
558 * Examples:
559 * <pre>
560 * CPLProjectRelativeFilename("abc/def","tmp/abc.gif") == "abc/def/tmp/abc.gif"
561 * CPLProjectRelativeFilename("abc/def","/tmp/abc.gif") == "/tmp/abc.gif"
562 * CPLProjectRelativeFilename("/xy", "abc.gif") == "/xy/abc.gif"
563 * CPLProjectRelativeFilename("/abc/def","../abc.gif") == "/abc/def/../abc.gif"
564 * CPLProjectRelativeFilename("C:\WIN","abc.gif") == "C:\WIN\abc.gif"
565 * </pre>
566 *
567 * @param pszProjectDir the directory relative to which the secondary files
568 * path should be interpreted.
569 * @param pszSecondaryFilename the filename (potentially with path) that
570 * is to be interpreted relative to the project directory.
571 *
572 * @return a composed path to the secondary file. The returned string is
573 * internal and should not be altered, freed, or depending on past the next
574 * CPL call.
575 */
576
CPLProjectRelativeFilename(const char * pszProjectDir,const char * pszSecondaryFilename)577 const char *CPLProjectRelativeFilename( const char *pszProjectDir,
578 const char *pszSecondaryFilename )
579
580 {
581 if( !CPLIsFilenameRelative( pszSecondaryFilename ) )
582 return pszSecondaryFilename;
583
584 if( pszProjectDir == NULL || strlen(pszProjectDir) == 0 )
585 return pszSecondaryFilename;
586
587 strncpy( szStaticResult, pszProjectDir, CPL_PATH_BUF_SIZE );
588 szStaticResult[CPL_PATH_BUF_SIZE - 1] = '\0';
589
590 if( pszProjectDir[strlen(pszProjectDir)-1] != '/'
591 && pszProjectDir[strlen(pszProjectDir)-1] != '\\' )
592 {
593 CPLAssert( strlen(SEP_STRING) + 1 < CPL_PATH_BUF_SIZE );
594
595 strcat( szStaticResult, SEP_STRING );
596 }
597
598 CPLAssert( strlen(pszSecondaryFilename) + 1 < CPL_PATH_BUF_SIZE );
599
600 strcat( szStaticResult, pszSecondaryFilename );
601
602 return szStaticResult;
603 }
604
605 /************************************************************************/
606 /* CPLIsFilenameRelative() */
607 /************************************************************************/
608
609 /**
610 * Is filename relative or absolute?
611 *
612 * The test is filesystem convention agnostic. That is it will test for
613 * Unix style and windows style path conventions regardless of the actual
614 * system in use.
615 *
616 * @param pszFilename the filename with path to test.
617 *
618 * @return TRUE if the filename is relative or FALSE if it is absolute.
619 */
620
CPLIsFilenameRelative(const char * pszFilename)621 int CPLIsFilenameRelative( const char *pszFilename )
622
623 {
624 if( (strlen(pszFilename) > 2 && strncmp(pszFilename+1,":\\",2) == 0)
625 || pszFilename[0] == '\\'
626 || pszFilename[0] == '/' )
627 return FALSE;
628 else
629 return TRUE;
630 }
631