1 //------------------------------------------------------------------------------
5 // Copyright (C) 2001 Moonlight Enterprises, All Rights Reserved
6 // Copyright (C) 2002 Ximian, Inc. (http://www.ximian.com)
7 // Copyright (C) 2003 Ben Maurer
9 // Author: Jim Richardson, develop@wtfo-guru.com
10 // Dan Lewis (dihlewis@yahoo.co.uk)
11 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
12 // Ben Maurer (bmaurer@users.sourceforge.net)
13 // Created: Saturday, August 11, 2001
15 //------------------------------------------------------------------------------
18 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
20 // Permission is hereby granted, free of charge, to any person obtaining
21 // a copy of this software and associated documentation files (the
22 // "Software"), to deal in the Software without restriction, including
23 // without limitation the rights to use, copy, modify, merge, publish,
24 // distribute, sublicense, and/or sell copies of the Software, and to
25 // permit persons to whom the Software is furnished to do so, subject to
26 // the following conditions:
28 // The above copyright notice and this permission notice shall be
29 // included in all copies or substantial portions of the Software.
31 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
32 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
33 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
34 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
35 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
36 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
37 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
41 using System.Runtime.CompilerServices;
45 public sealed class Path
47 public static readonly char AltDirectorySeparatorChar;
48 public static readonly char DirectorySeparatorChar;
49 public static readonly char[] InvalidPathChars;
50 public static readonly char PathSeparator;
51 internal static readonly string DirectorySeparatorStr;
52 public static readonly char VolumeSeparatorChar;
54 private static readonly char[] PathSeparatorChars;
55 private static readonly bool dirEqualsVolume;
62 public static string ChangeExtension (string path, string extension)
67 if (path.IndexOfAny (InvalidPathChars) != -1)
68 throw new ArgumentException ("Illegal characters in path", "path");
70 int iExt = findExtension (path);
72 if (extension == null)
73 return iExt < 0 ? path : path.Substring (0, iExt);
74 else if (extension == String.Empty)
75 return iExt < 0 ? path + '.' : path.Substring (0, iExt + 1);
77 else if (path.Length != 0) {
78 if (extension.Length > 0 && extension [0] != '.')
79 extension = "." + extension;
81 extension = String.Empty;
84 return path + extension;
85 } else if (iExt > 0) {
86 string temp = path.Substring (0, iExt);
87 return temp + extension;
93 public static string Combine (string path1, string path2)
96 throw new ArgumentNullException ("path1");
99 throw new ArgumentNullException ("path2");
101 if (path1 == String.Empty)
104 if (path2 == String.Empty)
107 if (path1.IndexOfAny (InvalidPathChars) != -1)
108 throw new ArgumentException ("Illegal characters in path", "path1");
110 if (path2.IndexOfAny (InvalidPathChars) != -1)
111 throw new ArgumentException ("Illegal characters in path", "path2");
114 // LAMESPEC: MS says that if path1 is not empty and path2 is a full path
115 // it should throw ArgumentException
116 if (IsPathRooted (path2))
119 char p1end = path1 [path1.Length - 1];
120 if (p1end != DirectorySeparatorChar && p1end != AltDirectorySeparatorChar && p1end != VolumeSeparatorChar)
121 return path1 + DirectorySeparatorChar + path2;
123 return path1 + path2;
126 public static string GetDirectoryName (string path)
128 // LAMESPEC: For empty string MS docs say both
129 // return null AND throw exception. Seems .NET throws.
130 if (path == String.Empty)
131 throw new ArgumentException();
133 if (path == null || GetPathRoot (path) == path)
136 CheckArgument.WhitespaceOnly (path);
137 CheckArgument.PathChars (path);
139 int nLast = path.LastIndexOfAny (PathSeparatorChars);
144 string ret = path.Substring (0, nLast);
146 if (l >= 2 && ret [l - 1] == VolumeSeparatorChar)
147 return ret + DirectorySeparatorChar;
155 public static string GetExtension (string path)
160 if (path.IndexOfAny (InvalidPathChars) != -1)
161 throw new ArgumentException ("Illegal characters in path", "path");
163 int iExt = findExtension (path);
167 if (iExt < path.Length - 1)
168 return path.Substring (iExt);
173 public static string GetFileName (string path)
175 if (path == null || path == String.Empty)
178 if (path.IndexOfAny (InvalidPathChars) != -1)
179 throw new ArgumentException ("Illegal characters in path", "path");
181 int nLast = path.LastIndexOfAny (PathSeparatorChars);
183 return path.Substring (nLast + 1);
188 public static string GetFileNameWithoutExtension (string path)
190 return ChangeExtension (GetFileName (path), null);
193 public static string GetFullPath (string path)
196 throw (new ArgumentNullException (
198 "You must specify a path when calling System.IO.Path.GetFullPath"));
200 if (path.Trim () == String.Empty)
201 throw new ArgumentException ("The path is not of a legal form", "path");
203 if (path.Length >= 2 &&
206 if (path.Length == 2 || path.IndexOf (path [0], 2) < 0)
207 throw new ArgumentException ("UNC pass should be of the form \\\\server\\share.");
209 if (path [0] == DirectorySeparatorChar)
212 return path.Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
215 if (!IsPathRooted (path))
216 path = Directory.GetCurrentDirectory () + DirectorySeparatorStr + path;
217 else if (DirectorySeparatorChar == '\\' &&
220 !IsDsc (path [1])) { // like `\abc\def'
221 string current = Directory.GetCurrentDirectory ();
222 if (current [1] == VolumeSeparatorChar)
223 path = current.Substring (0, 2) + path;
225 path = current.Substring (0, current.IndexOf ('\\', current.IndexOf ("\\\\") + 1));
227 return CanonicalizePath (path);
230 static bool IsDsc (char c) {
231 return c == DirectorySeparatorChar || c == AltDirectorySeparatorChar;
234 public static string GetPathRoot (string path)
239 if (path == String.Empty)
240 throw new ArgumentException ("This specified path is invalid.");
242 if (!IsPathRooted (path))
245 if (DirectorySeparatorChar == '/') {
247 return IsDsc (path [0]) ? DirectorySeparatorStr : String.Empty;
252 if (path.Length == 1 && IsDsc (path [0]))
253 return DirectorySeparatorStr;
254 else if (path.Length < 2)
257 if (IsDsc (path [0]) && IsDsc (path[1])) {
258 // UNC: \\server or \\server\share
260 while (len < path.Length && !IsDsc (path [len])) len++;
262 while (len < path.Length && !IsDsc (path [len])) len++;
263 return DirectorySeparatorStr +
264 DirectorySeparatorStr +
265 path.Substring (2).Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
266 } else if (IsDsc (path [0])) {
267 // path starts with '\' or '/'
268 return DirectorySeparatorStr;
269 } else if (path[1] == VolumeSeparatorChar) {
271 if (path.Length >= 3 && (IsDsc (path [2]))) len++;
273 return Directory.GetCurrentDirectory ().Substring (0, 2);// + path.Substring (0, len);
274 return path.Substring (0, len);
278 public static string GetTempFileName ()
289 path = Path.Combine (GetTempPath(), "tmp" + num.ToString("x"));
292 f = new FileStream (path, FileMode.CreateNew);
302 /// Returns the path of the current systems temp directory
304 public static string GetTempPath ()
306 string p = get_temp_path ();
307 if (p.Length > 0 && p [p.Length - 1] != DirectorySeparatorChar)
308 return p + DirectorySeparatorChar;
313 [MethodImplAttribute(MethodImplOptions.InternalCall)]
314 private static extern string get_temp_path ();
316 public static bool HasExtension (string path)
318 if (path == null || path.Trim () == String.Empty)
321 int pos = findExtension (path);
322 return 0 <= pos && pos < path.Length - 1;
325 public static bool IsPathRooted (string path)
327 if (path == null || path.Length == 0)
330 if (path.IndexOfAny (InvalidPathChars) != -1)
331 throw new ArgumentException ("Illegal characters in path", "path");
334 return (c == DirectorySeparatorChar ||
335 c == AltDirectorySeparatorChar ||
336 (!dirEqualsVolume && path.Length > 1 && path [1] == VolumeSeparatorChar));
339 // private class methods
341 private static int findExtension (string path)
343 // method should return the index of the path extension
344 // start or -1 if no valid extension
346 int iLastDot = path.LastIndexOf ('.');
347 int iLastSep = path.LastIndexOfAny ( PathSeparatorChars );
349 if (iLastDot > iLastSep)
356 VolumeSeparatorChar = MonoIO.VolumeSeparatorChar;
357 DirectorySeparatorChar = MonoIO.DirectorySeparatorChar;
358 AltDirectorySeparatorChar = MonoIO.AltDirectorySeparatorChar;
360 PathSeparator = MonoIO.PathSeparator;
361 InvalidPathChars = MonoIO.InvalidPathChars;
365 DirectorySeparatorStr = DirectorySeparatorChar.ToString ();
366 PathSeparatorChars = new char [] {
367 DirectorySeparatorChar,
368 AltDirectorySeparatorChar,
372 dirEqualsVolume = (DirectorySeparatorChar == VolumeSeparatorChar);
376 static string CanonicalizePath (string path) {
378 // STEP 1: Check for empty string
379 if (path == null) return path;
381 if (path == String.Empty) return path;
383 // STEP 2: Check to see if this is only a root
384 string root = GetPathRoot (path);
385 // it will return '\' for path '\', while it should return 'c:\' or so.
386 // Note: commenting this out makes the ened for the (target == 1...) check in step 5
387 //if (root == path) return path;
389 // STEP 3: split the directories, this gets rid of consecutative "/"'s
390 string [] dirs = path.Split (DirectorySeparatorChar, AltDirectorySeparatorChar);
391 // STEP 4: Get rid of directories containing . and ..
394 for (int i = 0; i < dirs.Length; i++) {
395 if (dirs [i] == "." || (i != 0 && dirs [i] == String.Empty)) continue;
396 else if (dirs [i] == "..") {
397 if (target != 0) target--;
400 dirs [target++] = dirs [i];
403 // STEP 5: Combine everything.
404 if (target == 0 || (target == 1 && dirs [0] == ""))
407 string ret = String.Join (DirectorySeparatorStr, dirs, 0, target);
408 switch (DirectorySeparatorChar) {
409 case '\\': // Windows
410 // In GetFullPath(), it is assured that here never comes UNC. So this must only applied to such path that starts with '\', without drive specification.
411 if (path [0] != DirectorySeparatorChar && path.StartsWith (root)) {
412 if (ret.Length <= 2 && !ret.EndsWith (DirectorySeparatorStr)) // '\' after "c:"
413 ret += DirectorySeparatorChar;
416 string current = Directory.GetCurrentDirectory ();
417 if (current.Length > 1 && current [1] == VolumeSeparatorChar) {
418 // DOS local file path
419 if (ret.Length == 0 || IsDsc (ret [0]))
421 return current.Substring (0, 2) + ret;
423 else if (IsDsc (current [current.Length - 1]) && IsDsc (ret [0]))
424 return current + ret.Substring (1);
426 return current + ret;