-
-
Notifications
You must be signed in to change notification settings - Fork 924
/
PathFunc.pas
538 lines (496 loc) · 16.2 KB
/
PathFunc.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
unit PathFunc;
{
Inno Setup
Copyright (C) 1997-2024 Jordan Russell
Portions by Martijn Laan
For conditions of distribution and use, see LICENSE.TXT.
This unit provides some path-related functions.
}
interface
function AddBackslash(const S: String): String;
function PathChangeExt(const Filename, Extension: String): String;
function PathCharCompare(const S1, S2: PChar): Boolean;
function PathCharIsSlash(const C: Char): Boolean;
function PathCharIsTrailByte(const S: String; const Index: Integer): Boolean;
function PathCharLength(const S: String; const Index: Integer): Integer;
function PathCombine(const Dir, Filename: String): String;
function PathCompare(const S1, S2: String): Integer;
function PathDrivePartLength(const Filename: String): Integer;
function PathDrivePartLengthEx(const Filename: String;
const IncludeSignificantSlash: Boolean): Integer;
function PathExpand(const Filename: String): String; overload;
function PathExpand(const Filename: String; out ExpandedFilename: String): Boolean; overload;
function PathExtensionPos(const Filename: String): Integer;
function PathExtractDir(const Filename: String): String;
function PathExtractDrive(const Filename: String): String;
function PathExtractExt(const Filename: String): String;
function PathExtractName(const Filename: String): String;
function PathExtractPath(const Filename: String): String;
function PathIsRooted(const Filename: String): Boolean;
function PathLastChar(const S: String): PChar;
function PathLastDelimiter(const Delimiters, S: string): Integer;
function PathLowercase(const S: String): String;
function PathNormalizeSlashes(const S: String): String;
function PathPathPartLength(const Filename: String;
const IncludeSlashesAfterPath: Boolean): Integer;
function PathPos(Ch: Char; const S: String): Integer;
function PathStartsWith(const S, AStartsWith: String): Boolean;
function PathStrNextChar(const S: PChar): PChar;
function PathStrPrevChar(const Start, Current: PChar): PChar;
function PathStrScan(const S: PChar; const C: Char): PChar;
function RemoveBackslash(const S: String): String;
function RemoveBackslashUnlessRoot(const S: String): String;
implementation
uses
Windows, SysUtils;
function AddBackslash(const S: String): String;
{ Returns S plus a trailing backslash, unless S is an empty string or already
ends in a backslash/slash. }
begin
if (S <> '') and not PathCharIsSlash(PathLastChar(S)^) then
Result := S + '\'
else
Result := S;
end;
function PathCharLength(const S: String; const Index: Integer): Integer;
{ Returns the length in characters of the character at Index in S. }
begin
Result := 1;
end;
function PathCharIsSlash(const C: Char): Boolean;
{ Returns True if C is a backslash or slash. }
begin
Result := (C = '\') or (C = '/');
end;
function PathCharIsTrailByte(const S: String; const Index: Integer): Boolean;
{ Returns False if S[Index] is a single byte character or a lead byte.
Returns True otherwise (i.e. it must be a trail byte). }
var
I: Integer;
begin
I := 1;
while I <= Index do begin
if I = Index then begin
Result := False;
Exit;
end;
Inc(I, PathCharLength(S, I));
end;
Result := True;
end;
function PathCharCompare(const S1, S2: PChar): Boolean;
{ Compares two first characters, and returns True if they are equal. }
var
N, I: Integer;
begin
N := PathStrNextChar(S1) - S1;
if N = PathStrNextChar(S2) - S2 then begin
for I := 0 to N-1 do begin
if S1[I] <> S2[I] then begin
Result := False;
Exit;
end;
end;
Result := True;
end else
Result := False;
end;
function PathChangeExt(const Filename, Extension: String): String;
{ Takes Filename, removes any existing extension, then adds the extension
specified by Extension and returns the resulting string. }
var
I: Integer;
begin
I := PathExtensionPos(Filename);
if I = 0 then
Result := Filename + Extension
else
Result := Copy(Filename, 1, I - 1) + Extension;
end;
function PathCombine(const Dir, Filename: String): String;
{ Combines a directory and filename into a path.
If Dir is empty, it just returns Filename.
If Filename is empty, it returns an empty string (ignoring Dir).
If Filename begins with a drive letter or slash, it returns Filename
(ignoring Dir).
If Dir specifies only a drive letter and colon ('c:'), it returns
Dir + Filename.
Otherwise, it returns the equivalent of AddBackslash(Dir) + Filename. }
var
I: Integer;
begin
if (Dir = '') or (Filename = '') or PathIsRooted(Filename) then
Result := Filename
else begin
I := PathCharLength(Dir, 1) + 1;
if ((I = Length(Dir)) and (Dir[I] = ':')) or
PathCharIsSlash(PathLastChar(Dir)^) then
Result := Dir + Filename
else
Result := Dir + '\' + Filename;
end;
end;
function PathCompare(const S1, S2: String): Integer;
{ Compares two filenames, and returns 0 if they are equal. }
begin
Result := CompareStr(PathLowercase(S1), PathLowercase(S2));
end;
function PathDrivePartLength(const Filename: String): Integer;
begin
Result := PathDrivePartLengthEx(Filename, False);
end;
function PathDrivePartLengthEx(const Filename: String;
const IncludeSignificantSlash: Boolean): Integer;
{ Returns length of the drive portion of Filename, or 0 if there is no drive
portion.
If IncludeSignificantSlash is True, the drive portion can include a trailing
slash if it is significant to the meaning of the path (i.e. 'x:' and 'x:\'
are not equivalent, nor are '\' and '').
If IncludeSignificantSlash is False, the function works as follows:
'x:file' -> 2 ('x:')
'x:\file' -> 2 ('x:')
'\\server\share\file' -> 14 ('\\server\share')
'\file' -> 0 ('')
If IncludeSignificantSlash is True, the function works as follows:
'x:file' -> 2 ('x:')
'x:\file' -> 3 ('x:\')
'\\server\share\file' -> 14 ('\\server\share')
'\file' -> 1 ('\')
}
var
Len, I, C: Integer;
begin
Len := Length(Filename);
{ \\server\share }
if (Len >= 2) and PathCharIsSlash(Filename[1]) and PathCharIsSlash(Filename[2]) then begin
I := 3;
C := 0;
while I <= Len do begin
if PathCharIsSlash(Filename[I]) then begin
Inc(C);
if C >= 2 then
Break;
repeat
Inc(I);
{ And skip any additional consecutive slashes: }
until (I > Len) or not PathCharIsSlash(Filename[I]);
end
else
Inc(I, PathCharLength(Filename, I));
end;
Result := I - 1;
Exit;
end;
{ \ }
{ Note: Test this before 'x:' since '\:stream' means access stream 'stream'
on the root directory of the current drive, not access drive '\:' }
if (Len >= 1) and PathCharIsSlash(Filename[1]) then begin
if IncludeSignificantSlash then
Result := 1
else
Result := 0;
Exit;
end;
{ x: }
if Len > 0 then begin
I := PathCharLength(Filename, 1) + 1;
if (I <= Len) and (Filename[I] = ':') then begin
if IncludeSignificantSlash and (I < Len) and PathCharIsSlash(Filename[I+1]) then
Result := I+1
else
Result := I;
Exit;
end;
end;
Result := 0;
end;
function PathIsRooted(const Filename: String): Boolean;
{ Returns True if Filename begins with a slash or drive ('x:').
Equivalent to: PathDrivePartLengthEx(Filename, True) <> 0 }
var
Len, I: Integer;
begin
Result := False;
Len := Length(Filename);
if Len > 0 then begin
{ \ or \\ }
if PathCharIsSlash(Filename[1]) then
Result := True
else begin
{ x: }
I := PathCharLength(Filename, 1) + 1;
if (I <= Len) and (Filename[I] = ':') then
Result := True;
end;
end;
end;
function PathPathPartLength(const Filename: String;
const IncludeSlashesAfterPath: Boolean): Integer;
{ Returns length of the path portion of Filename, or 0 if there is no path
portion.
Note these differences from Delphi's ExtractFilePath function:
- The result will never be less than what PathDrivePartLength returns.
If you pass a UNC root path, e.g. '\\server\share', it will return the
length of the entire string, NOT the length of '\\server\'.
- If you pass in a filename with a reference to an NTFS alternate data
stream, e.g. 'abc:def', it will return the length of the entire string,
NOT the length of 'abc:'. }
var
LastCharToKeep, Len, I: Integer;
begin
Result := PathDrivePartLengthEx(Filename, True);
LastCharToKeep := Result;
Len := Length(Filename);
I := Result + 1;
while I <= Len do begin
if PathCharIsSlash(Filename[I]) then begin
if IncludeSlashesAfterPath then
Result := I
else
Result := LastCharToKeep;
Inc(I);
end
else begin
Inc(I, PathCharLength(Filename, I));
LastCharToKeep := I-1;
end;
end;
end;
function PathExpand(const Filename: String; out ExpandedFilename: String): Boolean;
{ Like Delphi's ExpandFileName, but does proper error checking. }
var
Res: Integer;
FilePart: PChar;
Buf: array[0..4095] of Char;
begin
DWORD(Res) := GetFullPathName(PChar(Filename), SizeOf(Buf) div SizeOf(Buf[0]),
Buf, FilePart);
Result := (Res > 0) and (Res < SizeOf(Buf) div SizeOf(Buf[0]));
if Result then
SetString(ExpandedFilename, Buf, Res)
end;
function PathExpand(const Filename: String): String;
begin
if not PathExpand(Filename, Result) then
Result := Filename;
end;
function PathExtensionPos(const Filename: String): Integer;
{ Returns index of the last '.' character in the filename portion of Filename,
or 0 if there is no '.' in the filename portion.
Note: Filename is assumed to NOT include an NTFS alternate data stream name
(i.e. 'filename:stream'). }
var
Len, I: Integer;
begin
Result := 0;
Len := Length(Filename);
I := PathPathPartLength(Filename, True) + 1;
while I <= Len do begin
if Filename[I] = '.' then begin
Result := I;
Inc(I);
end
else
Inc(I, PathCharLength(Filename, I));
end;
end;
function PathExtractDir(const Filename: String): String;
{ Like PathExtractPath, but strips any trailing slashes, unless the resulting
path is the root directory of a drive (i.e. 'C:\' or '\'). }
var
I: Integer;
begin
I := PathPathPartLength(Filename, False);
Result := Copy(Filename, 1, I);
end;
function PathExtractDrive(const Filename: String): String;
{ Returns the drive portion of Filename (either 'x:' or '\\server\share'),
or an empty string if there is no drive portion. }
var
L: Integer;
begin
L := PathDrivePartLength(Filename);
if L = 0 then
Result := ''
else
Result := Copy(Filename, 1, L);
end;
function PathExtractExt(const Filename: String): String;
{ Returns the extension portion of the last component of Filename (e.g. '.txt')
or an empty string if there is no extension. }
var
I: Integer;
begin
I := PathExtensionPos(Filename);
if I = 0 then
Result := ''
else
Result := Copy(Filename, I, Maxint);
end;
function PathExtractName(const Filename: String): String;
{ Returns the filename portion of Filename (e.g. 'filename.txt'). If Filename
ends in a slash or consists only of a drive part, the result will be an empty
string.
This function is essentially the opposite of PathExtractPath. }
var
I: Integer;
begin
I := PathPathPartLength(Filename, True);
Result := Copy(Filename, I + 1, Maxint);
end;
function PathExtractPath(const Filename: String): String;
{ Returns the path portion of Filename (e.g. 'c:\dir\'). If Filename contains
no drive part or slash, the result will be an empty string.
This function is essentially the opposite of PathExtractName. }
var
I: Integer;
begin
I := PathPathPartLength(Filename, True);
Result := Copy(Filename, 1, I);
end;
function PathLastChar(const S: String): PChar;
{ Returns pointer to last character in the string. Returns nil if the string is
empty. }
begin
if S = '' then
Result := nil
else
Result := PathStrPrevChar(Pointer(S), @S[Length(S)+1]);
end;
function PathLastDelimiter(const Delimiters, S: string): Integer;
{ Returns the index of the last occurrence in S of one of the characters in
Delimiters, or 0 if none were found.
Note: S is allowed to contain null characters. }
var
P, E: PChar;
begin
Result := 0;
if (S = '') or (Delimiters = '') then
Exit;
P := Pointer(S);
E := @P[Length(S)];
while P < E do begin
if P^ <> #0 then begin
if StrScan(PChar(Pointer(Delimiters)), P^) <> nil then
Result := (P - PChar(Pointer(S))) + 1;
P := PathStrNextChar(P);
end
else
Inc(P);
end;
end;
function PathLowercase(const S: String): String;
{ Converts the specified path name to lowercase }
begin
Result := AnsiLowerCase(S);
end;
function PathPos(Ch: Char; const S: String): Integer;
var
Len, I: Integer;
begin
Len := Length(S);
I := 1;
while I <= Len do begin
if S[I] = Ch then begin
Result := I;
Exit;
end;
Inc(I, PathCharLength(S, I));
end;
Result := 0;
end;
function PathNormalizeSlashes(const S: String): String;
{ Returns S minus any superfluous slashes, and with any forward slashes
converted to backslashes. For example, if S is 'C:\\\some//path', it returns
'C:\some\path'. Does not remove a double backslash at the beginning of the
string, since that signifies a UNC path. }
var
Len, I: Integer;
begin
Result := S;
Len := Length(Result);
I := 1;
while I <= Len do begin
if Result[I] = '/' then
Result[I] := '\';
Inc(I, PathCharLength(Result, I));
end;
I := 1;
while I < Length(Result) do begin
if (Result[I] = '\') and (Result[I+1] = '\') and (I > 1) then
Delete(Result, I+1, 1)
else
Inc(I, PathCharLength(Result, I));
end;
end;
function PathStartsWith(const S, AStartsWith: String): Boolean;
{ Returns True if S starts with (or is equal to) AStartsWith. Uses path casing
rules. }
var
AStartsWithLen: Integer;
begin
AStartsWithLen := Length(AStartsWith);
if Length(S) = AStartsWithLen then
Result := (PathCompare(S, AStartsWith) = 0)
else if (Length(S) > AStartsWithLen) and not PathCharIsTrailByte(S, AStartsWithLen+1) then
Result := (PathCompare(Copy(S, 1, AStartsWithLen), AStartsWith) = 0)
else
Result := False;
end;
function PathStrNextChar(const S: PChar): PChar;
{ Returns pointer to the character after S, unless S points to a null (#0). }
begin
Result := S;
if Result^ <> #0 then
Inc(Result);
end;
function PathStrPrevChar(const Start, Current: PChar): PChar;
{ Returns pointer to the character before Current, unless Current = Start. }
begin
Result := Current;
if Result > Start then
Dec(Result);
end;
function PathStrScan(const S: PChar; const C: Char): PChar;
{ Returns pointer to first occurrence of C in S, or nil if there are no
occurrences. As with StrScan, specifying #0 for the search character is legal. }
begin
Result := S;
while Result^ <> C do begin
if Result^ = #0 then begin
Result := nil;
Break;
end;
Result := PathStrNextChar(Result);
end;
end;
function RemoveBackslash(const S: String): String;
{ Returns S minus any trailing slashes. Use of this function is discouraged;
use RemoveBackslashUnlessRoot instead when working with file system paths. }
var
I: Integer;
begin
I := Length(S);
while (I > 0) and PathCharIsSlash(PathStrPrevChar(Pointer(S), @S[I+1])^) do
Dec(I);
if I = Length(S) then
Result := S
else
Result := Copy(S, 1, I);
end;
function RemoveBackslashUnlessRoot(const S: String): String;
{ Returns S minus any trailing slashes, unless S specifies the root directory
of a drive (i.e. 'C:\' or '\'), in which case it leaves 1 slash. }
var
DrivePartLen, I: Integer;
begin
DrivePartLen := PathDrivePartLengthEx(S, True);
I := Length(S);
while (I > DrivePartLen) and PathCharIsSlash(PathStrPrevChar(Pointer(S), @S[I+1])^) do
Dec(I);
if I = Length(S) then
Result := S
else
Result := Copy(S, 1, I);
end;
end.