如何从绝对path获取相对path
我的应用程序中有一部分显示用户通过OpenFileDialog加载的文件path。 显示整个path占用太多空间,但是我不想只显示文件名,因为它可能是不明确的。 所以我宁愿显示相对于程序集/ exe目录的文件path。
例如,程序集驻留在“C:\ Program Files \ Dummy Folder \ MyProgram”和“C:\ Program Files \ Dummy Folder \ MyProgram \ Data \ datafile1.dat”文件中,然后我想显示“。 \ DATA \ datafile1.dat”。 如果该文件位于“C:\ Program Files \ Dummy Folder \ datafile1.dat”,那么我想要“.. \ datafile1.dat”。 但是,如果该文件位于根目录或根目录下的1个目录,则显示完整path。
你会推荐什么解决scheme? 正则expression式?
基本上我想显示有用的文件path信息,而不占用太多的屏幕空间。
编辑:只是澄清一点点。 这个解决scheme的目的是帮助用户或我自己知道我最后加载了哪个文件,并大致从哪个目录下载。 我正在使用只读文本框来显示path。 大多数情况下,文件path比文本框的显示空间长得多。 path假设是信息性的,但不足以占用更多的屏幕空间。
Alex Brault的评论很好,Jonathan Leffler也是如此。 由DavidK提供的Win32函数只能帮助解决部分问题,而不能解决整个问题,不过谢谢。 至于James Newton-King的解决scheme,当我有空的时候,我会试一试。
/// <summary> /// Creates a relative path from one file or folder to another. /// </summary> /// <param name="fromPath">Contains the directory that defines the start of the relative path.</param> /// <param name="toPath">Contains the path that defines the endpoint of the relative path.</param> /// <returns>The relative path from the start directory to the end path or <c>toPath</c> if the paths are not related.</returns> /// <exception cref="ArgumentNullException"></exception> /// <exception cref="UriFormatException"></exception> /// <exception cref="InvalidOperationException"></exception> public static String MakeRelativePath(String fromPath, String toPath) { if (String.IsNullOrEmpty(fromPath)) throw new ArgumentNullException("fromPath"); if (String.IsNullOrEmpty(toPath)) throw new ArgumentNullException("toPath"); Uri fromUri = new Uri(fromPath); Uri toUri = new Uri(toPath); if (fromUri.Scheme != toUri.Scheme) { return toPath; } // path can't be made relative. Uri relativeUri = fromUri.MakeRelativeUri(toUri); String relativePath = Uri.UnescapeDataString(relativeUri.ToString()); if (toUri.Scheme.Equals("file", StringComparison.InvariantCultureIgnoreCase)) { relativePath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); } return relativePath; }
有点迟了,但我只是需要这个function。 我同意DavidK的观点,既然有一个内置的API函数提供了这个function ,你应该使用它。 这是一个托pipe包装它:
public static string GetRelativePath(string fromPath, string toPath) { int fromAttr = GetPathAttribute(fromPath); int toAttr = GetPathAttribute(toPath); StringBuilder path = new StringBuilder(260); // MAX_PATH if(PathRelativePathTo( path, fromPath, fromAttr, toPath, toAttr) == 0) { throw new ArgumentException("Paths must have a common prefix"); } return path.ToString(); } private static int GetPathAttribute(string path) { DirectoryInfo di = new DirectoryInfo(path); if (di.Exists) { return FILE_ATTRIBUTE_DIRECTORY; } FileInfo fi = new FileInfo(path); if(fi.Exists) { return FILE_ATTRIBUTE_NORMAL; } throw new FileNotFoundException(); } private const int FILE_ATTRIBUTE_DIRECTORY = 0x10; private const int FILE_ATTRIBUTE_NORMAL = 0x80; [DllImport("shlwapi.dll", SetLastError = true)] private static extern int PathRelativePathTo(StringBuilder pszPath, string pszFrom, int dwAttrFrom, string pszTo, int dwAttrTo);
在shlwapi.dll中有一个Win32(C ++)函数,它正是你想要的: PathRelativePathTo()
但是我不知道有什么方法可以从.NET访问这个,除了P / Invoke它。
@如果path是目录path,那么当文件path不以'/'结尾时,@ Dave的解决scheme不起作用。 这个解决scheme解决了这个问题,并且使用了Uri.UriSchemeFile
常量而不是硬编码"FILE"
。
这里提供的许多其他解决scheme使用string操作,但没有提供任何保证或指示,如多less可靠性,如unit testing等等。总的来说,我build议使用Uri.MakeRelativeUri
是最安全的纯.NET选项,而最好的select是@ ctacke的Windows互操作示例。
/// <summary> /// Creates a relative path from one file or folder to another. /// </summary> /// <param name="fromPath">Contains the directory that defines the start of the relative path.</param> /// <param name="toPath">Contains the path that defines the endpoint of the relative path.</param> /// <returns>The relative path from the start directory to the end path.</returns> /// <exception cref="ArgumentNullException"><paramref name="fromPath"/> or <paramref name="toPath"/> is <c>null</c>.</exception> /// <exception cref="UriFormatException"></exception> /// <exception cref="InvalidOperationException"></exception> public static string GetRelativePath(string fromPath, string toPath) { if (string.IsNullOrEmpty(fromPath)) { throw new ArgumentNullException("fromPath"); } if (string.IsNullOrEmpty(toPath)) { throw new ArgumentNullException("toPath"); } Uri fromUri = new Uri(AppendDirectorySeparatorChar(fromPath)); Uri toUri = new Uri(AppendDirectorySeparatorChar(toPath)); if (fromUri.Scheme != toUri.Scheme) { return toPath; } Uri relativeUri = fromUri.MakeRelativeUri(toUri); string relativePath = Uri.UnescapeDataString(relativeUri.ToString()); if (string.Equals(toUri.Scheme, Uri.UriSchemeFile, StringComparison.OrdinalIgnoreCase)) { relativePath = relativePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); } return relativePath; } private static string AppendDirectorySeparatorChar(string path) { // Append a slash only if the path is a directory and does not have a slash. if (!Path.HasExtension(path) && !path.EndsWith(Path.DirectorySeparatorChar.ToString())) { return path + Path.DirectorySeparatorChar; } return path; }
我以前用过这个。
/// <summary> /// Creates a relative path from one file /// or folder to another. /// </summary> /// <param name="fromDirectory"> /// Contains the directory that defines the /// start of the relative path. /// </param> /// <param name="toPath"> /// Contains the path that defines the /// endpoint of the relative path. /// </param> /// <returns> /// The relative path from the start /// directory to the end path. /// </returns> /// <exception cref="ArgumentNullException"></exception> public static string MakeRelative(string fromDirectory, string toPath) { if (fromDirectory == null) throw new ArgumentNullException("fromDirectory"); if (toPath == null) throw new ArgumentNullException("toPath"); bool isRooted = (Path.IsPathRooted(fromDirectory) && Path.IsPathRooted(toPath)); if (isRooted) { bool isDifferentRoot = (string.Compare(Path.GetPathRoot(fromDirectory), Path.GetPathRoot(toPath), true) != 0); if (isDifferentRoot) return toPath; } List<string> relativePath = new List<string>(); string[] fromDirectories = fromDirectory.Split(Path.DirectorySeparatorChar); string[] toDirectories = toPath.Split(Path.DirectorySeparatorChar); int length = Math.Min(fromDirectories.Length, toDirectories.Length); int lastCommonRoot = -1; // find common root for (int x = 0; x < length; x++) { if (string.Compare(fromDirectories[x], toDirectories[x], true) != 0) break; lastCommonRoot = x; } if (lastCommonRoot == -1) return toPath; // add relative folders in from path for (int x = lastCommonRoot + 1; x < fromDirectories.Length; x++) { if (fromDirectories[x].Length > 0) relativePath.Add(".."); } // add to folders to path for (int x = lastCommonRoot + 1; x < toDirectories.Length; x++) { relativePath.Add(toDirectories[x]); } // create relative path string[] relativeParts = new string[relativePath.Count]; relativePath.CopyTo(relativeParts, 0); string newPath = string.Join(Path.DirectorySeparatorChar.ToString(), relativeParts); return newPath; }
正如Alex Brault所指出的那样,特别是在Windows上,绝对path(带有盘符和全部)是明确的,而且往往更好。
不应该OpenFileDialog使用常规的树型浏览器结构?
为了得到一些命名法, RefDir是你想要指定path的目录; AbsName是您要映射的绝对path名称; 而RelPath是最终的相对path。
采取匹配的第一个选项:
- 如果您有不同的驱动器号,RefDir和AbsName之间没有相对path; 你必须使用AbsName。
- 如果AbsName位于RefDir的子目录中,或者是RefDir中的文件,则只需从AbsName的起始处移除RefDir以创buildRelPath; 可选地加上“./”(或者“。\”,因为你在Windows上)。
- findRefDir和AbsName(其中D:\ Abc \ Def和D:\ Abc \ Default)的最长公共前缀作为最长的公共前缀共享D:\ Abc;它必须是名称组件的映射,而不是一个简单的最长的公共子); 称之为LCP。 从AbsName和RefDir中删除LCP。 对于(RefDir – LCP)中剩下的每个path组件,将“.. \”前缀(AbsName – LCP)生成RelPath。
为了说明最后的规则(当然这是最复杂的),首先是:
RefDir = D:\Abc\Def\Ghi AbsName = D:\Abc\Default\Karma\Crucible
然后
LCP = D:\Abc (RefDir - LCP) = Def\Ghi (Absname - LCP) = Default\Karma\Crucible RelPath = ..\..\Default\Karma\Crucible
在我打字的时候,DavidK给出了一个答案,表明你不是第一个需要这个function的人,而且有一个标准的function来完成这项工作。 用它。 但是从最初的原则来看,也没有什么坏处。
除了Unix系统不支持驱动器盘符(因此所有东西总是位于同一个根目录下,因此第一个项目符号是不相关的),在Unix上也可以使用同样的技术。
这是一个很长的路,但System.Uri类有一个名为MakeRelativeUri的方法。 也许你可以使用它。 真的很遗憾System.IO.Path没有这个function。
如果你确定你的绝对path2总是相对于绝对path,只要从path2中删除前N个字符,其中N是path1的长度。
如果您知道fromPath包含toPath,那么您可以保持简单。 为了简洁起见,我将省略断言。
public static string MakeRelativePath(string fromPath, string toPath) { // use Path.GetFullPath to canonicalise the paths (deal with multiple directory seperators, etc) return Path.GetFullPath(toPath).Substring(Path.GetFullPath(fromPath).Length + 1); }
你想使用这个RelativePath
类的CommonPath
方法。 一旦你有了共同的道路,就把它从你想要显示的path中剥离出来。
Namespace IO.Path Public NotInheritable Class RelativePath Private Declare Function PathRelativePathTo Lib "shlwapi" Alias "PathRelativePathToA" ( _ ByVal pszPath As String, _ ByVal pszFrom As String, _ ByVal dwAttrFrom As Integer, _ ByVal pszTo As String, _ ByVal dwAttrTo As Integer) As Integer Private Declare Function PathCanonicalize Lib "shlwapi" Alias "PathCanonicalizeA" ( _ ByVal pszBuf As String, _ ByVal pszPath As String) As Integer Private Const FILE_ATTRIBUTE_DIRECTORY As Short = &H10S Private Const MAX_PATH As Short = 260 Private _path As String Private _isDirectory As Boolean #Region " Constructors " Public Sub New() End Sub Public Sub New(ByVal path As String) _path = path End Sub Public Sub New(ByVal path As String, ByVal isDirectory As Boolean) _path = path _isDirectory = isDirectory End Sub #End Region Private Shared Function StripNulls(ByVal value As String) As String StripNulls = value If (InStr(value, vbNullChar) > 0) Then StripNulls = Left(value, InStr(value, vbNullChar) - 1) End If End Function Private Shared Function TrimCurrentDirectory(ByVal path As String) As String TrimCurrentDirectory = path If Len(path) >= 2 And Left(path, 2) = ".\" Then TrimCurrentDirectory = Mid(path, 3) End If End Function ''' <summary> ''' 3. conforming to general principles: conforming to accepted principles or standard practice ''' </summary> Public Shared Function Canonicalize(ByVal path As String) As String Dim sPath As String sPath = New String(Chr(0), MAX_PATH) If PathCanonicalize(sPath, path) = 0 Then Canonicalize = vbNullString Else Canonicalize = StripNulls(sPath) End If End Function ''' <summary> ''' Returns the most common path between two paths. ''' </summary> ''' <remarks> ''' <para>returns the path that is common between two paths</para> ''' <para>c:\FolderA\FolderB\FolderC</para> ''' c:\FolderA\FolderD\FolderE\File.Ext ''' ''' results in: ''' c:\FolderA\ ''' </remarks> Public Shared Function CommonPath(ByVal path1 As String, ByVal path2 As String) As String 'returns the path that is common between two paths ' ' c:\FolderA\FolderB\FolderC ' c:\FolderA\FolderD\FolderE\File.Ext ' ' results in: ' c:\FolderA\ Dim sResult As String = String.Empty Dim iPos1, iPos2 As Integer path1 = Canonicalize(path1) path2 = Canonicalize(path2) Do If Left(path1, iPos1) = Left(path2, iPos2) Then sResult = Left(path1, iPos1) End If iPos1 = InStr(iPos1 + 1, path1, "\") iPos2 = InStr(iPos2 + 1, path1, "\") Loop While Left(path1, iPos1) = Left(path2, iPos2) Return sResult End Function Public Function CommonPath(ByVal path As String) As String Return CommonPath(_path, path) End Function Public Shared Function RelativePathTo(ByVal source As String, ByVal isSourceDirectory As Boolean, ByVal target As String, ByVal isTargetDirectory As Boolean) As String 'DEVLIB ' 05/23/05 1:47PM - Fixed call to PathRelativePathTo, iTargetAttribute is now passed to dwAttrTo instead of IsTargetDirectory. ' For Visual Basic 6.0, the fix does not change testing results, ' because when the Boolean IsTargetDirectory is converted to the Long dwAttrTo it happens to contain FILE_ATTRIBUTE_DIRECTORY, ' Dim sRelativePath As String Dim iSourceAttribute, iTargetAttribute As Integer sRelativePath = New String(Chr(0), MAX_PATH) source = Canonicalize(source) target = Canonicalize(target) If isSourceDirectory Then iSourceAttribute = FILE_ATTRIBUTE_DIRECTORY End If If isTargetDirectory Then iTargetAttribute = FILE_ATTRIBUTE_DIRECTORY End If If PathRelativePathTo(sRelativePath, source, iSourceAttribute, target, iTargetAttribute) = 0 Then RelativePathTo = vbNullString Else RelativePathTo = TrimCurrentDirectory(StripNulls(sRelativePath)) End If End Function Public Function RelativePath(ByVal target As String) As String Return RelativePathTo(_path, _isDirectory, target, False) End Function End Class End Namespace
我正在使用这个:
public static class StringExtensions { /// <summary> /// Creates a relative path from one file or folder to another. /// </summary> /// <param name="absPath">Absolute path.</param> /// <param name="relTo">Directory that defines the start of the relative path.</param> /// <returns>The relative path from the start directory to the end path.</returns> public static string MakeRelativePath(this string absPath, string relTo) { string[] absParts = absPath.Split(Path.DirectorySeparatorChar); string[] relParts = relTo.Split(Path.DirectorySeparatorChar); // Get the shortest of the two paths int len = absParts.Length < relParts.Length ? absParts.Length : relParts.Length; // Use to determine where in the loop we exited int lastCommonRoot = -1; int index; // Find common root for (index = 0; index < len; index++) { if (absParts[index].Equals(relParts[index], StringComparison.OrdinalIgnoreCase)) lastCommonRoot = index; else break; } // If we didn't find a common prefix then throw if (lastCommonRoot == -1) throw new ArgumentException("The path of the two files doesn't have any common base."); // Build up the relative path var relativePath = new StringBuilder(); // Add on the .. for (index = lastCommonRoot + 1; index < relParts.Length; index++) { relativePath.Append(".."); relativePath.Append(Path.DirectorySeparatorChar); } // Add on the folders for (index = lastCommonRoot + 1; index < absParts.Length - 1; index++) { relativePath.Append(absParts[index]); relativePath.Append(Path.DirectorySeparatorChar); } relativePath.Append(absParts[absParts.Length - 1]); return relativePath.ToString(); } }
使用:
RelPath = AbsPath.Replace(ApplicationPath, ".")
我会在目录级分割你的两个path。 从那里,find分歧点,然后回到程序集文件夹,每次传递目录时都会预先设置一个“../”。
但请记住,一条绝对的道路无处不在,通常比相对的道路更容易阅读。 除非绝对必要,否则我个人不会向用户展示相对path。
使用URI的函数返回“几乎”相对path。 它包含了直接包含我想获得的相对path的文件的目录。
前段时间我写了一个简单的函数来返回文件夹或文件的相对path,即使它在另一个驱动器上,也包含驱动器号。
请看一下:
public static string GetRelativePath(string BasePath, string AbsolutePath) { char Separator = Path.DirectorySeparatorChar; if (string.IsNullOrWhiteSpace(BasePath)) BasePath = Directory.GetCurrentDirectory(); var ReturnPath = ""; var CommonPart = ""; var BasePathFolders = BasePath.Split(Separator); var AbsolutePathFolders = AbsolutePath.Split(Separator); var i = 0; while (i < BasePathFolders.Length & i < AbsolutePathFolders.Length) { if (BasePathFolders[i].ToLower() == AbsolutePathFolders[i].ToLower()) { CommonPart += BasePathFolders[i] + Separator; } else { break; } i += 1; } if (CommonPart.Length > 0) { var parents = BasePath.Substring(CommonPart.Length - 1).Split(Separator); foreach (var ParentDir in parents) { if (!string.IsNullOrEmpty(ParentDir)) ReturnPath += ".." + Separator; } } ReturnPath += AbsolutePath.Substring(CommonPart.Length); return ReturnPath; }
如果你有一个只读文本框,你能不能把它作为一个标签,并设置AutoEllipsis = true?
或者有一些代码可以自动生成自动校正:(这是为了网格,你需要传递给我的文本框的宽度,而不是正确的,因为它超过了必要的瑕疵,而且我还没有find计算不正确的地方,如果你愿意的话,修改删除目录的第一部分而不是最后一部分将是很容易的。
Private Function AddEllipsisPath(ByVal text As String, ByVal colIndex As Integer, ByVal grid As DataGridView) As String 'Get the size with the column's width Dim colWidth As Integer = grid.Columns(colIndex).Width 'Calculate the dimensions of the text with the current font Dim textSize As SizeF = MeasureString(text, grid.Font) Dim rawText As String = text Dim FileNameLen As Integer = text.Length - text.LastIndexOf("\") Dim ReplaceWith As String = "\..." Do While textSize.Width > colWidth ' Trim to make room for the ellipsis Dim LastFolder As Integer = rawText.LastIndexOf("\", rawText.Length - FileNameLen - 1) If LastFolder < 0 Then Exit Do End If rawText = rawText.Substring(0, LastFolder) + ReplaceWith + rawText.Substring(rawText.Length - FileNameLen) If ReplaceWith.Length > 0 Then FileNameLen += 4 ReplaceWith = "" End If textSize = MeasureString(rawText, grid.Font) Loop Return rawText End Function Private Function MeasureString(ByVal text As String, ByVal fontInfo As Font) As SizeF Dim size As SizeF Dim emSize As Single = fontInfo.Size If emSize = 0 Then emSize = 12 Dim stringFont As New Font(fontInfo.Name, emSize) Dim bmp As New Bitmap(1000, 100) Dim g As Graphics = Graphics.FromImage(bmp) size = g.MeasureString(text, stringFont) g.Dispose() Return size End Function
public static string ToRelativePath(string filePath, string refPath) { var pathNormalized = Path.GetFullPath(filePath); var refNormalized = Path.GetFullPath(refPath); refNormalized = refNormalized.TrimEnd('\\', '/'); if (!pathNormalized.StartsWith(refNormalized)) throw new ArgumentException(); var res = pathNormalized.Substring(refNormalized.Length + 1); return res; }
这应该工作:
private string rel(string path) { string[] cwd = new Regex(@"[\\]").Split(Directory.GetCurrentDirectory()); string[] fp = new Regex(@"[\\]").Split(path); int common = 0; for (int n = 0; n < fp.Length; n++) { if (n < cwd.Length && n < fp.Length && cwd[n] == fp[n]) { common++; } } if (common > 0) { List<string> rp = new List<string>(); for (int n = 0; n < (cwd.Length - common); n++) { rp.Add(".."); } for (int n = common; n < fp.Length; n++) { rp.Add(fp[n]); } return String.Join("/", rp.ToArray()); } else { return String.Join("/", fp); } }
与Uri的方式没有在Linux / MacOS系统上工作。 path'/ var / www / root'不能被转换成Uri。 更普遍的方式 – 全部用手。
public static string MakeRelativePath(string fromPath, string toPath, string sep = "/") { var fromParts = fromPath.Split(new[] { '/', '\\'}, StringSplitOptions.RemoveEmptyEntries); var toParts = toPath.Split(new[] { '/', '\\'}, StringSplitOptions.RemoveEmptyEntries); var matchedParts = fromParts .Zip(toParts, (x, y) => string.Compare(x, y, true) == 0) .TakeWhile(x => x).Count(); return string.Join("", Enumerable.Range(0, fromParts.Length - matchedParts) .Select(x => ".." + sep)) + string.Join(sep, toParts.Skip(matchedParts)); }
PS:我使用“/”作为分隔符的默认值,而不是Path.DirectorySeparatorChar,因为这个方法的结果在我的应用程序中用作uri。
这是我的:
public static string RelativePathTo(this System.IO.DirectoryInfo @this, string to) { var rgFrom = @this.FullName.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries); var rgTo = to.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries); var cSame = rgFrom.TakeWhile((p, i) => i < rgTo.Length && string.Equals(p, rgTo[i])).Count(); return Path.Combine( Enumerable.Range(0, rgFrom.Length - cSame) .Select(_ => "..") .Concat(rgTo.Skip(cSame)) .ToArray() ); }