Given full path, check if path is subdirectory of some other path, or otherwise

前端 未结 9 1326
难免孤独
难免孤独 2020-12-03 13:22

I have 2 strings - dir1 and dir2, and I need to check if one is sub-directory for other. I tried to go with Contains method:

dir1.contains(dir2);


        
相关标签:
9条回答
  • 2020-12-03 14:06

    Based on @BrokenGlass's answer but tweaked:

    using System.IO;
    
    internal static class DirectoryInfoExt
    {
        internal static bool IsSubDirectoryOfOrSame(this DirectoryInfo directoryInfo, DirectoryInfo potentialParent)
        {
            if (DirectoryInfoComparer.Default.Equals(directoryInfo, potentialParent))
            {
                return true;
            }
    
            return IsStrictSubDirectoryOf(directoryInfo, potentialParent);
        }
    
        internal static bool IsStrictSubDirectoryOf(this DirectoryInfo directoryInfo, DirectoryInfo potentialParent)
        {
            while (directoryInfo.Parent != null)
            {
                if (DirectoryInfoComparer.Default.Equals(directoryInfo.Parent, potentialParent))
                {
                    return true;
                }
    
                directoryInfo = directoryInfo.Parent;
            }
    
            return false;
        }
    }
    

    using System;
    using System.Collections.Generic;
    using System.IO;
    
    public class DirectoryInfoComparer : IEqualityComparer<DirectoryInfo>
    {
        private static readonly char[] TrimEnd = { '\\' };
        public static readonly DirectoryInfoComparer Default = new DirectoryInfoComparer();
        private static readonly StringComparer OrdinalIgnoreCaseComparer = StringComparer.OrdinalIgnoreCase;
    
        private DirectoryInfoComparer()
        {
        }
    
        public bool Equals(DirectoryInfo x, DirectoryInfo y)
        {
            if (ReferenceEquals(x, y))
            {
                return true;
            }
    
            if (x == null || y == null)
            {
                return false;
            }
    
            return OrdinalIgnoreCaseComparer.Equals(x.FullName.TrimEnd(TrimEnd), y.FullName.TrimEnd(TrimEnd));
        }
    
        public int GetHashCode(DirectoryInfo obj)
        {
            if (obj == null)
            {
                throw new ArgumentNullException(nameof(obj));
            }
            return OrdinalIgnoreCaseComparer.GetHashCode(obj.FullName.TrimEnd(TrimEnd));
        }
    }
    

    Not ideal if performance is essential.

    0 讨论(0)
  • 2020-12-03 14:11
    • Case insensitive
    • Tolerates mix of \ and / folder delimiters
    • Tolerates ..\ in path
    • Avoids matching on partial folder names (c:\foobar not a subpath of c:\foo)

    Note: This only matches on the path string and does not work for symbolic links and other kinds of links in the filesystem.

    Code:

    public static class StringExtensions
    {
        /// <summary>
        /// Returns true if <paramref name="path"/> starts with the path <paramref name="baseDirPath"/>.
        /// The comparison is case-insensitive, handles / and \ slashes as folder separators and
        /// only matches if the base dir folder name is matched exactly ("c:\foobar\file.txt" is not a sub path of "c:\foo").
        /// </summary>
        public static bool IsSubPathOf(this string path, string baseDirPath)
        {
            string normalizedPath = Path.GetFullPath(path.Replace('/', '\\')
                .WithEnding("\\"));
    
            string normalizedBaseDirPath = Path.GetFullPath(baseDirPath.Replace('/', '\\')
                .WithEnding("\\"));
    
            return normalizedPath.StartsWith(normalizedBaseDirPath, StringComparison.OrdinalIgnoreCase);
        }
    
        /// <summary>
        /// Returns <paramref name="str"/> with the minimal concatenation of <paramref name="ending"/> (starting from end) that
        /// results in satisfying .EndsWith(ending).
        /// </summary>
        /// <example>"hel".WithEnding("llo") returns "hello", which is the result of "hel" + "lo".</example>
        public static string WithEnding([CanBeNull] this string str, string ending)
        {
            if (str == null)
                return ending;
    
            string result = str;
    
            // Right() is 1-indexed, so include these cases
            // * Append no characters
            // * Append up to N characters, where N is ending length
            for (int i = 0; i <= ending.Length; i++)
            {
                string tmp = result + ending.Right(i);
                if (tmp.EndsWith(ending))
                    return tmp;
            }
    
            return result;
        }
    
        /// <summary>Gets the rightmost <paramref name="length" /> characters from a string.</summary>
        /// <param name="value">The string to retrieve the substring from.</param>
        /// <param name="length">The number of characters to retrieve.</param>
        /// <returns>The substring.</returns>
        public static string Right([NotNull] this string value, int length)
        {
            if (value == null)
            {
                throw new ArgumentNullException("value");
            }
            if (length < 0)
            {
                throw new ArgumentOutOfRangeException("length", length, "Length is less than zero");
            }
    
            return (length < value.Length) ? value.Substring(value.Length - length) : value;
        }
    }
    

    Test cases (NUnit):

    [TestFixture]
    public class StringExtensionsTest
    {
        [TestCase(@"c:\foo", @"c:", Result = true)]
        [TestCase(@"c:\foo", @"c:\", Result = true)]
        [TestCase(@"c:\foo", @"c:\foo", Result = true)]
        [TestCase(@"c:\foo", @"c:\foo\", Result = true)]
        [TestCase(@"c:\foo\", @"c:\foo", Result = true)]
        [TestCase(@"c:\foo\bar\", @"c:\foo\", Result = true)]
        [TestCase(@"c:\foo\bar", @"c:\foo\", Result = true)]
        [TestCase(@"c:\foo\a.txt", @"c:\foo", Result = true)]
        [TestCase(@"c:\FOO\a.txt", @"c:\foo", Result = true)]
        [TestCase(@"c:/foo/a.txt", @"c:\foo", Result = true)]
        [TestCase(@"c:\foobar", @"c:\foo", Result = false)]
        [TestCase(@"c:\foobar\a.txt", @"c:\foo", Result = false)]
        [TestCase(@"c:\foobar\a.txt", @"c:\foo\", Result = false)]
        [TestCase(@"c:\foo\a.txt", @"c:\foobar", Result = false)]
        [TestCase(@"c:\foo\a.txt", @"c:\foobar\", Result = false)]
        [TestCase(@"c:\foo\..\bar\baz", @"c:\foo", Result = false)]
        [TestCase(@"c:\foo\..\bar\baz", @"c:\bar", Result = true)]
        [TestCase(@"c:\foo\..\bar\baz", @"c:\barr", Result = false)]
        public bool IsSubPathOfTest(string path, string baseDirPath)
        {
            return path.IsSubPathOf(baseDirPath);
        }
    }
    

    Updates

    • 2015-08-18: Fix bug matching on partial folder names. Add test cases.
    • 2015-09-02: Support ..\ in paths, add missing code
    • 2017-09-06: Add note on symbolic links.
    0 讨论(0)
  • 2020-12-03 14:11
    public static bool IsSubpathOf(string rootPath, string subpath)
    {
        if (string.IsNullOrEmpty(rootPath))
            throw new ArgumentNullException("rootPath");
        if (string.IsNullOrEmpty(subpath))
            throw new ArgumentNulLException("subpath");
        Contract.EndContractBlock();
    
        return subath.StartsWith(rootPath, StringComparison.OrdinalIgnoreCase);
    }
    
    0 讨论(0)
提交回复
热议问题