.net 如何从绝对路径得到相对路径
“我的应用”中有一个部分显示用户通过OpenFileDialog加载的文件路径。它占用了太多的空间来显示整个路径,但我不想只显示文件名,因为它可能不明确。因此,我更愿意显示相对于assembly/exe目录的文件路径 例如,程序集位于.net 如何从绝对路径得到相对路径,.net,.net,“我的应用”中有一个部分显示用户通过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\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函数只对部分问题有帮助,而不是全部问题,不过还是要谢谢你。至于詹姆斯·牛顿·金的解决方案,等我有空的时候我会试一试。使用:
RelPath = AbsPath.Replace(ApplicationPath, ".")
使用:
我会在目录级别拆分你的两条路径。从那里,找到分歧点并返回到程序集文件夹,每次通过目录时都在“../”前面加上前缀
但是请记住,绝对路径无处不在,通常比相对路径更容易阅读。我个人不会向用户显示相对路径,除非这是绝对必要的。我会在目录级别拆分两条路径。从那里,找到分歧点并返回到程序集文件夹,每次通过目录时都在“../”前面加上前缀 但是请记住,绝对路径无处不在,通常比相对路径更容易阅读。我个人不会向用户显示相对路径,除非它是绝对必要的。shlwapi.dll中有一个Win32(C++)函数,它可以完全满足您的要求: 但是,我不知道从.NET访问此文件的任何方法,只能通过p/调用它。shlwapi.dll中有一个Win32(C++)函数,它完全满足您的要求:
但是,除了p/调用它之外,我不知道从.NET访问它的任何方法。正如Alex Brault指出的那样,特别是在Windows上,绝对路径(包括驱动器号和全部)是明确的,而且通常更好 OpenFileDialog不应该使用常规的树浏览器结构吗 为了获得适当的命名,RefDir是要指定路径的相对目录;AbsName是要映射的绝对路径名;而RelPath是得到的相对路径 选择以下匹配的第一个选项:
- 如果驱动器号不同,则从RefDir到AbsName之间没有相对路径;您必须使用该名称
- 如果AbsName位于RefDir的子目录中或是RefDir中的文件,则只需从AbsName的开头删除RefDir即可创建RelPath;可以选择在“/”(或“\”之前加前缀,因为您在Windows上)
- 查找RefDir和AbsName的最长公共前缀(其中D:\Abc\Def和D:\Abc\Default共享D:\Abc作为最长公共前缀;它必须是名称组件的映射,而不是简单的最长公共子字符串);称之为LCP。从AbsName和RefDir中删除LCP。对于(RefDir-LCP)中剩余的每个路径组件,在“.\”之前加上(AbsName-LCP)以生成RelPath
RefDir = D:\Abc\Def\Ghi
AbsName = D:\Abc\Default\Karma\Crucible
然后
在我打字时,DavidK给出了一个答案,表明您不是第一个需要此功能的人,并且有一个标准函数来完成此工作使用它。但从第一原则出发思考也没有坏处
除了Unix系统不支持驱动器号(因此所有内容都始终位于同一根目录下,因此第一个项目符号不相关)之外,在Unix上也可以使用相同的技术。正如Alex Brault指出的,特别是在Windows上,绝对路径(包含驱动器号和全部)是明确的,而且通常更好 OpenFileDialog不应该使用常规的树浏览器结构吗 为了获得适当的命名,RefDir是要指定路径的相对目录;AbsName是要映射的绝对路径名;而RelPath是得到的相对路径 选择以下匹配的第一个选项:
- 如果驱动器号不同,则从RefDir到AbsName之间没有相对路径;您必须使用该名称
- 如果AbsName位于RefDir的子目录中或是RefDir中的文件,则只需从AbsName的开头删除RefDir即可创建RelPath;可以选择在“/”(或“\”之前加前缀,因为您在Windows上)
- 查找RefDir和AbsName的最长公共前缀(其中D:\Abc\Def和D:\Abc\Default共享D:\Abc作为最长公共前缀;它必须是名称组件的映射,而不是简单的最长公共子字符串);称之为LCP。从AbsName和RefDir中删除LCP。对于(RefDir-LCP)中剩余的每个路径组件,在“.\”之前加上(AbsName-LCP)以生成RelPath
LCP = D:\Abc
(RefDir - LCP) = Def\Ghi
(Absname - LCP) = Default\Karma\Crucible
RelPath = ..\..\Default\Karma\Crucible
/// <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;
}
/// <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;
}
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);
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);
}
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;
}
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();
}
}
var relativePath = Path.GetRelativePath(
@"C:\Program Files\Dummy Folder\MyProgram",
@"C:\Program Files\Dummy Folder\MyProgram\Data\datafile1.dat");
/// <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;
}
var relativePath = PathExtended.GetRelativePath(
@"C:\Program Files\Dummy Folder\MyProgram",
@"C:\Program Files\Dummy Folder\MyProgram\Data\datafile1.dat");
public static class PathExtended
{
private const int FILE_ATTRIBUTE_DIRECTORY = 0x10;
private const int FILE_ATTRIBUTE_NORMAL = 0x80;
private const int MaximumPath = 260;
public static string GetRelativePath(string fromPath, string toPath)
{
var fromAttribute = GetPathAttribute(fromPath);
var toAttribute = GetPathAttribute(toPath);
var stringBuilder = new StringBuilder(MaximumPath);
if (PathRelativePathTo(
stringBuilder,
fromPath,
fromAttribute,
toPath,
toAttribute) == 0)
{
throw new ArgumentException("Paths must have a common prefix.");
}
return stringBuilder.ToString();
}
private static int GetPathAttribute(string path)
{
var directory = new DirectoryInfo(path);
if (directory.Exists)
{
return FILE_ATTRIBUTE_DIRECTORY;
}
var file = new FileInfo(path);
if (file.Exists)
{
return FILE_ATTRIBUTE_NORMAL;
}
throw new FileNotFoundException(
"A file or directory with the specified path was not found.",
path);
}
[DllImport("shlwapi.dll", SetLastError = true)]
private static extern int PathRelativePathTo(
StringBuilder pszPath,
string pszFrom,
int dwAttrFrom,
string pszTo,
int dwAttrTo);
}
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);
}
}
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));
}
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()
);
}
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");
}
}
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
// 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);
}
}
}
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;
}
}