Quantcast
Channel: How to get relative path from absolute path - Stack Overflow
Viewing all articles
Browse latest Browse all 25

Answer by Anton Krouglov for How to get relative path from absolute path

$
0
0

As pointed above .NET Core 2.x has implementation of Path.GetRelativePath.

Code below is adapted from sources and works fine with .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 Krouglovusing 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);        }    }}

Viewing all articles
Browse latest Browse all 25

Latest Images

Trending Articles



Latest Images

<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>