-
Notifications
You must be signed in to change notification settings - Fork 13
/
GetFullPath.m
341 lines (310 loc) · 12.1 KB
/
GetFullPath.m
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
function File = GetFullPath(File, Style)
% GetFullPath - Get absolute canonical path of a file or folder
% Absolute path names are safer than relative paths, when e.g. a GUI or TIMER
% callback changes the current directory. Only canonical paths without "." and
% ".." can be recognized uniquely.
% Long path names (>259 characters) require a magic initial key "\\?\" to be
% handled by Windows API functions, e.g. for Matlab's FOPEN, DIR and EXIST.
%
% FullName = GetFullPath(Name, Style)
% INPUT:
% Name: CHAR vector, cell string or strin, absolute or relative name of a
% file or folder. The path need not exist. Unicode, UNC paths and long
% names are supported.
% Style: Style of the output as CHAR vector, optional, default: 'auto'.
% 'auto': Add '\\?\' or '\\?\UNC\' for long names on demand.
% 'lean': Magic string is not added.
% 'fat': Magic string is added for short names also.
% The Style is ignored when not running under Windows.
%
% OUTPUT:
% FullName: Absolute canonical path name as CHAR vector or cell string.
% If name is '', the current directory is replied.
% '\\?\' or '\\?\UNC' is added on demand.
%
% NOTE: The M- and the MEX-version create the same results, the faster MEX
% function works under Windows only.
% Some functions of the Windows-API still do not support long file names.
% E.g. the Recycler and the Windows Explorer fail even with the magic '\\?\'
% prefix. Some functions of Matlab accept 260 characters (value of MAX_PATH),
% some at 259 already. Don't blame me.
% The 'fat' style is useful e.g. when Matlab's DIR command is called for a
% folder with les than 260 characters, but together with the file name this
% limit is exceeded. Then "dir(GetFullPath([folder, '\*.*], 'fat'))" helps.
%
% EXAMPLES:
% cd(tempdir); % Assumed as 'C:\Temp' here
% GetFullPath('File.Ext') % 'C:\Temp\File.Ext'
% GetFullPath('..\File.Ext') % 'C:\File.Ext'
% GetFullPath('..\..\File.Ext') % 'C:\File.Ext'
% GetFullPath('.\File.Ext') % 'C:\Temp\File.Ext'
% GetFullPath('*.txt') % 'C:\Temp\*.txt'
% GetFullPath('..') % 'C:\'
% GetFullPath('..\..\..') % 'C:\'
% GetFullPath('Folder\') % 'C:\Temp\Folder\'
% GetFullPath('D:\A\..\B') % 'D:\B'
% GetFullPath('\\Server\Folder\Sub\..\File.ext')
% % '\\Server\Folder\File.ext'
% GetFullPath({'..', 'new'}) % {'C:\', 'C:\Temp\new'}
% GetFullPath('.', 'fat') % '\\?\C:\Temp\File.Ext'
%
% COMPILE:
% Automatic: InstallMex GetFullPath.c uTest_GetFullPath
% Manual: mex -O GetFullPath.c
% Download: http://www.n-simon.de/mex
% Run the unit-test uTest_GetFullPath after compiling.
%
% Alternatives:
% .NET (thanks Urs), more features, ~50 times slower
% FInfo = System.IO.FileInfo(File);
% FullFile = char(FInfo.FullName)
% Java, ~6 times slower, does not convert realtive paths:
% javaF = java.io.File(File);
% FullFile = javaF.getAbsolutePath()
%
% Tested: Matlab 2009a, 2015b(32/64), 2016b, 2018b, Win7/10
% Compiler: LCC2.4/3.8, BCC5.5, OWC1.8, MSVC2008/2010
% Assumed Compatibility: higher Matlab versions
% Author: Jan Simon, Heidelberg, (C) 2009-2021 matlab.2010(a)n(MINUS)simon.de
%
% See also: CD, FULLFILE, FILEPARTS.
% $JRev: R-U V:046 Sum:bivvMoNgkjmz Date:03-May-2021 22:54:45 $
% $License: BSD (use/copy/change/redistribute on own risk, mention the author) $
% $UnitTest: uTest_GetFullPath $
% $File: Tools\GLFile\GetFullPath.m $
% History:
% 001: 20-Apr-2010 22:28, Successor of Rel2AbsPath.
% 010: 27-Jul-2008 21:59, Consider leading separator in M-version also.
% 011: 24-Jan-2011 12:11, Cell strings, '~File' under linux.
% Check of input types in the M-version.
% 015: 31-Mar-2011 10:48, BUGFIX: Accept [] as input as in the Mex version.
% Thanks to Jiro Doke, who found this bug by running the test function for
% the M-version.
% 020: 18-Oct-2011 00:57, BUGFIX: Linux version created bad results.
% Thanks to Daniel.
% 024: 10-Dec-2011 14:00, Care for long names under Windows in M-version.
% Improved the unittest function for Linux. Thanks to Paul Sexton.
% 025: 09-Aug-2012 14:00, In MEX: Paths starting with "\\" can be non-UNC.
% The former version treated "\\?\C:\<longpath>\file" as UNC path and
% replied "\\?\UNC\?\C:\<longpath>\file".
% 032: 12-Jan-2013 21:16, 'auto', 'lean' and 'fat' style.
% 038: 19-May-2019 17:25, BUGFIX, Thanks HHang Li, "File(7:..." -> "File(8:..."
% 044: 02-May-2021 17:47. File name can be a STRING.
% Initialize: ==================================================================
% Do the work: =================================================================
% #############################################
% ### USE THE MUCH FASTER MEX ON WINDOWS!!! ###
% #############################################
% Difference between M- and Mex-version:
% - Mex does not work under MacOS/Unix.
% - Mex calls Windows API function GetFullPath.
% - Mex is much faster.
% Magic prefix for long Windows names:
if nargin < 2
Style = 'auto';
end
% Handle cell strings:
% NOTE: It is faster to create a function @cell\GetFullPath.m under Linux, but
% under Windows this would shadow the fast C-Mex.
if ~ischar(File)
if isa(File, 'cell')
for iC = 1:numel(File)
File{iC} = GetFullPath(File{iC}, Style);
end
return;
elseif isa(File, 'string')
for iC = 1:numel(File)
File(iC) = GetFullPath(File{iC}, Style);
end
return;
elseif ~isempty(File) % Non-empty inputs must be strings
error(['JSimon:', mfilename, ':BadTypeInput1'], ...
'*** %s: File name must be: CHAR, cell string or string.', mfilename);
end
end
% Check this once only:
isWIN = strncmpi(computer, 'PC', 2);
MAX_PATH = 260;
% Warn once per session (disable this under Linux/MacOS):
persistent hasDataRead
if isempty(hasDataRead)
% Test this once only - there is no relation to the existence of DATAREAD!
if isWIN
% Show a warning, if the slower Matlab version is used - commented, because
% this is not a problem and it might be even useful when the MEX-folder is
% not inlcuded in the path yet.
warning(['JSimon:', mfilename, ':NoMex'], ...
['GetFullPath: Using slower Matlab-version instead of fast Mex.', ...
newline, 'Compile: InstallMex GetFullPath.c']);
end
% DATAREAD is deprecated in 2011b, but still available. In Matlab 6.5, REGEXP
% does not know the 'split' command, therefore DATAREAD is preferred:
hasDataRead = ~isempty(which('dataread'));
end
if isempty(File) % Accept empty matrix as input:
File = cd;
return;
end
if isWIN % Windows: -----------------------------------------------------------
FSep = '\';
File = strrep(File, '/', FSep);
% Remove the magic key on demand, it is appended finally again:
if strncmp(File, '\\?\', 4)
if strncmpi(File, '\\?\UNC\', 8)
% [BUGFIX] 19-May-2019, Thanks HHang Li, "File(7:..." -> "File(8:..."
File = ['\', File(8:length(File))]; % Two leading backslashes!
else
File = File(5:length(File));
end
end
isUNC = strncmp(File, '\\', 2);
FileLen = length(File);
if isUNC == 0 % File is not a UNC path
% Leading file separator means relative to current drive or base folder:
ThePath = cd;
if File(1) == FSep
if strncmp(ThePath, '\\', 2) % Current directory is a UNC path
sepInd = strfind(ThePath, '\');
ThePath = ThePath(1:sepInd(4));
else
ThePath = ThePath(1:3); % Drive letter only
end
end
if FileLen < 2 || File(2) ~= ':' % Does not start with drive letter
if ThePath(length(ThePath)) ~= FSep
if File(1) ~= FSep
File = [ThePath, FSep, File];
else % File starts with separator:
File = [ThePath, File];
end
else % Current path ends with separator:
if File(1) ~= FSep
File = [ThePath, File];
else % File starts with separator:
ThePath(length(ThePath)) = [];
File = [ThePath, File];
end
end
elseif FileLen == 2 && File(2) == ':' % "C:" current directory on C!
% "C:" is the current directory on the C-disk, even if the current
% directory is on another disk! This was ignored in Matlab 6.5, but
% modern versions considers this strange behaviour.
if strncmpi(ThePath, File, 2)
File = ThePath;
else
try
backCD = cd;
File = cd(cd(File));
cd(backCD);
catch ME
if exist(File, 'dir') % No idea what could cause an error then!
rethrow(ME);
else % Reply "K:\" for not existing disk:
File = [File, FSep];
end
end
end
end
end
else % Linux, MacOS: ---------------------------------------------------
FSep = '/';
File = strrep(File, '\', FSep);
if strcmp(File, '~') || strncmp(File, '~/', 2) % Home directory:
HomeDir = getenv('HOME');
if ~isempty(HomeDir)
File(1) = [];
File = [HomeDir, File];
end
elseif strncmpi(File, FSep, 1) == 0
% Append relative path to current folder:
ThePath = cd;
if ThePath(length(ThePath)) == FSep
File = [ThePath, File];
else
File = [ThePath, FSep, File];
end
end
end
% Care for "\." and "\.." - no efficient algorithm, but the fast Mex is
% recommended at all!
if ~isempty(strfind(File, [FSep, '.'])) %#ok<STREMP>
if isWIN
if strncmp(File, '\\', 2) % UNC path
index = strfind(File, '\');
if length(index) < 4 % UNC path without separator after the folder:
return;
end
Drive = File(1:index(4));
File(1:index(4)) = [];
else
Drive = File(1:3);
File(1:3) = [];
end
else % Unix, MacOS:
isUNC = false;
Drive = FSep;
File(1) = [];
end
hasTrailFSep = (File(length(File)) == FSep);
if hasTrailFSep
File(length(File)) = [];
end
if hasDataRead
if isWIN % Need "\\" as separator:
C = dataread('string', File, '%s', 'delimiter', '\\'); %#ok<REMFF1>
else
C = dataread('string', File, '%s', 'delimiter', FSep); %#ok<REMFF1>
end
else % Use the slower REGEXP, when DATAREAD is not available anymore:
C = regexp(File, FSep, 'split');
end
% Remove '\.\' directly without side effects:
C(strcmp(C, '.')) = [];
% Remove '\..' with the parent recursively:
R = 1:length(C);
for dd = reshape(find(strcmp(C, '..')), 1, [])
index = find(R == dd);
R(index) = [];
if index > 1
R(index - 1) = [];
end
end
if isempty(R)
File = Drive;
if isUNC && ~hasTrailFSep
File(length(File)) = [];
end
elseif isWIN
% If you have CStr2String, use the faster:
% File = CStr2String(C(R), FSep, hasTrailFSep);
File = sprintf('%s\\', C{R});
if hasTrailFSep
File = [Drive, File];
else
File = [Drive, File(1:length(File) - 1)];
end
else % Unix:
File = [Drive, sprintf('%s/', C{R})];
if ~hasTrailFSep
File(length(File)) = [];
end
end
end
% "Very" long names under Windows:
if isWIN
if ~ischar(Style)
error(['JSimon:', mfilename, ':BadTypeInput2'], ...
'*** %s: Input must be a CHAR vector or cell string', mfilename);
end
if (strncmpi(Style, 'a', 1) && length(File) >= MAX_PATH) || ...
strncmpi(Style, 'f', 1)
% Do not use [isUNC] here, because this concerns the input, which can
% '.\File', while the current directory is an UNC path.
if strncmp(File, '\\', 2) % UNC path
File = ['\\?\UNC', File(2:end)];
else
File = ['\\?\', File];
end
end
end
end