我的应用程序中有一部分显示用户通过OpenFileDialog加载的文件路径。它占用了太多空间来显示整个路径,但我不想只显示文件名,因为它可能不明确。所以我更喜欢显示相对于assembly / exe目录的文件路径。
例如,程序集位于“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个目录中,则显示完整路径。
您会推荐什么解决方案?正则表达式?
基本上我想显示有用的文件路径信息而不占用太多屏幕空间。
编辑:只是为了澄清一点。此解决方案的目的是帮助用户或我自己知道我最后加载了哪个文件,大致来自哪个目录。我正在使用只读文本框来显示路径。大多数情况下,文件路径比文本框的显示空间长得多。该路径应该是提供信息的,但不足以占用更多的屏幕空间。
Alex Brault的评论很好,Jonathan Leffler也是如此。 DavidK提供的Win32功能只能帮助解决部分问题,而不是整个问题,但无论如何都要感谢。至于James Newton-King的解决方案,我稍后会在我有空的时候试一试。
答案 0 :(得分:181)
/// <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;
}
答案 1 :(得分:50)
这个问题有点晚了,但我也需要这个功能。我同意DavidK的观点,因为有built-in API function提供了这个,你应该使用它。这是一个托管包装器:
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);
答案 2 :(得分:26)
shlwapi.dll中有一个Win32(C ++)函数可以完全按照您的要求执行:PathRelativePathTo()
我不知道有什么方法可以从.NET访问它,而不是P / Invoke它。
答案 3 :(得分:20)
Uri.UriSchemeFile
常量而不是硬编码"FILE"
。
这里提供的许多其他解决方案都使用字符串操作,但没有提供关于它们的可靠性的保证或指示,例如单元测试的数量等。总的来说,我建议使用Uri.MakeRelativeUri
是最安全的纯.NET选项,而最好的选择是@ 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;
}
答案 4 :(得分:12)
如果您使用.NET Core 2.0, Path.GetRelativePath()
,则提供此特定功能:
var relativeTo = @"C:\Program Files\Dummy Folder\MyProgram";
var path = @"C:\Program Files\Dummy Folder\MyProgram\Data\datafile1.dat";
string relativePath = System.IO.Path.GetRelativePath(relativeTo, path);
System.Console.WriteLine(relativePath);
// output --> Data\datafile1.dat
否则,对于.NET完整框架(从v4.7开始),建议使用其他建议的答案之一。
答案 5 :(得分:9)
我过去曾经使用过它。
/// <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;
}
答案 6 :(得分:5)
正如Alex Brault指出的那样,特别是在Windows上,绝对路径(带驱动器号和所有内容)是明确的,通常更好。
您的OpenFileDialog不应该使用常规的树形浏览器结构吗?
要获得一些术语, RefDir 是您要指定路径的相对目录; AbsName 是您要映射的绝对路径名; 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提出了一个答案,表明你不是第一个需要这个功能的人,并且有一个标准的功能来完成这项工作。 使用它。但是,从第一原则开始思考你的方式也没有坏处。
除了Unix系统不支持驱动器号(因此所有内容始终位于同一根目录下,因此第一个项目无关紧要),可以在Unix上使用相同的技术。
答案 7 :(得分:4)
这是一个很长的路要走,但System.Uri类有一个名为MakeRelativeUri的方法。也许你可以使用它。真的很遗憾System.IO.Path没有这个。
答案 8 :(得分:3)
我正在使用它:
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();
}
}
答案 9 :(得分:2)
使用:
RelPath = AbsPath.Replace(ApplicationPath, ".")
答案 10 :(得分:2)
如果你确定你的绝对路径2总是相对于绝对路径,只需从path2中删除前N个字符,其中N是path1的长度。
答案 11 :(得分:2)
您想要使用此CommonPath
课程的RelativePath
方法。获得公共路径后,只需将其从要显示的路径中删除即可。
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
答案 12 :(得分:1)
我在目录级别拆分了两条路径。从那里,找到分歧点,然后回到程序集文件夹,每次传递目录时都会加上'../'。
但请记住,绝对路径无处不在,通常比相对路径更容易阅读。除非绝对必要,否则我个人不会向用户显示相对路径。
答案 13 :(得分:1)
如above所述,.NET Core 2.x具有Path.GetRelativePath
的实现。
下面的代码是从源代码改编而成的,并且可以与.NET 4.7.1 Framework正常工作。
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
//Adapted from https://github.com/dotnet/corefx/blob/master/src/Common/src/CoreLib/System/IO/Path.cs#L697
// by Anton Krouglov
using System.Runtime.CompilerServices;
using System.Diagnostics;
using System.Text;
using Xunit;
namespace System.IO {
// Provides methods for processing file system strings in a cross-platform manner.
// Most of the methods don't do a complete parsing (such as examining a UNC hostname),
// but they will handle most string operations.
public static class PathNetCore {
/// <summary>
/// Create a relative path from one path to another. Paths will be resolved before calculating the difference.
/// Default path comparison for the active platform will be used (OrdinalIgnoreCase for Windows or Mac, Ordinal for Unix).
/// </summary>
/// <param name="relativeTo">The source path the output should be relative to. This path is always considered to be a directory.</param>
/// <param name="path">The destination path.</param>
/// <returns>The relative path or <paramref name="path"/> if the paths don't share the same root.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="relativeTo"/> or <paramref name="path"/> is <c>null</c> or an empty string.</exception>
public static string GetRelativePath(string relativeTo, string path) {
return GetRelativePath(relativeTo, path, StringComparison);
}
private static string GetRelativePath(string relativeTo, string path, StringComparison comparisonType) {
if (string.IsNullOrEmpty(relativeTo)) throw new ArgumentNullException(nameof(relativeTo));
if (string.IsNullOrEmpty(path)) throw new ArgumentNullException(nameof(path));
Debug.Assert(comparisonType == StringComparison.Ordinal ||
comparisonType == StringComparison.OrdinalIgnoreCase);
relativeTo = Path.GetFullPath(relativeTo);
path = Path.GetFullPath(path);
// Need to check if the roots are different- if they are we need to return the "to" path.
if (!PathInternalNetCore.AreRootsEqual(relativeTo, path, comparisonType))
return path;
int commonLength = PathInternalNetCore.GetCommonPathLength(relativeTo, path,
ignoreCase: comparisonType == StringComparison.OrdinalIgnoreCase);
// If there is nothing in common they can't share the same root, return the "to" path as is.
if (commonLength == 0)
return path;
// Trailing separators aren't significant for comparison
int relativeToLength = relativeTo.Length;
if (PathInternalNetCore.EndsInDirectorySeparator(relativeTo))
relativeToLength--;
bool pathEndsInSeparator = PathInternalNetCore.EndsInDirectorySeparator(path);
int pathLength = path.Length;
if (pathEndsInSeparator)
pathLength--;
// If we have effectively the same path, return "."
if (relativeToLength == pathLength && commonLength >= relativeToLength) return ".";
// We have the same root, we need to calculate the difference now using the
// common Length and Segment count past the length.
//
// Some examples:
//
// C:\Foo C:\Bar L3, S1 -> ..\Bar
// C:\Foo C:\Foo\Bar L6, S0 -> Bar
// C:\Foo\Bar C:\Bar\Bar L3, S2 -> ..\..\Bar\Bar
// C:\Foo\Foo C:\Foo\Bar L7, S1 -> ..\Bar
StringBuilder
sb = new StringBuilder(); //StringBuilderCache.Acquire(Math.Max(relativeTo.Length, path.Length));
// Add parent segments for segments past the common on the "from" path
if (commonLength < relativeToLength) {
sb.Append("..");
for (int i = commonLength + 1; i < relativeToLength; i++) {
if (PathInternalNetCore.IsDirectorySeparator(relativeTo[i])) {
sb.Append(DirectorySeparatorChar);
sb.Append("..");
}
}
}
else if (PathInternalNetCore.IsDirectorySeparator(path[commonLength])) {
// No parent segments and we need to eat the initial separator
// (C:\Foo C:\Foo\Bar case)
commonLength++;
}
// Now add the rest of the "to" path, adding back the trailing separator
int differenceLength = pathLength - commonLength;
if (pathEndsInSeparator)
differenceLength++;
if (differenceLength > 0) {
if (sb.Length > 0) {
sb.Append(DirectorySeparatorChar);
}
sb.Append(path, commonLength, differenceLength);
}
return sb.ToString(); //StringBuilderCache.GetStringAndRelease(sb);
}
// Public static readonly variant of the separators. The Path implementation itself is using
// internal const variant of the separators for better performance.
public static readonly char DirectorySeparatorChar = PathInternalNetCore.DirectorySeparatorChar;
public static readonly char AltDirectorySeparatorChar = PathInternalNetCore.AltDirectorySeparatorChar;
public static readonly char VolumeSeparatorChar = PathInternalNetCore.VolumeSeparatorChar;
public static readonly char PathSeparator = PathInternalNetCore.PathSeparator;
/// <summary>Returns a comparison that can be used to compare file and directory names for equality.</summary>
internal static StringComparison StringComparison => StringComparison.OrdinalIgnoreCase;
}
/// <summary>Contains internal path helpers that are shared between many projects.</summary>
internal static class PathInternalNetCore {
internal const char DirectorySeparatorChar = '\\';
internal const char AltDirectorySeparatorChar = '/';
internal const char VolumeSeparatorChar = ':';
internal const char PathSeparator = ';';
internal const string ExtendedDevicePathPrefix = @"\\?\";
internal const string UncPathPrefix = @"\\";
internal const string UncDevicePrefixToInsert = @"?\UNC\";
internal const string UncExtendedPathPrefix = @"\\?\UNC\";
internal const string DevicePathPrefix = @"\\.\";
//internal const int MaxShortPath = 260;
// \\?\, \\.\, \??\
internal const int DevicePrefixLength = 4;
/// <summary>
/// Returns true if the two paths have the same root
/// </summary>
internal static bool AreRootsEqual(string first, string second, StringComparison comparisonType) {
int firstRootLength = GetRootLength(first);
int secondRootLength = GetRootLength(second);
return firstRootLength == secondRootLength
&& string.Compare(
strA: first,
indexA: 0,
strB: second,
indexB: 0,
length: firstRootLength,
comparisonType: comparisonType) == 0;
}
/// <summary>
/// Gets the length of the root of the path (drive, share, etc.).
/// </summary>
internal static int GetRootLength(string path) {
int i = 0;
int volumeSeparatorLength = 2; // Length to the colon "C:"
int uncRootLength = 2; // Length to the start of the server name "\\"
bool extendedSyntax = path.StartsWith(ExtendedDevicePathPrefix);
bool extendedUncSyntax = path.StartsWith(UncExtendedPathPrefix);
if (extendedSyntax) {
// Shift the position we look for the root from to account for the extended prefix
if (extendedUncSyntax) {
// "\\" -> "\\?\UNC\"
uncRootLength = UncExtendedPathPrefix.Length;
}
else {
// "C:" -> "\\?\C:"
volumeSeparatorLength += ExtendedDevicePathPrefix.Length;
}
}
if ((!extendedSyntax || extendedUncSyntax) && path.Length > 0 && IsDirectorySeparator(path[0])) {
// UNC or simple rooted path (e.g. "\foo", NOT "\\?\C:\foo")
i = 1; // Drive rooted (\foo) is one character
if (extendedUncSyntax || (path.Length > 1 && IsDirectorySeparator(path[1]))) {
// UNC (\\?\UNC\ or \\), scan past the next two directory separators at most
// (e.g. to \\?\UNC\Server\Share or \\Server\Share\)
i = uncRootLength;
int n = 2; // Maximum separators to skip
while (i < path.Length && (!IsDirectorySeparator(path[i]) || --n > 0)) i++;
}
}
else if (path.Length >= volumeSeparatorLength &&
path[volumeSeparatorLength - 1] == PathNetCore.VolumeSeparatorChar) {
// Path is at least longer than where we expect a colon, and has a colon (\\?\A:, A:)
// If the colon is followed by a directory separator, move past it
i = volumeSeparatorLength;
if (path.Length >= volumeSeparatorLength + 1 && IsDirectorySeparator(path[volumeSeparatorLength])) i++;
}
return i;
}
/// <summary>
/// True if the given character is a directory separator.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool IsDirectorySeparator(char c) {
return c == PathNetCore.DirectorySeparatorChar || c == PathNetCore.AltDirectorySeparatorChar;
}
/// <summary>
/// Get the common path length from the start of the string.
/// </summary>
internal static int GetCommonPathLength(string first, string second, bool ignoreCase) {
int commonChars = EqualStartingCharacterCount(first, second, ignoreCase: ignoreCase);
// If nothing matches
if (commonChars == 0)
return commonChars;
// Or we're a full string and equal length or match to a separator
if (commonChars == first.Length
&& (commonChars == second.Length || IsDirectorySeparator(second[commonChars])))
return commonChars;
if (commonChars == second.Length && IsDirectorySeparator(first[commonChars]))
return commonChars;
// It's possible we matched somewhere in the middle of a segment e.g. C:\Foodie and C:\Foobar.
while (commonChars > 0 && !IsDirectorySeparator(first[commonChars - 1]))
commonChars--;
return commonChars;
}
/// <summary>
/// Gets the count of common characters from the left optionally ignoring case
/// </summary>
internal static unsafe int EqualStartingCharacterCount(string first, string second, bool ignoreCase) {
if (string.IsNullOrEmpty(first) || string.IsNullOrEmpty(second)) return 0;
int commonChars = 0;
fixed (char* f = first)
fixed (char* s = second) {
char* l = f;
char* r = s;
char* leftEnd = l + first.Length;
char* rightEnd = r + second.Length;
while (l != leftEnd && r != rightEnd
&& (*l == *r || (ignoreCase &&
char.ToUpperInvariant((*l)) == char.ToUpperInvariant((*r))))) {
commonChars++;
l++;
r++;
}
}
return commonChars;
}
/// <summary>
/// Returns true if the path ends in a directory separator.
/// </summary>
internal static bool EndsInDirectorySeparator(string path)
=> path.Length > 0 && IsDirectorySeparator(path[path.Length - 1]);
}
/// <summary> Tests for PathNetCore.GetRelativePath </summary>
public static class GetRelativePathTests {
[Theory]
[InlineData(@"C:\", @"C:\", @".")]
[InlineData(@"C:\a", @"C:\a\", @".")]
[InlineData(@"C:\A", @"C:\a\", @".")]
[InlineData(@"C:\a\", @"C:\a", @".")]
[InlineData(@"C:\", @"C:\b", @"b")]
[InlineData(@"C:\a", @"C:\b", @"..\b")]
[InlineData(@"C:\a", @"C:\b\", @"..\b\")]
[InlineData(@"C:\a\b", @"C:\a", @"..")]
[InlineData(@"C:\a\b", @"C:\a\", @"..")]
[InlineData(@"C:\a\b\", @"C:\a", @"..")]
[InlineData(@"C:\a\b\", @"C:\a\", @"..")]
[InlineData(@"C:\a\b\c", @"C:\a\b", @"..")]
[InlineData(@"C:\a\b\c", @"C:\a\b\", @"..")]
[InlineData(@"C:\a\b\c", @"C:\a", @"..\..")]
[InlineData(@"C:\a\b\c", @"C:\a\", @"..\..")]
[InlineData(@"C:\a\b\c\", @"C:\a\b", @"..")]
[InlineData(@"C:\a\b\c\", @"C:\a\b\", @"..")]
[InlineData(@"C:\a\b\c\", @"C:\a", @"..\..")]
[InlineData(@"C:\a\b\c\", @"C:\a\", @"..\..")]
[InlineData(@"C:\a\", @"C:\b", @"..\b")]
[InlineData(@"C:\a", @"C:\a\b", @"b")]
[InlineData(@"C:\a", @"C:\A\b", @"b")]
[InlineData(@"C:\a", @"C:\b\c", @"..\b\c")]
[InlineData(@"C:\a\", @"C:\a\b", @"b")]
[InlineData(@"C:\", @"D:\", @"D:\")]
[InlineData(@"C:\", @"D:\b", @"D:\b")]
[InlineData(@"C:\", @"D:\b\", @"D:\b\")]
[InlineData(@"C:\a", @"D:\b", @"D:\b")]
[InlineData(@"C:\a\", @"D:\b", @"D:\b")]
[InlineData(@"C:\ab", @"C:\a", @"..\a")]
[InlineData(@"C:\a", @"C:\ab", @"..\ab")]
[InlineData(@"C:\", @"\\LOCALHOST\Share\b", @"\\LOCALHOST\Share\b")]
[InlineData(@"\\LOCALHOST\Share\a", @"\\LOCALHOST\Share\b", @"..\b")]
//[PlatformSpecific(TestPlatforms.Windows)] // Tests Windows-specific paths
public static void GetRelativePath_Windows(string relativeTo, string path, string expected) {
string result = PathNetCore.GetRelativePath(relativeTo, path);
Assert.Equal(expected, result);
// Check that we get the equivalent path when the result is combined with the sources
Assert.Equal(
Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar),
Path.GetFullPath(Path.Combine(Path.GetFullPath(relativeTo), result))
.TrimEnd(Path.DirectorySeparatorChar),
ignoreCase: true,
ignoreLineEndingDifferences: false,
ignoreWhiteSpaceDifferences: false);
}
}
}
答案 14 :(得分:1)
使用URI的函数返回“几乎”相对路径。它包括直接包含我想要获得的相对路径的文件的目录。
前段时间我写了一个简单的函数来返回文件夹或文件的相对路径,即使它在另一个驱动器上,它也包含驱动器号。
请看一下:
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;
}
答案 15 :(得分:1)
如果您知道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);
}
答案 16 :(得分:1)
如果你有一个只读文本框,你能不能把它作为标签并设置AutoEllipsis = true?
或者有些帖子包含自己生成自动生成的代码:(对于网格而言,你需要传递i文本框的宽度。这不是很正确,因为它更多地破解了一点比必要的,我还没有找到计算错误的地方。 如果你愿意,可以很容易地修改删除目录的第一部分而不是最后一部分。
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
答案 17 :(得分:0)
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;
}
答案 18 :(得分:0)
这应该有效:
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);
}
}
答案 19 :(得分:0)
Uri的方式不适用于linux / macOS系统。路径'/ 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。
答案 20 :(得分:0)
这是我的:
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()
);
}
答案 21 :(得分:0)
玩类似的东西:
private String GetRelativePath(Int32 level, String directory, out String errorMessage) {
if (level < 0 || level > 5) {
errorMessage = "Find some more smart input data";
return String.Empty;
}
// ==========================
while (level != 0) {
directory = Path.GetDirectoryName(directory);
level -= 1;
}
// ==========================
errorMessage = String.Empty;
return directory;
}
并测试它
[Test]
public void RelativeDirectoryPathTest() {
var relativePath =
GetRelativePath(3, AppDomain.CurrentDomain.BaseDirectory, out var errorMessage);
Console.WriteLine(relativePath);
if (String.IsNullOrEmpty(errorMessage) == false) {
Console.WriteLine(errorMessage);
Assert.Fail("Can not find relative path");
}
}
答案 22 :(得分:0)
在ASP.NET Core 2
中,如果要到bin\Debug\netcoreapp2.2
的相对路径,可以使用以下组合:
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
public class RenderingService : IRenderingService
{
private readonly IHostingEnvironment _hostingEnvironment;
public RenderingService(IHostingEnvironment hostingEnvironment)
{
_hostingEnvironment = hostingEnvironment;
}
public string RelativeAssemblyDirectory()
{
var contentRootPath = _hostingEnvironment.ContentRootPath;
string executingAssemblyDirectoryAbsolutePath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
string executingAssemblyDirectoryRelativePath = System.IO.Path.GetRelativePath(contentRootPath, executingAssemblyDirectoryAbsolutePath);
return executingAssemblyDirectoryRelativePath;
}
}